From 51446a37d0666ba17368c6e804d2a3a85173ed5e Mon Sep 17 00:00:00 2001 From: Thomas Binsfeld Date: Tue, 1 Oct 2019 10:44:39 +0200 Subject: [PATCH 01/13] [ADD] Contract Variable Qty Prorated --- contract_variable_qty_prorated/README.rst | 89 ++++ contract_variable_qty_prorated/__init__.py | 2 + .../__manifest__.py | 21 + .../data/contract_variable_qty_prorated.xml | 16 + .../i18n/contract_variable_qty_prorated.pot | 27 ++ contract_variable_qty_prorated/i18n/fr.po | 27 ++ .../models/__init__.py | 1 + .../models/contract_line.py | 45 ++ .../readme/CONTRIBUTORS.rst | 2 + .../readme/DESCRIPTION.rst | 2 + .../readme/ROADMAP.rst | 2 + .../static/description/index.html | 429 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/test_compute_proprata.py | 395 ++++++++++++++++ .../views/abstract_contract_view.xml | 22 + 15 files changed, 1081 insertions(+) create mode 100644 contract_variable_qty_prorated/README.rst create mode 100644 contract_variable_qty_prorated/__init__.py create mode 100644 contract_variable_qty_prorated/__manifest__.py create mode 100644 contract_variable_qty_prorated/data/contract_variable_qty_prorated.xml create mode 100644 contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot create mode 100644 contract_variable_qty_prorated/i18n/fr.po create mode 100644 contract_variable_qty_prorated/models/__init__.py create mode 100644 contract_variable_qty_prorated/models/contract_line.py create mode 100644 contract_variable_qty_prorated/readme/CONTRIBUTORS.rst create mode 100644 contract_variable_qty_prorated/readme/DESCRIPTION.rst create mode 100644 contract_variable_qty_prorated/readme/ROADMAP.rst create mode 100644 contract_variable_qty_prorated/static/description/index.html create mode 100644 contract_variable_qty_prorated/tests/__init__.py create mode 100644 contract_variable_qty_prorated/tests/test_compute_proprata.py create mode 100644 contract_variable_qty_prorated/views/abstract_contract_view.xml diff --git a/contract_variable_qty_prorated/README.rst b/contract_variable_qty_prorated/README.rst new file mode 100644 index 0000000000..9b2ee60e0c --- /dev/null +++ b/contract_variable_qty_prorated/README.rst @@ -0,0 +1,89 @@ +============================== +Contract Variable Qty Prorated +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/12.0/contract_variable_qty_prorated + :alt: OCA/contract +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/contract-12-0/contract-12-0-contract_variable_qty_prorated + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/110/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds a formula to compute prorated quantity to invoice as +extension of the module contract_variable_quantity. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +If this module is installed with product_contract, the quantity field of sale order lines +won't be displayed. + +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 smashing 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 +* Thomas Binsfeld + +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_variable_qty_prorated/__init__.py b/contract_variable_qty_prorated/__init__.py new file mode 100644 index 0000000000..0ee8b5073e --- /dev/null +++ b/contract_variable_qty_prorated/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import tests diff --git a/contract_variable_qty_prorated/__manifest__.py b/contract_variable_qty_prorated/__manifest__.py new file mode 100644 index 0000000000..fae0e3f631 --- /dev/null +++ b/contract_variable_qty_prorated/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Contract Variable Qty Prorated", + "summary": """ + This module adds a formula to compute prorated quantity to invoice as + extension of the module contract_variable_quantity""", + "version": "12.0.1.0.0", + "development_status": "Beta", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "maintainers": ["sbejaoui"], + "website": "https://github.com/oca/contract", + "depends": ["contract_variable_quantity"], + "data": [ + "data/contract_variable_qty_prorated.xml", + "views/abstract_contract_view.xml", + ], + "demo": [], +} diff --git a/contract_variable_qty_prorated/data/contract_variable_qty_prorated.xml b/contract_variable_qty_prorated/data/contract_variable_qty_prorated.xml new file mode 100644 index 0000000000..01e3489976 --- /dev/null +++ b/contract_variable_qty_prorated/data/contract_variable_qty_prorated.xml @@ -0,0 +1,16 @@ + + + + + + Prorated Quantity + +result = 0 +if line: + result = line.quantity * line.compute_prorated(period_first_date, period_last_date, invoice_date) + + + + diff --git a/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot b/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot new file mode 100644 index 0000000000..77c48adc7e --- /dev/null +++ b/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-01 09:32+0000\n" +"PO-Revision-Date: 2019-10-01 09:32+0000\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_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "" + diff --git a/contract_variable_qty_prorated/i18n/fr.po b/contract_variable_qty_prorated/i18n/fr.po new file mode 100644 index 0000000000..7c3693d23b --- /dev/null +++ b/contract_variable_qty_prorated/i18n/fr.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-01 09:33+0000\n" +"PO-Revision-Date: 2019-10-01 09:33+0000\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_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "Ligne de contrat" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "Quantité prorata" + diff --git a/contract_variable_qty_prorated/models/__init__.py b/contract_variable_qty_prorated/models/__init__.py new file mode 100644 index 0000000000..6143a36501 --- /dev/null +++ b/contract_variable_qty_prorated/models/__init__.py @@ -0,0 +1 @@ +from . import contract_line diff --git a/contract_variable_qty_prorated/models/contract_line.py b/contract_variable_qty_prorated/models/contract_line.py new file mode 100644 index 0000000000..f653aab21d --- /dev/null +++ b/contract_variable_qty_prorated/models/contract_line.py @@ -0,0 +1,45 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class ContractLine(models.Model): + _inherit = "contract.line" + + @api.model + def _compute_prorated( + self, real_next_date, real_last_date, theoretical_next_date, + theoretical_last_date): + def _invoiced_days(next_date, last_date): + return (next_date - last_date).days + 1 + + return _invoiced_days(real_next_date, real_last_date) / _invoiced_days( + theoretical_next_date, theoretical_last_date + ) + + @api.multi + def compute_prorated(self, period_first_date, period_last_date, + invoice_date): + self.ensure_one() + relative_delta = self.get_relative_delta( + self.recurring_rule_type, self.recurring_interval + ) + theoretical_next_date = invoice_date + if self.recurring_rule_type == "monthlylastday": + relative_delta = self.get_relative_delta("monthly", + self.recurring_interval) + theoretical_next_date += self.get_relative_delta("daily", 1) + if ( + self.recurring_invoicing_type == "pre-paid" + and self.recurring_rule_type != "monthlylastday" + ): + theoretical_next_date += relative_delta + theoretical_last_date = theoretical_next_date - relative_delta + theoretical_next_date -= self.get_relative_delta("daily", 1) + real_last_date = period_first_date + real_next_date = period_last_date + return self._compute_prorated( + real_next_date, real_last_date, theoretical_next_date, + theoretical_last_date + ) diff --git a/contract_variable_qty_prorated/readme/CONTRIBUTORS.rst b/contract_variable_qty_prorated/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..724261d73c --- /dev/null +++ b/contract_variable_qty_prorated/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Souheil Bejaoui +* Thomas Binsfeld diff --git a/contract_variable_qty_prorated/readme/DESCRIPTION.rst b/contract_variable_qty_prorated/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..3877b5d97e --- /dev/null +++ b/contract_variable_qty_prorated/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module adds a formula to compute prorated quantity to invoice as +extension of the module contract_variable_quantity. diff --git a/contract_variable_qty_prorated/readme/ROADMAP.rst b/contract_variable_qty_prorated/readme/ROADMAP.rst new file mode 100644 index 0000000000..33bec5c8c1 --- /dev/null +++ b/contract_variable_qty_prorated/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +If this module is installed with product_contract, the quantity field of sale order lines +won't be displayed. diff --git a/contract_variable_qty_prorated/static/description/index.html b/contract_variable_qty_prorated/static/description/index.html new file mode 100644 index 0000000000..20b1a7e1f8 --- /dev/null +++ b/contract_variable_qty_prorated/static/description/index.html @@ -0,0 +1,429 @@ + + + + + + +Contract Variable Qty Prorated + + + +
+

Contract Variable Qty Prorated

+ + +

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

+

This module adds a formula to compute prorated quantity to invoice as +extension of the module contract_variable_quantity.

+

Table of contents

+ +
+

Known issues / Roadmap

+

If this module is installed with product_contract, the quantity field of sale order lines +won’t be displayed.

+
+
+

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 smashing 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_variable_qty_prorated/tests/__init__.py b/contract_variable_qty_prorated/tests/__init__.py new file mode 100644 index 0000000000..2b7f5ecf77 --- /dev/null +++ b/contract_variable_qty_prorated/tests/__init__.py @@ -0,0 +1 @@ +from . import test_compute_proprata diff --git a/contract_variable_qty_prorated/tests/test_compute_proprata.py b/contract_variable_qty_prorated/tests/test_compute_proprata.py new file mode 100644 index 0000000000..7fd610c14c --- /dev/null +++ b/contract_variable_qty_prorated/tests/test_compute_proprata.py @@ -0,0 +1,395 @@ +# Copyright 2018 ACSONE SA/NV. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields +from odoo.tests.common import TransactionCase + + +def to_date(date): + return fields.Date.to_date(date) + + +class TestProductTemplate(TransactionCase): + def setUp(self): + super(TestProductTemplate, self).setUp() + self.partner = self.env.ref("base.res_partner_2") + self.product = self.env.ref("product.product_product_1") + self.contract = self.env["contract.contract"].create( + { + "name": "Test Contract 2", + "partner_id": self.partner.id, + "pricelist_id": self.partner.property_product_pricelist.id, + "contract_type": "purchase", + "contract_line_ids": [ + ( + 0, + 0, + { + "product_id": self.product.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": self.product.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "monthly", + "recurring_interval": 1, + "date_start": "2016-02-15", + "recurring_next_date": "2016-02-29", + }, + ) + ], + } + ) + self.contract_line = self.contract.contract_line_ids[0] + + def test_compute_prorated(self): + def update_contract_line( + case, + recurring_rule_type, + recurring_interval, + recurring_invoicing_type, + date_start, + recurring_next_date, + date_end, + last_date_invoiced=False, + ): + self.contract_line.write( + { + "recurring_rule_type": recurring_rule_type, + "recurring_invoicing_type": recurring_invoicing_type, + "recurring_interval": recurring_interval, + "date_start": date_start, + "recurring_next_date": recurring_next_date, + "date_end": date_end, + "last_date_invoiced": last_date_invoiced, + } + ) + + def error_message( + case, + recurring_rule_type, + recurring_interval, + recurring_invoicing_type, + date_start, + recurring_next_date, + date_end, + last_date_invoiced=False, + ): + return ( + "%s : Error in %s every %d %s case, start %s, next %s, end %s," + " last %s" + % ( + case, + recurring_invoicing_type, + recurring_interval, + recurring_rule_type, + date_start, + recurring_next_date, + date_end, + last_date_invoiced or "", + ) + ) + + combinations = [ + ( + 1.00, + ( + "Case 1", + "monthly", + 1, + "pre-paid", + to_date("2018-01-05"), + to_date("2018-01-05"), + False, + ), + ), + ( + 1.00, + ( + "Case 2", + "monthly", + 1, + "pre-paid", + to_date("2018-01-05"), + to_date("2018-02-01"), + False, + to_date("2018-01-31"), + ), + ), + ( + 1.00, + ( + "Case 3", + "monthly", + 1, + "pre-paid", + to_date("2017-01-05"), + to_date("2018-02-01"), + to_date("2018-03-01"), + to_date("2018-01-31"), + ), + ), + ( + 0.892, + ( + "Case 4", + "monthly", + 1, + "pre-paid", + to_date("2017-01-05"), + to_date("2018-02-01"), + to_date("2018-02-25"), + to_date("2018-01-31"), + ), + ), + ( + 1.00, + ( + "Case 5", + "monthly", + 1, + "post-paid", + to_date("2018-01-05"), + to_date("2018-02-05"), + False, + ), + ), + ( + 0.87, + ( + "Case 6", + "monthly", + 1, + "post-paid", + to_date("2018-01-05"), + to_date("2018-02-01"), + False, + ), + ), + ( + 1.00, + ( + "Case 7", + "monthly", + 1, + "post-paid", + to_date("2017-01-05"), + to_date("2018-02-01"), + to_date("2018-03-01"), + to_date("2017-12-31"), + ), + ), + ( + 0.892, + ( + "Case 8", + "monthly", + 1, + "post-paid", + to_date("2017-01-05"), + to_date("2018-03-01"), + to_date("2018-02-25"), + to_date("2018-01-31"), + ), + ), + ( + 1.00, + ( + "Case 9", + "monthlylastday", + 1, + "post-paid", + to_date("2018-01-01"), + to_date("2018-01-31"), + to_date("2018-02-25"), + ), + ), + ( + 0.87, + ( + "Case 10", + "monthlylastday", + 1, + "post-paid", + to_date("2018-01-05"), + to_date("2018-01-31"), + to_date("2018-02-25"), + ), + ), + ( + 0.892, + ( + "Case 11", + "monthlylastday", + 1, + "post-paid", + to_date("2018-01-05"), + to_date("2018-02-28"), + to_date("2018-02-25"), + to_date("2018-01-31"), + ), + ), + ( + 0.5, + ( + "Case 12", + "monthlylastday", + 1, + "post-paid", + to_date("2018-02-01"), + to_date("2018-02-28"), + to_date("2018-02-14"), + ), + ), + ( + 0.5, + ( + "Case 13", + "monthlylastday", + 1, + "post-paid", + to_date("2018-02-15"), + to_date("2018-02-28"), + False, + ), + ), + ( + 0.032, + ( + "Case 14", + "monthlylastday", + 1, + "post-paid", + to_date("2017-02-15"), + to_date("2018-01-31"), + False, + to_date("2018-01-30"), + ), + ), + ( + 1.035, + ( + "Case 15", + "monthlylastday", + 1, + "post-paid", + to_date("2017-02-15"), + to_date("2018-02-28"), + False, + to_date("2018-01-30"), + ), + ), + ( + 0.032, + ( + "Case 16", + "monthly", + 1, + "post-paid", + to_date("2017-02-15"), + to_date("2018-02-01"), + False, + to_date("2018-01-30"), + ), + ), + ( + 1.035, + ( + "Case 17", + "monthly", + 1, + "post-paid", + to_date("2017-02-15"), + to_date("2018-03-01"), + False, + to_date("2018-01-30"), + ), + ), + ( + 0.032, + ( + "Case 18", + "monthly", + 1, + "pre-paid", + to_date("2017-02-15"), + to_date("2018-01-01"), + False, + to_date("2018-01-30"), + ), + ), + ( + 1.035, + ( + "Case 19", + "monthly", + 1, + "pre-paid", + to_date("2017-02-15"), + to_date("2018-02-01"), + False, + to_date("2018-01-30"), + ), + ), + ( + 1.0, + ( + "Case 18", + "monthlylastday", + 1, + "pre-paid", + to_date("2017-02-15"), + to_date("2018-01-31"), + False, + to_date("2017-12-31"), + ), + ), + ( + 1.566, + ( + "Case 19", + "monthlylastday", + 1, + "pre-paid", + to_date("2018-03-15"), + to_date("2018-04-30"), + False, + ), + ), + ( + 1.48, + ( + "Case 20", + "monthly", + 1, + "post-paid", + to_date("2018-03-15"), + to_date("2018-04-30"), + False, + ), + ), + ( + 2.53, + ( + "Case 21", + "monthly", + 1, + "pre-paid", + to_date("2018-03-15"), + to_date("2018-04-30"), + False, + ), + ), + ] + for result, combination in combinations: + update_contract_line(*combination) + dates = self.contract_line._get_period_to_invoice( + self.contract_line.last_date_invoiced, + self.contract_line.recurring_next_date, + ) + self.assertAlmostEqual( + result, + self.contract_line.compute_prorated(*dates), + places=2, + msg=error_message(*combination), + ) diff --git a/contract_variable_qty_prorated/views/abstract_contract_view.xml b/contract_variable_qty_prorated/views/abstract_contract_view.xml new file mode 100644 index 0000000000..3a4e3b6173 --- /dev/null +++ b/contract_variable_qty_prorated/views/abstract_contract_view.xml @@ -0,0 +1,22 @@ + + + + + + + + Contract Line Form View (in abstract_contract_view.xml) + contract.abstract.contract.line + + + + + True + + + + + From d8b52979bc7ef7fc499919129d97a81ea6b0a141 Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Fri, 15 May 2020 19:51:00 +0200 Subject: [PATCH 02/13] [REF] - code refactoring + improve unit tests [UPD] Update contract_variable_qty_prorated.pot [UPD] README.rst [ADD] icon.png --- .../i18n/contract_variable_qty_prorated.pot | 2 - .../models/contract_line.py | 53 +++++++++++------- .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 2 +- .../tests/test_compute_proprata.py | 42 +++++++++----- 5 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 contract_variable_qty_prorated/static/description/icon.png diff --git a/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot b/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot index 77c48adc7e..45bd9d90cc 100644 --- a/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot +++ b/contract_variable_qty_prorated/i18n/contract_variable_qty_prorated.pot @@ -6,8 +6,6 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-01 09:32+0000\n" -"PO-Revision-Date: 2019-10-01 09:32+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" diff --git a/contract_variable_qty_prorated/models/contract_line.py b/contract_variable_qty_prorated/models/contract_line.py index f653aab21d..79e2e408dc 100644 --- a/contract_variable_qty_prorated/models/contract_line.py +++ b/contract_variable_qty_prorated/models/contract_line.py @@ -7,39 +7,52 @@ class ContractLine(models.Model): _inherit = "contract.line" + @api.multi + def compute_prorated( + self, period_first_date, period_last_date, invoice_date + ): + self.ensure_one() + return self._compute_prorated( + period_first_date, + period_last_date, + invoice_date, + self.recurring_rule_type, + self.recurring_interval, + self.recurring_invoicing_type, + ) + @api.model def _compute_prorated( - self, real_next_date, real_last_date, theoretical_next_date, - theoretical_last_date): + self, + period_first_date, + period_last_date, + invoice_date, + recurring_rule_type, + recurring_interval, + recurring_invoicing_type, + ): def _invoiced_days(next_date, last_date): return (next_date - last_date).days + 1 - return _invoiced_days(real_next_date, real_last_date) / _invoiced_days( - theoretical_next_date, theoretical_last_date - ) - - @api.multi - def compute_prorated(self, period_first_date, period_last_date, - invoice_date): - self.ensure_one() relative_delta = self.get_relative_delta( - self.recurring_rule_type, self.recurring_interval + recurring_rule_type, recurring_interval ) theoretical_next_date = invoice_date - if self.recurring_rule_type == "monthlylastday": - relative_delta = self.get_relative_delta("monthly", - self.recurring_interval) - theoretical_next_date += self.get_relative_delta("daily", 1) if ( - self.recurring_invoicing_type == "pre-paid" - and self.recurring_rule_type != "monthlylastday" + recurring_rule_type == "monthlylastday" + and recurring_invoicing_type == "post-paid" ): + relative_delta = self.get_relative_delta( + "monthly", recurring_interval + ) + theoretical_next_date += self.get_relative_delta("daily", 1) + + if recurring_invoicing_type == "pre-paid": theoretical_next_date += relative_delta theoretical_last_date = theoretical_next_date - relative_delta theoretical_next_date -= self.get_relative_delta("daily", 1) real_last_date = period_first_date real_next_date = period_last_date - return self._compute_prorated( - real_next_date, real_last_date, theoretical_next_date, - theoretical_last_date + return _invoiced_days(real_next_date, real_last_date) / _invoiced_days( + theoretical_next_date, theoretical_last_date ) diff --git a/contract_variable_qty_prorated/static/description/icon.png b/contract_variable_qty_prorated/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_variable_qty_prorated/static/description/index.html b/contract_variable_qty_prorated/static/description/index.html index 20b1a7e1f8..b309b7a22a 100644 --- a/contract_variable_qty_prorated/static/description/index.html +++ b/contract_variable_qty_prorated/static/description/index.html @@ -3,7 +3,7 @@ - + Contract Variable Qty Prorated -
-

