[IMP] small fix
[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({'voucher_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         res= {}
48         for expense in self.browse(cr, uid, ids, context=context):
49             total = 0.0
50             for line in expense.line_ids:
51                 total += line.unit_amount * line.unit_quantity
52             res[expense.id] = total
53         return res
54
55     def _get_currency(self, cr, uid, context=None):
56         user = self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0]
57         if user.company_id:
58             return user.company_id.currency_id.id
59         else:
60             return self.pool.get('res.currency').search(cr, uid, [('rate','=',1.0)], context=context)[0]
61
62     _name = "hr.expense.expense"
63     _inherit = ['mail.thread']
64     _description = "Expense"
65     _order = "id desc"
66     _columns = {
67         'name': fields.char('Description', size=128, required=True),
68         'id': fields.integer('Sheet ID', readonly=True),
69         'date': fields.date('Date', select=True),
70         'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."),
71         'employee_id': fields.many2one('hr.employee', "Employee", required=True),
72         'user_id': fields.many2one('res.users', 'User', required=True),
73         '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."),
74         '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."),
75         'user_valid': fields.many2one('res.users', 'Validation User'),
76         'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
77         'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
78         'note': fields.text('Note'),
79         'amount': fields.function(_amount, string='Total Amount'),
80         'voucher_id': fields.many2one('account.voucher', "Employee's Receipt"),
81         'currency_id': fields.many2one('res.currency', 'Currency', required=True),
82         'department_id':fields.many2one('hr.department','Department'),
83         'company_id': fields.many2one('res.company', 'Company', required=True),
84         'state': fields.selection([
85             ('draft', 'New'),
86             ('cancelled', 'Refused'),
87             ('confirm', 'Waiting Approval'),
88             ('accepted', 'Approved'),
89             ('done', 'Done'),
90             ],
91             'Status', readonly=True, 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\'.\
92             \nIf the admin accepts it, the status is \'Accepted\'.\n If a receipt is made for the expense request, the status is \'Done\'.'),
93     }
94     _defaults = {
95         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.employee', context=c),
96         'date': fields.date.context_today,
97         'state': 'draft',
98         'employee_id': _employee_get,
99         'user_id': lambda cr, uid, id, c={}: id,
100         'currency_id': _get_currency,
101     }
102
103     def onchange_currency_id(self, cr, uid, ids, currency_id=False, company_id=False, context=None):
104         res =  {'value': {'journal_id': False}}
105         journal = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase'), ('currency','=',currency_id), ('company_id', '=', company_id)], context=context)
106         if journal:
107             res['value']['journal_id'] = journal[0]
108         return res
109
110     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
111         emp_obj = self.pool.get('hr.employee')
112         department_id = False
113         company_id = False
114         if employee_id:
115             employee = emp_obj.browse(cr, uid, employee_id, context=context)
116             department_id = employee.department_id.id
117             company_id = employee.company_id.id
118         return {'value': {'department_id': department_id, 'company_id': company_id}}
119
120     def expense_confirm(self, cr, uid, ids, *args):
121         self.write(cr, uid, ids, {
122             'state':'confirm',
123             'date_confirm': time.strftime('%Y-%m-%d')
124         })
125         return True
126
127     def expense_accept(self, cr, uid, ids, *args):
128         self.write(cr, uid, ids, {
129             'state':'accepted',
130             'date_valid':time.strftime('%Y-%m-%d'),
131             'user_valid': uid,
132             })
133         return True
134
135     def expense_canceled(self, cr, uid, ids, *args):
136         self.write(cr, uid, ids, {'state':'cancelled'})
137         return True
138
139     def action_receipt_create(self, cr, uid, ids, context=None):
140         property_obj = self.pool.get('ir.property')
141         sequence_obj = self.pool.get('ir.sequence')
142         analytic_journal_obj = self.pool.get('account.analytic.journal')
143         account_journal = self.pool.get('account.journal')
144         voucher_obj = self.pool.get('account.voucher')
145         currency_obj = self.pool.get('res.currency')
146         wkf_service = netsvc.LocalService("workflow")
147         if context is None:
148             context = {}
149         for exp in self.browse(cr, uid, ids, context=context):
150             company_id = exp.company_id.id
151             lines = []
152             total = 0.0
153             ctx = context.copy()
154             ctx.update({'date': exp.date})
155             journal = False
156             if exp.journal_id:
157                 journal = exp.journal_id
158             else:
159                 journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
160                 if journal_id:
161                     journal = account_journal.browse(cr, uid, journal_id, context=context)
162             for line in exp.line_ids:
163                 if line.product_id:
164                     acc = line.product_id.product_tmpl_id.property_account_expense
165                     if not acc:
166                         acc = line.product_id.categ_id.property_account_expense_categ
167                 else:
168                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
169                     if not acc:
170                         raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
171                 total_amount = 0.0
172                 if journal.currency and exp.currency_id.id != journal.currency.id:
173                     total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, line.total_amount, context=ctx)
174                 else:
175                     total_amount = line.total_amount
176                 lines.append((0, False, {
177                     'name': line.name,
178                     'account_id': acc.id,
179                     'account_analytic_id': line.analytic_account.id,
180                     'amount': total_amount,
181                     'type': 'dr'
182                 }))
183                 total += total_amount
184             if not exp.employee_id.address_home_id:
185                 raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
186             acc = exp.employee_id.address_home_id.property_account_payable.id
187             voucher = {
188                 'name': exp.name,
189                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
190                 'account_id': acc,
191                 'type': 'purchase',
192                 'partner_id': exp.employee_id.address_home_id.id,
193                 'company_id': company_id,
194                 'currency_id': exp.currency_id.id,
195                 'line_ids': lines,
196                 'amount': total,
197                 'journal_id': journal.id,
198             }
199             if journal and not journal.analytic_journal_id:
200                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
201                 if analytic_journal_ids:
202                     account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
203             voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
204             wkf_service.trg_validate(uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
205             self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
206         return True
207     
208     def action_view_receipt(self, cr, uid, ids, context=None):
209         '''
210         This function returns an action that display existing receipt of given expense ids.
211         '''
212         assert len(ids) == 1, 'This option should only be used for a single id at a time'
213         voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
214         res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
215         result = {
216             'name': _('Expense Receipt'),
217             'view_type': 'form',
218             'view_mode': 'form',
219             'view_id': res and res[1] or False,
220             'res_model': 'account.voucher',
221             'type': 'ir.actions.act_window',
222             'nodestroy': True,
223             'target': 'current',
224             'res_id': voucher_id,
225         }
226         return result
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', 'Unit of Measure'),
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: