# Copyright 2019 Tecnativa - David Vidal # Copyright 2020 Tecnativa - Pedro M. Baeza # Copyright 2021 Tecnativa - Víctor Martínez # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import exceptions from odoo.tests import Form, tagged from odoo.addons.account.tests.common import AccountTestInvoicingCommon @tagged("post_install", "-at_install") class TestGlobalDiscount(AccountTestInvoicingCommon): @classmethod def setUpClass(cls, chart_template_ref=None): super().setUpClass() cls.env = cls.env( context=dict(cls.env.context, test_account_global_discount=True) ) cls.env.ref("base_global_discount.group_global_discount").write( {"users": [(4, cls.env.user.id)]} ) cls.account = cls.company_data["default_account_revenue"] cls.global_discount_obj = cls.env["global.discount"] cls.global_discount_1 = cls.global_discount_obj.create( { "name": "Test Discount 1", "discount_scope": "sale", "discount": 20, "account_id": cls.account.id, "sequence": 3, } ) cls.global_discount_2 = cls.global_discount_obj.create( { "name": "Test Discount 2", "discount_scope": "purchase", "discount": 30, "account_id": cls.account.id, "sequence": 2, } ) cls.global_discount_3 = cls.global_discount_obj.create( { "name": "Test Discount 3", "discount_scope": "purchase", "discount": 50, "account_id": cls.account.id, "sequence": 1, } ) cls.partner_1 = cls.env["res.partner"].create( { "name": "Mr. Odoo", } ) cls.partner_2 = cls.env["res.partner"].create( { "name": "Mrs. Odoo", } ) cls.partner_2.supplier_global_discount_ids = cls.global_discount_2 cls.tax = cls.tax_purchase_a cls.tax.amount = 15 cls.tax_0 = cls.tax_purchase_b cls.tax_0.amount = 0 cls.product_3 = cls.env["product.product"].create( { "name": "Test Product 3", "type": "service", "bypass_global_discount": True, } ) cls.invoice_line = cls.env["account.move.line"] cls.invoice = ( cls.env["account.move"] .with_context(default_move_type="in_invoice") .create( { "partner_id": cls.partner_1.id, "ref": "Test global discount", "invoice_line_ids": [ ( 0, 0, { "name": "Line 1", "price_unit": 200.0, "quantity": 1, "tax_ids": [(6, 0, [cls.tax.id])], }, ), ( 0, 0, { "name": "Line 2", "product_id": cls.product_3.id, "price_unit": 200.0, "quantity": 1, "tax_ids": [(6, 0, [cls.tax_0.id])], }, ), ], } ) ) def test_01_global_invoice_succesive_discounts(self): """Add global discounts to the invoice""" invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(self.invoice.amount_total, 430) self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 200.0) self.assertAlmostEqual(invoice_tax_line.balance, 30.0) # Global discounts are applied to the base and taxes are recomputed: # 200 - 50% (global disc. 3) = 100 with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.clear() invoice_form.global_discount_ids.add(self.global_discount_3) self.assertEqual(len(self.invoice.invoice_global_discount_ids), 1) precision = self.env["decimal.precision"].precision_get("Discount") self.assertEqual( self.invoice.invoice_global_discount_ids.discount_display, "-50.{}%".format("0" * precision), ) invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 100.0) self.assertAlmostEqual(invoice_tax_line.balance, 15.0) self.assertAlmostEqual(self.invoice.amount_untaxed, 300.0) self.assertAlmostEqual(self.invoice.amount_total, 315.0) self.assertAlmostEqual(self.invoice.amount_global_discount, -100.0) # Global discounts are computed succecively: # 200 - 50% (global disc. 1) = 100 # 100 - 30% (global disc. 2) = 70 # The global discounts amount is then 200 - 70 = 130 with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_2) self.assertEqual(len(self.invoice.invoice_global_discount_ids), 2) invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 70.0) self.assertAlmostEqual(invoice_tax_line.balance, 10.5) self.assertAlmostEqual(self.invoice.amount_untaxed, 270.0) self.assertAlmostEqual(self.invoice.amount_total, 280.5) self.assertAlmostEqual(self.invoice.amount_global_discount, -130.0) # Line discounts apply before global ones so: # 200 - 20% (line discount) = 160 # 160 - 50% (global disc. 1) = 80 # 80 - 30% (global disc. 2) = 56 # The global discounts amount is then 160 - 56 = 104 with Form(self.invoice) as invoice_form: with invoice_form.invoice_line_ids.edit(0) as line_form: if "discount1" in self.env["account.move.line"]._fields: line_form.discount1 = 20 else: line_form.discount = 20 self.assertEqual(len(self.invoice.invoice_global_discount_ids), 2) invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 56.0) self.assertAlmostEqual(invoice_tax_line.balance, 8.4) self.assertAlmostEqual(self.invoice.amount_untaxed, 256.0) self.assertAlmostEqual(self.invoice.amount_total, 264.4) self.assertAlmostEqual(self.invoice.amount_global_discount, -104.0) def test_02_global_invoice_discounts_from_partner(self): """Change the partner and his global discounts go to the invoice""" invoice_tax_line = self.invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(self.invoice.amount_total, 430) self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 200.0) self.assertAlmostEqual(invoice_tax_line.balance, 30.0) # When we change the parter, his global discounts are fetched depending # on the type of the invoice. In this case, we fetch the supplier # global discounts with Form(self.invoice) as invoice_form: invoice_form.partner_id = self.partner_2 self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 140.0) self.assertAlmostEqual(invoice_tax_line.balance, 21.0) self.assertAlmostEqual(self.invoice.amount_untaxed, 340.0) self.assertAlmostEqual(self.invoice.amount_total, 361.0) self.assertAlmostEqual(self.invoice.amount_global_discount, -60.0) def test_03_multiple_taxes_multi_line(self): tax2 = self.tax.copy(default={"amount": 20.0, "name": "Tax 2"}) with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.new() as line_form: line_form.name = "Line 2" line_form.price_unit = 100.0 line_form.quantity = 1 line_form.tax_ids.clear() line_form.tax_ids.add(tax2) self.assertEqual(len(self.invoice.invoice_global_discount_ids), 2) discount_tax_15 = self.invoice.invoice_global_discount_ids.filtered( lambda x: x.tax_ids == self.tax ) discount_tax_20 = self.invoice.invoice_global_discount_ids.filtered( lambda x: x.tax_ids == tax2 ) self.assertAlmostEqual(discount_tax_15.discount_amount, 40) self.assertAlmostEqual(discount_tax_20.discount_amount, 20) tax_line_15 = self.invoice.line_ids.filtered( lambda x: x.tax_line_id == self.tax ) tax_line_20 = self.invoice.line_ids.filtered(lambda x: x.tax_line_id == tax2) self.assertAlmostEqual(tax_line_15.tax_base_amount, 160) self.assertAlmostEqual(tax_line_15.balance, 24) self.assertAlmostEqual(tax_line_20.tax_base_amount, 80.0) self.assertAlmostEqual(tax_line_20.balance, 16) self.assertAlmostEqual(self.invoice.amount_untaxed, 440.0) self.assertAlmostEqual(self.invoice.amount_total, 480) self.assertAlmostEqual(self.invoice.amount_global_discount, -60.0) # Check journal items validity lines = self.invoice.line_ids line_15 = lines.filtered( lambda x: x.invoice_global_discount_id and x.tax_ids == self.tax ) self.assertAlmostEqual(line_15.credit, 40) line_20 = lines.filtered( lambda x: x.invoice_global_discount_id and x.tax_ids == tax2 ) self.assertAlmostEqual(line_20.credit, 20) def test_04_multiple_taxes_same_line(self): tax2 = self.tax.copy( default={"amount": -20.0, "name": "Tax 2"} ) # negative for testing more use cases with Form(self.invoice.with_context(check_move_validity=False)) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.edit(0) as line_form: line_form.tax_ids.add(tax2) # Global discounts are applied to the base and taxes are recomputed: # 300 - 20% (global disc. 1) = 240 self.assertEqual(len(self.invoice.invoice_global_discount_ids), 1) self.assertAlmostEqual( self.invoice.invoice_global_discount_ids.discount_amount, 40 ) self.assertEqual( self.invoice.invoice_global_discount_ids.tax_ids, self.tax + tax2 ) tax_line_15 = self.invoice.line_ids.filtered( lambda x: x.tax_line_id == self.tax ) tax_line_20 = self.invoice.line_ids.filtered(lambda x: x.tax_line_id == tax2) self.assertAlmostEqual(tax_line_15.tax_base_amount, 160) self.assertAlmostEqual(tax_line_15.balance, 24) self.assertAlmostEqual(tax_line_20.tax_base_amount, 160.0) self.assertAlmostEqual(tax_line_20.balance, -32) self.assertAlmostEqual(self.invoice.amount_untaxed, 360.0) self.assertAlmostEqual(self.invoice.amount_total, 352) self.assertAlmostEqual(self.invoice.amount_global_discount, -40.0) def test_05_incompatible_taxes(self): # Line 1 with tax and tax2 # Line 2 with only tax2 tax2 = self.tax.copy( default={"amount": -20.0, "name": "Tax 2"} ) # negative for testing more use cases with self.assertRaises(exceptions.UserError): with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.new() as line_form: line_form.name = "Line 2" line_form.price_unit = 100.0 line_form.quantity = 1 line_form.tax_ids.clear() line_form.tax_ids.add(self.tax) line_form.tax_ids.add(tax2) def test_06_no_taxes(self): with self.assertRaises(exceptions.UserError): with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.edit(0) as line_form: line_form.tax_ids.clear() def test_07_line_with_tax_0(self): with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.edit(0) as line_form: line_form.tax_ids.clear() line_form.tax_ids.add(self.tax_0) discounts = self.invoice.invoice_global_discount_ids self.assertEqual(len(discounts), 1) self.assertAlmostEqual(discounts.discount_amount, 40) def test_08_line2_with_tax_0(self): with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) with invoice_form.invoice_line_ids.new() as line_form: line_form.name = "Line 2" line_form.price_unit = 100.0 line_form.quantity = 1 line_form.tax_ids.clear() line_form.tax_ids.add(self.tax_0) self.assertEqual(len(self.invoice.invoice_global_discount_ids), 2) discount_tax_15 = self.invoice.invoice_global_discount_ids.filtered( lambda x: x.tax_ids == self.tax ) self.assertAlmostEqual(discount_tax_15.discount_amount, 40) discount_tax_0 = self.invoice.invoice_global_discount_ids.filtered( lambda x: x.tax_ids == self.tax_0 ) self.assertAlmostEqual(discount_tax_0.discount_amount, 20) def test_09_customer_invoice(self): global_discount = self.global_discount_obj.create( { "name": "Test Discount Sales", "discount_scope": "sale", "discount": 50, "account_id": self.account.id, "sequence": 1, } ) tax = self.tax_sale_a.copy(default={"amount": 15.0, "name": "Tax 2"}) invoice = ( self.env["account.move"] .with_context(test_account_global_discount=True) .create( { "move_type": "out_invoice", "partner_id": self.partner_1.id, "global_discount_ids": [(6, 0, global_discount.ids)], "invoice_line_ids": [ ( 0, 0, { "name": "Line 1", "price_unit": 200.0, "quantity": 1, "tax_ids": [(6, 0, tax.ids)], }, ) ], } ) ) self.assertEqual(len(invoice.invoice_global_discount_ids), 1) invoice_tax_line = invoice.line_ids.filtered("tax_line_id") self.assertAlmostEqual(invoice_tax_line.tax_base_amount, 100.0) self.assertAlmostEqual(invoice_tax_line.balance, -15.0) self.assertAlmostEqual(invoice.amount_untaxed, 100.0) self.assertAlmostEqual(invoice.amount_total, 115.0) self.assertAlmostEqual(invoice.amount_global_discount, -100.0) # Check journal item validity lines = invoice.line_ids line_15 = lines.filtered( lambda x: x.invoice_global_discount_id and x.tax_ids == tax ) self.assertAlmostEqual(line_15.debit, 100) def test_10_customer_invoice_currency(self): """Multi-currency""" eur = self.env.ref("base.EUR") usd = self.env.ref("base.USD") self.assertEqual(self.env.user.company_id.currency_id, usd) self.invoice.currency_id = self.env.ref("base.EUR") invoice = self.invoice self.assertAlmostEqual(invoice.amount_total, 430.0) self.assertAlmostEqual(invoice.amount_untaxed, 400.0) self.assertAlmostEqual(invoice.amount_global_discount, 0) base_line = invoice.line_ids.filtered( lambda line: ( line.tax_ids and not line.invoice_global_discount_id and not line.product_id ) ) base_line_with_product = invoice.line_ids.filtered( lambda line: ( line.tax_ids and not line.invoice_global_discount_id and line.product_id ) ) self.assertEqual(len(base_line) + len(base_line_with_product), 2) self.assertAlmostEqual( base_line.balance + base_line_with_product.balance, eur._convert( invoice.amount_untaxed, usd, self.env.user.company_id, invoice.date ), ) tax_line = invoice.line_ids.filtered( lambda line: line.tax_line_id and not line.invoice_global_discount_id ) line_with_tax = invoice.line_ids.filtered( lambda line: line.tax_ids.amount > 0 and not line.invoice_global_discount_id ) self.assertEqual(len(tax_line), 1) tax_line_balance_before_discount = tax_line.balance self.assertAlmostEqual( tax_line_balance_before_discount, eur._convert( line_with_tax.balance * (self.tax.amount / 100), usd, self.env.user.company_id, invoice.date, ), ) self.assertAlmostEqual( tax_line.tax_base_amount, eur._convert( line_with_tax.balance, usd, self.env.user.company_id, invoice.date, ), ) discount_line = invoice.line_ids.filtered("invoice_global_discount_id") self.assertFalse(discount_line) with Form(self.invoice) as invoice_form: invoice_form.global_discount_ids.add(self.global_discount_1) invoice = invoice_form.save() # Check that when we add a global discount it will be based on the # correct currency self.assertAlmostEqual(invoice.amount_total, 384) self.assertAlmostEqual(invoice.amount_untaxed, 360.0) self.assertAlmostEqual(invoice.amount_global_discount, -40.0) base_line = invoice.line_ids.filtered( lambda line: ( line.tax_ids and not line.invoice_global_discount_id and not line.product_id ) ) base_line_with_product = invoice.line_ids.filtered( lambda line: ( line.tax_ids and not line.invoice_global_discount_id and line.product_id ) ) self.assertEqual(len(base_line) + len(base_line_with_product), 2) self.assertAlmostEqual( base_line.balance + base_line_with_product.balance, eur._convert( invoice.amount_untaxed_before_global_discounts, usd, self.env.user.company_id, invoice.date, ), ) tax_line = invoice.line_ids.filtered( lambda line: line.tax_line_id and not line.invoice_global_discount_id ) line_without_tax = invoice.line_ids.filtered( lambda line: line.tax_ids.amount == 0 and not line.tax_line_id and line.product_id and not line.invoice_global_discount_id ) self.assertEqual(len(tax_line), 1) self.assertAlmostEqual( tax_line.tax_base_amount + line_without_tax.balance, eur._convert( invoice.amount_untaxed, usd, self.env.user.company_id, invoice.date, ), ) self.assertAlmostEqual( tax_line.balance, eur._convert( (invoice.amount_untaxed - line_without_tax.balance) * (self.tax.amount / 100), usd, self.env.user.company_id, invoice.date, ), ) self.assertLess(tax_line.balance, tax_line_balance_before_discount) discount_line = invoice.line_ids.filtered("invoice_global_discount_id") self.assertEqual(len(discount_line), 1) self.assertAlmostEqual( discount_line.balance, eur._convert( invoice.amount_global_discount, usd, self.env.user.company_id, invoice.date, ), )