修复:收紧托盘业务权限与数据契约

This commit is contained in:
焦龙言
2026-07-27 17:40:24 +08:00
parent 9b1a807098
commit 4a321098f8
16 changed files with 823 additions and 59 deletions
@@ -104,6 +104,64 @@ class InboundService:
raise ServiceException(message='目标仓库已停用')
return warehouse
@classmethod
async def _lock_and_authorize_warehouses(
cls,
query_db: AsyncSession,
current_user: CurrentUserModel,
*,
warehouse_ids: set[int],
hidden_warehouse_ids: set[int],
require_active: bool,
) -> None:
"""锁定仓库及授权映射,并基于锁后最新状态重新校验写权限。"""
warehouses: dict[int, Any] = {}
permission_errors: dict[int, PermissionException] = {}
for warehouse_id in sorted(warehouse_ids):
warehouses[warehouse_id] = await MasterDao.get_warehouse(
query_db,
warehouse_id,
for_update=True,
)
try:
await AccessService.assert_warehouse_access(
query_db,
current_user,
warehouse_id,
for_update=True,
)
except PermissionException as exc:
permission_errors[warehouse_id] = exc
if hidden_warehouse_ids & permission_errors.keys():
cls._raise_inbound_not_accessible(current_user)
if permission_errors:
raise permission_errors[min(permission_errors)]
for warehouse_id in sorted(warehouses):
warehouse = warehouses[warehouse_id]
if warehouse is None:
raise ServiceException(message='目标仓库不存在')
if require_active and warehouse.status != '0':
raise ServiceException(message='目标仓库已停用')
@classmethod
async def _lock_and_reauthorize_warehouse(
cls,
query_db: AsyncSession,
inbound: BizInboundOrder,
current_user: CurrentUserModel,
*,
require_active: bool = True,
) -> None:
await cls._lock_and_authorize_warehouses(
query_db,
current_user,
warehouse_ids={inbound.warehouse_id},
hidden_warehouse_ids={inbound.warehouse_id},
require_active=require_active,
)
@staticmethod
def _validate_pallet_pair(pallet: BizPallet, inventory: BizPalletInventory) -> None:
if pallet.asset_status != '0':
@@ -296,6 +354,13 @@ class InboundService:
)
ordered_ids = sorted(page_object.pallet_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids)
await cls._lock_and_authorize_warehouses(
query_db,
current_user,
warehouse_ids={page_object.warehouse_id},
hidden_warehouse_ids=set(),
require_active=True,
)
current_time = datetime.now()
inbound = BizInboundOrder(
@@ -348,6 +413,13 @@ class InboundService:
)
ordered_ids = sorted(page_object.pallet_ids)
await cls._lock_and_validate_pallets(query_db, ordered_ids)
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,
)
current_time = datetime.now()
inbound.warehouse_id = page_object.warehouse_id
@@ -383,6 +455,7 @@ class InboundService:
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,
@@ -393,6 +466,16 @@ class InboundService:
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)
@@ -429,17 +512,14 @@ class InboundService:
current_user: CurrentUserModel,
) -> CrudResponseModel:
try:
warehouse_ids = await cls._warehouse_scope(query_db, current_user)
recovered = await OperationDao.get_inbound_by_confirm_idempotency_key(
query_db,
idempotency_key,
warehouse_ids=warehouse_ids,
)
if recovered is None:
raise ServiceException(message='确认入库失败,幂等冲突')
await AccessService.assert_warehouse_access(
query_db,
current_user,
recovered.warehouse_id,
)
if not cls._is_exact_confirmed(
recovered,
inbound_id,
@@ -447,6 +527,26 @@ class InboundService:
request_hash,
):
raise ServiceException(message='确认入库失败,幂等键已被其他请求使用')
locked_recovered = await OperationDao.get_inbound_for_update(
query_db,
inbound_id,
warehouse_ids=warehouse_ids,
)
if locked_recovered is None:
cls._raise_inbound_not_accessible(current_user)
if not cls._is_exact_confirmed(
locked_recovered,
inbound_id,
idempotency_key,
request_hash,
):
raise ServiceException(message='确认入库失败,幂等键已被其他请求使用')
await cls._lock_and_reauthorize_warehouse(
query_db,
locked_recovered,
current_user,
require_active=False,
)
return cls._success('确认成功')
finally:
await query_db.rollback()
@@ -475,6 +575,7 @@ class InboundService:
existing = await OperationDao.get_inbound_by_confirm_idempotency_key(
query_db,
idempotency_key,
warehouse_ids=warehouse_ids,
)
if existing is not None:
await AccessService.assert_warehouse_access(
@@ -504,6 +605,12 @@ class InboundService:
idempotency_key,
request_hash,
):
await cls._lock_and_reauthorize_warehouse(
query_db,
locked_inbound,
current_user,
require_active=False,
)
await query_db.rollback()
return cls._success('确认成功')
raise ServiceException(message='入库单已确认,幂等信息不匹配')
@@ -521,6 +628,11 @@ class InboundService:
raise ServiceException(message='入库单托盘不能重复')
ordered_ids = sorted(pallet_ids)
inventories, _ = await cls._lock_and_validate_pallets(query_db, ordered_ids)
await cls._lock_and_reauthorize_warehouse(
query_db,
locked_inbound,
current_user,
)
for inventory in inventories:
PalletStateMachine.ensure_transition(