From 2ee9d43a9837dca7922c4a3afa03bd7d3c203b5c Mon Sep 17 00:00:00 2001 From: mariadforgeflow Date: Mon, 27 Feb 2023 13:28:52 +0100 Subject: [PATCH] [FIX] rma_purchase: write-off differences in price between rma line and vendor refund --- rma_purchase/models/__init__.py | 1 + rma_purchase/models/account_move.py | 173 +++++++++++++++++- rma_purchase/models/account_move_line.py | 20 ++ .../tests/test_rma_stock_account_purchase.py | 2 +- 4 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 rma_purchase/models/account_move_line.py diff --git a/rma_purchase/models/__init__.py b/rma_purchase/models/__init__.py index 39c026827..25f674834 100644 --- a/rma_purchase/models/__init__.py +++ b/rma_purchase/models/__init__.py @@ -5,3 +5,4 @@ from . import rma_operation from . import procurement from . import account_move +from . import account_move_line diff --git a/rma_purchase/models/account_move.py b/rma_purchase/models/account_move.py index 8b0230906..6dc25001d 100644 --- a/rma_purchase/models/account_move.py +++ b/rma_purchase/models/account_move.py @@ -1,7 +1,8 @@ # Copyright 2017-22 ForgeFlow S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) -from odoo import models +from odoo import fields, models +from odoo.tools.float_utils import float_compare class AccountMove(models.Model): @@ -40,3 +41,173 @@ def action_post(self): ) amls.reconcile() return res + + def _stock_account_prepare_anglo_saxon_in_lines_vals(self): + lines_vals_list_rma = [] + rma_refunds = self.env["account.move"] + price_unit_prec = self.env["decimal.precision"].precision_get("Product Price") + for move in self: + if ( + move.move_type != "in_refund" + or not move.company_id.anglo_saxon_accounting + ): + continue + move = move.with_company(move.company_id) + for line in move.invoice_line_ids.filtered(lambda l: l.rma_line_id): + # Filter out lines being not eligible for price difference. + # Moreover, this function is used for standard cost method only. + if ( + line.product_id.type != "product" + or line.product_id.valuation != "real_time" + ): + continue + + # Retrieve accounts needed to generate the price difference. + debit_expense_account = line._get_price_diff_account() + if not debit_expense_account: + continue + # Retrieve stock valuation moves. + valuation_stock_moves = ( + self.env["stock.move"].search( + [ + ("rma_line_id", "=", line.rma_line_id.id), + ("state", "=", "done"), + ("product_qty", "!=", 0.0), + ] + ) + if line.rma_line_id + else self.env["stock.move"] + ) + + if line.product_id.cost_method != "standard" and line.rma_line_id: + if move.move_type == "in_refund": + valuation_stock_moves = valuation_stock_moves.filtered( + lambda stock_move: stock_move._is_out() + ) + else: + valuation_stock_moves = valuation_stock_moves.filtered( + lambda stock_move: stock_move._is_in() + ) + + if not valuation_stock_moves: + continue + + ( + valuation_price_unit_total, + valuation_total_qty, + ) = valuation_stock_moves._get_valuation_price_and_qty( + line, move.currency_id + ) + valuation_price_unit = ( + valuation_price_unit_total / valuation_total_qty + ) + valuation_price_unit = line.product_id.uom_id._compute_price( + valuation_price_unit, line.product_uom_id + ) + else: + # Valuation_price unit is always expressed in invoice currency, + # so that it can always be computed with the good rate + price_unit = line.product_id.uom_id._compute_price( + line.product_id.standard_price, line.product_uom_id + ) + price_unit = ( + -price_unit + if line.move_id.move_type == "in_refund" + else price_unit + ) + valuation_date = ( + valuation_stock_moves + and max(valuation_stock_moves.mapped("date")) + or move.date + ) + valuation_price_unit = line.company_currency_id._convert( + price_unit, + move.currency_id, + move.company_id, + valuation_date, + round=False, + ) + + price_unit = line._get_gross_unit_price() + + price_unit_val_dif = abs(price_unit) - valuation_price_unit + relevant_qty = line.quantity + price_subtotal = relevant_qty * price_unit_val_dif + # We consider there is a price difference if the subtotal is not zero. In case a + # discount has been applied, we can't round the price unit anymore, and hence we + # can't compare them. + if ( + not move.currency_id.is_zero(price_subtotal) + and float_compare( + line["price_unit"], + line.price_unit, + precision_digits=price_unit_prec, + ) + == 0 + ): + # Add price difference account line. + vals = { + "name": line.name[:64], + "move_id": move.id, + "partner_id": line.partner_id.id + or move.commercial_partner_id.id, + "currency_id": line.currency_id.id, + "product_id": line.product_id.id, + "product_uom_id": line.product_uom_id.id, + "quantity": relevant_qty, + "price_unit": price_unit_val_dif, + "price_subtotal": relevant_qty * price_unit_val_dif, + "amount_currency": relevant_qty + * price_unit_val_dif + * line.move_id.direction_sign, + "balance": line.currency_id._convert( + relevant_qty + * price_unit_val_dif + * line.move_id.direction_sign, + line.company_currency_id, + line.company_id, + fields.Date.today(), + ), + "account_id": debit_expense_account.id, + "analytic_distribution": line.analytic_distribution, + "display_type": "cogs", + "rma_line_id": line.rma_line_id.id, + } + + lines_vals_list_rma.append(vals) + + # Correct the amount of the current line. + vals = { + "name": line.name[:64], + "move_id": move.id, + "partner_id": line.partner_id.id + or move.commercial_partner_id.id, + "currency_id": line.currency_id.id, + "product_id": line.product_id.id, + "product_uom_id": line.product_uom_id.id, + "quantity": relevant_qty, + "price_unit": -price_unit_val_dif, + "price_subtotal": relevant_qty * -price_unit_val_dif, + "amount_currency": relevant_qty + * -price_unit_val_dif + * line.move_id.direction_sign, + "balance": line.currency_id._convert( + relevant_qty + * -price_unit_val_dif + * line.move_id.direction_sign, + line.company_currency_id, + line.company_id, + fields.Date.today(), + ), + "account_id": line.account_id.id, + "analytic_distribution": line.analytic_distribution, + "display_type": "cogs", + "rma_line_id": line.rma_line_id.id, + } + lines_vals_list_rma.append(vals) + rma_refunds |= move + lines_vals_list = super( + AccountMove, self - rma_refunds + )._stock_account_prepare_anglo_saxon_in_lines_vals() + lines_vals_list += lines_vals_list_rma + return lines_vals_list diff --git a/rma_purchase/models/account_move_line.py b/rma_purchase/models/account_move_line.py new file mode 100644 index 000000000..a838457cc --- /dev/null +++ b/rma_purchase/models/account_move_line.py @@ -0,0 +1,20 @@ +from odoo import models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + def _get_price_diff_account(self): + # force the price difference account to be taken from the price + # different properties as they was in the previous Odoo versions + self.ensure_one() + if self.product_id.cost_method != "standard": + debit_pdiff_account = ( + self.product_id.property_account_creditor_price_difference + or self.product_id.categ_id.property_account_creditor_price_difference_categ + ) + debit_pdiff_account = self.move_id.fiscal_position_id.map_account( + debit_pdiff_account + ) + return debit_pdiff_account + return super()._get_price_diff_account() diff --git a/rma_purchase/tests/test_rma_stock_account_purchase.py b/rma_purchase/tests/test_rma_stock_account_purchase.py index e00e19daf..b6142226d 100644 --- a/rma_purchase/tests/test_rma_stock_account_purchase.py +++ b/rma_purchase/tests/test_rma_stock_account_purchase.py @@ -17,7 +17,7 @@ def setUpClass(cls): cls.rma_operation_supplier_refund = cls.env.ref( "rma_account.rma_operation_supplier_refund" ) - acc_type = cls._create_account_type("expense", "other") + acc_type = "expense" cls.account_price_diff = cls._create_account( acc_type, "Refund Price Difference Expense", "rpde", cls.company, False )