# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import Command from odoo.exceptions import UserError from odoo.tests import tagged, common, Form from odoo.tools import float_compare, float_is_zero @tagged('post_install', '-at_install') class TestRepair(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() # Partners cls.res_partner_1 = cls.env['res.partner'].create({'name': 'Wood Corner'}) cls.res_partner_address_1 = cls.env['res.partner'].create({'name': 'Willie Burke', 'parent_id': cls.res_partner_1.id}) cls.res_partner_12 = cls.env['res.partner'].create({'name': 'Partner 12'}) # Products cls.product_product_3 = cls.env['product.product'].create({'name': 'Desk Combination'}) cls.product_product_11 = cls.env['product.product'].create({ 'name': 'Conference Chair', 'lst_price': 30.0, }) cls.product_product_5 = cls.env['product.product'].create({'name': 'Product 5'}) cls.product_product_6 = cls.env['product.product'].create({'name': 'Large Cabinet'}) cls.product_product_12 = cls.env['product.product'].create({'name': 'Office Chair Black'}) cls.product_product_13 = cls.env['product.product'].create({'name': 'Corner Desk Left Sit'}) # Storable products cls.product_storable_no = cls.env['product.product'].create({ 'name': 'Product Storable No Tracking', 'is_storable': True, 'tracking': 'none', }) cls.product_storable_serial = cls.env['product.product'].create({ 'name': 'Product Storable Serial', 'is_storable': True, 'tracking': 'serial', }) cls.product_storable_lot = cls.env['product.product'].create({ 'name': 'Product Storable Lot', 'is_storable': True, 'tracking': 'lot', }) # 'Create Repair' Products cls.product_consu_order_repair = cls.env['product.product'].create({ 'name': 'Repair Consumable', 'type': 'consu', 'create_repair': True, }) cls.product_storable_order_repair = cls.env['product.product'].create({ 'name': 'Repair Storable', 'is_storable': True, 'create_repair': True, }) cls.product_service_order_repair = cls.env['product.product'].create({ 'name': 'Repair Service', 'type': 'service', 'create_repair': True, }) # Location cls.stock_warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.env.company.id)], limit=1) cls.stock_location_14 = cls.env['stock.location'].create({ 'name': 'Shelf 2', 'location_id': cls.stock_warehouse.lot_stock_id.id, }) # Repair Orders cls.repair1 = cls.env['repair.order'].create({ 'product_id': cls.product_product_3.id, 'product_uom': cls.env.ref('uom.product_uom_unit').id, 'picking_type_id': cls.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'product_id': cls.product_product_11.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', 'company_id': cls.env.company.id, }) ], 'partner_id': cls.res_partner_12.id, }) cls.repair0 = cls.env['repair.order'].create({ 'product_id': cls.product_product_5.id, 'product_uom': cls.env.ref('uom.product_uom_unit').id, 'user_id': False, 'picking_type_id': cls.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'product_id': cls.product_product_12.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', 'company_id': cls.env.company.id, }) ], 'partner_id': cls.res_partner_12.id, }) cls.repair2 = cls.env['repair.order'].create({ 'product_id': cls.product_product_6.id, 'product_uom': cls.env.ref('uom.product_uom_unit').id, 'user_id': False, 'picking_type_id': cls.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'product_id': cls.product_product_13.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', 'company_id': cls.env.company.id, }) ], 'partner_id': cls.res_partner_12.id, }) cls.env.user.groups_id |= cls.env.ref('stock.group_stock_user') def _create_simple_repair_order(self): product_to_repair = self.product_product_5 return self.env['repair.order'].create({ 'product_id': product_to_repair.id, 'product_uom': product_to_repair.uom_id.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, 'partner_id': self.res_partner_12.id }) def _create_simple_part_move(self, repair_id=False, qty=0.0, product=False): if not product: product = self.product_product_5 return self.env['stock.move'].create({ 'repair_line_type': 'add', 'product_id': product.id, 'product_uom_qty': qty, 'repair_id': repair_id, 'company_id': self.env.company.id, }) @classmethod def create_quant(cls, product, qty, offset=0, name="L"): i = 1 if product.tracking == 'serial': i, qty = qty, 1 if name == "L": name = "S" vals = [] for x in range(1, i + 1): qDict = { 'location_id': cls.stock_warehouse.lot_stock_id.id, 'product_id': product.id, 'inventory_quantity': qty, } if product.tracking != 'none': qDict['lot_id'] = cls.env['stock.lot'].create({ 'name': name + str(offset + x), 'product_id': product.id, }).id vals.append(qDict) return cls.env['stock.quant'].create(vals) def test_01_repair_states_transition(self): repair = self._create_simple_repair_order() # Draft -> Confirmed -> Cancel -> Draft -> Done -> Failing Cancel # draft -> confirmed (action_validate -> _action_repair_confirm) # PRE # lines' qty >= 0 !-> UserError # product's qty IS available !-> Warning w/ choice # POST # state = confirmed # move_ids in (partially reserved, fully reserved, waiting availability) # Line A with qty < 0 --> UserError lineA = self._create_simple_part_move(repair.id, -1.0, self.product_storable_no) repair.move_ids |= lineA with self.assertRaises(UserError): repair.action_validate() # Line A with qty > 0 & not available, Line B with qty >= 0 & available --> Warning (stock.warn.insufficient.qty.repair) lineA.product_uom_qty = 2.0 lineB = self._create_simple_part_move(repair.id, 2.0, self.product_storable_lot) repair.move_ids |= lineB quant = self.create_quant(self.product_storable_no, 1) quant |= self.create_quant(self.product_storable_lot, 3) quant.action_apply_inventory() lineC = self._create_simple_part_move(repair.id, 1.0, self.product_storable_order_repair) repair.move_ids |= lineC repair.product_id = self.product_storable_serial validate_action = repair.action_validate() self.assertEqual(validate_action.get("res_model"), "stock.warn.insufficient.qty.repair") # Warn qty Wizard only apply to "product TO repair" warn_qty_wizard = Form( self.env['stock.warn.insufficient.qty.repair'] .with_context(**validate_action['context']) ).save() warn_qty_wizard.action_done() self.assertEqual(repair.state, "confirmed", 'Repair order should be in "Confirmed" state.') self.assertEqual(lineA.state, "partially_available", 'Repair line #1 should be in "Partial Availability" state.') self.assertEqual(lineB.state, "assigned", 'Repair line #2 should be in "Available" state.') self.assertEqual(lineC.state, "confirmed", 'Repair line #3 should be in "Waiting Availability" state.') # Create quotation # No partner warning -> working case -> already linked warning # Ensure SO doesn't exist self.assertEqual(len(repair.sale_order_id), 0) repair.partner_id = None with self.assertRaises(UserError) as err: repair.action_create_sale_order() self.assertIn("You need to define a customer", err.exception.args[0]) repair.partner_id = self.res_partner_12.id repair.action_create_sale_order() # Ensure SO and SOL were created self.assertNotEqual(len(repair.sale_order_id), 0) self.assertEqual(len(repair.sale_order_id.order_line), 3) with self.assertRaises(UserError) as err: repair.action_create_sale_order() # (*) -> cancel (action_repair_cancel) # PRE # state != done !-> UserError (cf. end of this test) # POST # moves_ids state == cancelled # 'Lines" SOL product_uom_qty == 0 # state == cancel self.assertNotEqual(repair.state, "done") repair.action_repair_cancel() self.assertEqual(repair.state, "cancel") self.assertTrue(all(m.state == "cancel" for m in repair.move_ids)) self.assertTrue(all(float_is_zero(sol.product_uom_qty, 2) for sol in repair.sale_order_id.order_line)) # (*)/cancel -> draft (action_repair_cancel_draft) # PRE # state == cancel !-> action_repair_cancel() # state != done !~> UserError (transitive..., don't care) # POST # move_ids.state == draft # state == draft repair.action_repair_cancel_draft() self.assertEqual(repair.state, "draft") self.assertTrue(all(m.state == "draft" for m in repair.move_ids)) # draft -> confirmed # Enforce product_id availability to skip warning quant = self.create_quant(self.product_storable_serial, 1) quant.action_apply_inventory() repair.lot_id = quant.lot_id repair.action_validate() self.assertEqual(repair.state, "confirmed") # confirmed -> under_repair (action_repair_start) # Purely informative state repair.action_repair_start() self.assertEqual(repair.state, "under_repair") # under_repair -> done (action_repair_end -> action_repair_done) # PRE # state == under_repair !-> UserError # lines' quantity >= lines' product_uom_qty !-> Warning # line tracked => line has lot_ids !-> ValidationError # POST # lines with quantity == 0 are cancelled (related sol product_uom_qty is consequently set to 0) # repair.product_id => repair.move_id # move_ids.state == (done || cancel) # state == done # move_ids with quantity (LOWER or HIGHER than) product_uom_qty MUST NOT be splitted # Any line with quantity < product_uom_qty => Warning repair.move_ids.picked = True self.assertTrue(repair.has_uncomplete_moves) # LineB : no serial => ValidationError lot = lineB.move_line_ids.lot_id with self.assertRaises(UserError) as err: lineB.move_line_ids.lot_id = False repair.action_repair_done() # LineB with lots lineB.move_line_ids.lot_id = lot lineA.quantity = 2 # quantity = product_uom_qty lineC.quantity = 2 # quantity > product_uom_qty (No warning) lineD = self._create_simple_part_move(repair.id, 0.0) repair.move_ids |= lineD # product_uom_qty = 0 : state is cancelled self.assertEqual(lineD.state, 'assigned') num_of_lines = len(repair.move_ids) self.assertFalse(repair.move_id) self.assertFalse(repair.has_uncomplete_moves) repair.action_repair_end() self.assertFalse((repair.move_id | repair.move_ids).picking_id, "No picking for repair moves") self.assertEqual(repair.state, "done") done_moves = repair.move_ids - lineD #line a,b,c are 'done', line d is 'cancel' self.assertTrue(all(m.state == 'done' for m in done_moves)) self.assertEqual(lineD.state, 'cancel') self.assertEqual(len(repair.move_id), 1) self.assertEqual(len(repair.move_ids), num_of_lines) # No split # (*) -> cancel (action_repair_cancel) # PRE # state != done !-> UserError with self.assertRaises(UserError) as err: repair.action_repair_cancel() def test_02_repair_sale_order_binding(self): # Binding from SO to RO(s) # On SO Confirm # - Create linked RO per line (only if item with "create_repair" checked) # Create Repair SOL # - sol qty updated to 0 -> RO canceled (Reciprocal is true too) # - sol qty back to >0 -> RO Confirmed (Reciprocal is not true) # RO Parts SOL # - SOL qty change is NOT propagated to RO # - However, these changes FROM RO are propagated to SO #---------------------------------------------------------------------------------- # Binding from RO to SO so_form = Form(self.env['sale.order']) so_form.partner_id = self.res_partner_1 with so_form.order_line.new() as line: line.product_id = self.product_consu_order_repair line.product_uom_qty = 2.0 with so_form.order_line.new() as line: line.display_type = 'line_section' line.name = 'Dummy Section' sale_order = so_form.save() order_line = sale_order.order_line[0] line_section = sale_order.order_line[1] self.assertEqual(len(sale_order.repair_order_ids), 0) sale_order.action_confirm() # Quantity set on the "create repair" product doesn't affect the number of RO created self.assertEqual(len(sale_order.repair_order_ids), 1) repair_order = sale_order.repair_order_ids[0] self.assertEqual(sale_order, repair_order.sale_order_id) self.assertEqual(repair_order.state, 'confirmed') order_line.product_uom_qty = 0 self.assertEqual(repair_order.state, 'cancel') order_line.product_uom_qty = 1 line_section.name = 'updated section' self.assertEqual(repair_order.state, 'confirmed') repair_order.action_repair_cancel() self.assertTrue(float_is_zero(order_line.product_uom_qty, 2)) order_line.product_uom_qty = 3 self.assertEqual(repair_order.state, 'confirmed') # Add RO line ro_form = Form(repair_order) with ro_form.move_ids.new() as ro_line_form: ro_line_form.repair_line_type = 'add' ro_line_form.product_id = self.product_product_11 ro_line_form.product_uom_qty = 1 ro_form.save() ro_line_0 = repair_order.move_ids[0] sol_part_0 = ro_line_0.sale_line_id self.assertEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.product_uom_qty, 2), 0) # chg qty in SO -> No effect on RO sol_part_0.product_uom_qty = 5 self.assertNotEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.product_uom_qty, 2), 0) # chg qty in RO -> Update qty in SO ro_line_0.product_uom_qty = 3 self.assertEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.product_uom_qty, 2), 0) # with/without warranty self.assertFalse(float_is_zero(sol_part_0.price_unit, 2)) repair_order.under_warranty = True self.assertTrue(float_is_zero(sol_part_0.price_unit, 2)) repair_order.under_warranty = False self.assertFalse(float_is_zero(sol_part_0.price_unit, 2)) # stock_move transitions # add -> remove -> add -> recycle -> add transitions ro_line_0.repair_line_type = 'remove' self.assertTrue(float_is_zero(sol_part_0.product_uom_qty, 2)) ro_line_0.repair_line_type = 'add' self.assertEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.product_uom_qty, 2), 0) ro_line_0.repair_line_type = 'recycle' self.assertTrue(float_is_zero(sol_part_0.product_uom_qty, 2)) ro_line_0.repair_line_type = 'add' self.assertEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.product_uom_qty, 2), 0) # remove and recycle line : not added to SO. sol_count = len(sale_order.order_line) with ro_form.move_ids.new() as ro_line_form: ro_line_form.repair_line_type = 'remove' ro_line_form.product_id = self.product_product_12 ro_line_form.product_uom_qty = 1 with ro_form.move_ids.new() as ro_line_form: ro_line_form.repair_line_type = 'recycle' ro_line_form.product_id = self.product_product_13 ro_line_form.product_uom_qty = 1 ro_form.save() ro_line_1 = repair_order.move_ids[1] self.assertEqual(len(sale_order.order_line), sol_count) # remove to add -> added to SO ro_line_1.repair_line_type = 'add' sol_part_1 = ro_line_1.sale_line_id self.assertNotEqual(len(sale_order.order_line), sol_count) self.assertEqual(float_compare(sol_part_1.product_uom_qty, ro_line_1.product_uom_qty, 2), 0) # delete 'remove to add' line in RO -> SOL qty set to 0 repair_order.move_ids = [(2, ro_line_1.id, 0)] self.assertTrue(float_is_zero(sol_part_1.product_uom_qty, 2)) # repair_order.action_repair_end() # -> order_line.qty_delivered == order_line.product_uom_qty # -> "RO Lines"'s SOL.qty_delivered == move.quantity repair_order.action_repair_start() for line in repair_order.move_ids: line.quantity = line.product_uom_qty repair_order.action_repair_end() self.assertTrue(float_is_zero(order_line.qty_delivered, 2)) self.assertEqual(float_compare(sol_part_0.product_uom_qty, ro_line_0.quantity, 2), 0) self.assertTrue(float_is_zero(sol_part_1.qty_delivered, 2)) def test_03_sale_order_delivered_qty(self): so_form = Form(self.env['sale.order']) so_form.partner_id = self.res_partner_1 with so_form.order_line.new() as line: line.product_id = self.product_consu_order_repair line.product_uom_qty = 1.0 with so_form.order_line.new() as line: line.product_id = self.product_storable_order_repair line.product_uom_qty = 1.0 with so_form.order_line.new() as line: line.product_id = self.product_service_order_repair line.product_uom_qty = 1.0 sale_order = so_form.save() sale_order.action_confirm() repair_order_ids = sale_order.repair_order_ids repair_order_ids.action_repair_start() repair_order_ids.action_repair_end() for sol in sale_order.order_line: if sol.product_template_id.type == 'service': self.assertEqual(float_compare(sol.product_uom_qty, sol.qty_delivered, 2), 0) else: self.assertTrue(float_is_zero(sol.qty_delivered, 2)) def test_repair_compute_product_uom(self): repair = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'repair_line_type': 'add', 'product_id': self.product_product_11.id, }) ], }) self.assertEqual(repair.product_uom, self.product_product_3.uom_id) self.assertEqual(repair.move_ids[0].product_uom, self.product_product_11.uom_id) def test_repair_compute_location(self): repair = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'repair_line_type': 'add', 'product_id': self.product_product_11.id, }) ], }) self.assertEqual(repair.location_id, self.stock_warehouse.lot_stock_id) self.assertEqual(repair.move_ids[0].location_id, self.stock_warehouse.lot_stock_id) location_dest_id = self.env['stock.location'].search([ ('usage', '=', 'production'), ('company_id', '=', repair.company_id.id), ], limit=1) self.assertEqual(repair.move_ids[0].location_dest_id, location_dest_id) def test_purchase_price_so_create_from_repair(self): """ Test that the purchase price is correctly set on the SO line, when creating a SO from a repair order. """ if not self.env['ir.module.module'].search([('name', '=', 'sale_margin'), ('state', '=', 'installed')]): self.skipTest("sale_margin is not installed, so there is no purchase price to test") self.product_product_11.standard_price = 10 repair = self.env['repair.order'].create({ 'partner_id': self.res_partner_1.id, 'product_id': self.product_product_3.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'repair_line_type': 'add', 'product_id': self.product_product_11.id, }) ], }) repair.action_create_sale_order() self.assertEqual(repair.sale_order_id.order_line.product_id, self.product_product_11) self.assertEqual(repair.sale_order_id.order_line.purchase_price, 10) def test_repair_from_return(self): """ create a repair order from a return delivery and ensure that the stock.move resulting from the repair is not associated with the return picking. """ product = self.env['product.product'].create({ 'name': 'Test Product', 'is_storable': True, }) self.env['stock.quant']._update_available_quantity(product, self.stock_location_14, 1) picking_form = Form(self.env['stock.picking']) #create a delivery order picking_form.picking_type_id = self.stock_warehouse.out_type_id picking_form.partner_id = self.res_partner_1 with picking_form.move_ids_without_package.new() as move: move.product_id = product move.product_uom_qty = 1.0 picking = picking_form.save() picking.action_confirm() picking.action_assign() picking.button_validate() self.assertEqual(picking.state, 'done') # Create a return stock_return_picking_form = Form(self.env['stock.return.picking'] .with_context(active_ids=picking.ids, active_id=picking.ids[0], active_model='stock.picking')) stock_return_picking = stock_return_picking_form.save() stock_return_picking.product_return_moves.quantity = 1.0 stock_return_picking_action = stock_return_picking.action_create_returns() return_picking = self.env['stock.picking'].browse(stock_return_picking_action['res_id']) return_picking.move_ids.picked = True return_picking.button_validate() self.assertEqual(return_picking.state, 'done') res_dict = return_picking.action_repair_return() repair_form = Form.from_action(self.env, res_dict) repair_form.product_id = product # The repair needs to be saved to ensure the context is correctly set. repair = repair_form.save() repair_form = Form(repair) with repair_form.move_ids.new() as move: move.product_id = self.product_product_5 move.product_uom_qty = 1.0 move.quantity = 1.0 move.repair_line_type = 'add' repair = repair_form.save() repair.action_repair_start() repair.action_repair_end() self.assertEqual(repair.state, 'done') self.assertEqual(len(return_picking.move_ids), 1, "Parts added to the repair order shoudln't be added to the return picking") self.assertEqual(repair.location_id, return_picking.location_dest_id, "Repair location should have defaulted to return destination location") self.assertEqual(repair.partner_id, return_picking.partner_id, "Repair customer should have defaulted to return customer") self.assertEqual(repair.picking_type_id, return_picking.picking_type_id.warehouse_id.repair_type_id) def test_repair_with_product_in_package(self): """ Test That a repair order can be validated when the repaired product is tracked and in a package """ self.product_product_3.tracking = 'serial' self.product_product_3.is_storable = True # Create two serial numbers sn_1 = self.env['stock.lot'].create({'name': 'sn_1', 'product_id': self.product_product_3.id}) sn_2 = self.env['stock.lot'].create({'name': 'sn_2', 'product_id': self.product_product_3.id}) # Create two packages package_1 = self.env['stock.quant.package'].create({'name': 'Package-test-1'}) package_2 = self.env['stock.quant.package'].create({'name': 'Package-test-2'}) # update the quantity of the product in the stock self.env['stock.quant']._update_available_quantity(self.product_product_3, self.stock_warehouse.lot_stock_id, 1, lot_id=sn_1, package_id=package_1) self.env['stock.quant']._update_available_quantity(self.product_product_3, self.stock_warehouse.lot_stock_id, 1, lot_id=sn_2, package_id=package_2) self.assertEqual(self.product_product_3.qty_available, 2) # create a repair order repair_order = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'product_uom': self.product_product_3.uom_id.id, # 'guarantee_limit': '2019-01-01', 'location_id': self.stock_warehouse.lot_stock_id.id, 'lot_id': sn_1.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, 'move_ids': [ (0, 0, { 'product_id': self.product_product_5.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', }) ], }) # Validate and complete the repair order repair_order.action_validate() self.assertEqual(repair_order.state, 'confirmed') repair_order.action_repair_start() self.assertEqual(repair_order.state, 'under_repair') repair_order.move_ids.quantity = 1 repair_order.action_repair_end() self.assertEqual(repair_order.state, 'done') def test_sn_with_no_tracked_product(self): """ Check that the lot_id field is cleared after updating the product in the repair order. """ self.env.ref('base.group_user').implied_ids += ( self.env.ref('stock.group_production_lot') ) sn_1 = self.env['stock.lot'].create({'name': 'sn_1', 'product_id': self.product_storable_serial.id}) ro_form = Form(self.env['repair.order']) ro_form.product_id = self.product_storable_serial ro_form.lot_id = sn_1 repair_order = ro_form.save() ro_form = Form(repair_order) ro_form.product_id = self.product_storable_no repair_order = ro_form.save() self.assertFalse(repair_order.lot_id) def test_repair_multi_unit_order_with_serial_tracking(self): """ Test that a sale order with a single order line with quantity > 1 for a product that creates a repair order and is tracked via serial number creates multiple repair orders rather than grouping the line into a single RO """ product_a = self.env['product.product'].create({ 'name': 'productA', 'is_storable': True, 'tracking': 'serial', 'create_repair': True, }) sale_order = self.env['sale.order'].create({ 'partner_id': self.res_partner_1.id, 'order_line': [Command.create({ 'product_id': product_a.id, 'product_uom_qty': 3.0, })] }) sale_order.action_confirm() repair_orders = sale_order.repair_order_ids self.assertRecordValues(repair_orders, [ {'product_id': product_a.id, 'product_qty': 1.0}, {'product_id': product_a.id, 'product_qty': 1.0}, {'product_id': product_a.id, 'product_qty': 1.0}, ]) def test_onchange_picking_type_id_and_name(self): """ Test that when changing the picking_type_id, the name of the repair order should be changed too """ repair_order = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'picking_type_id': self.stock_warehouse.repair_type_id.id, }) picking_type_1 = self.env['stock.picking.type'].create({ 'name': 'new_picking_type_1', 'code': 'repair_operation', 'sequence_code': 'PT1/', }) picking_type_2 = self.env['stock.picking.type'].create({ 'name': 'new_picking_type_2', 'code': 'repair_operation', 'sequence_code': 'PT2/', }) repair_order.picking_type_id = picking_type_1 self.assertEqual(repair_order.name, "PT1/00001") repair_order.picking_type_id = picking_type_2 self.assertEqual(repair_order.name, "PT2/00001") repair_order.picking_type_id = picking_type_1 self.assertEqual(repair_order.name, "PT1/00002") repair_order.picking_type_id = picking_type_1 self.assertEqual(repair_order.name, "PT1/00002") def test_repair_components_lots_show_in_invoice(self): """ Test that the lots of the components of a repair order are shown in the invoice """ quant = self.create_quant(self.product_storable_serial, 1) quant.action_apply_inventory() repair_order = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'product_uom': self.product_product_3.uom_id.id, 'partner_id': self.res_partner_12.id, 'move_ids': [ Command.create({ 'product_id': self.product_storable_serial.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', }) ], }) repair_order.action_validate() repair_order.action_repair_start() repair_order.action_repair_end() repair_order.action_create_sale_order() sale_order = repair_order.sale_order_id sale_order.action_confirm() invoice = sale_order._create_invoices() invoice.action_post() res = invoice._get_invoiced_lot_values() self.assertEqual(len(res), 1, "The invoice should have one line") self.assertEqual(res[0]['product_name'], self.product_storable_serial.display_name, "The product name should be the same") self.assertEqual(res[0]['lot_name'], quant.lot_id.name, "The lot name should be the same") def test_create_repair_order_from_cross_company_sn(self): """ Test that a repair order can be created from a cross-company SN. """ sn_01 = self.env['stock.lot'].create({'name': 'sn_1', 'product_id': self.product_product_3.id}) action = sn_01.action_lot_open_repairs() repair_order = self.env['repair.order'].with_context(action.get('context')).create({ 'product_id': self.product_product_3.id, 'product_uom': self.product_product_3.uom_id.id, 'partner_id': self.res_partner_12.id, }) self.assertEqual(repair_order.company_id, self.env.company) def test_delivered_qty_of_generated_so(self): """ Test that checks that `qty_delivered` of the generated SOL is correctly set when the repair is done. """ repair_order = self.env['repair.order'].create({ 'product_id': self.product_storable_order_repair.id, 'product_uom': self.product_storable_order_repair.uom_id.id, 'partner_id': self.res_partner_1.id, 'move_ids': [ Command.create({ 'product_id': self.product_consu_order_repair.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', }) ], }) repair_order.action_validate() repair_order.action_repair_start() repair_order.action_repair_end() self.assertEqual(repair_order.state, 'done') self.assertEqual(repair_order.move_ids.quantity, 1.0) repair_order.action_create_sale_order() sale_order = repair_order.sale_order_id sale_order.action_confirm() self.assertEqual(sale_order.order_line.qty_delivered, 1.0) def test_repair_order_uncomplete_moves(self): """ This test checks that the `has_uncomplete_moves` field is correctly set on a repair order. """ repair_order = self.env['repair.order'].create({ 'product_id': self.product_storable_order_repair.id, 'product_uom': self.product_storable_order_repair.uom_id.id, 'partner_id': self.res_partner_1.id, 'move_ids': [ Command.create({ 'product_id': self.product_product_5.id, 'product_uom_qty': 3.0, 'state': 'draft', 'repair_line_type': 'add', }), Command.create({ 'product_id': self.product_product_6.id, 'product_uom_qty': 4.0, 'state': 'draft', 'repair_line_type': 'add', }), ], }) repair_order.action_validate() repair_order.action_repair_start() self.assertFalse(repair_order.has_uncomplete_moves) repair_order.move_ids[0].quantity = 1.0 self.assertTrue(repair_order.has_uncomplete_moves) repair_order.move_ids[1].quantity = 2 self.assertTrue(repair_order.has_uncomplete_moves) repair_order.move_ids[0].quantity = 3.0 repair_order.move_ids[1].quantity = 4.0 self.assertFalse(repair_order.has_uncomplete_moves) repair_order.action_repair_end() def test_trigger_orderpoint_from_repair(self): """ Test that the order point triggered by the repair order creates a move linked to a picking. """ self.assertFalse(self.env['stock.move'].search([('product_id', '=', self.product_storable_no.id)])) route = self.env['stock.route'].create({ 'name': 'new route', 'rule_ids': [(0, False, { 'name': 'rule_test', 'location_src_id': self.stock_warehouse.lot_stock_id.id, 'location_dest_id': self.stock_location_14.id, 'company_id': self.env.company.id, 'action': 'pull', 'picking_type_id': self.env.ref('stock.picking_type_in').id, 'procure_method': 'make_to_stock' })], }) self.env['stock.warehouse.orderpoint'].create({ 'name': 'Cake RR', 'product_id': self.product_storable_no, 'route_id': route.id, 'location_id': self.stock_location_14.id, 'product_id': self.product_storable_no.id, 'product_min_qty': 0, 'product_max_qty': 1, 'trigger': 'auto' }) # The product to be repaired should be storable and out of stock # to trigger the wizard indicating that the product has an insufficient quantity. self.product_product_3.is_storable = True repair_order = self.env['repair.order'].create({ 'product_id': self.product_product_3.id, 'product_uom': self.product_product_3.uom_id.id, 'partner_id': self.res_partner_12.id, 'location_id': self.stock_location_14.id, 'move_ids': [ Command.create({ 'product_id': self.product_storable_no.id, 'product_uom_qty': 1.0, 'state': 'draft', 'repair_line_type': 'add', }) ], }) validate_action = repair_order.action_validate() self.assertEqual(validate_action.get("res_model"), "stock.warn.insufficient.qty.repair") warn_qty_wizard = Form( self.env['stock.warn.insufficient.qty.repair'] .with_context(**validate_action['context']) ).save() warn_qty_wizard.action_done() self.assertEqual(repair_order.state, "confirmed", 'Repair order should be in "Confirmed" state.') move = self.env['stock.move'].search([ ('product_id', '=', self.product_storable_no.id), ('location_dest_id', '=', self.stock_location_14.id,) ]) self.assertTrue(move.picking_id) self.assertFalse(move.repair_id) self.assertEqual(move.location_id, self.stock_warehouse.lot_stock_id) self.assertEqual(move.location_dest_id, self.stock_location_14)