quality_check_lot_preserve/tests/test_edge_cases.py
2025-11-27 10:00:38 +07:00

400 lines
15 KiB
Python

# -*- 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")