Remplace all occurences of my_browse_rec.product_id.product_tmpl_id.field to my_brows...
[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         'invoice_id': fields.many2one('account.invoice', "Employee's Invoice"),
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         wf_service = netsvc.LocalService("workflow")
135         mod_obj = self.pool.get('ir.model.data')
136         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
137         inv_ids = []
138         for id in ids:
139             wf_service.trg_validate(uid, 'hr.expense.expense', id, 'invoice', cr)
140             inv_ids.append(self.browse(cr, uid, id).invoice_id.id)
141         return {
142             'name': _('Supplier Invoices'),
143             'view_type': 'form',
144             'view_mode': 'form',
145             'view_id': [res and res[1] or False],
146             'res_model': 'account.invoice',
147             'context': "{'type':'out_invoice', 'journal_type': 'purchase'}",
148             'type': 'ir.actions.act_window',
149             'nodestroy': True,
150             'target': 'current',
151             'res_id': inv_ids and inv_ids[0] or False,
152         }
153
154     def action_invoice_create(self, cr, uid, ids):
155         res = False
156         invoice_obj = self.pool.get('account.invoice')
157         property_obj = self.pool.get('ir.property')
158         sequence_obj = self.pool.get('ir.sequence')
159         analytic_journal_obj = self.pool.get('account.analytic.journal')
160         account_journal = self.pool.get('account.journal')
161         for exp in self.browse(cr, uid, ids):
162             company_id = exp.company_id.id
163             lines = []
164             for l in exp.line_ids:
165                 tax_id = []
166                 if l.product_id:
167                     acc = l.product_id.property_account_expense
168                     if not acc:
169                         acc = l.product_id.categ_id.property_account_expense_categ
170                     tax_id = [x.id for x in l.product_id.supplier_taxes_id]
171                 else:
172                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
173                     if not acc:
174                         raise osv.except_osv(_('Error !'), _('Please configure Default Expense account for Product purchase, `property_account_expense_categ`'))
175                 lines.append((0, False, {
176                     'name': l.name,
177                     'account_id': acc.id,
178                     'price_unit': l.unit_amount,
179                     'quantity': l.unit_quantity,
180                     'uos_id': l.uom_id.id,
181                     'product_id': l.product_id and l.product_id.id or False,
182                     'invoice_line_tax_id': tax_id and [(6, 0, tax_id)] or False,
183                     'account_analytic_id': l.analytic_account.id,
184                 }))
185             if not exp.employee_id.address_home_id:
186                 raise osv.except_osv(_('Error !'), _('The employee must have a Home address.'))
187             acc = exp.employee_id.address_home_id.property_account_payable.id
188             payment_term_id = exp.employee_id.address_home_id.property_payment_term.id
189             inv = {
190                 'name': exp.name,
191                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
192                 'account_id': acc,
193                 'type': 'in_invoice',
194                 'partner_id': exp.employee_id.address_home_id.id,
195                 'company_id': company_id,
196                 'origin': exp.name,
197                 'invoice_line': lines,
198                 'currency_id': exp.currency_id.id,
199                 'payment_term': payment_term_id,
200                 'fiscal_position': exp.employee_id.address_home_id.property_account_position.id
201             }
202             if payment_term_id:
203                 to_update = invoice_obj.onchange_payment_term_date_invoice(cr, uid, [], payment_term_id, None)
204                 if to_update:
205                     inv.update(to_update['value'])
206             journal = False
207             if exp.journal_id:
208                 inv['journal_id']=exp.journal_id.id
209                 journal = exp.journal_id
210             else:
211                 journal_id = invoice_obj._get_journal(cr, uid, context={'type': 'in_invoice', 'company_id': company_id})
212                 if journal_id:
213                     inv['journal_id'] = journal_id
214                     journal = account_journal.browse(cr, uid, journal_id)
215             if journal and not journal.analytic_journal_id:
216                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')])
217                 if analytic_journal_ids:
218                     account_journal.write(cr, uid, [journal.id],{'analytic_journal_id':analytic_journal_ids[0]})
219             inv_id = invoice_obj.create(cr, uid, inv, {'type': 'in_invoice'})
220             invoice_obj.button_compute(cr, uid, [inv_id], {'type': 'in_invoice'}, set_total=True)
221             self.write(cr, uid, [exp.id], {'invoice_id': inv_id, 'state': 'invoiced'})
222             res = inv_id
223         return res
224
225 hr_expense_expense()
226
227 class product_product(osv.osv):
228     _inherit = "product.product"
229     _columns = {
230         '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."),
231     }
232
233     def on_change_hr_expense_ok(self, cr, uid, id, hr_expense_ok):
234
235         if not hr_expense_ok:
236             return {}
237         data_obj = self.pool.get('ir.model.data')
238         cat_id = data_obj._get_id(cr, uid, 'hr_expense', 'cat_expense')
239         categ_id = data_obj.browse(cr, uid, cat_id).res_id
240         res = {'value' : {'type':'service','procure_method':'make_to_stock','supply_method':'buy','purchase_ok':True,'sale_ok' :False,'categ_id':categ_id }}
241         return res
242
243 product_product()
244
245 class hr_expense_line(osv.osv):
246     _name = "hr.expense.line"
247     _description = "Expense Line"
248
249     def _amount(self, cr, uid, ids, field_name, arg, context=None):
250         if not ids:
251             return {}
252         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),))
253         res = dict(cr.fetchall())
254         return res
255
256     _columns = {
257         'name': fields.char('Expense Note', size=128, required=True),
258         'date_value': fields.date('Date', required=True),
259         'expense_id': fields.many2one('hr.expense.expense', 'Expense', ondelete='cascade', select=True),
260         'total_amount': fields.function(_amount, string='Total', digits_compute=dp.get_precision('Account')),
261         'unit_amount': fields.float('Unit Price', digits_compute=dp.get_precision('Account')),
262         'unit_quantity': fields.float('Quantities' ),
263         'product_id': fields.many2one('product.product', 'Product', domain=[('hr_expense_ok','=',True)]),
264         'uom_id': fields.many2one('product.uom', 'Unit of Measure'),
265         'description': fields.text('Description'),
266         'analytic_account': fields.many2one('account.analytic.account','Analytic account'),
267         'ref': fields.char('Reference', size=32),
268         'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of expense lines."),
269         }
270     _defaults = {
271         'unit_quantity': 1,
272         'date_value': lambda *a: time.strftime('%Y-%m-%d'),
273     }
274     _order = "sequence, date_value desc"
275
276     def onchange_product_id(self, cr, uid, ids, product_id, uom_id, employee_id, context=None):
277         res = {}
278         if product_id:
279             product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
280             res['name'] = product.name
281             amount_unit = product.price_get('standard_price')[product.id]
282             res['unit_amount'] = amount_unit
283             if not uom_id:
284                 res['uom_id'] = product.uom_id.id
285         return {'value': res}
286
287 hr_expense_line()
288
289 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: