# -*- coding: utf-8 -*- # pylint: disable=bad-whitespace from freezegun import freeze_time from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import Form, tagged from odoo import fields, Command from odoo.osv import expression from odoo.exceptions import ValidationError, UserError from datetime import date from collections import defaultdict @tagged('post_install', '-at_install') class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.other_currency = cls.setup_other_currency('EUR') cls.invoice = cls.init_invoice('in_invoice', products=cls.product_a+cls.product_b) cls.product_line_vals_1 = { 'name': 'product_a', 'product_id': cls.product_a.id, 'account_id': cls.product_a.property_account_expense_id.id, 'partner_id': cls.partner_a.id, 'product_uom_id': cls.product_a.uom_id.id, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 800.0, 'price_subtotal': 800.0, 'price_total': 920.0, 'tax_ids': cls.product_a.supplier_taxes_id.ids, 'tax_line_id': False, 'currency_id': cls.company_data['currency'].id, 'amount_currency': 800.0, 'debit': 800.0, 'credit': 0.0, 'date_maturity': False, } cls.product_line_vals_2 = { 'name': 'product_b', 'product_id': cls.product_b.id, 'account_id': cls.product_b.property_account_expense_id.id, 'partner_id': cls.partner_a.id, 'product_uom_id': cls.product_b.uom_id.id, 'quantity': 1.0, 'discount': 0.0, 'price_unit': 160.0, 'price_subtotal': 160.0, 'price_total': 208.0, 'tax_ids': cls.product_b.supplier_taxes_id.ids, 'tax_line_id': False, 'currency_id': cls.company_data['currency'].id, 'amount_currency': 160.0, 'debit': 160.0, 'credit': 0.0, 'date_maturity': False, } cls.tax_line_vals_1 = { 'name': cls.tax_purchase_a.name, 'product_id': False, 'account_id': cls.company_data['default_account_tax_purchase'].id, 'partner_id': cls.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': cls.tax_purchase_a.id, 'currency_id': cls.company_data['currency'].id, 'amount_currency': 144.0, 'debit': 144.0, 'credit': 0.0, 'date_maturity': False, } cls.tax_line_vals_2 = { 'name': cls.tax_purchase_b.name, 'product_id': False, 'account_id': cls.company_data['default_account_tax_purchase'].id, 'partner_id': cls.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': cls.tax_purchase_b.id, 'currency_id': cls.company_data['currency'].id, 'amount_currency': 24.0, 'debit': 24.0, 'credit': 0.0, 'date_maturity': False, } cls.term_line_vals_1 = { 'name': '', 'product_id': False, 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': cls.company_data['currency'].id, 'amount_currency': -1128.0, 'debit': 0.0, 'credit': 1128.0, 'date_maturity': fields.Date.from_string('2019-01-01'), } cls.move_vals = { 'partner_id': cls.partner_a.id, 'currency_id': cls.company_data['currency'].id, 'journal_id': cls.company_data['default_journal_purchase'].id, 'date': fields.Date.from_string('2019-01-01'), 'fiscal_position_id': False, 'payment_reference': False, 'invoice_payment_term_id': cls.pay_terms_a.id, 'amount_untaxed': 960.0, 'amount_tax': 168.0, 'amount_total': 1128.0, } cls.env.user.groups_id += cls.env.ref('uom.group_uom') @classmethod def setup_armageddon_tax(cls, tax_name, company_data, **kwargs): return super().setup_armageddon_tax(tax_name, company_data, type_tax_use='purchase', **kwargs) def setUp(self): super(TestAccountMoveInInvoiceOnchanges, self).setUp() self.assertInvoiceValues(self.invoice, [ self.product_line_vals_1, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) def test_in_invoice_onchange_invoice_date(self): for tax_date, invoice_date, accounting_date in [ ('2019-03-31', '2019-05-12', '2019-05-31'), ('2019-03-31', '2019-02-10', '2019-04-30'), ('2019-05-31', '2019-06-15', '2019-06-30'), ]: self.invoice.company_id.tax_lock_date = tax_date with Form(self.invoice) as move_form: move_form.invoice_date = invoice_date self.assertEqual(self.invoice.date, fields.Date.to_date(accounting_date)) @freeze_time('2021-09-16') def test_in_invoice_onchange_invoice_date_2(self): invoice_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) invoice_form.partner_id = self.partner_a invoice_form.invoice_payment_term_id = self.env.ref('account.account_payment_term_30days') with invoice_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a invoice_form.invoice_date = fields.Date.from_string('2021-09-01') invoice = invoice_form.save() self.assertRecordValues(invoice, [{ 'date': fields.Date.from_string('2021-09-16'), 'invoice_date': fields.Date.from_string('2021-09-01'), 'invoice_date_due': fields.Date.from_string('2021-10-01'), }]) def test_in_invoice_line_onchange_product_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.product_id = self.product_b move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'name': 'product_b', 'product_id': self.product_b.id, 'product_uom_id': self.product_b.uom_id.id, 'account_id': self.product_b.property_account_expense_id.id, 'price_unit': 160.0, 'price_subtotal': 160.0, 'price_total': 208.0, 'tax_ids': self.product_b.supplier_taxes_id.ids, 'amount_currency': 160.0, 'debit': 160.0, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'amount_currency': 48.0, 'debit': 48.0, }, { **self.tax_line_vals_2, 'amount_currency': 48.0, 'debit': 48.0, }, { **self.term_line_vals_1, 'amount_currency': -416.0, 'credit': 416.0, }, ], { **self.move_vals, 'amount_untaxed': 320.0, 'amount_tax': 96.0, 'amount_total': 416.0, }) def test_in_invoice_line_onchange_product_2_with_fiscal_pos(self): ''' Test mapping a price-included tax (10%) with a price-excluded tax (20%) on a price_unit of 110.0. The price_unit should be 100.0 after applying the fiscal position. ''' tax_price_include = self.env['account.tax'].create({ 'name': '10% incl', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 10, 'price_include_override': 'tax_included', 'include_base_amount': True, }) tax_price_exclude = self.env['account.tax'].create({ 'name': '15% excl', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 15, }) fiscal_position = self.env['account.fiscal.position'].create({ 'name': 'fiscal_pos_a', 'tax_ids': [ (0, None, { 'tax_src_id': tax_price_include.id, 'tax_dest_id': tax_price_exclude.id, }), ], }) product = self.env['product.product'].create({ 'name': 'product', 'uom_id': self.env.ref('uom.product_uom_unit').id, 'standard_price': 110.0, 'supplier_taxes_id': [(6, 0, tax_price_include.ids)], }) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) move_form.partner_id = self.partner_a move_form.invoice_date = fields.Date.from_string('2019-01-01') move_form.currency_id = self.other_currency move_form.fiscal_position_id = fiscal_position with move_form.invoice_line_ids.new() as line_form: line_form.product_id = product invoice = move_form.save() self.assertInvoiceValues(invoice, [ { 'product_id': product.id, 'price_unit': 200.0, 'price_subtotal': 200.0, 'price_total': 230.0, 'tax_ids': tax_price_exclude.ids, 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': 200.0, 'debit': 100.0, 'credit': 0.0, }, { 'product_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': tax_price_exclude.id, 'currency_id': self.other_currency.id, 'amount_currency': 30.0, 'debit': 15.0, 'credit': 0.0, }, { 'product_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': -230.0, 'debit': 0.0, 'credit': 115.0, }, ], { 'currency_id': self.other_currency.id, 'fiscal_position_id': fiscal_position.id, 'amount_untaxed': 200.0, 'amount_tax': 30.0, 'amount_total': 230.0, }) uom_dozen = self.env.ref('uom.product_uom_dozen') with Form(invoice) as move_form: with move_form.invoice_line_ids.edit(0) as line_form: line_form.product_uom_id = uom_dozen self.assertInvoiceValues(invoice, [ { 'product_id': product.id, 'product_uom_id': uom_dozen.id, 'price_unit': 2400.0, 'price_subtotal': 2400.0, 'price_total': 2760.0, 'tax_ids': tax_price_exclude.ids, 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': 2400.0, 'debit': 1200.0, 'credit': 0.0, }, { 'product_id': False, 'product_uom_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': tax_price_exclude.id, 'currency_id': self.other_currency.id, 'amount_currency': 360.0, 'debit': 180.0, 'credit': 0.0, }, { 'product_id': False, 'product_uom_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': -2760.0, 'debit': 0.0, 'credit': 1380.0, }, ], { 'currency_id': self.other_currency.id, 'fiscal_position_id': fiscal_position.id, 'amount_untaxed': 2400.0, 'amount_tax': 360.0, 'amount_total': 2760.0, }) def test_in_invoice_line_onchange_product_2_with_fiscal_pos_2(self): ''' Test mapping a price-included tax (10%) with another price-included tax (20%) on a price_unit of 110.0. The price_unit should be 120.0 after applying the fiscal position. ''' tax_price_include_1 = self.env['account.tax'].create({ 'name': '10% incl', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 10, 'price_include_override': 'tax_included', 'include_base_amount': True, }) tax_price_include_2 = self.env['account.tax'].create({ 'name': '20% incl', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 20, 'price_include_override': 'tax_included', 'include_base_amount': True, }) fiscal_position = self.env['account.fiscal.position'].create({ 'name': 'fiscal_pos_a', 'tax_ids': [ (0, None, { 'tax_src_id': tax_price_include_1.id, 'tax_dest_id': tax_price_include_2.id, }), ], }) product = self.env['product.product'].create({ 'name': 'product', 'uom_id': self.env.ref('uom.product_uom_unit').id, 'standard_price': 110.0, 'supplier_taxes_id': [(6, 0, tax_price_include_1.ids)], }) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) move_form.partner_id = self.partner_a move_form.invoice_date = fields.Date.from_string('2019-01-01') move_form.currency_id = self.other_currency move_form.fiscal_position_id = fiscal_position with move_form.invoice_line_ids.new() as line_form: line_form.product_id = product invoice = move_form.save() self.assertInvoiceValues(invoice, [ { 'product_id': product.id, 'price_unit': 240.0, 'price_subtotal': 200.0, 'price_total': 240.0, 'tax_ids': tax_price_include_2.ids, 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': 200.0, 'debit': 100.0, 'credit': 0.0, }, { 'product_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': tax_price_include_2.id, 'currency_id': self.other_currency.id, 'amount_currency': 40.0, 'debit': 20.0, 'credit': 0.0, }, { 'product_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': -240.0, 'debit': 0.0, 'credit': 120.0, }, ], { 'currency_id': self.other_currency.id, 'fiscal_position_id': fiscal_position.id, 'amount_untaxed': 200.0, 'amount_tax': 40.0, 'amount_total': 240.0, }) uom_dozen = self.env.ref('uom.product_uom_dozen') with Form(invoice) as move_form: with move_form.invoice_line_ids.edit(0) as line_form: line_form.product_uom_id = uom_dozen self.assertInvoiceValues(invoice, [ { 'product_id': product.id, 'product_uom_id': uom_dozen.id, 'price_unit': 2880.0, 'price_subtotal': 2400.0, 'price_total': 2880.0, 'tax_ids': tax_price_include_2.ids, 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': 2400.0, 'debit': 1200.0, 'credit': 0.0, }, { 'product_id': False, 'product_uom_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': tax_price_include_2.id, 'currency_id': self.other_currency.id, 'amount_currency': 480.0, 'debit': 240.0, 'credit': 0.0, }, { 'product_id': False, 'product_uom_id': False, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.other_currency.id, 'amount_currency': -2880.0, 'debit': 0.0, 'credit': 1440.0, }, ], { 'currency_id': self.other_currency.id, 'fiscal_position_id': fiscal_position.id, 'amount_untaxed': 2400.0, 'amount_tax': 480.0, 'amount_total': 2880.0, }) def test_in_invoice_line_onchange_business_fields_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: # Current price_unit is 800. # We set quantity = 4, discount = 50%, price_unit = 400. The debit/credit fields don't change because (4 * 400) * 0.5 = 800. line_form.quantity = 4 line_form.discount = 50 line_form.price_unit = 400 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 4, 'discount': 50.0, 'price_unit': 400.0, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: # Reset field except the discount that becomes 100%. # /!\ The modification is made on the accounting tab. line_form.quantity = 1 line_form.discount = 100 line_form.price_unit = 800 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'discount': 100.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'amount_currency': 0.0, 'debit': 0.0, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'amount_currency': 24.0, 'debit': 24.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'amount_currency': -208.0, 'credit': 208.0, }, ], { **self.move_vals, 'amount_untaxed': 160.0, 'amount_tax': 48.0, 'amount_total': 208.0, }) def test_in_invoice_line_onchange_partner_1(self): move_form = Form(self.invoice) move_form.partner_id = self.partner_b move_form.payment_reference = 'turlututu' move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'partner_id': self.partner_b.id, }, { **self.product_line_vals_2, 'partner_id': self.partner_b.id, }, { **self.tax_line_vals_1, 'partner_id': self.partner_b.id, }, { **self.tax_line_vals_2, 'partner_id': self.partner_b.id, }, { **self.term_line_vals_1, 'name': 'turlututu installment #1', 'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id, 'amount_currency': -338.4, 'credit': 338.4, }, { **self.term_line_vals_1, 'name': 'turlututu installment #2', 'partner_id': self.partner_b.id, 'account_id': self.partner_b.property_account_payable_id.id, 'amount_currency': -789.6, 'credit': 789.6, 'date_maturity': fields.Date.from_string('2019-02-28'), }, ], { **self.move_vals, 'partner_id': self.partner_b.id, 'payment_reference': 'turlututu', 'fiscal_position_id': self.fiscal_pos_a.id, 'invoice_payment_term_id': self.pay_terms_b.id, 'amount_untaxed': 960.0, 'amount_tax': 168.0, 'amount_total': 1128.0, }) # Remove lines and recreate them to apply the fiscal position. move_form = Form(self.invoice) move_form.invoice_line_ids.remove(0) move_form.invoice_line_ids.remove(0) with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_b move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'account_id': self.product_b.property_account_expense_id.id, 'partner_id': self.partner_b.id, 'tax_ids': self.tax_purchase_b.ids, }, { **self.product_line_vals_2, 'partner_id': self.partner_b.id, 'price_total': 184.0, 'tax_ids': self.tax_purchase_b.ids, }, { **self.tax_line_vals_1, 'name': self.tax_purchase_b.name, 'partner_id': self.partner_b.id, 'tax_line_id': self.tax_purchase_b.id, }, { **self.term_line_vals_1, 'name': 'turlututu installment #1', 'account_id': self.partner_b.property_account_payable_id.id, 'partner_id': self.partner_b.id, 'amount_currency': -331.2, 'credit': 331.2, }, { **self.term_line_vals_1, 'name': 'turlututu installment #2', 'account_id': self.partner_b.property_account_payable_id.id, 'partner_id': self.partner_b.id, 'amount_currency': -772.8, 'credit': 772.8, 'date_maturity': fields.Date.from_string('2019-02-28'), }, ], { **self.move_vals, 'partner_id': self.partner_b.id, 'payment_reference': 'turlututu', 'fiscal_position_id': self.fiscal_pos_a.id, 'invoice_payment_term_id': self.pay_terms_b.id, 'amount_untaxed': 960.0, 'amount_tax': 144.0, 'amount_total': 1104.0, }) def test_in_invoice_line_onchange_taxes_1(self): move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.price_unit = 960 line_form.tax_ids.add(self.tax_armageddon) move_form.save() child_tax_1 = self.tax_armageddon.children_tax_ids[0] child_tax_2 = self.tax_armageddon.children_tax_ids[1] self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'price_unit': 960.0, 'price_subtotal': 800.0, 'price_total': 1176.0, 'tax_ids': (self.tax_purchase_a + self.tax_armageddon).ids, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, { 'name': child_tax_1.name, 'product_id': False, 'account_id': self.company_data['default_account_tax_sale'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 64.0, 'debit': 64.0, 'credit': 0.0, 'date_maturity': False, }, { 'name': child_tax_1.name, 'product_id': False, 'account_id': self.company_data['default_account_expense'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': child_tax_2.ids, 'tax_line_id': child_tax_1.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 96.0, 'debit': 96.0, 'credit': 0.0, 'date_maturity': False, }, { 'name': child_tax_2.name, 'product_id': False, 'account_id': child_tax_2.cash_basis_transition_account_id.id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': child_tax_2.id, 'currency_id': self.company_data['currency'].id, 'amount_currency': 96.0, 'debit': 96.0, 'credit': 0.0, 'date_maturity': False, }, { **self.term_line_vals_1, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'amount_currency': -1384.0, 'credit': 1384.0, }, ], { **self.move_vals, 'amount_untaxed': 960.0, 'amount_tax': 424.0, 'amount_total': 1384.0, }) def test_compute_cash_rounding_lines(self): cash_rounding_add_invoice_line = self.env['account.cash.rounding'].create({ 'name': 'Add invoice line Rounding Down', 'rounding': .1, 'strategy': 'add_invoice_line', 'profit_account_id': self.company_data['default_account_revenue'].id, 'loss_account_id': self.company_data['default_account_expense'].id, 'rounding_method': 'DOWN', }) move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': '2019-01-01', 'invoice_cash_rounding_id': cash_rounding_add_invoice_line.id, 'invoice_line_ids': [ Command.create({ 'name': 'line', 'price_unit': 295, 'tax_ids': [], }), Command.create({ 'name': 'cost', 'price_unit': 280.33, }), Command.create({ 'name': 'cost neg', 'price_unit': -280.33, }), ], }) self.assertFalse(move.line_ids.filtered(lambda line: line.display_type == 'rounding')) def test_in_invoice_line_onchange_cash_rounding_1(self): # Required for `invoice_cash_rounding_id` to be visible in the view self.env.user.groups_id += self.env.ref('account.group_cash_rounding') # Test 'add_invoice_line' rounding move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. move_form.invoice_cash_rounding_id = self.cash_rounding_a move_form.save() # The cash rounding does nothing as the total is already rounded. self.assertInvoiceValues(self.invoice, [ self.product_line_vals_1, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: line_form.price_unit = 799.99 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'price_unit': 799.99, 'price_subtotal': 799.99, 'price_total': 919.99, 'amount_currency': 799.99, 'debit': 799.99, }, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, { 'name': 'add_invoice_line', 'product_id': False, 'account_id': self.cash_rounding_a.loss_account_id.id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': False, 'currency_id': self.company_data['currency'].id, 'amount_currency': 0.01, 'debit': 0.01, 'credit': 0.0, 'date_maturity': False, }, self.term_line_vals_1, ], self.move_vals) # Test 'biggest_tax' rounding self.company_data['company'].country_id = self.env.ref('base.us') # Add a tag to product_a's default tax tax_line_tag = self.env['account.account.tag'].create({ 'name': "Tax tag", 'applicability': 'taxes', 'country_id': self.company_data['company'].country_id.id, }) repartition_line = self.tax_purchase_a.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax') repartition_line.write({'tag_ids': [(4, tax_line_tag.id, 0)]}) # Create the invoice biggest_tax_invoice = self.env['account.move'].create({ 'move_type': 'in_invoice', 'date': '2019-01-01', 'invoice_date': '2019-01-01', 'partner_id': self.partner_a.id, 'invoice_cash_rounding_id': self.cash_rounding_b.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ (0, 0, { 'product_id': self.product_a.id, 'price_unit': 799.99, 'tax_ids': [(6, 0, self.product_a.supplier_taxes_id.ids)], 'product_uom_id': self.product_a.uom_id.id, }), (0, 0, { 'product_id': self.product_b.id, 'price_unit': self.product_b.standard_price, 'tax_ids': [(6, 0, self.product_b.supplier_taxes_id.ids)], 'product_uom_id': self.product_b.uom_id.id, }), ], }) self.assertInvoiceValues(biggest_tax_invoice, [ { **self.product_line_vals_1, 'price_unit': 799.99, 'price_subtotal': 799.99, 'price_total': 919.99, 'amount_currency': 799.99, 'debit': 799.99, 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { **self.product_line_vals_2, 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, { **self.tax_line_vals_1, 'tax_repartition_line_id': repartition_line.id, 'tax_tag_ids': tax_line_tag.ids, }, { **self.tax_line_vals_2, 'tax_repartition_line_id': self.tax_purchase_b.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id, 'tax_tag_ids': [], }, { 'name': '%s (rounding)' % self.tax_purchase_a.name, 'product_id': False, 'account_id': self.company_data['default_account_tax_purchase'].id, 'partner_id': self.partner_a.id, 'product_uom_id': False, 'quantity': False, 'discount': 0.0, 'price_unit': 0.0, 'price_subtotal': 0.0, 'price_total': 0.0, 'tax_ids': [], 'tax_line_id': self.tax_purchase_a.id, 'tax_repartition_line_id': repartition_line.id, 'tax_tag_ids': tax_line_tag.ids, 'currency_id': self.company_data['currency'].id, 'amount_currency': -0.04, 'debit': 0.0, 'credit': 0.04, 'date_maturity': False, }, { **self.term_line_vals_1, 'amount_currency': -1127.95, 'credit': 1127.95, 'tax_repartition_line_id': None, 'tax_tag_ids': [], }, ], { **self.move_vals, 'amount_untaxed': 959.99, 'amount_tax': 167.96, 'amount_total': 1127.95, }) def test_in_invoice_line_onchange_currency_1(self): move_form = Form(self.invoice) move_form.currency_id = self.other_currency move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'debit': 400.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 80.0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'debit': 72.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 12.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'credit': 564.0, }, ], { **self.move_vals, 'currency_id': self.other_currency.id, }) # Change the date to get another rate: 1/3 instead of 1/2. with Form(self.invoice) as move_form: move_form.invoice_date = fields.Date.from_string('2016-01-01') move_form.date = fields.Date.from_string('2016-01-01') self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'debit': 266.67, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 53.33, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'debit': 48.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 8.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'credit': 376.0, 'date_maturity': fields.Date.from_string('2016-01-01'), }, ], { **self.move_vals, 'currency_id': self.other_currency.id, 'date': fields.Date.from_string('2016-01-01'), }) move_form = Form(self.invoice) with move_form.invoice_line_ids.edit(0) as line_form: # 0.045 * 0.1 = 0.0045. As the foreign currency has a 0.001 rounding, # the result should be 0.005 after rounding. line_form.quantity = 0.1 line_form.price_unit = 0.045 move_form.save() self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 0.1, 'price_unit': 0.05, 'price_subtotal': 0.005, 'price_total': 0.006, 'currency_id': self.other_currency.id, 'amount_currency': 0.005, 'debit': 0.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 53.33, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 24.001, 'debit': 8.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 8.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -208.006, 'credit': 69.33, 'date_maturity': fields.Date.from_string('2016-01-01'), }, ], { **self.move_vals, 'currency_id': self.other_currency.id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.005, 'amount_tax': 48.001, 'amount_total': 208.006, }) # Exit the multi-currencies. with Form(self.invoice) as move_form: move_form.currency_id = self.company_data['currency'] self.assertInvoiceValues(self.invoice, [ { **self.product_line_vals_1, 'quantity': 0.1, 'price_unit': 0.05, 'price_subtotal': 0.01, 'price_total': 0.01, 'amount_currency': 0.01, 'debit': 0.01, }, self.product_line_vals_2, { **self.tax_line_vals_1, 'amount_currency': 24.0, 'debit': 24.0, }, self.tax_line_vals_2, { **self.term_line_vals_1, 'amount_currency': -208.01, 'credit': 208.01, 'date_maturity': fields.Date.from_string('2016-01-01'), }, ], { **self.move_vals, 'currency_id': self.company_data['currency'].id, 'date': fields.Date.from_string('2016-01-01'), 'amount_untaxed': 160.01, 'amount_tax': 48.0, 'amount_total': 208.01, }) def test_in_invoice_onchange_past_invoice_1(self): if self.env.ref('purchase.group_purchase_manager', raise_if_not_found=False): # `purchase` adds a view which makes `invoice_vendor_bill_id` invisible # for purchase users # https://github.com/odoo/odoo/blob/385884afd31f25d61e99d139ecd4c574d99a1863/addons/purchase/views/account_move_views.xml#L26 self.env.user.groups_id -= self.env.ref('purchase.group_purchase_manager') self.env.user.groups_id -= self.env.ref('purchase.group_purchase_user') copy_invoice = self.invoice.copy() move_form = Form(self.invoice) move_form.invoice_line_ids.remove(0) move_form.invoice_line_ids.remove(0) move_form.invoice_vendor_bill_id = copy_invoice move_form.save() self.assertInvoiceValues(self.invoice, [ self.product_line_vals_1, self.product_line_vals_2, self.tax_line_vals_1, self.tax_line_vals_2, self.term_line_vals_1, ], self.move_vals) def test_in_invoice_create_refund(self): self.invoice.action_post() bank1 = self.env['res.partner.bank'].create({ 'acc_number': 'BE43798822936101', 'partner_id': self.company_data['company'].partner_id.id, }) move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({ 'date': fields.Date.from_string('2019-02-01'), 'reason': 'no reason', 'journal_id': self.invoice.journal_id.id, }) reversal = move_reversal.refund_moves() reverse_move = self.env['account.move'].browse(reversal['res_id']) self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.") self.assertInvoiceValues(reverse_move, [ { **self.product_line_vals_1, 'amount_currency': -800.0, 'debit': 0.0, 'credit': 800.0, 'tax_tag_invert': True, 'tax_base_amount': 0.0, }, { **self.product_line_vals_2, 'amount_currency': -160.0, 'debit': 0.0, 'credit': 160.0, 'tax_tag_invert': True, 'tax_base_amount': 0.0, }, { **self.tax_line_vals_1, 'amount_currency': -144.0, 'debit': 0.0, 'credit': 144.0, 'tax_tag_invert': True, 'tax_base_amount': 960.0, }, { **self.tax_line_vals_2, 'amount_currency': -24.0, 'debit': 0.0, 'credit': 24.0, 'tax_tag_invert': True, 'tax_base_amount': 160.0, }, { **self.term_line_vals_1, 'name': '', 'amount_currency': 1128.0, 'debit': 1128.0, 'credit': 0.0, 'date_maturity': move_reversal.date, 'tax_tag_invert': False, 'tax_base_amount': 0.0, }, ], { **self.move_vals, 'invoice_payment_term_id': None, 'date': move_reversal.date, 'state': 'draft', 'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason), 'payment_state': 'not_paid', 'partner_bank_id': bank1.id, }) move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({ 'date': fields.Date.from_string('2019-02-01'), 'reason': 'no reason again', 'journal_id': self.invoice.journal_id.id, }) reversal = move_reversal.modify_moves() new_move = self.env['account.move'].browse(reversal['res_id']) self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.") self.assertInvoiceValues(new_move, [ { **self.product_line_vals_1, 'amount_currency': 800.0, 'debit': 800.0, 'credit': 0.0, 'tax_tag_invert': False, 'tax_base_amount': 0, }, { **self.product_line_vals_2, 'amount_currency': 160.0, 'debit': 160.0, 'credit': 0.0, 'tax_tag_invert': False, 'tax_base_amount': 0, }, { **self.tax_line_vals_1, 'amount_currency': 144.0, 'debit': 144.0, 'credit': 0.0, 'tax_tag_invert': False, 'tax_base_amount': 960.0, }, { **self.tax_line_vals_2, 'amount_currency': 24.0, 'debit': 24.0, 'credit': 0.0, 'tax_tag_invert': False, 'tax_base_amount': 160.0, }, { **self.term_line_vals_1, 'name': '', 'amount_currency': -1128.0, 'debit': 0.0, 'credit': 1128.0, 'date_maturity': move_reversal.date, 'tax_tag_invert': False, 'tax_base_amount': 0, }, ], { **self.move_vals, 'invoice_payment_term_id': self.pay_terms_a.id, 'date': move_reversal.date, 'state': 'draft', 'ref': False, 'payment_state': 'not_paid', }) def test_in_invoice_create_refund_multi_currency(self): ''' Test the account.move.reversal takes care about the currency rates when setting a custom reversal date. ''' with Form(self.invoice) as move_form: move_form.date = '2016-01-01' move_form.currency_id = self.other_currency self.invoice.action_post() # The currency rate changed from 1/3 to 1/2. move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({ 'date': fields.Date.from_string('2017-01-01'), 'reason': 'no reason', 'journal_id': self.invoice.journal_id.id, }) reversal = move_reversal.refund_moves() reverse_move = self.env['account.move'].browse(reversal['res_id']) self.assertEqual(self.invoice.payment_state, 'not_paid', "Refunding with a draft credit note should keep the invoice 'not_paid'.") self.assertInvoiceValues(reverse_move, [ { **self.product_line_vals_1, 'amount_currency': -800.0, 'currency_id': self.other_currency.id, 'debit': 0.0, 'credit': 400.0, }, { **self.product_line_vals_2, 'amount_currency': -160.0, 'currency_id': self.other_currency.id, 'debit': 0.0, 'credit': 80.0, }, { **self.tax_line_vals_1, 'amount_currency': -144.0, 'currency_id': self.other_currency.id, 'debit': 0.0, 'credit': 72.0, }, { **self.tax_line_vals_2, 'amount_currency': -24.0, 'currency_id': self.other_currency.id, 'debit': 0.0, 'credit': 12.0, }, { **self.term_line_vals_1, 'name': '', 'amount_currency': 1128.0, 'currency_id': self.other_currency.id, 'debit': 564.0, 'credit': 0.0, 'date_maturity': move_reversal.date, }, ], { **self.move_vals, 'invoice_payment_term_id': None, 'currency_id': self.other_currency.id, 'date': move_reversal.date, 'state': 'draft', 'ref': 'Reversal of: %s, %s' % (self.invoice.name, move_reversal.reason), 'payment_state': 'not_paid', }) move_reversal = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=self.invoice.ids).create({ 'date': fields.Date.from_string('2017-01-01'), 'reason': 'no reason again', 'journal_id': self.invoice.journal_id.id, }) reversal = move_reversal.modify_moves() new_move = self.env['account.move'].browse(reversal['res_id']) self.assertEqual(self.invoice.payment_state, 'reversed', "After cancelling it with a reverse invoice, an invoice should be in 'reversed' state.") self.assertInvoiceValues(new_move, [ { **self.product_line_vals_1, 'amount_currency': 800.0, 'currency_id': self.other_currency.id, 'debit': 400.0, 'credit': 0.0, }, { **self.product_line_vals_2, 'amount_currency': 160.0, 'currency_id': self.other_currency.id, 'debit': 80.0, 'credit': 0.0, }, { **self.tax_line_vals_1, 'amount_currency': 144.0, 'currency_id': self.other_currency.id, 'debit': 72.0, 'credit': 0.0, }, { **self.tax_line_vals_2, 'amount_currency': 24.0, 'currency_id': self.other_currency.id, 'debit': 12.0, 'credit': 0.0, }, { **self.term_line_vals_1, 'name': '', 'amount_currency': -1128.0, 'currency_id': self.other_currency.id, 'debit': 0.0, 'credit': 564.0, 'date_maturity': move_reversal.date, }, ], { **self.move_vals, 'invoice_payment_term_id': self.pay_terms_a.id, 'currency_id': self.other_currency.id, 'date': move_reversal.date, 'state': 'draft', 'ref': False, 'payment_state': 'not_paid', }) def test_in_invoice_create_1(self): # Test creating an account_move with the least information. move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'price_unit': self.product_line_vals_1['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])], }), Command.create({ 'product_id': self.product_line_vals_2['product_id'], 'product_uom_id': self.product_line_vals_2['product_uom_id'], 'price_unit': self.product_line_vals_2['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])], }), ], }) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'debit': 400.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 80.0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'debit': 72.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 12.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'credit': 564.0, }, ], { **self.move_vals, 'currency_id': self.other_currency.id, 'date': fields.Date.from_string('2019-01-31'), }) def test_in_invoice_write_1(self): # Test creating an account_move with the least information. move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'price_unit': self.product_line_vals_1['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])], }), ], }) move.write({ 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_2['product_id'], 'product_uom_id': self.product_line_vals_2['product_uom_id'], 'price_unit': self.product_line_vals_2['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])], }), ], }) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'debit': 400.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 80.0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'debit': 72.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 12.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'credit': 564.0, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, }) @freeze_time('2023-02-01') def test_in_invoice_payment_register_wizard(self): # Test creating an account_move with an in_invoice_type and check payment register wizard values move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2023-01-30'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_b.id, 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'price_unit': self.product_line_vals_1['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])], }), ], }) move.action_post() with Form.from_action(self.env, move.action_register_payment()) as wiz_form: self.assertEqual(wiz_form.payment_date.strftime('%Y-%m-%d'), '2023-02-01') self.assertEqual(wiz_form.amount, 276) # First installment of 30% self.assertTrue(wiz_form.group_payment) self.assertTrue(wiz_form._get_modifier('group_payment', 'invisible')) self.assertFalse(wiz_form._get_modifier('group_payment', 'readonly')) # We can also force the registration of the payment of a draft move with a button hidden # in the gear icon menu. move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2023-01-30'), 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], }), ], }) action_register_payment = move.action_force_register_payment() # should not raise an error on non-posted move self.assertTrue(action_register_payment) wizard = self.env[action_register_payment['res_model']].with_context(action_register_payment['context']).create({}) action_create_payment = wizard.action_create_payments() payment = self.env[action_create_payment['res_model']].browse(action_create_payment['res_id']) move.action_post() self.assertFalse(move.payment_ids) # don't auto reconcile payments # Reconcile manually, the move is now fully paid (move.line_ids[-1] | payment.move_id.line_ids.filtered(lambda line: line.account_id == move.line_ids[-1].account_id)).reconcile() # If the move is already fully paid, we should alert the user with self.assertRaisesRegex(UserError, r"You can't register a payment because there is nothing left"): action_register_payment = move.action_force_register_payment() self.env[action_register_payment['res_model']].with_context(action_register_payment['context']).create({}) def test_in_invoice_switch_type_1(self): # Test creating an account_move with an in_invoice_type and switch it in an in_refund, # then switching it back to an in_invoice. move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'price_unit': self.product_line_vals_1['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])], }), Command.create({ 'product_id': self.product_line_vals_2['product_id'], 'product_uom_id': self.product_line_vals_2['product_uom_id'], 'price_unit': self.product_line_vals_2['price_unit'], 'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])], }), ], }) move.action_switch_move_type() # Switch to refund. self.assertRecordValues(move, [{'move_type': 'in_refund'}]) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -800.0, 'credit': 400.0, 'debit': 0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -160.0, 'credit': 80.0, 'debit': 0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -144.0, 'credit': 72.0, 'debit': 0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -24.0, 'credit': 12.0, 'debit': 0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 1128.0, 'debit': 564.0, 'credit': 0, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, }) move.action_switch_move_type() # Switch back to invoice. self.assertRecordValues(move, [{'move_type': 'in_invoice'}]) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'credit': 0, 'debit': 400.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'credit': 0.0, 'debit': 80, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'credit': 0, 'debit': 72, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'credit': 0, 'debit': 12, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'debit': 0, 'credit': 564, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, }) def test_in_invoice_switch_type_2(self): # Test creating an account_move with an in_invoice_type and switch it in an in_refund and a negative quantity, # then switching it back to an in_invoice. move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2019-01-01'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ Command.create({ 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'price_unit': self.product_line_vals_1['price_unit'], 'quantity': -self.product_line_vals_1['quantity'], 'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])], }), Command.create({ 'product_id': self.product_line_vals_2['product_id'], 'product_uom_id': self.product_line_vals_2['product_uom_id'], 'price_unit': self.product_line_vals_2['price_unit'], 'quantity': -self.product_line_vals_2['quantity'], 'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])], }), ], }) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -800.0, 'price_subtotal': -800.0, 'price_total': -920.0, 'credit': 400.0, 'debit': 0, 'quantity': -1.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -160.0, 'price_subtotal': -160.0, 'price_total': -208.0, 'credit': 80.0, 'debit': 0, 'quantity': -1.0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -144.0, 'credit': 72.0, 'debit': 0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -24.0, 'credit': 12.0, 'debit': 0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 1128.0, 'debit': 564.0, 'credit': 0, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, 'amount_tax' : -self.move_vals['amount_tax'], 'amount_total' : -self.move_vals['amount_total'], 'amount_untaxed' : -self.move_vals['amount_untaxed'], }) move.action_switch_move_type() # Switch to refund self.assertRecordValues(move, [{'move_type': 'in_refund'}]) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -800.0, 'credit': 400.0, 'debit': 0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -160.0, 'credit': 80.0, 'debit': 0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -144.0, 'credit': 72.0, 'debit': 0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': -24.0, 'credit': 12.0, 'debit': 0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 1128.0, 'debit': 564.0, 'credit': 0, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, 'amount_tax' : self.move_vals['amount_tax'], 'amount_total' : self.move_vals['amount_total'], 'amount_untaxed' : self.move_vals['amount_untaxed'], }) move.action_switch_move_type() # Switch back to invoice self.assertRecordValues(move, [{'move_type': 'in_invoice'}]) self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'credit': 0, 'debit': 400, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'credit': 0, 'debit': 80, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'credit': 0, 'debit': 72, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'credit': 0, 'debit': 12, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'debit': 0, 'credit': 564, }, ], { **self.move_vals, 'date': fields.Date.from_string('2019-01-31'), 'currency_id': self.other_currency.id, 'amount_tax': self.move_vals['amount_tax'], 'amount_total': self.move_vals['amount_total'], 'amount_untaxed': self.move_vals['amount_untaxed'], }) def test_in_invoice_change_period_accrual_1(self): move = self.env['account.move'].create({ 'move_type': 'in_invoice', 'date': '2017-01-01', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2017-01-01'), 'currency_id': self.other_currency.id, 'invoice_payment_term_id': self.pay_terms_a.id, 'invoice_line_ids': [ (0, None, { 'name': self.product_line_vals_1['name'], 'product_id': self.product_line_vals_1['product_id'], 'product_uom_id': self.product_line_vals_1['product_uom_id'], 'quantity': self.product_line_vals_1['quantity'], 'price_unit': self.product_line_vals_1['price_unit'], 'tax_ids': self.product_line_vals_1['tax_ids'], }), (0, None, { 'name': self.product_line_vals_2['name'], 'product_id': self.product_line_vals_2['product_id'], 'product_uom_id': self.product_line_vals_2['product_uom_id'], 'quantity': self.product_line_vals_2['quantity'], 'price_unit': self.product_line_vals_2['price_unit'], 'tax_ids': self.product_line_vals_2['tax_ids'], }), ] }) move.action_post() wizard = self.env['account.automatic.entry.wizard'].with_context( active_model='account.move.line', active_ids=move.invoice_line_ids.ids, ).create({ 'action': 'change_period', 'date': '2018-01-01', 'percentage': 60, 'journal_id': self.company_data['default_journal_misc'].id, 'expense_accrual_account': self.env['account.account'].create({ 'name': 'Accrual Expense Account', 'code': '234567', 'account_type': 'expense', 'reconcile': True, }).id, 'revenue_accrual_account': self.env['account.account'].create({ 'name': 'Accrual Revenue Account', 'code': '765432', 'account_type': 'expense', 'reconcile': True, }).id, }) wizard_res = wizard.do_action() self.assertInvoiceValues(move, [ { **self.product_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 800.0, 'debit': 400.0, 'credit': 0.0, }, { **self.product_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 160.0, 'debit': 80.0, 'credit': 0.0, }, { **self.tax_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': 144.0, 'debit': 72.0, 'credit': 0.0, }, { **self.tax_line_vals_2, 'currency_id': self.other_currency.id, 'amount_currency': 24.0, 'debit': 12.0, 'credit': 0.0, }, { **self.term_line_vals_1, 'currency_id': self.other_currency.id, 'amount_currency': -1128.0, 'debit': 0.0, 'credit': 564.0, 'date_maturity': fields.Date.from_string('2017-01-01'), }, ], { **self.move_vals, 'currency_id': self.other_currency.id, 'date': fields.Date.from_string('2017-01-01'), }) accrual_lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted('date') self.assertRecordValues(accrual_lines, [ {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': -480.0, 'debit': 0.0, 'credit': 240.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': 480.0, 'debit': 240.0, 'credit': 0.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': -96.0, 'debit': 0.0, 'credit': 48.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': 96.0, 'debit': 48.0, 'credit': 0.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': 480.0, 'debit': 240.0, 'credit': 0.0, 'account_id': self.product_line_vals_1['account_id'], 'reconciled': False}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': -480.0, 'debit': 0.0, 'credit': 240.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': 96.0, 'debit': 48.0, 'credit': 0.0, 'account_id': self.product_line_vals_2['account_id'], 'reconciled': False}, {'name': 'Cut-off BILL/2017/01/0001 60.00%', 'amount_currency': -96.0, 'debit': 0.0, 'credit': 48.0, 'account_id': wizard.expense_accrual_account.id, 'reconciled': True}, ]) def test_in_invoice_reverse_caba(self): tax_waiting_account = self.env['account.account'].create({ 'name': 'TAX_WAIT', 'code': 'TWAIT', 'account_type': 'liability_current', 'reconcile': True, }) tax_final_account = self.env['account.account'].create({ 'name': 'TAX_TO_DEDUCT', 'code': 'TDEDUCT', 'account_type': 'asset_current', }) tax_base_amount_account = self.env['account.account'].create({ 'name': 'TAX_BASE', 'code': 'TBASE', 'account_type': 'asset_current', }) self.env.company.account_cash_basis_base_account_id = tax_base_amount_account self.env.company.tax_exigibility = True tax_tags = defaultdict(dict) for line_type, repartition_type in [(l, r) for l in ('invoice', 'refund') for r in ('base', 'tax')]: tax_tags[line_type][repartition_type] = self.env['account.account.tag'].create({ 'name': '%s %s tag' % (line_type, repartition_type), 'applicability': 'taxes', 'country_id': self.env.ref('base.us').id, }) tax = self.env['account.tax'].create({ 'name': 'cash basis 10%', 'type_tax_use': 'purchase', 'amount': 10, 'tax_exigibility': 'on_payment', 'cash_basis_transition_account_id': tax_waiting_account.id, 'invoice_repartition_line_ids': [ (0, 0, { 'repartition_type': 'base', 'tag_ids': [(6, 0, tax_tags['invoice']['base'].ids)], }), (0, 0, { 'repartition_type': 'tax', 'account_id': tax_final_account.id, 'tag_ids': [(6, 0, tax_tags['invoice']['tax'].ids)], }), ], 'refund_repartition_line_ids': [ (0, 0, { 'repartition_type': 'base', 'tag_ids': [(6, 0, tax_tags['refund']['base'].ids)], }), (0, 0, { 'repartition_type': 'tax', 'account_id': tax_final_account.id, 'tag_ids': [(6, 0, tax_tags['refund']['tax'].ids)], }), ], }) # create invoice move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) move_form.partner_id = self.partner_a move_form.invoice_date = fields.Date.from_string('2017-01-01') with move_form.invoice_line_ids.new() as line_form: line_form.product_id = self.product_a line_form.tax_ids.clear() line_form.tax_ids.add(tax) invoice = move_form.save() invoice.action_post() # make payment self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'payment_date': invoice.date, })._create_payments() # check caba move partial_rec = invoice.mapped('line_ids.matched_debit_ids') caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) expected_values = [ { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': tax_base_amount_account.id, 'debit': 0.0, 'credit': 800.0, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': tax.ids, 'tax_tag_ids': tax_tags['invoice']['base'].ids, 'account_id': tax_base_amount_account.id, 'debit': 800.0, 'credit': 0.0, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': tax_waiting_account.id, 'debit': 0.0, 'credit': 80.0, }, { 'tax_line_id': tax.id, 'tax_repartition_line_id': tax.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id, 'tax_ids': [], 'tax_tag_ids': tax_tags['invoice']['tax'].ids, 'account_id': tax_final_account.id, 'debit': 80.0, 'credit': 0.0, }, ] self.assertRecordValues(caba_move.line_ids, expected_values) # unreconcile credit_aml = invoice.line_ids.filtered('credit') credit_aml.remove_move_reconcile() # check caba move reverse is same as caba move with only debit/credit inverted reversed_caba_move = self.env['account.move'].search([('reversed_entry_id', '=', caba_move.id)]) for value in expected_values: value.update({ 'debit': value['credit'], 'credit': value['debit'], }) self.assertRecordValues(reversed_caba_move.line_ids, expected_values) def test_in_invoice_with_down_payment_caba(self): tax_waiting_account = self.env['account.account'].create({ 'name': 'TAX_WAIT', 'code': 'TWAIT', 'account_type': 'liability_current', 'reconcile': True, }) tax_final_account = self.env['account.account'].create({ 'name': 'TAX_TO_DEDUCT', 'code': 'TDEDUCT', 'account_type': 'asset_current', }) default_expense_account = self.company_data['default_account_expense'] not_default_expense_account = self.env['account.account'].create({ 'name': 'NOT_DEFAULT_EXPENSE', 'code': 'NDE', 'account_type': 'expense', }) self.env.company.tax_exigibility = True tax_tags = defaultdict(dict) for line_type, repartition_type in [(l, r) for l in ('invoice', 'refund') for r in ('base', 'tax')]: tax_tags[line_type][repartition_type] = self.env['account.account.tag'].create({ 'name': '%s %s tag' % (line_type, repartition_type), 'applicability': 'taxes', 'country_id': self.env.ref('base.us').id, }) tax = self.env['account.tax'].create({ 'name': 'cash basis 10%', 'type_tax_use': 'purchase', 'amount': 10, 'tax_exigibility': 'on_payment', 'cash_basis_transition_account_id': tax_waiting_account.id, 'invoice_repartition_line_ids': [ Command.create({ 'repartition_type': 'base', 'tag_ids': [Command.set(tax_tags['invoice']['base'].ids)], }), Command.create({ 'repartition_type': 'tax', 'account_id': tax_final_account.id, 'tag_ids': [Command.set(tax_tags['invoice']['tax'].ids)], }), ], 'refund_repartition_line_ids': [ Command.create({ 'repartition_type': 'base', 'tag_ids': [Command.set(tax_tags['refund']['base'].ids)], }), Command.create({ 'repartition_type': 'tax', 'account_id': tax_final_account.id, 'tag_ids': [Command.set(tax_tags['refund']['tax'].ids)], }), ], }) # create bill # one downpayment on default account and one product line on not default account, both with the caba tax invoice = self.env['account.move'].create({ 'move_type': 'in_invoice', 'partner_id': self.partner_a.id, 'invoice_date': fields.Date.from_string('2017-01-01'), 'invoice_line_ids': [ Command.create({ 'account_id': not_default_expense_account.id, 'product_id': self.product_a.id, 'tax_ids': [Command.set(tax.ids)], }), Command.create({ 'name': 'Down payment', 'price_unit': 300, 'quantity': -1, 'tax_ids': [Command.set(tax.ids)], }), ] }) invoice.action_post() # make payment self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({ 'payment_date': invoice.date, })._create_payments() # check caba move partial_rec = invoice.mapped('line_ids.matched_debit_ids') caba_move = self.env['account.move'].search([('tax_cash_basis_rec_id', '=', partial_rec.id)]) # all amls with tax_tag should all have tax_tag_invert at False since the caba move comes from a bill expected_values = [ { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': not_default_expense_account.id, 'debit': 0.0, 'credit': 800.0, 'tax_tag_invert': False, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': tax.ids, 'tax_tag_ids': tax_tags['invoice']['base'].ids, 'account_id': not_default_expense_account.id, 'debit': 800.0, 'credit': 0.0, 'tax_tag_invert': False, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': default_expense_account.id, 'debit': 300.0, 'credit': 0.0, 'tax_tag_invert': False, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': tax.ids, 'tax_tag_ids': tax_tags['invoice']['base'].ids, 'account_id': default_expense_account.id, 'debit': 0.0, 'credit': 300.0, 'tax_tag_invert': False, }, { 'tax_line_id': False, 'tax_repartition_line_id': False, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': tax_waiting_account.id, 'debit': 0.0, 'credit': 50.0, 'tax_tag_invert': False, }, { 'tax_line_id': tax.id, 'tax_repartition_line_id': tax.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id, 'tax_ids': [], 'tax_tag_ids': tax_tags['invoice']['tax'].ids, 'account_id': tax_final_account.id, 'debit': 50.0, 'credit': 0.0, 'tax_tag_invert': False, }, ] self.assertRecordValues(caba_move.line_ids, expected_values) def test_in_invoice_line_tax_line_delete(self): with self.assertRaisesRegex(ValidationError, "You cannot delete a tax line"): with Form(self.invoice) as invoice_form: invoice_form.line_ids.remove(2) @freeze_time('2022-06-17') def test_fiduciary_mode_date_suggestion(self): """Test that the fiduciary mode invoice date suggestion is correct.""" # Fiduciary mode not enabled, no date suggestion move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertFalse(move_form.invoice_date) # Fiduciary mode enabled, date suggestion self.env.company.quick_edit_mode = "out_and_in_invoices" # We are June 17th. No Lock date. Bill Date of the most recent Vendor Bill : March 15th # ==> Default New Vendor Bill date = March 31st (last day of March) self.init_invoice(move_type='in_invoice', invoice_date='2022-03-15', products=self.product_a, post=True) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertEqual(move_form.invoice_date.strftime('%Y-%m-%d'), '2022-03-31') # We are June 17th. No Lock date. Bill Date of the most recent Vendor Bill : March 31st # ==> Default New Vendor Bill date = March 31st 2022 (last day of March) self.init_invoice(move_type='in_invoice', invoice_date='2022-03-31', products=self.product_b, post=True) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertEqual(move_form.invoice_date.strftime('%Y-%m-%d'), '2022-03-31') # We are June 17th. No Lock date. Bill Date of the most recent Vendor Bill : June 16th # ==> Default New Vendor Bill date = June 17th (today is smaller than end of June) move = self.init_invoice(move_type='in_invoice', invoice_date='2022-06-16', products=self.product_b, post=True) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertEqual(move_form.invoice_date, date.today()) move.button_draft() move.unlink() # We are June 17th. Lock date : April 30th. Bill Date of the most recent Vendor Bill : April 30th # ==> Default New Vendor Bill date = May 31st (last day of the first month not locked) self.env['account.move'].search([('state', '!=', 'posted')]).unlink() move = self.init_invoice(move_type='in_invoice', invoice_date='2022-04-30', products=self.product_a, post=True) move.company_id.fiscalyear_lock_date = fields.Date.from_string('2022-04-30') move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertEqual(move_form.invoice_date.strftime('%Y-%m-%d'), '2022-05-31') # If the user changes the invoice date, we should not override it self.init_invoice(move_type='in_invoice', invoice_date='2022-05-01', products=self.product_b, post=True) move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) self.assertEqual(move_form.invoice_date.strftime('%Y-%m-%d'), '2022-05-31') move_form.invoice_date = fields.Date.from_string('2022-05-06') move = move_form.save() self.assertEqual(move.invoice_date.strftime('%Y-%m-%d'), '2022-05-06') def _assert_payment_move_state(self, move_type, amount, counterpart_values_list, payment_state): def assert_partial(line1, line2): partial = self.env['account.partial.reconcile'].search(expression.OR([ [('debit_move_id', '=', line1.id), ('credit_move_id', '=', line2.id)], [('debit_move_id', '=', line2.id), ('credit_move_id', '=', line1.id)], ]), limit=1) self.assertTrue(partial) def create_move(move_type, amount, account=None): move_vals = { 'move_type': move_type, 'date': '2020-01-10', } if move_type in self.env['account.move'].get_sale_types(include_receipts=True) + self.env['account.move'].get_purchase_types(include_receipts=True): move_vals.update({ 'partner_id': self.partner_a.id, 'invoice_date': '2020-01-10', 'invoice_line_ids': [Command.create({'product_id': self.product_a.id, 'price_unit': amount, 'tax_ids': []})], }) else: if amount > 0.0: debit_account = account or self.company_data['default_account_receivable'] credit_account = self.company_data['default_account_revenue'] debit_balance = amount else: credit_account = account or self.company_data['default_account_receivable'] debit_account = self.company_data['default_account_revenue'] debit_balance = -amount move_vals['line_ids'] = [ Command.create({ 'name': "line1", 'account_id': debit_account.id, 'balance': debit_balance, }), Command.create({ 'name': "line2", 'account_id': credit_account.id, 'balance': -debit_balance, }), ] move = self.env['account.move'].create(move_vals) move.action_post() return move def create_payment(move, amount): self.env['account.payment.register']\ .with_context(active_ids=move.ids, active_model='account.move')\ .create({ 'amount': amount, })\ ._create_payments() def create_reverse(move, amount): move_reversal = self.env['account.move.reversal']\ .with_context(active_model='account.move', active_ids=move.ids)\ .create({ 'reason': 'no reason', 'journal_id': move.journal_id.id, }) reversal = move_reversal.refund_moves() reverse_move = self.env['account.move'].browse(reversal['res_id']) if reverse_move.move_type in ('out_refund', 'in_refund'): reverse_move.write({ 'invoice_line_ids': [ Command.update(reverse_move.invoice_line_ids.id, {'price_unit': amount}), ], }) else: line = move.line_ids.filtered(lambda line: line.account_type in ('asset_receivable', 'liability_payable')) reverse_move.with_context(skip_readonly_check=True).write({ 'line_ids': [ Command.update(line.id, {'balance': amount}), ], }) if reverse_move.state == 'draft': reverse_move.action_post() def create_statement_line(move, amount): statement_line = self.env['account.bank.statement.line'].create({ 'payment_ref': 'ref', 'journal_id': self.company_data['default_journal_bank'].id, 'amount': amount, 'date': '2020-01-10', }) _st_liquidity_lines, st_suspense_lines, _st_other_lines = statement_line\ .with_context(skip_account_move_synchronization=True)\ ._seek_for_lines() line = move.line_ids.filtered(lambda line: line.account_type in ('asset_receivable', 'liability_payable')) st_suspense_lines.account_id = line.account_id (st_suspense_lines + line).reconcile() assert_partial(st_suspense_lines, line) move = create_move(move_type, amount) line = move.line_ids.filtered(lambda line: line.account_type in ('asset_receivable', 'liability_payable')) for counterpart_move_type, counterpart_amount in counterpart_values_list: if counterpart_move_type == 'payment': create_payment(move, counterpart_amount) elif counterpart_move_type == 'reverse': create_reverse(move, counterpart_amount) elif counterpart_move_type == 'statement_line': create_statement_line(move, counterpart_amount) else: counterpart_move = create_move(counterpart_move_type, counterpart_amount, account=line.account_id) counterpart_line = counterpart_move.line_ids.filtered(lambda x: x.account_id == line.account_id) (line + counterpart_line).reconcile() assert_partial(line, counterpart_line) if payment_state == 'in_payment' and move._get_invoice_in_payment_state() == 'paid': payment_state = 'paid' self.assertRecordValues(move, [{'payment_state': payment_state}]) def test_payment_move_state(self): for move_type, amount, counterpart_values_list, payment_state in ( ('out_invoice', 1000.0, [('out_refund', 1000.0)], 'reversed'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('out_refund', 500.0)], 'reversed'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('entry', -500.0)], 'reversed'), ('out_invoice', 1000.0, [('reverse', 1000.0)], 'reversed'), ('out_receipt', 1000.0, [('out_refund', 1000.0)], 'reversed'), ('out_receipt', 1000.0, [('out_refund', 500.0), ('out_refund', 500.0)], 'reversed'), ('out_receipt', 1000.0, [('reverse', 1000.0)], 'reversed'), ('out_refund', 1000.0, [('reverse', -1000.0)], 'paid'), ('out_refund', 1000.0, [('entry', 1000.0)], 'reversed'), ('in_invoice', 1000.0, [('in_refund', 1000.0)], 'reversed'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('in_refund', 500.0)], 'reversed'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('entry', 500.0)], 'reversed'), ('in_invoice', 1000.0, [('reverse', 1000.0)], 'reversed'), ('in_receipt', 1000.0, [('in_refund', 1000.0)], 'reversed'), ('in_receipt', 1000.0, [('in_refund', 500.0), ('in_refund', 500.0)], 'reversed'), ('in_receipt', 1000.0, [('reverse', 1000.0)], 'reversed'), ('in_refund', 1000.0, [('reverse', 1000.0)], 'paid'), ('in_refund', 1000.0, [('entry', -1000.0)], 'reversed'), ('entry', 1000.0, [('entry', -1000.0)], 'not_paid'), ('entry', 1000.0, [('reverse', 1000.0)], 'not_paid'), ('out_invoice', 1000.0, [('payment', 500.0)], 'partial'), ('out_invoice', 1000.0, [('payment', 1000.0)], 'in_payment'), ('out_invoice', 1000.0, [('statement_line', 500.0)], 'partial'), ('out_invoice', 1000.0, [('statement_line', 1000.0)], 'paid'), ('out_receipt', 1000.0, [('payment', 500.0)], 'partial'), ('out_receipt', 1000.0, [('payment', 1000.0)], 'in_payment'), ('out_receipt', 1000.0, [('statement_line', 500.0)], 'partial'), ('out_receipt', 1000.0, [('statement_line', 1000.0)], 'paid'), ('out_refund', 1000.0, [('payment', 500.0)], 'partial'), ('out_refund', 1000.0, [('payment', 1000.0)], 'in_payment'), ('out_refund', 1000.0, [('statement_line', -500.0)], 'partial'), ('out_refund', 1000.0, [('statement_line', -1000.0)], 'paid'), ('in_invoice', 1000.0, [('payment', 500.0)], 'partial'), ('in_invoice', 1000.0, [('payment', 1000.0)], 'in_payment'), ('in_invoice', 1000.0, [('statement_line', -500.0)], 'partial'), ('in_invoice', 1000.0, [('statement_line', -1000.0)], 'paid'), ('in_receipt', 1000.0, [('payment', 500.0)], 'partial'), ('in_receipt', 1000.0, [('payment', 1000.0)], 'in_payment'), ('in_receipt', 1000.0, [('statement_line', -500.0)], 'partial'), ('in_receipt', 1000.0, [('statement_line', -1000.0)], 'paid'), ('in_refund', 1000.0, [('payment', 500.0)], 'partial'), ('in_refund', 1000.0, [('payment', 1000.0)], 'in_payment'), ('in_refund', 1000.0, [('statement_line', 500.0)], 'partial'), ('in_refund', 1000.0, [('statement_line', 1000.0)], 'paid'), ('entry', 1000.0, [('payment', 500.0)], 'not_paid'), ('entry', 1000.0, [('payment', 1000.0)], 'not_paid'), ('entry', 1000.0, [('statement_line', 500.0)], 'not_paid'), ('entry', 1000.0, [('statement_line', 1000.0)], 'not_paid'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('payment', 500.0)], 'in_payment'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('payment', 400.0)], 'partial'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('statement_line', 500.0)], 'paid'), ('out_invoice', 1000.0, [('out_refund', 500.0), ('statement_line', 400.0)], 'partial'), ('out_invoice', 1000.0, [('entry', -1000.0)], 'paid'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('payment', 500.0)], 'in_payment'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('payment', 400.0)], 'partial'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('statement_line', -500.0)], 'paid'), ('in_invoice', 1000.0, [('in_refund', 500.0), ('statement_line', -400.0)], 'partial'), ('in_invoice', 1000.0, [('entry', 1000.0)], 'paid'), ): with self.subTest( move_type=move_type, amount=amount, counterpart_values_list=counterpart_values_list, payment_state=payment_state, ): self._assert_payment_move_state(move_type, amount, counterpart_values_list, payment_state) def test_onchange_journal_currency(self): """ Ensure invoice currency changes on journal change, iff the journal has a currency_id set. """ chf = self.env.ref('base.CHF') eur = self.env.ref('base.EUR') journal_gen_exp = self.env['account.journal'].create({ 'name': 'Expenses (generic)', 'type': 'purchase', 'code': 'EXPGEN', }) journal_swiss_exp = self.env['account.journal'].create({ 'name': 'Expenses (CHF)', 'type': 'purchase', 'code': 'EXPCHF', 'currency_id': chf.id, }) self.invoice.currency_id = eur move_form = Form(self.invoice) invoice = move_form.save() self.assertEqual(invoice.currency_id, eur) # No change expected with generic journal move_form.journal_id = journal_gen_exp move_form.save() self.assertEqual(invoice.currency_id, eur, "Changing to a journal without set currency shouldn't affect invoice currency") # Currency should change move_form.journal_id = journal_swiss_exp move_form.save() self.assertEqual(invoice.currency_id, chf, "Changing to a journal with a set currency should change invoice currency") def test_onchange_payment_reference(self): """ Ensure payment reference propagation from move to payment term line is done correctly """ payment_term_line = self.invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term') with Form(self.invoice) as move_form: move_form.payment_reference = 'test' self.assertEqual(payment_term_line.name, 'test') with Form(self.invoice) as move_form: move_form.payment_reference = False self.assertEqual(payment_term_line.name, '', 'Payment term line was not changed') def test_taxes_onchange_product_uom_and_price_unit(self): """ Ensure that taxes are recomputed correctly when product uom and price unit are changed for users without 'uom.group_uom' group """ self.env.user.groups_id -= self.env.ref('uom.group_uom') tax = self.company_data['default_tax_purchase'] product = self.env['product.product'].create({ 'name': 'product', 'uom_id': self.env.ref('uom.product_uom_unit').id, 'standard_price': 0.0, 'supplier_taxes_id': [Command.set(tax.ids)], }) expected_values = [ { 'tax_line_id': False, 'debit': 100.0, 'credit': 0.0, }, { 'tax_line_id': tax.id, 'debit': 15.0, 'credit': 0.0, }, { 'tax_line_id': False, 'debit': 0.0, 'credit': 115.0, }, ] move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) move_form.partner_id = self.partner_a move_form.invoice_date = fields.Date.from_string('2024-03-01') # add a line (without product) with a price of 100.0 and a 15% tax with move_form.invoice_line_ids.new() as line_form: line_form.name = 'no product' line_form.price_unit = 100.0 line_form.tax_ids.add(tax) invoice = move_form.save() self.assertRecordValues(invoice.line_ids, expected_values) move_form = Form(invoice) # edit line to add a product and set price to 100.0 manually # that should recompute the taxes with move_form.invoice_line_ids.edit(0) as line_form: line_form.product_id = product line_form.price_unit = 100.0 invoice = move_form.save() self.assertRecordValues(invoice.line_ids, expected_values) def test_in_invoice_line_product_taxes_on_branch(self): """ Check taxes populated on bill lines from product on branch company. Taxes from the branch company should be taken with a fallback on parent company. """ # create the following branch hierarchy: # Parent company # |----> Branch X # |----> Branch XX company = self.env.company branch_x = self.env['res.company'].create({ 'name': 'Branch X', 'country_id': company.country_id.id, 'parent_id': company.id, }) branch_xx = self.env['res.company'].create({ 'name': 'Branch XX', 'country_id': company.country_id.id, 'parent_id': branch_x.id, }) self.cr.precommit.run() # load the CoA # create taxes for the parent company and its branches tax_groups = self.env['account.tax.group'].create([{ 'name': 'Tax Group', 'company_id': company.id, }, { 'name': 'Tax Group X', 'company_id': branch_x.id, }, { 'name': 'Tax Group XX', 'company_id': branch_xx.id, }]) tax_a = self.env['account.tax'].create({ 'name': 'Tax A', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 10, 'tax_group_id': tax_groups[0].id, 'company_id': company.id, }) tax_b = self.env['account.tax'].create({ 'name': 'Tax B', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 15, 'tax_group_id': tax_groups[0].id, 'company_id': company.id, }) tax_x = self.env['account.tax'].create({ 'name': 'Tax X', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 20, 'tax_group_id': tax_groups[1].id, 'company_id': branch_x.id, }) tax_xx = self.env['account.tax'].create({ 'name': 'Tax XX', 'type_tax_use': 'purchase', 'amount_type': 'percent', 'amount': 25, 'tax_group_id': tax_groups[2].id, 'company_id': branch_xx.id, }) # create several products with different taxes combination product_all_taxes = self.env['product.product'].create({ 'name': 'Product all taxes', 'taxes_id': [Command.set((tax_a + tax_b + tax_x + tax_xx).ids)], 'supplier_taxes_id': [Command.set((tax_a + tax_b + tax_x + tax_xx).ids)], }) product_no_xx_tax = self.env['product.product'].create({ 'name': 'Product no tax from XX', 'taxes_id': [Command.set((tax_a + tax_b + tax_x).ids)], 'supplier_taxes_id': [Command.set((tax_a + tax_b + tax_x).ids)], }) product_no_branch_tax = self.env['product.product'].create({ 'name': 'Product no tax from branch', 'taxes_id': [Command.set((tax_a + tax_b).ids)], 'supplier_taxes_id': [Command.set((tax_a + tax_b).ids)], }) product_no_tax = self.env['product.product'].create({ 'name': 'Product no tax', 'taxes_id': [], 'supplier_taxes_id': [], }) # create a bill from Branch XX with the different products: # - Product all taxes => tax from Branch XX should be set # - Product no tax from XX => tax from Branch X should be set # - Product no tax from branch => 2 taxes from parent company should be set # - Product no tax => no tax should be set bill = self.init_invoice( 'in_invoice', products=product_all_taxes + product_no_xx_tax + product_no_branch_tax + product_no_tax, company=branch_xx ) self.assertRecordValues(bill.invoice_line_ids, [ {'product_id': product_all_taxes.id, 'tax_ids': tax_xx.ids}, {'product_id': product_no_xx_tax.id, 'tax_ids': tax_x.ids}, {'product_id': product_no_branch_tax.id, 'tax_ids': (tax_a + tax_b).ids}, {'product_id': product_no_tax.id, 'tax_ids': []}, ]) def test_purchase_uom_on_vendor_bills(self): uom_gram = self.env.ref('uom.product_uom_gram') uom_kgm = self.env.ref('uom.product_uom_kgm') # product with different sale and purchase UOM product = self.env['product.product'].create({ 'name': 'product', 'uom_id': uom_gram.id, 'uom_po_id': uom_kgm.id, 'standard_price': 110.0, }) # customer invoice should have sale uom invoice = self.init_invoice(move_type='out_invoice', products=[product]) invoice_uom = invoice.invoice_line_ids[0].product_uom_id self.assertEqual(invoice_uom, uom_gram) # vendor bill should have purchase uom bill = self.init_invoice(move_type='in_invoice', products=[product]) bill_uom = bill.invoice_line_ids[0].product_uom_id self.assertEqual(bill_uom, uom_kgm) def test_manual_label_change_on_payment_term_line(self): """ Ensure label of the payment term line can be changed manually """ payment_term_line = self.invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term') index = self.invoice.line_ids.ids.index(payment_term_line.id) with Form(self.invoice) as move_form: with move_form.line_ids.edit(index) as line_form: line_form.name = 'XYZ' move_form.save() self.invoice.action_post() self.assertEqual(payment_term_line.name, 'XYZ', 'Manual name of payment term line should be kept')