[14.0][FIX] rma_account: reconcile GDNI when receiving but refund is expected

This commit is contained in:
AaronHForgeFlow
2023-01-04 11:57:36 +01:00
committed by AaronHForgeFlow
parent f6b203f37d
commit 45f5fe0047
6 changed files with 169 additions and 4 deletions

View File

@@ -27,6 +27,16 @@ class RmaOperation(models.Model):
comodel_name="account.journal",
compute="_compute_domain_valid_journal",
)
automated_refund = fields.Boolean(
help="In the scenario where a company uses anglo-saxon accounting, if "
"you receive products from a customer and don't expect to refund the customer "
"but send a replacement unit, mark this flag to be accounting consistent"
)
refund_free_of_charge = fields.Boolean(
help="In case of automated refund you should mark this option as long automated"
"refunds mean to compensate Stock Interim accounts only without hitting"
"Accounts receivable"
)
@api.onchange("type")
def _compute_domain_valid_journal(self):
@@ -39,3 +49,9 @@ class RmaOperation(models.Model):
rec.valid_refund_journal_ids = self.env["account.journal"].search(
[("type", "=", "purchase")]
)
@api.onchange("automated_refund")
def _onchange_automated_refund(self):
for rec in self:
if rec.automated_refund:
rec.refund_free_of_charge = True

View File

@@ -357,3 +357,39 @@ class RmaOrderLine(models.Model):
# We get the cost from the original invoice line
price_unit = self.account_move_line_id.price_unit
return price_unit
def _refund_at_zero_cost(self):
make_refund = (
self.env["rma.refund"]
.with_context(
{
"customer": True,
"active_ids": self.ids,
"active_model": "rma.order.line",
}
)
.create({"description": "RMA Anglosaxon Regularisation"})
)
for item in make_refund.item_ids:
item.qty_to_refund = item.line_id.qty_received - item.line_id.qty_refunded
action_refund = make_refund.invoice_refund()
refund_id = action_refund.get("res_id", False)
if refund_id:
refund = self.env["account.move"].browse(refund_id)
refund._post()
def _check_refund_zero_cost(self):
"""
In the scenario where a company uses anglo-saxon accounting, if you receive
products from a customer and don't expect to refund the customer but send a
replacement unit you still need to create a debit entry on the
Stock Interim (Delivered) account. In order to do this the best approach is
to create a customer refund from the RMA, but set as free of charge
(price unit = 0). The refund will be 0, but the Stock Interim (Delivered)
account will be posted anyways.
"""
# For some reason api.depends on qty_received is not working. Using the
# _account_entry_move method in stock move as trigger then
for rec in self.filtered(lambda l: l.operation_id.automated_refund):
if rec.qty_received > rec.qty_refunded:
rec._refund_at_zero_cost()

View File

@@ -28,4 +28,5 @@ class StockMove(models.Model):
# Eventually reconcile together the invoice and valuation accounting
# entries on the stock interim accounts
self.rma_line_id._stock_account_anglo_saxon_reconcile_valuation()
self.rma_line_id._check_refund_zero_cost()
return res

View File

@@ -25,6 +25,9 @@ class TestRmaStockAccount(TestRma):
"rma_account.rma_operation_customer_refund"
)
cls.rma_basic_user.write({"groups_id": [(4, cls.g_account_user.id)]})
cls.customer_route = cls.env.ref("rma.route_rma_customer")
cls.input_location = cls.env.ref("stock.stock_location_company")
cls.output_location = cls.env.ref("stock.stock_location_output")
# The product category created in the base module is not automated valuation
# we have to create a new category here
# Create account for Goods Received Not Invoiced
@@ -248,3 +251,110 @@ class TestRmaStockAccount(TestRma):
self.assertEqual(gdni_balance, 0.0)
# The GDNI entries should be now reconciled
self.assertEqual(all(gdni_amls.mapped("reconciled")), True)
def test_08_cost_from_move_multi_step(self):
"""
Receive a product and then return it using a multi-step route.
The Goods Delivered Not Invoiced should result in 0
"""
# Alter the customer RMA route to make it multi-step
# Get rid of the duplicated rule
self.env.ref("rma.rule_rma_customer_out_pull").active = False
self.env.ref("rma.rule_rma_customer_in_pull").active = False
cust_in_pull_rule = self.customer_route.rule_ids.filtered(
lambda r: r.location_id == self.stock_rma_location
)
cust_in_pull_rule.location_id = self.input_location
cust_out_pull_rule = self.customer_route.rule_ids.filtered(
lambda r: r.location_src_id == self.env.ref("rma.location_rma")
)
cust_out_pull_rule.location_src_id = self.output_location
cust_out_pull_rule.procure_method = "make_to_order"
self.env["stock.rule"].create(
{
"name": "RMA->Output",
"action": "pull",
"warehouse_id": self.wh.id,
"location_src_id": self.env.ref("rma.location_rma").id,
"location_id": self.output_location.id,
"procure_method": "make_to_stock",
"route_id": self.customer_route.id,
"picking_type_id": self.env.ref("stock.picking_type_internal").id,
}
)
self.env["stock.rule"].create(
{
"name": "Customers->RMA",
"action": "pull",
"warehouse_id": self.wh.id,
"location_src_id": self.customer_location.id,
"location_id": self.env.ref("rma.location_rma").id,
"procure_method": "make_to_order",
"route_id": self.customer_route.id,
"picking_type_id": self.env.ref("stock.picking_type_in").id,
}
)
# Set a standard price on the products
self.product_fifo_1.standard_price = 10
self._create_inventory(
self.product_fifo_1, 20.0, self.env.ref("stock.stock_location_customers")
)
products2move = [
(self.product_fifo_1, 3),
]
self.product_fifo_1.categ_id.rma_customer_operation_id = (
self.rma_cust_replace_op_id
)
rma_customer_id = self._create_rma_from_move(
products2move,
"customer",
self.env.ref("base.res_partner_2"),
dropship=False,
)
# Set an incorrect price in the RMA (this should not affect cost)
rma = rma_customer_id.rma_line_ids
rma.price_unit = 999
rma.action_rma_to_approve()
self._receive_rma(rma)
layers = rma.move_ids.sudo().stock_valuation_layer_ids
gdni_amls = layers.account_move_id.line_ids.filtered(
lambda l: l.account_id == self.account_gdni
)
gdni_balance = sum(gdni_amls.mapped("balance"))
self.assertEqual(len(gdni_amls), 1)
# Balance should be -30, as we have only received
self.assertEqual(gdni_balance, -30.0)
self._deliver_rma(rma)
layers = rma.move_ids.sudo().stock_valuation_layer_ids
gdni_amls = layers.account_move_id.line_ids.filtered(
lambda l: l.account_id == self.account_gdni
)
gdni_balance = sum(gdni_amls.mapped("balance"))
self.assertEqual(len(gdni_amls), 2)
# Balance should be 0, as we have received and shipped
self.assertEqual(gdni_balance, 0.0)
# The GDNI entries should be now reconciled
self.assertEqual(all(gdni_amls.mapped("reconciled")), True)
def test_05_reconcile_grni_when_no_refund(self):
"""
Test that receive and send a replacement order leaves GDNI reconciled
"""
self.product_fifo_1.standard_price = 15
rma_line = Form(self.rma_line)
rma_line.partner_id = self.partner_id
rma_line.product_id = self.product_fifo_1
rma_line.operation_id.automated_refund = True
rma_line = rma_line.save()
rma_line.action_rma_to_approve()
# receiving should trigger the refund at zero cost
self._receive_rma(rma_line)
gdni_amls = self.env["account.move.line"].search(
[
("rma_line_id", "in", rma_line.ids),
("account_id", "=", self.account_gdni.id),
]
) + rma_line.refund_line_ids.filtered(
lambda l: l.account_id == self.account_gdni
)
self.assertEqual(all(gdni_amls.mapped("reconciled")), True)

View File

@@ -21,6 +21,10 @@
<field name="valid_refund_journal_ids" invisible="1" />
<field name="refund_journal_id" />
</field>
<field name="customer_to_supplier" position="before">
<field name="automated_refund" />
<field name="refund_free_of_charge" />
</field>
</field>
</record>
</odoo>

View File

@@ -92,10 +92,6 @@ class RmaRefund(models.TransientModel):
def invoice_refund(self):
rma_line_ids = self.env["rma.order.line"].browse(self.env.context["active_ids"])
for line in rma_line_ids:
if line.refund_policy == "no":
raise ValidationError(
_("The operation is not refund for at least one line")
)
if line.state != "approved":
raise ValidationError(_("RMA %s is not approved") % line.name)
new_invoice = self.compute_refund()
@@ -111,6 +107,8 @@ class RmaRefund(models.TransientModel):
return result
def _get_refund_price_unit(self, rma):
if rma.operation_id.refund_free_of_charge:
return 0.0
price_unit = rma.price_unit
# If this references a previous invoice/bill, use the same unit price
if rma.account_move_line_id: