From 60e19b0719cc8510da1698677bb610e98c37a2d0 Mon Sep 17 00:00:00 2001 From: alexis-anthonissen Date: Thu, 9 Oct 2025 14:26:52 +0200 Subject: [PATCH 1/2] [IMP] product_contract - add constraints --- product_contract/models/product_template.py | 52 +++++++++++++++++++ .../models/sale_order_line_contract_mixin.py | 42 ++++++++++++++- product_contract/tests/test_product.py | 52 +++++++++++++++++++ product_contract/tests/test_sale_order.py | 36 +++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 1d06c07886..151792054a 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -167,3 +167,55 @@ def _check_contract_product_type(self): """ if any([product.is_contract and product.type != "service" for product in self]): raise ValidationError(_("Contract product should be service type")) + + @api.constrains("recurrence_number") + def _check_recurrence_number_is_strictly_positive(self): + for product in self: + if not product.is_contract: + return + if product.recurrence_number <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + product._fields["recurrence_number"].string, + ) + ) + + @api.constrains("recurring_interval") + def _check_recurring_interval_is_strictly_positive(self): + for product in self: + if not product.is_contract: + return + if product.recurring_interval <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + product._fields["recurring_interval"].string, + ) + ) + + @api.constrains("auto_renew_interval") + def _check_auto_renew_interval_is_strictly_positive(self): + for product in self: + if not product.is_contract or not product.is_auto_renew: + return + if product.auto_renew_interval <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + product._fields["auto_renew_interval"].string, + ) + ) + + @api.constrains("termination_notice_interval") + def _check_termination_notice_interval_is_positive(self): + for product in self: + if not product.is_contract or not product.is_auto_renew: + return + if product.termination_notice_interval < 0: + raise ValidationError( + _( + "Value of %r should be positive", + product._fields["termination_notice_interval"].string, + ) + ) diff --git a/product_contract/models/sale_order_line_contract_mixin.py b/product_contract/models/sale_order_line_contract_mixin.py index 58fc3fc6c5..2b9832ed7f 100644 --- a/product_contract/models/sale_order_line_contract_mixin.py +++ b/product_contract/models/sale_order_line_contract_mixin.py @@ -3,7 +3,8 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class SaleOrderLineContractMixin(models.AbstractModel): @@ -224,3 +225,42 @@ def _get_date_end(self): - relativedelta(days=1) ) return date_end + + @api.constrains("recurrence_number") + def _check_recurrence_number_is_strictly_positive(self): + for line in self: + if not line.is_contract: + return + if line.recurrence_number <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + line._fields["recurrence_number"].string, + ) + ) + + @api.constrains("recurring_interval") + def _check_recurring_interval_is_strictly_positive(self): + for line in self: + if not line.is_contract: + return + if line.recurring_interval <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + line._fields["recurring_interval"].string, + ) + ) + + @api.constrains("auto_renew_interval") + def _check_auto_renew_interval_is_strictly_positive(self): + for line in self: + if not line.is_contract or not line.is_auto_renew: + return + if line.auto_renew_interval <= 0: + raise ValidationError( + _( + "Value of %r should be strictly positive", + line._fields["auto_renew_interval"].string, + ) + ) diff --git a/product_contract/tests/test_product.py b/product_contract/tests/test_product.py index d5f1c767a4..da89e7be34 100644 --- a/product_contract/tests/test_product.py +++ b/product_contract/tests/test_product.py @@ -37,3 +37,55 @@ def test_check_contract_product_type(self): """ with self.assertRaises(ValidationError): self.consu_product.is_contract = True + + def test_check_recurrence_number_is_strictly_positive(self): + """ + It should raise ValidationError if recurrence_number is not strictly positive + for a contract product + """ + self.service_product.is_contract = True + self.service_product.property_contract_template_id = self.contract.id + with self.assertRaises(ValidationError): + self.service_product.recurrence_number = -1 + with self.assertRaises(ValidationError): + self.service_product.recurrence_number = 0 + self.service_product.recurrence_number = 1 + + def test_check_recurring_interval_is_strictly_positive(self): + """ + It should raise ValidationError if recurring_interval is not strictly positive + for a contract product + """ + self.service_product.is_contract = True + self.service_product.property_contract_template_id = self.contract.id + with self.assertRaises(ValidationError): + self.service_product.recurring_interval = -1 + with self.assertRaises(ValidationError): + self.service_product.recurring_interval = 0 + self.service_product.recurring_interval = 1 + + def test_check_auto_renew_interval_is_strictly_positive(self): + """ + It should raise ValidationError if auto_renew_interval is not strictly positive + for a contract product + """ + self.service_product.is_contract = True + self.service_product.is_auto_renew = True + self.service_product.property_contract_template_id = self.contract.id + with self.assertRaises(ValidationError): + self.service_product.auto_renew_interval = -1 + with self.assertRaises(ValidationError): + self.service_product.auto_renew_interval = 0 + self.service_product.auto_renew_interval = 1 + + def test_check_termination_notice_interval_is_positive(self): + """ + It should raise ValidationError if termination_notice_interval is not positive + for a contract product + """ + self.service_product.is_contract = True + self.service_product.is_auto_renew = True + self.service_product.property_contract_template_id = self.contract.id + with self.assertRaises(ValidationError): + self.service_product.termination_notice_interval = -1 + self.service_product.termination_notice_interval = 0 diff --git a/product_contract/tests/test_sale_order.py b/product_contract/tests/test_sale_order.py index 3bf8aac2e9..db6b3db645 100644 --- a/product_contract/tests/test_sale_order.py +++ b/product_contract/tests/test_sale_order.py @@ -669,3 +669,39 @@ def test_order_line_date_start_confirm(self): sale.order_line.contract_id.contract_line_ids.date_start, fields.Date.to_date("2025-02-28"), ) + + def test_check_recurrence_number_is_strictly_positive(self): + """ + It should raise ValidationError if recurrence_number is not strictly positive + for a sale order line with a contract product + """ + self.sale.order_line.is_auto_renew = True + with self.assertRaises(ValidationError): + self.sale.order_line.recurrence_number = -1 + with self.assertRaises(ValidationError): + self.sale.order_line.recurrence_number = 0 + self.sale.order_line.recurrence_number = 1 + + def test_check_recurring_interval_is_strictly_positive(self): + """ + It should raise ValidationError if recurring_interval is not strictly positive + for a sale order line with a contract product + """ + self.sale.order_line.is_auto_renew = True + with self.assertRaises(ValidationError): + self.sale.order_line.recurring_interval = -1 + with self.assertRaises(ValidationError): + self.sale.order_line.recurring_interval = 0 + self.sale.order_line.recurring_interval = 1 + + def test_check_auto_renew_interval_is_strictly_positive(self): + """ + It should raise ValidationError if auto_renew_interval is not strictly positive + for a sale order line with a contract product + """ + self.sale.order_line.is_auto_renew = True + with self.assertRaises(ValidationError): + self.sale.order_line.auto_renew_interval = -1 + with self.assertRaises(ValidationError): + self.sale.order_line.auto_renew_interval = 0 + self.sale.order_line.auto_renew_interval = 1 From 5a409fe24185e8a0f5f9844b64c5e71688bc75f7 Mon Sep 17 00:00:00 2001 From: anthonissen-a Date: Mon, 23 Feb 2026 10:26:12 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Pedro M. Baeza --- product_contract/models/product_template.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/product_contract/models/product_template.py b/product_contract/models/product_template.py index 151792054a..75c579376b 100644 --- a/product_contract/models/product_template.py +++ b/product_contract/models/product_template.py @@ -172,7 +172,7 @@ def _check_contract_product_type(self): def _check_recurrence_number_is_strictly_positive(self): for product in self: if not product.is_contract: - return + continue if product.recurrence_number <= 0: raise ValidationError( _( @@ -185,7 +185,7 @@ def _check_recurrence_number_is_strictly_positive(self): def _check_recurring_interval_is_strictly_positive(self): for product in self: if not product.is_contract: - return + continue if product.recurring_interval <= 0: raise ValidationError( _( @@ -198,7 +198,7 @@ def _check_recurring_interval_is_strictly_positive(self): def _check_auto_renew_interval_is_strictly_positive(self): for product in self: if not product.is_contract or not product.is_auto_renew: - return + continue if product.auto_renew_interval <= 0: raise ValidationError( _( @@ -211,7 +211,7 @@ def _check_auto_renew_interval_is_strictly_positive(self): def _check_termination_notice_interval_is_positive(self): for product in self: if not product.is_contract or not product.is_auto_renew: - return + continue if product.termination_notice_interval < 0: raise ValidationError( _(