fix: WIP矩阵口径重构-在制无条件计入,终结态按完工时间锚点(completed_at兜底created_at)过滤,多维终结归类(含扫码入库/出库节点),下钻同步

This commit is contained in:
2026-09-07 14:46:58 +08:00
parent 2925eb8e0b
commit d757c7985b

View File

@ -652,7 +652,9 @@ async def get_wip_matrix(
- 其他已完成工序(如「测试」完成)按原工序显示,不强制归仓库态
这样一台设备在「上一步已完成 + 下一步待确认」时只算一次(待确认),不会重复计数。
since/until 按设备最新主任务的创建时间过滤。
since/until 时间过滤为“动静分离”:仅对 终结(收口/在库/出库) 设备过滤,锚点取
最新主线任务的 completed_at完工/变动时间,兜底 created_atschema 无 updated_at
在制(未收口)设备无条件全量计入,保证与人员看板实时在制不脱节。
dimension:
- assignee: 按当前任务负责人聚合dimension_key 为中文姓名)
@ -671,6 +673,7 @@ async def get_wip_matrix(
Task.task_name,
Task.assignee_id,
Task.created_at,
Task.completed_at,
Product.current_location_id,
Product.overall_status,
Product.status,
@ -679,7 +682,7 @@ async def get_wip_matrix(
.where(
or_(
Task.parent_task_id.is_(None),
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
Task.task_type.in_(["TRANSFER", "RECOVERY", "WAREHOUSE"]),
)
)
.order_by(Product.id, Task.created_at.desc())
@ -689,33 +692,43 @@ async def get_wip_matrix(
# 每台设备 → (spec, 当前工序/负责人, 当前负责人ID)
device_cur: dict[str, tuple] = {}
seen: set[str] = set()
for pid, spec, product_name, task_name, assignee, created, loc, overall_status, product_status in rows:
for pid, spec, product_name, task_name, assignee, created, completed, loc, overall_status, product_status in rows:
if pid in seen:
continue
seen.add(pid)
# 时间筛选:设备最新主任务的创建时间
if created is not None and created.tzinfo is None:
created = created.replace(tzinfo=dt_timezone.utc)
if since and created is not None and created < since:
continue
if until and created is not None and created > until:
continue
# ── 真实产出时间锚点:完工/变动时间优先,创建时间仅兜底 ──
# 说明tasks 表无 updated_at 列(已核实 information_schema故锚点取
# completed_at or created_at若后续新增 updated_at 可并入。
anchor = completed or created
# ── 动静分离:仅终结(收口/在库/出库)设备按 since~until 过滤;在制设备无条件全量 ──
is_terminal = (
loc == "virtual_warehouse"
or overall_status in ("待仓库收货", "已入库", "在库", "已出库")
or str(product_status).upper() in ("ARCHIVED", "OUTBOUND")
or task_name in ("扫码入库", "扫码出库")
)
if is_terminal:
if anchor is not None and anchor.tzinfo is None:
anchor = anchor.replace(tzinfo=dt_timezone.utc)
if since and anchor is not None and anchor < since:
continue
if until and anchor is not None and anchor > until:
continue
# else: 在制设备无视时间参数,保证与人员看板“实时在制”总数一致
if dimension == "task_name":
# 🔧 在仓库的设备按"已入库/已出库/待仓库收货"区分:
# 已入库 = MOM 已扫码实收 (overall_status=='已入库'/'在库' 或 status=='ARCHIVED')
# 已出库 = MOM 已发货出库 (overall_status=='已出库' 或 status=='OUTBOUND')
# 待仓库收货 = 完工已转交仓库、MOM 尚未扫码实收
# 否则按最新主任务工序名(活跃/完成态都归该工序)
if loc == "virtual_warehouse":
# 统一术语:待仓库收货(车间完工) → "已完成";扫码实收 → "已入库";已出库保持
if overall_status in ("已入库", "在库") or str(product_status).upper() == "ARCHIVED":
key = "已入库"
elif overall_status == "出库" or str(product_status).upper() == "OUTBOUND":
key = "已出库"
else:
key = "已完成"
# ── 终结三列判定(多维防御,不依赖单一 loc / overall──
# 已入库: 整体实收(已入库/在库) 或 status==ARCHIVED 或 收口节点「扫码入库」
if overall_status in ("已入库", "在库") or str(product_status).upper() == "ARCHIVED" or task_name == "扫码入库":
key = "已入库"
# 已出库: 整体已出库 或 status==OUTBOUND 或 收口节点「扫码出库」
elif overall_status == "已出库" or str(product_status).upper() == "OUTBOUND" or task_name == "扫码出库":
key = "已出库"
# 已完成: 车间完工待实收(待仓库收货) 或 处于仓库(未实收/未出库)
elif overall_status == "待仓库收货" or loc == "virtual_warehouse":
key = "完成"
else:
key = task_name or ""
else:
@ -796,13 +809,14 @@ async def get_wip_matrix_detail(
Task.assignee_id,
Task.status.label("task_status"),
Task.created_at,
Task.completed_at,
Task.received_at,
)
.join(Task, Task.product_id == Product.id)
.where(
or_(
Task.parent_task_id.is_(None),
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
Task.task_type.in_(["TRANSFER", "RECOVERY", "WAREHOUSE"]),
)
)
.order_by(Product.id, Task.created_at.desc())
@ -814,7 +828,7 @@ async def get_wip_matrix_detail(
matched: list[WipMatrixDetailRow] = []
raw_names: set[str] = set()
for pid, serial, ext, mat_name, spec, loc, overall, pstatus, task_name, assignee, tstatus, created, received in rows:
for pid, serial, ext, mat_name, spec, loc, overall, pstatus, task_name, assignee, tstatus, created, completed, received in rows:
if pid in seen:
continue
seen.add(pid)
@ -822,22 +836,32 @@ async def get_wip_matrix_detail(
if spec_model and (spec or "") != spec_model:
continue
# 时间筛选(设备最新主任务创建时间)
if created is not None and created.tzinfo is None:
created = created.replace(tzinfo=dt_timezone.utc)
if since and created is not None and created < since:
continue
if until and created is not None and created > until:
continue
# ── 真实产出时间锚点:完工/变动时间优先创建时间仅兜底schema 无 updated_at──
anchor = completed or created
# 当前工序归类(与 get_wip_matrix 一致)
if loc == "virtual_warehouse":
if overall in ("已入库", "在库") or str(pstatus).upper() == "ARCHIVED":
key = "已入库"
elif overall == "已出库" or str(pstatus).upper() == "OUTBOUND":
key = "出库"
else:
key = "已完成"
# ── 动静分离:仅终结(收口/在库/出库)设备按 since~until 过滤;在制设备无条件全量 ──
is_terminal = (
loc == "virtual_warehouse"
or overall in ("待仓库收货", "已入库", "在库", "已出库")
or str(pstatus).upper() in ("ARCHIVED", "OUTBOUND")
or task_name in ("扫码入库", "扫码出库")
)
if is_terminal:
if anchor is not None and anchor.tzinfo is None:
anchor = anchor.replace(tzinfo=dt_timezone.utc)
if since and anchor is not None and anchor < since:
continue
if until and anchor is not None and anchor > until:
continue
# else: 在制设备无视时间参数,保证与人员看板“实时在制”总数一致
# ── 终结三列判定(与 get_wip_matrix 一字不差同步,多维防御)──
if overall in ("已入库", "在库") or str(pstatus).upper() == "ARCHIVED" or task_name == "扫码入库":
key = "已入库"
elif overall == "已出库" or str(pstatus).upper() == "OUTBOUND" or task_name == "扫码出库":
key = "已出库"
elif overall == "待仓库收货" or loc == "virtual_warehouse":
key = "已完成"
else:
key = task_name or ""