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 @@
-
-
-
+
+
+
+
+