Skip to content

Commit 2f51a8e

Browse files
committed
sale_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.
1 parent c81b3f0 commit 2f51a8e

File tree

4 files changed

+114
-65
lines changed

4 files changed

+114
-65
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2024 Camptocamp SA
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
3+
4+
import openupgrade
5+
6+
7+
@openupgrade.logging()
8+
def compute_discount(env):
9+
env["sale.order.line"].search(
10+
[
11+
"|",
12+
"|",
13+
("discount1", "!=", 0),
14+
("discount2", "!=", 0),
15+
("discount3", "!=", 0),
16+
]
17+
)
18+
env["sale.order.line"]._compute_discount()
19+
20+
21+
@openupgrade.migrate()
22+
def migrate(env, version):
23+
compute_discount(env)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2024 Camptocamp SA
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
3+
import openupgrade
4+
5+
6+
def migrate_discount_to_discount1():
7+
openupgrade.add_fields(
8+
[
9+
(
10+
"discount1",
11+
"sale.order.line",
12+
"sale_order_line",
13+
"float",
14+
"numeric",
15+
"sale_triple_discount",
16+
0.0,
17+
)
18+
]
19+
)
20+
openupgrade.logged_query(
21+
"""
22+
UPDATE sale_order_line
23+
SET discount1 = discount;
24+
"""
25+
)
26+
27+
28+
def migrate(cr, version):
29+
migrate_discount_to_discount1()

sale_triple_discount/models/sale_order_line.py

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
class SaleOrderLine(models.Model):
1212
_inherit = "sale.order.line"
1313

