Files

86 lines
3.5 KiB
Python

"""按真实收货时间推荐可用物料,不推断历史到货时间。"""
from typing import Any
from sqlalchemy import and_, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from module_business.domain.test_batch_scope import TestBatchScope, batch_scope_condition
from module_business.entity.do.master_do import BizPallet
from module_business.entity.do.operation_do import BizFillingRecord, BizPalletInventory
class UsageFifoDao:
@staticmethod
async def recommendation(
db: AsyncSession,
*,
site_id: int,
strain_id: int,
tunnel_id: int | None,
trace_no: str | None,
scope: TestBatchScope,
for_update: bool = False,
) -> tuple[dict[str, Any] | None, int]:
inventory, filling, pallet = BizPalletInventory, BizFillingRecord, BizPallet
unbound = and_(inventory.tunnel_id.is_(None), inventory.trace_no.is_(None))
conditions = [
inventory.site_id == site_id,
inventory.warehouse_id.is_(None),
inventory.flow_state.in_(['AT_SITE_UNUSED', 'AT_SITE_PARTIAL']),
inventory.remaining_packages > 0,
inventory.remaining_packages <= pallet.capacity_packages,
filling.strain_id == strain_id,
filling.active_slot == 'ACTIVE',
filling.closed_at.is_(None),
pallet.asset_status == '0',
batch_scope_condition(inventory.test_batch_id, scope),
batch_scope_condition(filling.test_batch_id, scope),
batch_scope_condition(pallet.test_batch_id, scope),
(
unbound
if tunnel_id is None
else or_(unbound, and_(inventory.tunnel_id == tunnel_id, inventory.trace_no == trace_no))
),
]
base = (
select(
pallet.pallet_id,
pallet.pallet_code,
filling.batch_no,
inventory.remaining_packages,
inventory.site_arrived_at,
)
.select_from(inventory)
.join(pallet, pallet.pallet_id == inventory.pallet_id)
.join(
filling,
and_(filling.filling_id == inventory.active_filling_id, filling.pallet_id == inventory.pallet_id),
)
.where(*conditions)
)
statement = (
base.where(inventory.site_arrived_at.is_not(None))
.order_by(
inventory.site_arrived_at,
inventory.pallet_id,
)
.limit(1)
)
if for_update:
# 写入决策必须用当前读,不能复用权限预检期间建立的 MVCC 旧快照。
# 不使用 skip_locked,避免把正在操作的早到物料悄悄跳过。
# 此时已持有所选库存及现场/隧道锁,不能等待其他候选库存造成反向锁依赖。
# NOWAIT 不会跳过早到托盘:冲突令整个命令回滚,再按原幂等键有限重试。
statement = statement.with_for_update(nowait=True)
known = await db.execute(statement)
first = known.mappings().first()
if for_update:
return (dict(first) if first is not None else None, 0)
unknown = await db.execute(
select(func.count()).select_from(
base.where(inventory.site_arrived_at.is_(None)).subquery(),
)
)
return (dict(first) if first is not None else None, unknown.scalar_one())