feat: 直接完结通道与报表口径收口(后端)
【直接完结:保留动作,剥离状态】 - transfer_task 权限硬拦截:仅 SUPER_ADMIN / SUPERVISOR。判据取签名保护的 operator_role,刻意不用客户端可通过 ?operator_id= 伪造的 operator_id - 直接完结【不再改写】overall_status —— 它只是任务闭环动作,不改变物理状态。 已出库设备完结后依然是「已出库」,回归 WIP 矩阵的已出库列 - 物理终态保护:create_task / receive_task / transfer_task 三处写入点统一加 _is_physical_terminal 守卫,禁止用任务名覆写 已入库/在库/已出库。 历史缺陷:「发货测试」会把设备的「已出库」标识静默抹掉,跨报表口径随之打架 【位置与工序口径】 - _recalc_product_location:已出库且闲置 → 位置清空(货发走就离场)。 只清「已出库」;已入库/在库的设备确实还在仓库里,位置必须保留 - MOM 出库回调同步清空 current_location_id - 新增 ProductResponse.current_step:只认活跃主干任务,无活跃任务时返回 宏观终态或空。不再让已 COMPLETED 的历史工序(扫码出库/测试…)冒充"当前工序" 【售后烙印去死锁】 - 拆分 OUTBOUND_QC_STEPS(发货测试) / AFTER_SALES_REPAIR_STEPS(售后维修) - resolve_phase_for_step 与 _mark_after_sales_if_reactivated 双双改为仅 「售后维修」触发 AFTER_SALES,解除「已出库 + 发货测试」被永久烙印的死锁 - PRODUCTION_OVERALL_STEPS 补入「发货测试」,使出厂质检在生产阶段合法 (否则 _enforce_step_isolation 会把已出库设备的发货测试直接 400) 【游魂收口】 - get_wip_matrix / get_wip_matrix_detail 的 is_terminal 增加"名下再无任何活跃 任务则视同终结态",让被直接完结的生产设备接受时间筛选,不再恒挂在看板上 冒充在制。两处必须一字不差同步,否则会出现"矩阵有数、下钻为空"
This commit is contained in:
@ -673,6 +673,8 @@ async def get_all_products(
|
||||
|
||||
# 🔧 动态主干状态名:只从主干任务中获取最高优先级任务的 task_name(宏观状态名)
|
||||
overall_names: dict[uuid.UUID, str] = {}
|
||||
# 🔧 【当前工序】专用:仅「活跃主干任务」的工序名(见下方 main_stmt 循环填充)
|
||||
active_step_map: dict[uuid.UUID, str] = {}
|
||||
if product_ids:
|
||||
from sqlalchemy import and_, func as sa_func, case as sa_case
|
||||
main_where = and_(
|
||||
@ -694,7 +696,7 @@ async def get_all_products(
|
||||
.group_by(Task.product_id)
|
||||
).subquery("mp")
|
||||
main_stmt = (
|
||||
select(Task.product_id, Task.task_name)
|
||||
select(Task.product_id, Task.task_name, Task.status)
|
||||
.join(max_prio, and_(
|
||||
Task.product_id == max_prio.c.product_id,
|
||||
prio_expr == max_prio.c.prio,
|
||||
@ -706,6 +708,11 @@ async def get_all_products(
|
||||
main_result = await db.execute(main_stmt)
|
||||
for row in main_result:
|
||||
overall_names[row[0]] = row[1]
|
||||
# 🔧 供【当前工序】专用:只收【活跃】主干任务的工序名。
|
||||
# overall_names 保持原语义不动(它连 COMPLETED 的任务也收,
|
||||
# 是"最新主干任务名",被 overall_status 复用,改动影响面太大)。
|
||||
if row[2] in ("WIP", "PENDING"):
|
||||
active_step_map[row[0]] = row[1]
|
||||
|
||||
# 🔧 当前位置:汇总所有活跃任务(WIP/PENDING,不分主线/分支)的负责人,去重保序
|
||||
active_assignees_map: dict[uuid.UUID, list[str]] = {}
|
||||
@ -825,6 +832,28 @@ async def get_all_products(
|
||||
return "COMPLETED"
|
||||
return macro_map.get(p.id) or "PENDING"
|
||||
|
||||
# 宏观终态 —— 表达的是"货在哪",不是"在做什么"。
|
||||
# 这类值即使当前没有活跃任务也必须照常展示(业务要求保留物理标识)。
|
||||
_TERMINAL_OVERALL = ("待仓库收货", "已入库", "在库", "已出库")
|
||||
|
||||
def _resolve_current_step(p: Product) -> str:
|
||||
"""【当前工序】—— 只反映"此刻在做什么",绝不拿已完结的历史工序冒充。
|
||||
|
||||
· 有活跃主干任务(WIP/PENDING) → 该任务工序名
|
||||
· 否则产品处于宏观终态 → 该终态("货在哪",保留)
|
||||
· 否则(活已干完、只剩工序名残留)→ ""(前端显示「—」)
|
||||
|
||||
历史缺陷:ProductResponse.overall_status 会被"最新主干任务名"覆盖
|
||||
(见上方 overall_names),于是已 COMPLETED 的「扫码出库 / 测试 / 发货测试」
|
||||
会长期冒充"当前工序",误导管理者以为活还在干。
|
||||
"""
|
||||
raw = (p.overall_status or "").strip()
|
||||
if not raw:
|
||||
return ""
|
||||
if raw in _TERMINAL_OVERALL:
|
||||
return raw
|
||||
return active_step_map.get(p.id, "")
|
||||
|
||||
return [
|
||||
ProductResponse(
|
||||
id=p.id,
|
||||
@ -849,6 +878,7 @@ async def get_all_products(
|
||||
),
|
||||
macro_status=_resolve_macro_status(p),
|
||||
overall_status=overall_names.get(p.id) or p.overall_status,
|
||||
current_step=_resolve_current_step(p),
|
||||
status=p.status,
|
||||
lifecycle_phase=p.lifecycle_phase,
|
||||
created_at=p.created_at,
|
||||
|
||||
Reference in New Issue
Block a user