Skip to content

Commit

Permalink
[MIG] sale_global_discount: 16.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ferran-S73 committed Jun 20, 2024
1 parent a6e38a2 commit 8e464a8
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 94 deletions.
2 changes: 1 addition & 1 deletion sale_global_discount/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Sale Global Discount",
"version": "15.0.1.0.0",
"version": "16.0.1.0.0",
"category": "Sales Management",
"author": "Tecnativa," "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/sale-workflow",
Expand Down
2 changes: 2 additions & 0 deletions sale_global_discount/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import account_tax
from . import sale_order_line
from . import sale_order
73 changes: 73 additions & 0 deletions sale_global_discount/models/account_tax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 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 _convert_to_tax_base_line_dict(
self,
base_line,
partner=None,
currency=None,
product=None,
taxes=None,
price_unit=None,
quantity=None,
discount=None,
account=None,
analytic_distribution=None,
price_subtotal=None,
is_refund=False,
rate=None,
handle_price_include=True,
extra_context=None,
):
if (
not isinstance(base_line, models.Model)
or base_line._name != "sale.order.line"
or not base_line.order_id.global_discount_ids
or not self.env.context.get("from_tax_calculation", False)
):
return super()._convert_to_tax_base_line_dict(
base_line,
partner,
currency,
product,
taxes,
price_unit,
quantity,
discount,
account,
analytic_distribution,
price_subtotal,
is_refund,
rate,
handle_price_include,
extra_context,
)
discounts = base_line.order_id.global_discount_ids.mapped("discount")
discounted_price_unit = price_unit
if base_line.product_id.apply_global_discount:
discounted_price_unit = base_line.order_id.get_discounted_global(
price_unit, discounts.copy()
)
return super()._convert_to_tax_base_line_dict(
base_line,
partner,
currency,
product,
taxes,
discounted_price_unit,
quantity,
discount,
account,
analytic_distribution,
price_subtotal,
is_refund,
rate,
handle_price_include,
extra_context,
)
64 changes: 20 additions & 44 deletions sale_global_discount/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright 2020 Tecnativa - David Vidal
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import json

from odoo import _, api, exceptions, fields, models

