166 lines
6.5 KiB
Python
166 lines
6.5 KiB
Python
# Copyright 2020 Tecnativa - David Vidal
|
|
# Copyright 2020 Tecnativa - Pedro M. Baeza
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
from functools import partial
|
|
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)]",
|
|
)
|
|
# 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='_amount_all',
|
|
currency_field='currency_id',
|
|
readonly=True,
|
|
)
|
|
amount_untaxed_before_global_discounts = fields.Monetary(
|
|
string='Amount Untaxed Before Discounts',
|
|
compute='_amount_all',
|
|
currency_field='currency_id',
|
|
readonly=True,
|
|
)
|
|
amount_total_before_global_discounts = fields.Monetary(
|
|
string='Amount Total Before Discounts',
|
|
compute='_amount_all',
|
|
currency_field='currency_id',
|
|
readonly=True,
|
|
)
|
|
|
|
@api.model
|
|
def get_discounted_global(self, price=0, discounts=None):
|
|
"""Compute discounts successively"""
|
|
discounts = discounts or []
|
|
if not discounts:
|
|
return price
|
|
discount = discounts.pop(0)
|
|
price *= 1 - (discount / 100)
|
|
return self.get_discounted_global(price, discounts)
|
|
|
|
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 l: not l.display_type):
|
|
if not line.tax_id:
|
|
raise exceptions.UserError(_(
|
|
"With global discounts, taxes in lines are required."
|
|
))
|
|
for key in taxes_keys:
|
|
if key == line.tax_id:
|
|
break
|
|
elif key & line.tax_id:
|
|
raise exceptions.UserError(_(
|
|
"Incompatible taxes found for global discounts."
|
|
))
|
|
else:
|
|
taxes_keys[line.tax_id] = True
|
|
|
|
@api.depends('order_line.price_total', 'global_discount_ids')
|
|
def _amount_all(self):
|
|
res = super()._amount_all()
|
|
for order in self:
|
|
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 = self.get_discounted_global(
|
|
line.price_subtotal, discounts.copy())
|
|
amount_discounted_untaxed += discounted_subtotal
|
|
discounted_tax = line.tax_id.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
|
|
|
|
@api.onchange('partner_id')
|
|
def onchange_partner_id(self):
|
|
res = super().onchange_partner_id()
|
|
self.global_discount_ids = (
|
|
self.partner_id.customer_global_discount_ids or
|
|
self.partner_id.commercial_partner_id
|
|
.customer_global_discount_ids)
|
|
return res
|
|
|
|
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
|
|
|
|
def action_invoice_create(self, grouped=False, final=False):
|
|
res = super().action_invoice_create(grouped=grouped, final=final)
|
|
invoices = self.env['account.invoice'].browse(res)
|
|
invoices._set_global_discounts()
|
|
return res
|
|
|
|
def _amount_by_group(self):
|
|
"""We can apply discounts directly by tax groups."""
|
|
super()._amount_by_group()
|
|
discounts = self.global_discount_ids.mapped('discount')
|
|
if not discounts:
|
|
return
|
|
for order in self:
|
|
round_curr = order.currency_id.round
|
|
fmt = partial(
|
|
formatLang, self.with_context(lang=order.partner_id.lang).env,
|
|
currency_obj=order.currency_id
|
|
)
|
|
res = []
|
|
for tax in order.amount_by_group:
|
|
tax_amount = round_curr(
|
|
self.get_discounted_global(tax[1], discounts.copy()))
|
|
tax_base = round_curr(
|
|
self.get_discounted_global(tax[2], discounts.copy()))
|
|
res.append(
|
|
(
|
|
tax[0],
|
|
tax_amount,
|
|
tax_base,
|
|
fmt(tax_amount),
|
|
fmt(tax_base),
|
|
len(order.amount_by_group)
|
|
)
|
|
)
|
|
order.amount_by_group = res
|