功能:隔离五类测试业务链路

This commit is contained in:
焦龙言
2026-09-23 08:27:14 +08:00
parent 43fe17cc70
commit f5df2150d8
18 changed files with 1206 additions and 831 deletions
@@ -8,7 +8,7 @@ from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from common.vo import CrudResponseModel, PageModel
from exceptions.exception import PermissionException, ServiceException
from exceptions.exception import PermissionException, ServiceException, ServiceWarning
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_business.dao.access_dao import AccessDao
from module_business.dao.inventory_dao import InventoryDao
@@ -16,6 +16,7 @@ from module_business.dao.master_dao import MasterDao
from module_business.dao.operation_dao import OperationDao
from module_business.domain.enums import DocumentStatus, PalletEventType, PalletFlowState
from module_business.domain.pallet_state_machine import PalletStateMachine
from module_business.domain.test_batch_scope import TestBatchScope
from module_business.entity.do.master_do import BizPallet
from module_business.entity.do.operation_do import (
BizInboundItem,
@@ -35,6 +36,7 @@ from module_business.entity.vo.operation_vo import (
PalletScanResponseModel,
)
from module_business.service.access_service import AccessService
from module_business.service.test_batch_access_service import TestBatchAccessService
class InboundService:
@@ -95,9 +97,10 @@ class InboundService:
query_db: AsyncSession,
warehouse_id: int,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> Any:
await AccessService.assert_warehouse_access(query_db, current_user, warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, warehouse_id, test_scope)
if warehouse is None:
raise ServiceException(message='目标仓库不存在')
if warehouse.status != '0':
@@ -113,6 +116,7 @@ class InboundService:
warehouse_ids: set[int],
hidden_warehouse_ids: set[int],
require_active: bool,
test_scope: TestBatchScope,
) -> None:
"""锁定仓库及授权映射,并基于锁后最新状态重新校验写权限。"""
warehouses: dict[int, Any] = {}
@@ -121,6 +125,7 @@ class InboundService:
warehouses[warehouse_id] = await MasterDao.get_warehouse(
query_db,
warehouse_id,
test_scope,
for_update=True,
)
try:
@@ -152,6 +157,7 @@ class InboundService:
inbound: BizInboundOrder,
current_user: CurrentUserModel,
*,
test_scope: TestBatchScope,
require_active: bool = True,
) -> None:
await cls._lock_and_authorize_warehouses(
@@ -160,6 +166,7 @@ class InboundService:
warehouse_ids={inbound.warehouse_id},
hidden_warehouse_ids={inbound.warehouse_id},
require_active=require_active,
test_scope=test_scope,
)
@staticmethod
@@ -183,10 +190,11 @@ class InboundService:
cls,
query_db: AsyncSession,
pallet_ids: list[int],
test_scope: TestBatchScope,
) -> tuple[list[BizPalletInventory], list[BizPallet]]:
ordered_ids = sorted(pallet_ids)
inventories = await InventoryDao.get_inventories_for_update(query_db, ordered_ids)
pallets = await InventoryDao.get_pallets_by_ids(query_db, ordered_ids)
inventories = await InventoryDao.get_inventories_for_update(query_db, ordered_ids, test_scope)
pallets = await InventoryDao.get_pallets_by_ids(query_db, ordered_ids, test_scope)
expected_ids = set(ordered_ids)
inventory_by_id = {inventory.pallet_id: inventory for inventory in inventories}
pallet_by_id = {pallet.pallet_id: pallet for pallet in pallets}
@@ -205,6 +213,7 @@ class InboundService:
pallet_ids: list[int],
user_name: str,
current_time: datetime,
test_batch_id: int | None,
) -> list[BizInboundItem]:
return [
BizInboundItem(
@@ -212,6 +221,7 @@ class InboundService:
pallet_id=pallet_id,
create_by=user_name,
create_time=current_time,
test_batch_id=test_batch_id,
)
for pallet_id in sorted(pallet_ids)
]
@@ -226,6 +236,7 @@ class InboundService:
query_db: AsyncSession,
query_object: InboundPageQueryModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
is_page: bool = True,
) -> PageModel | list[InboundModel]:
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
@@ -239,8 +250,9 @@ class InboundService:
result = await OperationDao.get_inbound_list(
query_db,
query_object,
is_page,
warehouse_ids=warehouse_ids,
scope=test_scope,
is_page=is_page,
)
rows = result.rows if isinstance(result, PageModel) else result
models = [InboundModel.model_validate(record) for record in rows]
@@ -254,11 +266,13 @@ class InboundService:
query_db: AsyncSession,
inbound_id: int,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> InboundModel:
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
inbound = await OperationDao.get_inbound_by_id(
query_db,
inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if inbound is None:
@@ -270,12 +284,14 @@ class InboundService:
cls,
query_db: AsyncSession,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> list[BusinessOptionModel]:
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
allowed_ids = None if warehouse_ids is None else set(warehouse_ids)
warehouses = await MasterDao.list_warehouse(
query_db,
WarehousePageQueryModel(status='0'),
test_scope,
False,
)
options = []
@@ -301,22 +317,23 @@ class InboundService:
qr_token: str,
query_object: InboundScanQueryModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> PalletScanResponseModel:
await AccessService.assert_warehouse_access(
query_db,
current_user,
query_object.warehouse_id,
)
warehouse = await MasterDao.get_warehouse(query_db, query_object.warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, query_object.warehouse_id, test_scope)
if warehouse is None:
raise ServiceException(message='目标仓库不存在')
if warehouse.status != '0':
raise ServiceException(message='目标仓库已停用')
pallet = await InventoryDao.get_pallet_by_qr_token(query_db, qr_token)
pallet = await InventoryDao.get_pallet_by_qr_token(query_db, qr_token, test_scope)
if pallet is None:
raise ServiceException(message='托盘不存在')
inventory = await MasterDao.get_pallet_inventory(query_db, pallet.pallet_id)
inventory = await MasterDao.get_pallet_inventory(query_db, pallet.pallet_id, test_scope)
if inventory is None:
raise ServiceException(message='托盘库存不存在')
cls._validate_pallet_pair(pallet, inventory)
@@ -344,22 +361,26 @@ class InboundService:
query_db: AsyncSession,
page_object: InboundCreateModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
await TestBatchAccessService.lock_active_batch(query_db, test_scope)
_, user_name = cls._current_identity(current_user)
await cls._validate_active_warehouse(
query_db,
page_object.warehouse_id,
current_user,
test_scope,
)
ordered_ids = sorted(page_object.pallet_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids, test_scope)
await cls._lock_and_authorize_warehouses(
query_db,
current_user,
warehouse_ids={page_object.warehouse_id},
hidden_warehouse_ids=set(),
require_active=True,
test_scope=test_scope,
)
current_time = datetime.now()
@@ -371,12 +392,13 @@ class InboundService:
create_time=current_time,
update_by=user_name,
update_time=current_time,
test_batch_id=test_scope.batch_id,
)
inbound = await OperationDao.add_inbound(query_db, inbound)
await OperationDao.replace_inbound_items(
query_db,
inbound.inbound_id,
cls._build_items(inbound.inbound_id, ordered_ids, user_name, current_time),
cls._build_items(inbound.inbound_id, ordered_ids, user_name, current_time, test_scope.batch_id),
)
await query_db.commit()
return cls._success('新增成功')
@@ -393,13 +415,16 @@ class InboundService:
query_db: AsyncSession,
page_object: InboundUpdateModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
await TestBatchAccessService.lock_active_batch(query_db, test_scope)
_, user_name = cls._current_identity(current_user)
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
inbound = await OperationDao.get_inbound_for_update(
query_db,
page_object.inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if inbound is None:
@@ -410,15 +435,17 @@ class InboundService:
query_db,
page_object.warehouse_id,
current_user,
test_scope,
)
ordered_ids = sorted(page_object.pallet_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids, test_scope)
await cls._lock_and_authorize_warehouses(
query_db,
current_user,
warehouse_ids={inbound.warehouse_id, page_object.warehouse_id},
hidden_warehouse_ids={inbound.warehouse_id},
require_active=True,
test_scope=test_scope,
)
current_time = datetime.now()
@@ -429,7 +456,7 @@ class InboundService:
await OperationDao.replace_inbound_items(
query_db,
inbound.inbound_id,
cls._build_items(inbound.inbound_id, ordered_ids, user_name, current_time),
cls._build_items(inbound.inbound_id, ordered_ids, user_name, current_time, test_scope.batch_id),
)
await query_db.commit()
return cls._success('修改成功')
@@ -446,47 +473,9 @@ class InboundService:
query_db: AsyncSession,
inbound_ids: list[int],
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
if not inbound_ids or any(inbound_id <= 0 for inbound_id in inbound_ids):
raise ServiceException(message='入库单ID必须为正整数')
if len(inbound_ids) != len(set(inbound_ids)):
raise ServiceException(message='入库单ID不能重复')
ordered_ids = sorted(inbound_ids)
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
locked_inbounds = []
for inbound_id in ordered_ids:
inbound = await OperationDao.get_inbound_for_update(
query_db,
inbound_id,
warehouse_ids=warehouse_ids,
)
if inbound is None:
cls._raise_inbound_not_accessible(current_user)
if inbound.status != DocumentStatus.DRAFT.value:
raise ServiceException(message='已确认入库单不允许删除')
locked_inbounds.append(inbound)
locked_warehouse_ids = {inbound.warehouse_id for inbound in locked_inbounds}
await cls._lock_and_authorize_warehouses(
query_db,
current_user,
warehouse_ids=locked_warehouse_ids,
hidden_warehouse_ids=locked_warehouse_ids,
require_active=False,
)
for inbound_id in ordered_ids:
await OperationDao.delete_inbound(query_db, inbound_id)
await query_db.commit()
return cls._success('删除成功')
except IntegrityError:
await query_db.rollback()
raise ServiceException(message='删除入库单失败,单据状态或关联数据已变化') from None
except Exception:
await query_db.rollback()
raise
raise ServiceWarning(message='不支持单条物理删除,请通过测试数据批次清理')
@staticmethod
def _is_exact_confirmed(
@@ -510,12 +499,14 @@ class InboundService:
idempotency_key: str,
request_hash: str,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
current = await OperationDao.get_inbound_by_id(
query_db,
inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if current is None:
@@ -523,6 +514,7 @@ class InboundService:
recovered = await OperationDao.get_inbound_by_confirm_idempotency_key(
query_db,
idempotency_key,
test_scope,
warehouse_ids=[current.warehouse_id],
)
if recovered is None:
@@ -537,6 +529,7 @@ class InboundService:
locked_recovered = await OperationDao.get_inbound_for_update(
query_db,
inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if locked_recovered is None:
@@ -552,6 +545,7 @@ class InboundService:
query_db,
locked_recovered,
current_user,
test_scope=test_scope,
require_active=False,
)
return cls._success('确认成功')
@@ -565,13 +559,16 @@ class InboundService:
inbound_id: int,
page_object: ConfirmCommandModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
await TestBatchAccessService.lock_active_batch(query_db, test_scope)
user_id, user_name = cls._current_identity(current_user)
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
inbound = await OperationDao.get_inbound_by_id(
query_db,
inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if inbound is None:
@@ -582,6 +579,7 @@ class InboundService:
existing = await OperationDao.get_inbound_by_confirm_idempotency_key(
query_db,
idempotency_key,
test_scope,
warehouse_ids=[inbound.warehouse_id],
)
if existing is not None:
@@ -601,6 +599,7 @@ class InboundService:
locked_inbound = await OperationDao.get_inbound_for_update(
query_db,
inbound_id,
test_scope,
warehouse_ids=warehouse_ids,
)
if locked_inbound is None:
@@ -616,6 +615,7 @@ class InboundService:
query_db,
locked_inbound,
current_user,
test_scope=test_scope,
require_active=False,
)
await query_db.rollback()
@@ -634,11 +634,12 @@ class InboundService:
if len(pallet_ids) != len(set(pallet_ids)):
raise ServiceException(message='入库单托盘不能重复')
ordered_ids = sorted(pallet_ids)
inventories, _ = await cls._lock_and_validate_pallets(query_db, ordered_ids)
inventories, _ = await cls._lock_and_validate_pallets(query_db, ordered_ids, test_scope)
await cls._lock_and_reauthorize_warehouse(
query_db,
locked_inbound,
current_user,
test_scope=test_scope,
)
for inventory in inventories:
@@ -691,6 +692,7 @@ class InboundService:
occurred_at=current_time,
idempotency_key=idempotency_key,
request_hash=request_hash,
test_batch_id=test_scope.batch_id,
)
await InventoryDao.add_event(query_db, inventory, event)
@@ -704,6 +706,7 @@ class InboundService:
page_object.idempotency_key,
cls._build_confirm_request_hash(inbound_id),
current_user,
test_scope,
)
except Exception:
await query_db.rollback()