Expand All @@ -27,23 +26,23 @@ class SaleOrder(models.Model):
)
amount_global_discount = fields.Monetary(
string="Total Global Discounts",
compute="_amount_all", # pylint: disable=C8108
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="_amount_all", # pylint: disable=C8108
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="_amount_all", # pylint: disable=C8108
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,
Expand All @@ -68,7 +67,9 @@ def _check_global_discounts_sanity(self):
if not self.global_discount_ids:
return True
taxes_keys = {}
for line in self.order_line.filtered(lambda l: not l.display_type):
for line in self.order_line.filtered(
lambda l: not l.display_type and l.product_id
):
if not line.tax_id:
raise exceptions.UserError(
_("With global discounts, taxes in lines are required.")
Expand All @@ -83,9 +84,14 @@ def _check_global_discounts_sanity(self):
else:
taxes_keys[line.tax_id] = True

@api.depends("order_line.price_total", "global_discount_ids")
def _amount_all(self):
res = super()._amount_all()
@api.depends(
"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
Expand Down Expand Up @@ -126,15 +132,18 @@ def _amount_all(self):
)
return res

def _compute_tax_totals(self):
return super(
SaleOrder, self.with_context(from_tax_calculation=True)
)._compute_tax_totals()

@api.onchange("partner_id")
def onchange_partner_id(self):
res = super().onchange_partner_id()
def onchange_partner_id_set_gbl_disc(self):
self.global_discount_ids = self.partner_id.customer_global_discount_ids.filtered(
lambda d: d.company_id == self.company_id
) or self.partner_id.commercial_partner_id.customer_global_discount_ids.filtered(
lambda d: d.company_id == self.company_id
)
return res

def _prepare_invoice(self):
invoice_vals = super()._prepare_invoice()
Expand All @@ -143,36 +152,3 @@ def _prepare_invoice(self):
{"global_discount_ids": [(6, 0, self.global_discount_ids.ids)]}
)
return invoice_vals

def _compute_tax_totals_json(self):
"""OVERRIDEN: add global discount in the tax calculation."""
super()._compute_tax_totals_json()

def compute_taxes(order_line):
price = order_line.price_unit * (1 - (order_line.discount or 0.0) / 100.0)
discounts = order_line.order_id.global_discount_ids.mapped("discount")
price = self.get_discounted_global(price, discounts.copy())
order = order_line.order_id
return order_line.tax_id._origin.compute_all(
price,
order.currency_id,
order_line.product_uom_qty,
product=order_line.product_id,
partner=order.partner_shipping_id,
)

account_move = self.env["account.move"]
for order in self.filtered("global_discount_ids"):
tax_lines_data = (
account_move._prepare_tax_lines_data_for_totals_from_object(
order.order_line, compute_taxes
)
)
tax_totals = account_move._get_tax_totals(
order.partner_id,
tax_lines_data,
order.amount_total,
order.amount_untaxed,
order.currency_id,
)
order.tax_totals_json = json.dumps(tax_totals)
14 changes: 14 additions & 0 deletions sale_global_discount/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2023 Studio73 - Ethan Hildick <[email protected]>
# 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()
74 changes: 31 additions & 43 deletions sale_global_discount/tests/test_sale_global_discount.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
# Copyright 2020 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json

from odoo import exceptions
from odoo.tests import Form, common
from odoo.tests import Form, tagged

from odoo.addons.account.tests.common import AccountTestInvoicingCommon

class TestSaleGlobalDiscount(common.TransactionCase):

@tagged("post_install", "-at_install")
class TestSaleGlobalDiscount(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.main_company = cls.env.ref("base.main_company")
cls.account_type = cls.env["account.account.type"].create(
{"name": "Test", "type": "other", "internal_group": "asset"}
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.env.ref("base_global_discount.group_global_discount").write(
{"users": [(4, cls.env.user.id)]}
)
cls.main_company = cls.env.ref("base.main_company")
cls.account = cls.env["account.account"].create(
{
"name": "Test account Global Discount",
"code": "TEST99999",
"user_type_id": cls.account_type.id,
"account_type": "asset_current",
"reconcile": True,
}
)
Expand Down Expand Up @@ -50,13 +52,7 @@ def setUpClass(cls):
"account_id": cls.account.id,
}
)
cls.pricelist = cls.env["product.pricelist"].create(
{
"name": "Test",
"currency_id": cls.main_company.currency_id.id,
"company_id": cls.main_company.id,
}
)
cls.pricelist = cls.env.ref("product.list0")
cls.partner_1 = cls.env["res.partner"].create(
{"name": "Mr. Odoo", "property_product_pricelist": cls.pricelist.id}
)
Expand All @@ -78,24 +74,10 @@ def setUpClass(cls):
cls.tax_group_15pc = cls.env["account.tax.group"].create(
{"name": "Test Tax Group 15%", "sequence": 2}
)
cls.tax_1 = cls.env["account.tax"].create(
{
"name": "Test TAX 15%",
"amount_type": "percent",
"type_tax_use": "sale",
"tax_group_id": cls.tax_group_15pc.id,
"amount": 15.0,
}
)
cls.tax_2 = cls.env["account.tax"].create(
{
"name": "TAX 5%",
"amount_type": "percent",
"tax_group_id": cls.tax_group_5pc.id,
"type_tax_use": "sale",
"amount": 5.0,
}
)
cls.tax_1 = cls.tax_sale_a
cls.tax_1.amount = 15.0
cls.tax_2 = cls.tax_sale_b
cls.tax_2.amount = 5.0
cls.sale_journal0 = cls.env["account.journal"].create(
{
"name": "Sale Journal",
Expand Down Expand Up @@ -124,9 +106,7 @@ def setUpClass(cls):
def get_taxes_widget_total_tax(self, order):
return sum(
tax_vals["tax_group_amount"]
for tax_vals in json.loads(order.tax_totals_json)["groups_by_subtotal"][
"Untaxed Amount"
]
for tax_vals in order.tax_totals["groups_by_subtotal"]["Untaxed Amount"]
)

def test_01_global_sale_succesive_discounts(self):
Expand Down Expand Up @@ -177,7 +157,7 @@ def test_02_global_sale_discounts_from_partner(self):
"""Change the partner and his global discounts go to the invoice"""
# (30% then 50%)
self.sale.partner_id = self.partner_2
self.sale.onchange_partner_id()
self.sale.onchange_partner_id_set_gbl_disc()
self.assertAlmostEqual(self.sale.amount_global_discount, 162.49)
self.assertAlmostEqual(self.sale.amount_untaxed, 87.5)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 249.99)
Expand All @@ -191,7 +171,7 @@ def test_02_global_sale_discounts_from_partner(self):
def test_03_global_sale_discounts_to_invoice(self):
"""All the discounts go to the invoice"""
self.sale.partner_id = self.partner_2
self.sale.onchange_partner_id()
self.sale.onchange_partner_id_set_gbl_disc()
self.sale.order_line.mapped("product_id").write({"invoice_policy": "order"})
self.sale.action_confirm()
move = self.sale._create_invoices()
Expand Down Expand Up @@ -220,10 +200,10 @@ def test_03_global_sale_discounts_to_invoice(self):
self.assertAlmostEqual(line_tax_1.credit, 13.13)
self.assertAlmostEqual(line_tax_2.credit, 4.38)
term_line = move.line_ids.filtered(
lambda x: x.account_id.user_type_id.type == "receivable"
lambda x: x.account_id.account_type == "asset_receivable"
)
self.assertAlmostEqual(term_line.debit, 105.01)
discount_lines = move.line_ids.filtered("global_discount_item")
discount_lines = move.line_ids.filtered("invoice_global_discount_id")
self.assertEqual(len(discount_lines), 2)
self.assertAlmostEqual(sum(discount_lines.mapped("debit")), 162.49)

Expand All @@ -233,10 +213,18 @@ def test_04_incompatible_taxes(self):
self.sale.order_line[1].tax_id = [(6, 0, self.tax_1.ids)]
with self.assertRaises(exceptions.UserError):
self.sale.global_discount_ids = self.global_discount_1
self.sale._amount_all()
self.sale._compute_amounts()

def test_05_no_taxes(self):
self.sale.order_line[1].tax_id = False
with self.assertRaises(exceptions.UserError):
self.sale.global_discount_ids = self.global_discount_1
self.sale._amount_all()
self.sale._compute_amounts()

def test_06_discounted_line(self):
self.sale.global_discount_ids = self.global_discount_1
line = self.sale.order_line[0]
line.discount = 10
self.assertAlmostEqual(line.price_subtotal, 135)
self.assertAlmostEqual(self.sale.amount_untaxed_before_global_discounts, 234.99)
self.assertAlmostEqual(self.sale.amount_untaxed, 187.99)
4 changes: 2 additions & 2 deletions sale_global_discount/views/report_sale_order.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<td>
<strong>Subtl. before disc.</strong>
</td>
<td class="text-right">
<td class="text-end">
<span
t-field="doc.amount_untaxed_before_global_discounts"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
Expand All @@ -25,7 +25,7 @@
t-esc="'→'.join(['{:.2f}%'.format(x.discount) for x in doc.global_discount_ids])"
/>
</td>
<td class="text-right">
<td class="text-end">
<span
t-field="doc.amount_global_discount"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
Expand Down
Loading

0 comments on commit 8e464a8

Please sign in to comment.