From 9315e89655971949e5996874b0da76607ae56aa5 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Mon, 6 Oct 2025 16:14:51 +0200 Subject: [PATCH 1/5] [18.0][ADD] contract_refund_on_stop This module allows stopping a contract line even after it has been invoiced --- contract_refund_on_stop/README.rst | 150 ++++++ contract_refund_on_stop/__init__.py | 1 + contract_refund_on_stop/__manifest__.py | 13 + .../i18n/contract_refund_on_stop.pot | 50 ++ contract_refund_on_stop/models/__init__.py | 3 + .../models/contract_line.py | 118 +++++ contract_refund_on_stop/models/res_company.py | 14 + .../models/res_config_settings.py | 12 + contract_refund_on_stop/pyproject.toml | 3 + contract_refund_on_stop/readme/CONFIGURE.md | 6 + contract_refund_on_stop/readme/CONTEXT.md | 9 + .../readme/CONTRIBUTORS.md | 1 + contract_refund_on_stop/readme/DESCRIPTION.md | 20 + contract_refund_on_stop/readme/USAGE.md | 6 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 493 ++++++++++++++++++ contract_refund_on_stop/tests/__init__.py | 1 + .../tests/test_contract_refund.py | 138 +++++ .../views/res_config_settings.xml | 16 + 19 files changed, 1054 insertions(+) create mode 100644 contract_refund_on_stop/README.rst create mode 100644 contract_refund_on_stop/__init__.py create mode 100644 contract_refund_on_stop/__manifest__.py create mode 100644 contract_refund_on_stop/i18n/contract_refund_on_stop.pot create mode 100644 contract_refund_on_stop/models/__init__.py create mode 100644 contract_refund_on_stop/models/contract_line.py create mode 100644 contract_refund_on_stop/models/res_company.py create mode 100644 contract_refund_on_stop/models/res_config_settings.py create mode 100644 contract_refund_on_stop/pyproject.toml create mode 100644 contract_refund_on_stop/readme/CONFIGURE.md create mode 100644 contract_refund_on_stop/readme/CONTEXT.md create mode 100644 contract_refund_on_stop/readme/CONTRIBUTORS.md create mode 100644 contract_refund_on_stop/readme/DESCRIPTION.md create mode 100644 contract_refund_on_stop/readme/USAGE.md create mode 100644 contract_refund_on_stop/static/description/icon.png create mode 100644 contract_refund_on_stop/static/description/index.html create mode 100644 contract_refund_on_stop/tests/__init__.py create mode 100644 contract_refund_on_stop/tests/test_contract_refund.py create mode 100644 contract_refund_on_stop/views/res_config_settings.xml diff --git a/contract_refund_on_stop/README.rst b/contract_refund_on_stop/README.rst new file mode 100644 index 0000000000..e82f123d60 --- /dev/null +++ b/contract_refund_on_stop/README.rst @@ -0,0 +1,150 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +======================= +Contract Refund On Stop +======================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3f294b8c8d68ea75034fa2d55dde59867c25bf25a722e001def2330953fe59d7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-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_refund_on_stop + :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_refund_on_stop + :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 allows stopping a contract line even after it has been +invoiced. + +When the stop date is earlier than the last invoiced date, the system +will: + +- Automatically create a refund invoice for the period between the stop + date and the last invoiced date. +- Adjust the ``last_date_invoiced`` of the contract line to match the + stop date. +- Proceed with the normal stop process. + +To accurately compute the refund amount, the module depends on +**``contract_variable_qty_prorated``**, which provides the prorating +logic used to determine how much of the previously invoiced quantity +should be refunded based on the actual number of days covered by the +refund period. + +Without this dependency, it would not be possible to proportionally +calculate the part of the invoiced quantity corresponding to unused +service time when a contract is stopped mid-period. + +This ensures that users can gracefully handle early contract +terminations without manual refund management, while maintaining +accurate prorated invoicing and accounting consistency. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In the standard behavior of the Contract module, it is not possible to +stop a contract line if its stop date is earlier than the last invoiced +date. This restriction prevents users from adjusting contracts that were +invoiced too far in advance. + +In some business cases, however, a contract may need to be stopped +retroactively (e.g., customer cancellation, early termination, or +service interruption). In such cases, it is necessary to **automatically +create a refund** for the invoiced period that should no longer be +billed. + +Configuration +============= + +To enable the refund behavior when stopping invoiced contract lines: + +1. Go to **Invoicing / Configuration / Settings**. + +2. In the **Contracts** section, enable the checkbox: + + **Enable Contract Line Refund on Stop** + +Usage +===== + +1. Ensure the **Enable Contract Line Refund on Stop** option is enabled + for your company. +2. Open any **active contract line** that has been invoiced. +3. Click **Stop** and choose a stop date earlier than the last invoiced + period. +4. The system will: + + - Create a **refund invoice** covering the over-invoiced period. + - Update the contract line to end on the chosen stop date. + +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 +------------ + +- Souheil Bejaoui souheil.bejaoui@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. + +.. |maintainer-sbejaoui| image:: https://github.com/sbejaoui.png?size=40px + :target: https://github.com/sbejaoui + :alt: sbejaoui + +Current `maintainer `__: + +|maintainer-sbejaoui| + +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_refund_on_stop/__init__.py b/contract_refund_on_stop/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/contract_refund_on_stop/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/contract_refund_on_stop/__manifest__.py b/contract_refund_on_stop/__manifest__.py new file mode 100644 index 0000000000..fcbde7df74 --- /dev/null +++ b/contract_refund_on_stop/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Contract Refund On Stop", + "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_line_successor", "contract_variable_qty_prorated"], + "data": ["views/res_config_settings.xml"], + "maintainers": ["sbejaoui"], +} diff --git a/contract_refund_on_stop/i18n/contract_refund_on_stop.pot b/contract_refund_on_stop/i18n/contract_refund_on_stop.pot new file mode 100644 index 0000000000..6f7b7d6e05 --- /dev/null +++ b/contract_refund_on_stop/i18n/contract_refund_on_stop.pot @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_refund_on_stop +# +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_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_company +msgid "Companies" +msgstr "" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_contract_line +msgid "Contract Line" +msgstr "" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "Enable Contract Line Refund On Stop" +msgstr "" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "" +"If enabled, users can stop a contract line even after it has been invoiced. " +"A refund will automatically be created for the invoiced period beyond the " +"stop date." +msgstr "" + +#. module: contract_refund_on_stop +#. odoo-python +#: code:addons/contract_refund_on_stop/models/contract_line.py:0 +msgid "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s" +msgstr "" diff --git a/contract_refund_on_stop/models/__init__.py b/contract_refund_on_stop/models/__init__.py new file mode 100644 index 0000000000..f933fcff36 --- /dev/null +++ b/contract_refund_on_stop/models/__init__.py @@ -0,0 +1,3 @@ +from . import contract_line +from . import res_company +from . import res_config_settings diff --git a/contract_refund_on_stop/models/contract_line.py b/contract_refund_on_stop/models/contract_line.py new file mode 100644 index 0000000000..8207cf13a5 --- /dev/null +++ b/contract_refund_on_stop/models/contract_line.py @@ -0,0 +1,118 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from dateutil.relativedelta import relativedelta + +from odoo import Command, _, models + + +class ContractLine(models.Model): + _inherit = "contract.line" + + def stop(self, date_end, manual_renew_needed=False, post_message=True): + for rec in self: + if ( + not rec.company_id.enable_contract_line_refund_on_stop + or not rec.last_date_invoiced + or rec.last_date_invoiced <= date_end + ): + continue + rec._create_refund_on_stop( + to_refund_start_date=date_end, + to_refund_end_date=rec.last_date_invoiced, + ) + rec.last_date_invoiced = date_end + return super().stop( + date_end, manual_renew_needed=manual_renew_needed, post_message=post_message + ) + + def _create_refund_on_stop(self, to_refund_start_date, to_refund_end_date): + self.ensure_one() + self.env["account.move"].create( + self._prepare_refund_on_stop_vals(to_refund_start_date, to_refund_end_date) + ) + + def _prepare_refund_on_stop_vals(self, to_refund_start_date, to_refund_end_date): + self.ensure_one() + refund_vals = self.contract_id._prepare_invoice(to_refund_start_date) + move_type = ( + "in_refund" if refund_vals["move_type"] == "in_invoice" else "out_refund" + ) + refund_vals["move_type"] = move_type + refund_vals["invoice_line_ids"] = [ + Command.create( + self._prepare_refund_on_stop_line( + to_refund_start_date, to_refund_end_date + ) + ) + ] + return refund_vals + + def _prepare_refund_on_stop_line(self, to_refund_start_date, to_refund_end_date): + self.ensure_one() + line_vals = self._prepare_invoice_line() + line_vals["name"] = self._get_refund_on_stop_line_name( + to_refund_start_date, to_refund_end_date + ) + line_vals["quantity"] = self._get_refund_on_stop_quantity( + to_refund_start_date, to_refund_end_date + ) + return line_vals + + def _get_refund_on_stop_line_name(self, to_refund_start_date, to_refund_end_date): + lang = self.env["res.lang"].search( + [("code", "=", self.contract_id.partner_id.lang)] + ) + date_format = lang.date_format or "%m/%d/%Y" + return _( + "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s" + ) % ( + dict( + to_refund_start_date=to_refund_start_date.strftime(date_format), + to_refund_end_date=to_refund_end_date.strftime(date_format), + ) + ) + + def _get_refund_on_stop_quantity(self, to_refund_start_date, to_refund_end_date): + """ + Compute the total refund quantity for a contract line. + + This method calculates the prorated quantity to be refunded when a contract + line is stopped before the end of the last invoiced period. + + The computation is done **period by period**, to properly handle cases where + the refund interval spans multiple theoretical billing periods. For each + period, the prorated quantity is computed based on the refund start and end + dates, and then accumulated to obtain the total refund quantity. + + :param date to_refund_start_date: Start date of the refund period. + :param date to_refund_end_date: End date of the refund period. + :return: Total prorated refund quantity. + :rtype: float + """ + self.ensure_one() + quantity = 0 + while to_refund_start_date < to_refund_end_date: + next_period_date_end = min( + ( + to_refund_start_date + + self.get_relative_delta( + self.recurring_rule_type, self.recurring_interval + ) + - relativedelta(days=1) + ), + to_refund_end_date, + ) + invoice_date = self.get_next_invoice_date( + to_refund_start_date, + self.recurring_invoicing_type, + self.recurring_invoicing_offset, + self.recurring_rule_type, + self.recurring_interval, + max_date_end=next_period_date_end, + ) + quantity += self.quantity * self.compute_prorated( + to_refund_start_date, next_period_date_end, invoice_date + ) + to_refund_start_date = next_period_date_end + relativedelta(days=1) + return quantity diff --git a/contract_refund_on_stop/models/res_company.py b/contract_refund_on_stop/models/res_company.py new file mode 100644 index 0000000000..d6b81c8a30 --- /dev/null +++ b/contract_refund_on_stop/models/res_company.py @@ -0,0 +1,14 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + enable_contract_line_refund_on_stop = fields.Boolean( + help="If enabled, users can stop a contract line even after it has been " + "invoiced. A refund will automatically be created for the invoiced period " + "beyond the stop date.", + ) diff --git a/contract_refund_on_stop/models/res_config_settings.py b/contract_refund_on_stop/models/res_config_settings.py new file mode 100644 index 0000000000..c3467964f8 --- /dev/null +++ b/contract_refund_on_stop/models/res_config_settings.py @@ -0,0 +1,12 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + enable_contract_line_refund_on_stop = fields.Boolean( + related="company_id.enable_contract_line_refund_on_stop", readonly=False + ) diff --git a/contract_refund_on_stop/pyproject.toml b/contract_refund_on_stop/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/contract_refund_on_stop/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/contract_refund_on_stop/readme/CONFIGURE.md b/contract_refund_on_stop/readme/CONFIGURE.md new file mode 100644 index 0000000000..468a636689 --- /dev/null +++ b/contract_refund_on_stop/readme/CONFIGURE.md @@ -0,0 +1,6 @@ +To enable the refund behavior when stopping invoiced contract lines: + +1. Go to **Invoicing / Configuration / Settings**. +2. In the **Contracts** section, enable the checkbox: + + **Enable Contract Line Refund on Stop** diff --git a/contract_refund_on_stop/readme/CONTEXT.md b/contract_refund_on_stop/readme/CONTEXT.md new file mode 100644 index 0000000000..568a143892 --- /dev/null +++ b/contract_refund_on_stop/readme/CONTEXT.md @@ -0,0 +1,9 @@ +In the standard behavior of the Contract module, it is not possible to stop +a contract line if its stop date is earlier than the last invoiced date. +This restriction prevents users from adjusting contracts that were invoiced +too far in advance. + +In some business cases, however, a contract may need to be stopped retroactively +(e.g., customer cancellation, early termination, or service interruption). +In such cases, it is necessary to **automatically create a refund** +for the invoiced period that should no longer be billed. diff --git a/contract_refund_on_stop/readme/CONTRIBUTORS.md b/contract_refund_on_stop/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..dbdd727b4a --- /dev/null +++ b/contract_refund_on_stop/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Souheil Bejaoui diff --git a/contract_refund_on_stop/readme/DESCRIPTION.md b/contract_refund_on_stop/readme/DESCRIPTION.md new file mode 100644 index 0000000000..2f952c7d35 --- /dev/null +++ b/contract_refund_on_stop/readme/DESCRIPTION.md @@ -0,0 +1,20 @@ +This module allows stopping a contract line even after it has been invoiced. + +When the stop date is earlier than the last invoiced date, the system will: + +- Automatically create a refund invoice for the period between the stop date and the last invoiced date. +- Adjust the `last_date_invoiced` of the contract line to match the stop date. +- Proceed with the normal stop process. + +To accurately compute the refund amount, the module depends on +**`contract_variable_qty_prorated`**, which provides the prorating logic used to +determine how much of the previously invoiced quantity should be refunded based +on the actual number of days covered by the refund period. + +Without this dependency, it would not be possible to proportionally calculate +the part of the invoiced quantity corresponding to unused service time when a +contract is stopped mid-period. + +This ensures that users can gracefully handle early contract terminations +without manual refund management, while maintaining accurate prorated invoicing +and accounting consistency. \ No newline at end of file diff --git a/contract_refund_on_stop/readme/USAGE.md b/contract_refund_on_stop/readme/USAGE.md new file mode 100644 index 0000000000..938d8b2229 --- /dev/null +++ b/contract_refund_on_stop/readme/USAGE.md @@ -0,0 +1,6 @@ +1. Ensure the **Enable Contract Line Refund on Stop** option is enabled for your company. +2. Open any **active contract line** that has been invoiced. +3. Click **Stop** and choose a stop date earlier than the last invoiced period. +4. The system will: + - Create a **refund invoice** covering the over-invoiced period. + - Update the contract line to end on the chosen stop date. diff --git a/contract_refund_on_stop/static/description/icon.png b/contract_refund_on_stop/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_refund_on_stop/static/description/index.html b/contract_refund_on_stop/static/description/index.html new file mode 100644 index 0000000000..06ff855684 --- /dev/null +++ b/contract_refund_on_stop/static/description/index.html @@ -0,0 +1,493 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Contract Refund On Stop

+ +

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

+

This module allows stopping a contract line even after it has been +invoiced.

+

When the stop date is earlier than the last invoiced date, the system +will:

+
    +
  • Automatically create a refund invoice for the period between the stop +date and the last invoiced date.
  • +
  • Adjust the last_date_invoiced of the contract line to match the +stop date.
  • +
  • Proceed with the normal stop process.
  • +
+

To accurately compute the refund amount, the module depends on +``contract_variable_qty_prorated``, which provides the prorating +logic used to determine how much of the previously invoiced quantity +should be refunded based on the actual number of days covered by the +refund period.

+

Without this dependency, it would not be possible to proportionally +calculate the part of the invoiced quantity corresponding to unused +service time when a contract is stopped mid-period.

+

This ensures that users can gracefully handle early contract +terminations without manual refund management, while maintaining +accurate prorated invoicing and accounting consistency.

+

Table of contents

+ +
+

Use Cases / Context

+

In the standard behavior of the Contract module, it is not possible to +stop a contract line if its stop date is earlier than the last invoiced +date. This restriction prevents users from adjusting contracts that were +invoiced too far in advance.

+

In some business cases, however, a contract may need to be stopped +retroactively (e.g., customer cancellation, early termination, or +service interruption). In such cases, it is necessary to automatically +create a refund for the invoiced period that should no longer be +billed.

