diff --git a/payroll_contract_advantages/README.rst b/payroll_contract_advantages/README.rst index 29bf2e97c..5f28e338a 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 @@ -89,13 +85,13 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -.. |maintainer-nimarosa| image:: https://github.com/nimarosa.png?size=40px - :target: https://github.com/nimarosa - :alt: nimarosa +.. |maintainer-nimarosa, CristianoMafraJunior| image:: https://github.com/nimarosa, CristianoMafraJunior.png?size=40px + :target: https://github.com/nimarosa, CristianoMafraJunior + :alt: nimarosa, CristianoMafraJunior Current `maintainer `__: -|maintainer-nimarosa| +|maintainer-nimarosa, CristianoMafraJunior| This module is part of the `OCA/payroll `_ project on GitHub. diff --git a/payroll_contract_advantages/__manifest__.py b/payroll_contract_advantages/__manifest__.py index f8351fdbb..f28d1b8b6 100644 --- a/payroll_contract_advantages/__manifest__.py +++ b/payroll_contract_advantages/__manifest__.py @@ -15,5 +15,5 @@ "views/hr_contract_views.xml", ], "application": True, - "maintainers": ["nimarosa"], + "maintainers": ["nimarosa, CristianoMafraJunior"], } diff --git a/payroll_contract_advantages/models/hr_contract_advantage.py b/payroll_contract_advantages/models/hr_contract_advantage.py index ea3cd0b94..276b4bd90 100644 --- a/payroll_contract_advantages/models/hr_contract_advantage.py +++ b/payroll_contract_advantages/models/hr_contract_advantage.py @@ -8,9 +8,17 @@ class HrContractAdvantage(models.Model): _name = "hr.contract.advantage" _description = "Employee's Advantages on Contract" - contract_id = fields.Many2one("hr.contract") + _sql_constraints = [ + ( + "contract_template_unique", + "unique(contract_id, advantage_template_id)", + "This advantage is already set on this contract.", + ) + ] + + contract_id = fields.Many2one("hr.contract", required=True, ondelete="cascade") advantage_template_id = fields.Many2one( - "hr.contract.advantage.template", string="Advantage Template" + "hr.contract.advantage.template", string="Advantage Template", required=True ) advantage_template_code = fields.Char( string="Code", related="advantage_template_id.code", readonly=True @@ -21,7 +29,7 @@ class HrContractAdvantage(models.Model): advantage_upper_bound = fields.Float( string="Upper Bound", related="advantage_template_id.upper_bound", readonly=True ) - amount = fields.Float() + amount = fields.Float(digits="Payroll") @api.onchange("advantage_template_id") def _onchange_advantage_template_id(self): @@ -31,12 +39,22 @@ def _onchange_advantage_template_id(self): @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.") - ) + if not record.amount: + continue + # A bound left at 0.0 (the field default) means "no limit on that + # side", so a template without explicit bounds doesn't block + # every non-zero amount. + if ( + record.advantage_upper_bound + and record.amount > record.advantage_upper_bound + ): + raise ValidationError( + _("Advantage amount can't be greater than upper bound limit.") + ) + if ( + record.advantage_lower_bound + and record.amount < record.advantage_lower_bound + ): + raise ValidationError( + _("Advantage amount can't be less than lower bound limit.") + ) diff --git a/payroll_contract_advantages/models/hr_contract_advantage_template.py b/payroll_contract_advantages/models/hr_contract_advantage_template.py index d3db791b0..ef6c70f19 100644 --- a/payroll_contract_advantages/models/hr_contract_advantage_template.py +++ b/payroll_contract_advantages/models/hr_contract_advantage_template.py @@ -1,18 +1,58 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class HrContractAdvandageTemplate(models.Model): _name = "hr.contract.advantage.template" _description = "Employee's Advantage on Contract" + _sql_constraints = [ + ( + "code_unique", + "unique(code)", + "The code must be unique per advantage template: it is used to " + "reference the advantage from salary rules and payslip lines.", + ) + ] + name = fields.Char(required=True) code = fields.Char(required=True) lower_bound = fields.Float( - help="Lower bound authorized by the employer for this advantage" + digits="Payroll", + help="Lower bound authorized by the employer for this advantage. " + "Leave at 0 for no lower bound.", ) upper_bound = fields.Float( - help="Upper bound authorized by the employer for this advantage" + digits="Payroll", + help="Upper bound authorized by the employer for this advantage. " + "Leave at 0 for no upper bound.", ) - default_value = fields.Float() + default_value = fields.Float(digits="Payroll") + + @api.constrains("lower_bound", "upper_bound", "default_value") + def _check_bounds_consistency(self): + for template in self: + # 0.0 means "no bound on that side", matching + # hr.contract.advantage._check_bound_limits(). + if ( + template.lower_bound + and template.upper_bound + and template.lower_bound > template.upper_bound + ): + raise ValidationError( + _("Lower bound can't be greater than upper bound.") + ) + if template.upper_bound and template.default_value > template.upper_bound: + raise ValidationError( + _("Default value can't be greater than upper bound.") + ) + if ( + template.lower_bound + and template.default_value + and template.default_value < template.lower_bound + ): + raise ValidationError( + _("Default value can't be less than lower bound.") + ) diff --git a/payroll_contract_advantages/static/description/index.html b/payroll_contract_advantages/static/description/index.html index c0e8e1bf6..31a7a46ed 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 @@ -393,7 +388,7 @@

Payroll Contract Advantages

-

Usage

+

Usage

  • Set the advantages templates in the payroll module with lower and upper bounds and default value.
  • @@ -404,7 +399,7 @@

    Usage

-

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 +407,22 @@

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 @@ -436,12 +431,11 @@

Maintainers

mission is to support the collaborative development of Odoo features and promote its widespread use.

Current maintainer:

-

nimarosa

+

nimarosa, CristianoMafraJunior

This module is part of the OCA/payroll project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

-
diff --git a/payroll_contract_advantages/tests/test_payroll_contract_advantages.py b/payroll_contract_advantages/tests/test_payroll_contract_advantages.py index 86013c95c..b6eb8b5f6 100644 --- a/payroll_contract_advantages/tests/test_payroll_contract_advantages.py +++ b/payroll_contract_advantages/tests/test_payroll_contract_advantages.py @@ -1,7 +1,10 @@ # Copyright 2025 - TODAY, Cristiano Mafra Junior # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from psycopg2 import IntegrityError + from odoo.exceptions import ValidationError +from odoo.tests.common import mute_logger from odoo.addons.payroll.tests.common import TestPayslipBase @@ -33,7 +36,7 @@ def _create_template( def test_onchange_advantage_template_sets_default_amount(self): """Onchange should set amount from template default value.""" - template = self._create_template(default=123.45) + template = self._create_template(default=95.45) advantage = self.Advantage.new( { "contract_id": self.richard_contract.id, @@ -72,6 +75,21 @@ def test_constraint_raises_if_above_upper_bound(self): } ) + def test_constraint_allows_amount_when_bounds_unset(self): + """A template left with default (0.0) bounds must not block every + non-zero amount: 0.0 means "no limit on that side".""" + template = self._create_template(lower=0.0, upper=0.0, default=0.0) + + advantage = self.Advantage.create( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + "amount": 30.0, + } + ) + + self.assertEqual(advantage.amount, 30.0) + def test_constraint_raises_if_below_lower_bound(self): """Constraint should raise when amount is below lower bound.""" template = self._create_template(lower=10.0, upper=100.0) @@ -85,6 +103,54 @@ def test_constraint_raises_if_below_lower_bound(self): } ) + def test_duplicate_advantage_on_same_contract_is_rejected(self): + """The same advantage template can't be set twice on one contract: + it would silently overwrite one amount with the other in + get_current_contract_dict().""" + template = self._create_template() + self.Advantage.create( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + "amount": 10.0, + } + ) + + with ( + self.assertRaises(IntegrityError), + mute_logger("odoo.sql_db"), + self.cr.savepoint(), + ): + self.Advantage.create( + { + "contract_id": self.richard_contract.id, + "advantage_template_id": template.id, + "amount": 20.0, + } + ) + + def test_template_code_must_be_unique(self): + """Two templates can't share the same code: it's used as the key to + expose the advantage to salary rules (advantages.).""" + self._create_template(code="DUP") + + with ( + self.assertRaises(IntegrityError), + mute_logger("odoo.sql_db"), + self.cr.savepoint(), + ): + self._create_template(code="DUP", name="Other Benefit") + + def test_template_lower_bound_cannot_exceed_upper_bound(self): + """A template can't be saved with lower_bound > upper_bound.""" + with self.assertRaises(ValidationError): + self._create_template(lower=100.0, upper=10.0) + + def test_template_default_value_must_respect_bounds(self): + """A template's default_value can't fall outside its own bounds.""" + with self.assertRaises(ValidationError): + self._create_template(lower=0.0, upper=100.0, default=150.0) + def test_get_current_contract_dict_contains_advantages(self): """get_current_contract_dict should expose advantages by code.""" template = self._create_template(