Contract Variable Qty Prorated

+
+ + +Odoo Community Association + +
+

Contract Variable Qty Prorated

-

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 a formula to compute prorated quantity to invoice as extension of the module contract_variable_quantity.

Table of contents

@@ -386,46 +391,49 @@

Contract Variable Qty Prorated

-

Known issues / Roadmap

-

If this module is installed with product_contract, the quantity field of sale order lines -won’t be displayed.

+

Known issues / Roadmap

+

If this module is installed with product_contract, the quantity field of +sale order lines won’t be displayed.

-

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

+feedback.

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

-

Credits

+

Credits

-

Authors

+

Authors

  • ACSONE SA/NV
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +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.

+

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_variable_qty_prorated/tests/test_compute_proprata.py b/contract_variable_qty_prorated/tests/test_compute_proprata.py index 95833830c0..de472684b0 100644 --- a/contract_variable_qty_prorated/tests/test_compute_proprata.py +++ b/contract_variable_qty_prorated/tests/test_compute_proprata.py @@ -1,407 +1,304 @@ # Copyright 2018 ACSONE SA/NV. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import fields +from odoo import Command from odoo.tests.common import TransactionCase -def to_date(date): - return fields.Date.to_date(date) +class TestProductTemplate(TransactionCase): + """ + These tests verify that prorated invoice amounts are correctly computed + based on recurrence type, interval, invoicing type (pre-paid/post-paid), + and combinations of start, next, and end dates. + Main focuses: + - Monthly and monthly-last-day recurrences + - Pre-paid and post-paid contracts + - Edge cases like partial months, contract terminations, or prorations over + irregular dates + """ -class TestProductTemplate(TransactionCase): - def setUp(self): - super(TestProductTemplate, self).setUp() - self.partner = self.env.ref("base.res_partner_2") - self.product = self.env.ref("product.product_product_1") - self.contract = self.env["contract.contract"].create( + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env.ref("base.res_partner_2") + cls.product = cls.env.ref("product.product_product_1") + cls.contract = cls.env["contract.contract"].create( + [ + { + "name": "Test Contract 2", + "partner_id": cls.partner.id, + "pricelist_id": cls.partner.property_product_pricelist.id, + "contract_type": "purchase", + "contract_line_ids": [ + Command.create( + { + "product_id": cls.product.id, + "name": "Services from #START# to #END#", + "quantity": 1, + "uom_id": cls.product.uom_id.id, + "price_unit": 100, + "discount": 50, + "recurring_rule_type": "monthly", + "recurring_interval": 1, + "date_start": "2016-02-15", + "recurring_next_date": "2016-02-29", + }, + ) + ], + } + ] + ) + cls.contract_line = cls.contract.contract_line_ids[0] + + def _prepare_contract_line( + self, + recurring_rule_type, + recurring_interval, + recurring_invoicing_type, + date_start, + recurring_next_date, + date_end, + last_date_invoiced=False, + ): + """ + Update the existing contract line with the provided configuration. + """ + self.contract_line.write( { - "name": "Test Contract 2", - "partner_id": self.partner.id, - "pricelist_id": self.partner.property_product_pricelist.id, - "contract_type": "purchase", - "contract_line_ids": [ - ( - 0, - 0, - { - "product_id": self.product.id, - "name": "Services from #START# to #END#", - "quantity": 1, - "uom_id": self.product.uom_id.id, - "price_unit": 100, - "discount": 50, - "recurring_rule_type": "monthly", - "recurring_interval": 1, - "date_start": "2016-02-15", - "recurring_next_date": "2016-02-29", - }, - ) - ], + "recurring_rule_type": recurring_rule_type, + "recurring_invoicing_type": recurring_invoicing_type, + "recurring_interval": recurring_interval, + "date_start": date_start, + "recurring_next_date": recurring_next_date, + "date_end": date_end, + "last_date_invoiced": last_date_invoiced, } ) - self.contract_line = self.contract.contract_line_ids[0] - - def test_compute_prorated(self): - def update_contract_line( - case, - recurring_rule_type, - recurring_interval, - recurring_invoicing_type, - date_start, - recurring_next_date, - date_end, - last_date_invoiced=False, - ): - self.contract_line.write( - { - "recurring_rule_type": recurring_rule_type, - "recurring_invoicing_type": recurring_invoicing_type, - "recurring_interval": recurring_interval, - "date_start": date_start, - "recurring_next_date": recurring_next_date, - "date_end": date_end, - "last_date_invoiced": last_date_invoiced, - } - ) - - def error_message( - case, - recurring_rule_type, - recurring_interval, - recurring_invoicing_type, - date_start, - recurring_next_date, - date_end, - last_date_invoiced=False, - ): - return ( - "%s : Error in %s every %d %s case, start %s, next %s, end %s," - " last %s" - % ( - case, - recurring_invoicing_type, - recurring_interval, - recurring_rule_type, - date_start, - recurring_next_date, - date_end, - last_date_invoiced or "", - ) - ) - - combinations = [ - ( - 1.00, - ( - "Case 1", - "monthly", - 1, - "pre-paid", - to_date("2018-01-05"), - to_date("2018-01-05"), - False, - ), - ), - ( - 1.00, - ( - "Case 2", - "monthly", - 1, - "pre-paid", - to_date("2018-01-05"), - to_date("2018-02-01"), - False, - to_date("2018-01-31"), - ), - ), - ( - 1.00, - ( - "Case 3", - "monthly", - 1, - "pre-paid", - to_date("2017-01-05"), - to_date("2018-02-01"), - to_date("2018-03-01"), - to_date("2018-01-31"), - ), - ), - ( - 0.892, - ( - "Case 4", - "monthly", - 1, - "pre-paid", - to_date("2017-01-05"), - to_date("2018-02-01"), - to_date("2018-02-25"), - to_date("2018-01-31"), - ), - ), - ( - 1.00, - ( - "Case 5", - "monthly", - 1, - "post-paid", - to_date("2018-01-05"), - to_date("2018-02-05"), - False, - ), - ), - ( - 0.87, - ( - "Case 6", - "monthly", - 1, - "post-paid", - to_date("2018-01-05"), - to_date("2018-02-01"), - False, - ), - ), - ( - 1.00, - ( - "Case 7", - "monthly", - 1, - "post-paid", - to_date("2017-01-05"), - to_date("2018-02-01"), - to_date("2018-03-01"), - to_date("2017-12-31"), - ), - ), - ( - 0.892, - ( - "Case 8", - "monthly", - 1, - "post-paid", - to_date("2017-01-05"), - to_date("2018-03-01"), - to_date("2018-02-25"), - to_date("2018-01-31"), - ), - ), - ( - 1.00, - ( - "Case 9", - "monthlylastday", - 1, - "post-paid", - to_date("2018-01-01"), - to_date("2018-01-31"), - to_date("2018-02-25"), - ), - ), - ( - 0.87, - ( - "Case 10", - "monthlylastday", - 1, - "post-paid", - to_date("2018-01-05"), - to_date("2018-01-31"), - to_date("2018-02-25"), - ), - ), - ( - 0.892, - ( - "Case 11", - "monthlylastday", - 1, - "post-paid", - to_date("2018-01-05"), - to_date("2018-02-28"), - to_date("2018-02-25"), - to_date("2018-01-31"), - ), - ), - ( - 0.5, - ( - "Case 12", - "monthlylastday", - 1, - "post-paid", - to_date("2018-02-01"), - to_date("2018-02-28"), - to_date("2018-02-14"), - ), - ), - ( - 0.5, - ( - "Case 13", - "monthlylastday", - 1, - "post-paid", - to_date("2018-02-15"), - to_date("2018-02-28"), - False, - ), - ), - ( - 0.032, - ( - "Case 14", - "monthlylastday", - 1, - "post-paid", - to_date("2017-02-15"), - to_date("2018-01-31"), - False, - to_date("2018-01-30"), - ), - ), - ( - 1.035, - ( - "Case 15", - "monthlylastday", - 1, - "post-paid", - to_date("2017-02-15"), - to_date("2018-02-28"), - False, - to_date("2018-01-30"), - ), - ), - ( - 0.032, - ( - "Case 16", - "monthly", - 1, - "post-paid", - to_date("2017-02-15"), - to_date("2018-02-01"), - False, - to_date("2018-01-30"), - ), - ), - ( - 1.035, - ( - "Case 17", - "monthly", - 1, - "post-paid", - to_date("2017-02-15"), - to_date("2018-03-01"), - False, - to_date("2018-01-30"), - ), - ), - ( - 1.0, - ( - "Case 18", - "monthly", - 1, - "pre-paid", - to_date("2017-02-15"), - to_date("2018-01-01"), - False, - to_date("2017-12-31"), - ), - ), - ( - 1.035, - ( - "Case 19", - "monthly", - 1, - "pre-paid", - to_date("2017-02-15"), - to_date("2018-02-01"), - False, - to_date("2018-01-30"), - ), - ), - ( - 1.566, - ( - "Case 19", - "monthlylastday", - 1, - "pre-paid", - to_date("2018-03-15"), - to_date("2018-04-30"), - False, - ), - ), - ( - 1.48, - ( - "Case 20", - "monthly", - 1, - "post-paid", - to_date("2018-03-15"), - to_date("2018-04-30"), - False, - ), - ), - ( - 2.53, - ( - "Case 21", - "monthly", - 1, - "pre-paid", - to_date("2018-03-15"), - to_date("2018-04-30"), - False, - ), - ), - ( - 1, - ( - "Case 22", - "monthlylastday", - 1, - "pre-paid", - to_date("2018-03-01"), - to_date("2018-03-01"), - False, - ), - ), - ( - 0.5, - ( - "Case 23", - "monthlylastday", - 1, - "pre-paid", - to_date("2018-04-16"), - to_date("2018-04-16"), - False, - ), - ), - ] - for result, combination in combinations: - update_contract_line(*combination) - dates = self.contract_line._get_period_to_invoice( - self.contract_line.last_date_invoiced, - self.contract_line.recurring_next_date, - ) - self.assertAlmostEqual( - result, - self.contract_line.compute_prorated(*dates), - places=2, - msg=error_message(*combination), - ) + + def _assert_prorated(self, expected_result): + """ + Assert that the computed prorated invoice value matches the expected result + within 2 decimals. + """ + dates = self.contract_line._get_period_to_invoice( + self.contract_line.last_date_invoiced, + self.contract_line.recurring_next_date, + ) + self.assertAlmostEqual( + expected_result, + self.contract_line.compute_prorated(*dates), + places=2, + ) + + # ---- Test cases ---- + + def test_prorated_case_1(self): + """Pre-paid full period (same day start and next date).""" + self._prepare_contract_line( + "monthly", 1, "pre-paid", "2018-01-05", "2018-01-05", False + ) + self._assert_prorated(1.00) + + def test_prorated_case_2(self): + """Pre-paid, monthly recurrence within January (full month).""" + self._prepare_contract_line( + "monthly", 1, "pre-paid", "2018-01-05", "2018-02-01", False, "2018-01-31" + ) + self._assert_prorated(1.00) + + def test_prorated_case_3(self): + """Pre-paid full month, start from previous year.""" + self._prepare_contract_line( + "monthly", + 1, + "pre-paid", + "2017-01-05", + "2018-02-01", + "2018-03-01", + "2018-01-31", + ) + self._assert_prorated(1.00) + + def test_prorated_case_4(self): + """Pre-paid, partial last month before early termination.""" + self._prepare_contract_line( + "monthly", + 1, + "pre-paid", + "2017-01-05", + "2018-02-01", + "2018-02-25", + "2018-01-31", + ) + self._assert_prorated(0.892) + + def test_prorated_case_5(self): + """Post-paid, full monthly period.""" + self._prepare_contract_line( + "monthly", 1, "post-paid", "2018-01-05", "2018-02-05", False + ) + self._assert_prorated(1.00) + + def test_prorated_case_6(self): + """Post-paid, partial February period.""" + self._prepare_contract_line( + "monthly", 1, "post-paid", "2018-01-05", "2018-02-01", False + ) + self._assert_prorated(0.87) + + def test_prorated_case_7(self): + """Post-paid, full year to next February.""" + self._prepare_contract_line( + "monthly", + 1, + "post-paid", + "2017-01-05", + "2018-02-01", + "2018-03-01", + "2017-12-31", + ) + self._assert_prorated(1.00) + + def test_prorated_case_8(self): + """Post-paid, partial February coverage.""" + self._prepare_contract_line( + "monthly", + 1, + "post-paid", + "2017-01-05", + "2018-03-01", + "2018-02-25", + "2018-01-31", + ) + self._assert_prorated(0.892) + + def test_prorated_case_9(self): + """Post-paid, monthlylastday, full month.""" + self._prepare_contract_line( + "monthlylastday", 1, "post-paid", "2018-01-01", "2018-01-31", "2018-02-25" + ) + self._assert_prorated(1.00) + + def test_prorated_case_10(self): + """Post-paid, monthlylastday, partial month after 5th.""" + self._prepare_contract_line( + "monthlylastday", 1, "post-paid", "2018-01-05", "2018-01-31", "2018-02-25" + ) + self._assert_prorated(0.87) + + def test_prorated_case_11(self): + """Post-paid, monthlylastday, February with partial invoicing.""" + self._prepare_contract_line( + "monthlylastday", + 1, + "post-paid", + "2018-01-05", + "2018-02-28", + "2018-02-25", + "2018-01-31", + ) + self._assert_prorated(0.892) + + def test_prorated_case_12(self): + """Post-paid, February month split exactly in half.""" + self._prepare_contract_line( + "monthlylastday", 1, "post-paid", "2018-02-01", "2018-02-28", "2018-02-14" + ) + self._assert_prorated(0.5) + + def test_prorated_case_13(self): + """Post-paid, second half of February only.""" + self._prepare_contract_line( + "monthlylastday", 1, "post-paid", "2018-02-15", "2018-02-28", False + ) + self._assert_prorated(0.5) + + def test_prorated_case_14(self): + """Post-paid, very small fraction for one day in January.""" + self._prepare_contract_line( + "monthlylastday", + 1, + "post-paid", + "2017-02-15", + "2018-01-31", + False, + "2018-01-30", + ) + self._assert_prorated(0.032) + + def test_prorated_case_15(self): + """Post-paid, over month boundary with slight overrun.""" + self._prepare_contract_line( + "monthlylastday", + 1, + "post-paid", + "2017-02-15", + "2018-02-28", + False, + "2018-01-30", + ) + self._assert_prorated(1.035) + + def test_prorated_case_16(self): + """Post-paid, non-month-end recurrence, tiny fraction.""" + self._prepare_contract_line( + "monthly", 1, "post-paid", "2017-02-15", "2018-02-01", False, "2018-01-30" + ) + self._assert_prorated(0.032) + + def test_prorated_case_17(self): + """Post-paid, larger prorate because of two months in span.""" + self._prepare_contract_line( + "monthly", 1, "post-paid", "2017-02-15", "2018-03-01", False, "2018-01-30" + ) + self._assert_prorated(1.035) + + def test_prorated_case_18(self): + """Pre-paid, clean full month invoicing.""" + self._prepare_contract_line( + "monthly", 1, "pre-paid", "2017-02-15", "2018-01-01", False, "2017-12-31" + ) + self._assert_prorated(1.0) + + def test_prorated_case_19(self): + """Pre-paid, slight overrun from year-end to February.""" + self._prepare_contract_line( + "monthly", 1, "pre-paid", "2017-02-15", "2018-02-01", False, "2018-01-30" + ) + self._assert_prorated(1.035) + + def test_prorated_case_20(self): + """Pre-paid, monthlylastday spanning mid-March.""" + self._prepare_contract_line( + "monthlylastday", 1, "pre-paid", "2018-03-15", "2018-04-30", False + ) + self._assert_prorated(1.566) + + def test_prorated_case_21(self): + """Post-paid, large prorate end of March to April.""" + self._prepare_contract_line( + "monthly", 1, "post-paid", "2018-03-15", "2018-04-30", False + ) + self._assert_prorated(1.48) + + def test_prorated_case_22(self): + """Pre-paid, large prorate mid-March to April.""" + self._prepare_contract_line( + "monthly", 1, "pre-paid", "2018-03-15", "2018-04-30", False + ) + self._assert_prorated(2.53) + + def test_prorated_case_23(self): + """Pre-paid, monthlylastday, full month starting from March 1.""" + self._prepare_contract_line( + "monthlylastday", 1, "pre-paid", "2018-03-01", "2018-03-01", False + ) + self._assert_prorated(1.0) + + def test_prorated_case_24(self): + """Pre-paid, monthlylastday, half-month mid-April start.""" + self._prepare_contract_line( + "monthlylastday", 1, "pre-paid", "2018-04-16", "2018-04-16", False + ) + self._assert_prorated(0.5) diff --git a/contract_variable_qty_prorated/views/abstract_contract_view.xml b/contract_variable_qty_prorated/views/abstract_contract_view.xml deleted file mode 100644 index 3a4e3b6173..0000000000 --- a/contract_variable_qty_prorated/views/abstract_contract_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - Contract Line Form View (in abstract_contract_view.xml) - contract.abstract.contract.line - - - - - True - - - - - diff --git a/contract_variable_qty_prorated/views/contract_template_line.xml b/contract_variable_qty_prorated/views/contract_template_line.xml new file mode 100644 index 0000000000..9fe9b6500c --- /dev/null +++ b/contract_variable_qty_prorated/views/contract_template_line.xml @@ -0,0 +1,17 @@ + + + + + contract.template.line + + + + + True + + + + From d729a48a44adf864b9b2852de37e2440d1813c32 Mon Sep 17 00:00:00 2001 From: mymage Date: Wed, 30 Jul 2025 10:19:22 +0000 Subject: [PATCH 09/13] Added translation using Weblate (Italian) --- contract_variable_qty_prorated/i18n/it.po | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 contract_variable_qty_prorated/i18n/it.po diff --git a/contract_variable_qty_prorated/i18n/it.po b/contract_variable_qty_prorated/i18n/it.po new file mode 100644 index 0000000000..d5ef455f23 --- /dev/null +++ b/contract_variable_qty_prorated/i18n/it.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-31 10:25+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_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "Riga contratto" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "Quantità ripartita" From ad6bbc134d95a116cc2537b53fd144db7b6f45d3 Mon Sep 17 00:00:00 2001 From: jakobkrabbe Date: Fri, 8 Aug 2025 08:55:47 +0000 Subject: [PATCH 10/13] Added translation using Weblate (Swedish) --- contract_variable_qty_prorated/i18n/sv.po | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 contract_variable_qty_prorated/i18n/sv.po diff --git a/contract_variable_qty_prorated/i18n/sv.po b/contract_variable_qty_prorated/i18n/sv.po new file mode 100644 index 0000000000..71a009e616 --- /dev/null +++ b/contract_variable_qty_prorated/i18n/sv.po @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sv\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" + +#. module: contract_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "" From b5f9c315ef020135681dc20ffe7f6687f9d553e7 Mon Sep 17 00:00:00 2001 From: Bosd Date: Mon, 29 Dec 2025 08:39:04 +0000 Subject: [PATCH 11/13] Added translation using Weblate (Dutch) --- contract_variable_qty_prorated/i18n/nl.po | 25 ++++++++++++++++++ contract_variable_qty_prorated/i18n/nl_NL.po | 27 ++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 contract_variable_qty_prorated/i18n/nl.po create mode 100644 contract_variable_qty_prorated/i18n/nl_NL.po diff --git a/contract_variable_qty_prorated/i18n/nl.po b/contract_variable_qty_prorated/i18n/nl.po new file mode 100644 index 0000000000..a7ee506fca --- /dev/null +++ b/contract_variable_qty_prorated/i18n/nl.po @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: contract_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "" diff --git a/contract_variable_qty_prorated/i18n/nl_NL.po b/contract_variable_qty_prorated/i18n/nl_NL.po new file mode 100644 index 0000000000..1aa0c50989 --- /dev/null +++ b/contract_variable_qty_prorated/i18n/nl_NL.po @@ -0,0 +1,27 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * contract_variable_qty_prorated +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-12-29 10:42+0000\n" +"Last-Translator: Bosd \n" +"Language-Team: none\n" +"Language: nl_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_variable_qty_prorated +#: model:ir.model,name:contract_variable_qty_prorated.model_contract_line +msgid "Contract Line" +msgstr "Contractregel" + +#. module: contract_variable_qty_prorated +#: model:contract.line.qty.formula,name:contract_variable_qty_prorated.contract_variable_qty_prorated +msgid "Prorated Quantity" +msgstr "Pro rata hoeveelheid" From 3d74212059c3fec7fa0ac574fd2fa2b71d32f657 Mon Sep 17 00:00:00 2001 From: bosd Date: Mon, 29 Dec 2025 12:55:41 +0100 Subject: [PATCH 12/13] [MIG] contract_variable_qty_prorated: Migration to 19.0 --- contract_variable_qty_prorated/README.rst | 14 +++++------ .../__manifest__.py | 2 +- .../static/description/icon.png | Bin 9455 -> 10254 bytes .../static/description/index.html | 6 ++--- .../tests/test_compute_proprata.py | 22 ++++++++++++++---- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/contract_variable_qty_prorated/README.rst b/contract_variable_qty_prorated/README.rst index 5af83396b9..438efeaf3c 100644 --- a/contract_variable_qty_prorated/README.rst +++ b/contract_variable_qty_prorated/README.rst @@ -21,13 +21,13 @@ Contract Variable Qty Prorated :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_variable_qty_prorated + :target: https://github.com/OCA/contract/tree/19.0/contract_variable_qty_prorated :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_variable_qty_prorated + :target: https://translation.odoo-community.org/projects/contract-19-0/contract-19-0-contract_variable_qty_prorated :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| @@ -52,7 +52,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. @@ -67,8 +67,8 @@ Authors Contributors ------------ -- Souheil Bejaoui -- Thomas Binsfeld +- Souheil Bejaoui +- Thomas Binsfeld Maintainers ----------- @@ -91,6 +91,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_variable_qty_prorated/__manifest__.py b/contract_variable_qty_prorated/__manifest__.py index c495dfb470..12358b1e4c 100644 --- a/contract_variable_qty_prorated/__manifest__.py +++ b/contract_variable_qty_prorated/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Contract Variable Qty Prorated", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "license": "AGPL-3", "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "maintainers": ["sbejaoui"], diff --git a/contract_variable_qty_prorated/static/description/icon.png b/contract_variable_qty_prorated/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_variable_qty_prorated/static/description/index.html b/contract_variable_qty_prorated/static/description/index.html index e99fe66dc6..4ca03e7968 100644 --- a/contract_variable_qty_prorated/static/description/index.html +++ b/contract_variable_qty_prorated/static/description/index.html @@ -374,7 +374,7 @@