+
+
+

Configuration

+

To enable the refund behavior when stopping invoiced contract lines:

+
    +
  1. Go to Invoicing / Configuration / Settings.

    +
  2. +
  3. In the Contracts section, enable the checkbox:

    +

    Enable Contract Line Refund on Stop

    +
  4. +
+
+
+

Usage

+
    +
  1. Ensure the Enable Contract Line Refund on Stop option is enabled +for your company.
  2. +
  3. Open any active contract line that has been invoiced.
  4. +
  5. Click Stop and choose a stop date earlier than the last invoiced +period.
  6. +
  7. The system will:
      +
    • Create a refund invoice covering the over-invoiced period.
    • +
    • Update the contract line to end on the chosen stop date.
    • +
    +
  8. +
+
+
+

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

+ +
+
+

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.

+

Current maintainer:

+

sbejaoui

+

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_refund_on_stop/tests/__init__.py b/contract_refund_on_stop/tests/__init__.py new file mode 100644 index 0000000000..ea7861fbcf --- /dev/null +++ b/contract_refund_on_stop/tests/__init__.py @@ -0,0 +1 @@ +from . import test_contract_refund diff --git a/contract_refund_on_stop/tests/test_contract_refund.py b/contract_refund_on_stop/tests/test_contract_refund.py new file mode 100644 index 0000000000..a40ab7c14b --- /dev/null +++ b/contract_refund_on_stop/tests/test_contract_refund.py @@ -0,0 +1,138 @@ +# Copyright 2025 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from datetime import timedelta + +from odoo.exceptions import ValidationError + +from odoo.addons.contract.tests.test_contract import TestContractBase, to_date + + +class TestContractRefund(TestContractBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.acct_line.date_start = "2018-01-01" + cls.contract.company_id.enable_contract_line_refund_on_stop = True + + def _get_contract_invoices(self): + return self.env["account.move"].search( + [("line_ids.contract_line_id", "in", self.contract.contract_line_ids.ids)] + ) + + def test_0(self): + """ + standard behavior + stop before last date invoiced, company setting disabled, validationError raises + """ + self.contract.company_id.enable_contract_line_refund_on_stop = False + self.contract.recurring_create_invoice() + with self.assertRaisesRegex( + ValidationError, + "You can't have the end date before the date of last invoice", + ): + self.acct_line.stop(self.acct_line.last_date_invoiced - timedelta(days=1)) + + def test_1(self): + """ + stop create a refund for the invoiced period + if the company setting is enabled and the stop is before the last date invoiced + a refund is created + """ + self.contract.recurring_create_invoice() + self.assertEqual(len(self._get_contract_invoices()), 1) + self.acct_line.stop(self.acct_line.last_date_invoiced - timedelta(days=30)) + self.assertEqual(len(self._get_contract_invoices()), 2) + refund = self._get_contract_invoices().filtered( + lambda m: m.move_type == "out_refund" + ) + self.assertTrue(refund) + refund_line = refund.invoice_line_ids + self.assertEqual(refund_line.product_id, self.acct_line.product_id) + self.assertEqual(refund_line.quantity, 1) + self.assertEqual(refund_line.name, "Refund for period 01/01/2018 01/31/2018") + + def test_2(self): + """ + no refund if the stop is after the last date invoiced + """ + self.contract.recurring_create_invoice() + self.assertEqual(len(self._get_contract_invoices()), 1) + self.acct_line.stop(self.acct_line.last_date_invoiced + timedelta(days=1)) + self.assertEqual(len(self._get_contract_invoices()), 1) + + def _test_refund_quantity_prorated(self, stop_date, expected_quantity): + self.acct_line.stop(stop_date) + refund = self._get_contract_invoices().filtered( + lambda m: m.move_type == "out_refund" + ) + self.assertTrue(refund) + refund_line = refund.invoice_line_ids + self.assertEqual(refund_line.quantity, expected_quantity) + + def test_3(self): + """stop after creating two invoices, refund 2 periods""" + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + self._test_refund_quantity_prorated(to_date("2018-01-01"), 2) + + def test_4(self): + """stop after creating two invoices, refund one period and a half""" + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + self._test_refund_quantity_prorated(to_date("2018-01-15"), 1.5) + + def test_5(self): + """stop after creating two invoices, refund 3 periods""" + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + with self.assertRaisesRegex( + ValidationError, + "You can't have the start date after the date of last invoice" + " for the contract line", + ): + self._test_refund_quantity_prorated(to_date("2017-12-01"), 3) + + def test_6(self): + """monthlylastday post-paid stop after creating two invoices, refund 2 + periods""" + self.acct_line.recurring_rule_type = "monthlylastday" + self.acct_line.recurring_invoicing_type = "post-paid" + self.assertEqual(self.acct_line.recurring_next_date, to_date("2018-01-31")) + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + self._test_refund_quantity_prorated(to_date("2018-01-01"), 2) + + def test_7(self): + """monthlylastday post-paid stop after creating two invoices, refund 1.5 + periods""" + self.acct_line.recurring_rule_type = "monthlylastday" + self.acct_line.recurring_invoicing_type = "post-paid" + self.assertEqual(self.acct_line.recurring_next_date, to_date("2018-01-31")) + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + self._test_refund_quantity_prorated(to_date("2018-01-16"), 1.52) + + def test_8(self): + """monthlylastday pre-paid stop after creating two invoices, refund 1.5 + periods""" + self.acct_line.recurring_rule_type = "monthlylastday" + self.acct_line.recurring_invoicing_type = "pre-paid" + self.assertEqual(self.acct_line.recurring_next_date, to_date("2018-01-01")) + self.contract.recurring_create_invoice() + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-02-28")) + self._test_refund_quantity_prorated(to_date("2018-01-16"), 1.52) + + def test_9(self): + """yearly pre-paid stop after creating one invoice, refund 0.5 periods""" + self.acct_line.recurring_rule_type = "yearly" + self.acct_line.recurring_invoicing_type = "pre-paid" + self.assertEqual(self.acct_line.recurring_next_date, to_date("2018-01-01")) + self.contract.recurring_create_invoice() + self.assertEqual(self.acct_line.last_date_invoiced, to_date("2018-12-31")) + self._test_refund_quantity_prorated(to_date("2018-06-30"), 0.51) diff --git a/contract_refund_on_stop/views/res_config_settings.xml b/contract_refund_on_stop/views/res_config_settings.xml new file mode 100644 index 0000000000..b2d42aa9d3 --- /dev/null +++ b/contract_refund_on_stop/views/res_config_settings.xml @@ -0,0 +1,16 @@ + + + + + res.config.settings + + + + + + + + + + From c8cea5c5ff77f12e5eba533ca81491e58d8b757e Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 14 Oct 2025 07:07:30 +0000 Subject: [PATCH 2/5] Added translation using Weblate (Italian) --- contract_refund_on_stop/i18n/it.po | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 contract_refund_on_stop/i18n/it.po diff --git a/contract_refund_on_stop/i18n/it.po b/contract_refund_on_stop/i18n/it.po new file mode 100644 index 0000000000..da6810c6c3 --- /dev/null +++ b/contract_refund_on_stop/i18n/it.po @@ -0,0 +1,56 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_refund_on_stop +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-10-14 09:42+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_company +msgid "Companies" +msgstr "Aziende" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_config_settings +msgid "Config Settings" +msgstr "Impostazioni configurazione" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_contract_line +msgid "Contract Line" +msgstr "Riga contratto" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "Enable Contract Line Refund On Stop" +msgstr "Abilita il rimborso riga contratto al termine" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "" +"If enabled, users can stop a contract line even after it has been invoiced. " +"A refund will automatically be created for the invoiced period beyond the " +"stop date." +msgstr "" +"Se abilitata, gli utenti possono terminare una riga di contratto anche se è " +"già stata fatturata. Verrà creato automaticamente un rimborso per il periodo " +"fatturato oltre la data di termine." + +#. module: contract_refund_on_stop +#. odoo-python +#: code:addons/contract_refund_on_stop/models/contract_line.py:0 +msgid "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s" +msgstr "Rimborso per il periodo %(to_refund_start_date)s %(to_refund_end_date)s" From 3f08a8e7d5b46bd71183af13e772ae9967fdf73c Mon Sep 17 00:00:00 2001 From: Bosd Date: Mon, 29 Dec 2025 17:42:13 +0000 Subject: [PATCH 3/5] Added translation using Weblate (Dutch) --- contract_refund_on_stop/i18n/nl.po | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 contract_refund_on_stop/i18n/nl.po diff --git a/contract_refund_on_stop/i18n/nl.po b/contract_refund_on_stop/i18n/nl.po new file mode 100644 index 0000000000..28efc92c35 --- /dev/null +++ b/contract_refund_on_stop/i18n/nl.po @@ -0,0 +1,57 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_refund_on_stop +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-12-29 17:44+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_company +msgid "Companies" +msgstr "Bedrijven" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_res_config_settings +msgid "Config Settings" +msgstr "Configuratie-instellingen" + +#. module: contract_refund_on_stop +#: model:ir.model,name:contract_refund_on_stop.model_contract_line +msgid "Contract Line" +msgstr "Contractregel" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,field_description:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "Enable Contract Line Refund On Stop" +msgstr "Creditering van contractregel bij stopzetten inschakelen" + +#. module: contract_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_company__enable_contract_line_refund_on_stop +#: model:ir.model.fields,help:contract_refund_on_stop.field_res_config_settings__enable_contract_line_refund_on_stop +msgid "" +"If enabled, users can stop a contract line even after it has been invoiced. " +"A refund will automatically be created for the invoiced period beyond the " +"stop date." +msgstr "" +"Indien ingeschakeld, kunnen gebruikers een contractregel stopzetten, zelfs " +"nadat deze is gefactureerd. Er wordt automatisch een creditnota aangemaakt " +"voor de gefactureerde periode na de stopdatum." + +#. module: contract_refund_on_stop +#. odoo-python +#: code:addons/contract_refund_on_stop/models/contract_line.py:0 +msgid "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s" +msgstr "" +"Terugbetaling voor periode %(to_refund_start_date)s %(to_refund_end_date)s" From edea67858b4210988b0695ad1389744bec58d535 Mon Sep 17 00:00:00 2001 From: bosd Date: Mon, 29 Dec 2025 20:43:48 +0100 Subject: [PATCH 4/5] [MIG] contract_refund_on_stop: Migration to 19.0 --- contract_refund_on_stop/README.rst | 26 +++++++++--------- contract_refund_on_stop/__manifest__.py | 2 +- .../models/contract_line.py | 13 ++++----- .../static/description/icon.png | Bin 9455 -> 10254 bytes .../static/description/index.html | 6 ++-- contract_refund_on_stop/test-requirements.txt | 3 ++ 6 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 contract_refund_on_stop/test-requirements.txt diff --git a/contract_refund_on_stop/README.rst b/contract_refund_on_stop/README.rst index e82f123d60..e4999313a9 100644 --- a/contract_refund_on_stop/README.rst +++ b/contract_refund_on_stop/README.rst @@ -21,13 +21,13 @@ Contract Refund On Stop :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_refund_on_stop + :target: https://github.com/OCA/contract/tree/19.0/contract_refund_on_stop :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_refund_on_stop + :target: https://translation.odoo-community.org/projects/contract-19-0/contract-19-0-contract_refund_on_stop :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 + :target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -38,11 +38,11 @@ invoiced. When the stop date is earlier than the last invoiced date, the system will: -- Automatically create a refund invoice for the period between the stop - date and the last invoiced date. -- Adjust the ``last_date_invoiced`` of the contract line to match the - stop date. -- Proceed with the normal stop process. +- Automatically create a refund invoice for the period between the stop + date and the last invoiced date. +- Adjust the ``last_date_invoiced`` of the contract line to match the + stop date. +- Proceed with the normal stop process. To accurately compute the refund amount, the module depends on **``contract_variable_qty_prorated``**, which provides the prorating @@ -98,8 +98,8 @@ Usage period. 4. The system will: - - Create a **refund invoice** covering the over-invoiced period. - - Update the contract line to end on the chosen stop date. + - Create a **refund invoice** covering the over-invoiced period. + - Update the contract line to end on the chosen stop date. Bug Tracker =========== @@ -107,7 +107,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -122,7 +122,7 @@ Authors Contributors ------------ -- Souheil Bejaoui souheil.bejaoui@acsone.eu +- Souheil Bejaoui souheil.bejaoui@acsone.eu Maintainers ----------- @@ -145,6 +145,6 @@ Current `maintainer `__: |maintainer-sbejaoui| -This module is part of the `OCA/contract `_ project on GitHub. +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_refund_on_stop/__manifest__.py b/contract_refund_on_stop/__manifest__.py index fcbde7df74..e6b26d0dc3 100644 --- a/contract_refund_on_stop/__manifest__.py +++ b/contract_refund_on_stop/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Contract Refund On Stop", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "license": "AGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/contract", diff --git a/contract_refund_on_stop/models/contract_line.py b/contract_refund_on_stop/models/contract_line.py index 8207cf13a5..653177fb77 100644 --- a/contract_refund_on_stop/models/contract_line.py +++ b/contract_refund_on_stop/models/contract_line.py @@ -3,7 +3,7 @@ from dateutil.relativedelta import relativedelta -from odoo import Command, _, models +from odoo import Command, models class ContractLine(models.Model): @@ -64,13 +64,10 @@ def _get_refund_on_stop_line_name(self, to_refund_start_date, to_refund_end_date [("code", "=", self.contract_id.partner_id.lang)] ) date_format = lang.date_format or "%m/%d/%Y" - return _( - "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s" - ) % ( - dict( - to_refund_start_date=to_refund_start_date.strftime(date_format), - to_refund_end_date=to_refund_end_date.strftime(date_format), - ) + return self.env._( + "Refund for period %(to_refund_start_date)s %(to_refund_end_date)s", + to_refund_start_date=to_refund_start_date.strftime(date_format), + to_refund_end_date=to_refund_end_date.strftime(date_format), ) def _get_refund_on_stop_quantity(self, to_refund_start_date, to_refund_end_date): diff --git a/contract_refund_on_stop/static/description/icon.png b/contract_refund_on_stop/static/description/icon.png index 3a0328b516c4980e8e44cdb63fd945757ddd132d..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 100644 GIT binary patch literal 10254 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I diff --git a/contract_refund_on_stop/static/description/index.html b/contract_refund_on_stop/static/description/index.html index 06ff855684..9877ac075a 100644 --- a/contract_refund_on_stop/static/description/index.html +++ b/contract_refund_on_stop/static/description/index.html @@ -374,7 +374,7 @@

