[MERGE] lp881356
[odoo/odoo.git] / addons / hr_expense / hr_expense.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23
24 from osv import fields, osv
25 from tools.translate import _
26 import decimal_precision as dp
27 import netsvc
28
29 def _employee_get(obj, cr, uid, context=None):
30     if context is None:
31         context = {}
32     ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
33     if ids:
34         return ids[0]
35     return False
36
37 class hr_expense_expense(osv.osv):
38
39     def copy(self, cr, uid, id, default=None, context=None):
40         if context is None:
41             context = {}
42         if not default: default = {}
43         default.update({'invoice_id': False, 'date_confirm': False, 'date_valid': False, 'user_valid': False})
44         return super(hr_expense_expense, self).copy(cr, uid, id, default, context=context)
45
46     def _amount(self, cr, uid, ids, field_name, arg, context=None):
47         cr.execute("SELECT s.id,COALESCE(SUM(l.unit_amount*l.unit_quantity),0) AS amount FROM hr_expense_expense s LEFT OUTER JOIN hr_expense_line l ON (s.id=l.expense_id) WHERE s.id IN %s GROUP BY s.id ", (tuple(ids),))
48         res = dict(cr.fetchall())
49         return res
50
51     def _get_currency(self, cr, uid, context=None):
52         user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0]
53         if user.company_id:
54             return user.company_id.currency_id.id
55         else:
56             return self.pool.get('res.currency').search(cr, uid, [('rate','=',1.0)], context=context)[0]
57
58     _name = "hr.expense.expense"
59     _description = "Expense"
60     _order = "id desc"
61     _columns = {
62         'name': fields.char('Description', size=128, required=True),
63         'id': fields.integer('Sheet ID', readonly=True),
64         'ref': fields.char('Reference', size=32),
65         'date': fields.date('Date', select=True),
66         'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is invoiced"),
67         'employee_id': fields.many2one('hr.employee', "Employee", required=True),
68         'user_id': fields.many2one('res.users', 'User', required=True),
69         'date_confirm': fields.date('Confirmation Date', select=True, help = "Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."),
70         'date_valid': fields.date('Validation Date', select=True, help = "Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."),
71         'user_valid': fields.many2one('res.users', 'Validation User'),
72         'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
73         'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
74         'note': fields.text('Note'),
75         'amount': fields.function(_amount, string='Total Amount'),
76         'invoice_id': fields.many2one('account.invoice', "Employee's Invoice"),
77         'currency_id': fields.many2one('res.currency', 'Currency', required=True),
78         'department_id':fields.many2one('hr.department','Department'),
79         'company_id': fields.many2one('res.company', 'Company', required=True),
80         'state': fields.selection([
81             ('draft', 'New'),
82             ('confirm', 'Waiting Approval'),
83             ('accepted', 'Approved'),
84             ('invoiced', 'Invoiced'),
85             ('paid', 'Reimbursed'),
86             ('cancelled', 'Refused')],
87             'State', readonly=True, help='When the expense request is created the state is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the state is \'Waiting Confirmation\'.\
88             \nIf the admin accepts it, the state is \'Accepted\'.\n If an invoice is made for the expense request, the state is \'Invoiced\'.\n If the expense is paid to user, the state is \'Reimbursed\'.'),
89     }
90     _defaults = {
91         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.employee', context=c),
92         'date': fields.date.context_today,
93         'state': 'draft',
94         'employee_id': _employee_get,
95         'user_id': lambda cr, uid, id, c={}: id,
96         'currency_id': _get_currency,
97     }
98
99     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
100         emp_obj = self.pool.get('hr.employee')
101         department_id = False
102         company_id = False
103         if employee_id:
104             employee = emp_obj.browse(cr, uid, employee_id, context=context)
105             department_id = employee.department_id.id
106             company_id = employee.company_id.id
107         return {'value': {'department_id': department_id, 'company_id': company_id}}
108
109     def expense_confirm(self, cr, uid, ids, *args):
110         self.write(cr, uid, ids, {
111             'state':'confirm',
112             'date_confirm': time.strftime('%Y-%m-%d')
113         })
114         return True
115
116     def expense_accept(self, cr, uid, ids, *args):
117         self.write(cr, uid, ids, {
118             'state':'accepted',
119             'date_valid':time.strftime('%Y-%m-%d'),
120             'user_valid': uid,
121             })
122         return True
123
124     def expense_canceled(self, cr, uid, ids, *args):
125         self.write(cr, uid, ids, {'state':'cancelled'})
126         return True
127
128     def expense_paid(self, cr, uid, ids, *args):
129         self.write(cr, uid, ids, {'state':'paid'})
130         return True
131
132     def invoice(self, cr, uid, ids, context=None):
133         wf_service = netsvc.LocalService("workflow")
134         mod_obj = self.pool.get('ir.model.data')
135         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
136         inv_ids = []
137         for id in ids:
138             wf_service.trg_validate(uid, 'hr.expense.expense', id, 'invoice', cr)
139             inv_ids.append(self.browse(cr, uid, id).invoice_id.id)
140         return {
141             'name': _('Supplier Invoices'),
142             'view_type': 'form',
143             'view_mode': 'form',
144             'view_id': [res and res[1] or False],
145             'res_model': 'account.invoice',
146             'context': "{'type':'out_invoice', 'journal_type': 'purchase'}",
147             'type': 'ir.actions.act_window',
148             'nodestroy': True,
149             'target': 'current',
150             'res_id': inv_ids and inv_ids[0] or False,
151         }
152
153     def action_invoice_create(self, cr, uid, ids):
154         res = False
155         invoice_obj = self.pool.get('account.invoice')
156         property_obj = self.pool.get('ir.property')
157         sequence_obj = self.pool.get('ir.sequence')
158         analytic_journal_obj = self.pool.get('account.analytic.journal')
159         account_journal = self.pool.get('account.journal')
160         for exp in self.browse(cr, uid, ids):
161             company_id = exp.company_id.id
162             lines = []
163             for l in exp.line_ids:
164                 tax_id = []
165                 if l.product_id:
166                     acc = l.product_id.product_tmpl_id.property_account_expense
167                     if not acc:
168                         acc = l.product_id.categ_id.property_account_expense_categ
169                     tax_id = [x.id for x in l.product_id.supplier_taxes_id]
170                 else:
171                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
172                     if not acc:
173                         raise osv.except_osv(_('Error !'), _('Please configure Default Expense account for Product purchase, `property_account_expense_categ`'))
174                 lines.append((0, False, {
175                     'name': l.name,
176                     'account_id': acc.id,
177                     'price_unit': l.unit_amount,
178                     'quantity': l.unit_quantity,
179                     'uos_id': l.uom_id.id,
180                     'product_id': l.product_id and l.product_id.id or False,
181                     'invoice_line_tax_id': tax_id and [(6, 0, tax_id)] or False,
182                     'account_analytic_id': l.analytic_account.id,
183                 }))
184             if not exp.employee_id.address_home_id:
185                 raise osv.except_osv(_('Error !'), _('The employee must have a Home address.'))
186             if not exp.employee_id.address_home_id.partner_id:
187                 raise osv.except_osv(_('Error !'), _("The employee's home address must have a partner linked."))
188             acc = exp.employee_id.address_home_id.partner_id.property_account_payable.id
189             payment_term_id = exp.employee_id.address_home_id.partner_id.property_payment_term.id
190             inv = {
191                 'name': exp.name,
192                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
193                 'account_id': acc,
194                 'type': 'in_invoice',
195                 'partner_id': exp.employee_id.address_home_id.partner_id.id,
196                 'address_invoice_id': exp.employee_id.address_home_id.id,
197                 'address_contact_id': exp.employee_id.address_home_id.id,
198                 'company_id': company_id,
199                 'origin': exp.name,
200                 'invoice_line': lines,
201                 'currency_id': exp.currency_id.id,
202                 'payment_term': payment_term_id,
203                 'fiscal_position': exp.employee_id.address_home_id.partner_id.property_account_position.id
204             }
205             if payment_term_id:
206                 to_update = invoice_obj.onchange_payment_term_date_invoice(cr, uid, [], payment_term_id, None)
207                 if to_update:
208                     inv.update(to_update['value'])
209             journal = False
210             if exp.journal_id:
211                 inv['journal_id']=exp.journal_id.id
212                 journal = exp.journal_id
213             else:
214                 journal_id = invoice_obj._get_journal(cr, uid, context={'type': 'in_invoice', 'company_id': company_id})
215                 if journal_id:
216                     inv['journal_id'] = journal_id
217                     journal = account_journal.browse(cr, uid, journal_id)
218             if journal and not journal.analytic_journal_id:
219                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')])
220                 if analytic_journal_ids:
221                     account_journal.write(cr, uid, [journal.id],{'analytic_journal_id':analytic_journal_ids[0]})
222             inv_id = invoice_obj.create(cr, uid, inv, {'type': 'in_invoice'})
223             invoice_obj.button_compute(cr, uid, [inv_id], {'type': 'in_invoice'}, set_total=True)
224             self.write(cr, uid, [exp.id], {'invoice_id': inv_id, 'state': 'invoiced'})
225             res = inv_id
226         return res
227
228 hr_expense_expense()
229
230 class product_product(osv.osv):
231     _inherit = "product.product"
232     _columns = {
233         'hr_expense_ok': fields.boolean('Can Constitute an Expense', help="Determines if the product can be visible in the list of product within a selection from an HR expense sheet line."),
234     }
235
236     def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok):
237
238         if not hr_expense_ok:
239             return {}
240         data_obj = self.pool.get('ir.model.data')
241         cat_id = data_obj._get_id(cr, uid, 'hr_expense', 'cat_expense')
242         categ_id = data_obj.browse(cr, uid, cat_id).res_id
243         res = {'value' : {'type':'service','procure_method':'make_to_stock','supply_method':'buy','purchase_ok':True,'sale_ok' :False,'categ_id':categ_id }}
244         return res
245
246 product_product()
247
248 class hr_expense_line(osv.osv):
249     _name = "hr.expense.line"
250     _description = "Expense Line"
251
252     def _amount(self, cr, uid, ids, field_name, arg, context=None):
253         if not ids:
254             return {}
255         cr.execute("SELECT l.id,COALESCE(SUM(l.unit_amount*l.unit_quantity),0) AS amount FROM hr_expense_line l WHERE id IN %s GROUP BY l.id ",(tuple(ids),))
256         res = dict(cr.fetchall())
257         return res
258
259     _columns = {
260         'name': fields.char('Expense Note', size=128, required=True),
261         'date_value': fields.date('Date', required=True),
262         'expense_id': fields.many2one('hr.expense.expense', 'Expense', ondelete='cascade', select=True),
263         'total_amount': fields.function(_amount, string='Total', digits_compute=dp.get_precision('Account')),
264         'unit_amount': fields.float('Unit Price', digits_compute=dp.get_precision('Account')),
265         'unit_quantity': fields.float('Quantities' ),
266         'product_id': fields.many2one('product.product', 'Product', domain=[('hr_expense_ok','=',True)]),
267         'uom_id': fields.many2one('product.uom', 'UoM'),
268         'description': fields.text('Description'),
269         'analytic_account': fields.many2one('account.analytic.account','Analytic account'),
270         'ref': fields.char('Reference', size=32),
271         'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of expense lines."),
272         }
273     _defaults = {
274         'unit_quantity': 1,
275         'date_value': lambda *a: time.strftime('%Y-%m-%d'),
276     }
277     _order = "sequence, date_value desc"
278
279     def onchange_product_id(self, cr, uid, ids, product_id, uom_id, employee_id, context=None):
280         res = {}
281         if product_id:
282             product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
283             res['name'] = product.name
284             amount_unit = product.price_get('standard_price')[product.id]
285             res['unit_amount'] = amount_unit
286             if not uom_id:
287                 res['uom_id'] = product.uom_id.id
288         return {'value': res}
289
290 hr_expense_line()
291
292 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: