diff --git a/frontend/src/constants/task.ts b/frontend/src/constants/task.ts index ce28da1..c778a04 100644 --- a/frontend/src/constants/task.ts +++ b/frontend/src/constants/task.ts @@ -4,7 +4,7 @@ * 供 TaskTreeViewer / TaskListCard / ProductCard 等组件共用, * 避免在多处重复维护相同的映射逻辑。 */ -import { TASK_STATUS } from "../types/api"; +import { TASK_STATUS, type LifecyclePhase } from "../types/api"; export interface StatusStyle { bg: string; @@ -112,6 +112,19 @@ export const STATUS_CONFIG: Record = { ring: "ring-blue-400", label: "维修", }, + // ---- 售后回流阶段专属工序(红系,与生产阶段一眼区分)---- + 发货测试: { + bg: "bg-red-50", + text: "text-red-700", + ring: "ring-red-400", + label: "发货测试", + }, + 售后维修: { + bg: "bg-red-50", + text: "text-red-700", + ring: "ring-red-400", + label: "售后维修", + }, }; const FALLBACK: StatusStyle = { @@ -137,3 +150,100 @@ export function statusColor(status: string): string { export function statusLabel(status: string): string { return getStatusConfig(status).label; } + +// ============================================================ +// 🔧 生命周期阶段(lifecycle_phase)与工序选项隔离 +// +// 生产制造(PRODUCTION)与售后回流(AFTER_SALES)各有一套合法工序名。 +// ⚠️ 本段与后端 app/core/lifecycle.py 是同一份口径,改动请两边同步。 +// +// 售后阶段使用**独立工序名**(发货测试 / 售后维修),不复用生产阶段的 +// 「测试 / 维修」—— 这样看板、下钻、导出无需 JOIN lifecycle_phase 即可区分。 +// ============================================================ + +export const LIFECYCLE_PHASE = { + PRODUCTION: "PRODUCTION", + AFTER_SALES: "AFTER_SALES", +} as const satisfies Record; + +/** 售后专属工序名 — 选中即代表设备进入售后生命周期 */ +export const STEP_SHIP_TEST = "发货测试"; +export const STEP_AFTER_SALES_REPAIR = "售后维修"; + +const AFTER_SALES_ONLY_STEPS = [STEP_SHIP_TEST, STEP_AFTER_SALES_REPAIR]; + +// ── 生产制造阶段合法工序(原有词表,一字未改)── +export const PRODUCTION_TASK_OPTIONS = ["备货", "生产", "测试", "维修", "在库"]; +export const PRODUCTION_OVERALL_OPTIONS = [ + "备货", "生产", "测试", "维修", "在库", "已入库", "已出库", +]; + +// ── 售后回流阶段合法工序:只保留「发货测试 / 售后维修 / 入库出库」── +export const AFTER_SALES_TASK_OPTIONS = [STEP_SHIP_TEST, STEP_AFTER_SALES_REPAIR, "在库"]; +export const AFTER_SALES_OVERALL_OPTIONS = [ + STEP_SHIP_TEST, STEP_AFTER_SALES_REPAIR, "在库", "已入库", "已出库", +]; + +/** + * 按生命周期阶段取「可选工序」——选项隔离的 UI 侧实现。 + * + * hasHistory = false(无任何历史任务的首次激活 / 老设备)时返回**并集**, + * 让操作员自行声明这是生产设备还是售后回流设备:一旦选中售后专属工序, + * 后端即把设备判为 AFTER_SALES,之后下拉就只剩售后选项。 + * + * 注意:这只是体验层的第一道防线,真正的拦截在后端 + * task_service._enforce_step_isolation(防伪造传参)。 + */ +export function taskOptionsFor( + phase: string | null | undefined, + hasHistory = true, +): string[] { + if (phase === LIFECYCLE_PHASE.AFTER_SALES) return AFTER_SALES_TASK_OPTIONS; + if (!hasHistory) return [...PRODUCTION_TASK_OPTIONS, ...AFTER_SALES_ONLY_STEPS]; + return PRODUCTION_TASK_OPTIONS; +} + +/** 按生命周期阶段取「可选宏观状态」— 同上,含已入库/已出库等终态 */ +export function overallOptionsFor( + phase: string | null | undefined, + hasHistory = true, +): string[] { + if (phase === LIFECYCLE_PHASE.AFTER_SALES) return AFTER_SALES_OVERALL_OPTIONS; + if (!hasHistory) return [...PRODUCTION_OVERALL_OPTIONS, ...AFTER_SALES_ONLY_STEPS]; + return PRODUCTION_OVERALL_OPTIONS; +} + +/** 列表筛选枚举 — 两阶段并集(用于筛选,不是录入项) */ +export const ALL_OVERALL_OPTIONS = [ + ...PRODUCTION_OVERALL_OPTIONS, ...AFTER_SALES_ONLY_STEPS, +]; + +/** 售后服务相关环节 — 只有落到这些工序才打红色标签 */ +const AFTER_SALES_TAG_STEPS = new Set(AFTER_SALES_ONLY_STEPS); + +export interface LifecycleBadge { + label: string; + className: string; + phase: LifecyclePhase; +} + +/** + * 生命周期标签 — 只在设备确实处于**售后回流环节**时返回: + * 生命周期为 AFTER_SALES 且当前工序是「发货测试」或「售后维修」 + * → 红底白字,文案即工序名。 + * + * 生产制造阶段一律返回 null(不打标),避免和售后设备混淆。 + */ +export function lifecycleBadge( + overallStatus: string | null | undefined, + phase: string | null | undefined, +): LifecycleBadge | null { + if (phase !== LIFECYCLE_PHASE.AFTER_SALES) return null; + const step = (overallStatus || "").trim(); + if (!AFTER_SALES_TAG_STEPS.has(step)) return null; + return { + label: step, + className: "bg-red-600 text-white ring-1 ring-red-600", + phase: LIFECYCLE_PHASE.AFTER_SALES, + }; +} diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index b05ec76..0374467 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -1,4 +1,5 @@ import api from "./api"; +import type { LifecyclePhase } from "../types/api"; export interface DashboardStats { products_total: number; @@ -86,6 +87,8 @@ export interface WipMatrixDetailRow { material_name: string; spec_model: string; task_status: string; + /** 🔧 生命周期阶段:PRODUCTION(生产制造/发货测试) | AFTER_SALES(出库后返厂售后维修) */ + lifecycle_phase?: LifecyclePhase; assignee_id: string; assignee: string; duration_hours: number; diff --git a/frontend/src/types/admin.ts b/frontend/src/types/admin.ts index 223a269..9767d56 100644 --- a/frontend/src/types/admin.ts +++ b/frontend/src/types/admin.ts @@ -1,4 +1,5 @@ /** 管理端 API 响应类型 */ +import type { LifecyclePhase } from "./api"; export interface ProductResponse { id: string; @@ -17,6 +18,8 @@ export interface ProductResponse { macro_status: string | null; overall_status: string | null; status: string; + /** 🔧 生命周期阶段:PRODUCTION(生产制造/发货测试) | AFTER_SALES(出库后返厂售后维修) */ + lifecycle_phase?: LifecyclePhase; created_at: string; latest_record_time: string | null; latest_record_content: string | null; diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index 3af6c34..bc6180b 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -1,5 +1,11 @@ /** 后端 API 响应类型 */ +// ============================================================ +// 生命周期阶段 — 区分「生产阶段的测试(发货测试)」与「出库后返厂(售后维修)」 +// ============================================================ + +export type LifecyclePhase = "PRODUCTION" | "AFTER_SALES"; + // ============================================================ // 任务状态常量 // ============================================================ @@ -97,6 +103,8 @@ export interface ProductScanResponse { current_location_id: string | null; status: string; overall_status: string | null; + /** 🔧 生命周期阶段:PRODUCTION(生产制造/发货测试) | AFTER_SALES(出库后返厂售后维修) */ + lifecycle_phase?: LifecyclePhase; created_at: string; top_level_tasks: TaskSummary[]; /** 完整递归任务树 — 供十字矩阵树状图渲染 */