2010-06-05 06:39:12 +00:00
#-*- coding:utf-8 -*-
##############################################################################
#
2010-07-01 07:31:46 +00:00
# OpenERP, Open Source Management Solution
2010-06-05 06:39:12 +00:00
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
# d$
#
# This program is free software: you can redistribute it and/or modify
2010-10-28 06:54:18 +00:00
# it under the terms of the GNU Affero General Public License as published by
2010-06-05 06:39:12 +00:00
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2010-10-28 06:54:18 +00:00
# GNU Affero General Public License for more details.
2010-06-05 06:39:12 +00:00
#
2010-10-28 06:54:18 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-06-05 06:39:12 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
2010-06-05 07:32:16 +00:00
import time
2010-10-06 12:44:54 +00:00
from datetime import date
from datetime import datetime
from datetime import timedelta
2011-04-05 16:46:17 +00:00
from dateutil import relativedelta
2010-07-27 07:11:45 +00:00
2010-10-06 12:44:54 +00:00
import netsvc
2010-07-27 07:11:45 +00:00
from osv import fields , osv
2010-12-24 12:05:44 +00:00
import tools
2010-06-05 07:32:16 +00:00
from tools . translate import _
2010-11-03 08:04:42 +00:00
import decimal_precision as dp
2010-06-05 07:32:16 +00:00
2011-04-06 12:28:45 +00:00
from tools . safe_eval import safe_eval as eval
2011-03-22 10:35:31 +00:00
2010-06-06 18:26:08 +00:00
class hr_payroll_structure ( osv . osv ) :
"""
Salary structure used to defined
2010-06-09 14:38:22 +00:00
- Basic
2011-02-25 13:43:14 +00:00
- Allowances
2010-06-09 14:38:22 +00:00
- Deductions
2010-06-06 18:26:08 +00:00
"""
2010-10-26 12:23:32 +00:00
2010-06-06 18:26:08 +00:00
_name = ' hr.payroll.structure '
_description = ' Salary Structure '
2010-06-05 07:32:16 +00:00
_columns = {
2011-03-08 06:36:37 +00:00
' name ' : fields . char ( ' Name ' , size = 256 , required = True ) ,
2011-04-06 12:28:45 +00:00
' code ' : fields . char ( ' Reference ' , size = 64 , required = True ) ,
2011-03-07 07:18:29 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2010-06-06 16:29:11 +00:00
' note ' : fields . text ( ' Description ' ) ,
2011-03-07 07:18:29 +00:00
' parent_id ' : fields . many2one ( ' hr.payroll.structure ' , ' Parent ' ) ,
2010-06-05 07:32:16 +00:00
}
_defaults = {
' company_id ' : lambda self , cr , uid , context : \
self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ,
context = context ) . company_id . id ,
}
2010-07-01 07:31:46 +00:00
2010-06-05 07:32:16 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
2010-06-06 18:26:08 +00:00
"""
Create a new record in hr_payroll_structure model from existing one
@param cr : cursor to database
@param user : id of current user
@param id : list of record ids on which copy method executes
@param default : dict type contains the values to be override during copy of object
@param context : context arguments , like lang , time zone
2010-07-01 07:31:46 +00:00
2010-06-06 18:26:08 +00:00
@return : returns a id of newly created record
"""
2010-06-05 07:32:16 +00:00
default = {
2011-03-08 06:36:37 +00:00
' code ' : self . browse ( cr , uid , id , context = context ) . code + " (copy) " ,
2011-02-25 13:43:14 +00:00
' company_id ' : self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id . id
2010-06-05 07:32:16 +00:00
}
2010-10-27 13:10:19 +00:00
return super ( hr_payroll_structure , self ) . copy ( cr , uid , id , default , context = context )
2010-07-01 07:31:46 +00:00
2011-04-04 21:28:11 +00:00
def get_all_rules ( self , cr , uid , structure_ids , context = None ) :
"""
@param structure_ids : list of structure
@return : returns a list of tuple ( id , sequence ) of rules that are maybe to apply
"""
def recursive_search_of_rule ( rule_ids ) :
children_rules = [ ]
for rule in rule_ids :
if rule . child_ids :
children_rules + = recursive_search_of_rule ( rule . child_ids )
return [ ( r . id , r . sequence ) for r in rule_ids ] + children_rules
all_rules = [ ]
for struct in self . browse ( cr , uid , structure_ids , context = context ) :
all_rules + = recursive_search_of_rule ( struct . rule_ids )
return all_rules
def _get_parent_structure ( self , cr , uid , struct_ids , context = None ) :
if not struct_ids :
return [ ]
parent = [ ]
for struct in self . browse ( cr , uid , struct_ids , context = context ) :
if struct . parent_id :
parent . append ( struct . parent_id . id )
if parent :
parent = self . _get_parent_structure ( cr , uid , parent , context )
return parent + struct_ids
2010-06-06 18:26:08 +00:00
hr_payroll_structure ( )
2010-06-05 07:32:16 +00:00
class hr_contract ( osv . osv ) :
2010-06-06 19:09:53 +00:00
"""
Employee contract based on the visa , work permits
2011-02-25 13:43:14 +00:00
allows to configure different Salary structure
2010-06-06 19:09:53 +00:00
"""
2010-10-26 12:23:32 +00:00
2010-06-05 07:32:16 +00:00
_inherit = ' hr.contract '
_description = ' Employee Contract '
_columns = {
2010-10-27 12:49:59 +00:00
' struct_id ' : fields . many2one ( ' hr.payroll.structure ' , ' Salary Structure ' ) ,
2011-03-18 13:13:33 +00:00
' schedule_pay ' : fields . selection ( [
( ' monthly ' , ' Monthly ' ) ,
( ' quarterly ' , ' Quarterly ' ) ,
( ' semi-annually ' , ' Semi-annually ' ) ,
( ' annually ' , ' Annually ' ) ,
( ' weekly ' , ' Weekly ' ) ,
( ' bi-weekly ' , ' Bi-weekly ' ) ,
( ' bi-monthly ' , ' Bi-monthly ' ) ,
] , ' Scheduled Pay ' , select = True ) ,
2010-06-18 03:11:40 +00:00
}
2011-03-03 13:17:54 +00:00
2011-04-04 21:28:11 +00:00
def get_all_structures ( self , cr , uid , contract_ids , context = None ) :
"""
@param contract_ids : list of contracts
@return : the structures linked to the given contracts , ordered by hierachy ( parent = False first , then first level children and so on ) and without duplicata
"""
all_structures = [ ]
structure_ids = [ contract . struct_id . id for contract in self . browse ( cr , uid , contract_ids , context = context ) ]
return list ( set ( self . pool . get ( ' hr.payroll.structure ' ) . _get_parent_structure ( cr , uid , structure_ids , context = context ) ) )
2010-06-06 16:29:11 +00:00
2011-04-04 21:28:11 +00:00
hr_contract ( )
2010-06-06 16:29:11 +00:00
class contrib_register ( osv . osv ) :
'''
Contribution Register
'''
2010-10-26 12:23:32 +00:00
2010-06-06 16:29:11 +00:00
_name = ' hr.contibution.register '
_description = ' Contribution Register '
2010-07-01 07:31:46 +00:00
2010-06-06 16:29:11 +00:00
_columns = {
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = False ) ,
' name ' : fields . char ( ' Name ' , size = 256 , required = True , readonly = False ) ,
2011-04-07 10:12:26 +00:00
' register_line_ids ' : fields . one2many ( ' hr.payslip.line ' , ' register_id ' , ' Register Line ' , readonly = True ) ,
2010-06-06 16:29:11 +00:00
' note ' : fields . text ( ' Description ' ) ,
}
_defaults = {
' company_id ' : lambda self , cr , uid , context : \
self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ,
context = context ) . company_id . id ,
}
2011-03-08 06:36:37 +00:00
2010-06-06 16:29:11 +00:00
contrib_register ( )
2011-03-03 09:52:26 +00:00
class hr_salary_head ( osv . osv ) :
2010-06-09 14:38:22 +00:00
"""
2011-03-08 06:36:37 +00:00
HR Salary Head
2010-06-09 14:38:22 +00:00
"""
2010-07-01 07:31:46 +00:00
2011-03-03 09:52:26 +00:00
_name = ' hr.salary.head '
_description = ' Salary Head '
2010-06-09 14:38:22 +00:00
_columns = {
2011-03-07 07:18:29 +00:00
' name ' : fields . char ( ' Name ' , size = 64 , required = True , readonly = False ) ,
' code ' : fields . char ( ' Code ' , size = 64 , required = True , readonly = False ) ,
2011-04-04 11:32:19 +00:00
' parent_id ' : fields . many2one ( ' hr.salary.head ' , ' Parent ' , help = " Linking a salary head to its parent is used only for the reporting purpose. " ) ,
2010-06-09 14:54:48 +00:00
' note ' : fields . text ( ' Description ' ) ,
2010-06-09 14:38:22 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = False ) ,
2011-04-05 16:46:17 +00:00
' sequence ' : fields . integer ( ' Sequence ' , required = True , help = ' Display sequence order ' ) ,
2010-06-06 16:29:11 +00:00
}
2011-03-08 06:36:37 +00:00
2010-06-06 16:29:11 +00:00
_defaults = {
' company_id ' : lambda self , cr , uid , context : \
self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ,
context = context ) . company_id . id ,
2011-04-04 13:00:33 +00:00
' sequence ' : 5
2010-06-06 16:29:11 +00:00
}
2011-03-08 06:36:37 +00:00
2011-03-03 09:52:26 +00:00
hr_salary_head ( )
2010-06-06 16:29:11 +00:00
class hr_payslip ( osv . osv ) :
'''
Pay Slip
'''
2010-10-26 12:23:32 +00:00
2010-06-06 16:29:11 +00:00
_name = ' hr.payslip '
_description = ' Pay Slip '
2010-07-01 07:31:46 +00:00
2011-04-06 12:28:45 +00:00
#TODO unused for now, cause the field is commented but we want to put it back
# def _get_salary_rules(self, cr, uid, ids, field_names, arg=None, context=None):
# structure_obj = self.pool.get('hr.payroll.structure')
# contract_obj = self.pool.get('hr.contract')
# res = {}
# rules = []
# contracts = []
# structures = []
# rule_ids = []
# sorted_salary_heads = []
# for record in self.browse(cr, uid, ids, context=context):
# if record.contract_id:
# contracts.append(record.contract_id.id)
# else:
# contracts = self.get_contract(cr, uid, record.employee_id, record.date, context=context)
# for contract in contracts:
# structures = contract_obj.get_all_structures(cr, uid, [contract], context)
# res[record.id] = {}
# for struct in structures:
# rule_ids = structure_obj.get_all_rules(cr, uid, [struct], context=None)
# for rl in rule_ids:
# if rl[0] not in rules:
# rules.append(rl[0])
# cr.execute('''SELECT sr.id FROM hr_salary_rule as sr, hr_salary_head as sh
# WHERE sr.category_id = sh.id AND sr.id in %s ORDER BY sh.sequence''',(tuple(rules),))
# for x in cr.fetchall():
# sorted_salary_heads.append(x[0])
# for fn in field_names:
# if fn == 'details_by_salary_head':
# res[record.id] = {fn: sorted_salary_heads}
# return res
2011-03-14 06:16:21 +00:00
2010-06-06 16:29:11 +00:00
_columns = {
2011-04-06 12:28:45 +00:00
' struct_id ' : fields . many2one ( ' hr.payroll.structure ' , ' Structure ' , help = ' Defines the rules that have to be applied to this payslip, accordingly to the contract chosen. If you let empty the field contract, this field isn \' t mandatory anymore and thus the rules applied will be all the rules set on the structure of all contracts of the employee valid for the chosen period ' ) ,
2011-04-04 06:29:30 +00:00
' name ' : fields . char ( ' Description ' , size = 64 , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-06 12:28:45 +00:00
' number ' : fields . char ( ' Reference ' , size = 64 , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-04 06:29:30 +00:00
' employee_id ' : fields . many2one ( ' hr.employee ' , ' Employee ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-05 16:46:17 +00:00
' date_from ' : fields . date ( ' Date From ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , required = True ) ,
' date_to ' : fields . date ( ' Date To ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , required = True ) ,
2011-04-04 06:29:30 +00:00
' state ' : fields . selection ( [
( ' draft ' , ' Waiting for Verification ' ) ,
( ' hr_check ' , ' Waiting for HR Verification ' ) ,
( ' accont_check ' , ' Waiting for Account Verification ' ) ,
( ' confirm ' , ' Confirm Sheet ' ) ,
( ' done ' , ' Paid Salary ' ) ,
( ' cancel ' , ' Reject ' ) ,
] , ' State ' , select = True , readonly = True ,
help = ' * When the payslip is created the state is \' Waiting for verification \' . \
\n * It is varified by the user and payslip is sent for HR varification , the state is \' Waiting for HR Verification \' . \
\n * If HR varify the payslip , it is sent for account verification , the state is \' Waiting for Account Verification \' . \
\n * It is confirmed by the accountant and the state set to \' Confirm Sheet \' . \
\n * If the salary is paid then state is set to \' Paid Salary \' . \
\n * The \' Reject \' state is used when user cancel payslip. ' ) ,
' line_ids ' : fields . one2many ( ' hr.payslip.line ' , ' slip_id ' , ' Payslip Line ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-04 11:32:19 +00:00
' input_line_ids ' : fields . one2many ( ' hr.payslip.input ' , ' payslip_id ' , ' Payslip Inputs ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-04 06:29:30 +00:00
' paid ' : fields . boolean ( ' Made Payment Order ? ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
' note ' : fields . text ( ' Description ' ) ,
' contract_id ' : fields . many2one ( ' hr.contract ' , ' Contract ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2011-04-06 12:28:45 +00:00
#TODO put me back
# 'details_by_salary_head': fields.function(_get_salary_rules, method=True, type='one2many', relation='hr.salary.rule', string='Details by Salary Head', multi='details_by_salary_head'),
2010-06-06 16:29:11 +00:00
}
_defaults = {
2011-04-05 16:46:17 +00:00
' date_from ' : lambda * a : time . strftime ( ' % Y- % m-01 ' ) ,
' date_to ' : lambda * a : str ( datetime . now ( ) + relativedelta . relativedelta ( months = + 1 , day = 1 , days = - 1 ) ) [ : 10 ] ,
2011-03-14 08:57:50 +00:00
' state ' : ' draft ' ,
2010-06-06 16:29:11 +00:00
' company_id ' : lambda self , cr , uid , context : \
self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ,
context = context ) . company_id . id ,
}
2010-07-01 07:31:46 +00:00
2010-06-06 16:29:11 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
company_id = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id . id
default = {
2011-03-29 11:28:13 +00:00
' line_ids ' : [ ] ,
' move_ids ' : [ ] ,
' move_line_ids ' : [ ] ,
' move_payment_ids ' : [ ] ,
2011-03-22 10:35:31 +00:00
' company_id ' : company_id ,
2010-06-06 16:29:11 +00:00
' period_id ' : False ,
2011-03-29 07:26:48 +00:00
' basic_before_leaves ' : 0.0 ,
' basic_amount ' : 0.0
2010-06-06 16:29:11 +00:00
}
2011-03-14 11:22:27 +00:00
return super ( hr_payslip , self ) . copy ( cr , uid , id , default , context = context )
2010-07-01 07:31:46 +00:00
2010-07-27 07:11:45 +00:00
def set_to_draft ( self , cr , uid , ids , context = None ) :
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' draft ' } , context = context )
2010-07-01 07:31:46 +00:00
2010-07-27 07:11:45 +00:00
def cancel_sheet ( self , cr , uid , ids , context = None ) :
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' cancel ' } , context = context )
2010-07-01 07:31:46 +00:00
2010-07-27 07:11:45 +00:00
def account_check_sheet ( self , cr , uid , ids , context = None ) :
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' accont_check ' } , context = context )
2010-07-01 07:31:46 +00:00
2010-07-27 07:11:45 +00:00
def hr_check_sheet ( self , cr , uid , ids , context = None ) :
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' hr_check ' } , context = context )
2010-07-27 07:11:45 +00:00
def process_sheet ( self , cr , uid , ids , context = None ) :
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' paid ' : True , ' state ' : ' done ' } , context = context )
2010-10-26 12:23:32 +00:00
2010-11-19 13:48:01 +00:00
def verify_sheet ( self , cr , uid , ids , context = None ) :
2011-04-06 12:28:45 +00:00
#TODO clean me: this function should create the register lines accordingly to the rules computed (run the compute_sheet first)
# holiday_pool = self.pool.get('hr.holidays')
# salary_rule_pool = self.pool.get('hr.salary.rule')
# structure_pool = self.pool.get('hr.payroll.structure')
# register_line_pool = self.pool.get('hr.contibution.register.line')
# contracts = []
# structures = []
# rules = []
# lines = []
# sal_structures =[]
# for slip in self.browse(cr, uid, ids, context=context):
# if slip.contract_id:
# contracts.append(slip.contract_id)
# else:
# contracts = self.get_contract(cr, uid, slip.employee_id, slip.date, context=context)
# for contract in contracts:
# structures.append(contract.struct_id.id)
# leave_ids = self._get_leaves(cr, uid, slip.date, slip.employee_id, contract, context)
# for hday in holiday_pool.browse(cr, uid, leave_ids, context=context):
# salary_rules = salary_rule_pool.search(cr, uid, [('code', '=', hday.holiday_status_id.code)], context=context)
# rules += salary_rule_pool.browse(cr, uid, salary_rules, context=context)
# for structure in structures:
# sal_structures = self._get_parent_structure(cr, uid, [structure], context=context)
# for struct in sal_structures:
# lines = structure_pool.browse(cr, uid, struct, context=context).rule_ids
# for line in lines:
# if line.child_ids:
# for r in line.child_ids:
# lines.append(r)
# rules.append(line)
# base = {
# 'basic': slip.basic_amount,
# }
# if rules:
# for rule in rules:
# if rule.company_contribution:
# base[rule.code.lower()] = rule.amount
# if rule.register_id:
# for slip in slip.line_ids:
# if slip.category_id == rule.category_id:
# line_tot = slip.total
# value = eval(rule.computational_expression, base)
# company_contrib = self._compute(cr, uid, rule.id, value, employee, contract, context)
# reg_line = {
# 'name': rule.name,
# 'register_id': rule.register_id.id,
# 'code': rule.code,
# 'employee_id': slip.employee_id.id,
# 'emp_deduction': line_tot,
# 'comp_deduction': company_contrib,
# 'total': rule.amount + line_tot
# }
# register_line_pool.create(cr, uid, reg_line, context=context)
2011-04-04 09:06:56 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' confirm ' } , context = context )
2011-04-01 10:58:07 +00:00
2011-04-06 12:28:45 +00:00
#TODO move this function into hr_contract module, on hr.employee object
2011-04-05 16:46:17 +00:00
def get_contract ( self , cr , uid , employee , date_from , date_to , context = None ) :
"""
@param employee : browse record of employee
@param date_from : date field
@param date_to : date field
@return : returns the ids of all the contracts for the given employee that need to be considered for the given dates
"""
2011-03-29 13:32:43 +00:00
contract_obj = self . pool . get ( ' hr.contract ' )
2011-04-05 16:46:17 +00:00
clause = [ ]
2011-04-06 05:55:19 +00:00
#a contract is valid if it ends between the given dates
2011-04-05 16:46:17 +00:00
clause_1 = [ ' & ' , ( ' date_end ' , ' <= ' , date_to ) , ( ' date_end ' , ' >= ' , date_from ) ]
2011-04-06 05:55:19 +00:00
#OR if it starts between the given dates
2011-04-05 16:46:17 +00:00
clause_2 = [ ' & ' , ( ' date_start ' , ' <= ' , date_to ) , ( ' date_start ' , ' >= ' , date_from ) ]
#OR if it starts before the date_from and finish after the date_end (or never finish)
clause_3 = [ ( ' date_start ' , ' <= ' , date_from ) , ' | ' , ( ' date_end ' , ' = ' , False ) , ( ' date_end ' , ' >= ' , date ) ]
clause_final = [ ( ' employee_id ' , ' = ' , employee . id ) , ' | ' , ' | ' ] + clause_1 + clause_2 + clause_3
contract_ids = contract_obj . search ( cr , uid , [ ( ' employee_id ' , ' = ' , employee . id ) , ] , context = context )
2011-04-04 21:28:11 +00:00
return contract_ids
2010-07-01 07:31:46 +00:00
2011-04-05 16:46:17 +00:00
def compute_sheet ( self , cr , uid , ids , context = None ) :
2011-04-06 05:39:48 +00:00
slip_line_pool = self . pool . get ( ' hr.payslip.line ' )
2011-04-05 16:46:17 +00:00
for payslip in self . browse ( cr , uid , ids , context = context ) :
2011-04-06 05:55:19 +00:00
#delete old payslip lines
old_slipline_ids = slip_line_pool . search ( cr , uid , [ ( ' slip_id ' , ' = ' , payslip . id ) ] , context = context )
old_slipline_ids
if old_slipline_ids :
slip_line_pool . unlink ( cr , uid , old_slipline_ids , context = context )
2011-04-05 16:46:17 +00:00
if payslip . contract_id :
#set the list of contract for which the rules have to be applied
contract_ids = [ payslip . contract_id . id ]
2011-03-28 10:21:53 +00:00
else :
2011-04-05 16:46:17 +00:00
#if we don't give the contract, then the rules to apply should be for all current contracts of the employee
2011-04-06 05:39:48 +00:00
contract_ids = self . get_contract ( cr , uid , payslip . employee_id , payslip . date_from , payslip . date_to , context = context )
2011-04-06 12:54:21 +00:00
lines = [ ( 0 , 0 , line ) for line in self . pool . get ( ' hr.payslip ' ) . get_payslip_lines ( cr , uid , contract_ids , payslip . id , context = context ) ]
self . write ( cr , uid , [ payslip . id ] , { ' line_ids ' : lines } , context = context )
2011-04-05 16:46:17 +00:00
return True
def get_input_lines ( self , cr , uid , contract_ids , date_from , date_to , context = None ) :
"""
@param contract_ids : list of contract id
@return : returns a list of dict containing the input that should be applied for the given contract between date_from and date_to
"""
2011-04-06 12:28:45 +00:00
def was_on_leave ( employee_id , datetime_day , context = None ) :
res = False
day = datetime_day . strftime ( " % Y- % m- %d " )
holiday_ids = self . pool . get ( ' hr.holidays ' ) . search ( cr , uid , [ ( ' state ' , ' = ' , ' validate ' ) , ( ' employee_id ' , ' = ' , employee_id ) , ( ' type ' , ' = ' , ' remove ' ) , ( ' date_from ' , ' <= ' , day ) , ( ' date_to ' , ' >= ' , day ) ] )
if holiday_ids :
res = self . pool . get ( ' hr.holidays ' ) . browse ( cr , uid , holiday_ids , context = context ) [ 0 ] . holiday_status_id . name
return res
2011-04-05 16:46:17 +00:00
res = [ ]
for contract in self . pool . get ( ' hr.contract ' ) . browse ( cr , uid , contract_ids , context = context ) :
if not contract . working_hours :
#fill only if the contract as a working schedule linked
continue
2011-04-06 12:28:45 +00:00
attendances = {
' name ' : _ ( " Normal Working Days paid at 100 % " ) ,
' sequence ' : 1 ,
' code ' : ' WORK100 ' ,
' number_of_days ' : 0.0 ,
' number_of_hours ' : 0.0 ,
' contract_id ' : contract . id ,
}
leaves = { }
2011-04-05 16:46:17 +00:00
day_from = datetime . strptime ( date_from , " % Y- % m- %d " )
day_to = datetime . strptime ( date_to , " % Y- % m- %d " )
2011-04-06 12:28:45 +00:00
nb_of_days = ( day_to - day_from ) . days + 1
for day in range ( 0 , nb_of_days ) :
working_hours_on_day = self . pool . get ( ' resource.calendar ' ) . working_hours_on_day ( cr , uid , contract . working_hours , day_from + timedelta ( days = day ) , context )
if working_hours_on_day :
#the employee had to work
leave_type = was_on_leave ( contract . employee_id . id , day_from + timedelta ( days = day ) , context = context )
if leave_type :
#if he was on leave, fill the leaves dict
if leave_type in leaves :
leaves [ leave_type ] [ ' number_of_days ' ] + = 1.0
leaves [ leave_type ] [ ' number_of_hours ' ] + = working_hours_on_day
else :
leaves [ leave_type ] = {
' name ' : leave_type ,
' sequence ' : 5 ,
' code ' : leave_type ,
' number_of_days ' : 1.0 ,
' number_of_hours ' : working_hours_on_day ,
' contract_id ' : contract . id ,
}
else :
#add the input vals to tmp (increment if existing)
attendances [ ' number_of_days ' ] + = 1.0
attendances [ ' number_of_hours ' ] + = working_hours_on_day
leaves = [ value for key , value in leaves . items ( ) ]
res + = [ attendances ] + leaves
2011-04-05 16:46:17 +00:00
return res
2011-04-01 12:21:32 +00:00
2011-04-05 16:46:17 +00:00
def get_payslip_lines ( self , cr , uid , contract_ids , payslip_id , context ) :
2011-04-04 21:28:11 +00:00
result = [ ]
2011-04-06 12:28:45 +00:00
payslip = self . pool . get ( ' hr.payslip ' ) . browse ( cr , uid , payslip_id , context = context )
localdict = { ' rules ' : { } , ' heads ' : { } , ' payslip ' : payslip }
2011-04-06 05:55:19 +00:00
#get the ids of the structures on the contracts and their parent id as well
2011-04-04 21:28:11 +00:00
structure_ids = self . pool . get ( ' hr.contract ' ) . get_all_structures ( cr , uid , contract_ids , context = context )
#get the rules of the structure and thier children
rule_ids = self . pool . get ( ' hr.payroll.structure ' ) . get_all_rules ( cr , uid , structure_ids , context = context )
#run the rules by sequence
#import pdb;pdb.set_trace()
sorted_rule_ids = [ id for id , sequence in sorted ( rule_ids , key = lambda x : x [ 1 ] ) ]
2011-04-06 05:55:19 +00:00
2011-04-04 21:28:11 +00:00
for contract in self . pool . get ( ' hr.contract ' ) . browse ( cr , uid , contract_ids , context = context ) :
2011-04-06 12:28:45 +00:00
employee = contract . employee_id
localdict . update ( { ' employee ' : employee , ' contract ' : contract } )
2011-04-04 21:28:11 +00:00
for rule in self . pool . get ( ' hr.salary.rule ' ) . browse ( cr , uid , sorted_rule_ids , context = context ) :
#check if the rule can be applied
2011-04-07 12:40:50 +00:00
if self . pool . get ( ' hr.salary.rule ' ) . satisfy_condition ( cr , uid , rule . id , localdict , context = context ) :
2011-04-06 12:28:45 +00:00
amount = self . pool . get ( ' hr.salary.rule ' ) . compute_rule ( cr , uid , rule . id , localdict , context = context )
#set/overwrite the amount computed for this rule in the localdict
localdict [ ' rules ' ] [ rule . code ] = amount
#sum the amount for its salary head
localdict [ ' heads ' ] [ rule . category_id . code ] = rule . category_id . code in localdict [ ' heads ' ] and localdict [ ' heads ' ] [ rule . category_id . code ] + amount or amount
2011-04-04 21:28:11 +00:00
vals = {
' name ' : rule . name ,
' code ' : rule . code ,
' category_id ' : rule . category_id . id ,
' sequence ' : rule . sequence ,
' appears_on_payslip ' : rule . appears_on_payslip ,
' condition_select ' : rule . condition_select ,
' condition_python ' : rule . condition_python ,
' condition_range ' : rule . condition_range ,
' condition_range_min ' : rule . condition_range_min ,
' condition_range_max ' : rule . condition_range_max ,
' amount_select ' : rule . amount_select ,
' amount_fix ' : rule . amount_fix ,
' amount_python_compute ' : rule . amount_python_compute ,
' amount_percentage ' : rule . amount_percentage ,
' amount_percentage_base ' : rule . amount_percentage_base ,
' register_id ' : rule . register_id . id ,
' total ' : amount ,
' employee_id ' : contract . employee_id . id ,
}
result . append ( vals )
return result
2011-04-05 16:46:17 +00:00
def onchange_employee_id ( self , cr , uid , ids , date_from , date_to , employee_id = False , contract_id = False , context = None ) :
2011-02-25 13:43:14 +00:00
empolyee_obj = self . pool . get ( ' hr.employee ' )
2011-03-25 13:20:10 +00:00
contract_obj = self . pool . get ( ' hr.contract ' )
2011-04-06 12:28:45 +00:00
input_obj = self . pool . get ( ' hr.payslip.input ' )
2011-03-14 11:22:27 +00:00
2011-02-25 13:43:14 +00:00
if context is None :
context = { }
2011-04-06 12:28:45 +00:00
#delete old input lines
old_input_ids = ids and input_obj . search ( cr , uid , [ ( ' payslip_id ' , ' = ' , ids [ 0 ] ) ] , context = context ) or False
if old_input_ids :
input_obj . unlink ( cr , uid , old_input_ids , context = context )
2011-04-04 21:28:11 +00:00
#defaults
res = { ' value ' : {
2011-04-06 05:55:19 +00:00
' line_ids ' : [ ] ,
2011-04-06 12:28:45 +00:00
#'details_by_salary_head':[], TODO put me back
2011-04-04 21:28:11 +00:00
' name ' : ' ' ,
' contract_id ' : False ,
2011-04-06 05:55:19 +00:00
' struct_id ' : False ,
2011-04-04 21:28:11 +00:00
}
}
2011-02-25 13:43:14 +00:00
if not employee_id :
2011-04-04 21:28:11 +00:00
return res
2011-04-05 16:46:17 +00:00
ttyme = datetime . fromtimestamp ( time . mktime ( time . strptime ( date_from , " % Y- % m- %d " ) ) )
2011-04-04 21:28:11 +00:00
employee_id = empolyee_obj . browse ( cr , uid , employee_id , context = context )
res [ ' value ' ] . update ( {
' name ' : _ ( ' Salary Slip of %s for %s ' ) % ( employee_id . name , tools . ustr ( ttyme . strftime ( ' % B- % Y ' ) ) ) ,
' company_id ' : employee_id . company_id . id
} )
if not context . get ( ' contract ' , False ) :
#fill with the first contract of the employee
2011-04-05 16:46:17 +00:00
contract_ids = self . get_contract ( cr , uid , employee_id , date_from , date_to , context = context )
2011-04-04 21:28:11 +00:00
res [ ' value ' ] . update ( {
' struct_id ' : contract_ids and contract_obj . read ( cr , uid , contract_ids [ 0 ] , [ ' struct_id ' ] , context = context ) [ ' struct_id ' ] [ 0 ] or False ,
' contract_id ' : contract_ids and contract_ids [ 0 ] or False ,
2011-03-30 13:21:14 +00:00
} )
2011-04-04 21:28:11 +00:00
else :
2011-03-30 13:21:14 +00:00
if contract_id :
2011-04-05 16:46:17 +00:00
#set the list of contract for which the input have to be filled
2011-04-04 21:28:11 +00:00
contract_ids = [ contract_id ]
#fill the structure with the one on the selected contract
contract_record = contract_obj . browse ( cr , uid , contract_id , context = context )
2011-04-07 11:45:52 +00:00
res [ ' value ' ] . update ( { ' struct_id ' : contract_record . struct_id . id , ' contract_id ' : contract_id } )
2011-04-04 21:28:11 +00:00
else :
2011-04-05 16:46:17 +00:00
#if we don't give the contract, then the input to fill should be for all current contracts of the employee
contract_ids = self . get_contract ( cr , uid , employee_id , date_from , date_to , context = context )
2011-04-04 21:28:11 +00:00
if not contract_ids :
return res
2011-04-05 16:46:17 +00:00
#computation of the salary input
input_line_ids = self . get_input_lines ( cr , uid , contract_ids , date_from , date_to , context = context )
2011-04-04 21:28:11 +00:00
res [ ' value ' ] . update ( {
2011-04-05 16:46:17 +00:00
' input_line_ids ' : input_line_ids ,
2011-04-04 21:28:11 +00:00
} )
return res
2011-02-25 13:43:14 +00:00
2011-04-05 16:46:17 +00:00
def onchange_contract_id ( self , cr , uid , ids , date_from , date_to , employee_id = False , contract_id = False , context = None ) :
2011-03-30 13:21:14 +00:00
if context is None :
context = { }
2011-04-05 16:46:17 +00:00
res = { ' value ' : {
2011-04-06 05:55:19 +00:00
' line_ids ' : [ ] ,
' name ' : ' ' ,
2011-04-05 16:46:17 +00:00
}
}
2011-03-30 13:21:14 +00:00
context . update ( { ' contract ' : True } )
if not contract_id :
res [ ' value ' ] . update ( { ' struct_id ' : False } )
2011-04-05 16:46:17 +00:00
return self . onchange_employee_id ( cr , uid , ids , date_from = date_from , date_to = date_to , employee_id = employee_id , contract_id = contract_id , context = context )
2011-03-24 09:11:03 +00:00
2010-06-06 16:29:11 +00:00
hr_payslip ( )
2011-04-04 11:32:19 +00:00
class hr_payslip_input ( osv . osv ) :
'''
Payslip Input
'''
_name = ' hr.payslip.input '
_description = ' Payslip Input '
_columns = {
2011-04-04 13:00:33 +00:00
' name ' : fields . char ( ' Description ' , size = 256 , required = True ) ,
2011-04-04 11:32:19 +00:00
' payslip_id ' : fields . many2one ( ' hr.payslip ' , ' Pay Slip ' , required = True ) ,
' sequence ' : fields . integer ( ' Sequence ' , required = True , ) ,
' code ' : fields . char ( ' Code ' , size = 52 , required = True , help = " The code that can be used in the salary rules " ) ,
2011-04-04 21:28:11 +00:00
' number_of_days ' : fields . float ( ' Number of Days ' ) ,
' number_of_hours ' : fields . float ( ' Number of Hours ' ) ,
' contract_id ' : fields . many2one ( ' hr.contract ' , ' Contract ' , required = True , help = " The contract for which applied this input " ) ,
2011-04-04 11:32:19 +00:00
}
_order = ' payslip_id,sequence '
_defaults = {
' sequence ' : 10 ,
}
hr_payslip_input ( )
2011-02-24 13:12:20 +00:00
class hr_salary_rule ( osv . osv ) :
2010-07-01 07:31:46 +00:00
2011-02-24 13:12:20 +00:00
_name = ' hr.salary.rule '
_columns = {
2011-04-04 21:28:11 +00:00
' name ' : fields . char ( ' Name ' , size = 256 , required = True , readonly = False ) ,
' code ' : fields . char ( ' Code ' , size = 64 , required = True ) ,
' sequence ' : fields . integer ( ' Sequence ' , required = True , help = ' Use to arrange calculation sequence ' ) ,
' category_id ' : fields . many2one ( ' hr.salary.head ' , ' Salary Head ' , required = True ) ,
' active ' : fields . boolean ( ' Active ' , help = " If the active field is set to false, it will allow you to hide the salary rule without removing it. " ) ,
2011-03-02 10:17:36 +00:00
' appears_on_payslip ' : fields . boolean ( ' Appears on Payslip ' , help = " Used for the display of rule on payslip " ) ,
2011-04-04 21:28:11 +00:00
' parent_rule_id ' : fields . many2one ( ' hr.salary.rule ' , ' Parent Salary Rule ' , select = True ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = False ) ,
2011-04-06 12:28:45 +00:00
' condition_select ' : fields . selection ( [ ( ' none ' , ' Always True ' ) , ( ' range ' , ' Range ' ) , ( ' python ' , ' Python Expression ' ) ] , " Condition Based on " , required = True ) ,
2011-04-04 21:28:11 +00:00
' condition_range ' : fields . char ( ' Range Based on ' , size = 1024 , readonly = False , help = ' This will use to computer the % f ields 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 ' ) , #old name = conputional expression
2011-04-06 12:28:45 +00:00
' condition_python ' : fields . text ( ' Python Condition ' , required = True , readonly = False , help = ' Applied this rule for calculation if condition is true. You can specify condition like basic > 1000. ' ) , #old name = conditions
2011-03-03 09:52:26 +00:00
' condition_range_min ' : fields . float ( ' Minimum Range ' , required = False , help = " The minimum amount, applied for this rule. " ) ,
' condition_range_max ' : fields . float ( ' Maximum Range ' , required = False , help = " The maximum amount, applied for this rule. " ) ,
2011-04-04 21:28:11 +00:00
' amount_select ' : fields . selection ( [
( ' percentage ' , ' Percentage ( % ) ' ) ,
( ' fix ' , ' Fixed Amount ' ) ,
( ' code ' , ' Python Code ' ) ,
] , ' Amount Type ' , select = True , required = True , help = " The computation method for the rule amount. " ) ,
' amount_fix ' : fields . float ( ' Fixed Amount ' , digits_compute = dp . get_precision ( ' Account ' ) , ) ,
2011-04-06 12:28:45 +00:00
' amount_percentage ' : fields . float ( ' Percentage ( % ) ' , digits_compute = dp . get_precision ( ' Account ' ) , help = ' For example, enter 50.0 to apply a percentage of 50 % ' ) ,
2011-04-04 21:28:11 +00:00
' amount_python_compute ' : fields . text ( ' Python Code ' ) ,
2011-04-06 12:28:45 +00:00
' amount_percentage_base ' : fields . char ( ' Percentage based on ' , size = 1024 , required = False , readonly = False , help = ' result will be affected to a variable ' ) , #old name = expressiont
2011-03-15 13:33:22 +00:00
' child_ids ' : fields . one2many ( ' hr.salary.rule ' , ' parent_rule_id ' , ' Child Salary Rule ' ) ,
2011-03-03 12:44:06 +00:00
' register_id ' : fields . property (
' hr.contibution.register ' ,
type = ' many2one ' ,
relation = ' hr.contibution.register ' ,
string = " Contribution Register " ,
method = True ,
view_load = True ,
help = " Contribution register based on company " ,
required = False
) ,
2011-04-04 21:28:11 +00:00
' note ' : fields . text ( ' Description ' ) ,
2011-02-24 13:12:20 +00:00
}
_defaults = {
2011-04-06 12:28:45 +00:00
' amount_python_compute ' : '''
# Available variables:
#----------------------
# payslip: hr.payslip object
# employee: hr.employee object
# contract: hr.contract object
# rules: dictionary containing the previsouly computed rules. Keys are the rule codes.
# heads: dictionary containing the computed heads (sum of amount of all rules belonging to that head). Keys are the head codes.
# Note: returned value have to be set in the variable 'result'
result = contract . wage * 0.10 ''' ,
2011-04-07 11:45:52 +00:00
' condition_python ' :
2011-04-06 12:28:45 +00:00
'''
# Available variables:
#----------------------
# payslip: hr.payslip object
# employee: hr.employee object
# contract: hr.contract object
# rules: dictionary containing the previsouly computed rules. Keys are the rule codes.
# heads: dictionary containing the computed heads (sum of amount of all rules belonging to that head). Keys are the head codes.
# Note: returned value have to be set in the variable 'result'
2011-04-07 12:40:50 +00:00
result = rules [ ' NET ' ] > heads [ ' NET ' ] * 0.10 ''' ,
2011-04-06 12:28:45 +00:00
' condition_range ' : ' contract.wage ' ,
2011-03-03 12:44:06 +00:00
' sequence ' : 5 ,
' appears_on_payslip ' : True ,
' active ' : True ,
2011-03-07 09:47:10 +00:00
' company_id ' : lambda self , cr , uid , context : \
self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ,
context = context ) . company_id . id ,
2011-04-04 11:32:19 +00:00
' condition_select ' : ' none ' ,
2011-04-04 21:28:11 +00:00
' amount_select ' : ' fix ' ,
' amount_fix ' : 0.0 ,
' amount_percentage ' : 0.0 ,
2011-02-24 13:12:20 +00:00
}
2010-06-06 16:29:11 +00:00
2011-04-06 12:28:45 +00:00
#TODO should add some checks on the type of result (should be float)
def compute_rule ( self , cr , uid , rule_id , localdict , context = None ) :
"""
@param rule_id : id of rule to compute
@param localdict : dictionary containing the environement in which to compute the rule
@return : returns the result of computation as float
"""
2011-04-04 21:28:11 +00:00
rule = self . browse ( cr , uid , rule_id , context = context )
if rule . amount_select == ' fix ' :
return rule . amount_fix
elif rule . amount_select == ' percentage ' :
2011-04-06 12:28:45 +00:00
return rule . amount_percentage * eval ( rule . amount_percentage_base , localdict ) / 100
2011-04-04 21:28:11 +00:00
else :
2011-04-06 12:28:45 +00:00
eval ( rule . amount_python_compute , localdict , mode = ' exec ' , nocopy = True )
2011-04-04 21:28:11 +00:00
return localdict [ ' result ' ]
2011-04-07 12:40:50 +00:00
def satisfy_condition ( self , cr , uid , rule_id , localdict , context = None ) :
2011-04-04 21:28:11 +00:00
"""
@param rule_id : id of hr . salary . rule to be tested
@param contract_id : id of hr . contract to be tested
@return : returns True if the given rule match the condition for the given contract . Return False otherwise .
"""
rule = self . browse ( cr , uid , rule_id , context = context )
if rule . condition_select == ' none ' :
return True
elif rule . condition_select == ' range ' :
2011-04-06 12:28:45 +00:00
result = eval ( rule . condition_range , localdict )
return rule . condition_range_min < = result and result < = rule . condition_range_max or False
2011-04-04 21:28:11 +00:00
else : #python code
2011-04-06 12:28:45 +00:00
eval ( rule . condition_python , localdict , mode = ' exec ' , nocopy = True )
2011-04-04 21:28:11 +00:00
return ' result ' in localdict and localdict [ ' result ' ] or False
2011-02-24 13:12:20 +00:00
hr_salary_rule ( )
2010-10-26 12:23:32 +00:00
2011-04-04 21:28:11 +00:00
class hr_payslip_line ( osv . osv ) :
'''
Payslip Line
'''
_name = ' hr.payslip.line '
_inherit = ' hr.salary.rule '
_description = ' Payslip Line '
_order = ' sequence '
_columns = {
' slip_id ' : fields . many2one ( ' hr.payslip ' , ' Pay Slip ' , required = True ) ,
' employee_id ' : fields . many2one ( ' hr.employee ' , ' Employee ' , required = True ) ,
2011-04-06 12:28:45 +00:00
' total ' : fields . float ( ' Amount ' , digits_compute = dp . get_precision ( ' Account ' ) ) ,
2011-04-04 21:28:11 +00:00
' company_contrib ' : fields . float ( ' Company Contribution ' , readonly = True , digits_compute = dp . get_precision ( ' Account ' ) ) ,
}
hr_payslip_line ( )
2011-02-24 13:12:20 +00:00
class hr_payroll_structure ( osv . osv ) :
_inherit = ' hr.payroll.structure '
2010-06-06 16:29:11 +00:00
_columns = {
2011-03-14 11:22:27 +00:00
' rule_ids ' : fields . many2many ( ' hr.salary.rule ' , ' hr_structure_salary_rule_rel ' , ' struct_id ' , ' rule_id ' , ' Salary Rules ' ) ,
2010-06-06 16:29:11 +00:00
}
2011-04-01 10:58:07 +00:00
2011-02-24 13:12:20 +00:00
hr_payroll_structure ( )
2010-06-06 16:29:11 +00:00
class hr_employee ( osv . osv ) :
'''
Employee
'''
2010-10-26 12:23:32 +00:00
2011-03-14 08:58:54 +00:00
_inherit = ' hr.employee '
_description = ' Employee '
2011-04-06 12:28:45 +00:00
def _calculate_total_wage ( self , cr , uid , ids , name , args , context ) :
2011-03-09 10:10:41 +00:00
if not ids : return { }
2011-03-14 08:58:54 +00:00
res = { }
2011-03-09 10:10:41 +00:00
current_date = datetime . now ( ) . strftime ( ' % Y- % m- %d ' )
for employee in self . browse ( cr , uid , ids , context = context ) :
2011-03-14 08:58:54 +00:00
if not employee . contract_ids :
res [ employee . id ] = { ' basic ' : 0.0 }
2011-03-09 10:10:41 +00:00
continue
cr . execute ( ' SELECT SUM(wage) ' \
' FROM hr_contract ' \
' WHERE employee_id = %s ' \
2011-03-22 11:03:53 +00:00
' AND date_start <= %s ' \
2011-03-09 10:10:41 +00:00
' AND (date_end > %s OR date_end is NULL) ' ,
( employee . id , current_date , current_date ) )
result = dict ( cr . dictfetchone ( ) )
2011-03-14 08:58:54 +00:00
res [ employee . id ] = { ' basic ' : result [ ' sum ' ] }
return res
2010-10-26 12:23:32 +00:00
2010-06-06 16:29:11 +00:00
_columns = {
' slip_ids ' : fields . one2many ( ' hr.payslip ' , ' employee_id ' , ' Payslips ' , required = False , readonly = True ) ,
2011-04-06 12:28:45 +00:00
' total_wage ' : fields . function ( _calculate_total_wage , method = True , type = ' float ' , string = ' Total Basic Salary ' , digits_compute = dp . get_precision ( ' Account ' ) , help = " Sum of all current contract ' s wage of employee. " ) ,
2010-06-06 16:29:11 +00:00
}
2011-03-14 08:58:54 +00:00
2010-06-06 16:29:11 +00:00
hr_employee ( )
2011-03-24 09:11:03 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: