From 16f6f59367755066a62a9544fc79739062a80173 Mon Sep 17 00:00:00 2001 From: Cyril VINH-TUNG Date: Sat, 16 May 2026 15:16:33 -1000 Subject: [PATCH] [IMP] payroll_contract_advantages: add complex mechanism for advantage value calculations + demo data Assisted-by: Claude Opus 4.7 --- payroll_contract_advantages/README.rst | 20 +- payroll_contract_advantages/__manifest__.py | 1 + .../demo/payroll_contract_advantages_demo.xml | 49 +++++ .../models/hr_contract_advantage.py | 188 ++++++++++++++++-- .../models/hr_contract_advantage_template.py | 38 +++- .../models/hr_payslip.py | 13 +- .../readme/CONTRIBUTORS.md | 5 +- .../readme/DESCRIPTION.md | 5 + payroll_contract_advantages/readme/USAGE.md | 8 + .../static/description/index.html | 39 ++-- .../tests/test_payroll_contract_advantages.py | 183 ++++++++++++++++- .../views/hr_contract_advantage_views.xml | 25 ++- .../views/hr_contract_views.xml | 38 +++- 13 files changed, 565 insertions(+), 47 deletions(-) create mode 100644 payroll_contract_advantages/demo/payroll_contract_advantages_demo.xml diff --git a/payroll_contract_advantages/README.rst b/payroll_contract_advantages/README.rst index 29bf2e97c..4969eb64f 100644 --- a/payroll_contract_advantages/README.rst +++ b/payroll_contract_advantages/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - =========================== Payroll Contract Advantages =========================== @@ -17,7 +13,7 @@ Payroll Contract Advantages .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpayroll-lightgray.png?logo=github @@ -37,6 +33,11 @@ set in contract form. The advantages can be set in the contract form as a list of advantages templates. Then it can be used in the calculation of the salary rules. +Each advantage has a computation mode (fixed value, percentage of a +contract field, or Python expression); the amount is re-evaluated per +payslip. The default fixed mode reproduces the historical behaviour. +Template bounds are enforced on the amount. + **Table of contents** .. contents:: @@ -51,6 +52,14 @@ Usage this contract, default value will be populated but you can change it. - Then in the salary rules, access this value using current_contract.advantages.[ADVANTAGE_CODE] (without brackets) +- On the template, choose a computation mode (fixed value, percentage of + a contract field, or Python code). The definition is copied onto the + advantage and can be tuned per contract. +- The amount is re-evaluated for each payslip. Python formulas expose + ``advantage``, ``contract``, ``employee``, ``payslip`` and must set + ``result``. +- Bounds are enforced on the amount; a non-numeric formula result raises + an error. Bug Tracker =========== @@ -75,6 +84,7 @@ Contributors - Nimarosa (Nicolas Rodriguez) - Cristiano Mafra Junior +- Cyril VINH-TUNG Maintainers ----------- diff --git a/payroll_contract_advantages/__manifest__.py b/payroll_contract_advantages/__manifest__.py index f8351fdbb..518b5297f 100644 --- a/payroll_contract_advantages/__manifest__.py +++ b/payroll_contract_advantages/__manifest__.py @@ -14,6 +14,7 @@ "views/hr_contract_advantage_views.xml", "views/hr_contract_views.xml", ], + "demo": ["demo/payroll_contract_advantages_demo.xml"], "application": True, "maintainers": ["nimarosa"], } diff --git a/payroll_contract_advantages/demo/payroll_contract_advantages_demo.xml b/payroll_contract_advantages/demo/payroll_contract_advantages_demo.xml new file mode 100644 index 000000000..6cef7834d --- /dev/null +++ b/payroll_contract_advantages/demo/payroll_contract_advantages_demo.xml @@ -0,0 +1,49 @@ + + + + + Meal Voucher + MEAL + 0.0 + 500.0 + 10.0 + fixed + + + Housing Allowance + HOUS + 0.0 + 5000.0 + percentage + 5.0 + wage + + + Phone Allowance + PHONE + 0.0 + 1000.0 + python + result = contract.wage * 0.02 + + + + + + fixed + 10.0 + + + + + percentage + 5.0 + wage + + + + + python + result = contract.wage * 0.02 + + diff --git a/payroll_contract_advantages/models/hr_contract_advantage.py b/payroll_contract_advantages/models/hr_contract_advantage.py index ea3cd0b94..1ef4190db 100644 --- a/payroll_contract_advantages/models/hr_contract_advantage.py +++ b/payroll_contract_advantages/models/hr_contract_advantage.py @@ -1,7 +1,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import _, api, fields, models -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError, ValidationError +from odoo.tools.safe_eval import safe_eval class HrContractAdvantage(models.Model): @@ -21,22 +22,185 @@ class HrContractAdvantage(models.Model): advantage_upper_bound = fields.Float( string="Upper Bound", related="advantage_template_id.upper_bound", readonly=True ) - amount = fields.Float() + + # Definition copied from the template on selection, then editable + # per contract. Default "fixed" + the stored amount reproduces the + # historical behaviour. + computation_mode = fields.Selection( + selection=[ + ("fixed", "Fixed value"), + ("percentage", "Percentage of a contract field"), + ("python", "Python code"), + ], + default="fixed", + required=True, + ) + percentage = fields.Float(help="Percentage applied to the base field.") + percentage_base = fields.Char( + string="Percentage Base Field", + help="Name of a numeric hr.contract field used as base " + "(e.g. 'wage'). Unknown fields evaluate to 0.", + ) + python_code = fields.Text( + help="Python code; assign the amount to 'result'. " + "Available variables are listed in the field.", + ) + amount = fields.Float( + help="Latest evaluated amount, " "recomputed per payslip for non-fixed modes." + ) @api.onchange("advantage_template_id") def _onchange_advantage_template_id(self): + """Copy the template definition onto the advantage. + + In 'fixed' mode ``amount`` keeps coming from the template + default value (historical behaviour). In 'percentage'/'python' + mode it is previewed from the actual computation so the user + does not see 0 while configuring; bounds are still enforced by + the ``amount`` constraint on save. Payslips recompute it live. + """ for record in self: - record.amount = record.advantage_template_id.default_value + template = record.advantage_template_id + if not template: + continue + record.computation_mode = template.computation_mode + record.percentage = template.percentage + record.percentage_base = template.percentage_base + record.python_code = template.python_code + if record.computation_mode == "fixed": + record.amount = template.default_value + else: + preview, warning = record._preview_unit_value() + record.amount = preview + if warning: + return warning + + @api.onchange( + "computation_mode", + "percentage", + "percentage_base", + "python_code", + ) + def _onchange_computation_preview(self): + """Preview ``amount`` while configuring the line. + + Only in 'percentage'/'python' mode; 'fixed' keeps the + user-entered amount. Bounds are enforced by the ``amount`` + constraint on save and payslips recompute it live. + """ + for record in self: + if record.computation_mode and record.computation_mode != "fixed": + preview, warning = record._preview_unit_value() + record.amount = preview + if warning: + return warning + + def _preview_unit_value(self): + """Tolerant unit value for the configuration screen. + + Returns (value, warning). On any evaluation error the value is + 0.0 and a non-blocking warning carries the exception text, so + the user can keep configuring without a traceback dialog. The + payslip computation stays strict. + """ + self.ensure_one() + try: + return self._compute_unit_value(), None + except Exception as err: + warning = { + "warning": { + "title": _("Preview unavailable"), + "message": _( + "The amount could not be evaluated; set to 0. " + "It will be recomputed on the payslip.\n\n%s" + ) + % err, + } + } + return 0.0, warning + + def _compute_advantage_amount(self, payslip=None): + """Return the computed amount, bounded. Evaluated per payslip. + + :param payslip: optional hr.payslip, exposed to python formulas. + """ + self.ensure_one() + value = self._compute_unit_value(payslip=payslip) + self._check_bounds(value) + return value + + def _compute_unit_value(self, payslip=None): + """Unit value per the computation mode.""" + self.ensure_one() + contract = self.contract_id + mode = self.computation_mode or "fixed" + + if mode == "fixed": + # Backward compatibility: the amount is typed directly on + # the advantage, so existing flows/records are unaffected. + value = self.amount + elif mode == "percentage": + base_field = (self.percentage_base or "").strip() + base_value = 0.0 + if base_field and contract: + base_value = contract[base_field] if base_field in contract else 0.0 + value = (base_value or 0.0) * (self.percentage or 0.0) / 100.0 + elif mode == "python": + value = self._eval_code(self.python_code, payslip=payslip) + else: + value = 0.0 + + return self._coerce_float(value, _("unit value")) + + def _coerce_float(self, value, label): + """Float guarantee, mirroring hr.salary.rule._compute_rule.""" + try: + return float(value) + except (TypeError, ValueError) as err: + raise UserError( + _( + "The computed %(label)s of advantage " + "'%(advantage)s' must be a float." + ) + % { + "label": label, + "advantage": self.advantage_template_id.name + or self.advantage_template_code + or self.id, + } + ) from err + + def _eval_code(self, code, payslip=None): + """Safely evaluate a generic python expression.""" + self.ensure_one() + if not code: + return 0.0 + localdict = { + "advantage": self, + "contract": self.contract_id, + "employee": self.contract_id.employee_id + if self.contract_id + else self.env["hr.employee"], + "payslip": payslip, + "result": 0.0, + } + safe_eval(code, localdict, mode="exec", nocopy=True) + return localdict.get("result", 0.0) or 0.0 + + def _check_bounds(self, value): + """Enforce template lower/upper bounds on a candidate amount.""" + self.ensure_one() + if value and value != 0.00: + if self.advantage_upper_bound and value > self.advantage_upper_bound: + raise ValidationError( + _("Advantage amount can't be greater than upper bound limit.") + ) + elif self.advantage_lower_bound and value < self.advantage_lower_bound: + raise ValidationError( + _("Advantage amount can't be less than lower bound limit.") + ) @api.constrains("amount") def _check_bound_limits(self): for record in self: - if record.amount and record.amount != 0.00: - if record.amount > record.advantage_upper_bound: - raise ValidationError( - _("Advantage amount can't be greater than upper bound limit.") - ) - elif record.amount < record.advantage_lower_bound: - raise ValidationError( - _("Advantage amount can't be less than lower bound limit.") - ) + record._check_bounds(record.amount) diff --git a/payroll_contract_advantages/models/hr_contract_advantage_template.py b/payroll_contract_advantages/models/hr_contract_advantage_template.py index d3db791b0..a6555d44e 100644 --- a/payroll_contract_advantages/models/hr_contract_advantage_template.py +++ b/payroll_contract_advantages/models/hr_contract_advantage_template.py @@ -2,6 +2,14 @@ from odoo import fields, models +DEFAULT_PYTHON_CODE = """# Available variables: +# - advantage: the hr.contract.advantage record +# - contract: the related hr.contract record +# - employee: the related hr.employee record +# - payslip: the hr.payslip being computed (None outside a payslip) +# Assign the amount to: result +# E.g. result = contract.wage * 0.05\n\n\n""" + class HrContractAdvandageTemplate(models.Model): _name = "hr.contract.advantage.template" @@ -9,10 +17,30 @@ class HrContractAdvandageTemplate(models.Model): name = fields.Char(required=True) code = fields.Char(required=True) - lower_bound = fields.Float( - help="Lower bound authorized by the employer for this advantage" + lower_bound = fields.Float(help="Lower bound authorized for this advantage") + upper_bound = fields.Float(help="Upper bound authorized for this advantage") + default_value = fields.Float() + + # Default "fixed" keeps the historical behaviour (amount = + # default_value), so existing databases are unaffected. + computation_mode = fields.Selection( + selection=[ + ("fixed", "Fixed value"), + ("percentage", "Percentage of a contract field"), + ("python", "Python code"), + ], + default="fixed", + required=True, + help="How the unit value is computed.", ) - upper_bound = fields.Float( - help="Upper bound authorized by the employer for this advantage" + percentage = fields.Float(help="Percentage applied to the base field.") + percentage_base = fields.Char( + string="Percentage Base Field", + help="Name of a numeric hr.contract field used as base " + "(e.g. 'wage'). Unknown fields evaluate to 0.", + ) + python_code = fields.Text( + default=DEFAULT_PYTHON_CODE, + help="Python code; assign the amount to 'result'. " + "Available variables are listed in the field.", ) - default_value = fields.Float() diff --git a/payroll_contract_advantages/models/hr_payslip.py b/payroll_contract_advantages/models/hr_payslip.py index 9d4170cbb..c2d4094ed 100644 --- a/payroll_contract_advantages/models/hr_payslip.py +++ b/payroll_contract_advantages/models/hr_payslip.py @@ -9,11 +9,22 @@ class HrPayslip(models.Model): _inherit = "hr.payslip" def get_current_contract_dict(self, contract, contracts): + """Expose advantages by code in the salary rules localdict. + + Amounts are (re)evaluated per payslip from each advantage's + formula, so period-sensitive values stay correct. In 'fixed' + mode the value equals the stored amount, unchanged for existing + installations. + """ self.ensure_one() res = super().get_current_contract_dict(contract, contracts) advantages_dict = {} for advantage in contract.advantages_ids: - advantages_dict[advantage.advantage_template_code] = advantage.amount + amount = advantage._compute_advantage_amount(payslip=self) + # Keep the stored amount in sync for reporting / auditing. + if advantage.amount != amount: + advantage.amount = amount + advantages_dict[advantage.advantage_template_code] = amount res.update( {"advantages": BrowsableObject(self.employee_id, advantages_dict, self.env)} ) diff --git a/payroll_contract_advantages/readme/CONTRIBUTORS.md b/payroll_contract_advantages/readme/CONTRIBUTORS.md index 292dd54d3..15e640623 100644 --- a/payroll_contract_advantages/readme/CONTRIBUTORS.md +++ b/payroll_contract_advantages/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ -- Nimarosa (Nicolas Rodriguez) \ -- Cristiano Mafra Junior \ +- Nimarosa (Nicolas Rodriguez) \<\> +- Cristiano Mafra Junior \<\> +- Cyril VINH-TUNG \<\> diff --git a/payroll_contract_advantages/readme/DESCRIPTION.md b/payroll_contract_advantages/readme/DESCRIPTION.md index 21305a80b..cfb0dd3f0 100644 --- a/payroll_contract_advantages/readme/DESCRIPTION.md +++ b/payroll_contract_advantages/readme/DESCRIPTION.md @@ -2,3 +2,8 @@ This module adds support for advantages templates and advantages to be set in contract form. The advantages can be set in the contract form as a list of advantages templates. Then it can be used in the calculation of the salary rules. + +Each advantage has a computation mode (fixed value, percentage of a +contract field, or Python expression); the amount is re-evaluated per +payslip. The default fixed mode reproduces the historical behaviour. +Template bounds are enforced on the amount. diff --git a/payroll_contract_advantages/readme/USAGE.md b/payroll_contract_advantages/readme/USAGE.md index 0dc71a47c..b3326ac0c 100644 --- a/payroll_contract_advantages/readme/USAGE.md +++ b/payroll_contract_advantages/readme/USAGE.md @@ -4,3 +4,11 @@ this contract, default value will be populated but you can change it. - Then in the salary rules, access this value using current_contract.advantages.\[ADVANTAGE_CODE\] (without brackets) +- On the template, choose a computation mode (fixed value, percentage + of a contract field, or Python code). The definition is copied onto + the advantage and can be tuned per contract. +- The amount is re-evaluated for each payslip. Python formulas expose + ``advantage``, ``contract``, ``employee``, ``payslip`` and must set + ``result``. +- Bounds are enforced on the amount; a non-numeric formula result + raises an error. diff --git a/payroll_contract_advantages/static/description/index.html b/payroll_contract_advantages/static/description/index.html index c0e8e1bf6..47bd1e98a 100644 --- a/payroll_contract_advantages/static/description/index.html +++ b/payroll_contract_advantages/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Payroll Contract Advantages -
+
+

Payroll Contract Advantages

- - -Odoo Community Association - -
-

Payroll Contract Advantages

-

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

+

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

This module adds support for advantages templates and advantages to be set in contract form. The advantages can be set in the contract form as a list of advantages templates. Then it can be used in the calculation of the salary rules.

+

Each advantage has a computation mode (fixed value, percentage of a +contract field, or Python expression); the amount is re-evaluated per +payslip. The default fixed mode reproduces the historical behaviour. +Template bounds are enforced on the amount.

Table of contents

    @@ -393,7 +392,7 @@

    Payroll Contract Advantages

-

Usage

+

Usage

  • Set the advantages templates in the payroll module with lower and upper bounds and default value.
  • @@ -401,10 +400,18 @@

    Usage

    this contract, default value will be populated but you can change it.
  • Then in the salary rules, access this value using current_contract.advantages.[ADVANTAGE_CODE] (without brackets)
  • +
  • On the template, choose a computation mode (fixed value, percentage of +a contract field, or Python code). The definition is copied onto the +advantage and can be tuned per contract.
  • +
  • The amount is re-evaluated for each payslip. Python formulas expose +advantage, contract, employee, payslip and must set +result.
  • +
  • Bounds are enforced on the amount; a non-numeric formula result raises +an error.
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -412,22 +419,23 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Nimarosa
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -442,6 +450,5 @@

