diff --git a/rma/__init__.py b/rma/__init__.py index 79030e0e2..1e997d120 100644 --- a/rma/__init__.py +++ b/rma/__init__.py @@ -3,3 +3,4 @@ from . import models from . import wizards +from .hooks import post_load_hook diff --git a/rma/__manifest__.py b/rma/__manifest__.py index 015ed11d1..4d193fec6 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -36,4 +36,5 @@ ], "installable": True, "application": True, + "post_load": "post_load_hook", } diff --git a/rma/hooks.py b/rma/hooks.py new file mode 100644 index 000000000..ad500a35f --- /dev/null +++ b/rma/hooks.py @@ -0,0 +1,78 @@ +# Copyright 2024 ForgeFlow, S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +from odoo.addons.stock.models.stock_picking import Picking + + +# flake8: noqa: C901 +def post_load_hook(): + def _check_entire_pack(self): + """This function check if entire packs are moved in the picking""" + for picking in self: + origin_packages = picking.move_line_ids.mapped("package_id") + for pack in origin_packages: + if picking._check_move_lines_map_quant_package(pack): + package_level_ids = picking.package_level_ids.filtered( + lambda pl: pl.package_id == pack + ) + move_lines_to_pack = picking.move_line_ids.filtered( + lambda ml: ml.package_id == pack + and not ml.result_package_id + and ml.state not in ("done", "cancel") + ) + if not package_level_ids: + package_location = ( + self._get_entire_pack_location_dest(move_lines_to_pack) + or picking.location_dest_id.id + ) + self.env["stock.package_level"].with_context( + bypass_reservation_update=package_location + ).create( + { + "picking_id": picking.id, + "package_id": pack.id, + "location_id": pack.location_id.id, + "location_dest_id": package_location, + "move_line_ids": [(6, 0, move_lines_to_pack.ids)], + "company_id": picking.company_id.id, + } + ) + # Propagate the result package in the next move for disposable packages only. + # Start Hook + if ( + pack.package_use == "disposable" + and pack.location_id.usage != "customer" + ): + # End Hook + move_lines_to_pack.with_context( + bypass_reservation_update=package_location + ).write({"result_package_id": pack.id}) + + else: + move_lines_in_package_level = move_lines_to_pack.filtered( + lambda ml: ml.move_id.package_level_id + ) + move_lines_without_package_level = ( + move_lines_to_pack - move_lines_in_package_level + ) + + if pack.package_use == "disposable": + ( + move_lines_in_package_level + | move_lines_without_package_level + ).result_package_id = pack + move_lines_in_package_level.result_package_id = pack + for ml in move_lines_in_package_level: + ml.package_level_id = ml.move_id.package_level_id.id + + move_lines_without_package_level.package_level_id = ( + package_level_ids[0] + ) + for pl in package_level_ids: + pl.location_dest_id = ( + self._get_entire_pack_location_dest(pl.move_line_ids) + or picking.location_dest_id.id + ) + + if not hasattr(Picking, "_check_entire_pack_original"): + Picking._check_entire_pack_original = Picking._check_entire_pack + Picking._check_entire_pack = _check_entire_pack diff --git a/rma/models/__init__.py b/rma/models/__init__.py index a57381a19..52b7d7d4d 100644 --- a/rma/models/__init__.py +++ b/rma/models/__init__.py @@ -11,3 +11,4 @@ from . import res_partner from . import res_company from . import res_config_settings +from . import stock_package_level diff --git a/rma/models/rma_order_line.py b/rma/models/rma_order_line.py index eb66256d2..f8718116f 100644 --- a/rma/models/rma_order_line.py +++ b/rma/models/rma_order_line.py @@ -530,6 +530,10 @@ def _default_date_rma(self): string="Under Warranty?", readonly=True, states={"draft": [("readonly", False)]} ) + def _get_stock_move_reference(self): + self.ensure_one() + return self.reference_move_id + def _prepare_rma_line_from_stock_move(self, sm, lot=False): if not self.type: self.type = self._get_default_type() diff --git a/rma/models/stock_move.py b/rma/models/stock_move.py index 04e6b2054..4688af157 100644 --- a/rma/models/stock_move.py +++ b/rma/models/stock_move.py @@ -105,3 +105,22 @@ def _update_reserved_quantity( def _prepare_merge_moves_distinct_fields(self): res = super()._prepare_merge_moves_distinct_fields() 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 + + +class StockMoveLine(models.Model): + + _inherit = "stock.move.line" + + def _should_bypass_reservation(self, location): + res = super(StockMoveLine, self)._should_bypass_reservation(location) + if self.env.context.get( + "force_no_bypass_reservation" + ) and location.usage not in ("customer", "supplier"): + return False + return res diff --git a/rma/models/stock_package_level.py b/rma/models/stock_package_level.py new file mode 100644 index 000000000..e8e14c04f --- /dev/null +++ b/rma/models/stock_package_level.py @@ -0,0 +1,20 @@ +from odoo import models + + +class StockPackageLevel(models.Model): + + _inherit = "stock.package_level" + + def write(self, values): + ctx = self.env.context.copy() + if ( + len(self) == 1 + and "location_dest_id" in values + and self.location_dest_id.id == values.get("location_dest_id") + ): + ctx.update( + { + "bypass_reservation_update": True, + } + ) + return super(StockPackageLevel, self.with_context(**ctx)).write(values) diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index 20c1caa46..61a90eeea 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -3,6 +3,7 @@ from odoo.exceptions import UserError, ValidationError from odoo.tests import Form, common +from odoo.tools.safe_eval import safe_eval class TestRma(common.TransactionCase): @@ -16,6 +17,8 @@ def setUpClass(cls): cls.make_supplier_rma = cls.env["rma.order.line.make.supplier.rma"] cls.rma_add_stock_move = cls.env["rma_add_stock_move"] cls.product_ctg_model = cls.env["product.category"] + cls.lot_obj = cls.env["stock.lot"] + cls.package_obj = cls.env["stock.quant.package"] cls.stockpicking = cls.env["stock.picking"] cls.rma = cls.env["rma.order"] cls.rma_line = cls.env["rma.order.line"] @@ -31,6 +34,8 @@ def setUpClass(cls): ) cls.product_id = cls._create_product("PT0") cls.product_1 = cls._create_product("PT1") + cls.product_1_serial = cls._create_product("PT1 Serial", "serial") + cls.product_1_lot = cls._create_product("PT1 Lot", "lot") cls.product_2 = cls._create_product("PT2") cls.product_3 = cls._create_product("PT3") cls.uom_unit = cls.env.ref("uom.product_uom_unit") @@ -125,11 +130,20 @@ def _deliver_rma(cls, rma_line_ids): ).create({}) wizard._create_picking() res = rma_line_ids.action_view_out_shipments() - picking = cls.env["stock.picking"].browse(res["res_id"]) - picking.action_assign() - for mv in picking.move_ids: - mv.quantity_done = mv.product_uom_qty - picking._action_done() + picking = cls.env["stock.picking"].browse() + if res["res_id"]: + picking = cls.env["stock.picking"].browse(res["res_id"]) + elif res.get("domain", False): + domain = res["domain"] + if isinstance(domain, str): + domain = safe_eval(domain) + picking = cls.env["stock.picking"].browse(domain[0][2]) + if picking: + for pick in picking: + pick.action_assign() + for mv in pick.move_ids: + mv.quantity_done = mv.product_uom_qty + pick._action_done() return picking @classmethod @@ -146,9 +160,14 @@ def _create_product_category( ) @classmethod - def _create_product(cls, name): + def _create_product(cls, name, tracking="none"): return cls.product_product_model.create( - {"name": name, "categ_id": cls.category.id, "type": "product"} + { + "name": name, + "categ_id": cls.category.id, + "type": "product", + "tracking": tracking, + } ) @classmethod @@ -174,7 +193,7 @@ def _do_picking(cls, picking): picking.button_validate() @classmethod - def _create_inventory(cls, product, qty, location): + def _create_inventory(cls, product, qty, location, lot_id=False, package_id=False): """ Creates inventory of a product on a specific location, this will be used eventually to create a inventory at specific cost, that will be received in @@ -187,6 +206,8 @@ def _create_inventory(cls, product, qty, location): "location_id": location.id, "product_id": product.id, "inventory_quantity": qty, + "lot_id": lot_id, + "package_id": package_id, } ) .action_apply_inventory() @@ -231,13 +252,21 @@ def _create_rma_from_move( for item in products2move: product = item[0] product_qty = item[1] - cls._create_inventory(product, product_qty, cls.stock_location) + lot_id = len(item) >= 3 and item[2] or False + origin_package_id = len(item) >= 4 and item[3] or False + destination_package_id = len(item) >= 5 and item[4] or False + cls._create_inventory( + product, product_qty, cls.stock_location, lot_id, origin_package_id + ) move_values = cls._prepare_move( product, product_qty, cls.stock_location, cls.customer_location, picking, + lot_id, + origin_package_id=origin_package_id, + destination_package_id=destination_package_id, ) moves.append(cls.env["stock.move"].create(move_values)) else: @@ -248,13 +277,21 @@ def _create_rma_from_move( for item in products2move: product = item[0] product_qty = item[1] - cls._create_inventory(product, product_qty, cls.stock_location) + lot_id = len(item) >= 3 and item[2] or False + origin_package_id = len(item) >= 4 and item[3] or False + destination_package_id = len(item) >= 5 and item[4] or False + cls._create_inventory( + product, product_qty, cls.stock_location, lot_id, origin_package_id + ) move_values = cls._prepare_move( product, product_qty, cls.supplier_location, cls.stock_rma_location, picking, + lot_id, + origin_package_id=origin_package_id, + destination_package_id=destination_package_id, ) moves.append(cls.env["stock.move"].create(move_values)) # Process the picking @@ -290,7 +327,9 @@ def _create_rma_from_move( data = ( wizard.with_user(cls.rma_basic_user) .with_context(customer=1) - ._prepare_rma_line_from_stock_move(move) + ._prepare_rma_line_from_stock_move( + move, lot=len(move.lot_ids) == 1 and move.lot_ids[0] or False + ) ) else: @@ -332,10 +371,20 @@ def _create_rma_from_move( return rma_id @classmethod - def _prepare_move(cls, product, qty, src, dest, picking_in): + def _prepare_move( + cls, + product, + qty, + src, + dest, + picking_in, + lot_id=False, + origin_package_id=False, + destination_package_id=False, + ): location_id = src.id - return { + res = { "name": product.name, "partner_id": picking_in.partner_id.id, "origin": picking_in.name, @@ -349,6 +398,29 @@ def _prepare_move(cls, product, qty, src, dest, picking_in): "picking_id": picking_in.id, "price_unit": product.standard_price, } + if lot_id or origin_package_id or destination_package_id: + res.update( + { + "move_line_ids": [ + ( + 0, + 0, + { + "picking_id": picking_in.id, + "product_id": product.id, + "product_uom_id": product.uom_id.id, + "qty_done": qty, + "lot_id": lot_id, + "package_id": origin_package_id, + "result_package_id": destination_package_id, + "location_id": location_id, + "location_dest_id": dest.id, + }, + ) + ] + } + ) + return res def _check_equal_quantity(self, qty1, qty2, msg): self.assertEqual(qty1, qty2, msg) @@ -1184,3 +1256,135 @@ def test_10_rma_cancel_line(self): self.assertEqual(second_rma_out_move_orig.state, "cancel") # check picking is not canceled because third line has not been yet. self.assertEqual(second_rma_out_move.picking_id.state, "waiting") + + def test_11_customer_rma_tracking_lot(self): + lot = self.lot_obj.create( + { + "product_id": self.product_1_lot.id, + } + ) + origin_package = self.package_obj.create({}) + destination_package = self.package_obj.create({}) + products2move = [ + (self.product_1_lot, 5, lot.id, origin_package.id, destination_package.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() + wizard = self.rma_make_picking.with_context( + **{ + "active_ids": rma.ids, + "active_model": "rma.order.line", + "picking_type": "incoming", + "active_id": rma.ids[0], + } + ).create({}) + wizard.action_create_picking() + res = rma.action_view_in_shipments() + self.assertTrue("res_id" in res, "Incorrect number of pickings" "created") + picking = self.env["stock.picking"].browse(res["res_id"]) + self.assertEqual(len(picking), 1, "Incorrect number of pickings created") + moves = picking.move_ids + self.assertEqual( + destination_package, + moves.mapped("move_line_ids.package_id"), + "Should have same package assigned", + ) + self.assertFalse( + bool(moves.mapped("move_line_ids.result_package_id")), + "Destination package should not be assigned", + ) + picking.action_assign() + for mv in picking.move_ids: + mv.quantity_done = mv.product_uom_qty + picking._action_done() + wizard = self.rma_make_picking.with_context( + **{ + "active_id": rma.ids[0], + "active_ids": rma.ids, + "active_model": "rma.order.line", + "picking_type": "outgoing", + } + ).create({}) + wizard.action_create_picking() + res = rma.action_view_out_shipments() + picking = self.env["stock.picking"].browse(res["res_id"]) + picking.action_assign() + for mv in picking.move_ids: + mv.quantity_done = mv.product_uom_qty + picking._action_done() + self.assertEqual(picking.state, "done", "Final picking should has done state") + + def test_12_customer_rma_tracking_serial(self): + lot = self.lot_obj.create( + { + "product_id": self.product_1_serial.id, + } + ) + origin_package = self.package_obj.create({}) + destination_package = self.package_obj.create({}) + products2move = [ + ( + self.product_1_serial, + 1, + lot.id, + origin_package.id, + destination_package.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() + wizard = self.rma_make_picking.with_context( + **{ + "active_ids": rma.ids, + "active_model": "rma.order.line", + "picking_type": "incoming", + "active_id": rma.ids[0], + } + ).create({}) + wizard.action_create_picking() + res = rma.action_view_in_shipments() + self.assertTrue("res_id" in res, "Incorrect number of pickings" "created") + picking = self.env["stock.picking"].browse(res["res_id"]) + self.assertEqual(len(picking), 1, "Incorrect number of pickings created") + moves = picking.move_ids + self.assertEqual( + destination_package, + moves.mapped("move_line_ids.package_id"), + "Should have same package assigned", + ) + self.assertFalse( + bool(moves.mapped("move_line_ids.result_package_id")), + "Destination package should not be assigned", + ) + picking.action_assign() + for mv in picking.move_ids: + mv.quantity_done = mv.product_uom_qty + picking._action_done() + wizard = self.rma_make_picking.with_context( + **{ + "active_id": rma.ids[0], + "active_ids": rma.ids, + "active_model": "rma.order.line", + "picking_type": "outgoing", + } + ).create({}) + wizard.action_create_picking() + res = rma.action_view_out_shipments() + picking = self.env["stock.picking"].browse(res["res_id"]) + picking.action_assign() + for mv in picking.move_ids: + mv.quantity_done = mv.product_uom_qty + picking._action_done() + self.assertEqual(picking.state, "done", "Final picking should has done state") diff --git a/rma/wizards/rma_make_picking.py b/rma/wizards/rma_make_picking.py index 637caea9b..c0ea33f75 100644 --- a/rma/wizards/rma_make_picking.py +++ b/rma/wizards/rma_make_picking.py @@ -200,6 +200,21 @@ def _create_picking(self): procurements.extend(procurement) return procurements + def _is_final_step(self, move): + """This function helps to know if wizard is called to finish process of rma, + customer is delivery return, and supplier is receipt return""" + if ( + move.rma_line_id.type == "customer" + and self.env.context.get("picking_type") == "outgoing" + ): + return True + if ( + move.rma_line_id.type == "supplier" + and self.env.context.get("picking_type") == "incoming" + ): + return True + return False + def action_create_picking(self): self._create_picking() move_line_model = self.env["stock.move.line"] @@ -219,23 +234,68 @@ def action_create_picking(self): and x.rma_line_id.lot_id ): # Force the reservation of the RMA specific lot for incoming shipments. + is_final_step = self._is_final_step(move) move.move_line_ids.unlink() + reference_moves = ( + not is_final_step + and move.rma_line_id._get_stock_move_reference() + or self.env["stock.move"] + ) + package = reference_moves.mapped("move_line_ids.result_package_id") + quants = self.env["stock.quant"]._gather( + move.product_id, + move.location_id, + lot_id=move.rma_line_id.lot_id, + package_id=len(package) == 1 and package or False, + ) + move_line_data = move._prepare_move_line_vals( + reserved_quant=(len(quants) == 1) and quants or False + ) + move_line_data.update( + { + "qty_done": 0, + } + ) + if move.rma_line_id.lot_id and not quants: + # CHECK ME: force al least has lot assigned if quant is not found + move_line_data.update( + { + "lot_id": move.rma_line_id.lot_id.id, + } + ) if move.product_id.tracking == "serial": move.write( { - "lot_ids": [(6, 0, move.rma_line_id.lot_id.ids)], + "lot_ids": move.rma_line_id.lot_id.ids, } ) - quants = self.env["stock.quant"]._gather( - move.product_id, move.location_id, lot_id=move.rma_line_id.lot_id - ) - move.move_line_ids.write( + move_line_data.update( { - "reserved_uom_qty": 1 if picking_type == "incoming" else 0, - "qty_done": 0, - "package_id": len(quants) == 1 and quants.package_id.id, + "reserved_uom_qty": 1.0, } ) + if move.move_line_ids: + move.move_line_ids.with_context( + bypass_reservation_update=True + ).write( + { + "lot_id": move_line_data.get("lot_id"), + "package_id": move_line_data.get("package_id"), + "result_package_id": move_line_data.get( + "result_package_id", False + ), + "reserved_uom_qty": 1.0, + } + ) + if ( + len(quants) == 1 + and quants.reserved_quantity == 0 + and quants.quantity == 1 + and quants.location_id.usage not in ("customer", "supplier") + ): + quants.sudo().write( + {"reserved_quantity": quants.reserved_quantity + 1} + ) elif move.product_id.tracking == "lot": if picking_type == "incoming": qty = self.item_ids.filtered( @@ -245,15 +305,12 @@ def action_create_picking(self): qty = self.item_ids.filtered( lambda x: x.line_id.id == move.rma_line_id.id ).qty_to_deliver - move_line_data = move._prepare_move_line_vals() move_line_data.update( { - "lot_id": move.rma_line_id.lot_id.id, - "product_uom_id": move.product_id.uom_id.id, - "qty_done": 0, "reserved_uom_qty": qty if picking_type == "incoming" else 0, } ) + if not move.move_line_ids: move_line_model.create(move_line_data) pickings.with_context(force_no_bypass_reservation=True).action_assign() return action diff --git a/rma_sale/models/rma_order_line.py b/rma_sale/models/rma_order_line.py index 86c9aff46..7817a0d3b 100644 --- a/rma_sale/models/rma_order_line.py +++ b/rma_sale/models/rma_order_line.py @@ -81,6 +81,9 @@ def _compute_sales_count(self): readonly=False, ) sales_count = fields.Integer(compute="_compute_sales_count", string="# of Sales") + sale_line_domain_ids = fields.Many2many( + comodel_name="sale.order.line", compute="_compute_sale_line_domain" + ) @api.onchange("product_id", "partner_id") def _onchange_product_id(self): @@ -98,6 +101,34 @@ def _onchange_product_id(self): res["domain"]["sale_line_id"] = domain return res + def _get_stock_move_reference(self): + self.ensure_one() + move = self.reference_move_id + if self.sale_line_id: + # CHECK ME: backorder cases can be more than one move + sale_moves = self.sale_line_id.move_ids.filtered( + lambda x: x.location_dest_id.usage == "customer" and x.state == "done" + ) + if sale_moves: + return sale_moves + return move + + @api.depends("product_id", "partner_id") + def _compute_sale_line_domain(self): + line_model = self.env["sale.order.line"] + for rec in self: + domain = [] + if rec.partner_id: + domain = [ + "|", + ("order_id.partner_id", "=", rec.partner_id.id), + ("order_id.partner_id", "child_of", rec.partner_id.id), + ] + if rec.product_id: + domain.append(("product_id", "=", rec.product_id.id)) + lines = domain and line_model.search(domain) or line_model.browse() + rec.sale_line_domain_ids = lines.ids + @api.onchange("operation_id") def _onchange_operation_id(self): res = super(RmaOrderLine, self)._onchange_operation_id() diff --git a/rma_sale/tests/__init__.py b/rma_sale/tests/__init__.py index 2d622b802..13a4f1418 100644 --- a/rma_sale/tests/__init__.py +++ b/rma_sale/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_rma_sale from . import test_rma_stock_account_sale +from . import test_rma_sale_tracking diff --git a/rma_sale/tests/test_rma_sale_tracking.py b/rma_sale/tests/test_rma_sale_tracking.py new file mode 100644 index 000000000..84dd709e4 --- /dev/null +++ b/rma_sale/tests/test_rma_sale_tracking.py @@ -0,0 +1,153 @@ +from odoo.addons.rma.tests.test_rma import TestRma + + +class TestRmaSaleTracking(TestRma): + @classmethod + def setUpClass(cls): + super(TestRmaSaleTracking, cls).setUpClass() + cls.rma_obj = cls.env["rma.order"] + cls.rma_line_obj = cls.env["rma.order.line"] + cls.rma_op_obj = cls.env["rma.operation"] + cls.rma_add_sale_wiz = cls.env["rma_add_sale"] + cls.rma_make_sale_wiz = cls.env["rma.order.line.make.sale.order"] + cls.so_obj = cls.env["sale.order"] + cls.sol_obj = cls.env["sale.order.line"] + cls.product_obj = cls.env["product.product"] + cls.partner_obj = cls.env["res.partner"] + cls.rma_route_cust = cls.env.ref("rma.route_rma_customer") + cls.customer1 = cls.partner_obj.create({"name": "Customer 1"}) + cls.product_lot_2 = cls._create_product("PT2 Lot", "lot") + cls.product_serial_2 = cls._create_product("PT2 Serial", "serial") + + cls.so = cls.so_obj.create( + { + "partner_id": cls.customer1.id, + "partner_invoice_id": cls.customer1.id, + "partner_shipping_id": cls.customer1.id, + "order_line": [ + ( + 0, + 0, + { + "name": cls.product_serial_2.name, + "product_id": cls.product_serial_2.id, + "product_uom_qty": 1.0, + "product_uom": cls.product_serial_2.uom_id.id, + "price_unit": cls.product_serial_2.list_price, + }, + ), + ( + 0, + 0, + { + "name": cls.product_lot_2.name, + "product_id": cls.product_lot_2.id, + "product_uom_qty": 18.0, + "product_uom": cls.product_lot_2.uom_id.id, + "price_unit": cls.product_lot_2.list_price, + }, + ), + ], + "pricelist_id": cls.env.ref("product.list0").id, + } + ) + cls.so.action_confirm() + + cls.serial = cls.lot_obj.create( + { + "product_id": cls.product_serial_2.id, + } + ) + cls.lot = cls.lot_obj.create( + { + "product_id": cls.product_lot_2.id, + } + ) + + cls.package_1 = cls.package_obj.create({}) + cls.package_2 = cls.package_obj.create({}) + cls.package_3 = cls.package_obj.create({}) + cls.package_4 = cls.package_obj.create({}) + + cls._create_inventory( + cls.product_serial_2, 1, cls.stock_location, cls.serial.id, cls.package_1.id + ) + + cls._create_inventory( + cls.product_lot_2, 1, cls.stock_location, cls.lot.id, cls.package_3.id + ) + + picking = cls.so.picking_ids + + picking.action_assign() + + for move in picking.move_ids: + if move.product_id.id == cls.product_serial_2.id: + move.move_line_ids.write({"result_package_id": cls.package_2.id}) + if move.product_id.id == cls.product_lot_2.id: + move.move_line_ids.write({"result_package_id": cls.package_4.id}) + + cls._do_picking(picking) + + # Create RMA group and operation: + cls.rma_group = cls.rma_obj.create({"partner_id": cls.customer1.id}) + cls.operation_1 = cls.rma_op_obj.create( + { + "code": "TEST", + "name": "Sale afer receive", + "type": "customer", + "receipt_policy": "ordered", + "sale_policy": "received", + "in_route_id": cls.rma_route_cust.id, + "out_route_id": cls.rma_route_cust.id, + } + ) + + add_sale = cls.rma_add_sale_wiz.with_context( + **{ + "customer": True, + "active_ids": cls.rma_group.id, + "active_model": "rma.order", + } + ).create( + {"sale_id": cls.so.id, "sale_line_ids": [(6, 0, cls.so.order_line.ids)]} + ) + add_sale.add_lines() + + def test_01_customer_rma_tracking(self): + rma_serial = self.rma_group.rma_line_ids.filtered( + lambda r: r.product_id == self.product_serial_2 + ) + rma_lot = self.rma_group.rma_line_ids.filtered( + lambda r: r.product_id == self.product_lot_2 + ) + for rma in rma_serial + rma_lot: + wizard = self.rma_make_picking.with_context( + **{ + "active_ids": rma.ids, + "active_model": "rma.order.line", + "picking_type": "incoming", + "active_id": rma.ids[0], + } + ).create({}) + wizard.action_create_picking() + res = rma.action_view_in_shipments() + self.assertTrue("res_id" in res, "Incorrect number of pickings" "created") + picking = self.env["stock.picking"].browse(res["res_id"]) + self.assertEqual(len(picking), 1, "Incorrect number of pickings created") + moves = picking.move_lines + self.asserTrue( + bool(moves.mapped("move_line_ids.package_id")), + "Should have same package assigned", + ) + self.assertFalse( + bool(moves.mapped("move_line_ids.result_package_id")), + "Destination package should not be assigned", + ) + picking.action_assign() + for mv in picking.move_ids: + mv.quantity_done = mv.product_uom_qty + picking._action_done() + self.assertEqual( + picking.state, "done", "Final picking should has done state" + )