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

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 (
BizFillingRecord,
@@ -36,6 +37,7 @@ from module_business.entity.vo.operation_vo import (
ShipmentUpdateModel,
)
from module_business.service.access_service import AccessService
from module_business.service.test_batch_access_service import TestBatchAccessService
class ShipmentService:
@@ -97,15 +99,16 @@ class ShipmentService:
source_warehouse_id: int,
target_site_id: int,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> tuple[Any, Any]:
await AccessService.assert_warehouse_access(query_db, current_user, source_warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, source_warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, source_warehouse_id, test_scope)
if warehouse is None:
raise ServiceException(message='来源仓库不存在')
if warehouse.status != '0':
raise ServiceException(message='来源仓库已停用')
await AccessService.assert_site_access(query_db, current_user, target_site_id)
site = await MasterDao.get_site(query_db, target_site_id)
site = await MasterDao.get_site(query_db, target_site_id, test_scope)
if site is None:
raise ServiceException(message='目标现场不存在')
if site.status != '0':
@@ -158,10 +161,11 @@ class ShipmentService:
query_db: AsyncSession,
pallet_ids: list[int],
source_warehouse_id: int,
test_scope: TestBatchScope,
) -> tuple[list[BizPalletInventory], list[BizPallet], list[BizFillingRecord]]:
ordered_ids = cls._validate_pallet_ids(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)
if (
len(inventories) != len(ordered_ids)
or len(pallets) != len(ordered_ids)
@@ -181,7 +185,7 @@ class ShipmentService:
fillings = []
for filling_id in sorted(set(active_filling_ids)):
filling = await OperationDao.get_active_filling_for_update(query_db, filling_id)
filling = await OperationDao.get_active_filling_for_update(query_db, filling_id, test_scope)
if filling is None:
raise ServiceException(message='托盘活动装填周期不存在或已关闭')
fillings.append(filling)
@@ -207,6 +211,7 @@ class ShipmentService:
pallet_ids: list[int],
user_name: str,
current_time: datetime,
test_batch_id: int | None,
) -> list[BizShipmentItem]:
return [
BizShipmentItem(
@@ -216,6 +221,7 @@ class ShipmentService:
package_qty=None,
create_by=user_name,
create_time=current_time,
test_batch_id=test_batch_id,
)
for pallet_id in sorted(pallet_ids)
]
@@ -239,6 +245,7 @@ class ShipmentService:
query_db: AsyncSession,
query_object: ShipmentPageQueryModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
is_page: bool = True,
) -> PageModel | list[ShipmentModel]:
warehouse_ids, site_ids = await cls._scopes(query_db, current_user)
@@ -268,9 +275,10 @@ class ShipmentService:
result = await OperationDao.get_shipment_list(
query_db,
query_object,
is_page,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
scope=test_scope,
is_page=is_page,
)
rows = result.rows if isinstance(result, PageModel) else result
models = [ShipmentModel.model_validate(record) for record in rows]
@@ -284,11 +292,13 @@ class ShipmentService:
query_db: AsyncSession,
shipment_id: int,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> ShipmentModel:
warehouse_ids, site_ids = await cls._scopes(query_db, current_user)
shipment = await OperationDao.get_shipment_by_id(
query_db,
shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -301,12 +311,14 @@ class ShipmentService:
cls,
query_db: AsyncSession,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> list[BusinessOptionModel]:
warehouse_ids, _ = await cls._scopes(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 = []
@@ -330,12 +342,14 @@ class ShipmentService:
cls,
query_db: AsyncSession,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> list[BusinessOptionModel]:
_, site_ids = await cls._scopes(query_db, current_user)
allowed_ids = None if site_ids is None else set(site_ids)
sites = await MasterDao.list_site(
query_db,
SitePageQueryModel(status='0'),
test_scope,
False,
)
options = []
@@ -388,13 +402,14 @@ class ShipmentService:
qr_token: str,
query_object: ShipmentScanQueryModel,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> PalletScanResponseModel:
await AccessService.assert_warehouse_access(
query_db,
current_user,
query_object.source_warehouse_id,
)
warehouse = await MasterDao.get_warehouse(query_db, query_object.source_warehouse_id)
warehouse = await MasterDao.get_warehouse(query_db, query_object.source_warehouse_id, test_scope)
if warehouse is None:
raise ServiceException(message='来源仓库不存在')
if warehouse.status != '0':
@@ -404,16 +419,16 @@ class ShipmentService:
current_user,
query_object.target_site_id,
)
site = await MasterDao.get_site(query_db, query_object.target_site_id)
site = await MasterDao.get_site(query_db, query_object.target_site_id, test_scope)
if site is None:
raise ServiceException(message='目标现场不存在')
if site.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:
cls._raise_pallet_not_accessible(current_user)
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:
cls._raise_pallet_not_accessible(current_user, '托盘库存不存在')
if inventory.pallet_id != pallet.pallet_id or inventory.warehouse_id != query_object.source_warehouse_id:
@@ -422,7 +437,7 @@ class ShipmentService:
cls._raise_pallet_not_accessible(current_user, '托盘已在客户现场')
if inventory.active_filling_id is None:
raise ServiceException(message='托盘不存在活动装填周期')
filling = await OperationDao.get_filling_by_id(query_db, inventory.active_filling_id)
filling = await OperationDao.get_filling_by_id(query_db, inventory.active_filling_id, test_scope)
if filling is None:
raise ServiceException(message='托盘活动装填周期不存在')
cls._validate_scan_pallet(
@@ -455,20 +470,24 @@ class ShipmentService:
query_db: AsyncSession,
page_object: ShipmentCreateModel,
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_route(
query_db,
page_object.source_warehouse_id,
page_object.target_site_id,
current_user,
test_scope,
)
ordered_ids = cls._validate_pallet_ids(page_object.pallet_ids)
await cls._lock_and_validate_pallets(
query_db,
ordered_ids,
page_object.source_warehouse_id,
test_scope,
)
await cls._lock_and_authorize_routes(
query_db,
@@ -478,6 +497,7 @@ class ShipmentService:
hidden_warehouse_ids=set(),
hidden_site_ids=set(),
require_active=True,
test_scope=test_scope,
)
current_time = datetime.now()
@@ -490,12 +510,13 @@ class ShipmentService:
create_time=current_time,
update_by=user_name,
update_time=current_time,
test_batch_id=test_scope.batch_id,
)
shipment = await OperationDao.add_shipment(query_db, shipment)
await OperationDao.replace_shipment_items(
query_db,
shipment.shipment_id,
cls._build_items(shipment.shipment_id, ordered_ids, user_name, current_time),
cls._build_items(shipment.shipment_id, ordered_ids, user_name, current_time, test_scope.batch_id),
)
await query_db.commit()
return cls._success('新增成功')
@@ -512,13 +533,16 @@ class ShipmentService:
query_db: AsyncSession,
page_object: ShipmentUpdateModel,
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, site_ids = await cls._scopes(query_db, current_user)
shipment = await OperationDao.get_shipment_for_update(
query_db,
page_object.shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -534,12 +558,14 @@ class ShipmentService:
page_object.source_warehouse_id,
page_object.target_site_id,
current_user,
test_scope,
)
ordered_ids = cls._validate_pallet_ids(page_object.pallet_ids)
await cls._lock_and_validate_pallets(
query_db,
ordered_ids,
page_object.source_warehouse_id,
test_scope,
)
await cls._lock_and_authorize_routes(
query_db,
@@ -555,6 +581,7 @@ class ShipmentService:
hidden_warehouse_ids={shipment.source_warehouse_id},
hidden_site_ids={shipment.target_site_id},
require_active=True,
test_scope=test_scope,
)
current_time = datetime.now()
@@ -566,7 +593,7 @@ class ShipmentService:
await OperationDao.replace_shipment_items(
query_db,
shipment.shipment_id,
cls._build_items(shipment.shipment_id, ordered_ids, user_name, current_time),
cls._build_items(shipment.shipment_id, ordered_ids, user_name, current_time, test_scope.batch_id),
)
await query_db.commit()
return cls._success('修改成功')
@@ -583,53 +610,9 @@ class ShipmentService:
query_db: AsyncSession,
shipment_ids: list[int],
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
if not shipment_ids or any(
type(shipment_id) is not int or shipment_id <= 0 for shipment_id in shipment_ids
):
raise ServiceException(message='发货单ID必须为正整数')
if len(shipment_ids) != len(set(shipment_ids)):
raise ServiceException(message='发货单ID不能重复')
ordered_ids = sorted(shipment_ids)
warehouse_ids, site_ids = await cls._scopes(query_db, current_user)
locked_shipments = []
for shipment_id in ordered_ids:
shipment = await OperationDao.get_shipment_for_update(
query_db,
shipment_id,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
if shipment is None:
cls._raise_shipment_not_accessible(current_user)
if shipment.status != DocumentStatus.DRAFT.value:
raise ServiceException(message='已确认发货单不允许删除')
locked_shipments.append(shipment)
route_warehouse_ids = {shipment.source_warehouse_id for shipment in locked_shipments}
route_site_ids = {shipment.target_site_id for shipment in locked_shipments}
await cls._lock_and_authorize_routes(
query_db,
current_user,
warehouse_ids=route_warehouse_ids,
site_ids=route_site_ids,
hidden_warehouse_ids=route_warehouse_ids,
hidden_site_ids=route_site_ids,
require_active=False,
)
for shipment_id in ordered_ids:
await OperationDao.delete_shipment(query_db, shipment_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(
@@ -656,6 +639,7 @@ class ShipmentService:
hidden_warehouse_ids: set[int],
hidden_site_ids: set[int],
require_active: bool,
test_scope: TestBatchScope,
) -> None:
warehouses: dict[int, Any] = {}
warehouse_permission_errors: dict[int, PermissionException] = {}
@@ -663,6 +647,7 @@ class ShipmentService:
warehouse = await MasterDao.get_warehouse(
query_db,
warehouse_id,
test_scope,
for_update=True,
)
warehouses[warehouse_id] = warehouse
@@ -682,6 +667,7 @@ class ShipmentService:
site = await MasterDao.get_site(
query_db,
site_id,
test_scope,
for_update=True,
)
sites[site_id] = site
@@ -727,6 +713,7 @@ class ShipmentService:
shipment: BizShipmentOrder,
current_user: CurrentUserModel,
*,
test_scope: TestBatchScope,
require_active: bool = True,
) -> None:
await cls._lock_and_authorize_routes(
@@ -737,6 +724,7 @@ class ShipmentService:
hidden_warehouse_ids={shipment.source_warehouse_id},
hidden_site_ids={shipment.target_site_id},
require_active=require_active,
test_scope=test_scope,
)
@classmethod
@@ -747,12 +735,14 @@ class ShipmentService:
idempotency_key: str,
request_hash: str,
current_user: CurrentUserModel,
test_scope: TestBatchScope,
) -> CrudResponseModel:
try:
warehouse_ids, site_ids = await cls._scopes(query_db, current_user)
current = await OperationDao.get_shipment_by_id(
query_db,
shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -761,6 +751,7 @@ class ShipmentService:
recovered = await OperationDao.get_shipment_by_confirm_idempotency_key(
query_db,
idempotency_key,
test_scope,
warehouse_ids=[current.source_warehouse_id],
site_ids=[current.target_site_id],
)
@@ -776,6 +767,7 @@ class ShipmentService:
locked_recovered = await OperationDao.get_shipment_for_update(
query_db,
shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -792,6 +784,7 @@ class ShipmentService:
query_db,
locked_recovered,
current_user,
test_scope=test_scope,
require_active=False,
)
return cls._success('确认成功')
@@ -805,13 +798,16 @@ class ShipmentService:
shipment_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, site_ids = await cls._scopes(query_db, current_user)
shipment = await OperationDao.get_shipment_by_id(
query_db,
shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -823,6 +819,7 @@ class ShipmentService:
existing = await OperationDao.get_shipment_by_confirm_idempotency_key(
query_db,
idempotency_key,
test_scope,
warehouse_ids=[shipment.source_warehouse_id],
site_ids=[shipment.target_site_id],
)
@@ -837,6 +834,7 @@ class ShipmentService:
locked_shipment = await OperationDao.get_shipment_for_update(
query_db,
shipment_id,
test_scope,
warehouse_ids=warehouse_ids,
site_ids=site_ids,
)
@@ -853,6 +851,7 @@ class ShipmentService:
query_db,
locked_shipment,
current_user,
test_scope=test_scope,
require_active=False,
)
await query_db.rollback()
@@ -874,8 +873,9 @@ class ShipmentService:
query_db,
ordered_ids,
locked_shipment.source_warehouse_id,
test_scope,
)
await cls._lock_and_reauthorize_route(query_db, locked_shipment, current_user)
await cls._lock_and_reauthorize_route(query_db, locked_shipment, current_user, test_scope=test_scope)
for inventory in inventories:
PalletStateMachine.ensure_transition(
@@ -930,6 +930,7 @@ class ShipmentService:
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)
@@ -943,6 +944,7 @@ class ShipmentService:
page_object.idempotency_key,
cls._build_confirm_request_hash(shipment_id),
current_user,
test_scope,
)
except Exception:
await query_db.rollback()