Contract Refund On Stop

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:3f294b8c8d68ea75034fa2d55dde59867c25bf25a722e001def2330953fe59d7 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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 allows stopping a contract line even after it has been invoiced.

When the stop date is earlier than the last invoiced date, the system @@ -455,7 +455,7 @@

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.

+feedback.

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

@@ -483,7 +483,7 @@

Maintainers

promote its widespread use.

Current maintainer:

sbejaoui

-

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

+

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_refund_on_stop/test-requirements.txt b/contract_refund_on_stop/test-requirements.txt new file mode 100644 index 0000000000..1d7def1e6c --- /dev/null +++ b/contract_refund_on_stop/test-requirements.txt @@ -0,0 +1,3 @@ +# Dependencies that need to be migrated to version 19.0 +# odoo-addon-contract-line-successor @ git+https://github.com/OCA/contract#subdirectory=contract_line_successor +# odoo-addon-contract-variable-qty-prorated @ git+https://github.com/OCA/contract#subdirectory=contract_variable_qty_prorated \ No newline at end of file From c48823f8e86090ec3afd55f6b026ff5f3ce8f100 Mon Sep 17 00:00:00 2001 From: bosd Date: Mon, 29 Dec 2025 23:36:21 +0100 Subject: [PATCH 5/5] [DO NOT MERGE] --- contract_refund_on_stop/test-requirements.txt | 2 +- test-requirements.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test-requirements.txt diff --git a/contract_refund_on_stop/test-requirements.txt b/contract_refund_on_stop/test-requirements.txt index 1d7def1e6c..0d769f4002 100644 --- a/contract_refund_on_stop/test-requirements.txt +++ b/contract_refund_on_stop/test-requirements.txt @@ -1,3 +1,3 @@ # Dependencies that need to be migrated to version 19.0 # odoo-addon-contract-line-successor @ git+https://github.com/OCA/contract#subdirectory=contract_line_successor -# odoo-addon-contract-variable-qty-prorated @ git+https://github.com/OCA/contract#subdirectory=contract_variable_qty_prorated \ No newline at end of file +# odoo-addon-contract-variable-qty-prorated @ git+https://github.com/OCA/contract#subdirectory=contract_variable_qty_prorated diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000000..27b25af0cf --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,5 @@ +# Dependencies that need to be migrated to version 19.0 +odoo-addon-contract-line-successor @ git+https://github.com/OCA/contract@refs/pull/1370/head##subdirectory=contract_line_successor +odoo-addon-contract @ git+https://github.com/OCA/contract@refs/pull/1312/head#subdirectory=contract +odoo-addon-contract_variable_qty_prorated @ git+https://github.com/OCA/contract@refs/pull/1365/head#subdirectory=contract_variable_qty_prorated +odoo-addon-contract_variable_quantity @ git+https://github.com/OCA/contract@refs/pull/1364/head#subdirectory=contract_variable_quantity