Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
3
sale_global_discount/models/__init__.py
Executable file
3
sale_global_discount/models/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
||||
from . import account_tax
|
||||
from . import sale_order_line
|
||||
from . import sale_order
|
||||
27
sale_global_discount/models/account_tax.py
Executable file
27
sale_global_discount/models/account_tax.py
Executable file
@@ -0,0 +1,27 @@
|
||||
# Copyright 2023 Studio73 - Ferran Mora
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_inherit = "account.tax"
|
||||
|
||||
def _prepare_base_line_for_taxes_computation(self, record, **kwargs):
|
||||
if (
|
||||
not isinstance(record, models.Model)
|
||||
or record._name != "sale.order.line"
|
||||
or not record.order_id.global_discount_ids
|
||||
or not self.env.context.get("from_tax_calculation", False)
|
||||
):
|
||||
return super()._prepare_base_line_for_taxes_computation(record, **kwargs)
|
||||
|
||||
price_unit = kwargs.get("price_unit", 0.0)
|
||||
discounted_price_unit = price_unit
|
||||
if not record.product_id.bypass_global_discount:
|
||||
discounts = record.order_id.global_discount_ids.mapped("discount")
|
||||
discounted_price_unit = record.order_id.get_discounted_global(
|
||||
price_unit, discounts.copy()
|
||||
)
|
||||
kwargs.update({"price_unit": discounted_price_unit})
|
||||
return super()._prepare_base_line_for_taxes_computation(record, **kwargs)
|
||||
248
sale_global_discount/models/sale_order.py
Executable file
248
sale_global_discount/models/sale_order.py
Executable file
@@ -0,0 +1,248 @@
|
||||
# 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_id:
|
||||
raise exceptions.UserError(
|
||||
self.env._("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(
|
||||
self.env._("Incompatible taxes found for global discounts.")
|
||||
)
|
||||
else:
|
||||
taxes_keys[line.tax_id] = 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:
|
||||
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_id.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_id:
|
||||
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
|
||||
14
sale_global_discount/models/sale_order_line.py
Executable file
14
sale_global_discount/models/sale_order_line.py
Executable file
@@ -0,0 +1,14 @@
|
||||
# Copyright 2023 Studio73 - Ethan Hildick <ethan@studio73.es>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
@api.depends("product_uom_qty", "discount", "price_unit", "tax_id")
|
||||
def _compute_amount(self):
|
||||
return super(
|
||||
SaleOrderLine, self.with_context(from_tax_calculation=False)
|
||||
)._compute_amount()
|
||||
Reference in New Issue
Block a user