From 5b7c423b1baa98c4edea84e66f695b0253608ec8 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Fri, 5 Jan 2024 19:16:21 +0100 Subject: [PATCH 1/8] [REF] account_invoice_triple_discount: Consolidate discount in std field Until now, the triple discount feature did use the standard Odoo field as the first discount field, adding only extra fields for the second and the third discount. This implied we had to override any function using discount to consider the other discounts properly. By adding an extra field discount1 to store the first discount, it allows to redefine the standard discount field to a computed field that will consolidate the triple discount from the other fields, and avoid the need to redefine anything relying on the discount field as it will already consider the triple discounts. --- account_invoice_triple_discount/README.rst | 2 + .../__manifest__.py | 2 +- .../migrations/16.0.2.0.0/post-migrate.py | 41 +++++ .../migrations/16.0.2.0.0/pre-migrate.py | 32 ++++ .../models/__init__.py | 1 - .../models/account_move.py | 43 ------ .../models/account_move_line.py | 140 +++--------------- .../readme/CONTRIBUTORS.rst | 2 + .../report/invoice.xml | 22 ++- .../static/description/index.html | 1 + .../tests/test_invoice_triple_discount.py | 48 ++---- .../views/account_move.xml | 19 ++- 12 files changed, 148 insertions(+), 205 deletions(-) create mode 100644 account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py create mode 100644 account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py delete mode 100644 account_invoice_triple_discount/models/account_move.py diff --git a/account_invoice_triple_discount/README.rst b/account_invoice_triple_discount/README.rst index ee569703c4a..8ea71e91827 100644 --- a/account_invoice_triple_discount/README.rst +++ b/account_invoice_triple_discount/README.rst @@ -84,7 +84,9 @@ Contributors * `Aion Tech `_: * Simone Rubino + * Laurent Mignon +* Akim Juillerat Maintainers ~~~~~~~~~~~ diff --git a/account_invoice_triple_discount/__manifest__.py b/account_invoice_triple_discount/__manifest__.py index 87316604458..e6712b801bb 100644 --- a/account_invoice_triple_discount/__manifest__.py +++ b/account_invoice_triple_discount/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "Account Invoice Triple Discount", - "version": "16.0.1.0.3", + "version": "16.0.2.0.0", "category": "Accounting & Finance", "author": "QubiQ, Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/account-invoicing", diff --git a/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py b/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py new file mode 100644 index 00000000000..b2affcb2652 --- /dev/null +++ b/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py @@ -0,0 +1,41 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from openupgradelib import openupgrade + + +@openupgrade.logging() +def compute_discount(env): + move_lines_to_compute = env["account.move.line"].search( + [ + "|", + "|", + ("discount1", "!=", 0), + ("discount2", "!=", 0), + ("discount3", "!=", 0), + ] + ) + for line in move_lines_to_compute: + discount = line._get_aggregated_discount_from_values( + {fname: line[fname] for fname in line._get_multiple_discount_field_names()} + ) + rounded_discount = line._fields["discount"].convert_to_column(discount, line) + openupgrade.logged_query( + env.cr, + """ + UPDATE account_move_line + SET discount = %s + WHERE id = %s; + """, + tuple( + [ + rounded_discount, + line.id, + ] + ), + ) + + +@openupgrade.migrate() +def migrate(env, version): + compute_discount(env) diff --git a/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py b/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py new file mode 100644 index 00000000000..1e843bb608c --- /dev/null +++ b/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py @@ -0,0 +1,32 @@ +# Copyright 2024 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from openupgradelib import openupgrade + + +def migrate_discount_to_discount1(env): + openupgrade.add_fields( + env, + [ + ( + "discount1", + "account.move.line", + "account_move_line", + "float", + "numeric", + "account_invoice_triple_discount", + 0.0, + ) + ], + ) + openupgrade.logged_query( + env.cr, + """ + UPDATE account_move_line + SET discount1 = discount; + """, + ) + + +@openupgrade.migrate() +def migrate(env, version): + migrate_discount_to_discount1(env) diff --git a/account_invoice_triple_discount/models/__init__.py b/account_invoice_triple_discount/models/__init__.py index 936a0a60869..8795b3bea64 100644 --- a/account_invoice_triple_discount/models/__init__.py +++ b/account_invoice_triple_discount/models/__init__.py @@ -1,2 +1 @@ from . import account_move_line -from . import account_move diff --git a/account_invoice_triple_discount/models/account_move.py b/account_invoice_triple_discount/models/account_move.py deleted file mode 100644 index 258582904d5..00000000000 --- a/account_invoice_triple_discount/models/account_move.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2017 Tecnativa - David Vidal -# Copyright 2017 Tecnativa - Pedro M. Baeza -# Copyright 2023 Simone Rubino - Aion Tech -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, models - - -class AccountMove(models.Model): - - _inherit = "account.move" - - def _has_discount(self): - self.ensure_one() - return any( - [ - line._compute_aggregated_discount(line.discount) > 0 - for line in self.invoice_line_ids - ] - ) - - @api.model - def _field_will_change(self, record, vals, field_name): - result = super()._field_will_change(record, vals, field_name) - is_discount_field = ( - record._name == self.line_ids._name and field_name == "discount" - ) - if ( - not result - and self.env.context.get("restoring_triple_discount") - and is_discount_field - ): - # Discount value in the cache has many digits (e.g. 100.00000000000009), - # but we have just restored the original digits (e.g. 2) in the field - # and want to write the correct value (e.g. 100.00). - # The method in super compares: - # - the cache value rounded with the field's digits: 100.00 - # - the value we want to write: 100.00 - # and concludes that the value won't change, - # thus leaving the cache value (100.00000000000009) - # instead of updating the cache with the value we actually want (100.00) - result = True - return result diff --git a/account_invoice_triple_discount/models/account_move_line.py b/account_invoice_triple_discount/models/account_move_line.py index 9d6bee5dd96..aca1a5c959e 100644 --- a/account_invoice_triple_discount/models/account_move_line.py +++ b/account_invoice_triple_discount/models/account_move_line.py @@ -3,16 +3,24 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import functools -from contextlib import contextmanager from odoo import api, fields, models -from odoo.tools import float_compare class AccountMoveLine(models.Model): _inherit = "account.move.line" + discount = fields.Float( + string="Total discount", + compute="_compute_discount", + store=True, + readonly=True, + ) + discount1 = fields.Float( + string="Discount 1 (%)", + digits="Discount", + ) discount2 = fields.Float( string="Discount 2 (%)", digits="Discount", @@ -22,126 +30,18 @@ class AccountMoveLine(models.Model): digits="Discount", ) - @api.model_create_multi - def create(self, values_list): - """ - During the create of move lines, if the system detect that there is a - difference between the balance and the price subtotal, it will update - the unit price. When computing those, Odoo base module use a single - discount on creation. So as there is a difference of the price given - by the UI and the price computed during create method, the system will - change the unit price of the invoice line. To avoid that, we update - the discount field to have the aggregated discount, and we change it - back after the creation. - (Similar to _recompute_tax_lines on account.move) - """ - old_values = [] - dp_discount = self.env["decimal.precision"].precision_get("Discount") - for values in values_list: - old_discount = values.get("discount", 0.0) - new_discount = self._get_aggregated_discount_from_values(values) - tmp_values = {} - discount_changed = ( - float_compare(old_discount, new_discount, precision_digits=dp_discount) - != 0 + @api.depends("discount1", "discount2", "discount3") + def _compute_discount(self): + for line in self: + line.discount = line._get_aggregated_discount_from_values( + { + fname: line[fname] + for fname in line._get_multiple_discount_field_names() + } ) - if discount_changed: - values["discount"] = new_discount - tmp_values["discount"] = old_discount - old_values.append(tmp_values) - records = super().create(values_list) - for index, record in enumerate(records): - values = old_values[index] - if values: - record.write(old_values[index]) - return records - - @contextmanager - def _aggregated_discount(self): - """A context manager to temporarily change the discount value on the - records and restore it after the context is exited. It temporarily - changes the discount value to the aggregated discount value so that - methods that depend on the discount value will use the aggregated - discount value instead of the original one. - """ - discount_field = self._fields["discount"] - original_digits = discount_field._digits - # Change the discount field to have a higher precision to avoid - # rounding errors when computing the aggregated discount - discount_field._digits = (16, 16) - # Protect discount field from triggering recompute of totals - # and calls to the write method on account.move. This is safe - # because we are going to restore the original value at the end - # of the method. This is also required to avoid to trigger the - # _sync_dynamic_lines and _check_balanced methods on account.move - # that would lead to errors while computing the triple discount - # on the invoice lines and raise an exception when the computation is called - # on account.move.line with a date prior to fiscal year close date. - with self.env.protecting([discount_field], self): - old_values = {} - for line in self: - old_values[line.id] = line.discount - aggregated_discount = line._compute_aggregated_discount(line.discount) - line.update({"discount": aggregated_discount}) - yield - discount_field._digits = original_digits - for line in self: - if line.id not in old_values: - continue - line.with_context( - restoring_triple_discount=True, - ).update({"discount": old_values[line.id]}) - - @api.depends( - "quantity", - "discount", - "price_unit", - "tax_ids", - "currency_id", - "discount2", - "discount3", - ) - def _compute_totals(self): - # As the totals are recompute based on the discount field, we need - # to use the aggregated discount value to compute the totals. - with self._aggregated_discount(): - res = super()._compute_totals() - return res - - @api.depends( - "tax_ids", - "currency_id", - "partner_id", - "analytic_distribution", - "balance", - "partner_id", - "move_id.partner_id", - "price_unit", - "discount2", - "discount3", - ) - def _compute_all_tax(self): - # As the taxes are recompute based on the discount field, we need - # to use the aggregated discount value to compute the taxes. - with self._aggregated_discount(): - res = super()._compute_all_tax() - return res - - def _convert_to_tax_base_line_dict(self): - res = super()._convert_to_tax_base_line_dict() - res["discount"] = self._compute_aggregated_discount(res["discount"]) - return res - - def _compute_aggregated_discount(self, base_discount): - self.ensure_one() - discounts = [base_discount] - for discount_fname in self._get_multiple_discount_field_names(): - discounts.append(getattr(self, discount_fname, 0.0)) - return self._get_aggregated_multiple_discounts(discounts) def _get_aggregated_discount_from_values(self, values): - discount_fnames = ["discount"] - discount_fnames.extend(self._get_multiple_discount_field_names()) + discount_fnames = self._get_multiple_discount_field_names() discounts = [] for discount_fname in discount_fnames: discounts.append(values.get(discount_fname) or 0.0) @@ -157,4 +57,4 @@ def _get_aggregated_multiple_discounts(self, discounts): return aggregated_discount def _get_multiple_discount_field_names(self): - return ["discount2", "discount3"] + return ["discount1", "discount2", "discount3"] diff --git a/account_invoice_triple_discount/readme/CONTRIBUTORS.rst b/account_invoice_triple_discount/readme/CONTRIBUTORS.rst index b4fbae5129f..ad18e406bab 100644 --- a/account_invoice_triple_discount/readme/CONTRIBUTORS.rst +++ b/account_invoice_triple_discount/readme/CONTRIBUTORS.rst @@ -4,4 +4,6 @@ * `Aion Tech `_: * Simone Rubino + * Laurent Mignon +* Akim Juillerat diff --git a/account_invoice_triple_discount/report/invoice.xml b/account_invoice_triple_discount/report/invoice.xml index 7694ac0030c..e95cca6a767 100644 --- a/account_invoice_triple_discount/report/invoice.xml +++ b/account_invoice_triple_discount/report/invoice.xml @@ -5,14 +5,26 @@ inherit_id="account.report_invoice_document" priority="100" > - - + + False + + + + visibility: hidden; + + + Disc.1 % + Disc.3 % + + visibility: hidden; + + + + diff --git a/account_invoice_triple_discount/static/description/index.html b/account_invoice_triple_discount/static/description/index.html index 7f687813de1..3cc62d544d8 100644 --- a/account_invoice_triple_discount/static/description/index.html +++ b/account_invoice_triple_discount/static/description/index.html @@ -434,6 +434,7 @@

Contributors

  • Laurent Mignon <laurent.mignon@acsone.eu>
  • +
  • Akim Juillerat <akim.juillerat@camptocamp.com>
  • diff --git a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py index 027b834f3ff..b780d0ea5a6 100644 --- a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py +++ b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py @@ -62,7 +62,7 @@ def test_01_discounts(self): invoice_form = Form(invoice) with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 50.0 + line_form.discount1 = 50.0 invoice_form.save() invoice_line = invoice.invoice_line_ids[0] @@ -84,13 +84,13 @@ def test_01_discounts(self): # Deletes first discount with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 0 + line_form.discount1 = 0 invoice_form.save() self.assertEqual(invoice.amount_total, 69) # Charge 5% over price: with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = -5 + line_form.discount1 = -5 invoice_form.save() self.assertEqual(invoice.amount_total, 72.45) @@ -115,7 +115,7 @@ def test_02_discounts_multiple_lines(self): self.assertEqual(invoice.amount_total, 480.0) with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 50.0 + line_form.discount1 = 50.0 invoice_form.save() self.assertEqual(invoice.amount_total, 365.0) @@ -138,7 +138,7 @@ def test_03_discounts_decimals_price(self): self.assertEqual(invoice_line1.price_subtotal, 1393.0) with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 15.0 + line_form.discount1 = 15.0 invoice_form.save() self.assertEqual(invoice_line1.price_subtotal, 1184.05) @@ -153,54 +153,30 @@ def test_04_discounts_decimals_tax(self): line_form.name = "Line Decimals" line_form.quantity = 9950 line_form.price_unit = 0.14 - line_form.discount = 0 + line_form.discount1 = 0 line_form.discount2 = 0 invoice_form.save() self.assertEqual(invoice.amount_tax, 208.95) with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 15.0 + line_form.discount1 = 15.0 invoice_form.save() - def test_05_has_discount(self): - """ - Tests has_discount - """ - invoice = self.create_simple_invoice(0) - invoice_form = Form(invoice) - - self.assertFalse(invoice._has_discount()) - - with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 50.0 - invoice_form.save() - self.assertTrue(invoice._has_discount()) - - with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 0 - line_form.discount2 = 50.0 - invoice_form.save() - self.assertTrue(invoice._has_discount()) - - with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount2 = 0 - line_form.discount3 = 50.0 - invoice_form.save() - self.assertTrue(invoice._has_discount()) - def test_06_round_discount(self): """Discount value is rounded correctly""" invoice = self.create_simple_invoice(0) invoice_line = invoice.invoice_line_ids[0] - invoice_line.discount = 100 + invoice_line.discount1 = 100 + self.assertEqual(invoice_line.discount1, 100) self.assertEqual(invoice_line.discount, 100) def test_07_round_tax_discount(self): """Discount value is rounded correctly when taxes change""" invoice = self.create_simple_invoice(0) invoice_line = invoice.invoice_line_ids[0] - invoice_line.discount = 100 + invoice_line.discount1 = 100 invoice_line.tax_ids = False + self.assertEqual(invoice_line.discount1, 100) self.assertEqual(invoice_line.discount, 100) def test_tax_compute_with_lock_date(self): @@ -211,7 +187,7 @@ def test_tax_compute_with_lock_date(self): line_form.name = "Line Decimals" line_form.quantity = 9950 line_form.price_unit = 0.14 - line_form.discount = 10 + line_form.discount1 = 10 line_form.discount2 = 20 invoice_form.save() invoice_line = invoice.invoice_line_ids[0] diff --git a/account_invoice_triple_discount/views/account_move.xml b/account_invoice_triple_discount/views/account_move.xml index c57fa78a30b..e0a2908cd7d 100644 --- a/account_invoice_triple_discount/views/account_move.xml +++ b/account_invoice_triple_discount/views/account_move.xml @@ -5,17 +5,32 @@ account.move + + hide + Total discount + - - + + + + + + Total discount + From a5a615740b8bd8976f140706383a4b4f443bc385 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Wed, 5 Jun 2024 22:16:20 +0200 Subject: [PATCH 2/8] [IMP] account_invoice_triple_discount: Simplify account.move.line algorithm [FIX] account_invoice_triple_discount: populate discount1 field to avoid inconsistency if database contains account.move.line with not null discount before the installation of the module [REF] account_invoice_triple_discount: Add GRAP as coauthor of the module [FIX] account_invoice_triple_discount: Manage Correct creation of account move lines, where 'discount' field is set --- account_invoice_triple_discount/README.rst | 1 + account_invoice_triple_discount/__init__.py | 1 + .../__manifest__.py | 3 ++- account_invoice_triple_discount/hooks.py | 17 +++++++++++++ .../migrations/16.0.2.0.0/post-migrate.py | 4 +-- .../models/account_move_line.py | 25 +++++++++++-------- .../static/description/index.html | 12 ++++++--- .../tests/test_invoice_triple_discount.py | 25 +++++++++++++++++++ 8 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 account_invoice_triple_discount/hooks.py diff --git a/account_invoice_triple_discount/README.rst b/account_invoice_triple_discount/README.rst index 8ea71e91827..85fec66358f 100644 --- a/account_invoice_triple_discount/README.rst +++ b/account_invoice_triple_discount/README.rst @@ -74,6 +74,7 @@ Authors * QubiQ * Tecnativa +* GRAP Contributors ~~~~~~~~~~~~ diff --git a/account_invoice_triple_discount/__init__.py b/account_invoice_triple_discount/__init__.py index 0650744f6bc..cc6b6354ad8 100644 --- a/account_invoice_triple_discount/__init__.py +++ b/account_invoice_triple_discount/__init__.py @@ -1 +1,2 @@ from . import models +from .hooks import post_init_hook diff --git a/account_invoice_triple_discount/__manifest__.py b/account_invoice_triple_discount/__manifest__.py index e6712b801bb..3ca37a20043 100644 --- a/account_invoice_triple_discount/__manifest__.py +++ b/account_invoice_triple_discount/__manifest__.py @@ -5,11 +5,12 @@ "name": "Account Invoice Triple Discount", "version": "16.0.2.0.0", "category": "Accounting & Finance", - "author": "QubiQ, Tecnativa, Odoo Community Association (OCA)", + "author": "QubiQ, Tecnativa, GRAP, Odoo Community Association (OCA)", "website": "https://github.com/OCA/account-invoicing", "license": "AGPL-3", "summary": "Manage triple discount on invoice lines", "depends": ["account"], + "post_init_hook": "post_init_hook", "data": ["report/invoice.xml", "views/account_move.xml"], "installable": True, } diff --git a/account_invoice_triple_discount/hooks.py b/account_invoice_triple_discount/hooks.py new file mode 100644 index 00000000000..a27914aa616 --- /dev/null +++ b/account_invoice_triple_discount/hooks.py @@ -0,0 +1,17 @@ +# Copyright 2024-Today - Sylvain Le GAL (GRAP) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +_logger = logging.getLogger(__name__) + + +def post_init_hook(cr, registry): + _logger.info("Initializing column discount1 on table account_move_line") + cr.execute( + """ + UPDATE account_move_line + SET discount1 = discount + WHERE discount != 0 + """ + ) diff --git a/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py b/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py index b2affcb2652..91d75f6af68 100644 --- a/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py +++ b/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py @@ -16,8 +16,8 @@ def compute_discount(env): ] ) for line in move_lines_to_compute: - discount = line._get_aggregated_discount_from_values( - {fname: line[fname] for fname in line._get_multiple_discount_field_names()} + discount = line._get_aggregated_multiple_discounts( + [line[x] for x in ["discount1", "discount2", "discount3"]] ) rounded_discount = line._fields["discount"].convert_to_column(discount, line) openupgrade.logged_query( diff --git a/account_invoice_triple_discount/models/account_move_line.py b/account_invoice_triple_discount/models/account_move_line.py index aca1a5c959e..f64f18c5b7f 100644 --- a/account_invoice_triple_discount/models/account_move_line.py +++ b/account_invoice_triple_discount/models/account_move_line.py @@ -33,21 +33,23 @@ class AccountMoveLine(models.Model): @api.depends("discount1", "discount2", "discount3") def _compute_discount(self): for line in self: - line.discount = line._get_aggregated_discount_from_values( - { - fname: line[fname] - for fname in line._get_multiple_discount_field_names() - } + line.discount = line._get_aggregated_multiple_discounts( + [line[x] for x in line._get_multiple_discount_field_names()] ) - def _get_aggregated_discount_from_values(self, values): - discount_fnames = self._get_multiple_discount_field_names() - discounts = [] - for discount_fname in discount_fnames: - discounts.append(values.get(discount_fname) or 0.0) - return self._get_aggregated_multiple_discounts(discounts) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get("discount") and not vals.get("discount1"): + vals["discount1"] = vals.pop("discount") + return super().create(vals_list) def _get_aggregated_multiple_discounts(self, discounts): + """ + Returns the aggregate discount corresponding to any number of discounts. + For exemple, if discounts is [11.0, 22.0, 33.0] + It will return 46.5114 + """ discount_values = [] for discount in discounts: discount_values.append(1 - (discount or 0.0) / 100.0) @@ -56,5 +58,6 @@ def _get_aggregated_multiple_discounts(self, discounts): ) * 100 return aggregated_discount + @api.model def _get_multiple_discount_field_names(self): return ["discount1", "discount2", "discount3"] diff --git a/account_invoice_triple_discount/static/description/index.html b/account_invoice_triple_discount/static/description/index.html index 3cc62d544d8..9866603a598 100644 --- a/account_invoice_triple_discount/static/description/index.html +++ b/account_invoice_triple_discount/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -421,6 +422,7 @@

    Authors

    • QubiQ
    • Tecnativa
    • +
    • GRAP
    @@ -440,7 +442,9 @@

    Contributors

    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.

    diff --git a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py index b780d0ea5a6..af0fbaccb05 100644 --- a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py +++ b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py @@ -14,6 +14,7 @@ def setUpClass(cls): cls.env.user.groups_id += cls.env.ref("product.group_discount_per_so_line") cls.Account = cls.env["account.account"] cls.AccountMove = cls.env["account.move"] + cls.AccountMoveLine = cls.env["account.move.line"] cls.AccountTax = cls.env["account.tax"] cls.Partner = cls.env["res.partner"] cls.Journal = cls.env["account.journal"] @@ -179,6 +180,30 @@ def test_07_round_tax_discount(self): self.assertEqual(invoice_line.discount1, 100) self.assertEqual(invoice_line.discount, 100) + def test_09_create_with_main_discount(self): + """ + Tests if creating a invoice line with main discount field + set correctly discount1, discount2 and discount3 + """ + invoice = self.create_simple_invoice(0) + + invoice_line2 = self.AccountMoveLine.create( + { + "move_id": invoice.id, + "name": "Line With Main Discount", + "quantity": 1, + "price_unit": 1000, + "discount": 10, + "tax_ids": [], + } + ) + + # 1000 * 0.9 + self.assertEqual(invoice_line2.price_subtotal, 900.0) + self.assertEqual(invoice_line2.discount1, 10.0) + self.assertEqual(invoice_line2.discount2, 0.0) + self.assertEqual(invoice_line2.discount3, 0.0) + def test_tax_compute_with_lock_date(self): # Check that the tax computation works even if the lock date is set invoice = self.create_simple_invoice(0) From c18191531d49af4fa797fbe19c391a271d5ae917 Mon Sep 17 00:00:00 2001 From: David Ramia <84158523+ramiadavid@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:23:53 +0200 Subject: [PATCH 3/8] Add test --- .../tests/test_invoice_triple_discount.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py index af0fbaccb05..b2dec56578e 100644 --- a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py +++ b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py @@ -204,6 +204,32 @@ def test_09_create_with_main_discount(self): self.assertEqual(invoice_line2.discount2, 0.0) self.assertEqual(invoice_line2.discount3, 0.0) + def test_10_create_invoice_with_discounts(self): + invoice = self.env["account.move"].create( + { + "partner_id": self.partner.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "quantity": 1, + "price_unit": 100, + "discount1": 30, + "discount2": 20, + "discount3": 10, + }, + ) + ], + } + ) + invoice_line1 = invoice.invoice_line_ids[0] + self.assertEqual(invoice_line1.discount1, 30.0) + self.assertEqual(invoice_line1.discount2, 20.0) + self.assertEqual(invoice_line1.discount3, 10.0) + def test_tax_compute_with_lock_date(self): # Check that the tax computation works even if the lock date is set invoice = self.create_simple_invoice(0) From b30bbe898d00754c1bf3bd5c45c785d66df201e3 Mon Sep 17 00:00:00 2001 From: Akim Juillerat Date: Mon, 29 Jul 2024 19:12:58 +0200 Subject: [PATCH 4/8] Write discount into discount 1 if not defined In case this module is installed and sale module is also installed but not sale_triple_discount, or in case sale_triple_discount is not updated to its latest version, make sure that setting discount, without having discount1 defined, will not mess with the triple discounts. --- .../models/account_move_line.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/account_invoice_triple_discount/models/account_move_line.py b/account_invoice_triple_discount/models/account_move_line.py index f64f18c5b7f..452f150de16 100644 --- a/account_invoice_triple_discount/models/account_move_line.py +++ b/account_invoice_triple_discount/models/account_move_line.py @@ -37,13 +37,6 @@ def _compute_discount(self): [line[x] for x in line._get_multiple_discount_field_names()] ) - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - if vals.get("discount") and not vals.get("discount1"): - vals["discount1"] = vals.pop("discount") - return super().create(vals_list) - def _get_aggregated_multiple_discounts(self, discounts): """ Returns the aggregate discount corresponding to any number of discounts. @@ -61,3 +54,27 @@ def _get_aggregated_multiple_discounts(self, discounts): @api.model def _get_multiple_discount_field_names(self): return ["discount1", "discount2", "discount3"] + + def _fix_triple_discount_values(self, values): + res = values + if "discount" in values and "discount1" not in values: + res = values.copy() + res["discount1"] = res.pop("discount") + return res + + # In case sale is installed but not sale_triple_discount, Odoo + # will propagate sale.order.line.discount into account.move.line.discount + # but account.move.line.discount1 will not be set properly + # Override create/write and only in case discount is set but not + # discount1, move its value to discount1 + @api.model_create_multi + def create(self, vals_list): + for i, vals in enumerate(vals_list): + new_vals = self._fix_triple_discount_values(vals) + if new_vals: + vals_list[i] = new_vals + return super().create(vals_list) + + def write(self, vals): + vals = self._fix_triple_discount_values(vals) + return super().write(vals) From e03dba34cbc1dba5f5efcd7f0563d846e54da1f3 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Tue, 12 Nov 2024 21:46:23 +0100 Subject: [PATCH 5/8] [REF] account_global_discount: adapt test to make it working if account_invoice_triple_discount is installed --- account_global_discount/tests/test_global_discount.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/account_global_discount/tests/test_global_discount.py b/account_global_discount/tests/test_global_discount.py index a491b9c054d..af5e229162d 100644 --- a/account_global_discount/tests/test_global_discount.py +++ b/account_global_discount/tests/test_global_discount.py @@ -161,7 +161,12 @@ def test_01_global_invoice_succesive_discounts(self): # The global discounts amount is then 160 - 56 = 104 with Form(self.invoice) as invoice_form: with invoice_form.invoice_line_ids.edit(0) as line_form: - line_form.discount = 20 + # Make the test compatible if account_invoice_triple_discount + # is installed + if "discount1" in line_form._model._fields: + line_form.discount1 = 20 + else: + line_form.discount = 20 self.assertEqual(len(self.invoice.invoice_global_discount_ids), 2) invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 56.0) From 37c8e0873999442e450a13b8427c125edd497dfd Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Tue, 12 Nov 2024 22:26:13 +0100 Subject: [PATCH 6/8] [FIX] account_invoice_fixed_discount, account_invoice_triple_discount: - set modules as incompatible in the manifest. (For the time being, there is only a informative message in the README of the module account_invoice_fixed_discount) - Run separated tests for both modules. --- .copier-answers.yml | 4 +++- .github/workflows/test.yml | 19 +++++++++++++++++++ .../__manifest__.py | 1 + .../static/description/index.html | 11 +++++++---- .../__manifest__.py | 1 + 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 8a1c7de5622..30077a55a94 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -15,7 +15,9 @@ odoo_test_flavor: Both odoo_version: 16.0 org_name: Odoo Community Association (OCA) org_slug: OCA -rebel_module_groups: [] +rebel_module_groups: +- account_invoice_triple_discount +- account_invoice_fixed_discount repo_description: 'TODO: add repo description.' repo_name: account-invoicing repo_slug: account-invoicing diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3bf18e843c5..338a54dedc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,8 +36,24 @@ jobs: matrix: include: - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest + include: "account_invoice_triple_discount" name: test with Odoo - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest + include: "account_invoice_triple_discount" + name: test with OCB + makepot: "true" + - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest + include: "account_invoice_fixed_discount" + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest + include: "account_invoice_fixed_discount" + name: test with OCB + makepot: "true" + - container: ghcr.io/oca/oca-ci/py3.10-odoo16.0:latest + exclude: "account_invoice_triple_discount,account_invoice_fixed_discount" + name: test with Odoo + - container: ghcr.io/oca/oca-ci/py3.10-ocb16.0:latest + exclude: "account_invoice_triple_discount,account_invoice_fixed_discount" name: test with OCB makepot: "true" services: @@ -49,6 +65,9 @@ jobs: POSTGRES_DB: odoo ports: - 5432:5432 + env: + INCLUDE: "${{ matrix.include }}" + EXCLUDE: "${{ matrix.exclude }}" steps: - uses: actions/checkout@v3 with: diff --git a/account_invoice_fixed_discount/__manifest__.py b/account_invoice_fixed_discount/__manifest__.py index 90df4387b70..147f3b6c8b3 100644 --- a/account_invoice_fixed_discount/__manifest__.py +++ b/account_invoice_fixed_discount/__manifest__.py @@ -12,6 +12,7 @@ "application": False, "installable": True, "depends": ["account"], + "excludes": ["account_invoice_triple_discount"], "data": [ "security/res_groups.xml", "views/account_move_view.xml", diff --git a/account_invoice_fixed_discount/static/description/index.html b/account_invoice_fixed_discount/static/description/index.html index f85341546ed..39060a4fb5b 100644 --- a/account_invoice_fixed_discount/static/description/index.html +++ b/account_invoice_fixed_discount/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -429,7 +430,9 @@

    Contributors

    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.

    diff --git a/account_invoice_triple_discount/__manifest__.py b/account_invoice_triple_discount/__manifest__.py index 3ca37a20043..0658d9e5513 100644 --- a/account_invoice_triple_discount/__manifest__.py +++ b/account_invoice_triple_discount/__manifest__.py @@ -10,6 +10,7 @@ "license": "AGPL-3", "summary": "Manage triple discount on invoice lines", "depends": ["account"], + "excludes": ["account_invoice_fixed_discount"], "post_init_hook": "post_init_hook", "data": ["report/invoice.xml", "views/account_move.xml"], "installable": True, From 62d5d3f4b44a5cc603dda1028c333a4a2ca208b3 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 14 Nov 2024 11:35:19 +0100 Subject: [PATCH 7/8] [FIX] account_invoice_triple_discount: remove digits on overloaded field 'discount' as the field is now a technical field that should not have limitation. For exemple in the default case where digits precision of Discount is 2, if discounts are 05%, 09% and 13% the main discount is 24.7885 % (and not 24.79) --- .../models/account_move_line.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/account_invoice_triple_discount/models/account_move_line.py b/account_invoice_triple_discount/models/account_move_line.py index 452f150de16..ee469dabee4 100644 --- a/account_invoice_triple_discount/models/account_move_line.py +++ b/account_invoice_triple_discount/models/account_move_line.py @@ -11,11 +11,18 @@ class AccountMoveLine(models.Model): _inherit = "account.move.line" + # core discount field is now a computed field + # based on the 3 discounts defined below. + # the digits limitation is removed, to make + # the computation of the subtotal exact. + # For exemple, if discounts are 05%, 09% and 13% + # the main discount is 24.7885 % (and not 24.79) discount = fields.Float( string="Total discount", compute="_compute_discount", store=True, readonly=True, + digits=None, ) discount1 = fields.Float( string="Discount 1 (%)", From dfa322e6029f1724a9939218e340e6a45e1086f1 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 14 Nov 2024 11:38:58 +0100 Subject: [PATCH 8/8] [REF] account_invoice_triple_discount: Improve speed computation of the field 'discount' and reduce complexity --- .../migrations/16.0.2.0.0/post-migrate.py | 41 ------------------- .../migrations/16.0.2.0.0/pre-migrate.py | 15 +++++++ 2 files changed, 15 insertions(+), 41 deletions(-) delete mode 100644 account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py diff --git a/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py b/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py deleted file mode 100644 index 91d75f6af68..00000000000 --- a/account_invoice_triple_discount/migrations/16.0.2.0.0/post-migrate.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2024 Camptocamp SA -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) - -from openupgradelib import openupgrade - - -@openupgrade.logging() -def compute_discount(env): - move_lines_to_compute = env["account.move.line"].search( - [ - "|", - "|", - ("discount1", "!=", 0), - ("discount2", "!=", 0), - ("discount3", "!=", 0), - ] - ) - for line in move_lines_to_compute: - discount = line._get_aggregated_multiple_discounts( - [line[x] for x in ["discount1", "discount2", "discount3"]] - ) - rounded_discount = line._fields["discount"].convert_to_column(discount, line) - openupgrade.logged_query( - env.cr, - """ - UPDATE account_move_line - SET discount = %s - WHERE id = %s; - """, - tuple( - [ - rounded_discount, - line.id, - ] - ), - ) - - -@openupgrade.migrate() -def migrate(env, version): - compute_discount(env) diff --git a/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py b/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py index 1e843bb608c..397b6673778 100644 --- a/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py +++ b/account_invoice_triple_discount/migrations/16.0.2.0.0/pre-migrate.py @@ -25,6 +25,21 @@ def migrate_discount_to_discount1(env): SET discount1 = discount; """, ) + # if discounts are : 10% - 20% - 30% main discount is : 49.6 % + # if discounts are : 05% - 09% - 13% main discount is : 24.7885 % + openupgrade.logged_query( + env.cr, + """ + UPDATE account_move_line + SET discount = 100 * ( + 1 - ( + (100 - COALESCE(discount1, 0.0)) / 100 + * (100 - COALESCE(discount2, 0.0)) / 100 + * (100 - COALESCE(discount3, 0.0)) / 100 + ) + ); + """, + ) @openupgrade.migrate()