f61f8923411e006cc0176aa1022e800667da1429
[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 openerp import netsvc
25 from openerp.osv import fields, osv
26 from openerp.tools.translate import _
27
28 import openerp.addons.decimal_precision as dp
29
30 def _employee_get(obj, cr, uid, context=None):
31     if context is None:
32         context = {}
33     ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
34     if ids:
35         return ids[0]
36     return False
37
38 class hr_expense_expense(osv.osv):
39
40     def copy(self, cr, uid, id, default=None, context=None):
41         if context is None:
42             context = {}
43         if not default: default = {}
44         default.update({'voucher_id': False, 'date_confirm': False, 'date_valid': False, 'user_valid': False})
45         return super(hr_expense_expense, self).copy(cr, uid, id, default, context=context)
46
47     def _amount(self, cr, uid, ids, field_name, arg, context=None):
48         res= {}
49         for expense in self.browse(cr, uid, ids, context=context):
50             total = 0.0
51             for line in expense.line_ids:
52                 total += line.unit_amount * line.unit_quantity
53             res[expense.id] = total
54         return res
55
56     def _get_currency(self, cr, uid, context=None):
57         user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0]
58         if user.company_id:
59             return user.company_id.currency_id.id
60         else:
61             return self.pool.get('res.currency').search(cr, uid, [('rate','=',1.0)], context=context)[0]
62
63     _name = "hr.expense.expense"
64     _inherit = ['mail.thread']
65     _description = "Expense"
66     _order = "id desc"
67     _track = {
68         'state': {
69             'hr_expense.mt_expense_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'accepted',
70             'hr_expense.mt_expense_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancelled',
71             'hr_expense.mt_expense_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
72         },
73     }
74
75     _columns = {
76         'name': fields.char('Description', size=128, required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
77         'id': fields.integer('Sheet ID', readonly=True),
78         'date': fields.date('Date', select=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
79         'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."),
80         'employee_id': fields.many2one('hr.employee', "Employee", required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
81         'user_id': fields.many2one('res.users', 'User', required=True),
82         '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."),
83         '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."),
84         'user_valid': fields.many2one('res.users', 'Validation By', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
85         'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
86         'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
87         'note': fields.text('Note'),
88         'amount': fields.function(_amount, string='Total Amount', digits_compute=dp.get_precision('Account')),
89         'voucher_id': fields.many2one('account.voucher', "Employee's Receipt"),
90         'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
91         'department_id':fields.many2one('hr.department','Department', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
92         'company_id': fields.many2one('res.company', 'Company', required=True),
93         'state': fields.selection([
94             ('draft', 'New'),
95             ('cancelled', 'Refused'),
96             ('confirm', 'Waiting Approval'),
97             ('accepted', 'Approved'),
98             ('done', 'Done'),
99             ],
100             'Status', readonly=True, track_visibility='onchange',
101             help='When the expense request is created the status is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the status is \'Waiting Confirmation\'.\
102             \nIf the admin accepts it, the status is \'Accepted\'.\n If a receipt is made for the expense request, the status is \'Done\'.'),
103     }
104     _defaults = {
105         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.employee', context=c),
106         'date': fields.date.context_today,
107         'state': 'draft',
108         'employee_id': _employee_get,
109         'user_id': lambda cr, uid, id, c={}: id,
110         'currency_id': _get_currency,
111     }
112
113     def unlink(self, cr, uid, ids, context=None):
114         for rec in self.browse(cr, uid, ids, context=context):
115             if rec.state != 'draft':
116                 raise osv.except_osv(_('Warning!'),_('You can only delete draft expenses!'))
117         return super(hr_expense_expense, self).unlink(cr, uid, ids, context)
118
119     def onchange_currency_id(self, cr, uid, ids, currency_id=False, company_id=False, context=None):
120         res =  {'value': {'journal_id': False}}
121         journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase'), ('currency','=',currency_id), ('company_id', '=', company_id)], context=context)
122         if journal_ids:
123             res['value']['journal_id'] = journal_ids[0]
124         return res
125
126     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
127         emp_obj = self.pool.get('hr.employee')
128         department_id = False
129         company_id = False
130         if employee_id:
131             employee = emp_obj.browse(cr, uid, employee_id, context=context)
132             department_id = employee.department_id.id
133             company_id = employee.company_id.id
134         return {'value': {'department_id': department_id, 'company_id': company_id}}
135
136     def expense_confirm(self, cr, uid, ids, context=None):
137         for expense in self.browse(cr, uid, ids):
138             if expense.employee_id and expense.employee_id.parent_id.user_id:
139                 self.message_subscribe_users(cr, uid, [expense.id], user_ids=[expense.employee_id.parent_id.user_id.id])
140         return self.write(cr, uid, ids, {'state': 'confirm', 'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
141
142     def expense_accept(self, cr, uid, ids, context=None):
143         return self.write(cr, uid, ids, {'state': 'accepted', 'date_valid': time.strftime('%Y-%m-%d'), 'user_valid': uid}, context=context)
144
145     def expense_canceled(self, cr, uid, ids, context=None):
146         return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
147
148
149     def account_move_get(self, cr, uid, expense_id, journal_id, context=None):
150         '''
151         This method prepare the creation of the account move related to the given expense.
152
153         :param expense_id: Id of voucher for which we are creating account_move.
154         :return: mapping between fieldname and value of account move to create
155         :rtype: dict
156         '''
157         
158         
159         #Search for the period corresponding with confirmation date
160         expense_brw = self.browse(cr,uid,expense_id,context)
161         period_obj = self.pool.get('account.period')
162         company_id = expense_brw.company_id.id
163         ctx = context
164         ctx.update({'company_id': company_id})
165         date = expense_brw.date_confirm
166         pids = period_obj.find(cr, uid, date, context=ctx)
167         try:
168             period_id = pids[0]
169         except: 
170             raise osv.except_osv(_('Error! '), 
171                         _('Please define periods!'))
172         period = period_obj.browse(cr, uid, period_id, context=context)
173         
174         seq_obj = self.pool.get('ir.sequence')
175
176         journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
177         if journal.sequence_id:
178             if not journal.sequence_id.active:
179                 raise osv.except_osv(_('Configuration Error !'),
180                     _('Please activate the sequence of selected journal !'))
181             c = dict(context)
182             c.update({'fiscalyear_id': period.fiscalyear_id.id})
183             name = seq_obj.next_by_id(cr, uid, journal.sequence_id.id, context=c)
184         else:
185             raise osv.except_osv(_('Error!'),
186                         _('Please define a sequence on the journal.'))
187         #Look for the next expense number
188         ref = seq_obj.get(cr, uid, 'hr.expense.invoice')
189
190         move = {
191             'name': name,
192             'journal_id': journal_id,
193             'narration': '',
194             'date': expense_brw.date_confirm,
195             'ref': ref,
196             'period_id': period_id,
197         }
198         return move
199
200     def line_get_convert(self, cr, uid, x, part, date, context=None):
201         return {
202             'date_maturity': x.get('date_maturity', False),
203             'partner_id': part.id,
204             'name': x['name'][:64],
205             'date': date,
206             'debit': x['price']>0 and x['price'],
207             'credit': x['price']<0 and -x['price'],
208             'account_id': x['account_id'],
209             'analytic_lines': x.get('analytic_lines', False),
210             'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
211             'currency_id': x.get('currency_id', False),
212             'tax_code_id': x.get('tax_code_id', False),
213             'tax_amount': x.get('tax_amount', False),
214             'ref': x.get('ref', False),
215             'quantity': x.get('quantity',1.00),
216             'product_id': x.get('product_id', False),
217             'product_uom_id': x.get('uos_id', False),
218             'analytic_account_id': x.get('account_analytic_id', False),
219         }
220
221     def compute_expense_totals(self, cr, uid, inv, company_currency, ref, invoice_move_lines, context=None):
222         if context is None:
223             context={}
224         total = 0
225         total_currency = 0
226         cur_obj = self.pool.get('res.currency')
227         for i in invoice_move_lines:
228             if inv.currency_id.id != company_currency:
229                 context.update({'date': inv.date_confirm or time.strftime('%Y-%m-%d')})
230                 i['currency_id'] = inv.currency_id.id
231                 i['amount_currency'] = i['price']
232                 i['price'] = cur_obj.compute(cr, uid, inv.currency_id.id,
233                         company_currency, i['price'],
234                         context=context)
235             else:
236                 i['amount_currency'] = False
237                 i['currency_id'] = False
238             i['ref'] = ref
239             total -= i['price']
240             total_currency -= i['amount_currency'] or i['price']
241         return total, total_currency, invoice_move_lines
242
243
244     def action_move_create(self, cr, uid, ids, context=None):
245         property_obj = self.pool.get('ir.property')
246         sequence_obj = self.pool.get('ir.sequence')
247         analytic_journal_obj = self.pool.get('account.analytic.journal')
248         account_journal = self.pool.get('account.journal')
249         voucher_obj = self.pool.get('account.voucher')
250         currency_obj = self.pool.get('res.currency')
251         ait_obj = self.pool.get('account.invoice.tax')
252         move_obj = self.pool.get('account.move')
253         if context is None:
254             context = {}
255         for exp in self.browse(cr, uid, ids, context=context):
256             company_id = exp.company_id.id
257             lines = []
258             total = 0.0
259             ctx = context.copy()
260             ctx.update({'date': exp.date})
261             journal = False
262             if exp.journal_id:
263                 journal = exp.journal_id
264             else:
265                 journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
266                 if journal_id:
267                     journal = account_journal.browse(cr, uid, journal_id, context=context)
268             if not journal:
269                 raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
270             if not journal.sequence_id:
271                 raise osv.except_osv(_('Error!'), _('Please define sequence on the journal related to this invoice.'))
272             company_currency = exp.company_id.currency_id.id
273             current_currency = exp.currency_id
274 #            for line in exp.line_ids: 
275 #                if line.product_id:
276 #                    acc = line.product_id.property_account_expense
277 #                    if not acc:
278 #                        acc = line.product_id.categ_id.property_account_expense_categ
279 #                else:
280 #                    acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
281 #                    if not acc:
282 #                        raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
283 #                total_amount = line.total_amount
284 #                if journal.currency:
285 #                    if exp.currency_id != journal.currency:
286 #                        total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
287 #                elif exp.currency_id != exp.company_id.currency_id:
288 #                    total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
289 #                lines.append((0, False, {
290 #                    'name': line.name,
291 #                    'account_id': acc.id,
292 #                    'account_analytic_id': line.analytic_account.id,
293 #                    'amount': total_amount,
294 #                    'type': 'dr'
295 #                }))
296 #                total += total_amount
297             if not exp.employee_id.address_home_id:
298                 raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
299             acc = exp.employee_id.address_home_id.property_account_payable.id
300             
301             #From action_move_line_create of voucher
302             move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, journal.id, context=context), context=context)
303             move = move_obj.browse(cr, uid, move_id, context=context)
304             #iml = self._get_analytic_lines
305             
306             # within: iml = self.pool.get('account.invoice.line').move_line_get
307             iml = self.move_line_get(cr, uid, exp.id, context=context)
308             
309             
310             diff_currency_p = exp.currency_id.id <> company_currency
311             # create one move line for the total
312             total = 0
313             total_currency = 0
314             total, total_currency, iml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, iml, context=ctx)
315             
316             
317             #Need to have counterline: 
318             
319             iml.append({
320                     'type':'dest', 
321                     'name':'/',
322                     'price':total, 
323                     'account_id': acc, 
324                     'date_maturity': exp.date_confirm, 
325                     'amount_currency': diff_currency_p and total_currency or False, 
326                     'currency_id': diff_currency_p and exp.currency_id.id or False, 
327                     'ref': exp.name
328                     })
329             
330             line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.user_id.partner_id, exp.date_confirm, context=ctx)),iml)
331             move_obj.write(cr, uid, [move_id], {'line_id':line}, context=ctx)
332             self.write(cr, uid, ids, {'account_move_id':move_id, 'state':'done'}, context=context)
333             
334             
335             #compute_taxes = ait_obj.compute(cr, uid, , context=context)
336             
337             
338             
339 #    return {
340 #        'type':'src', 
341 #        'name':line.name.split('\n')[0][:64], 
342 #        'price_unit':line.price_unit, 
343 #        'quantity':line.quantity, 
344 #        'price':line.price_subtotal, 
345 #        'account_id':line.account_id.id, 
346 #        'product_id':line.product_id.id, 
347 #        'uos_id':line.uos_id.id, 
348 #        'account_analytic_id':line.account_analytic_id.id, 
349 #        'taxes':line.invoice_line_tax_id}
350
351
352     def move_line_get(self, cr, uid, expense_id, context=None):
353         res = []
354         tax_obj = self.pool.get('account.tax')
355         cur_obj = self.pool.get('res.currency')
356         if context is None:
357             context = {}
358         exp = self.browse(cr, uid, expense_id, context=context)
359         company_currency = exp.company_id.currency_id.id
360
361         for line in exp.line_ids:
362             mres = self.move_line_get_item(cr, uid, line, context)
363             if not mres:
364                 continue
365             res.append(mres)
366             tax_code_found= False
367             
368             #Calculate tax according to default tax on product
369             
370             #Taken from product_id_onchange in account.invoice
371             if line.product_id:
372                 fposition_id = False
373                 fpos_obj = self.pool.get('account.fiscal.position')
374                 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
375                 product = line.product_id
376                 taxes = product.supplier_taxes_id
377                 #If taxes are not related to the product, maybe they are in the account
378                 if not taxes:
379                     a = product.property_account_expense.id #Why is not there a check here?
380                     if not a:
381                         a = product.categ_id.property_account_expense_categ.id
382                     a = fpos_obj.map_account(cr, uid, fpos, a)
383                     taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
384                 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
385             if not taxes:
386                 taxes = []
387             #Calculating tax on the line and creating move?
388             for tax in tax_obj.compute_all(cr, uid, taxes,
389                     line.unit_amount ,
390                     line.unit_quantity, line.product_id,
391                     exp.user_id.partner_id)['taxes']:
392                 tax_code_id = tax['base_code_id']
393                 tax_amount = line.total_amount * tax['base_sign']
394                 if tax_code_found:
395                     if not tax_code_id:
396                         continue
397                     res.append(self.move_line_get_item(cr, uid, line, context))
398                     res[-1]['price'] = 0.0
399                     res[-1]['account_analytic_id'] = False
400                 elif not tax_code_id:
401                     continue
402                 tax_code_found = True
403                 res[-1]['tax_code_id'] = tax_code_id
404                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
405                 
406                 #Will create the tax here as we don't have the access 
407                 assoc_tax = {
408                              'type':'tax',
409                              'name':tax['name'],
410                              'price_unit': tax['price_unit'],
411                              'quantity': 1,
412                              'price':  tax['amount'] * tax['base_sign'] or 0.0,
413                              'account_id': tax['account_collected_id'],
414                              'tax_code_id': tax['tax_code_id'],
415                              'tax_amount': tax['amount'] * tax['base_sign'],
416                              }
417                 res.append(assoc_tax)
418         return res
419
420     def move_line_get_item(self, cr, uid, line, context=None):
421         company = line.expense_id.company_id
422         property_obj = self.pool.get('ir.property')
423         if line.product_id:
424             acc = line.product_id.property_account_expense
425             if not acc:
426                 acc = line.product_id.categ_id.property_account_expense_categ
427         else:
428             acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
429             if not acc:
430                 raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
431         return {
432             'type':'src',
433             'name': line.name.split('\n')[0][:64],
434             'price_unit':line.unit_amount,
435             'quantity':line.unit_quantity,
436             'price':line.total_amount,
437             'account_id':acc.id,
438             'product_id':line.product_id.id,
439             'uos_id':line.uom_id.id,
440             'account_analytic_id':line.analytic_account.id,
441             #'taxes':line.invoice_line_tax_id,
442         }
443
444
445
446
447
448     def action_receipt_create(self, cr, uid, ids, context=None):
449         property_obj = self.pool.get('ir.property')
450         sequence_obj = self.pool.get('ir.sequence')
451         analytic_journal_obj = self.pool.get('account.analytic.journal')
452         account_journal = self.pool.get('account.journal')
453         voucher_obj = self.pool.get('account.voucher')
454         currency_obj = self.pool.get('res.currency')
455         wkf_service = netsvc.LocalService("workflow")
456         if context is None:
457             context = {}
458         for exp in self.browse(cr, uid, ids, context=context):
459             company_id = exp.company_id.id
460             lines = []
461             total = 0.0
462             ctx = context.copy()
463             ctx.update({'date': exp.date})
464             journal = False
465             if exp.journal_id:
466                 journal = exp.journal_id
467             else:
468                 journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
469                 if journal_id:
470                     journal = account_journal.browse(cr, uid, journal_id, context=context)
471             if not journal:
472                raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
473             for line in exp.line_ids:
474                 if line.product_id:
475                     acc = line.product_id.property_account_expense
476                     if not acc:
477                         acc = line.product_id.categ_id.property_account_expense_categ
478                 else:
479                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
480                     if not acc:
481                         raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
482                 total_amount = line.total_amount
483                 if journal.currency:
484                     if exp.currency_id != journal.currency:
485                         total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
486                 elif exp.currency_id != exp.company_id.currency_id:
487                     total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
488                 lines.append((0, False, {
489                     'name': line.name,
490                     'account_id': acc.id,
491                     'account_analytic_id': line.analytic_account.id,
492                     'amount': total_amount,
493                     'type': 'dr'
494                 }))
495                 total += total_amount
496             if not exp.employee_id.address_home_id:
497                 raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
498             acc = exp.employee_id.address_home_id.property_account_payable.id
499             voucher = {
500                 'name': exp.name or '/',
501                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
502                 'account_id': acc,
503                 'type': 'purchase',
504                 'partner_id': exp.employee_id.address_home_id.id,
505                 'company_id': company_id,
506                 'line_ids': lines,
507                 'amount': total,
508                 'journal_id': journal.id,
509             }
510             if journal and not journal.analytic_journal_id:
511                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
512                 if analytic_journal_ids:
513                     account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
514             voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
515             self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
516         return True
517     
518     def action_view_receipt(self, cr, uid, ids, context=None):
519         '''
520         This function returns an action that display existing receipt of given expense ids.
521         '''
522         assert len(ids) == 1, 'This option should only be used for a single id at a time'
523         voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
524         res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
525         result = {
526             'name': _('Expense Receipt'),
527             'view_type': 'form',
528             'view_mode': 'form',
529             'view_id': res and res[1] or False,
530             'res_model': 'account.voucher',
531             'type': 'ir.actions.act_window',
532             'nodestroy': True,
533             'target': 'current',
534             'res_id': voucher_id,
535         }
536         return result
537
538 hr_expense_expense()
539
540 class product_product(osv.osv):
541     _inherit = "product.product"
542     _columns = {
543         'hr_expense_ok': fields.boolean('Can be Expensed', help="Specify if the product can be selected in an HR expense line."),
544     }
545
546 product_product()
547
548 class hr_expense_line(osv.osv):
549     _name = "hr.expense.line"
550     _description = "Expense Line"
551
552     def _amount(self, cr, uid, ids, field_name, arg, context=None):
553         if not ids:
554             return {}
555         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),))
556         res = dict(cr.fetchall())
557         return res
558
559     def _get_uom_id(self, cr, uid, context=None):
560         result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product', 'product_uom_unit')
561         return result and result[1] or False
562
563     _columns = {
564         'name': fields.char('Expense Note', size=128, required=True),
565         'date_value': fields.date('Date', required=True),
566         'expense_id': fields.many2one('hr.expense.expense', 'Expense', ondelete='cascade', select=True),
567         'total_amount': fields.function(_amount, string='Total', digits_compute=dp.get_precision('Account')),
568         'unit_amount': fields.float('Unit Price', digits_compute=dp.get_precision('Product Price')),
569         'unit_quantity': fields.float('Quantities', digits_compute= dp.get_precision('Product Unit of Measure')),
570         'product_id': fields.many2one('product.product', 'Product', domain=[('hr_expense_ok','=',True)]),
571         'uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True),
572         'description': fields.text('Description'),
573         'analytic_account': fields.many2one('account.analytic.account','Analytic account'),
574         'ref': fields.char('Reference', size=32),
575         'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of expense lines."),
576         }
577     _defaults = {
578         'unit_quantity': 1,
579         'date_value': lambda *a: time.strftime('%Y-%m-%d'),
580         'uom_id': _get_uom_id,
581     }
582     _order = "sequence, date_value desc"
583
584     def onchange_product_id(self, cr, uid, ids, product_id, context=None):
585         res = {}
586         if product_id:
587             product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
588             res['name'] = product.name
589             amount_unit = product.price_get('standard_price')[product.id]
590             res['unit_amount'] = amount_unit
591             res['uom_id'] = product.uom_id.id
592         return {'value': res}
593
594     def onchange_uom(self, cr, uid, ids, product_id, uom_id, context=None):
595         res = {'value':{}}
596         if not uom_id or not product_id:
597             return res
598         product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
599         uom = self.pool.get('product.uom').browse(cr, uid, uom_id, context=context)
600         if uom.category_id.id != product.uom_id.category_id.id:
601             res['warning'] = {'title': _('Warning'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure')}
602             res['value'].update({'uom_id': product.uom_id.id})
603         return res
604
605 hr_expense_line()
606
607 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: