# Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import Command from odoo.exceptions import UserError, ValidationError from odoo.tests import Form, new_test_user, tagged, users from odoo.tools import mute_logger from odoo.addons.base.tests.common import BaseCommon from .. import hooks class TestRma(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() if not cls.env.company.chart_template_id: # Load a CoA if there's none in current company coa = cls.env.ref("l10n_generic_coa.configurable_chart_template", False) if not coa: # Load the first available CoA coa = cls.env["account.chart.template"].search( [("visible", "=", True)], limit=1 ) coa.try_loading(company=cls.env.company, install_demo=False) cls.user_rma = new_test_user( cls.env, login="user_rma", groups="rma.rma_group_user_own,stock.group_stock_user", ) cls.res_partner = cls.env["res.partner"] cls.product_product = cls.env["product.product"] cls.company = cls.env.user.company_id cls.warehouse_company = cls.env["stock.warehouse"].search( [("company_id", "=", cls.company.id)], limit=1 ) cls.rma_loc = cls.warehouse_company.rma_loc_id cls.product = cls.product_product.create( {"name": "Product test 1", "type": "product"} ) cls.product_2 = cls.product_product.create( {"name": "Product test 2", "type": "product"} ) cls.account_receiv = cls.env["account.account"].create( { "name": "Receivable", "code": "RCV00", "account_type": "asset_receivable", "reconcile": True, } ) cls.partner = cls.res_partner.create( { "name": "Partner test", "property_account_receivable_id": cls.account_receiv.id, "property_payment_term_id": cls.env.ref( "account.account_payment_term_30days" ).id, } ) cls.partner_invoice = cls.res_partner.create( { "name": "Partner invoice test", "parent_id": cls.partner.id, "type": "invoice", } ) cls.partner_shipping = cls.res_partner.create( { "name": "Partner shipping test", "parent_id": cls.partner.id, "type": "delivery", } ) cls.finalization_reason_1 = cls.env["rma.finalization"].create( {"name": ("[Test] It can't be repaired and customer doesn't want it")} ) cls.finalization_reason_2 = cls.env["rma.finalization"].create( {"name": "[Test] It's out of warranty. To be scrapped"} ) cls.env.ref("rma.group_rma_manual_finalization").users |= cls.env.user cls.warehouse = cls.env.ref("stock.warehouse0") # Ensure grouping cls.env.company.rma_return_grouping = True cls.operation = cls.env.ref("rma.rma_operation_replace") def _create_rma( self, partner=None, product=None, qty=None, location=None, operation=None ): vals = {} if partner: vals["partner_id"] = partner.id if product: vals["product_id"] = product.id if qty: vals["product_uom_qty"] = qty if location: vals["location_id"] = location.id if operation: vals["operation_id"] = operation.id elif operation is None: vals["operation_id"] = self.operation.id return self.env["rma"].create(vals) def _create_confirm_receive( self, partner=None, product=None, qty=None, location=None, operation=None ): rma = self._create_rma(partner, product, qty, location, operation) rma.action_confirm() rma.reception_move_id.quantity_done = rma.product_uom_qty rma.reception_move_id.picking_id._action_done() return rma def _receive_and_replace(self, partner, product, qty, location): rma = self._create_confirm_receive(partner, product, qty, location) delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="replace", ) ) delivery_form.product_id = rma.product_id delivery_form.product_uom_qty = qty delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() return rma def _create_delivery(self): picking_type = self.env["stock.picking.type"].search( [ ("code", "=", "outgoing"), "|", ("warehouse_id.company_id", "=", self.company.id), ("warehouse_id", "=", False), ], limit=1, ) picking_form = Form( recordp=self.env["stock.picking"].with_context( default_picking_type_id=picking_type.id ), view="stock.view_picking_form", ) picking_form.partner_id = self.partner with picking_form.move_ids_without_package.new() as move: move.product_id = self.product move.product_uom_qty = 10 with picking_form.move_ids_without_package.new() as move: move.product_id = self.product_product.create( {"name": "Product 2 test", "type": "product"} ) move.product_uom_qty = 20 picking = picking_form.save() picking.action_confirm() for move in picking.move_ids: move.quantity_done = move.product_uom_qty picking.button_validate() return picking @tagged("-at_install", "post_install") class TestRmaCase(TestRma): def test_post_init_hook(self): warehouse = self.env["stock.warehouse"].create( { "name": "Test warehouse", "code": "code", "company_id": self.env.company.id, } ) hooks.post_init_hook(self.env.cr, self.registry) self.assertTrue(warehouse.rma_in_type_id) self.assertEqual( warehouse.rma_in_type_id.default_location_dest_id, warehouse.rma_loc_id ) self.assertEqual( warehouse.rma_out_type_id.default_location_src_id, warehouse.rma_loc_id ) self.assertTrue(warehouse.rma_loc_id) self.assertTrue(warehouse.rma_in_route_id) self.assertTrue(warehouse.rma_out_route_id) def test_rma_replace_pick_ship(self): self.warehouse.write({"delivery_steps": "pick_ship"}) rma = self._create_rma(self.partner, self.product, 1, self.rma_loc) rma.action_confirm() rma.reception_move_id.quantity_done = 1 rma.reception_move_id.picking_id._action_done() self.assertEqual(rma.reception_move_id.picking_id.state, "done") self.assertEqual(rma.state, "received") res = rma.action_replace() wizard_form = Form(self.env[res["res_model"]].with_context(**res["context"])) wizard_form.product_id = self.product wizard_form.product_uom_qty = rma.product_uom_qty wizard = wizard_form.save() wizard.action_deliver() self.assertEqual(rma.delivery_picking_count, 2) out_pickings = rma.mapped("delivery_move_ids.picking_id") self.assertIn( self.warehouse.pick_type_id, out_pickings.mapped("picking_type_id") ) self.assertIn( self.warehouse.out_type_id, out_pickings.mapped("picking_type_id") ) def test_computed(self): # If partner changes, the invoice address is set rma = self.env["rma"].new() rma.partner_id = self.partner self.assertEqual(rma.partner_invoice_id, self.partner_invoice) # If origin move changes, the product is set uom_ten = self.env["uom.uom"].create( { "name": "Ten", "category_id": self.env.ref("uom.product_uom_unit").id, "factor_inv": 10, "uom_type": "bigger", } ) product_2 = self.product_product.create( {"name": "Product test 2", "type": "product", "uom_id": uom_ten.id} ) outgoing_picking_type = self.env["stock.picking.type"].search( [ ("code", "=", "outgoing"), "|", ("warehouse_id.company_id", "=", self.company.id), ("warehouse_id", "=", False), ], limit=1, ) picking_form = Form( recordp=self.env["stock.picking"].with_context( default_picking_type_id=outgoing_picking_type.id ), view="stock.view_picking_form", ) picking_form.partner_id = self.partner with picking_form.move_ids_without_package.new() as move: move.product_id = product_2 move.product_uom_qty = 15 picking = picking_form.save() picking._action_done() rma.picking_id = picking rma.move_id = picking.move_ids self.assertEqual(rma.product_id, product_2) self.assertEqual(rma.product_uom_qty, 15) self.assertEqual(rma.product_uom, uom_ten) # If product changes, unit of measure changes rma.move_id = False rma.product_id = self.product self.assertEqual(rma.product_uom, self.product.uom_id) def test_ensure_required_fields_on_confirm(self): rma = self._create_rma(operation=False) with self.assertRaises(ValidationError) as e: rma.action_confirm() self.assertEqual( e.exception.args[0], "Required field(s):\nCustomer\nShipping Address\nInvoice Address\nProduct" "\nRequested operation", ) rma.partner_id = self.partner.id with self.assertRaises(ValidationError) as e: rma.action_confirm() self.assertEqual( e.exception.args[0], "Required field(s):\nProduct\nRequested operation" ) rma.product_id = self.product.id rma.location_id = self.rma_loc.id with self.assertRaises(ValidationError) as e: rma.action_confirm() self.assertEqual(e.exception.args[0], "Required field(s):\nRequested operation") rma.operation_id = self.operation rma.action_confirm() self.assertEqual(rma.state, "confirmed") def test_confirm_and_receive(self): rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma.action_confirm() self.assertEqual(rma.reception_move_id.picking_id.state, "assigned") self.assertEqual(rma.reception_move_id.product_id, rma.product_id) self.assertEqual(rma.reception_move_id.product_uom_qty, 10) self.assertEqual(rma.reception_move_id.product_uom, rma.product_uom) self.assertEqual(rma.state, "confirmed") rma.reception_move_id.quantity_done = 9 with self.assertRaises(ValidationError): rma.reception_move_id.picking_id._action_done() rma.reception_move_id.quantity_done = 10 rma.reception_move_id.picking_id._action_done() self.assertEqual(rma.reception_move_id.picking_id.state, "done") self.assertEqual(rma.reception_move_id.quantity_done, 10) self.assertEqual(rma.state, "received") def test_cancel(self): # cancel a draft RMA rma = self._create_rma(self.partner, self.product) rma.action_cancel() self.assertEqual(rma.state, "cancelled") # cancel a confirmed RMA rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma.action_confirm() rma.action_cancel() self.assertEqual(rma.state, "cancelled") # A RMA is only cancelled from draft and confirmed states rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) with self.assertRaises(UserError): rma.action_cancel() def test_lock_unlock(self): # A RMA is only locked from 'received' state rma_1 = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma_2 = self._create_confirm_receive( self.partner, self.product, 10, self.rma_loc ) self.assertEqual(rma_1.state, "draft") self.assertEqual(rma_2.state, "received") (rma_1 | rma_2).action_lock() self.assertEqual(rma_1.state, "draft") self.assertEqual(rma_2.state, "locked") # A RMA is only unlocked from 'lock' state and it will be set # to 'received' state (rma_1 | rma_2).action_unlock() self.assertEqual(rma_1.state, "draft") self.assertEqual(rma_2.state, "received") @users("__system__", "user_rma") def test_action_refund(self): rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) self.assertEqual(rma.state, "received") self.assertTrue(rma.can_be_refunded) self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_replaced) rma.action_refund() self.assertEqual(rma.refund_id.move_type, "out_refund") self.assertEqual(rma.refund_id.state, "draft") self.assertFalse(rma.refund_id.invoice_payment_term_id) self.assertEqual(rma.refund_line_id.product_id, rma.product_id) self.assertEqual(rma.refund_line_id.quantity, 10) self.assertEqual(rma.refund_line_id.product_uom_id, rma.product_uom) self.assertEqual(rma.state, "refunded") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_replaced) # A regular user can create the refund but only Invoicing users will be able # to edit it and post it if self.env.user.login != "__system__": return with Form(rma.refund_line_id.move_id) as refund_form: with refund_form.invoice_line_ids.edit(0) as refund_line: refund_line.quantity = 9 with self.assertRaises(ValidationError): rma.refund_id.action_post() with Form(rma.refund_line_id.move_id) as refund_form: with refund_form.invoice_line_ids.edit(0) as refund_line: refund_line.quantity = 10 rma.refund_id.action_post() self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_replaced) def test_mass_refund(self): # Create, confirm and receive rma_1 rma_1 = self._create_confirm_receive( self.partner, self.product, 10, self.rma_loc ) # create, confirm and receive 3 more RMAs # rma_2: Same partner and same product as rma_1 rma_2 = self._create_confirm_receive( self.partner, self.product, 15, self.rma_loc ) # rma_3: Same partner and different product than rma_1 product = self.product_product.create( {"name": "Product 2 test", "type": "product"} ) rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc) # rma_4: Different partner and same product as rma_1 partner = self.res_partner.create( { "name": "Partner 2 test", "property_account_receivable_id": self.account_receiv.id, "company_id": self.company.id, } ) rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc) # all rmas are ready to refund all_rmas = rma_1 | rma_2 | rma_3 | rma_4 self.assertEqual(all_rmas.mapped("state"), ["received"] * 4) self.assertEqual(all_rmas.mapped("can_be_refunded"), [True] * 4) # Mass refund of those four RMAs action = self.env.ref("rma.rma_refund_action_server") ctx = dict(self.env.context) ctx.update(active_ids=all_rmas.ids, active_model="rma") action.with_context(**ctx).run() # After that all RMAs are in 'refunded' state self.assertEqual(all_rmas.mapped("state"), ["refunded"] * 4) # Two refunds were created refund_1 = (rma_1 | rma_2 | rma_3).mapped("refund_id") refund_2 = rma_4.refund_id self.assertEqual(len(refund_1), 1) self.assertEqual(len(refund_2), 1) self.assertEqual((refund_1 | refund_2).mapped("state"), ["draft"] * 2) # One refund per partner self.assertNotEqual(refund_1.partner_id, refund_2.partner_id) self.assertEqual( refund_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("partner_invoice_id"), ) self.assertEqual(refund_2.partner_id, rma_4.partner_invoice_id) # Each RMA (rma_1, rma_2 and rma_3) is linked with a different # line of refund_1 self.assertEqual(len(refund_1.invoice_line_ids), 3) self.assertEqual( refund_1.invoice_line_ids.rma_id, (rma_1 | rma_2 | rma_3), ) self.assertEqual( (rma_1 | rma_2 | rma_3).mapped("refund_line_id"), refund_1.invoice_line_ids, ) # rma_4 is linked with the unique line of refund_2 self.assertEqual(len(refund_2.invoice_line_ids), 1) self.assertEqual(refund_2.invoice_line_ids.rma_id, rma_4) self.assertEqual(rma_4.refund_line_id, refund_2.invoice_line_ids) # Assert product and quantities are propagated correctly for rma in all_rmas: self.assertEqual(rma.product_id, rma.refund_line_id.product_id) self.assertEqual(rma.product_uom_qty, rma.refund_line_id.quantity) self.assertEqual(rma.product_uom, rma.refund_line_id.product_uom_id) # Less quantity -> error on confirm with Form(rma_2.refund_line_id.move_id) as refund_form: with refund_form.invoice_line_ids.edit(1) as refund_line: refund_line.quantity = 14 with self.assertRaises(ValidationError): refund_1.action_post() with Form(rma_2.refund_line_id.move_id) as refund_form: with refund_form.invoice_line_ids.edit(1) as refund_line: refund_line.quantity = 15 refund_1.action_post() refund_2.action_post() def test_replace(self): # Create, confirm and receive an RMA rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) # Replace with another product with quantity 2. product_2 = self.product_product.create( {"name": "Product 2 test", "type": "product"} ) delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="replace", ) ) delivery_form.product_id = product_2 delivery_form.product_uom_qty = 2 delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() self.assertEqual(len(rma.delivery_move_ids.picking_id.move_ids), 1) self.assertEqual(rma.delivery_move_ids.product_id, product_2) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) self.assertTrue(rma.delivery_move_ids.picking_id.state, "waiting") self.assertEqual(rma.state, "waiting_replacement") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) self.assertTrue(rma.can_be_replaced) self.assertEqual(rma.delivered_qty, 2) self.assertEqual(rma.remaining_qty, 8) self.assertEqual(rma.delivered_qty_done, 0) self.assertEqual(rma.remaining_qty_to_done, 10) first_move = rma.delivery_move_ids picking = first_move.picking_id # Replace again with another product with the remaining quantity product_3 = self.product_product.create( {"name": "Product 3 test", "type": "product"} ) delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="replace", ) ) delivery_form.product_id = product_3 delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() second_move = rma.delivery_move_ids - first_move self.assertEqual(len(rma.delivery_move_ids), 2) self.assertEqual(rma.delivery_move_ids.mapped("picking_id"), picking) self.assertEqual(first_move.product_id, product_2) self.assertEqual(first_move.product_uom_qty, 2) self.assertEqual(second_move.product_id, product_3) self.assertEqual(second_move.product_uom_qty, 8) self.assertTrue(picking.state, "waiting") self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 0) self.assertEqual(rma.remaining_qty_to_done, 10) # remaining_qty is 0 but rma is not set to 'replaced' until # remaining_qty_to_done is less than or equal to 0 first_move.quantity_done = 2 second_move.quantity_done = 8 picking.button_validate() self.assertEqual(picking.state, "done") self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.remaining_qty_to_done, 0) # The RMA is now in 'replaced' state self.assertEqual(rma.state, "replaced") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) # Despite being in 'replaced' state, # RMAs can still perform replacements. self.assertTrue(rma.can_be_replaced) def test_return_to_customer(self): # Create, confirm and receive an RMA rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) # Return the same product with quantity 2 to the customer. delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_form.product_uom_qty = 2 delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() picking = rma.delivery_move_ids.picking_id self.assertEqual(len(picking.move_ids), 1) self.assertEqual(rma.delivery_move_ids.product_id, self.product) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) self.assertTrue(picking.state, "waiting") self.assertEqual(rma.state, "waiting_return") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_replaced) self.assertTrue(rma.can_be_returned) self.assertEqual(rma.delivered_qty, 2) self.assertEqual(rma.remaining_qty, 8) self.assertEqual(rma.delivered_qty_done, 0) self.assertEqual(rma.remaining_qty_to_done, 10) first_move = rma.delivery_move_ids picking = first_move.picking_id # Validate the picking first_move.quantity_done = 2 picking.button_validate() self.assertEqual(picking.state, "done") self.assertEqual(rma.delivered_qty, 2) self.assertEqual(rma.remaining_qty, 8) self.assertEqual(rma.delivered_qty_done, 2) self.assertEqual(rma.remaining_qty_to_done, 8) # Return the remaining quantity to the customer delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() second_move = rma.delivery_move_ids - first_move second_move.quantity_done = 8 self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 2) self.assertEqual(rma.remaining_qty_to_done, 8) self.assertEqual(rma.state, "waiting_return") # remaining_qty is 0 but rma is not set to 'returned' until # remaining_qty_to_done is less than or equal to 0 picking_2 = second_move.picking_id picking_2.button_validate() self.assertEqual(picking_2.state, "done") self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.remaining_qty_to_done, 0) # The RMA is now in 'returned' state self.assertEqual(rma.state, "returned") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_replaced) def test_finish_rma(self): # Create, confirm and receive an RMA rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) rma.action_finish() finalization_form = Form( self.env["rma.finalization.wizard"].with_context( active_ids=rma.ids, rma_finalization_type="replace", ) ) finalization_form.finalization_id = self.finalization_reason_2 finalization_wizard = finalization_form.save() finalization_wizard.action_finish() self.assertEqual(rma.state, "finished") self.assertEqual(rma.finalization_id, self.finalization_reason_2) def test_mass_return_to_customer(self): # Create, confirm and receive rma_1 rma_1 = self._create_confirm_receive( self.partner, self.product, 10, self.rma_loc ) # create, confirm and receive 3 more RMAs # rma_2: Same partner and same product as rma_1 rma_2 = self._create_confirm_receive( self.partner, self.product, 15, self.rma_loc ) # rma_3: Same partner and different product than rma_1 product = self.product_product.create( {"name": "Product 2 test", "type": "product"} ) rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc) # rma_4: Different partner and same product as rma_1 partner = self.res_partner.create({"name": "Partner 2 test"}) rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc) # all rmas are ready to be returned to the customer all_rmas = rma_1 | rma_2 | rma_3 | rma_4 self.assertEqual(all_rmas.mapped("state"), ["received"] * 4) self.assertEqual(all_rmas.mapped("can_be_returned"), [True] * 4) all_in_pickings = all_rmas.mapped("reception_move_id.picking_id") self.assertEqual( all_in_pickings.mapped("picking_type_id"), self.warehouse.rma_in_type_id ) self.assertEqual( all_in_pickings.mapped("location_dest_id"), self.warehouse.rma_loc_id ) # Mass return of those four RMAs delivery_wizard = ( self.env["rma.delivery.wizard"] .with_context(active_ids=all_rmas.ids, rma_delivery_type="return") .create({}) ) delivery_wizard.action_deliver() # Two pickings were created pick_1 = (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids.picking_id") pick_2 = rma_4.delivery_move_ids.picking_id self.assertEqual(pick_1.picking_type_id, self.warehouse.rma_out_type_id) self.assertEqual(pick_1.location_id, self.warehouse.rma_loc_id) self.assertEqual(pick_2.picking_type_id, self.warehouse.rma_out_type_id) self.assertEqual(pick_2.location_id, self.warehouse.rma_loc_id) self.assertEqual(len(pick_1), 1) self.assertEqual(len(pick_2), 1) self.assertNotEqual(pick_1, pick_2) self.assertEqual((pick_1 | pick_2).mapped("state"), ["assigned"] * 2) # One picking per partner self.assertNotEqual(pick_1.partner_id, pick_2.partner_id) self.assertEqual( pick_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("partner_shipping_id"), ) self.assertEqual(pick_2.partner_id, rma_4.partner_id) # Each RMA of (rma_1, rma_2 and rma_3) is linked to a different # line of picking_1 self.assertEqual(len(pick_1.move_ids), 3) self.assertEqual( pick_1.move_ids.rma_id, (rma_1 | rma_2 | rma_3), ) self.assertEqual( (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids"), pick_1.move_ids, ) # rma_4 is linked with the unique move of pick_2 self.assertEqual(len(pick_2.move_ids), 1) self.assertEqual(pick_2.move_ids.rma_id, rma_4) self.assertEqual(rma_4.delivery_move_ids, pick_2.move_ids) # Assert product and quantities are propagated correctly for rma in all_rmas: self.assertEqual(rma.product_id, rma.delivery_move_ids.product_id) self.assertEqual(rma.product_uom_qty, rma.delivery_move_ids.product_uom_qty) self.assertEqual(rma.product_uom, rma.delivery_move_ids.product_uom) rma.delivery_move_ids.quantity_done = rma.product_uom_qty pick_1.button_validate() pick_2.button_validate() self.assertEqual(all_rmas.mapped("state"), ["returned"] * 4) def test_mass_return_to_customer_ungrouped(self): """We can choose to avoid the customer returns grouping""" self.env.company.rma_return_grouping = False # Create, confirm and receive rma_1 rma_1 = self._create_confirm_receive( self.partner, self.product, 10, self.rma_loc ) # create, confirm and receive 3 more RMAs # rma_2: Same partner and same product as rma_1 rma_2 = self._create_confirm_receive( self.partner, self.product, 15, self.rma_loc ) # rma_3: Same partner and different product than rma_1 product = self.product_product.create( {"name": "Product 2 test", "type": "product"} ) rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc) # rma_4: Different partner and same product as rma_1 partner = self.res_partner.create({"name": "Partner 2 test"}) rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc) # all rmas are ready to be returned to the customer all_rmas = rma_1 | rma_2 | rma_3 | rma_4 self.assertEqual(all_rmas.mapped("state"), ["received"] * 4) self.assertEqual(all_rmas.mapped("can_be_returned"), [True] * 4) # Mass return of those four RMAs delivery_wizard = ( self.env["rma.delivery.wizard"] .with_context(active_ids=all_rmas.ids, rma_delivery_type="return") .create({}) ) delivery_wizard.action_deliver() self.assertEqual(4, len(all_rmas.delivery_move_ids.picking_id)) def test_rma_from_picking_return(self): # Create a return from a delivery picking origin_delivery = self._create_delivery() stock_return_picking_form = Form( self.env["stock.return.picking"].with_context( active_ids=origin_delivery.ids, active_id=origin_delivery.id, active_model="stock.picking", ) ) stock_return_picking_form.create_rma = True stock_return_picking_form.rma_operation_id = self.operation return_wizard = stock_return_picking_form.save() picking_action = return_wizard.create_returns() # Each origin move is linked to a different RMA origin_moves = origin_delivery.move_ids self.assertTrue(origin_moves[0].rma_ids) self.assertTrue(origin_moves[1].rma_ids) rmas = origin_moves.rma_ids self.assertEqual(rmas.mapped("state"), ["confirmed"] * 2) # Each reception move is linked one of the generated RMAs reception = self.env["stock.picking"].browse(picking_action["res_id"]) reception_moves = reception.move_ids self.assertTrue(reception_moves[0].rma_receiver_ids) self.assertTrue(reception_moves[1].rma_receiver_ids) self.assertEqual(reception_moves.rma_receiver_ids, rmas) # Validate the reception picking to set rmas to 'received' state reception_moves[0].quantity_done = reception_moves[0].product_uom_qty reception_moves[1].quantity_done = reception_moves[1].product_uom_qty reception.button_validate() self.assertEqual(rmas.mapped("state"), ["received"] * 2) def test_split(self): origin_delivery = self._create_delivery() rma_form = Form(self.env["rma"]) rma_form.partner_id = self.partner rma_form.picking_id = origin_delivery rma_form.move_id = origin_delivery.move_ids.filtered( lambda r: r.product_id == self.product ) rma_form.operation_id = self.operation rma = rma_form.save() rma.action_confirm() rma.reception_move_id.quantity_done = 10 rma.reception_move_id.picking_id._action_done() # Return quantity 4 of the same product to the customer delivery_form = Form( self.env["rma.delivery.wizard"].with_context( active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_form.product_uom_qty = 4 delivery_wizard = delivery_form.save() delivery_wizard.action_deliver() rma.delivery_move_ids.quantity_done = 4 rma.delivery_move_ids.picking_id.button_validate() self.assertEqual(rma.state, "waiting_return") # Extract the remaining quantity to another RMA self.assertTrue(rma.can_be_split) split_wizard = ( self.env["rma.split.wizard"] .with_context( active_id=rma.id, active_ids=rma.ids, ) .create({}) ) action = split_wizard.action_split() # Check rma is set to 'returned' after split. Check new_rma values self.assertEqual(rma.state, "returned") new_rma = self.env["rma"].browse(action["res_id"]) self.assertEqual(new_rma.origin_split_rma_id, rma) self.assertEqual(new_rma.delivered_qty, 0) self.assertEqual(new_rma.remaining_qty, 6) self.assertEqual(new_rma.delivered_qty_done, 0) self.assertEqual(new_rma.remaining_qty_to_done, 6) self.assertEqual(new_rma.state, "received") self.assertTrue(new_rma.can_be_refunded) self.assertTrue(new_rma.can_be_returned) self.assertTrue(new_rma.can_be_replaced) self.assertEqual(new_rma.move_id, rma.move_id) self.assertEqual(new_rma.reception_move_id, rma.reception_move_id) self.assertEqual(new_rma.product_uom_qty + rma.product_uom_qty, 10) self.assertEqual(new_rma.move_id.quantity_done, 10) self.assertEqual(new_rma.reception_move_id.quantity_done, 10) @mute_logger("odoo.models.unlink") def test_rma_to_receive_on_delete_invoice(self): rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) rma.action_refund() self.assertEqual(rma.state, "refunded") rma.refund_id.unlink() self.assertFalse(rma.refund_id) self.assertEqual(rma.state, "received") self.assertTrue(rma.can_be_refunded) self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_replaced) def test_rma_picking_type_default_values(self): warehouse = self.env["stock.warehouse"].create( {"name": "Stock - RMA Test", "code": "SRT"} ) self.assertFalse(warehouse.rma_in_type_id.use_create_lots) self.assertTrue(warehouse.rma_in_type_id.use_existing_lots) def test_quantities_on_hand(self): rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) self.assertEqual(rma.product_id.qty_available, 0) def test_autoconfirm_email(self): self.company.send_rma_confirmation = True self.company.send_rma_receipt_confirmation = True self.company.send_rma_draft_confirmation = True self.company.rma_mail_confirmation_template_id = self.env.ref( "rma.mail_template_rma_notification" ) self.company.rma_mail_receipt_confirmation_template_id = self.env.ref( "rma.mail_template_rma_receipt_notification" ) self.company.rma_mail_draft_confirmation_template_id = self.env.ref( "rma.mail_template_rma_draft_notification" ) previous_mails = self.env["mail.mail"].search( [("partner_ids", "in", self.partner.ids)] ) self.assertFalse(previous_mails) # Force the context to mock an RMA created from the portal, which is # feature that we get on `rma_sale`. We drop it after the RMA creation # to avoid uncontrolled side effects ctx = self.env.context self.env.context = dict(ctx, from_portal=True) rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) self.env.context = ctx mail_draft = self.env["mail.message"].search( [("partner_ids", "in", self.partner.ids)] ) rma.action_confirm() mail_confirm = ( self.env["mail.message"].search([("partner_ids", "in", self.partner.ids)]) - mail_draft ) self.assertTrue(rma.name in mail_confirm.subject) self.assertTrue(rma.name in mail_confirm.body) self.assertEqual( self.env.ref("rma.mt_rma_notification"), mail_confirm.subtype_id ) # Now we'll confirm the incoming goods picking and the automatic # reception notification should be sent rma.reception_move_id.quantity_done = rma.product_uom_qty rma.reception_move_id.picking_id.button_validate() mail_receipt = ( self.env["mail.message"].search([("partner_ids", "in", self.partner.ids)]) - mail_draft - mail_confirm ) self.assertTrue(rma.name in mail_receipt.subject) self.assertTrue("products received" in mail_receipt.subject) def test_replace_picking_type(self): """ Test that by default, the replace operation uses the default delivery route, meaning the warehouse's default delivery picking type is applied. RMA replacement orders are not separated from regular deliveries, and both use the same picking type. """ rma = self._receive_and_replace(self.partner, self.product, 2, self.rma_loc) rma_in_type = self.warehouse.rma_in_type_id out_type = self.warehouse.out_type_id self.assertEqual(rma.reception_move_id.picking_type_id, rma_in_type) self.assertEqual(rma.delivery_move_ids.picking_type_id, out_type) def test_replace_picking_type_custom_picking_type(self): """ Test that when configured to use a custom route, the replace operation uses a custom picking type, separating RMA replacement orders from regular deliveries. The custom picking type is applied specifically for RMA replacements, instead of the default delivery picking type. """ rma_in_type = self.warehouse.rma_in_type_id rma_out_type = self.warehouse.rma_out_type_id route = self.env["stock.route"].create( { "name": "RMA OUT replace", "active": True, "sequence": 100, "product_selectable": True, "rule_ids": [ Command.create( { "name": "RMA OUT", "action": "pull", "picking_type_id": rma_out_type.id, "location_src_id": self.warehouse.lot_stock_id.id, "location_dest_id": self.env.ref( "stock.stock_location_customers" ).id, }, ) ], } ) self.warehouse.rma_out_replace_route_id = route rma = self._receive_and_replace(self.partner, self.product, 2, self.rma_loc) self.assertEqual(rma.reception_move_id.picking_type_id, rma_in_type) self.assertEqual(rma.delivery_move_ids.picking_type_id, rma_out_type) def test_grouping_reception_enabled(self): rma_1 = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma_2 = self._create_rma(self.partner, self.product_2, 10, self.rma_loc) (rma_1 | rma_2).action_confirm() self.assertEqual( rma_1.reception_move_id.picking_id, rma_2.reception_move_id.picking_id )