Contract Variable Qty Prorated

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:ff72cb63aa06a418f71a39644c35ca4000777aa6f61acc546fdfafa70bd669f6 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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 a formula to compute prorated quantity to invoice as extension of the module contract_variable_quantity.

Table of contents

@@ -400,7 +400,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.

@@ -429,7 +429,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_variable_qty_prorated/tests/test_compute_proprata.py b/contract_variable_qty_prorated/tests/test_compute_proprata.py index de472684b0..bbdbafa805 100644 --- a/contract_variable_qty_prorated/tests/test_compute_proprata.py +++ b/contract_variable_qty_prorated/tests/test_compute_proprata.py @@ -2,10 +2,13 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import Command -from odoo.tests.common import TransactionCase +from odoo.tests.common import tagged +from odoo.addons.base.tests.common import BaseCommon -class TestProductTemplate(TransactionCase): + +@tagged("post_install", "-at_install") +class TestProductTemplate(BaseCommon): """ These tests verify that prorated invoice amounts are correctly computed based on recurrence type, interval, invoicing type (pre-paid/post-paid), @@ -21,8 +24,19 @@ class TestProductTemplate(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - cls.partner = cls.env.ref("base.res_partner_2") - cls.product = cls.env.ref("product.product_product_1") + # Create partner and product instead of using references that may not exist + cls.partner = cls.env["res.partner"].create( + { + "name": "Test Partner", + "email": "test.partner@example.com", + } + ) + cls.product = cls.env["product.product"].create( + { + "name": "Test Service", + "type": "service", + } + ) cls.contract = cls.env["contract.contract"].create( [ { From 90b18c4538b1fe320ad4be1888c9e830da2535ee Mon Sep 17 00:00:00 2001 From: bosd Date: Mon, 29 Dec 2025 13:00:57 +0100 Subject: [PATCH 13/13] [DO NOT MERGE TEST REQUIREMENTS] --- test-requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test-requirements.txt diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000000..e3d8b66b7f --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +odoo-addon-contract @ git+https://github.com/OCA/contract@refs/pull/1312/head#subdirectory=contract +odoo-addon-contract_variable_quantity @ git+https://github.com/OCA/contract@refs/pull/1364/head#subdirectory=contract_variable_quantity