'company_id':fields.many2one('res.company', 'Company', required=True),
'note': fields.text('Description'),
'parent_id':fields.many2one('hr.payroll.structure', 'Parent'),
+ 'children_ids':fields.one2many('hr.payroll.structure', 'parent_id', 'Children'),
}
def _get_parent(self, cr, uid, context=None):
_inherit = 'hr.contract'
_description = 'Employee Contract'
_columns = {
- 'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure', required=True),
+ 'struct_id': fields.many2one('hr.payroll.structure', 'Salary Structure'),
'schedule_pay': fields.selection([
('monthly', 'Monthly'),
('quarterly', 'Quarterly'),
], 'Scheduled Pay', select=True),
}
+ _defaults = {
+ 'schedule_pay': 'monthly',
+ }
+
def get_all_structures(self, cr, uid, contract_ids, context=None):
"""
@param contract_ids: list of contracts
'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.rule.category', 'Parent', help="Linking a salary category to its parent is used only for the reporting purpose."),
+ 'children_ids': fields.one2many('hr.salary.rule.category', 'parent_id', 'Children'),
'note': fields.text('Description'),
'company_id':fields.many2one('res.company', 'Company', required=False),
}
class hr_payslip_run(osv.osv):
_name = 'hr.payslip.run'
+ _description = 'Payslip Batches'
_columns = {
'name': fields.char('Name', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
'slip_ids': fields.one2many('hr.payslip', 'payslip_run_id', 'Payslips', required=False, readonly=True, states={'draft': [('readonly', False)]}),
'state': fields.selection([
('draft', 'Draft'),
('close', 'Close'),
- ], 'State', select=True, readonly=True)
+ ], 'State', select=True, readonly=True),
+ 'date_start': fields.date('Date From', required=True, readonly=True, states={'draft': [('readonly', False)]}),
+ 'date_end': fields.date('Date To', required=True, readonly=True, states={'draft': [('readonly', False)]}),
+ 'credit_note': fields.boolean('Credit Note', readonly=True, states={'draft': [('readonly', False)]}, help="If its checked, indicates that all payslips generated from here are refund payslips."),
}
_defaults = {
'state': 'draft',
+ 'date_start': lambda *a: time.strftime('%Y-%m-01'),
+ 'date_end': lambda *a: str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10],
}
def draft_payslip_run(self, cr, uid, ids, context=None):
'note': fields.text('Description', readonly=True, states={'draft':[('readonly',False)]}),
'contract_id': fields.many2one('hr.contract', 'Contract', required=False, readonly=True, states={'draft': [('readonly', False)]}),
'details_by_salary_rule_category': fields.function(_get_lines_salary_rule_category, method=True, type='one2many', relation='hr.payslip.line', string='Details by Salary Rule Category'),
- 'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another"),
- 'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Run', readonly=True, states={'draft': [('readonly', False)]}),
+ 'credit_note': fields.boolean('Credit Note', help="Indicates this payslip has a refund of another", readonly=True, states={'draft': [('readonly', False)]}),
+ 'payslip_run_id': fields.many2one('hr.payslip.run', 'Payslip Batches', readonly=True, states={'draft': [('readonly', False)]}),
}
_defaults = {
'date_from': lambda *a: time.strftime('%Y-%m-01'),
context=context).company_id.id,
}
+ def _check_dates(self, cr, uid, ids, context=None):
+ for payslip in self.browse(cr, uid, ids, context=context):
+ if payslip.date_from > payslip.date_to:
+ return False
+ return True
+
+ _constraints = [(_check_dates, "Payslip 'Date From' must be before 'Date To'.", ['date_from', 'date_to'])]
+
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
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 hr_verify_sheet(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'verify'}, context=context)
-
+
def refund_sheet(self, cr, uid, ids, context=None):
mod_obj = self.pool.get('ir.model.data')
wf_service = netsvc.LocalService("workflow")
- for id in ids:
- id_copy = self.copy(cr, uid, id, {'credit_note': True}, context=context)
+ for payslip in self.browse(cr, uid, ids, context=context):
+ id_copy = self.copy(cr, uid, payslip.id, {'credit_note': True, 'name': _('Refund: ')+payslip.name}, context=context)
self.compute_sheet(cr, uid, [id_copy], context=context)
wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'hr_verify_sheet', cr)
wf_service.trg_validate(uid, 'hr.payslip', id_copy, 'process_sheet', cr)
'context': {}
}
- def verify_sheet(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
-
def check_done(self, cr, uid, ids, context=None):
return True
slip_line_pool = self.pool.get('hr.payslip.line')
sequence_obj = self.pool.get('ir.sequence')
for payslip in self.browse(cr, uid, ids, context=context):
- number = sequence_obj.get(cr, uid, 'salary.slip')
+ number = payslip.number or sequence_obj.get(cr, uid, 'salary.slip')
#delete old payslip lines
old_slipline_ids = slip_line_pool.search(cr, uid, [('slip_id', '=', payslip.id)], context=context)
# old_slipline_ids
return res
def get_payslip_lines(self, cr, uid, contract_ids, payslip_id, context):
-
- def _sum_salary_rule_category(categories_dict, category, amount):
+ def _sum_salary_rule_category(localdict, category, amount):
if category.parent_id:
- categories_dict = _sum_salary_rule_category(categories_dict, category.parent_id, amount)
- categories_dict[category.code] = category.code in categories_dict and categories_dict[category.code] + amount or amount
- return categories_dict
+ localdict = _sum_salary_rule_category(localdict, category.parent_id, amount)
+ localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount
+ return localdict
class BrowsableObject(object):
def __init__(self, pool, cr, uid, employee_id, dict):
self.dict = dict
def __getattr__(self, attr):
- return self.dict.__getitem__(attr)
+ return attr in self.dict and self.dict.__getitem__(attr) or 0.0
class InputLine(BrowsableObject):
"""a class that will be used into the python code, mainly for usability purposes"""
if to_date is None:
to_date = datetime.now().strftime('%Y-%m-%d')
result = 0.0
- self.cr.execute("SELECT sum(quantity) as sum\
+ self.cr.execute("SELECT sum(amount) as sum\
FROM hr_payslip as hp, hr_payslip_input as pi \
- WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \
+ WHERE hp.employee_id = %s AND hp.state = 'done' \
AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s",
(self.employee_id, from_date, to_date, code))
res = self.cr.fetchone()[0]
result = 0.0
self.cr.execute("SELECT sum(number_of_days) as number_of_days, sum(number_of_hours) as number_of_hours\
FROM hr_payslip as hp, hr_payslip_worked_days as pi \
- WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \
+ WHERE hp.employee_id = %s AND hp.state = 'done'\
AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pi.payslip_id AND pi.code = %s",
(self.employee_id, from_date, to_date, code))
return self.cr.fetchone()
to_date = datetime.now().strftime('%Y-%m-%d')
self.cr.execute("SELECT sum(case when hp.credit_note = False then (pl.total) else (-pl.total) end)\
FROM hr_payslip as hp, hr_payslip_line as pl \
- WHERE hp.employee_id = %s AND hp.state in ('confirm','done') \
+ WHERE hp.employee_id = %s AND hp.state = 'done' \
AND hp.date_from >= %s AND hp.date_to <= %s AND hp.id = pl.slip_id AND pl.code = %s",
(self.employee_id, from_date, to_date, code))
res = self.cr.fetchone()
#we keep a dict with the result because a value can be overwritten by another rule with the same code
result_dict = {}
+ rules = {}
+ categories_dict = {}
blacklist = []
payslip_obj = self.pool.get('hr.payslip')
inputs_obj = self.pool.get('hr.payslip.worked_days')
inputs = {}
for input_line in payslip.input_line_ids:
inputs[input_line.code] = input_line
- categories_dict = {}
+
categories_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, categories_dict)
input_obj = InputLine(self.pool, cr, uid, payslip.employee_id.id, inputs)
worked_days_obj = WorkedDays(self.pool, cr, uid, payslip.employee_id.id, worked_days)
payslip_obj = Payslips(self.pool, cr, uid, payslip.employee_id.id, payslip)
+ rules_obj = BrowsableObject(self.pool, cr, uid, payslip.employee_id.id, rules)
- localdict = {'payslip': payslip_obj, 'worked_days': worked_days_obj, 'inputs': input_obj, 'categories': categories_obj}
+ localdict = {'categories': categories_obj, 'rules': rules_obj, 'payslip': payslip_obj, 'worked_days': worked_days_obj, 'inputs': input_obj}
#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
for rule in obj_rule.browse(cr, uid, sorted_rule_ids, context=context):
key = rule.code + '-' + str(contract.id)
localdict['result'] = None
+ localdict['result_qty'] = 1.0
#check if the rule can be applied
if obj_rule.satisfy_condition(cr, uid, rule.id, localdict, context=context) and rule.id not in blacklist:
#compute the amount of the rule
- amount = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context)
+ amount, qty = obj_rule.compute_rule(cr, uid, rule.id, localdict, context=context)
#check if there is already a rule computed with that code
previous_amount = rule.code in localdict and localdict[rule.code] or 0.0
#set/overwrite the amount computed for this rule in the localdict
- localdict[rule.code] = amount
+ localdict[rule.code] = amount * qty
+ rules[rule.code] = rule
#sum the amount for its salary category
- categories_dict = _sum_salary_rule_category(categories_dict, rule.category_id, amount - previous_amount)
+ localdict = _sum_salary_rule_category(localdict, rule.category_id, (amount * qty) - previous_amount)
#create/overwrite the rule in the temporary results
result_dict[key] = {
'salary_rule_id': rule.id,
'amount_percentage': rule.amount_percentage,
'amount_percentage_base': rule.amount_percentage_base,
'register_id': rule.register_id.id,
- 'total': amount,
+ 'amount': amount,
'employee_id': contract.employee_id.id,
- 'quantity': rule.quantity,
+ 'quantity': qty,
}
else:
#blacklist this rule and its children
'struct_id': False,
}
}
- if not employee_id:
+ if (not employee_id) or (not date_from) or (not date_to):
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)
_description = 'Payslip Worked Days'
_columns = {
'name': fields.char('Description', size=256, required=True),
- 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True),
+ 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
'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'),
_description = 'Payslip Input'
_columns = {
'name': fields.char('Description', size=256, required=True),
- 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True),
+ 'payslip_id': fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
'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"),
- 'quantity': fields.float('Quantity', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs.SASUS.qunatity * contract.wage*0.01."),
+ 'amount': fields.float('Amount', help="It is used in computation. For e.g. A rule for sales having 1% commission of basic salary for per product can defined in expression like result = inputs.SALEURO.amount * contract.wage*0.01."),
'contract_id': fields.many2one('hr.contract', 'Contract', required=True, help="The contract for which applied this input"),
}
_order = 'payslip_id, sequence'
_defaults = {
'sequence': 10,
- 'quantity': 0.0,
+ 'amount': 0.0,
}
hr_payslip_input()
('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_fix': fields.float('Fixed Amount', digits_compute=dp.get_precision('Payroll'),),
+ 'amount_percentage': fields.float('Percentage (%)', digits_compute=dp.get_precision('Payroll'), 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'),
'child_ids':fields.one2many('hr.salary.rule', 'parent_rule_id', 'Child Salary Rule'),
- 'register_id':fields.property(
- 'hr.contribution.register',
- type='many2one',
- relation='hr.contribution.register',
- string="Contribution Register",
- method=True,
- view_load=True,
- help="Contribution register based on company",
- required=False
- ),
+ 'register_id':fields.many2one('hr.contribution.register', 'Contribution Register', help="Eventual third party involved in the salary payment of the employees."),
'input_ids': fields.one2many('hr.rule.input', 'input_id', 'Inputs'),
'note':fields.text('Description'),
}
# payslip: object containing the payslips
# employee: hr.employee object
# contract: hr.contract object
-# rules: rules code (previously computed)
-# categories: object containing the computed salary rule categories.
+# rules: object containing the rules code (previously computed)
+# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
# worked_days: object containing the computed worked days.
# inputs: object containing the computed inputs.
# payslip: object containing the payslips
# employee: hr.employee object
# contract: hr.contract object
-# rules: rules code (previously computed)
-# categories: object containing the computed salary rule categories.
+# rules: object containing the rules code (previously computed)
+# categories: object containing the computed salary rule categories (sum of amount of all rules belonging to that category).
# worked_days: object containing the computed worked days
# inputs: object containing the computed inputs
# Note: returned value have to be set in the variable 'result'
-result = rules['NET'] > categories['NET'] * 0.10''',
+result = rules.NET > categories.NET * 0.10''',
'condition_range': 'contract.wage',
'sequence': 5,
'appears_on_payslip': True,
'amount_select': 'fix',
'amount_fix': 0.0,
'amount_percentage': 0.0,
- 'quantity': '1',
+ 'quantity': '1.0',
}
def _recursive_search_of_rules(self, cr, uid, rule_ids, 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
+ @return: returns the result of computation and the quantity as floats
"""
rule = self.browse(cr, uid, rule_id, context=context)
if rule.amount_select == 'fix':
try:
- return rule.amount_fix * eval(rule.quantity, localdict)
+ return rule.amount_fix, eval(rule.quantity, localdict)
except:
raise osv.except_osv(_('Error'), _('Wrong quantity defined for salary rule %s (%s)')% (rule.name, rule.code))
elif rule.amount_select == 'percentage':
try:
amount = rule.amount_percentage * eval(rule.amount_percentage_base, localdict) / 100
- return amount * eval(rule.quantity, localdict)
+ return amount, eval(rule.quantity, localdict)
except:
raise osv.except_osv(_('Error'), _('Wrong percentage base or quantity defined for salary rule %s (%s)')% (rule.name, rule.code))
else:
try:
eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True)
- return localdict['result']
+ return localdict['result'], 'result_qty' in localdict and localdict['result_qty'] or 1.0
except:
raise osv.except_osv(_('Error'), _('Wrong python code defined for salary rule %s (%s) ')% (rule.name, rule.code))
_description = 'Payslip Line'
_order = 'contract_id, sequence'
+ def _calculate_total(self, cr, uid, ids, name, args, context):
+ if not ids: return {}
+ res = {}
+ for line in self.browse(cr, uid, ids, context=context):
+ res[line.id] = float(line.quantity) * line.amount
+ return res
+
_columns = {
- 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True),
+ 'slip_id':fields.many2one('hr.payslip', 'Pay Slip', required=True, ondelete='cascade'),
'salary_rule_id':fields.many2one('hr.salary.rule', 'Rule', required=True),
'employee_id':fields.many2one('hr.employee', 'Employee', required=True),
'contract_id':fields.many2one('hr.contract', 'Contract', 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')),
+ 'amount': fields.float('Amount', digits_compute=dp.get_precision('Payroll')),
+ 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Payroll')),
+ 'total': fields.function(_calculate_total, method=True, type='float', string='Total', digits_compute=dp.get_precision('Payroll'),store=True ),
+ }
+
+ _defaults = {
+ 'quantity': 1.0,
}
hr_payslip_line()
_columns = {
'slip_ids':fields.one2many('hr.payslip', 'employee_id', 'Payslips', required=False, readonly=True),
- '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."),
+ 'total_wage': fields.function(_calculate_total_wage, method=True, type='float', string='Total Basic Salary', digits_compute=dp.get_precision('Payroll'), help="Sum of all current contract's wage of employee."),
}
hr_employee()