diff --git a/payroll/__manifest__.py b/payroll/__manifest__.py index b23f35e7..e2530615 100644 --- a/payroll/__manifest__.py +++ b/payroll/__manifest__.py @@ -38,6 +38,7 @@ "views/res_config_settings_views.xml", "wizard/hr_payroll_send_email.xml", "wizard/hr_payslip_change_state_view.xml", + "views/hr_leave_type.xml", ], "demo": ["demo/hr_payroll_demo.xml"], "application": True, diff --git a/payroll/models/base_browsable.py b/payroll/models/base_browsable.py index 249b04b3..dc274989 100644 --- a/payroll/models/base_browsable.py +++ b/payroll/models/base_browsable.py @@ -85,7 +85,7 @@ class Payslips(BrowsableObject): """a class that will be used into the python code, mainly for usability purposes""" - def sum(self, code, from_date, to_date=None): + def sum_rule(self, code, from_date, to_date=None): if to_date is None: to_date = fields.Date.today() self.env.cr.execute( @@ -99,3 +99,269 @@ def sum(self, code, from_date, to_date=None): ) res = self.env.cr.fetchone() return res and res[0] or 0.0 + + def sum(self, code, from_date, to_date=None): + _logger.warning( + "Payslips Object: sum() method is DEPRECATED. Use sum_rule() instead." + ) + return self.sum_rule(code, from_date, to_date) + + def average_rule(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT avg(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 = '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.env.cr.fetchone() + return res and res[0] or 0.0 + + def average_rule_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT avg(total) FROM (SELECT max(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 = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND + hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""", + (self.employee_id, from_date, to_date, code), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def max_rule(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT max(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 = '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.env.cr.fetchone() + return res and res[0] or 0.0 + + def max_rule_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT max(total) FROM (SELECT max(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 = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND + hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""", + (self.employee_id, from_date, to_date, code), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def min_rule(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT min(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 = '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.env.cr.fetchone() + return res and res[0] or 0.0 + + def min_rule_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + self.env.cr.execute( + """SELECT min(total) FROM (SELECT max(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 = 'done' + AND hp.date_from >= %s AND hp.date_to <= %s AND + hp.id = pl.slip_id AND pl.code = %s) AS monthly_sum""", + (self.employee_id, from_date, to_date, code), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def sum_category(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT sum(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def average_category(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT avg(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def average_category_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT avg(total) FROM ( + SELECT DATE_TRUNC('month',hp.date_from) AS date_month, + sum(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) AS total + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s + GROUP BY date_month) AS monthly_sum""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def max_category(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT max(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def max_category_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT max(total) FROM ( + SELECT DATE_TRUNC('month',hp.date_from) AS date_month, + sum(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) AS total + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s + GROUP BY date_month) AS monthly_sum""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def min_category(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT min(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 + + def min_category_monthly(self, code, from_date, to_date=None): + if to_date is None: + to_date = fields.Date.today() + + hierarchy_codes = ( + self.env["hr.salary.rule.category"] + .search([("code", "=", code)]) + .children_ids.mapped("code") + ) + hierarchy_codes.append(code) + + self.env.cr.execute( + """SELECT min(total) FROM ( + SELECT DATE_TRUNC('month',hp.date_from) AS date_month, + sum(case when hp.credit_note is not True then + (pl.total) else (-pl.total) end) AS total + FROM hr_payslip as hp, hr_payslip_line as pl, hr_salary_rule_category as rc + 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 rc.id = pl.category_id AND rc.code in %s + GROUP BY date_month) AS monthly_sum""", + (self.employee_id, from_date, to_date, tuple(hierarchy_codes)), + ) + res = self.env.cr.fetchone() + return res and res[0] or 0.0 diff --git a/payroll/models/hr_payslip.py b/payroll/models/hr_payslip.py index 88b0ae69..721e0bfc 100644 --- a/payroll/models/hr_payslip.py +++ b/payroll/models/hr_payslip.py @@ -471,7 +471,10 @@ def get_current_contract_dict(self, contract, contracts): def _get_tools_dict(self): # _get_tools_dict() is intended to be inherited by other private modules # to add tools or python libraries available in localdict - return {"math": math} # "math" object is useful for doing calculations + return { + "math": math, + "datetime": datetime, + } # "math" object is useful for doing calculations def _get_baselocaldict(self, contracts): self.ensure_one() @@ -533,10 +536,10 @@ def _get_lines_dict( total = values["quantity"] * values["rate"] * values["amount"] / 100.0 values["total"] = total # set/overwrite the amount computed for this rule in the localdict - if rule.code: - localdict[rule.code] = total - localdict["rules"].dict[rule.code] = rule - localdict["result_rules"].dict[rule.code] = BaseBrowsableObject(values) + code = rule.code or rule.id + localdict[code] = total + localdict["rules"].dict[code] = rule + localdict["result_rules"].dict[code] = BaseBrowsableObject(values) # sum the amount for its salary category localdict = self._sum_salary_rule_category( localdict, rule.category_id, total - previous_amount @@ -781,3 +784,18 @@ def get_salary_line_total(self, code): return line[0].total else: return 0.0 + + def line_sum_where(self, field_name, value, rules, result_rules): + """ + The method may be used in salary rule code. + It will sum the total of the previously computed rules + where the given field has the given value. + E.g.: total_seq_10 = payslip.line_sum_where("sequence", 10, rules, result_rules) + """ + return sum( + [ + result_rules.dict[code].dict["total"] + for code, rule in rules.dict.items() + if getattr(rule, field_name) == value + ] + ) diff --git a/payroll/models/hr_salary_rule.py b/payroll/models/hr_salary_rule.py index 2a57eec3..2e081e11 100644 --- a/payroll/models/hr_salary_rule.py +++ b/payroll/models/hr_salary_rule.py @@ -208,8 +208,10 @@ def _compute_rule(self, localdict): :rtype: {"name": string, "quantity": float, "rate": float, "amount": float} """ self.ensure_one() - method = f"_compute_rule_{self.amount_select}" - return api.call_kw(self, method, [self.ids, localdict], {}) + method = "_compute_rule_{}".format(self.amount_select) + return api.call_kw( + self, method, [self.ids, localdict], {"context": self.env.context} + ) def _compute_rule_fix(self, localdict): try: diff --git a/payroll/tests/test_hr_salary_rule.py b/payroll/tests/test_hr_salary_rule.py index a4e34a19..f0064b91 100644 --- a/payroll/tests/test_hr_salary_rule.py +++ b/payroll/tests/test_hr_salary_rule.py @@ -200,3 +200,48 @@ def test_rule_and_category_with_and_without_code(self): lambda record: record.name == "rule without category" ) self.assertEqual(len(line), 1, "Line found: rule without category") + + def test_line_sum_where(self): + rule_fix_1 = self.SalaryRule.create( + { + "name": "Fixed rule 1", + "sequence": 1, + "condition_select": "none", + "amount_select": "fix", + "amount_fix": 100.0, + } + ) + rule_fix_2 = self.SalaryRule.create( + { + "name": "Fixed rule 2", + "sequence": 2, + "condition_select": "none", + "amount_select": "fix", + "amount_fix": 200.0, + } + ) + rule_line_sum_where = self.Rule.create( + { + "name": "Total fixed values", + "sequence": 3, + "amount_select": "code", + "amount_python_compute": """result = payslip.line_sum_where( + "amount_select", "fix", rules, result_rules + )""", + } + ) + self.sales_pay_structure.rule_ids = [ + (4, rule_fix_1.id), + (4, rule_fix_2.id), + (4, rule_line_sum_where.id), + ] + payslip = self.Payslip.create( + { + "employee_id": self.richard_emp.id, + "contract_id": self.richard_contract.id, + "struct_id": self.sales_pay_structure.id, + } + ) + payslip.compute_sheet() + line = payslip.line_ids.filtered(lambda l: l.name == "Total fixed values") + self.assertEqual(line.total, 300, "Fixed rules: 100 + 200 = 300") diff --git a/payroll/views/hr_leave_type.xml b/payroll/views/hr_leave_type.xml new file mode 100644 index 00000000..ef04dcf4 --- /dev/null +++ b/payroll/views/hr_leave_type.xml @@ -0,0 +1,13 @@ + + + + hr.leave.type.view.form.payroll + hr.leave.type + + + + + + + + diff --git a/payroll/views/hr_payslip_views.xml b/payroll/views/hr_payslip_views.xml index 343936c9..4ce5da5a 100644 --- a/payroll/views/hr_payslip_views.xml +++ b/payroll/views/hr_payslip_views.xml @@ -237,15 +237,17 @@ - - - + + + + +