# -*- coding: utf-8 -*- from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.exceptions import ValidationError from odoo.tests import tagged, Form from odoo import fields, Command @tagged('post_install', '-at_install') class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.other_currency = cls.setup_other_currency('EUR') # Payment Terms cls.early_pay_10_percents_10_days = cls.env['account.payment.term'].create({ 'name': '10% discount if paid within 10 days', 'company_id': cls.company_data['company'].id, 'early_discount': True, 'discount_percentage': 10, 'discount_days': 10, 'line_ids': [Command.create({ 'value': 'percent', 'nb_days': 0, 'value_amount': 100, })] }) cls.pay_term_net_30_days = cls.env['account.payment.term'].create({ 'name': 'Net 30 days', 'line_ids': [ (0, 0, { 'value_amount': 100, 'value': 'percent', 'nb_days': 30, }), ], }) cls.pay_30_percents_now_balance_60_days = cls.env['account.payment.term'].create({ 'name': '30% Now, Balance 60 Days', 'line_ids': [ Command.create({ 'value_amount': 30, 'value': 'percent', 'nb_days': 0, }), Command.create({ 'value_amount': 70, 'value': 'percent', 'nb_days': 60, }) ] }) # ========================== Tests Payment Terms ========================== def test_early_payment_end_date(self): inv_1200_10_percents_discount_no_tax = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({ 'name': 'line', 'price_unit': 1200.0, 'tax_ids': [] })], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) for line in inv_1200_10_percents_discount_no_tax.line_ids: if line.display_type == 'payment_term': self.assertEqual( line.discount_date, fields.Date.from_string('2019-01-11') or False ) def test_invoice_report_without_invoice_date(self): """ Ensure that an invoice with an early discount payment term and no invoice date can be previewed or printed. """ self.registry.enter_test_mode(self.cr) self.addCleanup(self.registry.leave_test_mode) out_invoice = self.env['account.move'].create([{ 'move_type': 'out_invoice', 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, 'invoice_line_ids': [Command.create({ 'name': 'line1', })] }]) # Assert that the invoice date is not set self.assertEqual(out_invoice.invoice_date, False) report = self.env['ir.actions.report'].with_context(force_report_rendering=True)._render_qweb_pdf('account.account_invoices', res_ids=out_invoice.id) self.assertTrue(report) #Test for invoices with multiple due dates and no early discount out_invoice.invoice_payment_term_id = self.pay_30_percents_now_balance_60_days new_report = self.env['ir.actions.report']._render_qweb_pdf('account.account_invoices', res_ids=out_invoice.id) self.assertTrue(new_report) # ========================== Tests Taxes Amounts ============================= def test_fixed_tax_amount_discounted_payment_mixed(self): fixed_tax = self.env['account.tax'].create({ 'name': 'Test 0.05', 'amount_type': 'fixed', 'amount': 0.05, }) self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed' invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({ 'name': 'line', 'price_unit': 1000.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids + fixed_tax.ids)], #15% tax + fixed 0.05 })], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) self.assertInvoiceValues(invoice, [ # pylint: disable=bad-whitespace {'display_type': 'epd', 'balance': -100.0}, {'display_type': 'epd', 'balance': 100.0}, {'display_type': 'product', 'balance': -1000.0}, {'display_type': 'tax', 'balance': -135}, {'display_type': 'tax', 'balance': -0.05}, {'display_type': 'payment_term', 'balance': 1135.05}, ], { 'amount_untaxed': 1000.0, 'amount_tax': 135.05, 'amount_total': 1135.05, }) # ========================== Tests Payment Register ========================== def test_register_discounted_payment_on_single_invoice(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) out_invoice_1.action_post() active_ids = out_invoice_1.ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-02', })._create_payments() self.assertTrue(payments.is_reconciled) self.assertEqual( payments.move_id.line_ids.sorted('balance').mapped('amount_currency'), [-1000.0, 100.0, 900.0], ) def test_register_discounted_payment_on_single_invoice_with_fixed_tax_1(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' fixed_tax = self.env['account.tax'].create({ 'name': 'Test 0.05', 'amount_type': 'fixed', 'amount': 0.05, 'type_tax_use': 'purchase', }) inv = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({ 'name': 'line', 'price_unit': 1500.0, 'tax_ids': [Command.set(self.product_a.supplier_taxes_id.ids + fixed_tax.ids)] })], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) inv.action_post() active_ids = inv.ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2017-01-01', })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -1552.55, 'tax_tag_invert': False}, {'amount_currency': -150.0, 'tax_tag_invert': True}, {'amount_currency': -22.5, 'tax_tag_invert': True}, {'amount_currency': 1725.05, 'tax_tag_invert': False}, ]) def test_register_discounted_payment_on_single_invoice_with_fixed_tax_2(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' fixed_tax = self.env['account.tax'].create({ 'name': 'Test 0.05', 'amount_type': 'fixed', 'amount': 0.05, 'type_tax_use': 'purchase', }) invoice = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({ 'name': 'line', 'price_unit': 50.0, 'tax_ids': [Command.set(self.product_a.supplier_taxes_id.ids + fixed_tax.ids)] })], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) invoice.action_post() payments = self.env['account.payment.register']\ .with_context(active_model='account.move', active_ids=invoice.ids)\ .create({'payment_date': '2017-01-01'})\ ._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -51.80, 'tax_tag_invert': False}, {'amount_currency': -5.00, 'tax_tag_invert': True}, {'amount_currency': -0.75, 'tax_tag_invert': True}, {'amount_currency': 57.55, 'tax_tag_invert': False}, ]) def test_register_discounted_payment_on_single_invoice_with_tax(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' inv_1500_10_percents_discount_tax_incl_15_percents_tax = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({'name': 'line', 'price_unit': 1500.0, 'tax_ids': [Command.set(self.product_a.supplier_taxes_id.ids)]})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) inv_1500_10_percents_discount_tax_incl_15_percents_tax.action_post() active_ids = inv_1500_10_percents_discount_tax_incl_15_percents_tax.ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2017-01-01', })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -1552.5, 'tax_tag_invert': False}, {'amount_currency': -150.0, 'tax_tag_invert': True}, {'amount_currency': -22.5, 'tax_tag_invert': True}, {'amount_currency': 1725.0, 'tax_tag_invert': False}, ]) def test_register_discounted_payment_on_single_out_invoice_with_tax(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' inv_1500_10_percents_discount_tax_incl_15_percents_tax = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [Command.create({'name': 'line', 'price_unit': 1500.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)]})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) inv_1500_10_percents_discount_tax_incl_15_percents_tax.action_post() active_ids = inv_1500_10_percents_discount_tax_incl_15_percents_tax.ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2017-01-01', })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -1725.0, 'tax_tag_invert': False}, {'amount_currency': 22.5, 'tax_tag_invert': False}, {'amount_currency': 150.0, 'tax_tag_invert': False}, {'amount_currency': 1552.5, 'tax_tag_invert': False}, ]) def test_register_discounted_payment_multi_line_discount(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' inv_mixed_lines_discount_and_no_discount = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [ Command.create({'name': 'line', 'price_unit': 1000.0, 'tax_ids': [Command.set(self.product_a.supplier_taxes_id.ids)]}), Command.create({'name': 'line', 'price_unit': 2000.0, 'tax_ids': None}) ], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) inv_mixed_lines_discount_and_no_discount.action_post() active_ids = inv_mixed_lines_discount_and_no_discount.ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2017-01-01', 'group_payment': True })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -2835.0, 'tax_tag_invert': False}, {'amount_currency': -200.0, 'tax_tag_invert': False}, {'amount_currency': -100.0, 'tax_tag_invert': True}, {'amount_currency': -15.0, 'tax_tag_invert': True}, {'amount_currency': 3150.0, 'tax_tag_invert': False}, ]) def test_register_payment_batch_included(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'included' out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': True })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -3000.0}, {'amount_currency': 300.0}, {'amount_currency': 2700}, ]) def test_register_payment_batch_excluded(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'excluded' out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)]})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': True })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -3150.0}, {'amount_currency': 300.0}, {'amount_currency': 2850}, ]) def test_register_payment_batch_mixed(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed' out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)]})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': True })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -3135.0}, {'amount_currency': 300.0}, {'amount_currency': 2835.0}, ]) def test_register_payment_batch_mixed_one_too_late(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed' out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2017-01-01', 'invoice_date': '2017-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)]})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': True })._create_payments() self.assertTrue(payments.is_reconciled) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -3135.0}, {'amount_currency': 200.0}, {'amount_currency': 2935.0}, ]) def test_mixed_epd_with_draft_invoice(self): self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed' tax = self.env['account.tax'].create({ 'name': 'WonderTax', 'amount': 10, }) with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice: invoice.partner_id = self.partner_a invoice.invoice_date = fields.Date.from_string('2022-02-21') invoice.invoice_payment_term_id = self.early_pay_10_percents_10_days with invoice.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a line_form.price_unit = 1000 line_form.quantity = 1 line_form.tax_ids.clear() line_form.tax_ids.add(tax) self._assert_tax_totals_summary(invoice.tax_totals, { 'same_tax_base': True, 'currency_id': self.env.company.currency_id.id, 'base_amount_currency': 1000.0, 'tax_amount_currency': 90.0, 'total_amount_currency': 1090.0, 'subtotals': [ { 'name': "Untaxed Amount", 'base_amount_currency': 1000.0, 'tax_amount_currency': 90.0, 'tax_groups': [ { 'id': tax.tax_group_id.id, 'base_amount_currency': 900.0, 'tax_amount_currency': 90.0, 'display_base_amount_currency': 900.0, }, ], }, ], }) def test_intracomm_bill_with_early_payment_included(self): tax_tags = self.env['account.account.tag'].create({ 'name': f'tax_tag_{i}', 'applicability': 'taxes', 'country_id': self.env.company.account_fiscal_country_id.id, } for i in range(6)) intracomm_tax = self.env['account.tax'].create({ 'name': 'tax20', 'amount_type': 'percent', 'amount': 20, 'type_tax_use': 'purchase', 'invoice_repartition_line_ids': [ # pylint: disable=bad-whitespace Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[0].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[1].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[2].ids)]}), ], 'refund_repartition_line_ids': [ # pylint: disable=bad-whitespace Command.create({'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[3].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [Command.set(tax_tags[4].ids)]}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0, 'tag_ids': [Command.set(tax_tags[5].ids)]}), ], }) early_payment_term = self.env['account.payment.term'].create({ 'name': "early_payment_term", 'company_id': self.company_data['company'].id, 'early_pay_discount_computation': 'included', 'early_discount': True, 'discount_percentage': 2, 'discount_days': 7, 'line_ids': [ Command.create({ 'value': 'percent', 'value_amount': 100.0, 'nb_days': 30, }), ], }) bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_payment_term_id': early_payment_term.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [ Command.create({ 'name': 'line', 'price_unit': 1000.0, 'tax_ids': [Command.set(intracomm_tax.ids)], }), ], }) bill.action_post() payment = self.env['account.payment.register']\ .with_context(active_model='account.move', active_ids=bill.ids)\ .create({'payment_date': '2019-01-01'})\ ._create_payments() self.assertRecordValues(payment.move_id.line_ids.sorted('balance'), [ # pylint: disable=bad-whitespace {'amount_currency': -980.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False}, {'amount_currency': -20.0, 'tax_ids': intracomm_tax.ids, 'tax_tag_ids': tax_tags[3].ids, 'tax_tag_invert': True}, {'amount_currency': -4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[4].ids, 'tax_tag_invert': True}, {'amount_currency': 4.0, 'tax_ids': [], 'tax_tag_ids': tax_tags[5].ids, 'tax_tag_invert': True}, {'amount_currency': 1000.0, 'tax_ids': [], 'tax_tag_ids': [], 'tax_tag_invert': False}, ]) def test_mixed_early_discount_with_tag_on_tax_base_line(self): """ Ensure that early payment discount line grouping works properly when using a tax that adds tax tags to its base line. """ tax_tag = self.env['account.account.tag'].create({ 'name': 'tax_tag', 'applicability': 'taxes', 'country_id': self.env.company.account_fiscal_country_id.id, }) tax_21 = self.env['account.tax'].create({ 'name': "tax_21", 'amount': 21, 'invoice_repartition_line_ids': [ Command.create({ 'factor_percent': 100, 'repartition_type': 'base', 'tag_ids': [Command.set(tax_tag.ids)], }), Command.create({ 'factor_percent': 100, 'repartition_type': 'tax', }), ], 'refund_repartition_line_ids': [ Command.create({ 'factor_percent': 100, 'repartition_type': 'base', }), Command.create({ 'factor_percent': 100, 'repartition_type': 'tax', }), ], }) self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed' bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) bill.write({ 'invoice_line_ids': [ Command.create({ 'name': 'line1', 'price_unit': 1000.0, 'tax_ids': [Command.set(tax_21.ids)], }), ], }) bill.write({ 'invoice_line_ids': [Command.create({ 'name': 'line2', 'price_unit': 1000.0, 'tax_ids': [Command.set(tax_21.ids)], })], }) epd_lines = bill.line_ids.filtered(lambda line: line.display_type == 'epd') self.assertRecordValues(epd_lines.sorted('balance'), [ {'balance': -200.0}, {'balance': 200.0}, ]) def test_mixed_epd_with_tax_included(self): early_pay_2_percents_10_days = self.env['account.payment.term'].create({ 'name': '2% discount if paid within 10 days', 'company_id': self.company_data['company'].id, 'early_discount': True, 'discount_percentage': 2, 'discount_days': 10, 'early_pay_discount_computation': 'mixed', 'line_ids': [Command.create({ 'value': 'percent', 'nb_days': 0, 'value_amount': 100, })] }) tax = self.env['account.tax'].create({ 'name': 'Tax 21% included', 'amount': 21, 'price_include_override': 'tax_included', }) with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice: invoice.partner_id = self.partner_a invoice.invoice_date = fields.Date.from_string('2022-02-21') invoice.invoice_payment_term_id = early_pay_2_percents_10_days with invoice.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a line_form.price_unit = 121 line_form.quantity = 1 line_form.tax_ids.clear() line_form.tax_ids.add(tax) self._assert_tax_totals_summary(invoice.tax_totals, { 'same_tax_base': True, 'currency_id': self.env.company.currency_id.id, 'base_amount_currency': 100.0, 'tax_amount_currency': 20.58, 'total_amount_currency': 120.58, 'subtotals': [ { 'name': "Untaxed Amount", 'base_amount_currency': 100.0, 'tax_amount_currency': 20.58, 'tax_groups': [ { 'id': tax.tax_group_id.id, 'base_amount_currency': 98.0, 'tax_amount_currency': 20.58, 'display_base_amount_currency': 98.0, }, ], }, ], }) def test_mixed_epd_with_tax_no_duplication(self): (self.pay_terms_a | self.early_pay_10_percents_10_days).write({'early_pay_discount_computation': 'mixed'}) inv = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [ Command.create({'name': 'line', 'price_unit': 100.0, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)]}), ], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) self.assertEqual(len(inv.line_ids), 5) # 1 prod, 1 tax, 1 epd, 1 epd tax discount, 1 payment terms inv.write({'invoice_payment_term_id': self.pay_terms_a.id}) self.assertEqual(len(inv.line_ids), 3) # 1 prod, 1 tax, 1 payment terms inv.write({'invoice_payment_term_id': self.early_pay_10_percents_10_days.id}) self.assertEqual(len(inv.line_ids), 5) def test_mixed_epd_with_tax_deleted_line(self): self.early_pay_10_percents_10_days.write({'early_pay_discount_computation': 'mixed'}) tax_a = self.env['account.tax'].create({ 'name': 'Test A', 'amount': 10, }) tax_b = self.env['account.tax'].create({ 'name': 'Test B', 'amount': 15, }) inv = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'date': '2019-01-01', 'invoice_line_ids': [ Command.create({'name': 'line', 'price_unit': 100.0, 'tax_ids': [Command.set(tax_a.ids)]}), Command.create({'name': 'line2', 'price_unit': 100.0, 'tax_ids': [Command.set(tax_b.ids)]}), ], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) self.assertEqual(len(inv.line_ids), 8) # 2 prod, 2 tax, 1 epd, 2 epd tax discount, 1 payment terms inv.invoice_line_ids[1].unlink() self.assertEqual(len(inv.line_ids), 5) # 1 prod, 1 tax, 1 epd, 1 epd tax discount, 1 payment terms self.assertEqual(inv.amount_tax, 9.00) # $100.0 @ 10% tax (-10% epd) def test_mixed_epd_with_rounding_issue(self): """ Ensure epd line will not unbalance the invoice """ tax_6 = self.env['account.tax'].create({ 'name': '6%', 'amount': 6, }) tax_12 = self.env['account.tax'].create({ 'name': '12%', 'amount': 12, }) tax_136 = self.env['account.tax'].create({ 'name': '136', 'amount': 0.136, 'amount_type': 'fixed', 'include_base_amount': True, }) tax_176 = self.env['account.tax'].create({ 'name': '176', 'amount': 0.176, 'amount_type': 'fixed', 'include_base_amount': True, }) early_pay_1_percents_7_days = self.env['account.payment.term'].create({ 'name': '1% discount if paid within 7 days', 'company_id': self.company_data['company'].id, 'early_pay_discount_computation': 'mixed', 'discount_percentage': 1, 'discount_days': 7, 'early_discount': True, 'line_ids': [Command.create({ 'value': 'percent', 'nb_days': 0, 'value_amount': 100, })] }) # The following vals will create a rounding issue line_create_vals = [ (116, 6, tax_6), (0.91, 350, tax_6), (194.21, 1, tax_136 | tax_12), (31.46, 5, tax_176 | tax_12) ] # If invoice is not balanced the following create will fail self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2022-02-21', 'invoice_payment_term_id': early_pay_1_percents_7_days.id, 'invoice_line_ids': [ Command.create({ 'price_unit': price_unit, 'quantity': quantity, 'tax_ids': [Command.set(taxes.ids)] }) for price_unit, quantity, taxes in line_create_vals ] }) def test_register_payment_batch_with_discount_and_without_discount(self): """ Test that a batch payment, that is - not grouped - with invoices having different payment terms (1 with discount, 1 without) -> will not crash """ out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.pay_term_net_30_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': False })._create_payments() self.assertTrue(all(payments.mapped('is_reconciled'))) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -2000.0}, {'amount_currency': -1000.0}, {'amount_currency': 200.0}, {'amount_currency': 1000}, {'amount_currency': 1800}, ]) def test_register_payment_batch_without_discount(self): """ Test that a batch payment, that is - not grouped - with invoices having the same payment terms (without discount) -> will not crash """ out_invoice_1 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 1000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.pay_term_net_30_days.id, }) out_invoice_2 = self.env['account.move'].create({ 'move_type': 'out_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'currency_id': self.other_currency.id, 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': 2000.0, 'tax_ids': []})], 'invoice_payment_term_id': self.pay_term_net_30_days.id, }) (out_invoice_1 + out_invoice_2).action_post() active_ids = (out_invoice_1 + out_invoice_2).ids payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({ 'payment_date': '2019-01-01', 'group_payment': False })._create_payments() self.assertTrue(all(payments.mapped('is_reconciled'))) self.assertRecordValues(payments.move_id.line_ids.sorted('balance'), [ {'amount_currency': -2000.0}, {'amount_currency': -1000.0}, {'amount_currency': 1000.0}, {'amount_currency': 2000}, ]) def test_mixed_epd_with_tax_refund(self): """ Ensure epd line are addeed to refunds """ self.early_pay_10_percents_10_days.write({'early_pay_discount_computation': 'mixed'}) invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2022-02-21', 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, 'invoice_line_ids': [ Command.create({ 'price_unit': 100.0, 'quantity': 1, 'tax_ids': [Command.set(self.product_a.taxes_id.ids)], }) ] }) invoice.action_post() move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=invoice.ids).create({ 'date': fields.Date.from_string('2017-01-01'), 'reason': 'no reason again', 'journal_id': invoice.journal_id.id, }) receivable_line = invoice.line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') reversal = move_reversal.modify_moves() reverse_move = self.env['account.move'].browse(reversal['res_id']) self.assertEqual(invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.") self.assertRecordValues(reverse_move.line_ids.sorted('id'), [ { 'balance': -100.0, 'tax_base_amount': 0.0, 'display_type': 'product', }, { 'balance': 10.0, 'tax_base_amount': 0.0, 'display_type': 'epd', }, { 'balance': -10.0, 'tax_base_amount': 0.0, 'display_type': 'epd', }, { 'balance': -13.5, 'tax_base_amount': 90.0, 'display_type': 'tax', }, { 'balance': receivable_line.balance, 'tax_base_amount': 0.0, 'display_type': 'payment_term', }, ]) def test_epd_validation_on_payment_terms(self): """ Test that enabling Early Payment Discount (EPD) on payment terms with multiple lines raises a ValidationError, and that enabling EPD on payment terms with a single line does not raise any error. """ payment_term = self.env['account.payment.term'].create({ 'name': 'Test Term', 'line_ids': [ Command.create({'value': 'percent', 'value_amount': 50, 'nb_days': 30}), Command.create({'value': 'percent', 'value_amount': 50, 'nb_days': 60}), ] }) with self.assertRaisesRegex( ValidationError, "The Early Payment Discount functionality can only be used with payment terms using a single 100% line.", msg="EPD should not be allowed with multiple term lines", ): payment_term.early_discount = True # Modify the payment term to have a single line payment_term.line_ids = [ Command.clear(), Command.create({"value": "percent", "value_amount": 100, "nb_days": 30}), ] try: payment_term.early_discount = True except ValidationError: self.fail( "ValidationError raised unexpectedly for single-line payment term with EPD" ) def test_epd_entry_tag_invert_with_distinct_negative_invoice_line(self): """ `tax_tag_invert` should be the same for all Early Payment Discount lines of a single entry """ analytic_plan = self.env['account.analytic.plan'].create({ 'name': 'existential plan', }) analytic_account_a = self.env['account.analytic.account'].create({ 'name': 'positive_account', 'plan_id': analytic_plan.id, }) analytic_account_b = self.env['account.analytic.account'].create({ 'name': 'negative_account', 'plan_id': analytic_plan.id, }) invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-10', 'date': '2019-01-10', 'invoice_line_ids': [ Command.create({ 'name': 'line', 'price_unit': 2000, 'tax_ids': self.tax_sale_a, 'analytic_distribution': {str(analytic_account_a.id): 100}, }), Command.create({ 'name': 'line', 'price_unit': -1500, 'tax_ids': self.tax_sale_a, 'analytic_distribution': {str(analytic_account_b.id): 100}, }), ], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) invoice.action_post() bill = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_b.id, 'invoice_date': '2019-01-10', 'date': '2019-01-10', 'invoice_line_ids': [ Command.create({ 'name': 'line', 'price_unit': 3000, 'tax_ids': self.tax_purchase_a, 'analytic_distribution': {str(analytic_account_a.id): 100}, }), Command.create({ 'name': 'line', 'price_unit': -2250, 'tax_ids': self.tax_purchase_a, 'analytic_distribution': {str(analytic_account_b.id): 100}, }), ], 'invoice_payment_term_id': self.early_pay_10_percents_10_days.id, }) bill.action_post() payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=invoice.ids, ).create({ 'payment_date': '2019-01-01', })._create_payments() payment_moves = payments.move_id for line in payment_moves.line_ids.filtered(lambda line: line.tax_repartition_line_id or line.tax_ids): self.assertFalse(line.tax_tag_invert) payments = self.env['account.payment.register'].with_context( active_model='account.move', active_ids=bill.ids, ).create({ 'payment_date': '2019-01-01', })._create_payments() payment_moves = payments.move_id for line in payment_moves.line_ids.filtered(lambda line: line.tax_repartition_line_id or line.tax_ids): self.assertTrue(line.tax_tag_invert) def test_epd_multiple_repartition_lines(self): """ In the case of multi repartition lines tax definition with an early payment discount We want to make sure that the EPD lines are correct. We want the rounding difference to be added to the "biggest" base line. """ # Taxes. common_values = { 'amount': 17.0, 'invoice_repartition_line_ids': [ Command.create({'repartition_type': 'base'}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0}), ], 'refund_repartition_line_ids': [ Command.create({'repartition_type': 'base'}), Command.create({'repartition_type': 'tax', 'factor_percent': 100.0}), Command.create({'repartition_type': 'tax', 'factor_percent': -100.0}), ], } tax1, tax2 = self.env['account.tax'].create([ {'name': "tax1", **common_values}, {'name': "tax2", **common_values}, ]) # Early payment. payment_term = self.env['account.payment.term'].create({ 'name': "10% discount if paid within 10 days", 'early_discount': True, 'early_pay_discount_computation': 'included', 'discount_percentage': 2, 'discount_days': 10, 'line_ids': [Command.create({ 'value': 'percent', 'nb_days': 0, 'value_amount': 100, })] }) # Invoice. invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'partner_id': self.partner_a.id, 'invoice_payment_term_id': payment_term.id, 'invoice_date': '2017-01-01', 'invoice_line_ids': [ Command.create({ 'name': "Line One", 'price_unit': 739.95, 'tax_ids': [Command.set(tax1.ids)], }), Command.create({ 'name': "Line Two", 'price_unit': 37.80, 'tax_ids': [Command.set(tax2.ids)], }), ], }) invoice.action_post() # Payment. payment = self.env['account.payment.register']\ .with_context(active_model='account.move', active_ids=invoice.ids)\ .create({'payment_date': '2017-01-01'})\ ._create_payments() self.assertRecordValues(payment.move_id.line_ids.sorted('amount_currency'), [ # Invoice's total: {'amount_currency': -777.75}, # Base / tax lines: {'amount_currency': -2.51}, {'amount_currency': -0.13}, {'amount_currency': 0.13}, {'amount_currency': 0.76}, {'amount_currency': 2.51}, {'amount_currency': 14.79}, # Discounted amount: {'amount_currency': 762.2}, ])