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

283 lines
11 KiB
Python

# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase
from hypothesis import given, strategies as st, settings, assume
class TestLotNumberUpdateSynchronization(TransactionCase):
"""
Property-based tests for lot number update synchronization.
Feature: quality-check-lot-preserve, Property 3: Lot number update synchronization
Property: For any quality check with an assigned lot number, when the lot number
on the related stock move line is changed, the quality check should be updated
with the new lot number.
Validates: Requirements 3.1
"""
def setUp(self):
super(TestLotNumberUpdateSynchronization, self).setUp()
# Get required models
self.StockMoveLine = self.env['stock.move.line']
self.QualityCheck = self.env['quality.check']
self.StockPicking = self.env['stock.picking']
self.StockMove = self.env['stock.move']
self.ProductProduct = self.env['product.product']
self.StockLocation = self.env['stock.location']
self.StockPickingType = self.env['stock.picking.type']
self.StockLot = self.env['stock.lot']
self.QualityPoint = self.env['quality.point']
# Get or create locations
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
self.stock_location = self.env.ref('stock.stock_location_stock')
# Get or create receipt picking type
self.receipt_picking_type = self.env['stock.picking.type'].search([
('code', '=', 'incoming')
], limit=1)
if not self.receipt_picking_type:
self.receipt_picking_type = self.env['stock.picking.type'].create({
'name': 'Receipts',
'code': 'incoming',
'sequence_code': 'IN',
'warehouse_id': self.env['stock.warehouse'].search([], limit=1).id,
})
def _create_product_with_tracking(self, name):
"""Helper to create a product with lot tracking enabled"""
return self.ProductProduct.create({
'name': name,
'type': 'consu',
'tracking': 'lot',
})
def _create_receipt_picking(self, product):
"""Helper to create a receipt picking with a move line for the given product"""
picking = self.StockPicking.create({
'picking_type_id': self.receipt_picking_type.id,
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
})
move = self.StockMove.create({
'name': product.name,
'product_id': product.id,
'product_uom_qty': 10.0,
'product_uom': product.uom_id.id,
'picking_id': picking.id,
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
})
# Confirm the picking to create move lines
picking.action_confirm()
return picking, move
def _create_lot(self, product, lot_name):
"""Helper to create a lot for a product"""
return self.StockLot.create({
'name': lot_name,
'product_id': product.id,
'company_id': self.env.company.id,
})
def _create_quality_check_for_move_line(self, move_line, state='none'):
"""Helper to create a quality check linked to a move line"""
# Get or create a quality team
quality_team = self.env['quality.alert.team'].search([], limit=1)
if not quality_team:
quality_team = self.env['quality.alert.team'].create({
'name': 'Test Quality Team',
})
# Create a quality point for the product if needed
quality_point = self.QualityPoint.search([
('product_ids', 'in', [move_line.product_id.id]),
('picking_type_ids', 'in', [self.receipt_picking_type.id]),
], limit=1)
if not quality_point:
# Get a test type - use passfail which should always exist
test_type = self.env.ref('quality_control.test_type_passfail', raise_if_not_found=False)
if not test_type:
# If no test type exists, create a minimal one
test_type = self.env['quality.point.test_type'].create({
'name': 'Pass/Fail Test',
'technical_name': 'passfail',
})
quality_point = self.QualityPoint.create({
'title': f'Quality Check for {move_line.product_id.name}',
'product_ids': [(4, move_line.product_id.id)],
'picking_type_ids': [(4, self.receipt_picking_type.id)],
'test_type_id': test_type.id,
'team_id': quality_team.id,
})
return self.QualityCheck.create({
'product_id': move_line.product_id.id,
'picking_id': move_line.picking_id.id,
'move_line_id': move_line.id,
'quality_state': state,
'point_id': quality_point.id,
'team_id': quality_team.id,
})
@settings(max_examples=100, deadline=None)
@given(
initial_lot_seed=st.integers(min_value=1, max_value=1000000),
updated_lot_seed=st.integers(min_value=1, max_value=1000000),
quality_state=st.sampled_from(['none', 'pass', 'fail']),
)
def test_property_lot_update_synchronization(self, initial_lot_seed, updated_lot_seed, quality_state):
"""
Property: For any quality check with an assigned lot number, when the lot number
on the related stock move line is changed, the quality check should be updated
with the new lot number.
This test verifies Requirement 3.1:
- When a lot number on a stock move line is changed after initial assignment,
the related quality check is updated with the new lot number
Test strategy:
1. Create a receipt with a quality check
2. Assign an initial lot number to the move line
3. Verify the quality check receives the initial lot number
4. Change the lot number on the move line to a different lot
5. Verify the quality check is updated with the new lot number
"""
# Ensure the two lot numbers are different
assume(initial_lot_seed != updated_lot_seed)
# Create a product with lot tracking
product = self._create_product_with_tracking(f'Test Product {initial_lot_seed}')
# Create receipt picking
picking, move = self._create_receipt_picking(product)
# Get the move line
move_line = picking.move_line_ids[0]
# Create quality check with the specified state
quality_check = self._create_quality_check_for_move_line(move_line, state=quality_state)
# Record the initial quality check state
initial_state = quality_check.quality_state
# Create and assign the initial lot number
initial_lot = self._create_lot(product, f'LOT-INITIAL-{initial_lot_seed}')
move_line.write({'lot_id': initial_lot.id})
# Refresh quality check from database
quality_check.invalidate_recordset()
# Verify the quality check received the initial lot number
self.assertEqual(
quality_check.lot_id.id,
initial_lot.id,
f"Quality check should have initial lot {initial_lot.name} assigned"
)
# Create a new lot number
updated_lot = self._create_lot(product, f'LOT-UPDATED-{updated_lot_seed}')
# Change the lot number on the move line
move_line.write({'lot_id': updated_lot.id})
# Refresh quality check from database
quality_check.invalidate_recordset()
# PROPERTY VERIFICATION:
# 1. The quality check should be updated with the new lot number (Requirement 3.1)
self.assertEqual(
quality_check.lot_id.id,
updated_lot.id,
f"Quality check should be updated with new lot {updated_lot.name} "
f"(was {initial_lot.name})"
)
# 2. The quality check state should remain unchanged (Requirement 3.2)
self.assertEqual(
quality_check.quality_state,
initial_state,
f"Quality check state should remain {initial_state} after lot update"
)
# 3. The relationship between quality check and move line should remain intact (Requirement 3.3)
self.assertEqual(
quality_check.move_line_id.id,
move_line.id,
"Quality check should still be linked to the same move line"
)
@settings(max_examples=100, deadline=None)
@given(
lot_seed_1=st.integers(min_value=1, max_value=1000000),
lot_seed_2=st.integers(min_value=1, max_value=1000000),
lot_seed_3=st.integers(min_value=1, max_value=1000000),
)
def test_property_multiple_lot_updates(self, lot_seed_1, lot_seed_2, lot_seed_3):
"""
Property: For any quality check, multiple consecutive lot number changes
on the move line should result in the quality check always reflecting
the most recent lot number.
This test verifies that the synchronization works correctly even with
multiple updates in sequence.
"""
# Ensure all lot numbers are different
assume(lot_seed_1 != lot_seed_2)
assume(lot_seed_2 != lot_seed_3)
assume(lot_seed_1 != lot_seed_3)
# Create a product with lot tracking
product = self._create_product_with_tracking(f'Test Product {lot_seed_1}')
# Create receipt picking
picking, move = self._create_receipt_picking(product)
# Get the move line
move_line = picking.move_line_ids[0]
# Create quality check
quality_check = self._create_quality_check_for_move_line(move_line, state='pass')
# Create three different lots
lot_1 = self._create_lot(product, f'LOT-1-{lot_seed_1}')
lot_2 = self._create_lot(product, f'LOT-2-{lot_seed_2}')
lot_3 = self._create_lot(product, f'LOT-3-{lot_seed_3}')
# First update: assign lot_1
move_line.write({'lot_id': lot_1.id})
quality_check.invalidate_recordset()
self.assertEqual(quality_check.lot_id.id, lot_1.id)
# Second update: change to lot_2
move_line.write({'lot_id': lot_2.id})
quality_check.invalidate_recordset()
self.assertEqual(quality_check.lot_id.id, lot_2.id)
# Third update: change to lot_3
move_line.write({'lot_id': lot_3.id})
quality_check.invalidate_recordset()
# PROPERTY VERIFICATION: Quality check should have the final lot number
self.assertEqual(
quality_check.lot_id.id,
lot_3.id,
f"Quality check should have the final lot {lot_3.name} after multiple updates"
)
# State should still be 'pass'
self.assertEqual(
quality_check.quality_state,
'pass',
"Quality check state should remain 'pass' after multiple lot updates"
)