From 483609c479e252245f7c36dfbebac21391acae71 Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Tue, 4 Jan 2022 08:51:24 +0000 Subject: [PATCH] [FIX] stock_account: base reversed anglo-saxon AML on original one When reversing an invoice that contains some anglo-saxo AML, the new anglo-saxo AML should have the same unit price than the original one To reproduce the issue: (Need account_accountant,purchase) 1. Create a product category PC: - Costing Method: AVCO - Inventory Valuation: Automated 2. Create a product P: - Type: Storable - Product Category: PC 3. Create a purchase order PO01 with 2 x P for $10 4. Confirm PO01 and process the receipt 5. Create and Post an invoice INV01 with 2 x P - As listed in the journal items, the anglo-saxo lines have been generated with a value of $20 6. Create a purchase order PO02 with 2 x P for $20 - The cost of P becomes $15 7. Confirm PO02 and process the receipt 8. Add a credit note to INV01: - Credit Method: Partial Refund 9. Set the quantity of P to 1 10. Post the new invoice INV02 Error: The anglo-saxo lines are listed in the journal items, which is correct, but their value is $15 (the new cost of P) while it should be $10 When reversing an invoice with the "partial refund" method, the anglo-saxo lines are generated via the regular flow (as if it were a "standard" invoice). Therefore, when getting the unit price of the line, it uses the product's standard price. In such situation, it should use the unit price of the original line. OPW-2646926 OPW-2628215 X-original-commit: 87f7e78340dfe553923b2b65f61de06d5778cfbd Part-of: odoo/odoo#82978 --- addons/stock_account/models/account_move.py | 6 +- .../tests/test_stockvaluationlayer.py | 59 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/addons/stock_account/models/account_move.py b/addons/stock_account/models/account_move.py index 5a35728e127..d68f3025051 100644 --- a/addons/stock_account/models/account_move.py +++ b/addons/stock_account/models/account_move.py @@ -240,4 +240,8 @@ class AccountMoveLine(models.Model): self.ensure_one() if not self.product_id: return self.price_unit - return self.product_id.with_company(self.company_id)._stock_account_get_anglo_saxon_price_unit(uom=self.product_uom_id) + original_line = self.move_id.reversed_entry_id.line_ids.filtered(lambda l: l.is_anglo_saxon_line + and l.product_id == self.product_id and l.product_uom_id == self.product_uom_id and l.price_unit >= 0) + original_line = original_line and original_line[0] + return original_line.price_unit if original_line \ + else self.product_id.with_company(self.company_id)._stock_account_get_anglo_saxon_price_unit(uom=self.product_uom_id) diff --git a/addons/stock_account/tests/test_stockvaluationlayer.py b/addons/stock_account/tests/test_stockvaluationlayer.py index f0c735b7c00..eb095226613 100644 --- a/addons/stock_account/tests/test_stockvaluationlayer.py +++ b/addons/stock_account/tests/test_stockvaluationlayer.py @@ -901,3 +901,62 @@ class TestStockValuationChangeValuation(TestStockValuationCommon): # An accounting entry should only be created for the emptying now that the category is manual. self.assertEqual(len(self.product1.stock_valuation_layer_ids.mapped('account_move_id')), 2) self.assertEqual(len(self.product1.stock_valuation_layer_ids), 3) + +@tagged('post_install', '-at_install') +class TestAngloSaxonAccounting(TestStockValuationCommon): + @classmethod + def setUpClass(cls): + super(TestAngloSaxonAccounting, cls).setUpClass() + cls.env.company.anglo_saxon_accounting = True + cls.stock_input_account, cls.stock_output_account, cls.stock_valuation_account, cls.expense_account, cls.stock_journal = _create_accounting_data(cls.env) + cls.product1.write({ + 'property_account_expense_id': cls.expense_account.id, + }) + cls.product1.categ_id.write({ + 'property_valuation': 'real_time', + 'property_stock_account_input_categ_id': cls.stock_input_account.id, + 'property_stock_account_output_categ_id': cls.stock_output_account.id, + 'property_stock_valuation_account_id': cls.stock_valuation_account.id, + 'property_stock_journal': cls.stock_journal.id, + }) + cls.default_journal_purchase = cls.env['account.journal'].search([ + ('company_id', '=', cls.env.company.id), + ('type', '=', 'purchase') + ], limit=1) + + def test_avco_and_credit_note(self): + """ + When reversing an invoice that contains some anglo-saxo AML, the new anglo-saxo AML should have the same value + """ + self.product1.categ_id.property_cost_method = 'average' + + self._make_in_move(self.product1, 2, unit_cost=10) + + invoice_form = Form(self.env['account.move'].with_context(default_move_type='out_invoice')) + invoice_form.partner_id = self.env['res.partner'].create({'name': 'Super Client'}) + with invoice_form.invoice_line_ids.new() as invoice_line_form: + invoice_line_form.product_id = self.product1 + invoice_line_form.quantity = 2 + invoice_line_form.price_unit = 25 + invoice_line_form.account_id = self.default_journal_purchase.default_account_id + invoice = invoice_form.save() + invoice.action_post() + + self._make_in_move(self.product1, 2, unit_cost=20) + self.assertEqual(self.product1.standard_price, 15) + + refund_wizard = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=invoice.ids).create({ + 'refund_method': 'refund', + 'journal_id': invoice.journal_id.id, + }) + action = refund_wizard.reverse_moves() + reverse_invoice = self.env['account.move'].browse(action['res_id']) + with Form(reverse_invoice) as reverse_invoice_form: + with reverse_invoice_form.invoice_line_ids.edit(0) as line: + line.quantity = 1 + reverse_invoice.action_post() + + anglo_lines = reverse_invoice.line_ids.filtered(lambda l: l.is_anglo_saxon_line) + self.assertEqual(len(anglo_lines), 2) + self.assertEqual(abs(anglo_lines[0].balance), 10) + self.assertEqual(abs(anglo_lines[1].balance), 10)