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