diff --git a/addons/hr/hr.py b/addons/hr/hr.py index e5541863922..fddbabc1901 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -139,6 +139,7 @@ class hr_employee(osv.osv): 'ssnid': fields.char('SSN No', size=32, help='Social Security Number'), 'sinid': fields.char('SIN No', size=32, help="Social Insurance Number"), 'identification_id': fields.char('Identification No', size=32), + 'otherid': fields.char('Other Id', size=64), 'gender': fields.selection([('male', 'Male'),('female', 'Female')], 'Gender'), 'marital': fields.many2one('hr.employee.marital.status', 'Marital Status'), 'department_id':fields.many2one('hr.department', 'Department'), diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index a3512b65588..96f753b4153 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -43,6 +43,7 @@ + diff --git a/addons/hr_contract/__openerp__.py b/addons/hr_contract/__openerp__.py index 380a4f1c696..67432c08a63 100644 --- a/addons/hr_contract/__openerp__.py +++ b/addons/hr_contract/__openerp__.py @@ -44,7 +44,9 @@ You can assign several contracts per employee. 'hr_contract_view.xml' ], 'demo_xml': [], - 'test': ['test/test_hr_contract.yml'], + 'test': [ + 'test/test_hr_contract.yml' + ], 'installable': True, 'active': False, 'certificate': '0046298028637', diff --git a/addons/hr_contract/hr_contract.py b/addons/hr_contract/hr_contract.py index 7ed2d16d6b7..403ba86e625 100644 --- a/addons/hr_contract/hr_contract.py +++ b/addons/hr_contract/hr_contract.py @@ -51,35 +51,28 @@ class hr_employee(osv.osv): hr_employee() -#Contract wage type period name -class hr_contract_wage_type_period(osv.osv): - _name='hr.contract.wage.type.period' - _description='Wage Period' - _columns = { - 'name': fields.char('Period Name', size=50, required=True, select=True), - 'factor_days': fields.float('Hours in the period', digits=(12,4), required=True, help='This field is used by the timesheet system to compute the price of an hour of work wased on the contract of the employee') - } - _defaults = { - 'factor_days': 168.0 - } -hr_contract_wage_type_period() +class hr_passport(osv.osv): + """ + Employee Passport + Passport based Contracts for Employees + """ -#Contract wage type (hourly, daily, monthly, ...) -class hr_contract_wage_type(osv.osv): - _name = 'hr.contract.wage.type' - _description = 'Wage Type' + _name = 'hr.passport' + _description = 'Passport Detail' _columns = { - 'name': fields.char('Wage Type Name', size=50, required=True, select=True), - 'period_id': fields.many2one('hr.contract.wage.type.period', 'Wage Period', required=True), - 'type': fields.selection([('gross','Gross'), ('net','Net')], 'Type', required=True), - 'factor_type': fields.float('Factor for hour cost', digits=(12,4), required=True, help='This field is used by the timesheet system to compute the price of an hour of work wased on the contract of the employee') + 'employee_id': fields.many2one('hr.employee', 'Employee', required=True), + 'name': fields.char('Passport No', size=64, required=True, readonly=False), + 'country_id': fields.many2one('res.country', 'Country of Issue', required=True), + 'address_id': fields.many2one('res.partner.address', 'Address', required=False), + 'date_issue': fields.date('Passport Issue Date', required=True), + 'date_expire': fields.date('Passport Expire Date', required=True), + 'note': fields.text('Description'), + 'contracts_ids': fields.one2many('hr.contract', 'passport_id', 'Contracts', required=False, readonly=True), } - _defaults = { - 'type': 'gross', - 'factor_type': 1.8 - } -hr_contract_wage_type() - + _sql_constraints = [ + ('passport_no_uniq', 'unique (employee_id, name)', 'The Passport No must be unique !'), + ] +hr_passport() class hr_contract_type(osv.osv): _name = 'hr.contract.type' @@ -103,15 +96,22 @@ class hr_contract(osv.osv): 'trial_date_start': fields.date('Trial Start Date'), 'trial_date_end': fields.date('Trial End Date'), 'working_hours': fields.many2one('resource.calendar','Working Schedule'), - 'wage_type_id': fields.many2one('hr.contract.wage.type', 'Wage Type', required=True), - 'wage': fields.float('Wage', digits=(16,2), required=True), + 'wage': fields.float('Wage', digits=(16,2), required=True, help="Basic Salary of the employee"), 'advantages': fields.text('Advantages'), - 'advantages_net': fields.float('Net Advantages Value', digits=(16,2)), - 'advantages_gross': fields.float('Gross Advantages Value', digits=(16,2)), 'notes': fields.text('Notes'), + 'permit_no': fields.char('Work Permit No', size=256, required=False, readonly=False), + 'passport_id': fields.many2one('hr.passport', 'Passport', required=False), + 'visa_no': fields.char('Visa No', size=64, required=False, readonly=False), + 'visa_expire': fields.date('Visa Expire Date'), } + + def _get_type(self, cr, uid, context=None): + type_ids = self.pool.get('hr.contract.type').search(cr, uid, [('name', '=', 'Employee')]) + return type_ids and type_ids[0] or False + _defaults = { 'date_start': lambda *a: time.strftime("%Y-%m-%d"), + 'type_id': _get_type } def _check_dates(self, cr, uid, ids, context=None): @@ -123,9 +123,6 @@ class hr_contract(osv.osv): _constraints = [ (_check_dates, 'Error! contract start-date must be lower then contract end-date.', ['date_start', 'date_end']) ] - - - hr_contract() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_contract/hr_contract_data.xml b/addons/hr_contract/hr_contract_data.xml index 6e7aca57d67..97200910188 100644 --- a/addons/hr_contract/hr_contract_data.xml +++ b/addons/hr_contract/hr_contract_data.xml @@ -1,43 +1,19 @@ - - - - daily - - - weekly - - - monthly - - - - - Monthly Gross Wage - gross - - - - Monthly Net Wage - net - - - - + Employee - - + + Worker - - + + Subcontractor - - + + diff --git a/addons/hr_contract/hr_contract_view.xml b/addons/hr_contract/hr_contract_view.xml index 67607af8146..ec3d21e092a 100644 --- a/addons/hr_contract/hr_contract_view.xml +++ b/addons/hr_contract/hr_contract_view.xml @@ -2,99 +2,83 @@ - - hr.contract.wage.type.view.form - hr.contract.wage.type + + + hr.passport.form + hr.passport form -
- - - - - + + + + + + + + + + + + + + + + + + + + +
- - - hr.contract.wage.type.view.tree - hr.contract.wage.type + + hr.passport.tree + hr.passport tree - - - - + + + + + + + - - hr.contract.wage.type.view.search - hr.contract.wage.type + + hr.passport.select + hr.passport search - - - - - - - - - - + + + + + + + + + + + + + + + - - - Wage Type - hr.contract.wage.type + + All Passports + hr.passport form - tree,form - [] - + + - + - - - - hr.contract.wage.type.period.view.form - hr.contract.wage.type.period - form - -
- - - - -
- - - hr.contract.wage.type.period.view.search - hr.contract.wage.type.period - search - - - - - - - - - - - Wage period - hr.contract.wage.type.period - form - [] - - - - hr.hr.employee.view.form2 @@ -132,9 +116,9 @@ - - - + + + @@ -143,11 +127,11 @@ - - + + - + @@ -161,33 +145,42 @@
- - - - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
@@ -203,11 +196,9 @@ - - + - @@ -270,11 +261,12 @@ - diff --git a/addons/hr_contract/security/ir.model.access.csv b/addons/hr_contract/security/ir.model.access.csv index 7c87e3c36b3..254cfe716aa 100644 --- a/addons/hr_contract/security/ir.model.access.csv +++ b/addons/hr_contract/security/ir.model.access.csv @@ -1,7 +1,7 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"access_hr_contract_wage_type_period_user","hr.contract.wage.type.period.user","model_hr_contract_wage_type_period","base.group_hr_user",1,1,1,1 -"access_hr_contract_wage_type_user","hr.contract.wage.type.user","model_hr_contract_wage_type","base.group_hr_user",1,1,1,1 "access_hr_resource_manager","hr.employee.resource.manager","resource.model_resource_resource","base.group_hr_manager",1,1,1,1 "access_hr_resource_calendar_user","hr.employee.resource.calendar.user","resource.model_resource_calendar","base.group_hr_user",1,1,1,1 "access_hr_contract_type_manager","hr.contract.type.manager","model_hr_contract_type","base.group_hr_manager",1,1,1,1 "access_hr_contract_manager","hr.contract.manager","model_hr_contract","base.group_hr_manager",1,1,1,1 +"access_hr_passport_manager","hr.passport.manager","model_hr_passport","base.group_hr_manager",1,1,1,1 +"access_hr_passport","hr.passport","model_hr_passport","base.group_hr_user",1,1,1,1 diff --git a/addons/hr_payroll/__openerp__.py b/addons/hr_payroll/__openerp__.py index 69cba040d6b..87fe437a1db 100644 --- a/addons/hr_payroll/__openerp__.py +++ b/addons/hr_payroll/__openerp__.py @@ -52,16 +52,16 @@ Generic Payroll system. 'hr_payroll_view.xml', 'hr_payroll_workflow.xml', 'hr_payroll_sequence.xml', - 'hr_paroll_report.xml', + 'hr_payroll_report.xml', 'hr_payroll_data.xml', 'security/ir.model.access.csv', 'wizard/hr_payroll_employees_detail.xml', 'wizard/hr_payroll_year_salary.xml', ], 'test': [ - 'test/payslip.yml', - 'test/payment_advice.yml', - 'test/payroll_register.yml', +# 'test/payslip.yml', +# 'test/payment_advice.yml', +# 'test/payroll_register.yml', # 'test/hr_payroll_report.yml', ], 'demo_xml': [ diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index b29bd1312ac..2538b9d539c 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -24,6 +24,7 @@ import time from datetime import date from datetime import datetime from datetime import timedelta +from dateutil import relativedelta import netsvc from osv import fields, osv @@ -31,72 +32,24 @@ import tools from tools.translate import _ import decimal_precision as dp -def prev_bounds(cdate=False): - when = date.fromtimestamp(time.mktime(time.strptime(cdate,"%Y-%m-%d"))) - this_first = date(when.year, when.month, 1) - month = when.month + 1 - year = when.year - if month > 12: - month = 1 - year += 1 - next_month = date(year, month, 1) - prev_end = next_month - timedelta(days=1) - return this_first, prev_end - -class hr_contract_wage_type(osv.osv): - """ - Wage types - Basic = Basic Salary - Grows = Basic + Allowances - New = Grows - Deductions - """ - - _inherit = 'hr.contract.wage.type' - _columns = { - 'type': fields.selection([('basic','Basic'), ('gross','Gross'), ('net','Net')], 'Type', required=True), - } - -hr_contract_wage_type() - -class hr_passport(osv.osv): - """ - Employee Passport - Passport based Contratacts for Employees - """ - - _name = 'hr.passport' - _description = 'Passport Detail' - _columns = { - 'employee_id':fields.many2one('hr.employee', 'Employee', required=True), - 'name':fields.char('Passport No', size=64, required=True, readonly=False), - 'country_id':fields.many2one('res.country', 'Country of Issue', required=True), - 'address_id':fields.many2one('res.partner.address', 'Address', required=False), - 'date_issue': fields.date('Passport Issue Date', required=True), - 'date_expire': fields.date('Passport Expire Date', required=True), - 'contracts_ids':fields.one2many('hr.contract', 'passport_id', 'Contracts', required=False, readonly=True), - 'note': fields.text('Description'), - } - _sql_constraints = [ - ('passport_no_uniq', 'unique (employee_id, name)', 'The Passport No must be unique !'), - ] -hr_passport() +from tools.safe_eval import safe_eval as eval class hr_payroll_structure(osv.osv): """ Salary structure used to defined - Basic - - Allowlance + - Allowances - Deductions """ _name = 'hr.payroll.structure' _description = 'Salary Structure' _columns = { - 'name':fields.char('Name', size=256, required=True, readonly=False), - 'code':fields.char('Code', size=64, required=True, readonly=False), - 'line_ids':fields.one2many('hr.payslip.line', 'function_id', 'Salary Structure', required=False), - 'company_id':fields.many2one('res.company', 'Company', required=False), + 'name':fields.char('Name', size=256, required=True), + 'code':fields.char('Reference', size=64, required=True), + 'company_id':fields.many2one('res.company', 'Company', required=True), 'note': fields.text('Description'), + 'parent_id':fields.many2one('hr.payroll.structure', 'Parent'), } _defaults = { 'company_id': lambda self, cr, uid, context: \ @@ -115,473 +68,75 @@ class hr_payroll_structure(osv.osv): @return: returns a id of newly created record """ - code = self.browse(cr, uid, id, context=context).code default = { - 'code':code+"(copy)", - 'company_id':self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + 'code': self.browse(cr, uid, id, context=context).code + "(copy)", + 'company_id': self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id } return super(hr_payroll_structure, self).copy(cr, uid, id, default, context=context) + 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 + hr_payroll_structure() class hr_contract(osv.osv): """ Employee contract based on the visa, work permits - allowas to configure different Salary structure + allows to configure different Salary structure """ - def compute_basic(self, cr, uid, ids, context=None): - res = {} - if context is None: - context = {} - ids += context.get('employee_structure', []) - - slip_line_pool = self.pool.get('hr.payslip.line') - - for contract in self.browse(cr, uid, ids, context=context): - all_per = 0.0 - ded_per = 0.0 - all_fix = 0.0 - ded_fix = 0.0 - obj = {'basic':0.0} - update = {} - if contract.wage_type_id.type == 'gross': - obj['gross'] = contract.wage - update['gross'] = contract.wage - if contract.wage_type_id.type == 'net': - obj['net'] = contract.wage - update['net'] = contract.wage - if contract.wage_type_id.type == 'basic': - obj['basic'] = contract.wage - update['basic'] = contract.wage - - sal_type = contract.wage_type_id.type -# function = contract.struct_id.id - lines = contract.struct_id.line_ids - if not contract.struct_id: - res[contract.id] = obj['basic'] - continue - - ad = [] - for line in lines: - cd = line.code.lower() - obj[cd] = line.amount or 0.0 - - for line in lines: - if line.category_id.code in ad: - continue - ad.append(line.category_id.code) - cd = line.category_id.code.lower() - calculate = False - try: - exp = line.category_id.condition - calculate = eval(exp, obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - - if not calculate: - continue - - percent = 0.0 - value = 0.0 - base = False -# company_contrib = 0.0 - base = line.category_id.base - - try: - #Please have a look at the configuration guide. - amt = eval(base, obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - - if sal_type in ('gross', 'net'): - if line.amount_type == 'per': - percent = line.amount - if amt > 1: - value = percent * amt - elif amt > 0 and amt <= 1: - percent = percent * amt - if value > 0: - percent = 0.0 - elif line.amount_type == 'fix': - value = line.amount - elif line.amount_type == 'func': - value = slip_line_pool.execute_function(cr, uid, line.id, amt, context) - line.amount = value - else: - if line.amount_type in ('fix', 'per'): - value = line.amount - elif line.amount_type == 'func': - value = slip_line_pool.execute_function(cr, uid, line.id, amt, context) - line.amount = value - if line.type == 'allowance': - all_per += percent - all_fix += value - elif line.type == 'deduction': - ded_per += percent - ded_fix += value - if sal_type in ('gross', 'net'): - sal = contract.wage - if sal_type == 'net': - sal += ded_fix - sal -= all_fix - per = 0.0 - if sal_type == 'net': - per = (all_per - ded_per) - else: - per = all_per - if per <=0: - per *= -1 - final = (per * 100) + 100 - basic = (sal * 100) / final - else: - basic = contract.wage - - res[contract.id] = basic - - return res - - def check_vals(self, val1, val2): - if val1 == val2 and val1 == 0: - return True - return False - - def _calculate_salary(self, cr, uid, ids, field_names, arg, context=None): - res = self.compute_basic(cr, uid, ids, context=context) - vals = {} - for rs in self.browse(cr, uid, ids, context=context): - allow = 0.0 - deduct = 0.0 - others = 0.0 - obj = {'basic':res[rs.id], 'gross':0.0, 'net':0.0} - if rs.wage_type_id.type == 'gross': - obj['gross'] = rs.wage - if rs.wage_type_id.type == 'net': - obj['net'] = rs.net - - if not rs.struct_id: - if self.check_vals(obj['basic'], obj['gross']): - obj['gross'] = obj['basic'] = obj['net'] - elif self.check_vals(obj['gross'], obj['net']): - obj['gross']= obj['net'] = obj['basic'] - elif self.check_vals(obj['net'], obj['basic']): - obj['net'] = obj['basic'] = obj['gross'] - record = { - 'advantages_gross':0.0, - 'advantages_net':0.0, - 'basic':obj['basic'], - 'gross':obj['gross'], - 'net':obj['net'] - } - vals[rs.id] = record - continue - - for line in rs.struct_id.line_ids: - amount = 0.0 - if line.amount_type == 'per': - try: - amount = line.amount * eval(str(line.category_id.base), obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - elif line.amount_type in ('fix', 'func'): - amount = line.amount - cd = line.category_id.code.lower() - obj[cd] = amount - - if line.type == 'allowance': - allow += amount - elif line.type == 'deduction': - deduct += amount - elif line.type == 'advance': - others += amount - elif line.type == 'loan': - others += amount - elif line.type == 'otherpay': - others += amount - record = { - 'advantages_gross':round(allow), - 'advantages_net':round(deduct), - 'basic':obj['basic'], - 'gross':round(obj['basic'] + allow), - 'net':round(obj['basic'] + allow - deduct) - } - vals[rs.id] = record - - return vals - _inherit = 'hr.contract' _description = 'Employee Contract' _columns = { - 'permit_no': fields.char('Work Permit No', size=256, required=False, readonly=False), - 'passport_id': fields.many2one('hr.passport', 'Passport No', required=False), - 'visa_no': fields.char('Visa No', size=64, required=False, readonly=False), - 'visa_expire': fields.date('Visa Expire Date'), 'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure'), - 'working_days_per_week': fields.integer('Working Days', help="No of Working days / week for an employee"), - 'basic': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Basic Salary', digits=(14,2)), - 'gross': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Gross Salary', digits=(14,2)), - 'net': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Net Salary', digits=(14,2)), - 'advantages_net': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Deductions', digits=(14,2)), - 'advantages_gross': fields.function(_calculate_salary, method=True, store=True, multi='dc', type='float', string='Allowances', digits=(14,2)), - } - _defaults = { - 'working_days_per_week': lambda *a: 5, + '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), } + + 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))) + hr_contract() -class payroll_register(osv.osv): - """ - Payroll Register - """ - - _name = 'hr.payroll.register' - _description = 'Payroll Register' - - def _calculate(self, cr, uid, ids, field_names, arg, context=None): - res = {} - allounce = 0.0 - deduction = 0.0 - net = 0.0 - grows = 0.0 - for register in self.browse(cr, uid, ids, context=context): - for slip in register.line_ids: - allounce += slip.allounce - deduction += slip.deduction - net += slip.net - grows += slip.grows - - res[register.id] = { - 'allounce':allounce, - 'deduction':deduction, - 'net':net, - 'grows':grows - } - return res - - _columns = { - 'name':fields.char('Name', size=64, required=True, readonly=False), - 'date': fields.date('Date', required=True), - 'number':fields.char('Number', size=64, required=False, readonly=True), - 'line_ids':fields.one2many('hr.payslip', 'register_id', 'Payslips', required=False), - 'state':fields.selection([ - ('new','New Slip'), - ('draft','Wating for Verification'), - ('hr_check','Wating for HR Verification'), - ('accont_check','Wating for Account Verification'), - ('confirm','Confirm Sheet'), - ('done','Paid Salary'), - ('cancel','Reject'), - ],'State', select=True, readonly=True), - 'active':fields.boolean('Active', required=False), - 'company_id':fields.many2one('res.company', 'Company', required=False), - 'grows': fields.function(_calculate, method=True, store=True, multi='dc', string='Gross Salary', type='float', digits=(16, 4)), - 'net': fields.function(_calculate, method=True, store=True, multi='dc', string='Net Salary', digits=(16, 4)), - 'allounce': fields.function(_calculate, method=True, store=True, multi='dc', string='Allowance', digits=(16, 4)), - 'deduction': fields.function(_calculate, method=True, store=True, multi='dc', string='Deduction', digits=(16, 4)), - 'note': fields.text('Description'), - 'bank_id':fields.many2one('res.bank', 'Bank', required=False, help="Select the Bank Address from whcih the salary is going to be paid"), - } - - _defaults = { - 'date': lambda *a: time.strftime('%Y-%m-%d'), - 'state': lambda *a: 'new', - 'active': lambda *a: True, - 'company_id': lambda self, cr, uid, context: \ - self.pool.get('res.users').browse(cr, uid, uid, - context=context).company_id.id, - } - - def compute_sheet(self, cr, uid, ids, context=None): - emp_pool = self.pool.get('hr.employee') - slip_pool = self.pool.get('hr.payslip') - wf_service = netsvc.LocalService("workflow") - if context is None: - context = {} - - vals = self.browse(cr, uid, ids[0], context=context) - emp_ids = emp_pool.search(cr, uid, [], context=context) - - for emp in emp_pool.browse(cr, uid, emp_ids, context=context): - old_slips = slip_pool.search(cr, uid, [('employee_id','=', emp.id), ('date','=',vals.date)], context=context) - if old_slips: - slip_pool.write(cr, uid, old_slips, {'register_id':ids[0]}, context=context) - for sid in old_slips: - wf_service.trg_validate(uid, 'hr.payslip', sid, 'compute_sheet', cr) - else: - res = { - 'employee_id':emp.id, - 'basic':0.0, - 'register_id':ids[0], - 'name':vals.name, - 'date':vals.date, - } - slip_id = slip_pool.create(cr, uid, res, context=context) - wf_service.trg_validate(uid, 'hr.payslip', slip_id, 'compute_sheet', cr) - - number = self.pool.get('ir.sequence').get(cr, uid, 'salary.register') - self.write(cr, uid, ids, {'state':'draft', 'number':number}, context=context) - return True - - def set_to_draft(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'draft'}, context=context) - return True - - def cancel_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'cancel'}, context=context) - return True - - def verify_sheet(self, cr, uid, ids, context=None): - slip_pool = self.pool.get('hr.payslip') - - for id in ids: - sids = slip_pool.search(cr, uid, [('register_id','=',id)], context=context) - wf_service = netsvc.LocalService("workflow") - for sid in sids: - wf_service.trg_validate(uid, 'hr.payslip', sid, 'verify_sheet', cr) - - self.write(cr, uid, ids, {'state':'hr_check'}, context=context) - return True - - def final_verify_sheet(self, cr, uid, ids, context=None): - slip_pool = self.pool.get('hr.payslip') - advice_pool = self.pool.get('hr.payroll.advice') - advice_line_pool = self.pool.get('hr.payroll.advice.line') - sequence_pool = self.pool.get('ir.sequence') - users_pool = self.pool.get('res.users') - - for id in ids: - sids = slip_pool.search(cr, uid, [('register_id','=',id), ('state','=','hr_check')], context=context) - wf_service = netsvc.LocalService("workflow") - for sid in sids: - wf_service.trg_validate(uid, 'hr.payslip', sid, 'final_verify_sheet', cr) - - company_name = users_pool.browse(cr, uid, uid, context=context).company_id.name - for reg in self.browse(cr, uid, ids, context=context): - advice = { - 'name': 'Payment Advice from %s' % (company_name), - 'number': sequence_pool.get(cr, uid, 'payment.advice'), - 'register_id':reg.id - } - pid = advice_pool.create(cr, uid, advice, context=context) - - for slip in reg.line_ids: - if not slip.employee_id.bank_account_id: - raise osv.except_osv(_('Error !'), _('Please define bank account for the %s employee') % (slip.employee_id.name)) - pline = { - 'advice_id':pid, - 'name':slip.employee_id.bank_account_id.acc_number, - 'employee_id':slip.employee_id.id, - 'amount':slip.other_pay + slip.net, - 'bysal':slip.net - } - id = advice_line_pool.create(cr, uid, pline, context=context) - - self.write(cr, uid, ids, {'state':'confirm'}, context=context) - return True - - def process_sheet(self, cr, uid, ids, context=None): - slip_pool = self.pool.get('hr.payslip') - for id in ids: - sids = slip_pool.search(cr, uid, [('register_id','=',id), ('state','=','confirm')], context=context) - wf_service = netsvc.LocalService("workflow") - for sid in sids: - wf_service.trg_validate(uid, 'hr.payslip', sid, 'process_sheet', cr) - - self.write(cr, uid, ids, {'state':'done'}, context=context) - return True - -payroll_register() - -class payroll_advice(osv.osv): - ''' - Bank Advice Note - ''' - - _name = 'hr.payroll.advice' - _description = 'Bank Advice Note' - _columns = { - 'register_id':fields.many2one('hr.payroll.register', 'Payroll Register', required=False), - 'name':fields.char('Name', size=2048, required=True, readonly=False), - 'note': fields.text('Description'), - 'date': fields.date('Date'), - 'state':fields.selection([ - ('draft','Draft Sheet'), - ('confirm','Confirm Sheet'), - ('cancel','Reject'), - ],'State', select=True, readonly=True), - 'number':fields.char('Number', size=64, required=False, readonly=True), - 'line_ids':fields.one2many('hr.payroll.advice.line', 'advice_id', 'Employee Salary', required=False), - 'chaque_nos':fields.char('Chaque Nos', size=256, required=False, readonly=False), - 'company_id':fields.many2one('res.company', 'Company', required=False), - '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"), - } - _defaults = { - 'date': lambda *a: time.strftime('%Y-%m-%d'), - 'state': lambda *a: 'draft', - 'company_id': lambda self, cr, uid, context: \ - self.pool.get('res.users').browse(cr, uid, uid, - context=context).company_id.id, - } - - def confirm_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'confirm'}, context=context) - return True - - def set_to_draft(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'draft'}, context=context) - return True - - def cancel_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'cancel'}, context=context) - return True - - def onchange_company_id(self, cr, uid, ids, company_id=False, context=None): - res = {} - if company_id: - company = self.pool.get('res.company').browse(cr, uid, company_id, context=context) - if company.partner_id.bank_ids: - res.update({'bank': company.partner_id.bank_ids[0].bank.name}) - return { - 'value':res - } -payroll_advice() - -class payroll_advice_line(osv.osv): - ''' - Bank Advice Lines - ''' - - _name = 'hr.payroll.advice.line' - _description = 'Bank Advice Lines' - _columns = { - 'advice_id':fields.many2one('hr.payroll.advice', 'Bank Advice', required=False), - 'name':fields.char('Bank Account A/C', size=64, required=True, readonly=False), - 'employee_id':fields.many2one('hr.employee', 'Employee', required=True), - 'amount': fields.float('Amount', digits=(16, 4)), - 'bysal': fields.float('By Salary', digits=(16, 4)), - 'flag':fields.char('D/C', size=8, required=True, readonly=False), - } - _defaults = { - 'flag': lambda *a: 'C', - } - - def onchange_employee_id(self, cr, uid, ids, ddate, employee_id, context=None): - vals = {} - slip_pool = self.pool.get('hr.payslip') - if employee_id: - dates = prev_bounds(ddate) - sids = False - sids = slip_pool.search(cr, uid, [('paid','=',False),('state','=','confirm'),('date','>=',dates[0]), ('employee_id','=',employee_id), ('date','<=',dates[1])], context=context) - if sids: - slip = slip_pool.browse(cr, uid, sids[0], context=context) - vals['name'] = slip.employee_id.identification_id - vals['amount'] = slip.net + slip.other_pay - vals['bysal'] = slip.net - return { - 'value':vals - } -payroll_advice_line() - +#TODO clean this class class contrib_register(osv.osv): ''' Contribution Register @@ -590,29 +145,29 @@ class contrib_register(osv.osv): _name = 'hr.contibution.register' _description = 'Contribution Register' - def _total_contrib(self, cr, uid, ids, field_names, arg, context=None): - line_pool = self.pool.get('hr.contibution.register.line') - - res = {} - for cur in self.browse(cr, uid, ids, context=context): - current = line_pool.search(cr, uid, [('register_id','=',cur.id)], context=context) - e_month = 0.0 - c_month = 0.0 - for i in line_pool.browse(cr, uid, current, context=context): - e_month += i.emp_deduction - c_month += i.comp_deduction - res[cur.id]={ - 'monthly_total_by_emp':e_month, - 'monthly_total_by_comp':c_month, - } - return res +# def _total_contrib(self, cr, uid, ids, field_names, arg, context=None): +# line_pool = self.pool.get('hr.contibution.register.line') +# +# res = {} +# for cur in self.browse(cr, uid, ids, context=context): +# current = line_pool.search(cr, uid, [('register_id','=',cur.id)], context=context) +# e_month = 0.0 +# c_month = 0.0 +# for i in line_pool.browse(cr, uid, current, context=context): +# e_month += i.emp_deduction +# c_month += i.comp_deduction +# res[cur.id]={ +# 'monthly_total_by_emp':e_month, +# 'monthly_total_by_comp':c_month, +# } +# return res _columns = { 'company_id':fields.many2one('res.company', 'Company', required=False), 'name':fields.char('Name', size=256, required=True, readonly=False), 'register_line_ids':fields.one2many('hr.contibution.register.line', 'register_id', 'Register Line', readonly=True), - 'monthly_total_by_emp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Employee', digits=(16, 4)), - 'monthly_total_by_comp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Company', digits=(16, 4)), +# 'monthly_total_by_emp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Employee', digits=(16, 4)), +# 'monthly_total_by_comp': fields.function(_total_contrib, method=True, multi='dc', string='Total By Company', digits=(16, 4)), 'note': fields.text('Description'), } _defaults = { @@ -620,8 +175,10 @@ class contrib_register(osv.osv): self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id, } + contrib_register() +#TODO clean this class class contrib_register_line(osv.osv): ''' Contribution Register Line @@ -631,10 +188,10 @@ class contrib_register_line(osv.osv): _description = 'Contribution Register Line' def _total(self, cr, uid, ids, field_names, arg, context=None): - res={} + res = {} for line in self.browse(cr, uid, ids, context=context): res[line.id] = line.emp_deduction + line.comp_deduction - return res + return res _columns = { 'name':fields.char('Name', size=256, required=True, readonly=False), @@ -651,61 +208,457 @@ class contrib_register_line(osv.osv): } contrib_register_line() -class payment_category(osv.osv): +class hr_salary_head(osv.osv): """ - Allowance, Deduction Heads - House Rent Allowance, Medical Allowance, Food Allowance - Professional Tax, Advance TDS, Providend Funds, etc + HR Salary Head """ - _name = 'hr.allounce.deduction.categoty' - _description = 'Allowance Deduction Heads' + _name = 'hr.salary.head' + _description = 'Salary Head' _columns = { - 'name':fields.char('Category Name', size=64, required=True, readonly=False), - 'code':fields.char('Category Code', size=64, required=True, readonly=False), - 'type':fields.selection([ - ('allowance','Allowance'), - ('deduction','Deduction'), - ('leaves','Leaves'), - ('advance','Advance'), - ('loan','Loan'), - ('installment','Loan Installment'), - ('otherpay','Other Payment'), - ('otherdeduct','Other Deduction'), - ],'Type', select=True, required=True), - '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'), - 'condition':fields.char('Condition', size=1024, required=True, readonly=False, help='Applied this head for calculation if condition is true'), - 'sequence': fields.integer('Sequence', required=True, help='Use to arrange calculation sequence'), + 'name':fields.char('Name', size=64, required=True, readonly=False), + 'code':fields.char('Code', size=64, required=True, readonly=False), + 'parent_id':fields.many2one('hr.salary.head', 'Parent', help="Linking a salary head to its parent is used only for the reporting purpose."), 'note': fields.text('Description'), - 'user_id':fields.char('User', size=64, required=False, readonly=False), - 'state':fields.char('Label', size=64, required=False, readonly=False), 'company_id':fields.many2one('res.company', 'Company', required=False), - 'contribute_ids':fields.one2many('company.contribution', 'category_id', 'Contributions', required=False), + 'sequence': fields.integer('Sequence', required=True, help='Display sequence order'), + } + + _defaults = { + 'company_id': lambda self, cr, uid, context: \ + self.pool.get('res.users').browse(cr, uid, uid, + context=context).company_id.id, + 'sequence': 5 + } + +hr_salary_head() + +class hr_payslip(osv.osv): + ''' + Pay Slip + ''' + + _name = 'hr.payslip' + _description = 'Pay Slip' + +#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 + + _columns = { + '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'), + 'name': fields.char('Description', size=64, required=False, readonly=True, states={'draft': [('readonly', False)]}), + 'number': fields.char('Reference', size=64, required=False, readonly=True, states={'draft': [('readonly', False)]}), + 'employee_id': fields.many2one('hr.employee', 'Employee', required=True, readonly=True, states={'draft': [('readonly', False)]}), + '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), + '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)]}), + 'input_line_ids': fields.one2many('hr.payslip.input', 'payslip_id', 'Payslip Inputs', required=False, readonly=True, states={'draft': [('readonly', False)]}), + '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)]}), + #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'), } _defaults = { - 'condition': lambda *a: 'True', - 'base': lambda *a:'basic', - 'sequence': lambda *a:5, + '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], + 'state': 'draft', 'company_id': lambda self, cr, uid, context: \ self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id, } -payment_category() -class company_contribution(osv.osv): - """ - Company contribution - Allows to configure company contribution for some taxes - """ + 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 = { + 'line_ids': [], + 'move_ids': [], + 'move_line_ids': [], + 'move_payment_ids': [], + 'company_id': company_id, + 'period_id': False, + 'basic_before_leaves': 0.0, + 'basic_amount': 0.0 + } + return super(hr_payslip, self).copy(cr, uid, id, default, context=context) - _name = 'company.contribution' - _description = "Company Contribution" + def set_to_draft(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'draft'}, context=context) + + def cancel_sheet(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'cancel'}, context=context) + + def account_check_sheet(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'accont_check'}, context=context) + + def hr_check_sheet(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state': 'hr_check'}, context=context) + + def process_sheet(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'paid': True, 'state': 'done'}, context=context) + + def verify_sheet(self, cr, uid, ids, context=None): + #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) + return self.write(cr, uid, ids, {'state': 'confirm'}, context=context) + + #TODO move this function into hr_contract module, on hr.employee object + 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 + """ + contract_obj = self.pool.get('hr.contract') + clause = [] + #a contract is valid if it ends between the given dates + clause_1 = ['&',('date_end', '<=', date_to),('date_end','>=', date_from)] + #OR if it starts between the given dates + 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) + return contract_ids + + def compute_sheet(self, cr, uid, ids, context=None): + slip_line_pool = self.pool.get('hr.payslip.line') + for payslip in self.browse(cr, uid, ids, context=context): + #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) + if payslip.contract_id: + #set the list of contract for which the rules have to be applied + contract_ids = [payslip.contract_id.id] + else: + #if we don't give the contract, then the rules to apply should be for all current contracts of the employee + contract_ids = self.get_contract(cr, uid, payslip.employee_id, payslip.date_from, payslip.date_to, context=context) + 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) + 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 + """ + 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 + + 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 + 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 = {} + day_from = datetime.strptime(date_from,"%Y-%m-%d") + day_to = datetime.strptime(date_to,"%Y-%m-%d") + 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 + return res + + def get_payslip_lines(self, cr, uid, contract_ids, payslip_id, context): + result = [] + payslip = self.pool.get('hr.payslip').browse(cr, uid, payslip_id, context=context) + localdict = {'rules': {}, 'heads': {}, 'payslip': payslip} + #get the ids of the structures on the contracts and their parent id as well + 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])] + + for contract in self.pool.get('hr.contract').browse(cr, uid, contract_ids, context=context): + employee = contract.employee_id + localdict.update({'employee': employee, 'contract': contract}) + for rule in self.pool.get('hr.salary.rule').browse(cr, uid, sorted_rule_ids, context=context): + #check if the rule can be applied + if self.pool.get('hr.salary.rule').satisfy_condition(cr, uid, rule.id, contract.id, payslip_id, context=context): + 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 + 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 + + def onchange_employee_id(self, cr, uid, ids, date_from, date_to, employee_id=False, contract_id=False, context=None): + empolyee_obj = self.pool.get('hr.employee') + contract_obj = self.pool.get('hr.contract') + input_obj = self.pool.get('hr.payslip.input') + + if context is None: + context = {} + #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) + + #defaults + res = {'value':{ + 'line_ids':[], + #'details_by_salary_head':[], TODO put me back + 'name':'', + 'contract_id': False, + 'struct_id': False, + } + } + if not employee_id: + return res + ttyme = datetime.fromtimestamp(time.mktime(time.strptime(date_from, "%Y-%m-%d"))) + 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 + contract_ids = self.get_contract(cr, uid, employee_id, date_from, date_to, context=context) + 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, + }) + else: + if contract_id: + #set the list of contract for which the input have to be filled + 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) + res['value'].update({'struct_id': contract_record.struct_id.id}) + else: + #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) + if not contract_ids: + return res + + #computation of the salary input + input_line_ids = self.get_input_lines(cr, uid, contract_ids, date_from, date_to, context=context) + res['value'].update({ + 'input_line_ids': input_line_ids, + }) + return res + + def onchange_contract_id(self, cr, uid, ids, date_from, date_to, employee_id=False, contract_id=False, context=None): + if context is None: + context = {} + res = {'value':{ + 'line_ids': [], + 'name': '', + } + } + context.update({'contract': True}) + if not contract_id: + res['value'].update({'struct_id': False}) + 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) + +hr_payslip() + +class hr_payslip_input(osv.osv): + ''' + Payslip Input + ''' + + _name = 'hr.payslip.input' + _description = 'Payslip Input' + _columns = { + 'name': fields.char('Description', size=256, required=True), + '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"), + '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"), + } + _order = 'payslip_id,sequence' + _defaults = { + 'sequence': 10, + } +hr_payslip_input() + +class hr_salary_rule(osv.osv): + + _name = 'hr.salary.rule' _columns = { - 'category_id':fields.many2one('hr.allounce.deduction.categoty', 'Heads', required=False), 'name':fields.char('Name', size=256, required=True, readonly=False), - 'code':fields.char('Code', size=64, required=True, readonly=False), - 'gratuity':fields.boolean('Use for Gratuity ?', required=False), - 'line_ids':fields.one2many('company.contribution.line', 'contribution_id', 'Calculations', required=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."), + 'appears_on_payslip': fields.boolean('Appears on Payslip', help="Used for the display of rule on payslip"), + 'parent_rule_id':fields.many2one('hr.salary.rule', 'Parent Salary Rule', select=True), + 'company_id':fields.many2one('res.company', 'Company', required=False), + 'condition_select': fields.selection([('none', 'Always True'),('range', 'Range'), ('python', 'Python Expression')], "Condition Based on", required=True), + 'condition_range':fields.char('Range Based on',size=1024, 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'),#old name = conputional expression + '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 + '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."), + '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'),), + 'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Account'), help='For example, enter 50.0 to apply a percentage of 50%'), + 'amount_python_compute':fields.text('Python Code'), + '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 + 'child_ids':fields.one2many('hr.salary.rule', 'parent_rule_id', 'Child Salary Rule'), 'register_id':fields.property( 'hr.contibution.register', type='many2one', @@ -716,563 +669,84 @@ class company_contribution(osv.osv): help="Contribution register based on company", required=False ), - 'amount_type':fields.selection([ - ('fix','Fixed Amount'), - ('per','Percentage'), - ('func','Function Calculation'), - ],'Amount Type', select=True), - 'contribute_per':fields.float('Contribution', digits=(16, 4), help='Define Company contribution ratio 1.00=100% contribution.'), - 'company_id':fields.many2one('res.company', 'Company', required=False), - 'active':fields.boolean('Active', required=False), - 'note': fields.text('Description'), - } - - _defaults = { - 'amount_type': lambda *a:'fix', - 'active': lambda *a:True, - 'company_id': lambda self, cr, uid, context: \ - self.pool.get('res.users').browse(cr, uid, uid, - context=context).company_id.id, - } - - def _execute_function(self, cr, uid, id, value, context=None): - """ - self: pointer to self object - cr: cursor to database - uid: user id of current executer - """ - line_pool = self.pool.get('company.contribution.line') - res = 0 - ids = line_pool.search(cr, uid, [('category_id','=',id), ('to_val','>=',value),('from_val','<=',value)], context=context) - if not ids: - ids = line_pool.search(cr, uid, [('category_id','=',id), ('from','<=',value)], context=context) - if not ids: - res = 0 - else: - res = line_pool.browse(cr, uid, ids, context=context)[0].value - return res - - def compute(self, cr, uid, id, value, context=None): - contrib = self.browse(cr, uid, id, context=context) - if contrib.amount_type == 'fix': - return contrib.contribute_per - elif contrib.amount_type == 'per': - return value * contrib.contribute_per - elif contrib.amount_type == 'func': - return self._execute_function(cr, uid, id, value, context) - return 0.0 -company_contribution() - -class company_contribution_line(osv.osv): - """ - Company contribution lines - """ - - _name = 'company.contribution.line' - _description = 'Allowance Deduction Category' - _order = 'sequence' - _columns = { - 'contribution_id':fields.many2one('company.contribution', 'Contribution', required=False), - 'name':fields.char('Name', size=64, required=False, readonly=False), - 'from_val': fields.float('From', digits=(16, 4)), - 'to_val': fields.float('To', digits=(16, 4)), - 'amount_type':fields.selection([ - ('fix','Fixed Amount'), - ],'Amount Type', select=True), - 'sequence':fields.integer('Sequence'), - 'value': fields.float('Value', digits=(16, 4)), - } -company_contribution_line() - -class hr_holidays_status(osv.osv): - - _inherit = "hr.holidays.status" - _columns = { - 'company_id':fields.many2one('res.company', 'Company', required=False), - 'type':fields.selection([ - ('paid','Paid Holiday'), - ('unpaid','Un-Paid Holiday'), - ('halfpaid','Half-Pay Holiday') - ], string='Payment'), - 'head_id': fields.many2one('hr.allounce.deduction.categoty', 'Payroll Head', domain=[('type','=','deduction')]), - 'code': fields.related('head_id','code', type='char', relation='hr.allounce.deduction.categoty', string='Code'), -# 'code':fields.char('Code', size=64, required=False, readonly=False), - } - _defaults = { - 'type': lambda *args: 'unpaid', - 'company_id': lambda self, cr, uid, context: \ - self.pool.get('res.users').browse(cr, uid, uid, - context=context).company_id.id, - } -hr_holidays_status() - -class hr_payslip(osv.osv): - ''' - Pay Slip - ''' - - _name = 'hr.payslip' - _description = 'Pay Slip' - - def _calculate(self, cr, uid, ids, field_names, arg, context=None): - slip_line_obj = self.pool.get('hr.payslip.line') - res = {} - for rs in self.browse(cr, uid, ids, context=context): - allow = 0.0 - deduct = 0.0 - others = 0.0 - obj = {'basic':rs.basic} - if rs.igross > 0: - obj['gross'] = rs.igross - if rs.inet > 0: - obj['net'] = rs.inet - for line in rs.line_ids: - amount = 0.0 - if line.amount_type == 'per': - try: - amount = line.amount * eval(str(line.category_id.base), obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - elif line.amount_type in ('fix', 'func'): - amount = line.amount - cd = line.category_id.code.lower() - obj[cd] = amount - contrib = 0.0 - if line.type == 'allowance': - allow += amount - others += contrib - amount -= contrib - elif line.type == 'deduction': - deduct += amount - others -= contrib - amount += contrib - elif line.type == 'advance': - others += amount - elif line.type == 'loan': - others += amount - elif line.type == 'otherpay': - others += amount - slip_line_obj.write(cr, uid, [line.id], {'total':amount}, context=context) - - record = { - 'allounce':allow, - 'deduction':deduct, - 'grows':rs.basic + allow, - 'net':rs.basic + allow - deduct, - 'other_pay':others, - 'total_pay':rs.basic + allow - deduct - } - res[rs.id] = record - return res - - _columns = { - 'deg_id':fields.many2one('hr.payroll.structure', 'Designation', readonly=True, states={'draft': [('readonly', False)]}), - 'register_id':fields.many2one('hr.payroll.register', 'Register', required=False, readonly=True, states={'new': [('readonly', False)]}), - 'name':fields.char('Name', size=64, required=False, readonly=True, states={'new': [('readonly', False)]}), - 'number':fields.char('Number', size=64, required=False, readonly=True), - 'employee_id':fields.many2one('hr.employee', 'Employee', required=True, readonly=True, states={'new': [('readonly', False)]}), - 'date': fields.date('Date', readonly=True, states={'new': [('readonly', False)]}), - 'state':fields.selection([ - ('new','New Slip'), - ('draft','Wating for Verification'), - ('hr_check','Wating for HR Verification'), - ('accont_check','Wating for Account Verification'), - ('confirm','Confirm Sheet'), - ('done','Paid Salary'), - ('cancel','Reject'), - ],'State', select=True, readonly=True), - 'basic_before_leaves': fields.float('Basic Salary', readonly=True, digits_compute=dp.get_precision('Account')), - 'leaves': fields.float('Leave Deductions', readonly=True, digits_compute=dp.get_precision('Account')), - 'basic': fields.float('Net Basic', readonly=True, digits_compute=dp.get_precision('Account')), - 'grows': fields.function(_calculate, method=True, store=True, multi='dc', string='Gross Salary', digits_compute=dp.get_precision('Account')), - 'net': fields.function(_calculate, method=True, store=True, multi='dc', string='Net Salary', digits_compute=dp.get_precision('Account')), - 'allounce': fields.function(_calculate, method=True, store=True, multi='dc', string='Allowance', digits_compute=dp.get_precision('Account')), - 'deduction': fields.function(_calculate, method=True, store=True, multi='dc', string='Deduction', digits_compute=dp.get_precision('Account')), - 'other_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Others', digits_compute=dp.get_precision('Account')), - 'total_pay': fields.function(_calculate, method=True, store=True, multi='dc', string='Total Payment', digits_compute=dp.get_precision('Account')), - '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)]}), - 'holiday_days': fields.float('No of Leaves', readonly=True), - 'worked_days': fields.float('Worked Day', readonly=True), - 'working_days': fields.float('Working Days', readonly=True), - 'paid':fields.boolean('Paid ? ', 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)]}), - 'igross': fields.float('Calculaton Field', readonly=True, digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"), - 'inet': fields.float('Calculaton Field', readonly=True, digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"), - } + } _defaults = { - 'date': lambda *a: time.strftime('%Y-%m-%d'), - 'state': lambda *a: 'new', + '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''', + 'condition_python': +''' +# 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 = rules['NET'] > heads[NET] * 0.10''', + 'condition_range': 'contract.wage', + 'sequence': 5, + 'appears_on_payslip': True, + 'active': True, 'company_id': lambda self, cr, uid, context: \ self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id, - } + 'condition_select': 'none', + 'amount_select': 'fix', + 'amount_fix': 0.0, + 'amount_percentage': 0.0, + } - 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 = { - 'line_ids': False, - 'move_ids': False, - 'move_line_ids': False, - 'move_payment_ids': False, - 'company_id':company_id, - 'period_id': False, - 'basic_before_leaves':0, - 'basic':0 - } - res_id = super(hr_payslip, self).copy(cr, uid, id, default, context=context) - return res_id - - def create_voucher(self, cr, uid, ids, name, voucher, sequence=5): - slip_move = self.pool.get('hr.payslip.account.move') - for slip in ids: - res = { - 'slip_id':slip, - 'move_id':voucher, - 'sequence':sequence, - 'name':name - } - slip_move.create(cr, uid, res) - - def set_to_draft(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'draft'}, context=context) - return True - - def cancel_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'cancel'}, context=context) - return True - - def account_check_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'accont_check'}, context=context) - return True - - def hr_check_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state':'hr_check'}, context=context) - return True - - def process_sheet(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'paid':True, 'state':'done'}, context=context) - return True - - def verify_sheet(self, cr, uid, ids, context=None): - register_pool = self.pool.get('company.contribution') - register_line_pool = self.pool.get('hr.contibution.register.line') - - for slip in self.browse(cr, uid, ids, context=context): - base = { - 'basic':slip.basic, - 'net':slip.net, - 'gross':slip.grows, - } - for line in slip.line_ids: - base[line.code.lower()] = line.total - for contrib in line.category_id.contribute_ids: - if contrib.register_id: - value = eval(line.category_id.base, base) - company_contrib = register_pool.compute(cr, uid, contrib.id, value, context) - reg_line = { - 'name':line.name, - 'register_id': contrib.register_id.id, - 'code':line.code, - 'employee_id':slip.employee_id.id, - 'emp_deduction':line.total, - 'comp_deduction':company_contrib, - 'total':line.total + line.total - } - register_line_pool.create(cr, uid, reg_line) - - self.write(cr, uid, ids, {'state':'confirm'}, context=context) - return True - - def get_contract(self, cr, uid, employee, date, context=None): - sql_req= ''' - SELECT c.id as id, c.wage as wage, struct_id as function - FROM hr_contract c - LEFT JOIN hr_employee emp on (c.employee_id=emp.id) - LEFT JOIN hr_contract_wage_type cwt on (cwt.id = c.wage_type_id) - LEFT JOIN hr_contract_wage_type_period p on (cwt.period_id = p.id) - WHERE - (emp.id=%s) AND - (date_start <= %s) AND - (date_end IS NULL OR date_end >= %s) - LIMIT 1 - ''' - cr.execute(sql_req, (employee.id, date, date)) - contract = cr.dictfetchone() - - contract = contract and contract or {} - return contract - - def _get_leaves(self, cr, user, slip, employee, context=None): + #TODO should add some checks on the type of result (should be float) + def compute_rule(self, cr, uid, rule_id, localdict, context=None): """ - Compute leaves for an employee - - @param cr: cursor to database - @param user: id of current user - @param slip: object of the hr.payroll.slip model - @param employee: object of the hr.employee model - @param context: context arguments, like lang, time zone - - @return: return a result + @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 """ - result = [] + rule = self.browse(cr, uid, rule_id, context=context) + if rule.amount_select == 'fix': + return rule.amount_fix + elif rule.amount_select == 'percentage': + return rule.amount_percentage * eval(rule.amount_percentage_base, localdict) / 100 + else: + eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True) + return localdict['result'] - dates = prev_bounds(slip.date) - sql = '''select id from hr_holidays - where date_from >= '%s' and date_to <= '%s' - and employee_id = %s - and state = 'validate' ''' % (dates[0], dates[1], slip.employee_id.id) - cr.execute(sql) - res = cr.fetchall() + def satisfy_condition(self, cr, uid, rule_id, contract_id, payslip_id, context=None): + """ + @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) + contract = self.pool.get('hr.contract').browse(cr, uid, contract_id, context=context) + employee = contract.employee_id + payslip = self.pool.get('hr.payslip').browse(cr, uid, payslip_id, context=context) + localdict = {'employee': employee, 'contract': contract, 'payslip': payslip} - if res: - result = [x[0] for x in res] - - return result - - def compute_sheet(self, cr, uid, ids, context=None): - func_pool = self.pool.get('hr.payroll.structure') - slip_line_pool = self.pool.get('hr.payslip.line') - holiday_pool = self.pool.get('hr.holidays') - sequence_obj = self.pool.get('ir.sequence') - if context is None: - context = {} - date = self.read(cr, uid, ids, ['date'], context=context)[0]['date'] - - #Check for the Holidays - def get_days(start, end, month, year, calc_day): - import datetime - count = 0 - for day in range(start, end): - if datetime.date(year, month, day).weekday() == calc_day: - count += 1 - return count - - for slip in self.browse(cr, uid, ids, context=context): - old_slip_ids = slip_line_pool.search(cr, uid, [('slip_id','=',slip.id)], context=context) - slip_line_pool.unlink(cr, uid, old_slip_ids, context=context) - update = {} - ttyme = datetime.fromtimestamp(time.mktime(time.strptime(slip.date,"%Y-%m-%d"))) - contracts = self.get_contract(cr, uid, slip.employee_id, date, context) - if contracts.get('id', False) == False: - update.update({ - 'basic': round(0.0), - 'basic_before_leaves': round(0.0), - 'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))), - 'state':'draft', - 'contract_id':False, - 'company_id':slip.employee_id.company_id.id - }) - self.write(cr, uid, [slip.id], update, context=context) - continue - - contract = slip.employee_id.contract_id - sal_type = contract.wage_type_id.type - function = contract.struct_id.id - lines = [] - if function: - func = func_pool.read(cr, uid, function, ['line_ids'], context=context) - lines = slip_line_pool.browse(cr, uid, func['line_ids'], context=context) - - #lines += slip.employee_id.line_ids - - ad = [] - all_per = 0.0 - ded_per = 0.0 - all_fix = 0.0 - ded_fix = 0.0 - - obj = {'basic':0.0} - if contract.wage_type_id.type == 'gross': - obj['gross'] = contract.wage - update['igross'] = contract.wage - if contract.wage_type_id.type == 'net': - obj['net'] = contract.wage - update['inet'] = contract.wage - if contract.wage_type_id.type == 'basic': - obj['basic'] = contract.wage - update['basic'] = contract.wage - - for line in lines: - cd = line.code.lower() - obj[cd] = line.amount or 0.0 - - for line in lines: - if line.category_id.code in ad: - continue - ad.append(line.category_id.code) - cd = line.category_id.code.lower() - calculate = False - try: - exp = line.category_id.condition - calculate = eval(exp, obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - - if not calculate: - continue - - percent = 0.0 - value = 0.0 - base = False -# company_contrib = 0.0 - base = line.category_id.base - - try: - #Please have a look at the configuration guide. - amt = eval(base, obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - - if sal_type in ('gross', 'net'): - if line.amount_type == 'per': - percent = line.amount - if amt > 1: - value = percent * amt - elif amt > 0 and amt <= 1: - percent = percent * amt - if value > 0: - percent = 0.0 - elif line.amount_type == 'fix': - value = line.amount - elif line.amount_type == 'func': - value = slip_line_pool.execute_function(cr, uid, line.id, amt, context) - line.amount = value - else: - if line.amount_type in ('fix', 'per'): - value = line.amount - elif line.amount_type == 'func': - value = slip_line_pool.execute_function(cr, uid, line.id, amt, context) - line.amount = value - if line.type == 'allowance': - all_per += percent - all_fix += value - elif line.type == 'deduction': - ded_per += percent - ded_fix += value - vals = { - 'amount':line.amount, - 'slip_id':slip.id, - 'employee_id':False, - 'function_id':False, - 'base':base - } - slip_line_pool.copy(cr, uid, line.id, vals, {}) - if sal_type in ('gross', 'net'): - sal = contract.wage - if sal_type == 'net': - sal += ded_fix - sal -= all_fix - per = 0.0 - if sal_type == 'net': - per = (all_per - ded_per) - else: - per = all_per - if per <=0: - per *= -1 - final = (per * 100) + 100 - basic = (sal * 100) / final - else: - basic = contract.wage - - number = sequence_obj.get(cr, uid, 'salary.slip') - update.update({ - 'deg_id':function, - 'number':number, - 'basic': round(basic), - 'basic_before_leaves': round(basic), - 'name':'Salary Slip of %s for %s' % (slip.employee_id.name, tools.ustr(ttyme.strftime('%B-%Y'))), - 'state':'draft', - 'contract_id':contract.id, - 'company_id':slip.employee_id.company_id.id - }) - - for line in slip.employee_id.line_ids: - vals = { - 'amount':line.amount, - 'slip_id':slip.id, - 'employee_id':False, - 'function_id':False, - 'base':base - } - slip_line_pool.copy(cr, uid, line.id, vals, {}) - - self.write(cr, uid, [slip.id], update, context=context) - - for slip in self.browse(cr, uid, ids, context=context): - if not slip.contract_id: - continue - - basic_before_leaves = slip.basic - working_day = 0 - off_days = 0 - dates = prev_bounds(slip.date) - - days_arr = [0, 1, 2, 3, 4, 5, 6] - for dy in range(slip.employee_id.contract_id.working_days_per_week, 7): - off_days += get_days(1, dates[1].day, dates[1].month, dates[1].year, days_arr[dy]) - total_off = off_days - working_day = dates[1].day - total_off - perday = slip.net / working_day - total = 0.0 - leave = 0.0 - leave_ids = self._get_leaves(cr, uid, slip, slip.employee_id, context) - total_leave = 0.0 - paid_leave = 0.0 - for hday in holiday_pool.browse(cr, uid, leave_ids, context=context): - if not hday.holiday_status_id.head_id: - raise osv.except_osv(_('Error !'), _('Please check configuration of %s, payroll head is missing') % (hday.holiday_status_id.name)) - - res = { - 'slip_id':slip.id, - 'name':hday.holiday_status_id.name + '-%s' % (hday.number_of_days), - 'code':hday.holiday_status_id.code, - 'amount_type':'fix', - 'category_id':hday.holiday_status_id.head_id.id, - 'sequence':hday.holiday_status_id.head_id.sequence - } - days = hday.number_of_days - if hday.number_of_days < 0: - days = hday.number_of_days * -1 - total_leave += days - if hday.holiday_status_id.type == 'paid': - paid_leave += days - continue -# res['name'] = hday.holiday_status_id.name + '-%s' % (days) -# res['amount'] = perday * days -# res['type'] = 'allowance' -# leave += days -# total += perday * days - - elif hday.holiday_status_id.type == 'halfpaid': - paid_leave += (days / 2) - res['name'] = hday.holiday_status_id.name + '-%s/2' % (days) - res['amount'] = perday * (days/2) - total += perday * (days/2) - leave += days / 2 - res['type'] = 'deduction' - else: - res['name'] = hday.holiday_status_id.name + '-%s' % (days) - res['amount'] = perday * days - res['type'] = 'deduction' - leave += days - total += perday * days - - slip_line_pool.create(cr, uid, res, context=context) - basic = basic - total -# leaves = total - update.update({ - 'basic':basic, - 'basic_before_leaves': round(basic_before_leaves), - 'leaves':total, - 'holiday_days':leave, - 'worked_days':working_day - leave, - 'working_days':working_day, - }) - self.write(cr, uid, [slip.id], update, context=context) - return True -hr_payslip() + if rule.condition_select == 'none': + return True + elif rule.condition_select == 'range': + result = eval(rule.condition_range, localdict) + return rule.condition_range_min <= result and result <= rule.condition_range_max or False + else: #python code + eval(rule.condition_python, localdict, mode='exec', nocopy=True) + return 'result' in localdict and localdict['result'] or False +hr_salary_rule() class hr_payslip_line(osv.osv): ''' @@ -1280,99 +754,27 @@ class hr_payslip_line(osv.osv): ''' _name = 'hr.payslip.line' + _inherit = 'hr.salary.rule' _description = 'Payslip Line' - - def onchange_category(self, cr, uid, ids, category_id): - res = { - } - if category_id: - category = self.pool.get('hr.allounce.deduction.categoty').browse(cr, uid, category_id) - res.update({ - 'sequence':category.sequence, - 'name':category.name, - 'code':category.code, - 'type':category.type - }) - return {'value':res} - - def onchange_amount(self, cr, uid, ids, amount, typ): - amt = amount - if typ and typ == 'per': - if int(amt) > 0: - amt = amt / 100 - return {'value':{'amount':amt}} + _order = 'sequence' _columns = { - 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=False), - 'function_id':fields.many2one('hr.payroll.structure', 'Function', required=False), - 'employee_id':fields.many2one('hr.employee', 'Employee', required=False), - 'name':fields.char('Name', size=256, required=True, readonly=False), - 'base':fields.char('Formula', size=1024, required=False, readonly=False), - 'code':fields.char('Code', size=64, required=False, readonly=False), - 'category_id':fields.many2one('hr.allounce.deduction.categoty', 'Category', required=True), - 'type':fields.selection([ - ('allowance','Allowance'), - ('deduction','Deduction'), - ('leaves','Leaves'), - ('advance','Advance'), - ('loan','Loan'), - ('installment','Loan Installment'), - ('otherpay','Other Payment'), - ('otherdeduct','Other Deduction'), - ],'Type', select=True, required=True), - #TODO: link type to the category_id instead of define again - #'type': fields.related('category_id','type', type='selection', size=64, relation='hr.allounce.deduction.categoty', string='Type', store=True), - 'amount_type':fields.selection([ - ('per','Percentage (%)'), - ('fix','Fixed Amount'), - ('func','Function Value'), - ],'Amount Type', select=True, required=True), - 'amount': fields.float('Amount / Percentage', digits=(16, 4)), - 'total': fields.float('Sub Total', readonly=True, digits_compute=dp.get_precision('Account')), - 'company_contrib': fields.float('Company Contribution', readonly=True, digits=(16, 4)), - 'sequence': fields.integer('Sequence'), - 'note':fields.text('Description'), - 'line_ids':fields.one2many('hr.payslip.line.line', 'slipline_id', 'Calculations', required=False) + 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True), + 'employee_id':fields.many2one('hr.employee', 'Employee', required=True), + 'total': fields.float('Amount', digits_compute=dp.get_precision('Account')), + 'company_contrib': fields.float('Company Contribution', readonly=True, digits_compute=dp.get_precision('Account')), } - _order = 'sequence' - _defaults = { - 'amount_type': lambda *a: 'per' - } - - def execute_function(self, cr, uid, id, value, context=None): - line_pool = self.pool.get('hr.payslip.line.line') - res = 0 - ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value), ('to_val','>=',value)], context=context) - if not ids: - ids = line_pool.search(cr, uid, [('slipline_id','=',id), ('from_val','<=',value)], context=context) - if not ids: - return res - - res = line_pool.browse(cr, uid, ids, context=context)[-1].value - return res hr_payslip_line() -class hr_payslip_line_line(osv.osv): - ''' - Function Line - ''' +class hr_payroll_structure(osv.osv): - _name = 'hr.payslip.line.line' - _description = 'Function Line' - _order = 'sequence' + _inherit = 'hr.payroll.structure' _columns = { - 'slipline_id':fields.many2one('hr.payslip.line', 'Slip Line', required=False), - 'name':fields.char('Name', size=64, required=False, readonly=False), - 'from_val': fields.float('From', digits=(16, 4)), - 'to_val': fields.float('To', digits=(16, 4)), - 'amount_type':fields.selection([ - ('fix','Fixed Amount'), - ],'Amount Type', select=True), - 'sequence':fields.integer('Sequence'), - 'value': fields.float('Value', digits=(16, 4)), + 'rule_ids':fields.many2many('hr.salary.rule', 'hr_structure_salary_rule_rel', 'struct_id', 'rule_id', 'Salary Rules'), } -hr_payslip_line_line() + +hr_payroll_structure() class hr_employee(osv.osv): ''' @@ -1382,66 +784,29 @@ class hr_employee(osv.osv): _inherit = 'hr.employee' _description = 'Employee' - def _calculate_salary(self, cr, uid, ids, field_names, arg, context=None): - vals = {} - slip_line_pool = self.pool.get('hr.payslip.line') - + def _calculate_total_wage(self, cr, uid, ids, name, args, context): + if not ids: return {} + res = {} + current_date = datetime.now().strftime('%Y-%m-%d') for employee in self.browse(cr, uid, ids, context=context): - if not employee.contract_id: - vals[employee.id] = {'basic':0.0, 'gross':0.0, 'net':0.0, 'advantages_gross':0.0, 'advantages_net':0.0} + if not employee.contract_ids: + res[employee.id] = {'basic': 0.0} continue - - basic = employee.contract_id.basic - gross = employee.contract_id.gross - net = employee.contract_id.net - allowance = employee.contract_id.advantages_gross - deduction = employee.contract_id.advantages_net - - obj = { - 'basic':basic, - 'gross':gross, - 'net':net - } - for line in employee.line_ids: - base = line.category_id.base - try: - amt = eval(base, obj) - except Exception, e: - raise osv.except_osv(_('Variable Error !'), _('Variable Error: %s ') % (e)) - amount = 0.0 - if line.amount_type == 'per': - amount = amt * line.amount - elif line.amount_type == 'func': - amount = slip_line_pool.execute_function(cr, uid, line.id, amt, context) - elif line.amount_type == 'fix': - amount = line.amount - - if line.type == 'allowance': - allowance += amount - elif line.type == 'deduction': - deduction += amount - - vals[employee.id] = { - 'basic':basic, - 'advantages_gross':allowance, - 'gross':basic + allowance, - 'advantages_net':deduction, - 'net':basic + allowance - deduction - } - return vals + cr.execute( 'SELECT SUM(wage) '\ + 'FROM hr_contract '\ + 'WHERE employee_id = %s '\ + 'AND date_start <= %s '\ + 'AND (date_end > %s OR date_end is NULL)', + (employee.id, current_date, current_date)) + result = dict(cr.dictfetchone()) + res[employee.id] = {'basic': result['sum']} + return res _columns = { - 'passport_id':fields.many2one('hr.passport', 'Passport No', required=False, domain="[('employee_id','=',active_id), ('address_id','=',address_home_id)]", help="Employee Passport Information"), - 'line_ids':fields.one2many('hr.payslip.line', 'employee_id', 'Salary Structure', required=False), 'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True), - 'otherid': fields.char('Other Id', size=64), - - 'basic': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Basic Salary', digits=(14,2)), - 'gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Gross Salary', digits=(14,2)), - 'net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Net Salary', digits=(14,2)), - 'advantages_net': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Deductions', digits=(14,2)), - 'advantages_gross': fields.function(_calculate_salary, method=True, multi='dc', type='float', string='Allowances', digits=(14,2)), + '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."), } + hr_employee() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_payroll/hr_payroll_data.xml b/addons/hr_payroll/hr_payroll_data.xml index cafc02cc1db..1144f2ddadb 100644 --- a/addons/hr_payroll/hr_payroll_data.xml +++ b/addons/hr_payroll/hr_payroll_data.xml @@ -1,137 +1,144 @@ - + + + Allowance + ALW + + + + Deduction + DED + + + HRA - allowance + House Rent Allowance - + CA - allowance + Conveyance Allowance - + MA - allowance + Medical Allowance - + TELA - allowance + Telephone Allowance - + LTA - allowance + Leave Travel Allowance - + NA - allowance + Newspaper Allowance - + TA - allowance + Traveling Allowance - + FA - allowance + Food Allowance - + DA - allowance + Dearness Allowance - + PF - deduction + Provident Fund - + PT - deduction + Professional Tax - + WF - deduction + Welfare - + TDS - deduction + Tax Deduct at Source - + LWP - deduction + Leave without pay - + FC - deduction + Food Coupons - + ESI - deduction + Employee's State Insurance - - OTHERD - otherdeduct - Other Deduction + + TP + + Total Payment - - OTHERA - otherpay - Other Taxable Allowance - + + code + NET + + NET + + total = basic+allowance+deduction - - - Monthly Basic Wage - basic - - diff --git a/addons/hr_payroll/hr_payroll_demo.xml b/addons/hr_payroll/hr_payroll_demo.xml index bc94426999e..229eb9de482 100644 --- a/addons/hr_payroll/hr_payroll_demo.xml +++ b/addons/hr_payroll/hr_payroll_demo.xml @@ -2,57 +2,59 @@ - - - ME - Marketing Executive - - + - - per - - + + percentage + + contract.wage HRA - allowance + - House Rent Allowance + - - fix - - + + fix + CA - allowance + - Conveyance Allowance + - - fix + + fix - + PT - deduction + - Professional Tax - - per + + percentage - + + contract.wage PF - deduction + - Provident Fund + + + + ME + Marketing Executive + + + + @@ -65,7 +67,6 @@ - First Contract @@ -77,15 +78,5 @@ - - - SLIP/001 - - - - - - - diff --git a/addons/hr_payroll/hr_paroll_report.xml b/addons/hr_payroll/hr_payroll_report.xml similarity index 100% rename from addons/hr_payroll/hr_paroll_report.xml rename to addons/hr_payroll/hr_payroll_report.xml diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml index 78b9da0f9c1..4c5202885e2 100644 --- a/addons/hr_payroll/hr_payroll_view.xml +++ b/addons/hr_payroll/hr_payroll_view.xml @@ -6,83 +6,6 @@ - - - hr.passport.form - hr.passport - form - -
- - - - - - - - - - - - - - - - - - - - -
-
-
- - hr.passport.tree - hr.passport - tree - - - - - - - - - - - - - - hr.passport.select - hr.passport - search - - - - - - - - - - - - - - - - - - - - All Passports - hr.passport - form - - - - - hr.hr.employee.view.form2 @@ -97,6 +20,7 @@ +
@@ -110,75 +34,26 @@
- - hr.employee.passport.form - hr.employee - form - - - - - - - - - - - hr.contract.view.form + + hr.contract.view.form.inherit hr.contract form + -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +
- + @@ -190,7 +65,7 @@ - +
@@ -202,59 +77,23 @@ form
- - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - Salary Structure + Salary Structures hr.payroll.structure form @@ -263,36 +102,10 @@ id="menu_hr_employee_function" action="action_view_hr_employee_grade_form" parent="payroll_configure" + sequence="13" /> - - - hr.holidays.status.inherit - hr.holidays.status - - form - - - - - - - - - - - - - - - - - - - - - hr.payslip.line.tree @@ -300,13 +113,13 @@ tree - + - - - + + + @@ -321,14 +134,13 @@ -
- - - - + + + + @@ -336,18 +148,6 @@ - - - - - - - - - - - - @@ -365,11 +165,9 @@ - - - - + + @@ -383,22 +181,22 @@
- - - - + + + + + + + - + - - - - - + + @@ -408,10 +206,10 @@ - - - - + + + + @@ -419,18 +217,6 @@ - - - - - - - - - - - - @@ -438,41 +224,49 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + - + - + +