From b5ca4cd6b19ac354d2da3145e3a3ca134fb2cc10 Mon Sep 17 00:00:00 2001 From: Quentin Groulard Date: Wed, 3 Jun 2026 15:05:22 +0200 Subject: [PATCH 1/5] [IMP] contract: _get_contract_line_total_value() Previous implementation had 3 problems: * Discounts were not taken into account * When combined with 'contract_variable_quantity', '_get_quantity_to_invoice()' returns 1 for fixed quantities * When combined with 'contract_variable_quantity', it is wrong to assume that the quantity to invoice of one big period is equal to the quantity to invoice of the sum of smaller chunks. Because of the two last problems, we are forced to iterate over each periods and sum the subtotals. --- contract/models/contract_line.py | 33 +++++++++++++--- contract/tests/test_contract.py | 67 +++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/contract/models/contract_line.py b/contract/models/contract_line.py index a86aa706f8..23efbc4744 100644 --- a/contract/models/contract_line.py +++ b/contract/models/contract_line.py @@ -361,9 +361,32 @@ def _get_contract_line_total_value(self): self.display_name, ) ) - quantity = self._get_quantity_to_invoice( - self.date_start, - self.date_end, - self.date_start, + + period_length = self.get_relative_delta( + self.recurring_rule_type, self.recurring_interval + ) + currency = ( + self.contract_id.pricelist_id.currency_id + if self.contract_id.pricelist_id + else self.currency_id ) - return quantity * self.price_unit + + current_period_start = self.date_start + total_amount = 0.0 + while current_period_start <= self.date_end: + current_period_end = ( + current_period_start + period_length - relativedelta(days=1) + ) + if current_period_end > self.date_end: + current_period_end = self.date_end + qty = self._get_quantity_to_invoice( + current_period_start, + current_period_end, + current_period_start, # Mock an invoice date at period start + ) + subtotal = qty * self.price_unit * (1 - (self.discount / 100.0)) + if currency: + subtotal = currency.round(subtotal) + total_amount += subtotal + current_period_start += period_length + return total_amount diff --git a/contract/tests/test_contract.py b/contract/tests/test_contract.py index 96073b686f..6a4727f49a 100644 --- a/contract/tests/test_contract.py +++ b/contract/tests/test_contract.py @@ -11,6 +11,7 @@ from odoo import Command, fields from odoo.exceptions import UserError, ValidationError +from odoo.fields import Date from odoo.tests import Form, common @@ -1617,33 +1618,41 @@ def test_get_contract_line_total_value_basic(self): { "quantity": 3, "price_unit": 400.0, + "discount": 0.0, "date_start": "2026-01-01", "date_end": "2026-12-31", - "recurring_next_date": "2026-01-01", + "recurring_interval": 1, + "recurring_rule_type": "monthly", } ) - self.assertEqual(self.acct_line._get_contract_line_total_value(), 3 * 400.0) + # 12 months * (3 * 400.0) = 14,400.0 + self.assertEqual(self.acct_line._get_contract_line_total_value(), 14400.0) def test_get_contract_line_total_value_single_unit(self): self.acct_line.write( { "quantity": 1, "price_unit": 1200.0, + "discount": 0.0, "date_start": "2026-01-01", "date_end": "2026-12-31", - "recurring_next_date": "2026-01-01", + "recurring_interval": 1, + "recurring_rule_type": "monthly", } ) - self.assertEqual(self.acct_line._get_contract_line_total_value(), 1200.0) + # 12 months * 1200.0 = 14,400.0 + self.assertEqual(self.acct_line._get_contract_line_total_value(), 14400.0) def test_get_contract_line_total_value_zero_price(self): self.acct_line.write( { "quantity": 5, "price_unit": 0.0, + "discount": 0.0, "date_start": "2026-01-01", "date_end": "2026-06-30", - "recurring_next_date": "2026-01-01", + "recurring_interval": 1, + "recurring_rule_type": "monthly", } ) self.assertEqual(self.acct_line._get_contract_line_total_value(), 0.0) @@ -1657,8 +1666,8 @@ def test_get_contract_line_total_value_no_date_end_raises(self): "name": "Test", "quantity": 1, "price_unit": 1000.0, + "discount": 0.0, "date_start": to_date("2026-01-01"), - "recurring_next_date": to_date("2026-01-01"), "recurring_interval": 1, "recurring_rule_type": "monthly", "recurring_invoicing_type": "pre-paid", @@ -1668,3 +1677,49 @@ def test_get_contract_line_total_value_no_date_end_raises(self): line.date_end = False with self.assertRaises(UserError): line._get_contract_line_total_value() + + def test_get_contract_line_total_value_with_discount(self): + self.acct_line.write( + { + "quantity": 2, + "price_unit": 100.0, + "discount": 10.0, + "date_start": "2026-01-01", + "date_end": "2026-12-31", + "recurring_interval": 1, + "recurring_rule_type": "monthly", + } + ) + # Per period: 2 * 100 * 0.90 = 180.0 + # Total (12 periods): 2160.0 + self.assertEqual(self.acct_line._get_contract_line_total_value(), 2160.0) + + def test_contract_line_total_value_period_prorata(self): + """Test a contract that terminates mid-way through an invoicing period.""" + self.acct_line.write( + { + "quantity": 1, + "price_unit": 100.0, + "discount": 0.0, + "date_start": "2026-01-01", + "date_end": "2026-02-15", + "recurring_interval": 1, + "recurring_rule_type": "monthly", + } + ) + + expected_qty_period_1 = self.acct_line._get_quantity_to_invoice( + Date.to_date("2026-01-01"), + Date.to_date("2026-01-31"), + Date.to_date("2026-01-01"), + ) + expected_qty_period_2 = self.acct_line._get_quantity_to_invoice( + Date.to_date("2026-02-01"), + Date.to_date("2026-02-15"), + Date.to_date("2026-02-01"), + ) + + expected_total = (expected_qty_period_1 + expected_qty_period_2) * 100.0 + self.assertEqual( + self.acct_line._get_contract_line_total_value(), expected_total + ) From 0c517ae3650eaffb85d7cc5df51cb56995965802 Mon Sep 17 00:00:00 2001 From: Benoit Aimont Date: Thu, 11 Jun 2026 10:52:59 +0200 Subject: [PATCH 2/5] [ADD] contract_service_dates --- contract_service_dates/README.rst | 104 ++++ contract_service_dates/__init__.py | 1 + contract_service_dates/__manifest__.py | 18 + contract_service_dates/models/__init__.py | 1 + .../models/contract_line.py | 58 +++ contract_service_dates/pyproject.toml | 3 + contract_service_dates/readme/CONTEXT.md | 4 + contract_service_dates/readme/CONTRIBUTORS.md | 1 + contract_service_dates/readme/DESCRIPTION.md | 3 + contract_service_dates/readme/USAGE.md | 13 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 451 ++++++++++++++++++ contract_service_dates/tests/__init__.py | 1 + .../tests/test_contract_service_dates.py | 100 ++++ .../views/contract_line.xml | 22 + 15 files changed, 780 insertions(+) create mode 100644 contract_service_dates/README.rst create mode 100644 contract_service_dates/__init__.py create mode 100644 contract_service_dates/__manifest__.py create mode 100644 contract_service_dates/models/__init__.py create mode 100644 contract_service_dates/models/contract_line.py create mode 100644 contract_service_dates/pyproject.toml create mode 100644 contract_service_dates/readme/CONTEXT.md create mode 100644 contract_service_dates/readme/CONTRIBUTORS.md create mode 100644 contract_service_dates/readme/DESCRIPTION.md create mode 100644 contract_service_dates/readme/USAGE.md create mode 100644 contract_service_dates/static/description/icon.png create mode 100644 contract_service_dates/static/description/index.html create mode 100644 contract_service_dates/tests/__init__.py create mode 100644 contract_service_dates/tests/test_contract_service_dates.py create mode 100644 contract_service_dates/views/contract_line.xml diff --git a/contract_service_dates/README.rst b/contract_service_dates/README.rst new file mode 100644 index 0000000000..cbd7cb7720 --- /dev/null +++ b/contract_service_dates/README.rst @@ -0,0 +1,104 @@ +====================== +Contract Service Dates +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:01fafa9a374719d9a39c0ca8a5e7794fe9c073a5de2da889a5a0a78e6e1eb4ac + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github + :target: https://github.com/OCA/contract/tree/18.0/contract_service_dates + :alt: OCA/contract +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/contract-18-0/contract-18-0-contract_service_dates + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds **Service Start Date** and **Service End Date** fields +to contract lines, allowing you to track the actual service period +independently from the billing dates. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Contract lines in the base contract module carry a single start date and +end date that drive the recurring invoicing schedule. There is no +dedicated field to record the period during which the service is +actually delivered, which is often distinct from the billing period. +This module fills that gap. + +Usage +===== + +On a contract line form, a new **Service Dates** group is displayed in +the **Recurrence** tab, below the contract line start and end dates: + +- **Service Start Date**: start of the service period. Populated + automatically from the contract line start date and kept in sync with + it unless manually overridden. +- **Service End Date**: end of the service period. Populated + automatically from the contract line end date and kept in sync with it + unless manually overridden. + +Once a service date is set to a value different from its corresponding +contract line date, it is considered decoupled and will no longer be +updated automatically when the contract line date changes. + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Benoit Aimont benoit.aimont@acsone.eu (https://www.acsone.eu/) + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/contract `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/contract_service_dates/__init__.py b/contract_service_dates/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/contract_service_dates/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/contract_service_dates/__manifest__.py b/contract_service_dates/__manifest__.py new file mode 100644 index 0000000000..23e6bdb08a --- /dev/null +++ b/contract_service_dates/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Contract Service Dates", + "summary": """This module adds Service Dates fields to contract lines""", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/contract", + "depends": [ + "contract", + ], + "data": [ + "views/contract_line.xml", + ], + "installable": True, +} diff --git a/contract_service_dates/models/__init__.py b/contract_service_dates/models/__init__.py new file mode 100644 index 0000000000..6143a36501 --- /dev/null +++ b/contract_service_dates/models/__init__.py @@ -0,0 +1 @@ +from . import contract_line diff --git a/contract_service_dates/models/contract_line.py b/contract_service_dates/models/contract_line.py new file mode 100644 index 0000000000..6453b6da4c --- /dev/null +++ b/contract_service_dates/models/contract_line.py @@ -0,0 +1,58 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ContractLine(models.Model): + _inherit = "contract.line" + + service_start_date = fields.Date( + help="Start date of the service period covered by this contract line. " + "Defaults to the contract line start date and follows it unless " + "manually overridden.", + ) + service_end_date = fields.Date( + help="End date of the service period covered by this contract line. " + "Defaults to the contract line end date and follows it unless " + "manually overridden.", + ) + + def _get_service_date_create_vals(self, vals): + """Return service date vals to initialize on create.""" + result = {} + if vals.get("date_start") and not vals.get("service_start_date"): + result["service_start_date"] = vals["date_start"] + if vals.get("date_end") and not vals.get("service_end_date"): + result["service_end_date"] = vals["date_end"] + return result + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + vals.update(self._get_service_date_create_vals(vals)) + return super().create(vals_list) + + def _get_service_date_sync_vals(self, vals): + """Return service date vals to sync from contract date vals.""" + self.ensure_one() + service_vals = {} + if "date_start" in vals and self.service_start_date == self.date_start: + service_vals["service_start_date"] = vals["date_start"] + if "date_end" in vals and self.service_end_date == self.date_end: + service_vals["service_end_date"] = vals["date_end"] + return service_vals + + def write(self, vals): + if not self.env.context.get("_sync_service_dates"): + service_vals_by_id = {} + for rec in self: + service_vals = rec._get_service_date_sync_vals(vals) + if service_vals: + service_vals_by_id[rec.id] = service_vals + for rec in self: + if rec.id in service_vals_by_id: + rec.with_context(_sync_service_dates=True).write( + service_vals_by_id[rec.id] + ) + return super().write(vals) diff --git a/contract_service_dates/pyproject.toml b/contract_service_dates/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/contract_service_dates/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/contract_service_dates/readme/CONTEXT.md b/contract_service_dates/readme/CONTEXT.md new file mode 100644 index 0000000000..de57297386 --- /dev/null +++ b/contract_service_dates/readme/CONTEXT.md @@ -0,0 +1,4 @@ +Contract lines in the base contract module carry a single start date and end +date that drive the recurring invoicing schedule. There is no dedicated field +to record the period during which the service is actually delivered, which is +often distinct from the billing period. This module fills that gap. diff --git a/contract_service_dates/readme/CONTRIBUTORS.md b/contract_service_dates/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..1fbb3f1bf0 --- /dev/null +++ b/contract_service_dates/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Benoit Aimont (https://www.acsone.eu/) diff --git a/contract_service_dates/readme/DESCRIPTION.md b/contract_service_dates/readme/DESCRIPTION.md new file mode 100644 index 0000000000..ee04e3c2f0 --- /dev/null +++ b/contract_service_dates/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module adds **Service Start Date** and **Service End Date** fields to +contract lines, allowing you to track the actual service period independently +from the billing dates. diff --git a/contract_service_dates/readme/USAGE.md b/contract_service_dates/readme/USAGE.md new file mode 100644 index 0000000000..5a2ad71f39 --- /dev/null +++ b/contract_service_dates/readme/USAGE.md @@ -0,0 +1,13 @@ +On a contract line form, a new **Service Dates** group is displayed in the +**Recurrence** tab, below the contract line start and end dates: + +- **Service Start Date**: start of the service period. Populated automatically + from the contract line start date and kept in sync with it unless manually + overridden. +- **Service End Date**: end of the service period. Populated automatically from + the contract line end date and kept in sync with it unless manually + overridden. + +Once a service date is set to a value different from its corresponding contract +line date, it is considered decoupled and will no longer be updated +automatically when the contract line date changes. diff --git a/contract_service_dates/static/description/icon.png b/contract_service_dates/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/contract_service_dates/static/description/index.html b/contract_service_dates/static/description/index.html new file mode 100644 index 0000000000..3ec8ac3817 --- /dev/null +++ b/contract_service_dates/static/description/index.html @@ -0,0 +1,451 @@ + + + + + +Contract Service Dates + + + +
+

Contract Service Dates

+ + +

Beta License: AGPL-3 OCA/contract Translate me on Weblate Try me on Runboat

+

This module adds Service Start Date and Service End Date fields +to contract lines, allowing you to track the actual service period +independently from the billing dates.

+

Table of contents

+ +
+

Use Cases / Context

+

Contract lines in the base contract module carry a single start date and +end date that drive the recurring invoicing schedule. There is no +dedicated field to record the period during which the service is +actually delivered, which is often distinct from the billing period. +This module fills that gap.

+
+
+

Usage

+

On a contract line form, a new Service Dates group is displayed in +the Recurrence tab, below the contract line start and end dates:

+
    +
  • Service Start Date: start of the service period. Populated +automatically from the contract line start date and kept in sync with +it unless manually overridden.
  • +
  • Service End Date: end of the service period. Populated +automatically from the contract line end date and kept in sync with it +unless manually overridden.
  • +
+

Once a service date is set to a value different from its corresponding +contract line date, it is considered decoupled and will no longer be +updated automatically when the contract line date changes.

+
+
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE SA/NV
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

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

+

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

+
+
+
+ + diff --git a/contract_service_dates/tests/__init__.py b/contract_service_dates/tests/__init__.py new file mode 100644 index 0000000000..46011f4449 --- /dev/null +++ b/contract_service_dates/tests/__init__.py @@ -0,0 +1 @@ +from . import test_contract_service_dates diff --git a/contract_service_dates/tests/test_contract_service_dates.py b/contract_service_dates/tests/test_contract_service_dates.py new file mode 100644 index 0000000000..09e3831d26 --- /dev/null +++ b/contract_service_dates/tests/test_contract_service_dates.py @@ -0,0 +1,100 @@ +# Copyright 2026 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import date + +from odoo.tests.common import TransactionCase + + +class TestContractServiceDates(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env["res.partner"].create({"name": "Test Partner"}) + cls.product = cls.env["product.product"].create( + {"name": "Test Product", "type": "service"} + ) + journal = cls.env["account.journal"].search( + [("type", "=", "sale"), ("company_id", "=", cls.env.company.id)], + limit=1, + ) + cls.contract = cls.env["contract.contract"].create( + { + "name": "Test Contract", + "partner_id": cls.partner.id, + "journal_id": journal.id, + "contract_type": "sale", + "line_recurrence": True, + } + ) + + def _create_line(self, date_start, date_end=None, **extra): + vals = { + "contract_id": self.contract.id, + "product_id": self.product.id, + "name": "Test line", + "quantity": 1.0, + "price_unit": 100.0, + "date_start": date_start, + "recurring_next_date": date_start, + "recurring_rule_type": "monthly", + "recurring_invoicing_type": "pre-paid", + "recurring_interval": 1, + } + if date_end is not None: + vals["date_end"] = date_end + vals.update(extra) + return self.env["contract.line"].create(vals) + + def test_create_copies_dates(self): + """service dates are initialised from date_start / date_end on create.""" + line = self._create_line(date(2025, 1, 1), date(2025, 12, 31)) + self.assertEqual(line.service_start_date, date(2025, 1, 1)) + self.assertEqual(line.service_end_date, date(2025, 12, 31)) + + def test_create_no_end_date(self): + """service_end_date is False when date_end is not set.""" + line = self._create_line(date(2025, 1, 1)) + self.assertEqual(line.service_start_date, date(2025, 1, 1)) + self.assertFalse(line.service_end_date) + + def test_create_explicit_service_dates_preserved(self): + """Explicitly supplied service dates are not overwritten on create.""" + line = self._create_line( + date(2025, 1, 1), + date(2025, 12, 31), + service_start_date=date(2025, 3, 1), + service_end_date=date(2025, 9, 30), + ) + self.assertEqual(line.service_start_date, date(2025, 3, 1)) + self.assertEqual(line.service_end_date, date(2025, 9, 30)) + + def test_write_date_start_syncs_service_start(self): + """Modifying date_start updates service_start_date when still in sync.""" + line = self._create_line(date(2025, 1, 1), date(2025, 12, 31)) + line.write({"date_start": date(2025, 2, 1)}) + self.assertEqual(line.service_start_date, date(2025, 2, 1)) + + def test_write_date_end_syncs_service_end(self): + """Modifying date_end updates service_end_date when still in sync.""" + line = self._create_line(date(2025, 1, 1), date(2025, 12, 31)) + line.write({"date_end": date(2025, 6, 30)}) + self.assertEqual(line.service_end_date, date(2025, 6, 30)) + + def test_write_date_start_does_not_overwrite_decoupled_service_start(self): + """Modifying date_start leaves service_start_date alone when decoupled.""" + line = self._create_line(date(2025, 1, 1), date(2025, 12, 31)) + # Manually decouple service_start_date + line.write({"service_start_date": date(2025, 3, 1)}) + # Now change date_start — service_start_date must not follow + line.write({"date_start": date(2025, 2, 1)}) + self.assertEqual(line.service_start_date, date(2025, 3, 1)) + + def test_write_date_end_does_not_overwrite_decoupled_service_end(self): + """Modifying date_end leaves service_end_date alone when decoupled.""" + line = self._create_line(date(2025, 1, 1), date(2025, 12, 31)) + # Manually decouple service_end_date + line.write({"service_end_date": date(2025, 9, 30)}) + # Now change date_end — service_end_date must not follow + line.write({"date_end": date(2026, 12, 31)}) + self.assertEqual(line.service_end_date, date(2025, 9, 30)) diff --git a/contract_service_dates/views/contract_line.xml b/contract_service_dates/views/contract_line.xml new file mode 100644 index 0000000000..12c55a4596 --- /dev/null +++ b/contract_service_dates/views/contract_line.xml @@ -0,0 +1,22 @@ + + + + + contract.line form view (in contract_service_dates) + contract.line + + + + + + + + + + + + + + + From c399248bdadf06a599fc5bbc95ba99eb06e7e3c3 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 28 Jul 2026 06:49:47 +0000 Subject: [PATCH 3/5] [BOT] post-merge updates --- README.md | 2 +- contract/README.rst | 2 +- contract/__manifest__.py | 2 +- contract/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ea5ea50656..d40e982c9c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Available addons addon | version | maintainers | summary --- | --- | --- | --- [agreement_rebate_partner_company_group](agreement_rebate_partner_company_group/) | 18.0.1.0.0 | | Rebate agreements applied to all company group members -[contract](contract/) | 18.0.2.5.0 | | Recurring - Contracts Management +[contract](contract/) | 18.0.2.5.1 | | Recurring - Contracts Management [contract_analytic_tag](contract_analytic_tag/) | 18.0.1.0.0 | victoralmau | Contract Analytic Tag [contract_forecast](contract_forecast/) | 18.0.1.0.1 | sbejaoui | Contract Forecast [contract_forecast_variable_quantity](contract_forecast_variable_quantity/) | 18.0.1.0.0 | | Contract Forecast Variable Quantity diff --git a/contract/README.rst b/contract/README.rst index 0e8c685553..6d7d7db1fc 100644 --- a/contract/README.rst +++ b/contract/README.rst @@ -11,7 +11,7 @@ Recurring - Contracts Management !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:f31a70112d86e7b2f6237d0ad274d0a55e384fe483eb0b1a4fe4a0fca1b05c9a + !! source digest: sha256:18b03b2e2527c4875b06eb16fdea75963f902195518f9c5c755649aa89352108 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png diff --git a/contract/__manifest__.py b/contract/__manifest__.py index c31d17ee55..9753be2b45 100644 --- a/contract/__manifest__.py +++ b/contract/__manifest__.py @@ -11,7 +11,7 @@ { "name": "Recurring - Contracts Management", - "version": "18.0.2.5.0", + "version": "18.0.2.5.1", "category": "Contract Management", "license": "AGPL-3", "author": "Tecnativa, ACSONE SA/NV, Odoo Community Association (OCA)", diff --git a/contract/static/description/index.html b/contract/static/description/index.html index 736d38a5a2..aa4bd95ab9 100644 --- a/contract/static/description/index.html +++ b/contract/static/description/index.html @@ -372,7 +372,7 @@

Recurring - Contracts Management

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:f31a70112d86e7b2f6237d0ad274d0a55e384fe483eb0b1a4fe4a0fca1b05c9a +!! source digest: sha256:18b03b2e2527c4875b06eb16fdea75963f902195518f9c5c755649aa89352108 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Production/Stable License: AGPL-3 OCA/contract Translate me on Weblate Try me on Runboat

This module enables contracts management with recurring invoicing From 5584b09503f107a3388a4fbb2d662e68bb7b5c87 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 28 Jul 2026 08:05:45 +0000 Subject: [PATCH 4/5] [UPD] Update contract_service_dates.pot --- .../i18n/contract_service_dates.pot | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 contract_service_dates/i18n/contract_service_dates.pot diff --git a/contract_service_dates/i18n/contract_service_dates.pot b/contract_service_dates/i18n/contract_service_dates.pot new file mode 100644 index 0000000000..ca155fc6e1 --- /dev/null +++ b/contract_service_dates/i18n/contract_service_dates.pot @@ -0,0 +1,48 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_service_dates +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: contract_service_dates +#: model:ir.model,name:contract_service_dates.model_contract_line +msgid "Contract Line" +msgstr "" + +#. module: contract_service_dates +#: model:ir.model.fields,help:contract_service_dates.field_contract_line__service_end_date +msgid "" +"End date of the service period covered by this contract line. Defaults to " +"the contract line end date and follows it unless manually overridden." +msgstr "" + +#. module: contract_service_dates +#: model_terms:ir.ui.view,arch_db:contract_service_dates.contract_line_form_view +msgid "Service Dates" +msgstr "" + +#. module: contract_service_dates +#: model:ir.model.fields,field_description:contract_service_dates.field_contract_line__service_end_date +msgid "Service End Date" +msgstr "" + +#. module: contract_service_dates +#: model:ir.model.fields,field_description:contract_service_dates.field_contract_line__service_start_date +msgid "Service Start Date" +msgstr "" + +#. module: contract_service_dates +#: model:ir.model.fields,help:contract_service_dates.field_contract_line__service_start_date +msgid "" +"Start date of the service period covered by this contract line. Defaults to " +"the contract line start date and follows it unless manually overridden." +msgstr "" From bd5e180390b99fa92f3bbde349fc492165070557 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 28 Jul 2026 08:12:04 +0000 Subject: [PATCH 5/5] [BOT] post-merge updates --- README.md | 1 + contract_service_dates/README.rst | 8 +++-- .../static/description/index.html | 30 +++++++++++-------- setup/_metapackage/pyproject.toml | 3 +- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d40e982c9c..a679f95b7a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ addon | version | maintainers | summary [contract_sale_mandate](contract_sale_mandate/) | 18.0.1.0.0 | | This module manages the banking mandate from the sale order to the contract. [contract_sale_payment_mode](contract_sale_payment_mode/) | 18.0.1.0.0 | | This addon manages payment mode from sale order to contract. [contract_sale_transmit_method](contract_sale_transmit_method/) | 18.0.1.0.0 | | Propagate transmit method (email, post, portal, ...) from sale orders to contracts. +[contract_service_dates](contract_service_dates/) | 18.0.1.0.0 | | This module adds Service Dates fields to contract lines [contract_termination](contract_termination/) | 18.0.1.0.0 | | contract_termination [contract_transmit_method](contract_transmit_method/) | 18.0.1.1.0 | | Set transmit method (email, post, portal, ...) in contracts and propagate it to invoices. [contract_update_last_date_invoiced](contract_update_last_date_invoiced/) | 18.0.1.0.1 | rafaelbn Andrii9090 | This module allows to update the last date invoiced if invoices are deleted. diff --git a/contract_service_dates/README.rst b/contract_service_dates/README.rst index cbd7cb7720..bca34517bb 100644 --- a/contract_service_dates/README.rst +++ b/contract_service_dates/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ====================== Contract Service Dates ====================== @@ -7,13 +11,13 @@ Contract Service Dates !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:01fafa9a374719d9a39c0ca8a5e7794fe9c073a5de2da889a5a0a78e6e1eb4ac + !! source digest: sha256:f5918b19fed1521629a39f7c85979c385121f87628d91fecd00889f2c7d699f4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github diff --git a/contract_service_dates/static/description/index.html b/contract_service_dates/static/description/index.html index 3ec8ac3817..68de22a185 100644 --- a/contract_service_dates/static/description/index.html +++ b/contract_service_dates/static/description/index.html @@ -3,7 +3,7 @@ -Contract Service Dates +README.rst -

-

Contract Service Dates

+
+ + +Odoo Community Association + +
+

Contract Service Dates

-

Beta License: AGPL-3 OCA/contract Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/contract Translate me on Weblate Try me on Runboat

This module adds Service Start Date and Service End Date fields to contract lines, allowing you to track the actual service period independently from the billing dates.

@@ -388,7 +393,7 @@

Contract Service Dates

-

Use Cases / Context

+

Use Cases / Context

Contract lines in the base contract module carry a single start date and end date that drive the recurring invoicing schedule. There is no dedicated field to record the period during which the service is @@ -396,7 +401,7 @@

Use Cases / Context

This module fills that gap.

-

Usage

+

Usage

On a contract line form, a new Service Dates group is displayed in the Recurrence tab, below the contract line start and end dates:

    @@ -412,7 +417,7 @@

    Usage

    updated automatically when the contract line date changes.

-

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 @@ -420,21 +425,21 @@

Bug Tracker

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

+
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 7ef6614daf..fa4079e2b8 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-contract" -version = "18.0.20260227.1" +version = "18.0.20260728.0" dependencies = [ "odoo-addon-agreement_rebate_partner_company_group==18.0.*", "odoo-addon-contract==18.0.*", @@ -22,6 +22,7 @@ dependencies = [ "odoo-addon-contract_sale_mandate==18.0.*", "odoo-addon-contract_sale_payment_mode==18.0.*", "odoo-addon-contract_sale_transmit_method==18.0.*", + "odoo-addon-contract_service_dates==18.0.*", "odoo-addon-contract_termination==18.0.*", "odoo-addon-contract_transmit_method==18.0.*", "odoo-addon-contract_update_last_date_invoiced==18.0.*",