Maintainers

-
diff --git a/payroll_contract_advantages/tests/test_payroll_contract_advantages.py b/payroll_contract_advantages/tests/test_payroll_contract_advantages.py index 86013c95c..e0fa73fad 100644 --- a/payroll_contract_advantages/tests/test_payroll_contract_advantages.py +++ b/payroll_contract_advantages/tests/test_payroll_contract_advantages.py @@ -1,7 +1,7 @@ # Copyright 2025 - TODAY, Cristiano Mafra Junior # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError, ValidationError from odoo.addons.payroll.tests.common import TestPayslipBase @@ -114,3 +114,184 @@ def test_get_current_contract_dict_contains_advantages(self): self.assertIsNotNone(advantages) self.assertEqual(advantages.FUEL, 30.0) + + # ------------------------------------------------------------------ + # Computation modes + # ------------------------------------------------------------------ + + def test_default_mode_is_fixed_backward_compatible(self): + """A template created the historical way defaults to 'fixed'.""" + template = self._create_template(default=99.0) + self.assertEqual(template.computation_mode, "fixed") + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + self.assertEqual(advantage.computation_mode, "fixed") + # Historical 'amount' still populated from default value. + self.assertEqual(advantage.amount, 99.0) + + def test_fixed_mode_amount_equals_stored_amount(self): + template = self._create_template(default=150.0, upper=1000000.0) + advantage = self.Advantage.create( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + "computation_mode": "fixed", + "amount": 150.0, + } + ) + self.assertEqual(advantage._compute_advantage_amount(), 150.0) + + def test_percentage_mode_uses_contract_field(self): + template = self._create_template( + lower=0.0, upper=1000000.0, code="HOUSING", name="Housing" + ) + template.computation_mode = "percentage" + template.percentage = 5.0 + template.percentage_base = "wage" + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + self.assertEqual(advantage.computation_mode, "percentage") + self.assertEqual(advantage.percentage, 5.0) + self.assertEqual(advantage.percentage_base, "wage") + + expected = self.richard_contract.wage * 5.0 / 100.0 + self.assertAlmostEqual(advantage._compute_advantage_amount(), expected) + + def test_python_mode_evaluates_formula(self): + template = self._create_template( + lower=0.0, upper=1000000.0, code="PY", name="Py" + ) + template.computation_mode = "python" + template.python_code = "result = contract.wage * 0.10" + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + expected = self.richard_contract.wage * 0.10 + self.assertAlmostEqual(advantage._compute_advantage_amount(), expected) + + def test_python_mode_receives_payslip_in_localdict(self): + """The python localdict must expose payslip (needed by + period-sensitive localisation formulas).""" + template = self._create_template( + lower=0.0, upper=1000000.0, code="PYPS", name="PyPayslip" + ) + template.computation_mode = "python" + # If payslip is exposed and not None, result is the wage, + # otherwise 0 -> asserts the name is present in the localdict. + template.python_code = "result = contract.wage if payslip is not None else 0.0" + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + + self.apply_contract_cron() + payslip = self.Payslip.create({"employee_id": self.richard_emp.id}) + payslip.onchange_employee() + amount = advantage._compute_advantage_amount(payslip=payslip) + self.assertAlmostEqual(amount, self.richard_contract.wage) + + def test_bounds_enforced_on_computed_amount(self): + """Bounds must be checked on the evaluated amount, not only on + a manually typed one.""" + template = self._create_template( + lower=0.0, upper=100.0, code="CAP", name="Capped" + ) + template.computation_mode = "python" + template.python_code = "result = 500.0" # above upper bound + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + with self.assertRaises(ValidationError): + advantage._compute_advantage_amount() + + def test_get_current_contract_dict_evaluates_percentage(self): + """End to end: the value exposed to salary rules is the + evaluated formula, recomputed at payslip time.""" + template = self._create_template( + lower=0.0, upper=1000000.0, code="PCT", name="Pct" + ) + template.computation_mode = "percentage" + template.percentage = 10.0 + template.percentage_base = "wage" + + self.Advantage.create( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + "computation_mode": "percentage", + "percentage": 10.0, + "percentage_base": "wage", + } + ) + + self.apply_contract_cron() + payslip = self.Payslip.create({"employee_id": self.richard_emp.id}) + payslip.onchange_employee() + contracts = payslip._get_employee_contracts() + res = payslip.get_current_contract_dict(self.richard_contract, contracts) + expected = self.richard_contract.wage * 10.0 / 100.0 + self.assertAlmostEqual(res.get("advantages").PCT, expected) + + def test_python_returning_non_float_raises_usererror(self): + """A python_code formula that yields a non-numeric value must + fail loudly (float guarantee mirrored from the payroll engine), + not silently corrupt the payslip.""" + template = self._create_template( + lower=0.0, upper=1000000.0, code="BAD", name="BadFormula" + ) + template.computation_mode = "python" + template.python_code = "result = 'not-a-number'" + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + with self.assertRaises(UserError): + advantage._compute_advantage_amount() + + def test_python_returning_int_is_coerced_to_float(self): + """An int result is acceptable and coerced to float (no error).""" + template = self._create_template( + lower=0.0, upper=1000000.0, code="INTOK", name="IntOk" + ) + template.computation_mode = "python" + template.python_code = "result = 1500" + + advantage = self.Advantage.new( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + } + ) + advantage._onchange_advantage_template_id() + amount = advantage._compute_advantage_amount() + self.assertEqual(amount, 1500.0) + self.assertIsInstance(amount, float) diff --git a/payroll_contract_advantages/views/hr_contract_advantage_views.xml b/payroll_contract_advantages/views/hr_contract_advantage_views.xml index b58eb7bd7..719e0d63b 100644 --- a/payroll_contract_advantages/views/hr_contract_advantage_views.xml +++ b/payroll_contract_advantages/views/hr_contract_advantage_views.xml @@ -17,11 +17,31 @@ - - + + + + + + + + @@ -34,6 +54,7 @@ + diff --git a/payroll_contract_advantages/views/hr_contract_views.xml b/payroll_contract_advantages/views/hr_contract_views.xml index dfe5643b4..7d19e078d 100644 --- a/payroll_contract_advantages/views/hr_contract_views.xml +++ b/payroll_contract_advantages/views/hr_contract_views.xml @@ -8,16 +8,48 @@ - + - - + + + +
+ + + + + + + + + + + + + + + +