obook/sale_global_discount/models/sale_order.py

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