[IMP] hr_expense : Improved the code.
[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 receipted."),
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             ('receipted', 'Waiting Reimbursement'),
90             ('paid', 'Reimbursed')
91             ],
92             '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\'.\
93             \nIf the admin accepts it, the status is \'Accepted\'.\n If a receipt is made for the expense request, the status is \'Waiting Reimbursement\'.\n If the expense is paid to user, the status is \'Reimbursed\'.'),
94     }
95     _defaults = {
96         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.employee', context=c),
97         'date': fields.date.context_today,
98         'state': 'draft',
99         'employee_id': _employee_get,
100         'user_id': lambda cr, uid, id, c={}: id,
101         'currency_id': _get_currency,
102     }
103
104     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
105         emp_obj = self.pool.get('hr.employee')
106         department_id = False
107         company_id = False
108         if employee_id:
109             employee = emp_obj.browse(cr, uid, employee_id, context=context)
110             department_id = employee.department_id.id
111             company_id = employee.company_id.id
112         return {'value': {'department_id': department_id, 'company_id': company_id}}
113
114     def expense_confirm(self, cr, uid, ids, *args):
115         self.write(cr, uid, ids, {
116             'state':'confirm',
117             'date_confirm': time.strftime('%Y-%m-%d')
118         })
119         return True
120
121     def expense_accept(self, cr, uid, ids, *args):
122         self.write(cr, uid, ids, {
123             'state':'accepted',
124             'date_valid':time.strftime('%Y-%m-%d'),
125             'user_valid': uid,
126             })
127         return True
128
129     def expense_canceled(self, cr, uid, ids, *args):
130         self.write(cr, uid, ids, {'state':'cancelled'})
131         return True
132
133     def expense_paid(self, cr, uid, ids, *args):
134         self.write(cr, uid, ids, {'state':'paid'})
135         return True
136
137     def action_receipt_create(self, cr, uid, ids, context=None):
138         property_obj = self.pool.get('ir.property')
139         sequence_obj = self.pool.get('ir.sequence')
140         analytic_journal_obj = self.pool.get('account.analytic.journal')
141         account_journal = self.pool.get('account.journal')
142         voucher_obj = self.pool.get('account.voucher')
143         wkf_service = netsvc.LocalService("workflow")
144         
145         for exp in self.browse(cr, uid, ids, context=context):
146             company_id = exp.company_id.id
147             lines = []
148             total = 0.0
149             for line in exp.line_ids:
150                 if line.product_id:
151                     acc = line.product_id.product_tmpl_id.property_account_expense
152                     if not acc:
153                         acc = line.product_id.categ_id.property_account_expense_categ
154                 else:
155                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
156                     if not acc:
157                         raise osv.except_osv(_('Error !'), _('Please configure Default Expense account for Product purchase, `property_account_expense_categ`'))
158                 
159                 lines.append((0, False, {
160                     'name': line.name,
161                     'account_id': acc.id,
162                     'account_analytic_id': line.analytic_account.id,
163                     'amount': line.total_amount,
164                     'type': 'dr'
165                 }))
166                 total += line.total_amount
167             if not exp.employee_id.address_home_id:
168                 raise osv.except_osv(_('Error !'), _('The employee must have a Home address.'))
169             acc = exp.employee_id.address_home_id.property_account_payable.id
170             voucher = {
171                 'name': exp.name,
172                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
173                 'account_id': acc,
174                 'type': 'purchase',
175                 'partner_id': exp.employee_id.address_home_id.id,
176                 'company_id': company_id,
177                 'line_ids': lines,
178                 'amount': total
179             }
180             journal = False
181             if exp.journal_id:
182                 voucher['journal_id'] = exp.journal_id.id
183                 journal = exp.journal_id
184             else:
185                 journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
186                 if journal_id:
187                     voucher['journal_id'] = journal_id
188                     journal = account_journal.browse(cr, uid, journal_id, context=context)
189             if journal and not journal.analytic_journal_id:
190                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
191                 if analytic_journal_ids:
192                     account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
193             voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
194             wkf_service.trg_validate(uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
195             self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'receipted'}, context=context)
196         return True
197     
198     def action_view_receipt(self, cr, uid, ids, context=None):
199         '''
200         This function returns an action that display existing receipt of given expense ids.
201         '''
202         assert len(ids) == 1, 'This option should only be used for a single id at a time'
203         voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
204         res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
205         result = {
206             'name': _('Expense Receipt'),
207             'view_type': 'form',
208             'view_mode': 'form',
209             'view_id': res and res[1] or False,
210             'res_model': 'account.voucher',
211             'type': 'ir.actions.act_window',
212             'nodestroy': True,
213             'target': 'current',
214             'res_id': voucher_id,
215         }
216         return result
217
218 hr_expense_expense()
219
220 class product_product(osv.osv):
221     _inherit = "product.product"
222     _columns = {
223         '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."),
224     }
225
226     def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok):
227
228         if not hr_expense_ok:
229             return {}
230         data_obj = self.pool.get('ir.model.data')
231         cat_id = data_obj._get_id(cr, uid, 'hr_expense', 'cat_expense')
232         categ_id = data_obj.browse(cr, uid, cat_id).res_id
233         res = {'value' : {'type':'service','procure_method':'make_to_stock','supply_method':'buy','purchase_ok':True,'sale_ok' :False,'categ_id':categ_id }}
234         return res
235
236 product_product()
237
238 class hr_expense_line(osv.osv):
239     _name = "hr.expense.line"
240     _description = "Expense Line"
241
242     def _amount(self, cr, uid, ids, field_name, arg, context=None):
243         if not ids:
244             return {}
245         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),))
246         res = dict(cr.fetchall())
247         return res
248
249     _columns = {
250         'name': fields.char('Expense Note', size=128, required=True),
251         'date_value': fields.date('Date', required=True),
252         'expense_id': fields.many2one('hr.expense.expense', 'Expense', ondelete='cascade', select=True),
253         'total_amount': fields.function(_amount, string='Total', digits_compute=dp.get_precision('Account')),
254         'unit_amount': fields.float('Unit Price', digits_compute=dp.get_precision('Account')),
255         'unit_quantity': fields.float('Quantities' ),
256         'product_id': fields.many2one('product.product', 'Product', domain=[('hr_expense_ok','=',True)]),
257         'uom_id': fields.many2one('product.uom', 'Unit of Measure'),
258         'description': fields.text('Description'),
259         'analytic_account': fields.many2one('account.analytic.account','Analytic account'),
260         'ref': fields.char('Reference', size=32),
261         'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of expense lines."),
262         }
263     _defaults = {
264         'unit_quantity': 1,
265         'date_value': lambda *a: time.strftime('%Y-%m-%d'),
266     }
267     _order = "sequence, date_value desc"
268
269     def onchange_product_id(self, cr, uid, ids, product_id, uom_id, employee_id, context=None):
270         res = {}
271         if product_id:
272             product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
273             res['name'] = product.name
274             amount_unit = product.price_get('standard_price')[product.id]
275             res['unit_amount'] = amount_unit
276             if not uom_id:
277                 res['uom_id'] = product.uom_id.id
278         return {'value': res}
279
280 hr_expense_line()
281
282 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: