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