fix: 仓储任务节点修复(assignee=None + WAREHOUSE 类型) + 前端防御性渲染
- webhook 生成任务 assignee_id 置 None,不再填中文,避免前端解析报错跳过渲染
- task_type=WAREHOUSE,并同步 isMain 判断(后端+双端前端)使其画在中央主干道
- 前端对扫码入库/出库任务:不请求用户数据,直接显示 📦 + 'MOM 仓储系统'
This commit is contained in:
@ -176,9 +176,10 @@ async def _append_warehouse_task(
|
|||||||
product_id=product.id,
|
product_id=product.id,
|
||||||
parent_task_id=last_main_task.id if last_main_task else None,
|
parent_task_id=last_main_task.id if last_main_task else None,
|
||||||
task_name=task_name,
|
task_name=task_name,
|
||||||
assignee_id="MOM 仓储系统",
|
# ★ assignee_id 显式置 None:不能填非 UUID 字符串,否则前端解析头像/用户信息报错导致节点跳过渲染
|
||||||
|
assignee_id=None,
|
||||||
status="COMPLETED",
|
status="COMPLETED",
|
||||||
task_type="TRANSFER", # 现有主线枚举 → is_main=True,画在中央主干道
|
task_type="WAREHOUSE", # 仓储任务类型(is_main 判断已兼容 WAREHOUSE → 画在中央主干道)
|
||||||
completed_at=_dt.now(),
|
completed_at=_dt.now(),
|
||||||
remark=record_remark,
|
remark=record_remark,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -140,7 +140,7 @@ async def get_product_by_serial(db: AsyncSession, serial_number: str) -> Product
|
|||||||
all_mains: list = []
|
all_mains: list = []
|
||||||
def _collect_main(tasks):
|
def _collect_main(tasks):
|
||||||
for t in tasks:
|
for t in tasks:
|
||||||
if not t.parent_task_id or t.task_type in ("TRANSFER", "RECOVERY"):
|
if not t.parent_task_id or t.task_type in ("TRANSFER", "RECOVERY", "WAREHOUSE"):
|
||||||
all_mains.append(t)
|
all_mains.append(t)
|
||||||
if t.child_tasks:
|
if t.child_tasks:
|
||||||
_collect_main(t.child_tasks)
|
_collect_main(t.child_tasks)
|
||||||
@ -465,7 +465,7 @@ async def update_overall_status(
|
|||||||
Task.status.in_(["WIP", "PENDING"]),
|
Task.status.in_(["WIP", "PENDING"]),
|
||||||
or_(
|
or_(
|
||||||
Task.parent_task_id.is_(None),
|
Task.parent_task_id.is_(None),
|
||||||
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
|
Task.task_type.in_(["TRANSFER", "RECOVERY", "WAREHOUSE"]),
|
||||||
),
|
),
|
||||||
).order_by(Task.created_at.desc()).limit(1)
|
).order_by(Task.created_at.desc()).limit(1)
|
||||||
)
|
)
|
||||||
@ -657,7 +657,7 @@ async def get_all_products(
|
|||||||
Task.product_id.in_(product_ids),
|
Task.product_id.in_(product_ids),
|
||||||
or_(
|
or_(
|
||||||
Task.parent_task_id.is_(None),
|
Task.parent_task_id.is_(None),
|
||||||
Task.task_type.in_(["TRANSFER", "RECOVERY"]),
|
Task.task_type.in_(["TRANSFER", "RECOVERY", "WAREHOUSE"]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
prio_expr = sa_case(
|
prio_expr = sa_case(
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { memo, useMemo, useState, useEffect } from "react";
|
import { memo, useMemo, useState, useEffect } from "react";
|
||||||
import { Image } from "antd";
|
import { Image } from "antd";
|
||||||
import { FileText, User, X } from "lucide-react";
|
import { FileText, Package, User, X } from "lucide-react";
|
||||||
import type { TaskResponse } from "../../types/api";
|
import type { TaskResponse } from "../../types/api";
|
||||||
import { TASK_STATUS } from "../../types/api";
|
import { TASK_STATUS } from "../../types/api";
|
||||||
import { getStatusConfig } from "../../constants/task";
|
import { getStatusConfig } from "../../constants/task";
|
||||||
@ -13,7 +13,7 @@ import type { ModalTarget } from "./TaskTreeViewer";
|
|||||||
// 工具
|
// 工具
|
||||||
// ============================================================
|
// ============================================================
|
||||||
function fmtTime(d: string | null) { if (!d) return ""; const dt = new Date(d); const pad = (n: number) => String(n).padStart(2, "0"); return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`; }
|
function fmtTime(d: string | null) { if (!d) return ""; const dt = new Date(d); const pad = (n: number) => String(n).padStart(2, "0"); return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`; }
|
||||||
function isMain(t: TaskResponse) { return !t.parent_task_id || t.task_type === "TRANSFER" || t.task_type === "RECOVERY"; }
|
function isMain(t: TaskResponse) { return !t.parent_task_id || t.task_type === "TRANSFER" || t.task_type === "RECOVERY" || t.task_type === "WAREHOUSE"; }
|
||||||
function active(s: string) { return s === "WIP" || s === "PENDING"; }
|
function active(s: string) { return s === "WIP" || s === "PENDING"; }
|
||||||
/** 微型右箭头 SVG */
|
/** 微型右箭头 SVG */
|
||||||
function ArrowRight({ color = "#9ca3af", big }: { color?: string; big?: boolean }) {
|
function ArrowRight({ color = "#9ca3af", big }: { color?: string; big?: boolean }) {
|
||||||
@ -117,17 +117,29 @@ const SlimCard = memo(function SlimCard({
|
|||||||
<span className={`rounded-full ${sz.status} font-medium ${cfg.bg} ${cfg.text}`}>{cfg.label}</span>
|
<span className={`rounded-full ${sz.status} font-medium ${cfg.bg} ${cfg.text}`}>{cfg.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className={`${sz.title} font-bold text-gray-800 truncate`}>{task.task_name}</p>
|
<p className={`${sz.title} font-bold text-gray-800 truncate`}>{task.task_name}</p>
|
||||||
{/* 操作人(带用户图标)+ 在库转入人 */}
|
{/* 操作人(带用户图标)+ 在库转入人;仓储任务防御性渲染为系统节点 */}
|
||||||
<div className={`${sz.metaRow} flex items-center gap-1`}>
|
<div className={`${sz.metaRow} flex items-center gap-1`}>
|
||||||
{!isWarehouse && (
|
{(() => {
|
||||||
<span className={`${sz.assignee} text-gray-400 truncate inline-flex items-center gap-0.5`}>
|
const isSystemTask = task.task_name?.includes("扫码") || task.task_type === "WAREHOUSE";
|
||||||
<User className="h-2.5 w-2.5 shrink-0" />{assigneeName || task.assignee_id || "—"}
|
if (isSystemTask) {
|
||||||
</span>
|
// ★ 仓储系统任务:不请求用户数据,直接显示系统图标 + 固定名(assignee_id 为 null 不报错)
|
||||||
)}
|
return (
|
||||||
{/* 📥 在库任务:显示实际执行"转入在库"的操作人(任务负责人) */}
|
<span className={`${sz.assignee} text-gray-400 truncate inline-flex items-center gap-0.5`}>
|
||||||
{isWarehouse && task.assignee_id && (
|
<Package className="h-2.5 w-2.5 shrink-0" />MOM 仓储系统
|
||||||
<span className={`${sz.sub} text-emerald-600`}>📥 转入在库: {assigneeName || task.assignee_id}</span>
|
</span>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
|
if (isWarehouse && task.assignee_id) {
|
||||||
|
return (
|
||||||
|
<span className={`${sz.sub} text-emerald-600`}>📥 转入在库: {assigneeName || task.assignee_id}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className={`${sz.assignee} text-gray-400 truncate inline-flex items-center gap-0.5`}>
|
||||||
|
<User className="h-2.5 w-2.5 shrink-0" />{assigneeName || task.assignee_id || "—"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
{/* 单行时间(精确到分钟) */}
|
{/* 单行时间(精确到分钟) */}
|
||||||
<p className={`${sz.time} text-gray-300`}>
|
<p className={`${sz.time} text-gray-300`}>
|
||||||
|
|||||||
@ -8,8 +8,10 @@
|
|||||||
</view>
|
</view>
|
||||||
<text class="fc-name">{{ node.task_name }}</text>
|
<text class="fc-name">{{ node.task_name }}</text>
|
||||||
<view class="fc-meta">
|
<view class="fc-meta">
|
||||||
|
<!-- 仓储系统任务:系统图标 + 固定名(assignee 为 null 不报错) -->
|
||||||
|
<text v-if="isSystemTask" class="fc-assignee">📦 MOM 仓储系统</text>
|
||||||
<!-- 在库任务显示"转入在库",其余显示负责人 -->
|
<!-- 在库任务显示"转入在库",其余显示负责人 -->
|
||||||
<text v-if="isWarehouseTask" class="fc-warehouse-in">📥 转入在库: {{ formatUserName(node.assignee_id) }}</text>
|
<text v-else-if="isWarehouseTask" class="fc-warehouse-in">📥 转入在库: {{ formatUserName(node.assignee_id) }}</text>
|
||||||
<text v-else class="fc-assignee">👤 {{ formatUserName(node.assignee_id) || '未分配' }}</text>
|
<text v-else class="fc-assignee">👤 {{ formatUserName(node.assignee_id) || '未分配' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 时间独占一行 -->
|
<!-- 时间独占一行 -->
|
||||||
@ -35,7 +37,13 @@ export default {
|
|||||||
isMainNode() {
|
isMainNode() {
|
||||||
return !this.node.parent_task_id
|
return !this.node.parent_task_id
|
||||||
|| this.node.task_type === "TRANSFER"
|
|| this.node.task_type === "TRANSFER"
|
||||||
|| this.node.task_type === "RECOVERY";
|
|| this.node.task_type === "RECOVERY"
|
||||||
|
|| this.node.task_type === "WAREHOUSE";
|
||||||
|
},
|
||||||
|
// ★ 仓储系统任务(扫码入库/扫码出库):防御性渲染,不请求用户数据
|
||||||
|
isSystemTask() {
|
||||||
|
const name = String(this.node.task_name || "");
|
||||||
|
return name.includes("扫码") || this.node.task_type === "WAREHOUSE";
|
||||||
},
|
},
|
||||||
isWarehouseTask() {
|
isWarehouseTask() {
|
||||||
const name = String(this.node.task_name || "");
|
const name = String(this.node.task_name || "");
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
isMain(t) {
|
isMain(t) {
|
||||||
return !t.parent_task_id || t.task_type === "TRANSFER" || t.task_type === "RECOVERY";
|
return !t.parent_task_id || t.task_type === "TRANSFER" || t.task_type === "RECOVERY" || t.task_type === "WAREHOUSE";
|
||||||
},
|
},
|
||||||
// 当前主线的直接分支子节点(非主线)
|
// 当前主线的直接分支子节点(非主线)
|
||||||
branchChildren(mainTask) {
|
branchChildren(mainTask) {
|
||||||
|
|||||||
Reference in New Issue
Block a user