478b8120935ce130c24b8ea54fb6e7c09271e6e6
[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     _columns = {
61         'name': fields.char('Description', size=128, required=True),
62         'id': fields.integer('Sheet ID', readonly=True),
63         'ref': fields.char('Reference', size=32),
64         'date': fields.date('Date', select=True),
65         'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is invoiced"),
66         'employee_id': fields.many2one('hr.employee', "Employee", required=True),
67         'user_id': fields.many2one('res.users', 'User', required=True),
68         '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."),
69         '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."),
70         'user_valid': fields.many2one('res.users', 'Validation User'),
71         'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
72         'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
73         'note': fields.text('Note'),
74         'amount': fields.function(_amount, string='Total Amount'),
75         'invoice_id': fields.many2one('account.invoice', "Employee's Invoice"),
76         'currency_id': fields.many2one('res.currency', 'Currency', required=True),
77         'department_id':fields.many2one('hr.department','Department'),
78         'company_id': fields.many2one('res.company', 'Company', required=True),
79         'state': fields.selection([
80             ('draft', 'New'),
81             ('confirm', 'Waiting Approval'),
82             ('accepted', 'Approved'),
83             ('invoiced', 'Invoiced'),
84             ('paid', 'Reimbursed'),
85             ('cancelled', 'Refused')],
86             '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\'.\
87             \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\'.'),
88     }
89     _defaults = {
90         'date': lambda *a: time.strftime('%Y-%m-%d'),
91         'state': 'draft',
92         'employee_id': _employee_get,
93         'user_id': lambda cr, uid, id, c={}: id,
94         'currency_id': _get_currency,
95     }
96
97     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
98         emp_obj = self.pool.get('hr.employee')
99         department_id = False
100         company_id = False
101         if employee_id:
102             employee = emp_obj.browse(cr, uid, employee_id, context=context)
103             department_id = employee.department_id.id
104             company_id = employee.company_id.id
105         return {'value': {'department_id': department_id, 'company_id': company_id}}
106
107     def expense_confirm(self, cr, uid, ids, *args):
108         self.write(cr, uid, ids, {
109             'state':'confirm',
110             'date_confirm': time.strftime('%Y-%m-%d')
111         })
112         return True
113
114     def expense_accept(self, cr, uid, ids, *args):
115         self.write(cr, uid, ids, {
116             'state':'accepted',
117             'date_valid':time.strftime('%Y-%m-%d'),
118             'user_valid': uid,
119             })
120         return True
121
122     def expense_canceled(self, cr, uid, ids, *args):
123         self.write(cr, uid, ids, {'state':'cancelled'})
124         return True
125
126     def expense_paid(self, cr, uid, ids, *args):
127         self.write(cr, uid, ids, {'state':'paid'})
128         return True
129
130     def invoice(self, cr, uid, ids, context=None):
131         wf_service = netsvc.LocalService("workflow")
132         mod_obj = self.pool.get('ir.model.data')
133         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
134         inv_ids = []
135         for id in ids:
136             wf_service.trg_validate(uid, 'hr.expense.expense', id, 'invoice', cr)
137             inv_ids.append(self.browse(cr, uid, id).invoice_id.id)
138         return {
139             'name': _('Supplier Invoices'),
140             'view_type': 'form',
141             'view_mode': 'form',
142             'view_id': [res and res[1] or False],
143             'res_model': 'account.invoice',
144             'context': "{'type':'out_invoice', 'journal_type': 'purchase'}",
145             'type': 'ir.actions.act_window',
146             'nodestroy': True,
147             'target': 'current',
148             'res_id': inv_ids and inv_ids[0] or False,
149         }
150
151     def action_invoice_create(self, cr, uid, ids):
152         res = False
153         invoice_obj = self.pool.get('account.invoice')
154         property_obj = self.pool.get('ir.property')
155         sequence_obj = self.pool.get('ir.sequence')
156         analytic_journal_obj = self.pool.get('account.analytic.journal')
157         account_journal = self.pool.get('account.journal')
158         for exp in self.browse(cr, uid, ids):
159             company_id = exp.company_id.id
160             lines = []
161             for l in exp.line_ids:
162                 tax_id = []
163                 if l.product_id:
164                     acc = l.product_id.product_tmpl_id.property_account_expense
165                     if not acc:
166                         acc = l.product_id.categ_id.property_account_expense_categ
167                     tax_id = [x.id for x in l.product_id.supplier_taxes_id]
168                 else:
169                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
170                     if not acc:
171                         raise osv.except_osv(_('Error !'), _('Please configure Default Expense account for Product purchase, `property_account_expense_categ`'))
172                 lines.append((0, False, {
173                     'name': l.name,
174                     'account_id': acc.id,
175                     'price_unit': l.unit_amount,
176                     'quantity': l.unit_quantity,
177                     'uos_id': l.uom_id.id,
178                     'product_id': l.product_id and l.product_id.id or False,
179                     'invoice_line_tax_id': tax_id and [(6, 0, tax_id)] or False,
180                     'account_analytic_id': l.analytic_account.id,
181                 }))
182             if not exp.employee_id.address_home_id:
183                 raise osv.except_osv(_('Error !'), _('The employee must have a Home address.'))
184             if not exp.employee_id.address_home_id.partner_id:
185                 raise osv.except_osv(_('Error !'), _("The employee's home address must have a partner linked."))
186             acc = exp.employee_id.address_home_id.partner_id.property_account_payable.id
187             payment_term_id = exp.employee_id.address_home_id.partner_id.property_payment_term.id
188             inv = {
189                 'name': exp.name,
190                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
191                 'account_id': acc,
192                 'type': 'in_invoice',
193                 'partner_id': exp.employee_id.address_home_id.partner_id.id,
194                 'address_invoice_id': exp.employee_id.address_home_id.id,
195                 'address_contact_id': exp.employee_id.address_home_id.id,
196                 'company_id': company_id,
197                 'origin': exp.name,
198                 'invoice_line': lines,
199                 'currency_id': exp.currency_id.id,
200                 'payment_term': payment_term_id,
201                 'fiscal_position': exp.employee_id.address_home_id.partner_id.property_account_position.id
202             }
203             if payment_term_id:
204                 to_update = invoice_obj.onchange_payment_term_date_invoice(cr, uid, [], payment_term_id, None)
205                 if to_update:
206                     inv.update(to_update['value'])
207             journal = False
208             if exp.journal_id:
209                 inv['journal_id']=exp.journal_id.id
210                 journal = exp.journal_id
211             else:
212                 journal_id = invoice_obj._get_journal(cr, uid, context={'type': 'in_invoice', 'company_id': company_id})
213                 if journal_id:
214                     inv['journal_id'] = journal_id
215                     journal = account_journal.browse(cr, uid, journal_id)
216             if journal and not journal.analytic_journal_id:
217                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')])
218                 if analytic_journal_ids:
219                     account_journal.write(cr, uid, [journal.id],{'analytic_journal_id':analytic_journal_ids[0]})
220             inv_id = invoice_obj.create(cr, uid, inv, {'type': 'in_invoice'})
221             invoice_obj.button_compute(cr, uid, [inv_id], {'type': 'in_invoice'}, set_total=True)
222             self.write(cr, uid, [exp.id], {'invoice_id': inv_id, 'state': 'invoiced'})
223             res = inv_id
224         return res
225
226 hr_expense_expense()
227
228 class product_product(osv.osv):
229     _inherit = "product.product"
230     _columns = {
231         '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."),
232     }
233
234     def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok):
235
236         if not hr_expense_ok:
237             return {}
238         data_obj = self.pool.get('ir.model.data')
239         cat_id = data_obj._get_id(cr, uid, 'hr_expense', 'cat_expense')
240         categ_id = data_obj.browse(cr, uid, cat_id).res_id
241         res = {'value' : {'type':'service','procure_method':'make_to_stock','supply_method':'buy','purchase_ok':True,'sale_ok' :False,'categ_id':categ_id }}
242         return res
243
244 product_product()
245
246 class hr_expense_line(osv.osv):
247     _name = "hr.expense.line"
248     _description = "Expense Line"
249
250     def _amount(self, cr, uid, ids, field_name, arg, context=None):
251         if not ids:
252             return {}
253         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),))
254         res = dict(cr.fetchall())
255         return res
256
257     _columns = {
258         'name': fields.char('Expense Note', size=128, required=True),
259         'date_value': fields.date('Date', required=True),
260         'expense_id': fields.many2one('hr.expense.expense', 'Expense', ondelete='cascade', select=True),
261         'total_amount': fields.function(_amount, string='Total', digits_compute=dp.get_precision('Account')),
262         'unit_amount': fields.float('Unit Price', digits_compute=dp.get_precision('Account')),
263         'unit_quantity': fields.float('Quantities' ),
264         'product_id': fields.many2one('product.product', 'Product', domain=[('hr_expense_ok','=',True)]),
265         'uom_id': fields.many2one('product.uom', 'UoM'),
266         'description': fields.text('Description'),
267         'analytic_account': fields.many2one('account.analytic.account','Analytic account'),
268         'ref': fields.char('Reference', size=32),
269         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of expense lines."),
270         }
271     _defaults = {
272         'unit_quantity': 1,
273         'date_value': lambda *a: time.strftime('%Y-%m-%d'),
274     }
275     _order = "sequence, date_value desc"
276
277     def onchange_product_id(self, cr, uid, ids, product_id, uom_id, employee_id, context=None):
278         res = {}
279         if product_id:
280             product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
281             res['name'] = product.name
282             amount_unit = product.price_get('standard_price')[product.id]
283             res['unit_amount'] = amount_unit
284             if not uom_id:
285                 res['uom_id'] = product.uom_id.id
286         return {'value': res}
287
288 hr_expense_line()
289
290 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: