[FIX] fix rma management using multi step routes

This commit is contained in:
Jordi Ballester Alomar
2022-11-24 18:04:05 +01:00
parent 4dd2316d67
commit dd29c431e9
6 changed files with 203 additions and 24 deletions

View File

@@ -70,6 +70,18 @@ class RmaOrderLine(models.Model):
moves |= moves moves |= moves
return moves return moves
@api.model
def _get_out_moves(self):
moves = self.env["stock.move"]
for move in self.move_ids:
first_usage = move._get_first_usage()
last_usage = move._get_last_usage()
if first_usage == "internal" and last_usage != "internal":
moves |= move
elif first_usage == "supplier" and last_usage == "customer":
moves |= moves
return moves
@api.model @api.model
def _get_out_pickings(self): def _get_out_pickings(self):
pickings = self.env["stock.picking"] pickings = self.env["stock.picking"]
@@ -97,12 +109,10 @@ class RmaOrderLine(models.Model):
product_obj = self.env["uom.uom"] product_obj = self.env["uom.uom"]
qty = 0.0 qty = 0.0
if direction == "in": if direction == "in":
op = ops["="] moves = rec._get_in_moves()
else: else:
op = ops["!="] moves = rec._get_out_moves()
for move in rec.move_ids.filtered( for move in moves.filtered(lambda m: m.state in states):
lambda m: m.state in states and op(m.location_id.usage, rec.type)
):
# If the move is part of a chain don't count it # If the move is part of a chain don't count it
if direction == "out" and move.move_orig_ids: if direction == "out" and move.move_orig_ids:
continue continue

View File

@@ -104,3 +104,9 @@ class StockMove(models.Model):
def _prepare_merge_moves_distinct_fields(self): def _prepare_merge_moves_distinct_fields(self):
res = super()._prepare_merge_moves_distinct_fields() res = super()._prepare_merge_moves_distinct_fields()
return res + ["rma_line_id"] return res + ["rma_line_id"]
def _prepare_procurement_values(self):
self.ensure_one()
res = super(StockMove, self)._prepare_procurement_values()
res["rma_line_id"] = self.rma_line_id.id
return res

View File

@@ -28,9 +28,14 @@ class StockRule(models.Model):
company_id, company_id,
values, values,
) )
line = self.env["rma.order.line"]
if "rma_line_id" in values: if "rma_line_id" in values:
line = values.get("rma_line_id") line = values.get("rma_line_id")
line = self.env["rma.order.line"].browse([line]) line = self.env["rma.order.line"].browse([line])
elif "group_id" in values:
pg = values["group_id"]
line = pg.rma_line_id
if line:
res["rma_line_id"] = line.id res["rma_line_id"] = line.id
if line.delivery_address_id: if line.delivery_address_id:
res["partner_id"] = line.delivery_address_id.id res["partner_id"] = line.delivery_address_id.id

View File

@@ -26,6 +26,9 @@ class TestRma(common.SavepointCase):
cls.rma_cust_replace_op_id = cls.env.ref("rma.rma_operation_customer_replace") cls.rma_cust_replace_op_id = cls.env.ref("rma.rma_operation_customer_replace")
cls.rma_sup_replace_op_id = cls.env.ref("rma.rma_operation_supplier_replace") cls.rma_sup_replace_op_id = cls.env.ref("rma.rma_operation_supplier_replace")
cls.rma_ds_replace_op_id = cls.env.ref("rma.rma_operation_ds_replace") cls.rma_ds_replace_op_id = cls.env.ref("rma.rma_operation_ds_replace")
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")
cls.category = cls._create_product_category( cls.category = cls._create_product_category(
"one_step", cls.rma_cust_replace_op_id, cls.rma_sup_replace_op_id "one_step", cls.rma_cust_replace_op_id, cls.rma_sup_replace_op_id
) )
@@ -82,7 +85,7 @@ class TestRma(common.SavepointCase):
@classmethod @classmethod
def _create_user(cls, login, groups, company): def _create_user(cls, login, groups, company):
group_ids = [group.id for group in groups] group_ids = [group.id for group in groups]
user = cls.res_users_model.create( user = cls.res_users_model.with_context(no_reset_password=True).create(
{ {
"name": login, "name": login,
"login": login, "login": login,
@@ -105,13 +108,15 @@ class TestRma(common.SavepointCase):
} }
).create({}) ).create({})
wizard._create_picking() wizard._create_picking()
res = rma_line_ids.action_view_in_shipments() pickings = rma_line_ids._get_in_pickings()
picking = cls.env["stock.picking"].browse(res["res_id"]) pickings.action_assign()
picking.action_assign() for picking in pickings:
for mv in picking.move_lines: for mv in picking.move_lines:
mv.quantity_done = mv.product_uom_qty mv.quantity_done = mv.product_uom_qty
picking._action_done() # In case of two step pickings, ship in two steps:
return picking while pickings.filtered(lambda p: p.state == "assigned"):
pickings._action_done()
return pickings
@classmethod @classmethod
def _deliver_rma(cls, rma_line_ids): def _deliver_rma(cls, rma_line_ids):
@@ -124,13 +129,13 @@ class TestRma(common.SavepointCase):
} }
).create({}) ).create({})
wizard._create_picking() wizard._create_picking()
res = rma_line_ids.action_view_out_shipments() pickings = rma_line_ids._get_out_pickings()
picking = cls.env["stock.picking"].browse(res["res_id"]) pickings.action_assign()
picking.action_assign() for picking in pickings:
for mv in picking.move_lines: for mv in picking.move_lines:
mv.quantity_done = mv.product_uom_qty mv.quantity_done = mv.product_uom_qty
picking._action_done() pickings._action_done()
return picking return pickings
@classmethod @classmethod
def _create_product_category( def _create_product_category(
@@ -773,9 +778,7 @@ class TestRma(common.SavepointCase):
} }
).create({}) ).create({})
wizard._create_picking() wizard._create_picking()
res = self.rma_supplier_id.rma_line_ids.action_view_out_shipments() picking = self.rma_supplier_id.rma_line_ids._get_out_pickings()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
picking = self.env["stock.picking"].browse(res["res_id"])
moves = picking.move_lines moves = picking.move_lines
self.assertEqual(len(moves), 3, "Incorrect number of moves created") self.assertEqual(len(moves), 3, "Incorrect number of moves created")
@@ -1073,3 +1076,73 @@ class TestRma(common.SavepointCase):
).create({}) ).create({})
with self.assertRaisesRegex(ValidationError, "No quantity to transfer"): with self.assertRaisesRegex(ValidationError, "No quantity to transfer"):
wizard._create_picking() wizard._create_picking()
def test_08_customer_rma_multi_step(self):
"""
Receive a product and then return it using a multi-step route.
"""
# 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": "Output->RMA",
"action": "pull",
"warehouse_id": self.wh.id,
"location_src_id": self.input_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_internal").id,
}
)
# Set a standard price on the products
self.product_1.standard_price = 10
self._create_inventory(
self.product_1, 20.0, self.env.ref("stock.stock_location_customers")
)
products2move = [
(self.product_1, 3),
]
self.product_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,
)
rma = rma_customer_id.rma_line_ids
rma.action_rma_to_approve()
self.assertEqual(rma.qty_to_receive, 3)
self.assertEqual(rma.qty_received, 0)
self._receive_rma(rma)
self.assertEqual(len(rma.move_ids), 2)
self.assertEqual(rma.qty_to_receive, 0)
self.assertEqual(rma.qty_received, 3)
self.assertEqual(rma.qty_to_deliver, 3)
self._deliver_rma(rma)
self.assertEqual(rma.qty_to_deliver, 0)
self.assertEqual(rma.qty_delivered, 3)
self.assertEqual(len(rma.move_ids), 4)

View File

@@ -73,8 +73,9 @@ class RmaMakePicking(models.TransientModel):
"partner_id": item.line_id.partner_id.id, "partner_id": item.line_id.partner_id.id,
"name": item.line_id.rma_id.name or item.line_id.name, "name": item.line_id.rma_id.name or item.line_id.name,
"rma_id": item.line_id.rma_id and item.line_id.rma_id.id or False, "rma_id": item.line_id.rma_id and item.line_id.rma_id.id or False,
"rma_line_id": item.line_id.id if not item.line_id.rma_id else False,
} }
if not item.line_id.rma_id:
group_data["rma_line_id"] = item.line_id and item.line_id.id or False
return group_data return group_data
@api.model @api.model

View File

@@ -240,3 +240,87 @@ class TestRmaStockAccount(TestRma):
self.assertEqual(gdni_balance, 0.0) self.assertEqual(gdni_balance, 0.0)
# The GDNI entries should be now reconciled # The GDNI entries should be now reconciled
self.assertEqual(all(gdni_amls.mapped("reconciled")), True) self.assertEqual(all(gdni_amls.mapped("reconciled")), True)
def test_04_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": "Output->RMA",
"action": "pull",
"warehouse_id": self.wh.id,
"location_src_id": self.input_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_internal").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)