diff --git a/account_invoice_triple_discount/__manifest__.py b/account_invoice_triple_discount/__manifest__.py
index 42477a56069..78f76d0621f 100644
--- a/account_invoice_triple_discount/__manifest__.py
+++ b/account_invoice_triple_discount/__manifest__.py
@@ -16,6 +16,7 @@
],
'data': [
'views/account_invoice_view.xml',
+ 'views/res_partner_views.xml',
],
'installable': True,
}
diff --git a/account_invoice_triple_discount/models/__init__.py b/account_invoice_triple_discount/models/__init__.py
index 6834786a795..655ebd643e5 100644
--- a/account_invoice_triple_discount/models/__init__.py
+++ b/account_invoice_triple_discount/models/__init__.py
@@ -1,3 +1,5 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from . import line_triple_discount
from . import account_invoice
+from . import res_partner
diff --git a/account_invoice_triple_discount/models/account_invoice.py b/account_invoice_triple_discount/models/account_invoice.py
index ad310130c88..e6e09c33ccc 100644
--- a/account_invoice_triple_discount/models/account_invoice.py
+++ b/account_invoice_triple_discount/models/account_invoice.py
@@ -3,59 +3,42 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
-from odoo.addons import decimal_precision as dp
class AccountInvoice(models.Model):
_inherit = "account.invoice"
+ discounting_type = fields.Selection(
+ related='partner_id.discounting_type',
+ )
+
def get_taxes_values(self):
- vals = {}
- for line in self.invoice_line_ids:
- vals[line] = {
- 'price_unit': line.price_unit,
- 'discount': line.discount,
- }
- price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
- price_unit *= (1 - (line.discount2 or 0.0) / 100.0)
- price_unit *= (1 - (line.discount3 or 0.0) / 100.0)
- line.update({
- 'price_unit': price_unit,
- 'discount': 0.0,
- })
- tax_grouped = super(AccountInvoice, self).get_taxes_values()
- for line in self.invoice_line_ids:
- line.update(vals[line])
+ lines = self.invoice_line_ids
+ prev_values = lines.triple_discount_preprocess()
+ tax_grouped = super().get_taxes_values()
+ lines.triple_discount_postprocess(prev_values)
return tax_grouped
+ @api.onchange('partner_id', 'company_id')
+ def _onchange_partner_id(self):
+ self.ensure_one()
+ res = super()._onchange_partner_id()
+ partner_discounting_type = self.partner_id.discounting_type
+ if partner_discounting_type:
+ self.invoice_line_ids.update({
+ 'discounting_type': partner_discounting_type,
+ })
+ return res
-class AccountInvoiceLine(models.Model):
- _inherit = "account.invoice.line"
- discount2 = fields.Float(
- 'Discount 2 (%)',
- digits=dp.get_precision('Discount'),
- )
- discount3 = fields.Float(
- 'Discount 3 (%)',
- digits=dp.get_precision('Discount'),
- )
+class AccountInvoiceLine(models.Model):
+ _name = "account.invoice.line"
+ _inherit = ["line.triple_discount.mixin", "account.invoice.line"]
@api.multi
- @api.depends('discount2', 'discount3')
+ @api.depends('discount2', 'discount3', 'discounting_type')
def _compute_price(self):
for line in self:
- prev_price_unit = line.price_unit
- prev_discount = line.discount
- price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
- price_unit *= (1 - (line.discount2 or 0.0) / 100.0)
- price_unit *= (1 - (line.discount3 or 0.0) / 100.0)
- line.update({
- 'price_unit': price_unit,
- 'discount': 0.0,
- })
+ prev_values = line.triple_discount_preprocess()
super(AccountInvoiceLine, line)._compute_price()
- line.update({
- 'price_unit': prev_price_unit,
- 'discount': prev_discount,
- })
+ line.triple_discount_postprocess(prev_values)
diff --git a/account_invoice_triple_discount/models/line_triple_discount.py b/account_invoice_triple_discount/models/line_triple_discount.py
new file mode 100644
index 00000000000..e1291b7c65a
--- /dev/null
+++ b/account_invoice_triple_discount/models/line_triple_discount.py
@@ -0,0 +1,111 @@
+# Copyright 2017 Tecnativa - David Vidal
+# Copyright 2017 Tecnativa - Pedro M. Baeza
+# Copyright 2021 Simone Rubino - Agile Business Group
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import api, fields, models
+from odoo.addons import decimal_precision as dp
+
+
+class LineTripleDiscount (models.AbstractModel):
+ _name = "line.triple_discount.mixin"
+ _description = "Add triple discount fields and logic"
+
+ discount = fields.Float()
+ discount2 = fields.Float(
+ 'Discount 2 (%)',
+ digits=dp.get_precision('Discount'),
+ )
+ discount3 = fields.Float(
+ 'Discount 3 (%)',
+ digits=dp.get_precision('Discount'),
+ )
+ discounting_type = fields.Selection(
+ string="Discounting type",
+ selection=[
+ ('additive', 'Additive'),
+ ('multiplicative', 'Multiplicative'),
+ ],
+ default="multiplicative",
+ required=True,
+ help="Specifies whether discounts should be additive "
+ "or multiplicative.\nAdditive discounts are summed first and "
+ "then applied.\nMultiplicative discounts are applied sequentially.\n"
+ "Multiplicative discounts are default",
+ )
+
+ _sql_constraints = [
+ ('discount2_limit', 'CHECK (discount2 <= 100.0)',
+ 'Discount 2 must be lower than 100%.'),
+ ('discount3_limit', 'CHECK (discount3 <= 100.0)',
+ 'Discount 3 must be lower than 100%.'),
+ ]
+
+ def _get_final_discount(self):
+ self.ensure_one()
+ if self.discounting_type == "additive":
+ return self._additive_discount()
+ elif self.discounting_type == "multiplicative":
+ return self._multiplicative_discount()
+
+ def _additive_discount(self):
+ self.ensure_one()
+ discount = sum(
+ [getattr(self, x) or 0.0 for x in self._discount_fields()]
+ )
+ if discount <= 0:
+ return 0
+ elif discount >= 100:
+ return 100
+ return discount
+
+ def _multiplicative_discount(self):
+ self.ensure_one()
+ discounts = [1 - (self[x] or 0.0) / 100
+ for x in self._discount_fields()]
+ final_discount = 1
+ for discount in discounts:
+ final_discount *= discount
+ return 100 - final_discount * 100
+
+ def _discount_fields(self):
+ return ['discount', 'discount2', 'discount3']
+
+ @api.multi
+ def triple_discount_preprocess(self):
+ """Save the values of the discounts in a dictionary,
+ to be restored in postprocess.
+ Resetting discount2 and discount3 to 0.0 avoids issues if
+ this method is called multiple times.
+ Updating the cache provides consistency through recomputations."""
+ prev_values = dict()
+
+ # The newly computed discount might have
+ # more digits than allowed from field's precision,
+ # so let's increase it just for saving it correctly in cache
+ discount_field = self._fields['discount']
+ discount_original_digits = discount_field._digits
+ discount_field._digits = (16, 10)
+
+ for line in self:
+ prev_values[line] = dict(
+ discount=line.discount,
+ discount2=line.discount2,
+ discount3=line.discount3,
+ )
+ line.update({
+ 'discount': line._get_final_discount(),
+ 'discount2': 0.0,
+ 'discount3': 0.0
+ })
+
+ # Restore discount field's precision
+ discount_field._digits = discount_original_digits
+ return prev_values
+
+ @api.model
+ def triple_discount_postprocess(self, prev_values):
+ """Restore the discounts of the lines in the dictionary prev_values.
+ Updating the cache provides consistency through recomputations."""
+ for line, prev_vals_dict in list(prev_values.items()):
+ line.update(prev_vals_dict)
diff --git a/account_invoice_triple_discount/models/res_partner.py b/account_invoice_triple_discount/models/res_partner.py
new file mode 100644
index 00000000000..7d8a2baffc8
--- /dev/null
+++ b/account_invoice_triple_discount/models/res_partner.py
@@ -0,0 +1,24 @@
+# Copyright 2021 Simone Rubino - Agile Business Group
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import fields, models
+
+
+class ResPartner (models.Model):
+ _inherit = 'res.partner'
+
+ discounting_type = fields.Selection(
+ string="Discounting type",
+ selection=[
+ ('additive', 'Additive'),
+ ('multiplicative', 'Multiplicative'),
+ ],
+ default="multiplicative",
+ required=True,
+ help="""
+ Specifies whether discounts should be additive or multiplicative.
+ Additive discounts are summed first and then applied.
+ Multiplicative discounts (default) are applied sequentially.
+ This type of discount will be the default for this partner's invoices.
+ """,
+ )
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 7099a710f87..47c1635f43f 100644
--- a/account_invoice_triple_discount/tests/test_invoice_triple_discount.py
+++ b/account_invoice_triple_discount/tests/test_invoice_triple_discount.py
@@ -83,3 +83,52 @@ def test_02_discounts_multiple_lines(self):
self.invoice_line1.discount = 50.0
self.invoice._onchange_invoice_line_ids()
self.assertEqual(self.invoice.amount_total, 365.0)
+
+ def test_03_discounts(self):
+ """ Tests discounts in edge case """
+ invoice = self.env['account.invoice'].create({
+ 'name': "Test Customer Invoice",
+ 'journal_id': self.env['account.journal'].search(
+ [('type', '=', 'sale')])[0].id,
+ 'partner_id': self.partner.id,
+ 'account_id': self.account.id,
+ 'invoice_line_ids': [(0, 0, {
+ 'name': 'Line 1',
+ 'price_unit': 25.0,
+ 'account_id': self.account.id,
+ 'quantity': 65,
+ 'discount': 50,
+ 'discount2': 13,
+ 'discount3': 0,
+ })],
+ })
+ self.assertEqual(invoice.invoice_line_ids.price_subtotal, 706.88)
+
+ def test_increased_precision(self):
+ """
+ Check that final discount is computed correctly
+ when final discount's precision exceeds
+ discount field's allowed precision.
+ """
+ invoice_line = self.invoice_line1
+ discount_field = invoice_line._fields['discount']
+ self.assertEqual(discount_field.digits[1], 2)
+ invoice_line.discount = 45.0
+ invoice_line.discount2 = 10.0
+ invoice_line.discount3 = 5.0
+
+ self.assertAlmostEqual(
+ invoice_line._get_final_discount(),
+ 52.975,
+ places=3,
+ )
+ invoice_line.triple_discount_preprocess()
+
+ # Check that the line's discount still has all the 3 digits
+ self.assertAlmostEqual(
+ invoice_line.discount,
+ 52.975,
+ places=3,
+ )
+ # Check that field's precision is not changed
+ self.assertEqual(discount_field.digits[1], 2)
diff --git a/account_invoice_triple_discount/views/account_invoice_view.xml b/account_invoice_triple_discount/views/account_invoice_view.xml
index c85806edfcd..f57c82e9527 100755
--- a/account_invoice_triple_discount/views/account_invoice_view.xml
+++ b/account_invoice_triple_discount/views/account_invoice_view.xml
@@ -6,10 +6,17 @@
account.invoice
+
+
+
+
+ {'type': type, 'journal_id': journal_id, 'default_invoice_id': id, 'default_discounting_type': discounting_type}
+
+
@@ -24,6 +31,7 @@
position="after">
+
diff --git a/account_invoice_triple_discount/views/res_partner_views.xml b/account_invoice_triple_discount/views/res_partner_views.xml
new file mode 100644
index 00000000000..a593bd89a75
--- /dev/null
+++ b/account_invoice_triple_discount/views/res_partner_views.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Add triple discount fields
+ res.partner
+
+
+
+
+
+
+
+
+
+