14+
discount1 = fields.Float(
15+
string="Disc. 1 (%)",
16+
digits="Discount",
17+
default=0.0,
18+
)
1419
discount2 = fields.Float(
1520
string="Disc. 2 (%)",
1621
digits="Discount",
@@ -62,16 +67,27 @@ def _multiplicative_discount(self):
6267

6368
@api.model
6469
def _discount_fields(self):
65-
return ["discount", "discount2", "discount3"]
70+
return ["discount1", "discount2", "discount3"]
6671

67-
@api.depends("discount2", "discount3", "discounting_type")
68-
def _compute_amount(self):
69-
prev_values = self.triple_discount_preprocess()
70-
res = super()._compute_amount()
71-
self.triple_discount_postprocess(prev_values)
72-
return res
72+
# pylint: disable=W8110
73+
@api.depends("discount1", "discount2", "discount3", "discounting_type")
74+
def _compute_discount(self):
75+
super()._compute_discount()
76+
for line in self:
77+
# Reset discount if not done in super
78+
if not (
79+
line.order_id.pricelist_id
80+
and line.order_id.pricelist_id.discount_policy == "without_discount"
81+
):
82+
line.discount = 0.0
83+
line.discount += line._get_final_discount()
7384

7485
_sql_constraints = [
86+
(
87+
"discount1_limit",
88+
"CHECK (discount1 <= 100.0)",
89+
"Discount 1 must be lower or equal than 100%.",
90+
),
7591
(
7692
"discount2_limit",
7793
"CHECK (discount2 <= 100.0)",
@@ -90,51 +106,14 @@ def _prepare_invoice_line(self, **kwargs):
90106
more discount fields to the invoice lines
91107
"""
92108
res = super()._prepare_invoice_line(**kwargs)
93-
res.update({"discount2": self.discount2, "discount3": self.discount3})
109+
if self.discounting_type == "multiplicative":
110+
res.update(
111+
{
112+
"discount1": self.discount1,
113+
"discount2": self.discount2,
114+
"discount3": self.discount3,
115+
}
116+
)
117+
else:
118+
res.update({"discount1": self.discount})
94119
return res
95-
96-
def triple_discount_preprocess(self):
97-
"""Prepare data for post processing.
98-
99-
Save the values of the discounts in a dictionary,
100-
to be restored in postprocess.
101-
Resetting every discount except the main one to 0.0 avoids issues if
102-
this method is called multiple times.
103-
Updating the cache provides consistency through re-computations."""
104-
prev_values = dict()
105-
self.invalidate_cache(fnames=self._discount_fields(), ids=self.ids)
106-
for line in self:
107-
prev_values[line] = {
108-
fname: line[fname] for fname in self._discount_fields()
109-
}
110-
111-
vals = {fname: 0 for fname in self._discount_fields()}
112-
vals["discount"] = line._get_final_discount()
113-
114-
line._cache.update(vals)
115-
return prev_values
116-
117-
@api.model
118-
def triple_discount_postprocess(self, prev_values):
119-
"""Restore the discounts of the lines in the dictionary prev_values.
120-
Updating the cache provides consistency through re-computations."""
121-
self.invalidate_cache(
122-
fnames=self._discount_fields(),
123-
ids=[line.id for line in list(prev_values.keys())],
124-
)
125-
for line, prev_vals_dict in list(prev_values.items()):
126-
line.update(prev_vals_dict)
127-
128-
def _convert_to_tax_base_line_dict(self):
129-
self.ensure_one()
130-
return self.env["account.tax"]._convert_to_tax_base_line_dict(
131-
self,
132-
partner=self.order_id.partner_id,
133-
currency=self.order_id.currency_id,
134-
product=self.product_id,
135-
taxes=self.tax_id,
136-
price_unit=self.price_unit,
137-
quantity=self.product_uom_qty,
138-
discount=self._get_final_discount(),
139-
price_subtotal=self.price_subtotal,
140-
)

sale_triple_discount/tests/test_sale_triple_discount.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def setUpClass(cls):
5151

5252
def test_01_sale_order_classic_discount(self):
5353
"""Tests with single discount"""
54-
self.so_line1.discount = 50.0
55-
self.so_line2.discount = 75.0
54+
self.so_line1.discount1 = 50.0
55+
self.so_line2.discount1 = 75.0
5656
self.assertAlmostEqual(self.so_line1.price_subtotal, 300.0)
5757
self.assertAlmostEqual(self.so_line2.price_subtotal, 150.0)
5858
self.assertAlmostEqual(self.order.amount_untaxed, 450.0)
@@ -65,19 +65,22 @@ def test_02_sale_order_simple_triple_discount(self):
6565
"""Tests on a single line"""
6666
self.so_line2.unlink()
6767
# Divide by two on every discount:
68-
self.so_line1.discount = 50.0
68+
self.so_line1.discount1 = 50.0
6969
self.so_line1.discount2 = 50.0
7070
self.so_line1.discount3 = 50.0
71+
self.assertAlmostEqual(self.so_line1.discount, 87.5)
7172
self.assertAlmostEqual(self.so_line1.price_subtotal, 75.0)
7273
self.assertAlmostEqual(self.order.amount_untaxed, 75.0)
7374
self.assertAlmostEqual(self.order.amount_tax, 11.25)
7475
# Unset first discount:
75-
self.so_line1.discount = 0.0
76+
self.so_line1.discount1 = 0.0
77+
self.assertAlmostEqual(self.so_line1.discount, 75)
7678
self.assertAlmostEqual(self.so_line1.price_subtotal, 150.0)
7779
self.assertAlmostEqual(self.order.amount_untaxed, 150.0)
7880
self.assertAlmostEqual(self.order.amount_tax, 22.5)
7981
# Set a charge instead:
8082
self.so_line1.discount2 = -50.0
83+
self.assertAlmostEqual(self.so_line1.discount, 25)
8184
self.assertAlmostEqual(self.so_line1.price_subtotal, 450.0)
8285
self.assertAlmostEqual(self.order.amount_untaxed, 450.0)
8386
self.assertAlmostEqual(self.order.amount_tax, 67.5)
@@ -88,10 +91,11 @@ def test_02_sale_order_simple_triple_discount(self):
8891
67.5,
8992
)
9093
# set discount_type to additive
91-
self.so_line1.discount = 10.0
94+
self.so_line1.discount1 = 10.0
9295
self.so_line1.discount2 = 10.0
9396
self.so_line1.discount3 = 10.0
9497
self.so_line1.discounting_type = "additive"
98+
self.assertAlmostEqual(self.so_line1.discount, 30.0)
9599
self.assertAlmostEqual(self.so_line1.price_subtotal, 420.0)
96100
self.assertAlmostEqual(self.order.amount_untaxed, 420.0)
97101
self.assertAlmostEqual(self.order.amount_tax, 63.0)
@@ -102,47 +106,54 @@ def test_02_sale_order_simple_triple_discount(self):
102106
63.0,
103107
)
104108
# set discount over 100%
105-
self.so_line1.discount = 30.0
109+
self.so_line1.discount1 = 30.0
106110
self.so_line1.discount2 = 70.0
107111
self.so_line1.discount3 = 50.0
112+
self.assertAlmostEqual(self.so_line1.discount, 100.0)
108113
self.assertAlmostEqual(self.so_line1.price_subtotal, 0.0)
109114
self.assertAlmostEqual(self.order.amount_untaxed, 0.0)
110115
self.assertAlmostEqual(self.order.amount_tax, 0.0)
111116
# set discount_type to multiplicative
112-
self.so_line1.discount = 50.0
117+
self.so_line1.discount1 = 50.0
113118
self.so_line1.discount2 = 50.0
114119
self.so_line1.discount3 = 50.0
115120
self.so_line1.discounting_type = "multiplicative"
121+
self.assertAlmostEqual(self.so_line1.discount, 87.5)
116122
self.assertAlmostEqual(self.so_line1.price_subtotal, 75.0)
117123
self.assertAlmostEqual(self.order.amount_untaxed, 75.0)
118124
self.assertAlmostEqual(self.order.amount_tax, 11.25)
119125

120126
def test_03_sale_order_complex_triple_discount(self):
121127
"""Tests on multiple lines"""
122-
self.so_line1.discount = 50.0
128+
self.so_line1.discount1 = 50.0
123129
self.so_line1.discount2 = 50.0
124130
self.so_line1.discount3 = 50.0
131+
self.assertAlmostEqual(self.so_line1.discount, 87.5)
125132
self.assertAlmostEqual(self.so_line1.price_subtotal, 75.0)
126133
self.assertAlmostEqual(self.order.amount_untaxed, 675.0)
127134
self.assertAlmostEqual(self.order.amount_tax, 101.25)
128135
# additive discount
129136
self.so_line2.discount3 = 50.0
137+
self.assertAlmostEqual(self.so_line2.discount, 50.0)
130138
self.assertAlmostEqual(self.so_line2.price_subtotal, 300.0)
131139
self.assertAlmostEqual(self.order.amount_untaxed, 375.0)
132140
self.assertAlmostEqual(self.order.amount_tax, 56.25)
133141
self.so_line2.discounting_type = "additive"
134142
self.so_line2.discount2 = 10.0
143+
self.assertAlmostEqual(self.so_line2.discount, 60.0)
135144
self.assertAlmostEqual(self.so_line2.price_subtotal, 240.0)
136145
self.assertAlmostEqual(self.order.amount_untaxed, 315.0)
137146
self.assertAlmostEqual(self.order.amount_tax, 47.25)
138147
# multiplicative discount
139148
self.so_line2.discount2 = 0.0
140149
self.so_line2.discount3 = 50.0
150+
self.assertAlmostEqual(self.so_line2.discount, 50.0)
141151
self.assertAlmostEqual(self.so_line2.price_subtotal, 300.0)
142152
self.assertAlmostEqual(self.order.amount_untaxed, 375.0)
143153
self.assertAlmostEqual(self.order.amount_tax, 56.25)
144154
self.so_line2.discounting_type = "multiplicative"
145155
self.so_line2.discount2 = 10.0
156+
self.assertAlmostEqual(self.so_line1.discount, 60.0)
146157
self.assertAlmostEqual(self.so_line2.price_subtotal, 270.0)
147158
self.assertAlmostEqual(self.order.amount_untaxed, 345.0)
148159
self.assertAlmostEqual(self.order.amount_tax, 51.75)
@@ -155,6 +166,7 @@ def test_04_sale_order_triple_discount_invoicing(self):
155166
self.so_line1.discount3 = 50.0
156167
self.so_line2.discount3 = 50.0
157168
self.order.action_confirm()
169+
# FIXME: AFAIK this is another module messing and shouldn't be in this test
158170
if self.order.state == "waiting_approval":
159171
self.order.action_approve()
160172
self.assertAlmostEqual(self.order.state, "approved")
@@ -164,6 +176,9 @@ def test_04_sale_order_triple_discount_invoicing(self):
164176
self.assertAlmostEqual(
165177
self.so_line1.discount, invoice.invoice_line_ids[0].discount
166178
)
179+
self.assertAlmostEqual(
180+
self.so_line1.discount1, invoice.invoice_line_ids[0].discount1
181+
)
167182
self.assertAlmostEqual(
168183
self.so_line1.discount2, invoice.invoice_line_ids[0].discount2
169184
)
@@ -173,6 +188,9 @@ def test_04_sale_order_triple_discount_invoicing(self):
173188
self.assertAlmostEqual(
174189
self.so_line1.price_subtotal, invoice.invoice_line_ids[0].price_subtotal
175190
)
191+
self.assertAlmostEqual(
192+
self.so_line2.discount, invoice.invoice_line_ids[1].discount
193+
)
176194
self.assertAlmostEqual(
177195
self.so_line2.discount3, invoice.invoice_line_ids[1].discount3
178196
)
@@ -184,7 +202,7 @@ def test_04_sale_order_triple_discount_invoicing(self):
184202
def test_05_round_globally(self):
185203
"""Tests on multiple lines when 'round_globally' is active"""
186204
self.env.user.company_id.tax_calculation_rounding_method = "round_globally"
187-
self.so_line1.discount = 50.0
205+
self.so_line1.discount1 = 50.0
188206
self.so_line1.discount2 = 50.0
189207
self.so_line1.discount3 = 50.0
190208
self.assertEqual(self.so_line1.price_subtotal, 75.0)
@@ -197,11 +215,11 @@ def test_05_round_globally(self):
197215

198216
def test_06_discount_0(self):
199217
self.so_line1.discounting_type = "additive"
200-
self.so_line1.discount = 0.0
218+
self.so_line1.discount1 = 0.0
201219
self.so_line1.discount2 = 0.0
202220
self.so_line1.discount3 = 0.0
203221
self.so_line2.discounting_type = "additive"
204-
self.so_line2.discount = 0.0
222+
self.so_line2.discount1 = 0.0
205223
self.so_line2.discount2 = 0.0
206224
self.so_line2.discount3 = 0.0
207225
self.assertAlmostEqual(self.so_line1.price_subtotal, 600.0)

0 commit comments

Comments
 (0)