# -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase class TestEdgeCases(TransactionCase): """Unit tests for edge cases in quality check lot preservation""" def setUp(self): super(TestEdgeCases, self).setUp() # Get model references self.StockMoveLine = self.env['stock.move.line'] self.QualityCheck = self.env['quality.check'] self.Product = self.env['product.product'] self.StockLocation = self.env['stock.location'] self.StockPicking = self.env['stock.picking'] self.StockPickingType = self.env['stock.picking.type'] self.StockMove = self.env['stock.move'] self.StockLot = self.env['stock.lot'] self.QualityPoint = self.env['quality.point'] # Create test product self.product = self.Product.create({ 'name': 'Test Product', 'type': 'consu', # Use 'consu' for consumable/storable product 'tracking': 'lot', }) # Get or create locations self.location_supplier = self.env.ref('stock.stock_location_suppliers') self.location_stock = self.env.ref('stock.stock_location_stock') self.location_customer = self.env.ref('stock.stock_location_customers') # Get picking types self.picking_type_in = self.env.ref('stock.picking_type_in') self.picking_type_out = self.env.ref('stock.picking_type_out') self.picking_type_internal = self.env.ref('stock.picking_type_internal') # Try to get manufacturing picking type if available try: self.picking_type_mrp = self.env.ref('mrp.picking_type_manufacturing') except ValueError: # Create a mock manufacturing picking type if MRP is not installed warehouse = self.picking_type_in.warehouse_id self.picking_type_mrp = self.StockPickingType.create({ 'name': 'Manufacturing', 'code': 'mrp_operation', 'sequence_code': 'MRP', 'warehouse_id': warehouse.id, 'default_location_src_id': warehouse.lot_stock_id.id, 'default_location_dest_id': warehouse.lot_stock_id.id, }) # Get or create a quality team self.quality_team = self.env['quality.alert.team'].search([], limit=1) if not self.quality_team: self.quality_team = self.env['quality.alert.team'].create({ 'name': 'Test Quality Team', }) # Get test type self.test_type = self.env.ref('quality_control.test_type_passfail', raise_if_not_found=False) if not self.test_type: self.test_type = self.env['quality.point.test_type'].create({ 'name': 'Pass/Fail Test', 'technical_name': 'passfail', }) # Create quality point for the product self.quality_point = self.QualityPoint.create({ 'product_ids': [(4, self.product.id)], 'picking_type_ids': [(4, self.picking_type_in.id)], 'test_type_id': self.test_type.id, 'team_id': self.quality_team.id, }) def _create_receipt_picking(self): """Helper to create a receipt picking""" return self.StockPicking.create({ 'picking_type_id': self.picking_type_in.id, 'location_id': self.location_supplier.id, 'location_dest_id': self.location_stock.id, }) def _create_stock_move(self, picking): """Helper to create a stock move""" return self.StockMove.create({ 'name': 'Test Move', 'product_id': self.product.id, 'product_uom_qty': 1.0, 'product_uom': self.product.uom_id.id, 'picking_id': picking.id, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, }) def _create_move_line(self, move, lot_id=False): """Helper to create a stock move line""" return self.StockMoveLine.create({ 'move_id': move.id, 'product_id': self.product.id, 'product_uom_id': self.product.uom_id.id, 'location_id': move.location_id.id, 'location_dest_id': move.location_dest_id.id, 'picking_id': move.picking_id.id, 'lot_id': lot_id, 'quantity': 1.0, }) def _create_lot(self, name): """Helper to create a lot""" return self.StockLot.create({ 'name': name, 'product_id': self.product.id, 'company_id': self.env.company.id, }) # Subtask 7.1: Test missing quality check scenario def test_lot_assignment_without_quality_check(self): """ Test lot assignment when no quality check exists. Verify no errors occur and standard behavior continues. Requirements: 2.1 """ # Create receipt picking and move picking = self._create_receipt_picking() move = self._create_stock_move(picking) move_line = self._create_move_line(move) # Verify no quality check exists quality_checks = self.QualityCheck.search([ ('move_line_id', '=', move_line.id) ]) self.assertEqual(len(quality_checks), 0, "No quality check should exist initially") # Assign lot number - should not raise error lot = self._create_lot('LOT-NO-QC-001') try: move_line.write({'lot_id': lot.id}) success = True except Exception as e: success = False error_msg = str(e) self.assertTrue(success, "Lot assignment should succeed even without quality check") self.assertEqual(move_line.lot_id.id, lot.id, "Lot should be assigned to move line") # Subtask 7.2: Test multiple quality checks scenario def test_multiple_quality_checks_update(self): """ Test that multiple quality checks for same move line are all updated. Verify unrelated quality checks are not affected. Requirements: 2.3 """ # Create receipt picking and move picking = self._create_receipt_picking() move = self._create_stock_move(picking) move_line = self._create_move_line(move) # Create multiple quality checks for the same move line qc1 = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': move_line.id, 'test_type': 'passfail', 'quality_state': 'pass', }) qc2 = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': move_line.id, 'test_type': 'passfail', 'quality_state': 'fail', }) # Create an unrelated quality check (different move line) other_move_line = self._create_move_line(move) qc_unrelated = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': other_move_line.id, 'test_type': 'passfail', 'quality_state': 'none', }) # Assign lot number to first move line lot = self._create_lot('LOT-MULTI-001') move_line.write({'lot_id': lot.id}) # Verify both related quality checks are updated self.assertEqual(qc1.lot_id.id, lot.id, "First quality check should have lot assigned") self.assertEqual(qc2.lot_id.id, lot.id, "Second quality check should have lot assigned") # Verify states are preserved self.assertEqual(qc1.quality_state, 'pass', "First quality check state should be preserved") self.assertEqual(qc2.quality_state, 'fail', "Second quality check state should be preserved") # Verify unrelated quality check is not affected self.assertFalse(qc_unrelated.lot_id, "Unrelated quality check should not have lot assigned") # Subtask 7.3: Test quality check without move line def test_quality_check_without_move_line(self): """ Test quality check without move_line_id. Verify no automatic updates occur. Requirements: 3.3 """ # Create receipt picking picking = self._create_receipt_picking() # Create quality check without move_line_id qc = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'test_type': 'passfail', 'quality_state': 'pass', }) # Create a move line with lot and assign it move = self._create_stock_move(picking) lot = self._create_lot('LOT-NO-LINK-001') move_line = self._create_move_line(move, lot_id=lot.id) # Verify quality check is not automatically updated self.assertFalse(qc.lot_id, "Quality check without move_line_id should not be auto-updated") self.assertEqual(qc.quality_state, 'pass', "Quality check state should remain unchanged") # Subtask 7.4: Test each quality check state preservation def test_pass_state_preservation(self): """ Test "Pass" state preservation during lot assignment. Requirements: 1.2 """ picking = self._create_receipt_picking() move = self._create_stock_move(picking) move_line = self._create_move_line(move) qc = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': move_line.id, 'test_type': 'passfail', 'quality_state': 'pass', }) lot = self._create_lot('LOT-PASS-001') move_line.write({'lot_id': lot.id}) self.assertEqual(qc.quality_state, 'pass', "Pass state should be preserved") self.assertEqual(qc.lot_id.id, lot.id, "Lot should be assigned") def test_fail_state_preservation(self): """ Test "Fail" state preservation during lot assignment. Requirements: 1.2 """ picking = self._create_receipt_picking() move = self._create_stock_move(picking) move_line = self._create_move_line(move) qc = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': move_line.id, 'test_type': 'passfail', 'quality_state': 'fail', }) lot = self._create_lot('LOT-FAIL-001') move_line.write({'lot_id': lot.id}) self.assertEqual(qc.quality_state, 'fail', "Fail state should be preserved") self.assertEqual(qc.lot_id.id, lot.id, "Lot should be assigned") def test_none_state_preservation(self): """ Test "In Progress" (none) state preservation during lot assignment. Requirements: 1.2 """ picking = self._create_receipt_picking() move = self._create_stock_move(picking) move_line = self._create_move_line(move) qc = self.QualityCheck.create({ 'product_id': self.product.id, 'picking_id': picking.id, 'move_line_id': move_line.id, 'test_type': 'passfail', 'quality_state': 'none', }) lot = self._create_lot('LOT-NONE-001') move_line.write({'lot_id': lot.id}) self.assertEqual(qc.quality_state, 'none', "None/In Progress state should be preserved") self.assertEqual(qc.lot_id.id, lot.id, "Lot should be assigned") # Subtask 7.5: Test different operation types def test_manufacturing_operation_standard_behavior(self): """ Test manufacturing operation uses standard behavior. Requirements: 4.2 """ # Create manufacturing picking picking = self.StockPicking.create({ 'picking_type_id': self.picking_type_mrp.id, 'location_id': self.location_stock.id, 'location_dest_id': self.location_stock.id, }) move = self.StockMove.create({ 'name': 'Test Move', 'product_id': self.product.id, 'product_uom_qty': 1.0, 'product_uom': self.product.uom_id.id, 'picking_id': picking.id, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, }) move_line = self._create_move_line(move) # Verify it's not a receipt operation self.assertFalse(move_line._is_receipt_operation(), "Manufacturing operation should not be receipt") def test_internal_transfer_standard_behavior(self): """ Test internal transfer uses standard behavior. Requirements: 4.2 """ # Create internal transfer picking picking = self.StockPicking.create({ 'picking_type_id': self.picking_type_internal.id, 'location_id': self.location_stock.id, 'location_dest_id': self.location_stock.id, }) move = self.StockMove.create({ 'name': 'Test Move', 'product_id': self.product.id, 'product_uom_qty': 1.0, 'product_uom': self.product.uom_id.id, 'picking_id': picking.id, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, }) move_line = self._create_move_line(move) # Verify it's not a receipt operation self.assertFalse(move_line._is_receipt_operation(), "Internal transfer should not be receipt") def test_delivery_operation_standard_behavior(self): """ Test delivery operation uses standard behavior. Requirements: 4.2 """ # Create delivery picking picking = self.StockPicking.create({ 'picking_type_id': self.picking_type_out.id, 'location_id': self.location_stock.id, 'location_dest_id': self.location_customer.id, }) move = self.StockMove.create({ 'name': 'Test Move', 'product_id': self.product.id, 'product_uom_qty': 1.0, 'product_uom': self.product.uom_id.id, 'picking_id': picking.id, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, }) move_line = self._create_move_line(move) # Verify it's not a receipt operation self.assertFalse(move_line._is_receipt_operation(), "Delivery operation should not be receipt")