[MERGE] merge with latest stable
[odoo/odoo.git] / addons / hr_payroll / hr_payroll.py
1 #-*- coding:utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    d$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 from datetime import date
25 from datetime import datetime
26 from datetime import timedelta
27
28 import netsvc
29 from osv import fields, osv
30 import tools
31 from tools.translate import _
32 import decimal_precision as dp
33
34 def prev_bounds(cdate=False):
35     when = date.fromtimestamp(time.mktime(time.strptime(cdate,"%Y-%m-%d")))
36     this_first = date(when.year, when.month, 1)
37     month = when.month + 1
38     year = when.year
39     if month > 12:
40         month = 1
41         year += 1
42     next_month = date(year, month, 1)
43     prev_end = next_month - timedelta(days=1)
44     return this_first, prev_end
45
46 class hr_contract_wage_type(osv.osv):
47     """
48     Wage types
49     Basic = Basic Salary
50     Grows = Basic + Allowances
51     New = Grows - Deductions
52     """
53
54     _inherit = 'hr.contract.wage.type'
55     _columns = {
56         'type': fields.selection([('basic','Basic'), ('gross','Gross'), ('net','Net')], 'Type', required=True),
57     }
58
59 hr_contract_wage_type()
60
61 class hr_passport(osv.osv):
62     """
63     Employee Passport
64     Passport based Contratacts for Employees
65     """
66
67     _name = 'hr.passport'
68     _description = 'Passport Detail'
69     _columns = {
70         'employee_id':fields.many2one('hr.employee', 'Employee', required=True),
71         'name':fields.char('Passport No', size=64, required=True, readonly=False),
72         'country_id':fields.many2one('res.country', 'Country of Issue', required=True),
73         'address_id':fields.many2one('res.partner.address', 'Address', required=False),
74         'date_issue': fields.date('Passport Issue Date', required=True),
75         'date_expire': fields.date('Passport Expire Date', required=True),
76         'contracts_ids':fields.one2many('hr.contract', 'passport_id', 'Contracts', required=False, readonly=True),
77         'note': fields.text('Description'),
78     }
79     _sql_constraints = [
80         ('passport_no_uniq', 'unique (employee_id, name)', 'The Passport No must be unique !'),
81     ]
82 hr_passport()
83
84 class hr_payroll_structure(osv.osv):
85     """
86     Salary structure used to defined
87     - Basic
88     - Allowlance
89     - Deductions
90     """
91
92     _name = 'hr.payroll.structure'
93     _description = 'Salary Structure'
94     _columns = {
95         'name':fields.char('Name', size=256, required=True, readonly=False),
96         'code':fields.char('Code', size=64, required=True, readonly=False),
97         'line_ids':fields.one2many('hr.payslip.line', 'function_id', 'Salary Structure', required=False),
98         'company_id':fields.many2one('res.company', 'Company', required=False),
99         'note': fields.text('Description'),
100     }
101     _defaults = {
102         'company_id': lambda self, cr, uid, context: \
103                 self.pool.get('res.users').browse(cr, uid, uid,
104                     context=context).company_id.id,
105     }
106
107     def copy(self, cr, uid, id, default=None, context=None):
108         """
109         Create a new record in hr_payroll_structure model from existing one
110         @param cr: cursor to database
111         @param user: id of current user
112         @param id: list of record ids on which copy method executes
113         @param default: dict type contains the values to be override during copy of object
114         @param context: context arguments, like lang, time zone
115
116         @return: returns a id of newly created record
117         """
118         code = self.browse(cr, uid, id, context=context).code
119         default = {
120             'code':code+"(copy)",
121             'company_id':self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
122         }
123         return super(hr_payroll_structure, self).copy(cr, uid, id, default, context=context)
124
125 hr_payroll_structure()
126
127 class hr_contract(osv.osv):
128     """
129     Employee contract based on the visa, work permits
130     allowas to configure different Salary structure
131     """
132
133     def compute_basic(self, cr, uid, ids, context=None):
134         res = {}
135         if context is None:
136             context = {}
137         ids += context.get('employee_structure', [])
138
139         slip_line_pool = self.pool.get('hr.payslip.line')
140
141         for contract in self.browse(cr, uid, ids, context=context):
142             all_per = 0.0
143             ded_per = 0.0
144             all_fix = 0.0
145             ded_fix = 0.0
146             obj = {'basic':0.0}
147             update = {}
148             if contract.wage_type_id.type == 'gross':
149                 obj['gross'] = contract.wage
150                 update['gross'] = contract.wage
151             if contract.wage_type_id.type == 'net':
152                 obj['net'] = contract.wage
153                 update['net'] = contract.wage
154             if contract.wage_type_id.type == 'basic':
155                 obj['basic'] = contract.wage
156                 update['basic'] = contract.wage
157
158             sal_type = contract.wage_type_id.type
159 #            function = contract.struct_id.id
160             lines = contract.struct_id.line_ids
161             if not contract.struct_id:
162                 res[contract.id] = obj['basic']
163                 continue
164
165             ad = []
166             for line in lines:
167                 cd = line.code.lower()
168                 obj[cd] = line.amount or 0.0
169
170             for line in lines:
171                 if line.category_id.code in ad:
172                     continue
173                 ad.append(line.category_id.code)
174                 cd = line.category_id.code.lower()
175                 calculate = False
176                 try:
177                     exp = line.category_id.condition
178                     calculate = eval(exp, obj)
179                 except Exception, e:
180                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
181
182                 if not calculate:
183                     continue
184
185                 percent = 0.0
186                 value = 0.0
187                 base = False
188 #                company_contrib = 0.0
189                 base = line.category_id.base
190
191                 try:
192                     #Please have a look at the configuration guide.
193                     amt = eval(base, obj)
194                 except Exception, e:
195                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
196
197                 if sal_type in ('gross', 'net'):
198                     if line.amount_type == 'per':
199                         percent = line.amount
200                         if amt > 1:
201                             value = percent * amt
202                         elif amt > 0 and amt <= 1:
203                             percent = percent * amt
204                         if value > 0:
205                             percent = 0.0
206                     elif line.amount_type == 'fix':
207                         value = line.amount
208                     elif line.amount_type == 'func':
209                         value = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
210                         line.amount = value
211                 else:
212                     if line.amount_type in ('fix', 'per'):
213                         value = line.amount
214                     elif line.amount_type == 'func':
215                         value = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
216                         line.amount = value
217                 if line.type == 'allowance':
218                     all_per += percent
219                     all_fix += value
220                 elif line.type == 'deduction':
221                     ded_per += percent
222                     ded_fix += value
223             if sal_type in ('gross', 'net'):
224                 sal = contract.wage
225                 if sal_type == 'net':
226                     sal += ded_fix
227                 sal -= all_fix
228                 per = 0.0
229                 if sal_type == 'net':
230                     per = (all_per - ded_per)
231                 else:
232                     per = all_per
233                 if per <=0:
234                     per *= -1
235                 final = (per * 100) + 100
236                 basic = (sal * 100) / final
237             else:
238                 basic = contract.wage
239
240             res[contract.id] = basic
241
242         return res
243
244     def check_vals(self, val1, val2):
245         if val1 == val2 and val1 == 0:
246             return True
247         return False
248
249     def _calculate_salary(self, cr, uid, ids, field_names, arg, context=None):
250         res = self.compute_basic(cr, uid, ids, context=context)
251         vals = {}
252         for rs in self.browse(cr, uid, ids, context=context):
253             allow = 0.0
254             deduct = 0.0
255             others = 0.0
256             obj = {'basic':res[rs.id], 'gross':0.0, 'net':0.0}
257             if rs.wage_type_id.type == 'gross':
258                 obj['gross'] = rs.wage
259             if rs.wage_type_id.type == 'net':
260                 obj['net'] = rs.net
261
262             if not rs.struct_id:
263                 if self.check_vals(obj['basic'], obj['gross']):
264                     obj['gross'] = obj['basic'] = obj['net']
265                 elif self.check_vals(obj['gross'], obj['net']):
266                     obj['gross']= obj['net'] = obj['basic']
267                 elif self.check_vals(obj['net'], obj['basic']):
268                     obj['net'] = obj['basic'] = obj['gross']
269                 record = {
270                     'advantages_gross':0.0,
271                     'advantages_net':0.0,
272                     'basic':obj['basic'],
273                     'gross':obj['gross'],
274                     'net':obj['net']
275                 }
276                 vals[rs.id] = record
277                 continue
278
279             for line in rs.struct_id.line_ids:
280                 amount = 0.0
281                 if line.amount_type == 'per':
282                     try:
283                         amount = line.amount * eval(str(line.category_id.base), obj)
284                     except Exception, e:
285                         raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
286                 elif line.amount_type in ('fix', 'func'):
287                     amount = line.amount
288                 cd = line.category_id.code.lower()
289                 obj[cd] = amount
290
291                 if line.type == 'allowance':
292                     allow += amount
293                 elif line.type == 'deduction':
294                     deduct += amount
295                 elif line.type == 'advance':
296                     others += amount
297                 elif line.type == 'loan':
298                     others += amount
299                 elif line.type == 'otherpay':
300                     others += amount
301             record = {
302                 'advantages_gross':round(allow),
303                 'advantages_net':round(deduct),
304                 'basic':obj['basic'],
305                 'gross':round(obj['basic'] + allow),
306                 'net':round(obj['basic'] + allow - deduct)
307             }
308             vals[rs.id] = record
309
310         return vals
311
312     _inherit = 'hr.contract'
313     _description = 'Employee Contract'
314     _columns = {
315         'permit_no': fields.char('Work Permit No', size=256, required=False, readonly=False),
316         'passport_id': fields.many2one('hr.passport', 'Passport No', required=False),
317         'visa_no': fields.char('Visa No', size=64, required=False, readonly=False),
318         'visa_expire': fields.date('Visa Expire Date'),
319         'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure'),
320         'working_days_per_week': fields.integer('Working Days', help="No of Working days / week for an employee"),
321         'basic': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Basic Salary', digits=(14,2)),
322         'gross': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Gross Salary', digits=(14,2)),
323         'net': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Net Salary', digits=(14,2)),
324         'advantages_net': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Deductions', digits=(14,2)),
325         'advantages_gross': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Allowances', digits=(14,2)),
326     }
327     _defaults = {
328         'working_days_per_week': lambda *a: 5,
329     }
330 hr_contract()
331
332 class payroll_register(osv.osv):
333     """
334     Payroll Register
335     """
336
337     _name = 'hr.payroll.register'
338     _description = 'Payroll Register'
339
340     def _calculate(self, cr, uid, ids, field_names, arg, context=None):
341         res = {}
342         allounce = 0.0
343         deduction = 0.0
344         net = 0.0
345         grows = 0.0
346         for register in self.browse(cr, uid, ids, context=context):
347             for slip in register.line_ids:
348                 allounce += slip.allounce
349                 deduction += slip.deduction
350                 net += slip.net
351                 grows += slip.grows
352
353             res[register.id] = {
354                 'allounce':allounce,
355                 'deduction':deduction,
356                 'net':net,
357                 'grows':grows
358             }
359         return res
360
361     _columns = {
362         'name':fields.char('Name', size=64, required=True, readonly=False),
363         'date': fields.date('Date', required=True),
364         'number':fields.char('Number', size=64, required=False, readonly=True),
365         'line_ids':fields.one2many('hr.payslip', 'register_id', 'Payslips', required=False),
366         'state':fields.selection([
367             ('new','New Slip'),
368             ('draft','Wating for Verification'),
369             ('hr_check','Wating for HR Verification'),
370             ('accont_check','Wating for Account Verification'),
371             ('confirm','Confirm Sheet'),
372             ('done','Paid Salary'),
373             ('cancel','Reject'),
374         ],'State', select=True, readonly=True),
375         'active':fields.boolean('Active', required=False),
376         'company_id':fields.many2one('res.company', 'Company', required=False),
377         'grows': fields.function(_calculate, method=True, store=True, multi='dc', string='Gross Salary', type='float', digits=(16, 4)),
378         'net': fields.function(_calculate, method=True, store=True, multi='dc', string='Net Salary', digits=(16, 4)),
379         'allounce': fields.function(_calculate, method=True, store=True, multi='dc', string='Allowance', digits=(16, 4)),
380         'deduction': fields.function(_calculate, method=True, store=True, multi='dc', string='Deduction', digits=(16, 4)),
381         'note': fields.text('Description'),
382         'bank_id':fields.many2one('res.bank', 'Bank', required=False, help="Select the Bank Address from whcih the salary is going to be paid"),
383     }
384
385     _defaults = {
386         'date': lambda *a: time.strftime('%Y-%m-%d'),
387         'state': lambda *a: 'new',
388         'active': lambda *a: True,
389         'company_id': lambda self, cr, uid, context: \
390                 self.pool.get('res.users').browse(cr, uid, uid,
391                     context=context).company_id.id,
392     }
393
394     def compute_sheet(self, cr, uid, ids, context=None):
395         emp_pool = self.pool.get('hr.employee')
396         slip_pool = self.pool.get('hr.payslip')
397         wf_service = netsvc.LocalService("workflow")
398         if context is None:
399             context = {}
400
401         vals = self.browse(cr, uid, ids[0], context=context)
402         emp_ids = emp_pool.search(cr, uid, [], context=context)
403
404         for emp in emp_pool.browse(cr, uid, emp_ids, context=context):
405             old_slips = slip_pool.search(cr, uid, [('employee_id','=', emp.id), ('date','=',vals.date)], context=context)
406             if old_slips:
407                 slip_pool.write(cr, uid, old_slips, {'register_id':ids[0]}, context=context)
408                 for sid in old_slips:
409                     wf_service.trg_validate(uid, 'hr.payslip', sid, 'compute_sheet', cr)
410             else:
411                 res = {
412                     'employee_id':emp.id,
413                     'basic':0.0,
414                     'register_id':ids[0],
415                     'name':vals.name,
416                     'date':vals.date,
417                 }
418                 slip_id = slip_pool.create(cr, uid, res, context=context)
419                 wf_service.trg_validate(uid, 'hr.payslip', slip_id, 'compute_sheet', cr)
420
421         number = self.pool.get('ir.sequence').get(cr, uid, 'salary.register')
422         self.write(cr, uid, ids, {'state':'draft', 'number':number}, context=context)
423         return True
424
425     def set_to_draft(self, cr, uid, ids, context=None):
426         self.write(cr, uid, ids, {'state':'draft'}, context=context)
427         return True
428
429     def cancel_sheet(self, cr, uid, ids, context=None):
430         self.write(cr, uid, ids, {'state':'cancel'}, context=context)
431         return True
432
433     def verify_sheet(self, cr, uid, ids, context=None):
434         slip_pool = self.pool.get('hr.payslip')
435
436         for id in ids:
437             sids = slip_pool.search(cr, uid, [('register_id','=',id)], context=context)
438             wf_service = netsvc.LocalService("workflow")
439             for sid in sids:
440                 wf_service.trg_validate(uid, 'hr.payslip', sid, 'verify_sheet', cr)
441
442         self.write(cr, uid, ids, {'state':'hr_check'}, context=context)
443         return True
444
445     def final_verify_sheet(self, cr, uid, ids, context=None):
446         slip_pool = self.pool.get('hr.payslip')
447         advice_pool = self.pool.get('hr.payroll.advice')
448         advice_line_pool = self.pool.get('hr.payroll.advice.line')
449         sequence_pool = self.pool.get('ir.sequence')
450         users_pool = self.pool.get('res.users')
451
452         for id in ids:
453             sids = slip_pool.search(cr, uid, [('register_id','=',id), ('state','=','hr_check')], context=context)
454             wf_service = netsvc.LocalService("workflow")
455             for sid in sids:
456                 wf_service.trg_validate(uid, 'hr.payslip', sid, 'final_verify_sheet', cr)
457
458         company_name = users_pool.browse(cr, uid, uid, context=context).company_id.name
459         for reg in self.browse(cr, uid, ids, context=context):
460             advice = {
461                 'name': 'Payment Advice from %s' % (company_name),
462                 'number': sequence_pool.get(cr, uid, 'payment.advice'),
463                 'register_id':reg.id
464             }
465             pid = advice_pool.create(cr, uid, advice, context=context)
466
467             for slip in reg.line_ids:
468                 if not slip.employee_id.bank_account_id:
469                     raise osv.except_osv(_('Error !'), _('Please define bank account for the %s employee') % (slip.employee_id.name))
470                 pline = {
471                     'advice_id':pid,
472                     'name':slip.employee_id.bank_account_id.acc_number,
473                     'employee_id':slip.employee_id.id,
474                     'amount':slip.other_pay + slip.net,
475                     'bysal':slip.net
476                 }
477                 id = advice_line_pool.create(cr, uid, pline, context=context)
478
479         self.write(cr, uid, ids, {'state':'confirm'}, context=context)
480         return True
481
482     def process_sheet(self, cr, uid, ids, context=None):
483         slip_pool = self.pool.get('hr.payslip')
484         for id in ids:
485             sids = slip_pool.search(cr, uid, [('register_id','=',id), ('state','=','confirm')], context=context)
486             wf_service = netsvc.LocalService("workflow")
487             for sid in sids:
488                 wf_service.trg_validate(uid, 'hr.payslip', sid, 'process_sheet', cr)
489
490         self.write(cr, uid, ids, {'state':'done'}, context=context)
491         return True
492
493 payroll_register()
494
495 class payroll_advice(osv.osv):
496     '''
497     Bank Advice Note
498     '''
499
500     _name = 'hr.payroll.advice'
501     _description = 'Bank Advice Note'
502     _columns = {
503         'register_id':fields.many2one('hr.payroll.register', 'Payroll Register', required=False),
504         'name':fields.char('Name', size=2048, required=True, readonly=False),
505         'note': fields.text('Description'),
506         'date': fields.date('Date'),
507         'state':fields.selection([
508             ('draft','Draft Sheet'),
509             ('confirm','Confirm Sheet'),
510             ('cancel','Reject'),
511         ],'State', select=True, readonly=True),
512         'number':fields.char('Number', size=64, required=False, readonly=True),
513         'line_ids':fields.one2many('hr.payroll.advice.line', 'advice_id', 'Employee Salary', required=False),
514         'chaque_nos':fields.char('Chaque Nos', size=256, required=False, readonly=False),
515         'company_id':fields.many2one('res.company', 'Company', required=False),
516         'bank_id': fields.related('register_id','bank_id', type='many2one', relation='res.bank', string='Bank', help="Select the Bank Address from whcih the salary is going to be paid"),
517     }
518     _defaults = {
519         'date': lambda *a: time.strftime('%Y-%m-%d'),
520         'state': lambda *a: 'draft',
521         'company_id': lambda self, cr, uid, context: \
522                 self.pool.get('res.users').browse(cr, uid, uid,
523                     context=context).company_id.id,
524     }
525
526     def confirm_sheet(self, cr, uid, ids, context=None):
527         self.write(cr, uid, ids, {'state':'confirm'}, context=context)
528         return True
529
530     def set_to_draft(self, cr, uid, ids, context=None):
531         self.write(cr, uid, ids, {'state':'draft'}, context=context)
532         return True
533
534     def cancel_sheet(self, cr, uid, ids, context=None):
535         self.write(cr, uid, ids, {'state':'cancel'}, context=context)
536         return True
537
538     def onchange_company_id(self, cr, uid, ids, company_id=False, context=None):
539         res = {}
540         if company_id:
541             company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
542             if company.partner_id.bank_ids:
543                 res.update({'bank': company.partner_id.bank_ids[0].bank.name})
544         return {
545             'value':res
546         }
547 payroll_advice()
548
549 class payroll_advice_line(osv.osv):
550     '''
551     Bank Advice Lines
552     '''
553
554     _name = 'hr.payroll.advice.line'
555     _description = 'Bank Advice Lines'
556     _columns = {
557         'advice_id':fields.many2one('hr.payroll.advice', 'Bank Advice', required=False),
558         'name':fields.char('Bank Account A/C', size=64, required=True, readonly=False),
559         'employee_id':fields.many2one('hr.employee', 'Employee', required=True),
560         'amount': fields.float('Amount', digits=(16, 4)),
561         'bysal': fields.float('By Salary', digits=(16, 4)),
562         'flag':fields.char('D/C', size=8, required=True, readonly=False),
563     }
564     _defaults = {
565         'flag': lambda *a: 'C',
566     }
567
568     def onchange_employee_id(self, cr, uid, ids, ddate, employee_id, context=None):
569         vals = {}
570         slip_pool = self.pool.get('hr.payslip')
571         if employee_id:
572             dates = prev_bounds(ddate)
573             sids = False
574             sids = slip_pool.search(cr, uid, [('paid','=',False),('state','=','confirm'),('date','>=',dates[0]), ('employee_id','=',employee_id), ('date','<=',dates[1])], context=context)
575             if sids:
576                 slip = slip_pool.browse(cr, uid, sids[0], context=context)
577                 vals['name'] = slip.employee_id.identification_id
578                 vals['amount'] = slip.net + slip.other_pay
579                 vals['bysal'] = slip.net
580         return {
581             'value':vals
582         }
583 payroll_advice_line()
584
585 class contrib_register(osv.osv):
586     '''
587     Contribution Register
588     '''
589
590     _name = 'hr.contibution.register'
591     _description = 'Contribution Register'
592
593     def _total_contrib(self, cr, uid, ids, field_names, arg, context=None):
594         line_pool = self.pool.get('hr.contibution.register.line')
595
596         res = {}
597         for cur in self.browse(cr, uid, ids, context=context):
598             current = line_pool.search(cr, uid, [('register_id','=',cur.id)], context=context)
599             e_month = 0.0
600             c_month = 0.0
601             for i in line_pool.browse(cr, uid, current, context=context):
602                 e_month += i.emp_deduction
603                 c_month += i.comp_deduction
604             res[cur.id]={
605                 'monthly_total_by_emp':e_month,
606                 'monthly_total_by_comp':c_month,
607             }
608         return res
609
610     _columns = {
611         'company_id':fields.many2one('res.company', 'Company', required=False),
612         'name':fields.char('Name', size=256, required=True, readonly=False),
613         'register_line_ids':fields.one2many('hr.contibution.register.line', 'register_id', 'Register Line', readonly=True),
614         'monthly_total_by_emp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Employee', digits=(16, 4)),
615         'monthly_total_by_comp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Company', digits=(16, 4)),
616         'note': fields.text('Description'),
617     }
618     _defaults = {
619         'company_id': lambda self, cr, uid, context: \
620                 self.pool.get('res.users').browse(cr, uid, uid,
621                     context=context).company_id.id,
622     }
623 contrib_register()
624
625 class contrib_register_line(osv.osv):
626     '''
627     Contribution Register Line
628     '''
629
630     _name = 'hr.contibution.register.line'
631     _description = 'Contribution Register Line'
632
633     def _total(self, cr, uid, ids, field_names, arg, context=None):
634         res={}
635         for line in self.browse(cr, uid, ids, context=context):
636             res[line.id] = line.emp_deduction + line.comp_deduction
637             return res
638
639     _columns = {
640         'name':fields.char('Name', size=256, required=True, readonly=False),
641         'register_id':fields.many2one('hr.contibution.register', 'Register', required=False),
642         'code':fields.char('Code', size=64, required=False, readonly=False),
643         'employee_id':fields.many2one('hr.employee', 'Employee', required=True),
644         'date': fields.date('Date'),
645         'emp_deduction': fields.float('Employee Deduction', digits=(16, 4)),
646         'comp_deduction': fields.float('Company Deduction', digits=(16, 4)),
647         'total': fields.function(_total, method=True, store=True,  string='Total', digits=(16, 4)),
648     }
649     _defaults = {
650         'date': lambda *a: time.strftime('%Y-%m-%d'),
651     }
652 contrib_register_line()
653
654 class payment_category(osv.osv):
655     """
656     Allowance, Deduction Heads
657     House Rent Allowance, Medical Allowance, Food Allowance
658     Professional Tax, Advance TDS, Providend Funds, etc
659     """
660
661     _name = 'hr.allounce.deduction.categoty'
662     _description = 'Allowance Deduction Heads'
663     _columns = {
664         'name':fields.char('Category Name', size=64, required=True, readonly=False),
665         'code':fields.char('Category Code', size=64, required=True, readonly=False),
666         'type':fields.selection([
667             ('allowance','Allowance'),
668             ('deduction','Deduction'),
669             ('leaves','Leaves'),
670             ('advance','Advance'),
671             ('loan','Loan'),
672             ('installment','Loan Installment'),
673             ('otherpay','Other Payment'),
674             ('otherdeduct','Other Deduction'),
675         ],'Type', select=True, required=True),
676         'base':fields.text('Based on', required=True, readonly=False, help='This will use to computer the % fields values, in general its on basic, but You can use all heads code field in small letter as a variable name i.e. hra, ma, lta, etc...., also you can use, static varible basic'),
677         'condition':fields.char('Condition', size=1024, required=True, readonly=False, help='Applied this head for calculation if condition is true'),
678         'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence'),
679         'note': fields.text('Description'),
680         'user_id':fields.char('User', size=64, required=False, readonly=False),
681         'state':fields.char('Label', size=64, required=False, readonly=False),
682         'company_id':fields.many2one('res.company', 'Company', required=False),
683         'contribute_ids':fields.one2many('company.contribution', 'category_id', 'Contributions', required=False),
684     }
685     _defaults = {
686         'condition': lambda *a: 'True',
687         'base': lambda *a:'basic',
688         'sequence': lambda *a:5,
689         'company_id': lambda self, cr, uid, context: \
690                 self.pool.get('res.users').browse(cr, uid, uid,
691                     context=context).company_id.id,
692     }
693 payment_category()
694
695 class company_contribution(osv.osv):
696     """
697     Company contribution
698     Allows to configure company contribution for some taxes
699     """
700
701     _name = 'company.contribution'
702     _description = "Company Contribution"
703     _columns = {
704         'category_id':fields.many2one('hr.allounce.deduction.categoty', 'Heads', required=False),
705         'name':fields.char('Name', size=256, required=True, readonly=False),
706         'code':fields.char('Code', size=64, required=True, readonly=False),
707         'gratuity':fields.boolean('Use for Gratuity ?', required=False),
708         'line_ids':fields.one2many('company.contribution.line', 'contribution_id', 'Calculations', required=False),
709         'register_id':fields.property(
710             'hr.contibution.register',
711             type='many2one',
712             relation='hr.contibution.register',
713             string="Contribution Register",
714             method=True,
715             view_load=True,
716             help="Contribution register based on company",
717             required=False
718         ),
719         'amount_type':fields.selection([
720             ('fix','Fixed Amount'),
721             ('per','Percentage'),
722             ('func','Function Calculation'),
723         ],'Amount Type', select=True),
724         'contribute_per':fields.float('Contribution', digits=(16, 4), help='Define Company contribution ratio 1.00=100% contribution.'),
725         'company_id':fields.many2one('res.company', 'Company', required=False),
726         'active':fields.boolean('Active', required=False),
727         'note': fields.text('Description'),
728     }
729
730     _defaults = {
731         'amount_type': lambda *a:'fix',
732         'active': lambda *a:True,
733         'company_id': lambda self, cr, uid, context: \
734                 self.pool.get('res.users').browse(cr, uid, uid,
735                     context=context).company_id.id,
736     }
737
738     def _execute_function(self, cr, uid, id, value, context=None):
739         """
740         self: pointer to self object
741         cr: cursor to database
742         uid: user id of current executer
743         """
744         line_pool = self.pool.get('company.contribution.line')
745         res = 0
746         ids = line_pool.search(cr, uid, [('category_id','=',id), ('to_val','>=',value),('from_val','<=',value)], context=context)
747         if not ids:
748             ids = line_pool.search(cr, uid, [('category_id','=',id), ('from','<=',value)], context=context)
749         if not ids:
750             res = 0
751         else:
752             res = line_pool.browse(cr, uid, ids, context=context)[0].value
753         return res
754
755     def compute(self, cr, uid, id, value, context=None):
756         contrib = self.browse(cr, uid, id, context=context)
757         if contrib.amount_type == 'fix':
758             return contrib.contribute_per
759         elif contrib.amount_type == 'per':
760             return value * contrib.contribute_per
761         elif contrib.amount_type == 'func':
762             return self._execute_function(cr, uid, id, value, context)
763         return 0.0
764 company_contribution()
765
766 class company_contribution_line(osv.osv):
767     """
768     Company contribution lines
769     """
770
771     _name = 'company.contribution.line'
772     _description = 'Allowance Deduction Category'
773     _order = 'sequence'
774     _columns = {
775         'contribution_id':fields.many2one('company.contribution', 'Contribution', required=False),
776         'name':fields.char('Name', size=64, required=False, readonly=False),
777         'from_val': fields.float('From', digits=(16, 4)),
778         'to_val': fields.float('To', digits=(16, 4)),
779         'amount_type':fields.selection([
780             ('fix','Fixed Amount'),
781         ],'Amount Type', select=True),
782         'sequence':fields.integer('Sequence'),
783         'value': fields.float('Value', digits=(16, 4)),
784     }
785 company_contribution_line()
786
787 class hr_holidays_status(osv.osv):
788
789     _inherit = "hr.holidays.status"
790     _columns = {
791         'company_id':fields.many2one('res.company', 'Company', required=False),
792         'type':fields.selection([
793             ('paid','Paid Holiday'),
794             ('unpaid','Un-Paid Holiday'),
795             ('halfpaid','Half-Pay Holiday')
796             ], string='Payment'),
797         'head_id': fields.many2one('hr.allounce.deduction.categoty', 'Payroll Head', domain=[('type','=','deduction')]),
798         'code': fields.related('head_id','code', type='char', relation='hr.allounce.deduction.categoty', string='Code'),
799 #        'code':fields.char('Code', size=64, required=False, readonly=False),
800     }
801     _defaults = {
802         'type': lambda *args: 'unpaid',
803         'company_id': lambda self, cr, uid, context: \
804                 self.pool.get('res.users').browse(cr, uid, uid,
805                     context=context).company_id.id,
806     }
807 hr_holidays_status()
808
809 class hr_payslip(osv.osv):
810     '''
811     Pay Slip
812     '''
813
814     _name = 'hr.payslip'
815     _description = 'Pay Slip'
816
817     def _calculate(self, cr, uid, ids, field_names, arg, context=None):
818         slip_line_obj = self.pool.get('hr.payslip.line')
819         register_pool = self.pool.get('company.contribution')
820         res = {}
821         for rs in self.browse(cr, uid, ids, context=context):
822             allow = 0.0
823             deduct = 0.0
824             others = 0.0
825             obj = {'basic':rs.basic}
826             if rs.igross > 0:
827                 obj['gross'] = rs.igross
828             if rs.inet > 0:
829                 obj['net'] = rs.inet
830             for line in rs.line_ids:
831                 amount = 0.0
832                 if line.amount_type == 'per':
833                     try:
834                         amount = line.amount * eval(str(line.category_id.base), obj)
835                     except Exception, e:
836                         raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
837                 elif line.amount_type in ('fix', 'func'):
838                     amount = line.amount
839                 cd = line.category_id.code.lower()
840                 obj[cd] = amount
841                 contrib = 0.0
842                 if line.type == 'allowance':
843                     allow += amount
844                     others += contrib
845                     amount -= contrib
846                 elif line.type == 'deduction':
847                     deduct += amount
848                     others -= contrib
849                     amount += contrib
850                 elif line.type == 'advance':
851                     others += amount
852                 elif line.type == 'loan':
853                     others += amount
854                 elif line.type == 'otherpay':
855                     others += amount
856
857                 company_contrib = 0.0
858                 for contrib_line in line.category_id.contribute_ids:
859                     company_contrib += register_pool.compute(cr, uid, contrib_line.id, amount, context)
860
861                 slip_line_obj.write(cr, uid, [line.id], {'total':amount, 'company_contrib':company_contrib}, context=context)
862
863             record = {
864                 'allounce':allow,
865                 'deduction':deduct,
866                 'grows':rs.basic + allow,
867                 'net':rs.basic + allow - deduct,
868                 'other_pay':others,
869                 'total_pay':rs.basic + allow - deduct
870             }
871             res[rs.id] = record
872         return res
873
874     _columns = {
875         'deg_id':fields.many2one('hr.payroll.structure', 'Designation', readonly=True, states={'draft': [('readonly', False)]}),
876         'register_id':fields.many2one('hr.payroll.register', 'Register', required=False, readonly=True, states={'new': [('readonly', False)]}),
877         'name':fields.char('Name', size=64, required=False, readonly=True, states={'new': [('readonly', False)]}),
878         'number':fields.char('Number', size=64, required=False, readonly=True),
879         'employee_id':fields.many2one('hr.employee', 'Employee', required=True, readonly=True, states={'new': [('readonly', False)]}),
880         'date': fields.date('Date', readonly=True, states={'new': [('readonly', False)]}),
881         'state':fields.selection([
882             ('new','New Slip'),
883             ('draft','Wating for Verification'),
884             ('hr_check','Wating for HR Verification'),
885             ('accont_check','Wating for Account Verification'),
886             ('confirm','Confirm Sheet'),
887             ('done','Paid Salary'),
888             ('cancel','Reject'),
889         ],'State', select=True, readonly=True),
890         'basic_before_leaves': fields.float('Basic Salary', readonly=True,  digits_compute=dp.get_precision('Account')),
891         'leaves': fields.float('Leave Deductions', readonly=True,  digits_compute=dp.get_precision('Account')),
892         'basic': fields.float('Net Basic', readonly=True,  digits_compute=dp.get_precision('Account')),
893         'grows': fields.function(_calculate, method=True, store=True, multi='dc', string='Gross Salary', digits_compute=dp.get_precision('Account')),
894         'net': fields.function(_calculate, method=True, store=True, multi='dc', string='Net Salary', digits_compute=dp.get_precision('Account')),
895         'allounce': fields.function(_calculate, method=True, store=True, multi='dc', string='Allowance', digits_compute=dp.get_precision('Account')),
896         'deduction': fields.function(_calculate, method=True, store=True, multi='dc', string='Deduction', digits_compute=dp.get_precision('Account')),
897         'other_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Others', digits_compute=dp.get_precision('Account')),
898         'total_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Total Payment', digits_compute=dp.get_precision('Account')),
899         'line_ids':fields.one2many('hr.payslip.line', 'slip_id', 'Payslip Line', required=False, readonly=True, states={'draft': [('readonly', False)]}),
900         'company_id':fields.many2one('res.company', 'Company', required=False, readonly=True, states={'draft': [('readonly', False)]}),
901         'holiday_days': fields.float('No of Leaves', readonly=True),
902         'worked_days': fields.float('Worked Day', readonly=True),
903         'working_days': fields.float('Working Days', readonly=True),
904         'paid':fields.boolean('Paid ? ', required=False, readonly=True, states={'draft': [('readonly', False)]}),
905         'note':fields.text('Description'),
906         'contract_id':fields.many2one('hr.contract', 'Contract', required=False, readonly=True, states={'draft': [('readonly', False)]}),
907         'igross': fields.float('Calculaton Field', readonly=True,  digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
908         'inet': fields.float('Calculaton Field', readonly=True,  digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
909     }
910     _defaults = {
911         'date': lambda *a: time.strftime('%Y-%m-%d'),
912         'state': lambda *a: 'new',
913         'company_id': lambda self, cr, uid, context: \
914                 self.pool.get('res.users').browse(cr, uid, uid,
915                     context=context).company_id.id,
916     }
917
918     def copy(self, cr, uid, id, default=None, context=None):
919         company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
920         default = {
921             'line_ids': False,
922             'move_ids': False,
923             'move_line_ids': False,
924             'move_payment_ids': False,
925             'company_id':company_id,
926             'period_id': False,
927             'basic_before_leaves':0,
928             'basic':0
929         }
930         res_id = super(hr_payslip, self).copy(cr, uid, id, default, context=context)
931         return res_id
932
933     def create_voucher(self, cr, uid, ids, name, voucher, sequence=5):
934         slip_move = self.pool.get('hr.payslip.account.move')
935         for slip in ids:
936             res = {
937                 'slip_id':slip,
938                 'move_id':voucher,
939                 'sequence':sequence,
940                 'name':name
941             }
942             slip_move.create(cr, uid, res)
943
944     def set_to_draft(self, cr, uid, ids, context=None):
945         self.write(cr, uid, ids, {'state':'draft'}, context=context)
946         return True
947
948     def cancel_sheet(self, cr, uid, ids, context=None):
949         self.write(cr, uid, ids, {'state':'cancel'}, context=context)
950         return True
951
952     def account_check_sheet(self, cr, uid, ids, context=None):
953         self.write(cr, uid, ids, {'state':'accont_check'}, context=context)
954         return True
955
956     def hr_check_sheet(self, cr, uid, ids, context=None):
957         self.write(cr, uid, ids, {'state':'hr_check'}, context=context)
958         return True
959
960     def process_sheet(self, cr, uid, ids, context=None):
961         self.write(cr, uid, ids, {'paid':True, 'state':'done'}, context=context)
962         return True
963
964     def verify_sheet(self, cr, uid, ids, context=None):
965         register_pool = self.pool.get('company.contribution')
966         register_line_pool = self.pool.get('hr.contibution.register.line')
967
968         for slip in self.browse(cr, uid, ids, context=context):
969             base = {
970                 'basic':slip.basic,
971                 'net':slip.net,
972                 'gross':slip.grows,
973             }
974             for line in slip.line_ids:
975                 base[line.code.lower()] = line.total
976                 for contrib in line.category_id.contribute_ids:
977                     if contrib.register_id:
978                         value = eval(line.category_id.base, base)
979                         company_contrib = register_pool.compute(cr, uid, contrib.id, value, context)
980                         reg_line = {
981                             'name':line.name,
982                             'register_id': contrib.register_id.id,
983                             'code':line.code,
984                             'employee_id':slip.employee_id.id,
985                             'emp_deduction':line.total,
986                             'comp_deduction':company_contrib,
987                             'total':line.total + line.total
988                         }
989                         register_line_pool.create(cr, uid, reg_line)
990
991         self.write(cr, uid, ids, {'state':'confirm'}, context=context)
992         return True
993
994     def get_contract(self, cr, uid, employee, date, context=None):
995         sql_req= '''
996             SELECT c.id as id, c.wage as wage, struct_id as function
997             FROM hr_contract c
998               LEFT JOIN hr_employee emp on (c.employee_id=emp.id)
999               LEFT JOIN hr_contract_wage_type cwt on (cwt.id = c.wage_type_id)
1000               LEFT JOIN hr_contract_wage_type_period p on (cwt.period_id = p.id)
1001             WHERE
1002               (emp.id=%s) AND
1003               (date_start <= %s) AND
1004               (date_end IS NULL OR date_end >= %s)
1005             LIMIT 1
1006             '''
1007         cr.execute(sql_req, (employee.id, date, date))
1008         contract = cr.dictfetchone()
1009
1010         contract = contract and contract or {}
1011         return contract
1012
1013     def _get_leaves(self, cr, user, slip, employee, context=None):
1014         """
1015         Compute leaves for an employee
1016
1017         @param cr: cursor to database
1018         @param user: id of current user
1019         @param slip: object of the hr.payroll.slip model
1020         @param employee: object of the hr.employee model
1021         @param context: context arguments, like lang, time zone
1022
1023         @return: return a result
1024         """
1025         result = []
1026
1027         dates = prev_bounds(slip.date)
1028         sql = '''select id from hr_holidays
1029                     where date_from >= '%s' and date_to <= '%s'
1030                     and employee_id = %s
1031                     and state = 'validate' ''' % (dates[0], dates[1], slip.employee_id.id)
1032         cr.execute(sql)
1033         res = cr.fetchall()
1034
1035         if res:
1036             result = [x[0] for x in res]
1037
1038         return result
1039
1040     def compute_sheet(self, cr, uid, ids, context=None):
1041         func_pool = self.pool.get('hr.payroll.structure')
1042         slip_line_pool = self.pool.get('hr.payslip.line')
1043         holiday_pool = self.pool.get('hr.holidays')
1044         sequence_obj = self.pool.get('ir.sequence')
1045         if context is None:
1046             context = {}
1047         date = self.read(cr, uid, ids, ['date'], context=context)[0]['date']
1048
1049         #Check for the Holidays
1050         def get_days(start, end, month, year, calc_day):
1051             import datetime
1052             count = 0
1053             for day in range(start, end):
1054                 if datetime.date(year, month, day).weekday() == calc_day:
1055                     count += 1
1056             return count
1057
1058         for slip in self.browse(cr, uid, ids, context=context):
1059             old_slip_ids = slip_line_pool.search(cr, uid, [('slip_id','=',slip.id)], context=context)
1060             slip_line_pool.unlink(cr, uid, old_slip_ids, context=context)
1061             update = {}
1062             ttyme = datetime.fromtimestamp(time.mktime(time.strptime(slip.date,"%Y-%m-%d")))
1063             contracts = self.get_contract(cr, uid, slip.employee_id, date, context)
1064             if contracts.get('id', False) == False:
1065                 update.update({
1066                     'basic': round(0.0),
1067                     'basic_before_leaves': round(0.0),
1068                     'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
1069                     'state':'draft',
1070                     'contract_id':False,
1071                     'company_id':slip.employee_id.company_id.id
1072                 })
1073                 self.write(cr, uid, [slip.id], update, context=context)
1074                 continue
1075
1076             contract = slip.employee_id.contract_id
1077             sal_type = contract.wage_type_id.type
1078             function = contract.struct_id.id
1079             lines = []
1080             if function:
1081                 func = func_pool.read(cr, uid, function, ['line_ids'], context=context)
1082                 lines = slip_line_pool.browse(cr, uid, func['line_ids'], context=context)
1083
1084             #lines += slip.employee_id.line_ids
1085
1086             ad = []
1087             all_per = 0.0
1088             ded_per = 0.0
1089             all_fix = 0.0
1090             ded_fix = 0.0
1091
1092             obj = {'basic':0.0}
1093             if contract.wage_type_id.type == 'gross':
1094                 obj['gross'] = contract.wage
1095                 update['igross'] = contract.wage
1096             if contract.wage_type_id.type == 'net':
1097                 obj['net'] = contract.wage
1098                 update['inet'] = contract.wage
1099             if contract.wage_type_id.type == 'basic':
1100                 obj['basic'] = contract.wage
1101                 update['basic'] = contract.wage
1102
1103             for line in lines:
1104                 cd = line.code.lower()
1105                 obj[cd] = line.amount or 0.0
1106
1107             for line in lines:
1108                 if line.category_id.code in ad:
1109                     continue
1110                 ad.append(line.category_id.code)
1111                 cd = line.category_id.code.lower()
1112                 calculate = False
1113                 try:
1114                     exp = line.category_id.condition
1115                     calculate = eval(exp, obj)
1116                 except Exception, e:
1117                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1118
1119                 if not calculate:
1120                     continue
1121
1122                 percent = 0.0
1123                 value = 0.0
1124                 base = False
1125 #                company_contrib = 0.0
1126                 base = line.category_id.base
1127
1128                 try:
1129                     #Please have a look at the configuration guide.
1130                     amt = eval(base, obj)
1131                 except Exception, e:
1132                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1133
1134                 if sal_type in ('gross', 'net'):
1135                     if line.amount_type == 'per':
1136                         percent = line.amount
1137                         if amt > 1:
1138                             value = percent * amt
1139                         elif amt > 0 and amt <= 1:
1140                             percent = percent * amt
1141                         if value > 0:
1142                             percent = 0.0
1143                     elif line.amount_type == 'fix':
1144                         value = line.amount
1145                     elif line.amount_type == 'func':
1146                         value = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
1147                         line.amount = value
1148                 else:
1149                     if line.amount_type in ('fix', 'per'):
1150                         value = line.amount
1151                     elif line.amount_type == 'func':
1152                         value = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
1153                         line.amount = value
1154                 if line.type == 'allowance':
1155                     all_per += percent
1156                     all_fix += value
1157                 elif line.type == 'deduction':
1158                     ded_per += percent
1159                     ded_fix += value
1160                 vals = {
1161                     'amount':line.amount,
1162                     'slip_id':slip.id,
1163                     'employee_id':False,
1164                     'function_id':False,
1165                     'base':base
1166                 }
1167                 slip_line_pool.copy(cr, uid, line.id, vals, {})
1168             if sal_type in ('gross', 'net'):
1169                 sal = contract.wage
1170                 if sal_type == 'net':
1171                     sal += ded_fix
1172                 sal -= all_fix
1173                 per = 0.0
1174                 if sal_type == 'net':
1175                     per = (all_per - ded_per)
1176                 else:
1177                     per = all_per
1178                 if per <=0:
1179                     per *= -1
1180                 final = (per * 100) + 100
1181                 basic = (sal * 100) / final
1182             else:
1183                 basic = contract.wage
1184
1185             number = sequence_obj.get(cr, uid, 'salary.slip')
1186             update.update({
1187                 'deg_id':function,
1188                 'number':number,
1189                 'basic': round(basic),
1190                 'basic_before_leaves': round(basic),
1191                 'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
1192                 'state':'draft',
1193                 'contract_id':contract.id,
1194                 'company_id':slip.employee_id.company_id.id
1195             })
1196
1197             for line in slip.employee_id.line_ids:
1198                 vals = {
1199                     'amount':line.amount,
1200                     'slip_id':slip.id,
1201                     'employee_id':False,
1202                     'function_id':False,
1203                     'base':base
1204                 }
1205                 slip_line_pool.copy(cr, uid, line.id, vals, {})
1206
1207             self.write(cr, uid, [slip.id], update, context=context)
1208
1209         for slip in self.browse(cr, uid, ids, context=context):
1210             if not slip.contract_id:
1211                 continue
1212
1213             basic_before_leaves = slip.basic
1214             working_day = 0
1215             off_days = 0
1216             dates = prev_bounds(slip.date)
1217
1218             days_arr = [0, 1, 2, 3, 4, 5, 6]
1219             for dy in range(slip.employee_id.contract_id.working_days_per_week, 7):
1220                 off_days += get_days(1, dates[1].day, dates[1].month, dates[1].year, days_arr[dy])
1221             total_off = off_days
1222             working_day = dates[1].day - total_off
1223             perday = slip.net / working_day
1224             total = 0.0
1225             leave = 0.0
1226             leave_ids = self._get_leaves(cr, uid, slip, slip.employee_id, context)
1227             total_leave = 0.0
1228             paid_leave = 0.0
1229             for hday in holiday_pool.browse(cr, uid, leave_ids, context=context):
1230                 if not hday.holiday_status_id.head_id:
1231                     raise osv.except_osv(_('Error !'), _('Please check configuration of %s, payroll head is missing') % (hday.holiday_status_id.name))
1232
1233                 res = {
1234                     'slip_id':slip.id,
1235                     'name':hday.holiday_status_id.name + '-%s' % (hday.number_of_days),
1236                     'code':hday.holiday_status_id.code,
1237                     'amount_type':'fix',
1238                     'category_id':hday.holiday_status_id.head_id.id,
1239                     'sequence':hday.holiday_status_id.head_id.sequence
1240                 }
1241                 days = hday.number_of_days
1242                 if hday.number_of_days < 0:
1243                     days = hday.number_of_days * -1
1244                 total_leave += days
1245                 if hday.holiday_status_id.type == 'paid':
1246                     paid_leave += days
1247                     continue
1248 #                    res['name'] = hday.holiday_status_id.name + '-%s' % (days)
1249 #                    res['amount'] = perday * days
1250 #                    res['type'] = 'allowance'
1251 #                    leave += days
1252 #                    total += perday * days
1253
1254                 elif hday.holiday_status_id.type == 'halfpaid':
1255                     paid_leave += (days / 2)
1256                     res['name'] = hday.holiday_status_id.name + '-%s/2' % (days)
1257                     res['amount'] = perday * (days/2)
1258                     total += perday * (days/2)
1259                     leave += days / 2
1260                     res['type'] = 'deduction'
1261                 else:
1262                     res['name'] = hday.holiday_status_id.name + '-%s' % (days)
1263                     res['amount'] = perday * days
1264                     res['type'] = 'deduction'
1265                     leave += days
1266                     total += perday * days
1267
1268                 slip_line_pool.create(cr, uid, res, context=context)
1269             basic = basic - total
1270 #            leaves = total
1271             update.update({
1272                 'basic':basic,
1273                 'basic_before_leaves': round(basic_before_leaves),
1274                 'leaves':total,
1275                 'holiday_days':leave,
1276                 'worked_days':working_day - leave,
1277                 'working_days':working_day,
1278             })
1279             self.write(cr, uid, [slip.id], update, context=context)
1280         return True
1281 hr_payslip()
1282
1283 class hr_payslip_line(osv.osv):
1284     '''
1285     Payslip Line
1286     '''
1287
1288     _name = 'hr.payslip.line'
1289     _description = 'Payslip Line'
1290
1291     def onchange_category(self, cr, uid, ids, category_id):
1292         res = {
1293         }
1294         if category_id:
1295             category = self.pool.get('hr.allounce.deduction.categoty').browse(cr, uid, category_id)
1296             res.update({
1297                 'sequence':category.sequence,
1298                 'name':category.name,
1299                 'code':category.code,
1300                 'type':category.type
1301             })
1302         return {'value':res}
1303
1304     def onchange_amount(self, cr, uid, ids, amount, typ):
1305         amt = amount
1306         if typ and typ == 'per':
1307             if int(amt) > 0:
1308                 amt = amt / 100
1309         return {'value':{'amount':amt}}
1310
1311     _columns = {
1312         'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=False),
1313         'function_id':fields.many2one('hr.payroll.structure', 'Function', required=False),
1314         'employee_id':fields.many2one('hr.employee', 'Employee', required=False),
1315         'name':fields.char('Name', size=256, required=True, readonly=False),
1316         'base':fields.char('Formula', size=1024, required=False, readonly=False),
1317         'code':fields.char('Code', size=64, required=False, readonly=False),
1318         'category_id':fields.many2one('hr.allounce.deduction.categoty', 'Category', required=True),
1319         'type':fields.selection([
1320             ('allowance','Allowance'),
1321             ('deduction','Deduction'),
1322             ('leaves','Leaves'),
1323             ('advance','Advance'),
1324             ('loan','Loan'),
1325             ('installment','Loan Installment'),
1326             ('otherpay','Other Payment'),
1327             ('otherdeduct','Other Deduction'),
1328         ],'Type', select=True, required=True),
1329         #TODO: link type to the category_id instead of define again
1330         #'type': fields.related('category_id','type', type='selection', size=64, relation='hr.allounce.deduction.categoty', string='Type', store=True),
1331         'amount_type':fields.selection([
1332             ('per','Percentage (%)'),
1333             ('fix','Fixed Amount'),
1334             ('func','Function Value'),
1335         ],'Amount Type', select=True, required=True),
1336         'amount': fields.float('Amount / Percentage', digits=(16, 4)),
1337         'total': fields.float('Sub Total', readonly=True, digits_compute=dp.get_precision('Account')),
1338         'company_contrib': fields.float('Company Contribution', readonly=True, digits=(16, 4)),
1339         'sequence': fields.integer('Sequence'),
1340         'note':fields.text('Description'),
1341         'line_ids':fields.one2many('hr.payslip.line.line', 'slipline_id', 'Calculations', required=False)
1342     }
1343     _order = 'sequence'
1344     _defaults = {
1345         'amount_type': lambda *a: 'per'
1346     }
1347
1348     def execute_function(self, cr, uid, id, value, context=None):
1349         line_pool = self.pool.get('hr.payslip.line.line')
1350         res = 0
1351         ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value), ('to_val','>=',value)], context=context)
1352         if not ids:
1353             ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value)], context=context)
1354         if not ids:
1355             return res
1356
1357         res = line_pool.browse(cr, uid, ids, context=context)[-1].value
1358         return res
1359
1360 hr_payslip_line()
1361
1362 class hr_payslip_line_line(osv.osv):
1363     '''
1364     Function Line
1365     '''
1366
1367     _name = 'hr.payslip.line.line'
1368     _description = 'Function Line'
1369     _order = 'sequence'
1370     _columns = {
1371         'slipline_id':fields.many2one('hr.payslip.line', 'Slip Line', required=False),
1372         'name':fields.char('Name', size=64, required=False, readonly=False),
1373         'from_val': fields.float('From', digits=(16, 4)),
1374         'to_val': fields.float('To', digits=(16, 4)),
1375         'amount_type':fields.selection([
1376             ('fix','Fixed Amount'),
1377         ],'Amount Type', select=True),
1378         'sequence':fields.integer('Sequence'),
1379         'value': fields.float('Value', digits=(16, 4)),
1380     }
1381 hr_payslip_line_line()
1382
1383 class hr_employee(osv.osv):
1384     '''
1385     Employee
1386     '''
1387
1388     _inherit = 'hr.employee'
1389     _description = 'Employee'
1390
1391     def _calculate_salary(self, cr, uid, ids, field_names, arg, context=None):
1392         vals = {}
1393         slip_line_pool = self.pool.get('hr.payslip.line')
1394
1395         for employee in self.browse(cr, uid, ids, context=context):
1396             if not employee.contract_id:
1397                 vals[employee.id] = {'basic':0.0, 'gross':0.0, 'net':0.0, 'advantages_gross':0.0, 'advantages_net':0.0}
1398                 continue
1399
1400             basic = employee.contract_id.basic
1401             gross = employee.contract_id.gross
1402             net = employee.contract_id.net
1403             allowance = employee.contract_id.advantages_gross
1404             deduction = employee.contract_id.advantages_net
1405
1406             obj = {
1407                 'basic':basic,
1408                 'gross':gross,
1409                 'net':net
1410             }
1411             for line in employee.line_ids:
1412                 base = line.category_id.base
1413                 try:
1414                     amt = eval(base, obj)
1415                 except Exception, e:
1416                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1417                 amount = 0.0
1418                 if line.amount_type == 'per':
1419                     amount = amt * line.amount
1420                 elif line.amount_type == 'func':
1421                     amount = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
1422                 elif line.amount_type == 'fix':
1423                     amount = line.amount
1424
1425                 if line.type == 'allowance':
1426                     allowance += amount
1427                 elif line.type == 'deduction':
1428                     deduction += amount
1429
1430             vals[employee.id] = {
1431                 'basic':basic,
1432                 'advantages_gross':allowance,
1433                 'gross':basic + allowance,
1434                 'advantages_net':deduction,
1435                 'net':basic + allowance - deduction
1436             }
1437         return vals
1438
1439     _columns = {
1440         'passport_id':fields.many2one('hr.passport', 'Passport No', required=False, domain="[('employee_id','=',active_id), ('address_id','=',address_home_id)]", help="Employee Passport Information"),
1441         'line_ids':fields.one2many('hr.payslip.line', 'employee_id', 'Salary Structure', required=False),
1442         'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True),
1443         'otherid': fields.char('Other Id', size=64),
1444
1445         'basic': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Basic Salary', digits=(14,2)),
1446         'gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Gross Salary', digits=(14,2)),
1447         'net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Net Salary', digits=(14,2)),
1448         'advantages_net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Deductions', digits=(14,2)),
1449         'advantages_gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Allowances', digits=(14,2)),
1450     }
1451 hr_employee()
1452
1453 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: