更新登录页及优化相关功能
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from exceptions.exception import PermissionException, ServiceException
|
||||
from module_business.dao.access_dao import AccessDao
|
||||
from module_business.dao.inventory_dao import InventoryDao
|
||||
from module_business.dao.master_dao import MasterDao
|
||||
from module_business.dao.operation_dao import OperationDao
|
||||
from module_business.domain.test_batch_scope import TestBatchScope
|
||||
from module_business.entity.do.master_do import BizCustomerSite
|
||||
from module_business.entity.vo.shipment_receipt_vo import ShipmentReceiptCommandModel
|
||||
from module_business.service.shipment_receipt_service import ShipmentReceiptService
|
||||
from tests.module_business.test_shipment_service import make_inventory, make_pallet, make_shipment, make_user
|
||||
|
||||
|
||||
class TestShipmentReceipt(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.db = MagicMock(spec=AsyncSession)
|
||||
self.user = make_user()
|
||||
self.scope = TestBatchScope.formal()
|
||||
self.order = make_shipment(status='CONFIRMED', pallet_ids=(2, 5))
|
||||
self.order.receipt_state = 'PENDING'
|
||||
self.order.confirmed_at = datetime(2026, 10, 8, 8)
|
||||
self.inventories = [
|
||||
make_inventory(i, warehouse_id=None, site_id=9, flow_state='IN_TRANSIT', remaining_packages=100)
|
||||
for i in (2, 5)
|
||||
]
|
||||
for item in self.order.items:
|
||||
item.active_filling_id = 100 + item.pallet_id
|
||||
item.package_qty = 100
|
||||
self.events = []
|
||||
self.site_access = AsyncMock(return_value=True)
|
||||
self.patches = [
|
||||
patch.object(AccessDao, 'list_user_site_ids', new=AsyncMock(return_value=[9])),
|
||||
patch.object(AccessDao, 'user_has_site', new=self.site_access),
|
||||
patch.object(OperationDao, 'get_shipment_for_update', new=AsyncMock(return_value=self.order)),
|
||||
patch.object(
|
||||
MasterDao,
|
||||
'get_site',
|
||||
new=AsyncMock(return_value=BizCustomerSite(site_id=9, status='0', test_batch_id=None)),
|
||||
),
|
||||
patch.object(InventoryDao, 'get_inventories_for_update', new=AsyncMock(return_value=self.inventories)),
|
||||
patch.object(
|
||||
InventoryDao,
|
||||
'get_pallets_by_ids',
|
||||
new=AsyncMock(return_value=[make_pallet(i, capacity=100) for i in (2, 5)]),
|
||||
),
|
||||
patch.object(InventoryDao, 'add_event', new=AsyncMock(side_effect=self.capture_event)),
|
||||
]
|
||||
for patcher in self.patches:
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
async def capture_event(self, _db, inventory, event, _scope):
|
||||
self.events.append(event)
|
||||
return event
|
||||
|
||||
def command(self, ids=None, key='receipt-key'):
|
||||
return ShipmentReceiptCommandModel(scannedPalletIds=ids if ids is not None else [5, 2], idempotencyKey=key)
|
||||
|
||||
async def receive(self, command=None):
|
||||
return await ShipmentReceiptService.confirm_services(
|
||||
self.db, 12, command or self.command(), self.user, self.scope
|
||||
)
|
||||
|
||||
async def test_receipt_keeps_original_pallet_filling_and_quantity(self) -> None:
|
||||
await self.receive()
|
||||
self.assertEqual(self.order.receipt_state, 'RECEIVED')
|
||||
self.assertEqual(self.order.received_by, 7)
|
||||
self.assertIsInstance(self.order.received_at, datetime)
|
||||
self.assertEqual(
|
||||
[(i.pallet_id, i.active_filling_id, i.remaining_packages, i.flow_state) for i in self.inventories],
|
||||
[
|
||||
(2, 102, 100, 'AT_SITE_UNUSED'),
|
||||
(5, 105, 100, 'AT_SITE_UNUSED'),
|
||||
],
|
||||
)
|
||||
self.assertTrue(all(i.site_arrived_at == self.order.received_at for i in self.inventories))
|
||||
self.assertEqual(
|
||||
[(e.event_type, e.before_state, e.after_state) for e in self.events],
|
||||
[
|
||||
('SHIPMENT_RECEIVED', 'IN_TRANSIT', 'AT_SITE_UNUSED'),
|
||||
('SHIPMENT_RECEIVED', 'IN_TRANSIT', 'AT_SITE_UNUSED'),
|
||||
],
|
||||
)
|
||||
self.db.commit.assert_awaited_once()
|
||||
|
||||
async def test_retry_after_later_consumption_does_not_restore_quantity(self) -> None:
|
||||
await self.receive()
|
||||
self.inventories[0].remaining_packages = 80
|
||||
self.inventories[0].flow_state = 'AT_SITE_PARTIAL'
|
||||
await self.receive(self.command(ids=[2, 5]))
|
||||
self.assertEqual(self.inventories[0].remaining_packages, 80)
|
||||
self.assertEqual(len(self.events), 2)
|
||||
self.db.commit.assert_awaited_once()
|
||||
|
||||
async def test_received_order_rejects_different_confirmation_key(self) -> None:
|
||||
await self.receive()
|
||||
with self.assertRaises(ServiceException):
|
||||
await self.receive(self.command(key='different'))
|
||||
self.assertEqual(len(self.events), 2)
|
||||
|
||||
async def test_missing_or_unexpected_scans_do_not_change_inventory(self) -> None:
|
||||
for ids in ([2], [2, 5, 6], [2, 6]):
|
||||
with self.subTest(ids=ids), self.assertRaises(ServiceException):
|
||||
await self.receive(self.command(ids=ids))
|
||||
self.assertEqual(self.order.receipt_state, 'PENDING')
|
||||
self.assertFalse(self.events)
|
||||
self.db.commit.assert_not_awaited()
|
||||
|
||||
async def test_target_site_access_is_required_even_with_warehouse_permissions(self) -> None:
|
||||
self.site_access.return_value = False
|
||||
with self.assertRaises(PermissionException):
|
||||
await self.receive()
|
||||
self.assertFalse(self.events)
|
||||
self.db.commit.assert_not_awaited()
|
||||
|
||||
async def test_draft_and_legacy_cannot_be_received_as_new_shipments(self) -> None:
|
||||
for status, receipt_state in [('DRAFT', 'PENDING'), ('CONFIRMED', 'LEGACY')]:
|
||||
self.order.status = status
|
||||
self.order.receipt_state = receipt_state
|
||||
with self.subTest(status=status, receipt_state=receipt_state), self.assertRaises(ServiceException):
|
||||
await self.receive()
|
||||
self.assertFalse(self.events)
|
||||
|
||||
async def test_changed_material_snapshot_is_rejected_before_any_mutation(self) -> None:
|
||||
self.inventories[1].active_filling_id = 999
|
||||
with self.assertRaises(ServiceException):
|
||||
await self.receive()
|
||||
self.assertEqual(self.inventories[0].flow_state, 'IN_TRANSIT')
|
||||
self.assertFalse(self.events)
|
||||
|
||||
async def test_cross_test_batch_inventory_is_rejected(self) -> None:
|
||||
self.inventories[1].test_batch_id = 3
|
||||
with self.assertRaises(ServiceException):
|
||||
await self.receive()
|
||||
self.assertFalse(self.events)
|
||||
Reference in New Issue
Block a user