feat: Track 打通已完成/已入库/已出库状态闭环与外部联动

- 完工转交入库同步 status=COMPLETED;MOM 入库/出库回调同步 status
- 新增 mom-outbound webhook(发货出库标记已出库),lookup 返回 material_id
- VALID_OVERALL_STATUS 新增已出库;update_overall_status 同步 status 字段
- WIP 矩阵区分已入库/待仓库收货;虚拟节点正确处理已出库/转入在库人
This commit is contained in:
2026-09-01 13:53:24 +08:00
parent 7f5bedf87c
commit 73faa1fd93
9 changed files with 503 additions and 41 deletions

View File

@ -160,12 +160,27 @@ async def get_dashboard_stats(
)
from app.models.notification import Notification
from app.models.message import ProductMessage
# ── 产品(实时快照,不过滤) ──
# 状态口径(用户确认):废弃 Product.status 恒值判断,
# COMPLETED=待仓库收货,ARCHIVED=已入库('在库' 旧命名等价)。
# 产品流转卡片第三段"已入库"= 全部完结(待收货 + 已实收 + 旧在库),保证三段和 = 总数。
p_total = await db.scalar(select(func.count(Product.id)))
p_pending = await db.scalar(select(func.count(Product.id)).where(Product.status == "pending"))
p_progress = await db.scalar(select(func.count(Product.id)).where(Product.status == "in_progress"))
p_done = await db.scalar(select(func.count(Product.id)).where(Product.status == "completed"))
finished_cond = Product.overall_status.in_(["待仓库收货", "已入库", "在库"])
p_done = await db.scalar(select(func.count(Product.id)).where(finished_cond))
# 在制 WIP = 未完结 且 存在活跃任务(PENDING/WIP) 的产品数
not_finished = or_(Product.overall_status.is_(None), ~finished_cond)
has_active_task = select(Task.id).where(
Task.product_id == Product.id,
Task.status.in_([TASK_STATUS_PENDING, TASK_STATUS_WIP]),
).exists()
p_progress = await db.scalar(
select(func.count(Product.id)).where(not_finished, has_active_task)
)
# 待流转 = 总数 - 在制 - 完结(三段互斥,保证进度条总和=总数)
p_pending = max((p_total or 0) - (p_progress or 0) - (p_done or 0), 0)
# ── 任务实时快照(PENDING/WIP/返工 — 永远不过滤) ──
t_pending = await db.scalar(select(func.count(Task.id)).where(Task.status == TASK_STATUS_PENDING))
@ -639,6 +654,8 @@ async def get_wip_matrix(
Task.assignee_id,
Task.created_at,
Product.current_location_id,
Product.overall_status,
Product.status,
)
.join(Task, Task.product_id == Product.id)
.where(
@ -654,7 +671,7 @@ async def get_wip_matrix(
# 每台设备 → (spec, 当前工序/负责人, 当前负责人ID)
device_cur: dict[str, tuple] = {}
seen: set[str] = set()
for pid, spec, task_name, assignee, created, loc in rows:
for pid, spec, task_name, assignee, created, loc, overall_status, product_status in rows:
if pid in seen:
continue
seen.add(pid)
@ -668,10 +685,15 @@ async def get_wip_matrix(
continue
if dimension == "task_name":
# 🔧 在库按设备实际位置判断:virtual_warehouse → 在库(生产完成)
# 🔧 在仓库的设备按"已完成/已入库"区分:
# 已入库 = MOM 已扫码实收 (overall_status=='已入库'/'在库' 或 status=='ARCHIVED')
# 待仓库收货 = 完工已转交仓库、MOM 尚未扫码实收
# 否则按最新主任务工序名(活跃/完成态都归该工序)
if loc == "virtual_warehouse":
key = "在库"
if overall_status in ("已入库", "在库") or str(product_status).upper() == "ARCHIVED":
key = "已入库"
else:
key = "待仓库收货"
else:
key = task_name or "—"
else: