修复:完善退回并发幂等与信息隐藏

This commit is contained in:
焦龙言
2026-07-27 00:08:57 +08:00
parent e58e110dd1
commit 12260fdf83
2 changed files with 556 additions and 39 deletions
@@ -377,7 +377,7 @@ class ReturnService:
cls._raise_pallet_not_accessible(current_user)
matching_items = [item for item in return_order.items if item.pallet_id == pallet.pallet_id]
if len(matching_items) != 1:
raise ServiceException(message='扫码托盘不属于该退回单')
cls._raise_pallet_not_accessible(current_user, '扫码托盘不属于该退回单')
inventory = await MasterDao.get_pallet_inventory(query_db, pallet.pallet_id)
if inventory is None:
cls._raise_pallet_not_accessible(current_user, '托盘库存不存在')
@@ -445,21 +445,37 @@ class ReturnService:
query_db: AsyncSession,
pallet_ids: list[int],
source_site_id: int,
) -> tuple[list[BizPalletInventory], list[BizPallet], dict[int, BizFillingRecord]]:
current_user: CurrentUserModel,
) -> tuple[
list[BizPalletInventory],
list[BizPallet],
dict[int, BizFillingRecord | None],
list[int] | None,
]:
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)
inventory_by_id = {inventory.pallet_id: inventory for inventory in inventories}
pallet_by_id = {pallet.pallet_id: pallet for pallet in pallets}
if set(inventory_by_id) != set(ordered_ids) or set(pallet_by_id) != set(ordered_ids):
raise ServiceException(message='存在不存在或库存缺失的托盘')
if set(inventory_by_id) != set(ordered_ids):
cls._raise_pallet_not_accessible(current_user, '存在不存在或库存缺失的托盘')
ordered_inventories = [inventory_by_id[pallet_id] for pallet_id in ordered_ids]
ordered_pallets = [pallet_by_id[pallet_id] for pallet_id in ordered_ids]
for pallet, inventory in zip(ordered_pallets, ordered_inventories, strict=True):
cls._validate_request_inventory(pallet, inventory, source_site_id)
if any(inventory.site_id != source_site_id for inventory in ordered_inventories):
cls._raise_pallet_not_accessible(current_user, '同一退回单托盘必须属于同一现场')
try:
await AccessService.assert_site_access(query_db, current_user, source_site_id)
except PermissionException:
cls._raise_pallet_not_accessible(current_user)
fresh_site_ids = await cls._site_scope(query_db, current_user)
if fresh_site_ids is not None and source_site_id not in fresh_site_ids:
cls._raise_pallet_not_accessible(current_user)
filling_by_id = {}
pallets = await InventoryDao.get_pallets_by_ids(query_db, ordered_ids)
pallet_by_id = {pallet.pallet_id: pallet for pallet in pallets}
if set(pallet_by_id) != set(ordered_ids):
cls._raise_pallet_not_accessible(current_user, '存在不存在或库存缺失的托盘')
ordered_pallets = [pallet_by_id[pallet_id] for pallet_id in ordered_ids]
filling_by_id: dict[int, BizFillingRecord | None] = {}
filling_ids = sorted(
{
inventory.active_filling_id
@@ -468,14 +484,31 @@ class ReturnService:
}
)
for filling_id in filling_ids:
filling = await OperationDao.get_active_filling_for_update(query_db, filling_id)
if filling is None:
raise ServiceException(message='托盘活动装填周期不存在或已关闭')
filling_by_id[filling_id] = filling
for inventory in ordered_inventories:
filling_by_id[filling_id] = await OperationDao.get_active_filling_for_update(query_db, filling_id)
return ordered_inventories, ordered_pallets, filling_by_id, fresh_site_ids
@classmethod
def _validate_locked_request_pallets(
cls,
inventories: list[BizPalletInventory],
pallets: list[BizPallet],
filling_by_id: dict[int, BizFillingRecord | None],
source_site_id: int,
current_user: CurrentUserModel,
) -> None:
for pallet, inventory in zip(pallets, inventories, strict=True):
cls._validate_request_inventory(pallet, inventory, source_site_id)
for inventory in inventories:
if inventory.active_filling_id is not None:
cls._validate_active_filling(filling_by_id[inventory.active_filling_id], inventory)
return ordered_inventories, ordered_pallets, filling_by_id
filling = filling_by_id[inventory.active_filling_id]
if filling is None:
cls._raise_pallet_not_accessible(current_user, '托盘活动装填周期不存在或已关闭')
try:
cls._validate_active_filling(filling, inventory)
except ServiceException:
if not cls._is_admin(current_user):
cls._raise_pallet_not_accessible(current_user)
raise
@staticmethod
def _build_return_items(
@@ -585,7 +618,7 @@ class ReturnService:
await query_db.rollback()
@classmethod
async def request_return_services(
async def request_return_services( # noqa: PLR0915
cls,
query_db: AsyncSession,
page_object: ReturnRequestModel,
@@ -595,30 +628,34 @@ class ReturnService:
user_id, user_name = cls._current_identity(current_user)
ordered_ids = cls._validate_pallet_ids(page_object.pallet_ids)
request_hash = cls._build_request_hash(page_object)
site_ids = await cls._site_scope(query_db, current_user)
if site_ids is not None and page_object.source_site_id not in site_ids:
raise PermissionException(data='', message='无权访问该客户现场')
await AccessService.assert_site_access(
query_db,
current_user,
page_object.source_site_id,
)
request_warehouse_ids = None if site_ids is None else []
preflight_site_ids = await cls._site_scope(query_db, current_user)
if preflight_site_ids is not None and page_object.source_site_id not in preflight_site_ids:
cls._raise_pallet_not_accessible(current_user)
try:
await AccessService.assert_site_access(
query_db,
current_user,
page_object.source_site_id,
)
except PermissionException:
cls._raise_pallet_not_accessible(current_user)
request_warehouse_ids = None if preflight_site_ids is None else []
existing = await OperationDao.get_return_by_request_idempotency_key(
query_db,
page_object.idempotency_key,
site_ids=site_ids,
site_ids=preflight_site_ids,
warehouse_ids=request_warehouse_ids,
)
if existing is not None and not cls._is_exact_request_result(existing, page_object, request_hash):
raise ServiceException(message='申请退回失败,幂等键已被其他请求使用')
preflight_existing_id = int(existing.return_id) if existing is not None else None
await query_db.rollback()
if existing is not None:
if preflight_existing_id is not None:
locked_existing = await OperationDao.get_return_for_update(
query_db,
existing.return_id,
site_ids=site_ids,
preflight_existing_id,
site_ids=preflight_site_ids,
warehouse_ids=request_warehouse_ids,
)
if locked_existing is None:
@@ -635,12 +672,35 @@ class ReturnService:
await query_db.rollback()
return cls._success('申请成功', return_id)
inventories, _, _ = await cls._lock_request_pallets(
inventories, pallets, filling_by_id, fresh_site_ids = await cls._lock_request_pallets(
query_db,
ordered_ids,
page_object.source_site_id,
current_user,
)
fresh_warehouse_ids = None if fresh_site_ids is None else []
fresh_existing = await OperationDao.get_return_by_request_idempotency_key(
query_db,
page_object.idempotency_key,
site_ids=fresh_site_ids,
warehouse_ids=fresh_warehouse_ids,
)
if fresh_existing is not None:
if not cls._is_exact_request_result(fresh_existing, page_object, request_hash):
raise ServiceException(message='申请退回失败,幂等键已被其他请求使用')
fresh_existing_id = int(fresh_existing.return_id)
await cls._lock_and_reauthorize_request_site(query_db, fresh_existing, current_user)
await query_db.rollback()
return cls._success('申请成功', fresh_existing_id)
await cls._lock_new_request_route(query_db, page_object, current_user)
cls._validate_locked_request_pallets(
inventories,
pallets,
filling_by_id,
page_object.source_site_id,
current_user,
)
for inventory in inventories:
PalletStateMachine.ensure_transition(
PalletFlowState(inventory.flow_state),