Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][FIX] rma_purchase: write-off differences in price between rma line and vendor refund #518

Open
wants to merge 2 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rma_purchase/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from . import rma_operation
from . import procurement
from . import account_move
from . import account_move_line
173 changes: 172 additions & 1 deletion rma_purchase/models/account_move.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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
20 changes: 20 additions & 0 deletions rma_purchase/models/account_move_line.py
Original file line number Diff line number Diff line change
@@ -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()
78 changes: 78 additions & 0 deletions rma_purchase/tests/test_rma_stock_account_purchase.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ def setUpClass(cls):
cls.rma_operation_supplier_refund = cls.env.ref(
"rma_account.rma_operation_supplier_refund"
)
acc_type = "expense"
cls.account_price_diff = cls._create_account(
acc_type, "Refund Price Difference Expense", "rpde", cls.company, False
)

def test_01_cost_from_po_move(self):
"""
Expand Down Expand Up @@ -142,3 +146,77 @@ def test_02_return_and_refund_ref_po(self):
)
self.assertEqual(sum(grni_amls.mapped("balance")), 0.0)
self.assertTrue(all(grni_amls.mapped("reconciled")))

def test_03_return_and_refund_diff_price(self):
"""
Purchase a product.
Then create an RMA to return it and get the refund from the supplier with
a different price than the original purchase price
"""
self.product_fifo_1.categ_id.update(
{
"property_account_creditor_price_difference_categ": self.account_price_diff
}
)
self.product_fifo_1.standard_price = 1234
po = self.po_model.create(
{
"partner_id": self.partner_id.id,
}
)
pol_1 = self.pol_model.create(
{
"name": self.product_fifo_1.name,
"order_id": po.id,
"product_id": self.product_fifo_1.id,
"product_qty": 10.0,
"product_uom": self.product_fifo_1.uom_id.id,
"price_unit": 100.0,
"date_planned": Datetime.now(),
}
)
po.button_confirm()
self._do_picking(po.picking_ids)
self.product_fifo_1.standard_price = 1234 # this should not be taken
supplier_view = self.env.ref("rma_purchase.view_rma_line_form")
rma_line = Form(
self.rma_line.with_context(supplier=1).with_user(self.rma_basic_user),
view=supplier_view.id,
)
rma_line.partner_id = po.partner_id
rma_line.purchase_order_line_id = pol_1
rma_line.price_unit = 4356
rma_line.operation_id = self.rma_operation_supplier_refund
rma_line = rma_line.save()
rma_line.action_rma_to_approve()
self._deliver_rma(rma_line)

with Form(
self.env["account.move"].with_context(default_move_type="in_refund")
) as bill_form:
bill_form.partner_id = rma_line.partner_id
bill_form.invoice_date = Date.today()
bill_form.add_rma_line_id = rma_line
bill = bill_form.save()
bill_form = Form(bill)
with bill_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 110
bill = bill_form.save()
bill.action_post()
self.assertEqual(len(bill.invoice_line_ids), 1)
self.assertEqual(bill.invoice_line_ids.rma_line_id, rma_line)
grni_amls = self.env["account.move.line"].search(
[
("account_id", "=", self.account_grni.id),
("rma_line_id", "=", rma_line.id),
]
)
self.assertEqual(sum(grni_amls.mapped("balance")), 0.0)
self.assertTrue(all(grni_amls.mapped("reconciled")))
price_diff_amls = self.env["account.move.line"].search(
[
("account_id", "=", self.account_price_diff.id),
("rma_line_id", "=", rma_line.id),
]
)
self.assertEqual(sum(price_diff_amls.mapped("balance")), -100)
Loading