forked from OCA/sale-workflow
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsale_order.py
More file actions
253 lines (243 loc) · 10.5 KB
/
sale_order.py
File metadata and controls
253 lines (243 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# Copyright 2020 Tecnativa - David Vidal
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, exceptions, fields, models
from odoo.tools.misc import formatLang
class SaleOrder(models.Model):
_inherit = "sale.order"
global_discount_ids = fields.Many2many(
comodel_name="global.discount",
string="Sale Global Discounts",
domain="[('discount_scope', '=', 'sale'), "
"('account_id', '!=', False), '|', "
"('company_id', '=', company_id), ('company_id', '=', False)]",
compute="_compute_global_discount_ids",
store=True,
readonly=False,
)
# HACK: Looks like UI doesn't behave well with Many2many fields and
# negative groups when the same field is shown. In this case, we want to
# show the readonly version to any not in the global discount group.
# TODO: Check if it's fixed in future versions
global_discount_ids_readonly = fields.Many2many(
related="global_discount_ids",
string="Sale Global Discounts (readonly)",
readonly=True,
)
amount_global_discount = fields.Monetary(
string="Total Global Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
amount_untaxed_before_global_discounts = fields.Monetary(
string="Amount Untaxed Before Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
amount_total_before_global_discounts = fields.Monetary(
string="Amount Total Before Discounts",
compute="_compute_amounts", # pylint: disable=C8108
currency_field="currency_id",
compute_sudo=True, # Odoo core fields are storable so compute_sudo is True
readonly=True,
store=True,
)
@api.model
def get_discounted_global(self, price=0, discounts=None):
if not discounts:
return price
discounted_price = price
for discount in discounts:
discounted_price *= 1 - (discount / 100)
return discounted_price
def _check_global_discounts_sanity(self):
"""Perform a sanity check for discarding cases that will lead to
incorrect data in discounts.
"""
self.ensure_one()
if not self.global_discount_ids:
return True
taxes_keys = {}
for line in self.order_line.filtered(
lambda _line: not _line.display_type
and _line.product_id
and not _line.product_id.bypass_global_discount
):
if not line.tax_ids:
raise exceptions.UserError(
self.env._("With global discounts, taxes in lines are required.")
)
for key in taxes_keys:
if key == line.tax_ids:
break
elif key & line.tax_ids:
raise exceptions.UserError(
self.env._("Incompatible taxes found for global discounts.")
)
else:
taxes_keys[line.tax_ids] = True
@api.depends(
"order_line.product_id.bypass_global_discount",
"order_line.price_subtotal",
"order_line.price_tax",
"order_line.price_total",
"global_discount_ids",
)
def _compute_amounts(self):
res = super()._compute_amounts()
for order in self:
if not order.global_discount_ids:
order.amount_untaxed_before_global_discounts = order.amount_untaxed
order.amount_total_before_global_discounts = order.amount_total
order.amount_global_discount = 0.0
continue
order._check_global_discounts_sanity()
amount_untaxed_before_global_discounts = order.amount_untaxed
amount_total_before_global_discounts = order.amount_total
discounts = order.global_discount_ids.mapped("discount")
amount_discounted_untaxed = amount_discounted_tax = 0
for line in order.order_line:
discounted_subtotal = line.price_subtotal
if not line.product_id.bypass_global_discount:
discounted_subtotal = self.get_discounted_global(
line.price_subtotal, discounts.copy()
)
amount_discounted_untaxed += discounted_subtotal
discounted_tax = line.tax_ids.with_context(
force_price_include=False
).compute_all(
discounted_subtotal,
line.order_id.currency_id,
1.0,
product=line.product_id,
partner=line.order_id.partner_shipping_id,
)
amount_discounted_tax += sum(
t.get("amount", 0.0) for t in discounted_tax.get("taxes", [])
)
order.update(
{
"amount_untaxed_before_global_discounts": (
amount_untaxed_before_global_discounts
),
"amount_total_before_global_discounts": (
amount_total_before_global_discounts
),
"amount_global_discount": (
amount_untaxed_before_global_discounts
- amount_discounted_untaxed
),
"amount_untaxed": amount_discounted_untaxed,
"amount_tax": amount_discounted_tax,
"amount_total": (amount_discounted_untaxed + amount_discounted_tax),
}
)
return res
def _compute_tax_totals(self):
res = super()._compute_tax_totals()
for order in self:
amount_discount_by_group = {}
cumulative_discount_rate = 1.0
currency = order.currency_id
# Calculate cumulative discount rate
for gbl_disc in order.global_discount_ids:
discount_rate = gbl_disc.discount / 100
cumulative_discount_rate *= 1 - discount_rate
base_amount = 0.0
# Calculate the total discount amount and discount by tax group
for line in order.order_line:
if line.display_type or not line.product_id:
continue
# Apply cumulative discount rate only if bypass_global_discount is False
if not line.product_id.bypass_global_discount:
discounted_price_subtotal = (
line.price_subtotal * cumulative_discount_rate
)
else:
discounted_price_subtotal = line.price_subtotal
base_amount += discounted_price_subtotal
# Calculate tax amounts for each tax group based on the discounted
# subtotal
for tax in line.tax_ids:
tax_group_id = tax.tax_group_id.id
if tax_group_id not in amount_discount_by_group:
amount_discount_by_group[tax_group_id] = 0.0
# Compute taxes on the correct base amount
discounted_tax_vals = tax.with_context(
force_price_include=False
).compute_all(
discounted_price_subtotal,
currency,
1.0,
product=line.product_id,
partner=order.partner_shipping_id,
)
amount_discount_by_group[tax_group_id] += sum(
t.get("amount", 0.0)
for t in discounted_tax_vals.get("taxes", [])
)
# Calculate the final amount total
total_amount = base_amount + sum(amount_discount_by_group.values())
base_amount_currency = formatLang(
self.env, base_amount, currency_obj=currency
)
order.tax_totals.update(
{
"base_amount": base_amount,
"total_amount": total_amount,
"base_amount_currency": base_amount,
"total_amount_currency": total_amount,
}
)
# Update subtotals and groups by subtotal
for group in order.tax_totals["subtotals"]:
group.update(
{
"base_amount": base_amount,
"base_amount_currency": base_amount,
"amount": base_amount,
"formatted_amount": base_amount_currency,
}
)
for tax_group in group["tax_groups"]:
discounted_tax_amount = amount_discount_by_group.get(
tax_group["id"], 0.0
)
tax_group.update(
{
"base_amount": base_amount,
"base_amount_currency": base_amount,
"tax_amount": discounted_tax_amount,
"tax_amount_currency": discounted_tax_amount,
}
)
return res
@api.depends("partner_id", "company_id")
def _compute_global_discount_ids(self):
for order in self:
commercial_global_disc = (
order.partner_id.commercial_partner_id.customer_global_discount_ids
)
_discounts = (
commercial_global_disc
if commercial_global_disc
else order.partner_id.customer_global_discount_ids
)
discounts = self.env["global.discount"]
for discount in _discounts:
if discount.company_id == order.company_id:
discounts |= discount
order.global_discount_ids = discounts
def _prepare_invoice(self):
invoice_vals = super()._prepare_invoice()
if self.global_discount_ids:
invoice_vals.update(
{"global_discount_ids": [(6, 0, self.global_discount_ids.ids)]}
)
return invoice_vals