[FIX] crm, hr_payroll: Corrected date problem in ymls.
[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         res = {}
820         for rs in self.browse(cr, uid, ids, context=context):
821             allow = 0.0
822             deduct = 0.0
823             others = 0.0
824             obj = {'basic':rs.basic}
825             if rs.igross > 0:
826                 obj['gross'] = rs.igross
827             if rs.inet > 0:
828                 obj['net'] = rs.inet
829             for line in rs.line_ids:
830                 amount = 0.0
831                 if line.amount_type == 'per':
832                     try:
833                         amount = line.amount * eval(str(line.category_id.base), obj)
834                     except Exception, e:
835                         raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
836                 elif line.amount_type in ('fix', 'func'):
837                     amount = line.amount
838                 cd = line.category_id.code.lower()
839                 obj[cd] = amount
840                 contrib = 0.0
841                 if line.type == 'allowance':
842                     allow += amount
843                     others += contrib
844                     amount -= contrib
845                 elif line.type == 'deduction':
846                     deduct += amount
847                     others -= contrib
848                     amount += contrib
849                 elif line.type == 'advance':
850                     others += amount
851                 elif line.type == 'loan':
852                     others += amount
853                 elif line.type == 'otherpay':
854                     others += amount
855                 slip_line_obj.write(cr, uid, [line.id], {'total':amount}, context=context)
856
857             record = {
858                 'allounce':allow,
859                 'deduction':deduct,
860                 'grows':rs.basic + allow,
861                 'net':rs.basic + allow - deduct,
862                 'other_pay':others,
863                 'total_pay':rs.basic + allow - deduct
864             }
865             res[rs.id] = record
866         return res
867
868     _columns = {
869         'deg_id':fields.many2one('hr.payroll.structure', 'Designation', readonly=True, states={'draft': [('readonly', False)]}),
870         'register_id':fields.many2one('hr.payroll.register', 'Register', required=False, readonly=True, states={'new': [('readonly', False)]}),
871         'name':fields.char('Name', size=64, required=False, readonly=True, states={'new': [('readonly', False)]}),
872         'number':fields.char('Number', size=64, required=False, readonly=True),
873         'employee_id':fields.many2one('hr.employee', 'Employee', required=True, readonly=True, states={'new': [('readonly', False)]}),
874         'date': fields.date('Date', readonly=True, states={'new': [('readonly', False)]}),
875         'state':fields.selection([
876             ('new','New Slip'),
877             ('draft','Wating for Verification'),
878             ('hr_check','Wating for HR Verification'),
879             ('accont_check','Wating for Account Verification'),
880             ('confirm','Confirm Sheet'),
881             ('done','Paid Salary'),
882             ('cancel','Reject'),
883         ],'State', select=True, readonly=True),
884         'basic_before_leaves': fields.float('Basic Salary', readonly=True,  digits_compute=dp.get_precision('Account')),
885         'leaves': fields.float('Leave Deductions', readonly=True,  digits_compute=dp.get_precision('Account')),
886         'basic': fields.float('Net Basic', readonly=True,  digits_compute=dp.get_precision('Account')),
887         'grows': fields.function(_calculate, method=True, store=True, multi='dc', string='Gross Salary', digits_compute=dp.get_precision('Account')),
888         'net': fields.function(_calculate, method=True, store=True, multi='dc', string='Net Salary', digits_compute=dp.get_precision('Account')),
889         'allounce': fields.function(_calculate, method=True, store=True, multi='dc', string='Allowance', digits_compute=dp.get_precision('Account')),
890         'deduction': fields.function(_calculate, method=True, store=True, multi='dc', string='Deduction', digits_compute=dp.get_precision('Account')),
891         'other_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Others', digits_compute=dp.get_precision('Account')),
892         'total_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Total Payment', digits_compute=dp.get_precision('Account')),
893         'line_ids':fields.one2many('hr.payslip.line', 'slip_id', 'Payslip Line', required=False, readonly=True, states={'draft': [('readonly', False)]}),
894         'company_id':fields.many2one('res.company', 'Company', required=False, readonly=True, states={'draft': [('readonly', False)]}),
895         'holiday_days': fields.float('No of Leaves', readonly=True),
896         'worked_days': fields.float('Worked Day', readonly=True),
897         'working_days': fields.float('Working Days', readonly=True),
898         'paid':fields.boolean('Paid ? ', required=False, readonly=True, states={'draft': [('readonly', False)]}),
899         'note':fields.text('Description'),
900         'contract_id':fields.many2one('hr.contract', 'Contract', required=False, readonly=True, states={'draft': [('readonly', False)]}),
901         'igross': fields.float('Calculaton Field', readonly=True,  digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
902         'inet': fields.float('Calculaton Field', readonly=True,  digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
903     }
904     _defaults = {
905         'date': lambda *a: time.strftime('%Y-%m-%d'),
906         'state': lambda *a: 'new',
907         'company_id': lambda self, cr, uid, context: \
908                 self.pool.get('res.users').browse(cr, uid, uid,
909                     context=context).company_id.id,
910     }
911
912     def copy(self, cr, uid, id, default=None, context=None):
913         company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
914         default = {
915             'line_ids': False,
916             'move_ids': False,
917             'move_line_ids': False,
918             'move_payment_ids': False,
919             'company_id':company_id,
920             'period_id': False,
921             'basic_before_leaves':0,
922             'basic':0
923         }
924         res_id = super(hr_payslip, self).copy(cr, uid, id, default, context=context)
925         return res_id
926
927     def create_voucher(self, cr, uid, ids, name, voucher, sequence=5):
928         slip_move = self.pool.get('hr.payslip.account.move')
929         for slip in ids:
930             res = {
931                 'slip_id':slip,
932                 'move_id':voucher,
933                 'sequence':sequence,
934                 'name':name
935             }
936             slip_move.create(cr, uid, res)
937
938     def set_to_draft(self, cr, uid, ids, context=None):
939         self.write(cr, uid, ids, {'state':'draft'}, context=context)
940         return True
941
942     def cancel_sheet(self, cr, uid, ids, context=None):
943         self.write(cr, uid, ids, {'state':'cancel'}, context=context)
944         return True
945
946     def account_check_sheet(self, cr, uid, ids, context=None):
947         self.write(cr, uid, ids, {'state':'accont_check'}, context=context)
948         return True
949
950     def hr_check_sheet(self, cr, uid, ids, context=None):
951         self.write(cr, uid, ids, {'state':'hr_check'}, context=context)
952         return True
953
954     def process_sheet(self, cr, uid, ids, context=None):
955         self.write(cr, uid, ids, {'paid':True, 'state':'done'}, context=context)
956         return True
957
958     def verify_sheet(self, cr, uid, ids, context=None):
959         register_pool = self.pool.get('company.contribution')
960         register_line_pool = self.pool.get('hr.contibution.register.line')
961
962         for slip in self.browse(cr, uid, ids, context=context):
963             base = {
964                 'basic':slip.basic,
965                 'net':slip.net,
966                 'gross':slip.grows,
967             }
968             for line in slip.line_ids:
969                 base[line.code.lower()] = line.total
970                 for contrib in line.category_id.contribute_ids:
971                     if contrib.register_id:
972                         value = eval(line.category_id.base, base)
973                         company_contrib = register_pool.compute(cr, uid, contrib.id, value, context)
974                         reg_line = {
975                             'name':line.name,
976                             'register_id': contrib.register_id.id,
977                             'code':line.code,
978                             'employee_id':slip.employee_id.id,
979                             'emp_deduction':line.total,
980                             'comp_deduction':company_contrib,
981                             'total':line.total + line.total
982                         }
983                         register_line_pool.create(cr, uid, reg_line)
984
985         self.write(cr, uid, ids, {'state':'confirm'}, context=context)
986         return True
987
988     def get_contract(self, cr, uid, employee, date, context=None):
989         sql_req= '''
990             SELECT c.id as id, c.wage as wage, struct_id as function
991             FROM hr_contract c
992               LEFT JOIN hr_employee emp on (c.employee_id=emp.id)
993               LEFT JOIN hr_contract_wage_type cwt on (cwt.id = c.wage_type_id)
994               LEFT JOIN hr_contract_wage_type_period p on (cwt.period_id = p.id)
995             WHERE
996               (emp.id=%s) AND
997               (date_start <= %s) AND
998               (date_end IS NULL OR date_end >= %s)
999             LIMIT 1
1000             '''
1001         cr.execute(sql_req, (employee.id, date, date))
1002         contract = cr.dictfetchone()
1003
1004         contract = contract and contract or {}
1005         return contract
1006
1007     def _get_leaves(self, cr, user, slip, employee, context=None):
1008         """
1009         Compute leaves for an employee
1010
1011         @param cr: cursor to database
1012         @param user: id of current user
1013         @param slip: object of the hr.payroll.slip model
1014         @param employee: object of the hr.employee model
1015         @param context: context arguments, like lang, time zone
1016
1017         @return: return a result
1018         """
1019         result = []
1020
1021         dates = prev_bounds(slip.date)
1022         sql = '''select id from hr_holidays
1023                     where date_from >= '%s' and date_to <= '%s'
1024                     and employee_id = %s
1025                     and state = 'validate' ''' % (dates[0], dates[1], slip.employee_id.id)
1026         cr.execute(sql)
1027         res = cr.fetchall()
1028
1029         if res:
1030             result = [x[0] for x in res]
1031
1032         return result
1033
1034     def compute_sheet(self, cr, uid, ids, context=None):
1035         func_pool = self.pool.get('hr.payroll.structure')
1036         slip_line_pool = self.pool.get('hr.payslip.line')
1037         holiday_pool = self.pool.get('hr.holidays')
1038         sequence_obj = self.pool.get('ir.sequence')
1039         if context is None:
1040             context = {}
1041         date = self.read(cr, uid, ids, ['date'], context=context)[0]['date']
1042
1043         #Check for the Holidays
1044         def get_days(start, end, month, year, calc_day):
1045             import datetime
1046             count = 0
1047             for day in range(start, end):
1048                 if datetime.date(year, month, day).weekday() == calc_day:
1049                     count += 1
1050             return count
1051
1052         for slip in self.browse(cr, uid, ids, context=context):
1053             old_slip_ids = slip_line_pool.search(cr, uid, [('slip_id','=',slip.id)], context=context)
1054             slip_line_pool.unlink(cr, uid, old_slip_ids, context=context)
1055             update = {}
1056             ttyme = datetime.fromtimestamp(time.mktime(time.strptime(slip.date,"%Y-%m-%d")))
1057             contracts = self.get_contract(cr, uid, slip.employee_id, date, context)
1058             if contracts.get('id', False) == False:
1059                 update.update({
1060                     'basic': round(0.0),
1061                     'basic_before_leaves': round(0.0),
1062                     'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
1063                     'state':'draft',
1064                     'contract_id':False,
1065                     'company_id':slip.employee_id.company_id.id
1066                 })
1067                 self.write(cr, uid, [slip.id], update, context=context)
1068                 continue
1069
1070             contract = slip.employee_id.contract_id
1071             sal_type = contract.wage_type_id.type
1072             function = contract.struct_id.id
1073             lines = []
1074             if function:
1075                 func = func_pool.read(cr, uid, function, ['line_ids'], context=context)
1076                 lines = slip_line_pool.browse(cr, uid, func['line_ids'], context=context)
1077
1078             #lines += slip.employee_id.line_ids
1079
1080             ad = []
1081             all_per = 0.0
1082             ded_per = 0.0
1083             all_fix = 0.0
1084             ded_fix = 0.0
1085
1086             obj = {'basic':0.0}
1087             if contract.wage_type_id.type == 'gross':
1088                 obj['gross'] = contract.wage
1089                 update['igross'] = contract.wage
1090             if contract.wage_type_id.type == 'net':
1091                 obj['net'] = contract.wage
1092                 update['inet'] = contract.wage
1093             if contract.wage_type_id.type == 'basic':
1094                 obj['basic'] = contract.wage
1095                 update['basic'] = contract.wage
1096
1097             for line in lines:
1098                 cd = line.code.lower()
1099                 obj[cd] = line.amount or 0.0
1100
1101             for line in lines:
1102                 if line.category_id.code in ad:
1103                     continue
1104                 ad.append(line.category_id.code)
1105                 cd = line.category_id.code.lower()
1106                 calculate = False
1107                 try:
1108                     exp = line.category_id.condition
1109                     calculate = eval(exp, obj)
1110                 except Exception, e:
1111                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1112
1113                 if not calculate:
1114                     continue
1115
1116                 percent = 0.0
1117                 value = 0.0
1118                 base = False
1119 #                company_contrib = 0.0
1120                 base = line.category_id.base
1121
1122                 try:
1123                     #Please have a look at the configuration guide.
1124                     amt = eval(base, obj)
1125                 except Exception, e:
1126                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1127
1128                 if sal_type in ('gross', 'net'):
1129                     if line.amount_type == 'per':
1130                         percent = line.amount
1131                         if amt > 1:
1132                             value = percent * amt
1133                         elif amt > 0 and amt <= 1:
1134                             percent = percent * amt
1135                         if value > 0:
1136                             percent = 0.0
1137                     elif line.amount_type == 'fix':
1138                         value = line.amount
1139                     elif line.amount_type == 'func':
1140                         value = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
1141                         line.amount = value
1142                 else:
1143                     if line.amount_type in ('fix', 'per'):
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                 if line.type == 'allowance':
1149                     all_per += percent
1150                     all_fix += value
1151                 elif line.type == 'deduction':
1152                     ded_per += percent
1153                     ded_fix += value
1154                 vals = {
1155                     'amount':line.amount,
1156                     'slip_id':slip.id,
1157                     'employee_id':False,
1158                     'function_id':False,
1159                     'base':base
1160                 }
1161                 slip_line_pool.copy(cr, uid, line.id, vals, {})
1162             if sal_type in ('gross', 'net'):
1163                 sal = contract.wage
1164                 if sal_type == 'net':
1165                     sal += ded_fix
1166                 sal -= all_fix
1167                 per = 0.0
1168                 if sal_type == 'net':
1169                     per = (all_per - ded_per)
1170                 else:
1171                     per = all_per
1172                 if per <=0:
1173                     per *= -1
1174                 final = (per * 100) + 100
1175                 basic = (sal * 100) / final
1176             else:
1177                 basic = contract.wage
1178
1179             number = sequence_obj.get(cr, uid, 'salary.slip')
1180             update.update({
1181                 'deg_id':function,
1182                 'number':number,
1183                 'basic': round(basic),
1184                 'basic_before_leaves': round(basic),
1185                 'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))),
1186                 'state':'draft',
1187                 'contract_id':contract.id,
1188                 'company_id':slip.employee_id.company_id.id
1189             })
1190
1191             for line in slip.employee_id.line_ids:
1192                 vals = {
1193                     'amount':line.amount,
1194                     'slip_id':slip.id,
1195                     'employee_id':False,
1196                     'function_id':False,
1197                     'base':base
1198                 }
1199                 slip_line_pool.copy(cr, uid, line.id, vals, {})
1200
1201             self.write(cr, uid, [slip.id], update, context=context)
1202
1203         for slip in self.browse(cr, uid, ids, context=context):
1204             if not slip.contract_id:
1205                 continue
1206
1207             basic_before_leaves = slip.basic
1208             working_day = 0
1209             off_days = 0
1210             dates = prev_bounds(slip.date)
1211
1212             days_arr = [0, 1, 2, 3, 4, 5, 6]
1213             for dy in range(slip.employee_id.contract_id.working_days_per_week, 7):
1214                 off_days += get_days(1, dates[1].day, dates[1].month, dates[1].year, days_arr[dy])
1215             total_off = off_days
1216             working_day = dates[1].day - total_off
1217             perday = slip.net / working_day
1218             total = 0.0
1219             leave = 0.0
1220             leave_ids = self._get_leaves(cr, uid, slip, slip.employee_id, context)
1221             total_leave = 0.0
1222             paid_leave = 0.0
1223             for hday in holiday_pool.browse(cr, uid, leave_ids, context=context):
1224                 if not hday.holiday_status_id.head_id:
1225                     raise osv.except_osv(_('Error !'), _('Please check configuration of %s, payroll head is missing') % (hday.holiday_status_id.name))
1226
1227                 res = {
1228                     'slip_id':slip.id,
1229                     'name':hday.holiday_status_id.name + '-%s' % (hday.number_of_days),
1230                     'code':hday.holiday_status_id.code,
1231                     'amount_type':'fix',
1232                     'category_id':hday.holiday_status_id.head_id.id,
1233                     'sequence':hday.holiday_status_id.head_id.sequence
1234                 }
1235                 days = hday.number_of_days
1236                 if hday.number_of_days < 0:
1237                     days = hday.number_of_days * -1
1238                 total_leave += days
1239                 if hday.holiday_status_id.type == 'paid':
1240                     paid_leave += days
1241                     continue
1242 #                    res['name'] = hday.holiday_status_id.name + '-%s' % (days)
1243 #                    res['amount'] = perday * days
1244 #                    res['type'] = 'allowance'
1245 #                    leave += days
1246 #                    total += perday * days
1247
1248                 elif hday.holiday_status_id.type == 'halfpaid':
1249                     paid_leave += (days / 2)
1250                     res['name'] = hday.holiday_status_id.name + '-%s/2' % (days)
1251                     res['amount'] = perday * (days/2)
1252                     total += perday * (days/2)
1253                     leave += days / 2
1254                     res['type'] = 'deduction'
1255                 else:
1256                     res['name'] = hday.holiday_status_id.name + '-%s' % (days)
1257                     res['amount'] = perday * days
1258                     res['type'] = 'deduction'
1259                     leave += days
1260                     total += perday * days
1261
1262                 slip_line_pool.create(cr, uid, res, context=context)
1263             basic = basic - total
1264 #            leaves = total
1265             update.update({
1266                 'basic':basic,
1267                 'basic_before_leaves': round(basic_before_leaves),
1268                 'leaves':total,
1269                 'holiday_days':leave,
1270                 'worked_days':working_day - leave,
1271                 'working_days':working_day,
1272             })
1273             self.write(cr, uid, [slip.id], update, context=context)
1274         return True
1275 hr_payslip()
1276
1277 class hr_payslip_line(osv.osv):
1278     '''
1279     Payslip Line
1280     '''
1281
1282     _name = 'hr.payslip.line'
1283     _description = 'Payslip Line'
1284
1285     def onchange_category(self, cr, uid, ids, category_id):
1286         res = {
1287         }
1288         if category_id:
1289             category = self.pool.get('hr.allounce.deduction.categoty').browse(cr, uid, category_id)
1290             res.update({
1291                 'sequence':category.sequence,
1292                 'name':category.name,
1293                 'code':category.code,
1294                 'type':category.type
1295             })
1296         return {'value':res}
1297
1298     def onchange_amount(self, cr, uid, ids, amount, typ):
1299         amt = amount
1300         if typ and typ == 'per':
1301             if int(amt) > 0:
1302                 amt = amt / 100
1303         return {'value':{'amount':amt}}
1304
1305     _columns = {
1306         'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=False),
1307         'function_id':fields.many2one('hr.payroll.structure', 'Function', required=False),
1308         'employee_id':fields.many2one('hr.employee', 'Employee', required=False),
1309         'name':fields.char('Name', size=256, required=True, readonly=False),
1310         'base':fields.char('Formula', size=1024, required=False, readonly=False),
1311         'code':fields.char('Code', size=64, required=False, readonly=False),
1312         'category_id':fields.many2one('hr.allounce.deduction.categoty', 'Category', required=True),
1313         'type':fields.selection([
1314             ('allowance','Allowance'),
1315             ('deduction','Deduction'),
1316             ('leaves','Leaves'),
1317             ('advance','Advance'),
1318             ('loan','Loan'),
1319             ('installment','Loan Installment'),
1320             ('otherpay','Other Payment'),
1321             ('otherdeduct','Other Deduction'),
1322         ],'Type', select=True, required=True),
1323         #TODO: link type to the category_id instead of define again
1324         #'type': fields.related('category_id','type', type='selection', size=64, relation='hr.allounce.deduction.categoty', string='Type', store=True),
1325         'amount_type':fields.selection([
1326             ('per','Percentage (%)'),
1327             ('fix','Fixed Amount'),
1328             ('func','Function Value'),
1329         ],'Amount Type', select=True, required=True),
1330         'amount': fields.float('Amount / Percentage', digits=(16, 4)),
1331         'total': fields.float('Sub Total', readonly=True, digits_compute=dp.get_precision('Account')),
1332         'company_contrib': fields.float('Company Contribution', readonly=True, digits=(16, 4)),
1333         'sequence': fields.integer('Sequence'),
1334         'note':fields.text('Description'),
1335         'line_ids':fields.one2many('hr.payslip.line.line', 'slipline_id', 'Calculations', required=False)
1336     }
1337     _order = 'sequence'
1338     _defaults = {
1339         'amount_type': lambda *a: 'per'
1340     }
1341
1342     def execute_function(self, cr, uid, id, value, context=None):
1343         line_pool = self.pool.get('hr.payslip.line.line')
1344         res = 0
1345         ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value), ('to_val','>=',value)], context=context)
1346         if not ids:
1347             ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value)], context=context)
1348         if not ids:
1349             return res
1350
1351         res = line_pool.browse(cr, uid, ids, context=context)[-1].value
1352         return res
1353
1354 hr_payslip_line()
1355
1356 class hr_payslip_line_line(osv.osv):
1357     '''
1358     Function Line
1359     '''
1360
1361     _name = 'hr.payslip.line.line'
1362     _description = 'Function Line'
1363     _order = 'sequence'
1364     _columns = {
1365         'slipline_id':fields.many2one('hr.payslip.line', 'Slip Line', required=False),
1366         'name':fields.char('Name', size=64, required=False, readonly=False),
1367         'from_val': fields.float('From', digits=(16, 4)),
1368         'to_val': fields.float('To', digits=(16, 4)),
1369         'amount_type':fields.selection([
1370             ('fix','Fixed Amount'),
1371         ],'Amount Type', select=True),
1372         'sequence':fields.integer('Sequence'),
1373         'value': fields.float('Value', digits=(16, 4)),
1374     }
1375 hr_payslip_line_line()
1376
1377 class hr_employee(osv.osv):
1378     '''
1379     Employee
1380     '''
1381
1382     _inherit = 'hr.employee'
1383     _description = 'Employee'
1384
1385     def _calculate_salary(self, cr, uid, ids, field_names, arg, context=None):
1386         vals = {}
1387         slip_line_pool = self.pool.get('hr.payslip.line')
1388
1389         for employee in self.browse(cr, uid, ids, context=context):
1390             if not employee.contract_id:
1391                 vals[employee.id] = {'basic':0.0, 'gross':0.0, 'net':0.0, 'advantages_gross':0.0, 'advantages_net':0.0}
1392                 continue
1393
1394             basic = employee.contract_id.basic
1395             gross = employee.contract_id.gross
1396             net = employee.contract_id.net
1397             allowance = employee.contract_id.advantages_gross
1398             deduction = employee.contract_id.advantages_net
1399
1400             obj = {
1401                 'basic':basic,
1402                 'gross':gross,
1403                 'net':net
1404             }
1405             for line in employee.line_ids:
1406                 base = line.category_id.base
1407                 try:
1408                     amt = eval(base, obj)
1409                 except Exception, e:
1410                     raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e))
1411                 amount = 0.0
1412                 if line.amount_type == 'per':
1413                     amount = amt * line.amount
1414                 elif line.amount_type == 'func':
1415                     amount = slip_line_pool.execute_function(cr, uid, line.id, amt, context)
1416                 elif line.amount_type == 'fix':
1417                     amount = line.amount
1418
1419                 if line.type == 'allowance':
1420                     allowance += amount
1421                 elif line.type == 'deduction':
1422                     deduction += amount
1423
1424             vals[employee.id] = {
1425                 'basic':basic,
1426                 'advantages_gross':allowance,
1427                 'gross':basic + allowance,
1428                 'advantages_net':deduction,
1429                 'net':basic + allowance - deduction
1430             }
1431         return vals
1432
1433     _columns = {
1434         'passport_id':fields.many2one('hr.passport', 'Passport No', required=False, domain="[('employee_id','=',active_id), ('address_id','=',address_home_id)]", help="Employee Passport Information"),
1435         'line_ids':fields.one2many('hr.payslip.line', 'employee_id', 'Salary Structure', required=False),
1436         'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True),
1437         'otherid': fields.char('Other Id', size=64),
1438
1439         'basic': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Basic Salary', digits=(14,2)),
1440         'gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Gross Salary', digits=(14,2)),
1441         'net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Net Salary', digits=(14,2)),
1442         'advantages_net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Deductions', digits=(14,2)),
1443         'advantages_gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Allowances', digits=(14,2)),
1444     }
1445 hr_employee()
1446
1447 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: