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