400 lines
15 KiB
Python
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")
|