# Robot Manager — 最终字段设计（v5 82 字段全归位）

> **created**: 2026-05-16
> **status**: Draft（等 review 后转 prisma schema）
> **作者**: chentao + Claude
> **依据**:
> - [standard 16 数据分层与元数据策略](../../../standards/16-data-layering-and-metadata-policy.md)
> - [数据分层方案.md](数据分层方案.md)（表结构总览）
> - `Master_Metadata_v5.xlsx`（172 行 / 82 列）
> - `Master_to_Node_Mapping_v3.xlsx`（字段-节点映射 5 sheet）
> - [汇总-v3-v5-业务方迭代.md](汇总-v3-v5-业务方迭代.md)

---

## 1. 这份文档解决什么

`数据分层方案.md` 给出了三层架构 + 每张表的**字段示例**。本文档把 v5 Master **82 列字段全部精确归位**到目标表的具体列：

- v5 列名 → 目标表 + 目标列名（snake_case）
- 字段类型 + 必填 + 主写节点 + 只读引用节点
- 派生字段（来自其他字段计算）vs 骨架字段
- 枚举字段的 enum 候选值

是 prisma schema 实施的事实源——PR3/PR4 直接对照本文档写 `*.prisma`。

---

## 2. 归位总览（82 字段分布）

| 目标位置 | 字段数 | 来源节点 |
|---|---|---|
| **L1** Customer / Supplier / Partner / Location FK 引用（主表字段不在 v5 82 列里）| 5 | 12, 11, 01, multi |
| **L2 RobotUnit**（不变属性骨架）| 8 | 01, 07 |
| **L2 RobotUnitSnapshot**（物化当前状态）| 5 派生 | multi |
| **L3 PurchaseOrder + Line** | 4 | 01 |
| **L3 SalesOrder + Line + Attachment** | 9 | 12 |
| **L3 DeliveryRequest** | 3 | 14 |
| **L3 DeliveryFulfillment** | 11 | 15, DA |
| **L3 PaymentRecord** | 5 | P6.1, 13 |
| **L3 QualityLabelRecord**（7 行/台）| 7 | 09 |
| **L3 RobotPackageReadiness** | 13 | 11 |
| **L3 InspectionRecord** | 2 | 08 |
| **L3 LogisticsLeg** | 5 | 04 |
| **L3 ComplianceCheck** | 3 | 03, 05, 06 |
| **L3 ServiceTicket** | 2 | ST |
| **RobotLifecycleEvent.payload**（跨节点 multi）| 5 | multi |
| **RobotImportAudit**（v5 GLOBAL 系统字段，导入审计专用）| 3 | GLOBAL |
| **小计** | **85** | （含派生 +3，实质 82）|

> 三状态合并后字段总数 = 82。v5 原 5 个 multi 字段中 Status (Build) / Lifecycle Status / Delivery Status **合为一个** `currentStage`（共 1 列），Location 独立 `currentLocationId`（1 列），Notes 进 LifecycleEvent.notes / payload。

---

## 3. 命名规则

| 规则 | 示例 |
|---|---|
| Prisma model 字段 = camelCase | `ffsn`, `purchaseOrderId`, `placeholderSnOrig` |
| Postgres 列 = snake_case（`@map`）| `ffsn`, `purchase_order_id`, `placeholder_sn_orig` |
| FK 列 = `{entity}Id` / `{entity}_id` | `customerId` / `customer_id` |
| 枚举列存 enum 类型，不存 string | `currentStage RobotLifecycleStage` |
| 标签 / 显示名放前端 i18n（`locales/{zh,en}/robot-manager.json`）| 不进 DB |
| 派生字段标 `// computed` 注释 + 物化在 Snapshot | `daysReadyForDelivery` |
| FK 到平台层 Currency / Country 用 `code` 而非 `id`（字典稳定）| `currency String @db.VarChar(3)` |
| 附件链接（Hyperlink / URL 列）走 `Attachment` 多态表 + FK | `signedFormAttachmentId` |

---

## 4. L1 平台公共层（FK 引用）

v5 82 列里通过名字引用平台主数据。主表完整字段在 [`数据分层方案.md` §4](数据分层方案.md#4-l1-平台公共层platform-master)。本文档只列 v5 引用点。

| v5 列名 | 中文 | 引用到 | RobotUnit 上的 FK 列 |
|---|---|---|---|
| `Customer` | 客户 | `platform_master.Customer` | `RobotUnitSnapshot.currentCustomerId` |
| `Partner / Supplier` | 出厂供应商 | `platform_master.Supplier`（type=MANUFACTURER）| `RobotUnit.originalSupplierId` |
| `Specialist` | 入库负责人 | `platform_iam.User` | `RobotUnitSnapshot.currentSpecialistId` |
| `Mentor` | 销售导师 | `platform_master.Partner`（role=MENTOR）| `SalesOrder.mentorId` |
| `Mentee` | 销售学员 | `platform_master.Partner`（role=MENTEE）| `SalesOrder.menteeId` |
| `Location`（multi）| 当前位置 | `platform_master.Location` | `RobotUnitSnapshot.currentLocationId` + `LogisticsLeg.from/toLocationId` + `RobotLifecycleEvent.from/toLocationId` |
| `Tariff Type` | 关税类型 | `platform_master.Dictionary(category=tariff_type)` | `LogisticsLeg.tariffTypeCode` |
| `Import Declaration Type` | 进口申报类型 | `platform_master.Dictionary(category=declaration_type)` | `LogisticsLeg.importDeclarationTypeCode` |

---

## 5. L2 模块主数据

### 5.1 Model

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `code` | String unique | ✓ | — | 型号编码 |
| `name` | String | ✓ | — | 显示名（中英文存 i18n 表，DB 存 key 也可）|
| `brand` | String? | | — | |
| `specSummary` | String? | | — | 简介 |
| `imageUrl` | String? | | — | |
| `retiredAt` | DateTime? | | — | 停产 |
| `enabled` | Boolean | ✓ | — | |

### 5.2 Sku（**品类**，同 SKU 物理属性相同）

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `modelId` | UUID FK | ✓ | — | |
| `code` | String unique | ✓ | — | |
| `name` | String | ✓ | — | |
| `variant` | String? | | **Variant (aux)** | v5 #7，品类细分（同 SKU 所有 unit 共享）|
| `defaultPrice` | Decimal? | | — | 建议零售价（SKU 级；PO/SO 实际成交价独立）|
| `defaultCost` | Decimal? | | — | |
| `currency` | String(3) FK Currency | | — | 默认结算币 |
| `enabled` | Boolean | ✓ | — | |

> **SKU = 品类，不是个体**。Usage Type 是个体属性（每台用途可能不同），归位到 `RobotUnit.usageType`（§5.3），不在 Sku 上。

### 5.3 RobotUnit（**仅不变属性**）

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | — | |
| `organizationId` | UUID | ✓ | — | — | DataScope 标准字段 |
| `ffsn` | String unique | ✓ | **FF SN (key)** #1 | 01 | FF 内部主键 |
| `ffsnDisplay` | String? | | **FF SN** #3 | 01 | 显示用，通常 = `ffsn`；偶尔不同 |
| `placeholderSnOrig` | String? | | **Placeholder SN (orig)** #4 | 01→07 | v5 新增；占位激活后保留原始占位值 |
| `supplierSn` | String? | | **Supplier SN** #2 | 07 | 07 RECEIVED 扫码激活；偶尔 03 出厂时已知 |
| `modelId` | UUID FK | ✓ | **Model (full)** #6 | 01 | |
| `skuId` | UUID FK | ✓ | — | 01 | |
| `usageType` | enum `RobotUsageType` | ✓ | **Usage Type** #16 | 01 | 个体用途（每台可不同），PO 时确定；罕见变更走事件 `usage_type_changed` |
| `purchaseOrderId` | UUID FK? | | — | 01 | 不变追溯 |
| `purchaseOrderLineId` | UUID FK? | | — | 01 | |
| `originalSupplierId` | UUID FK? | | **Partner / Supplier** #20 | 01 | 出厂供应商 |
| `manufactureDate` | Date? | | — | 03 | 出厂日期（可选；不一定 PO 时知道）|
| `retiredAt` | DateTime? | | — | — | 资产终止时点（进入 CLOSED 时填）|
| `disposalType` | enum `RobotDisposalType`? | | — | — | CLOSED 时填：SCRAPPED / SWAPPED_TO_AGIBOT / CANCELLED |
| `disposalNotes` | String? | | — | — | 终止原因 |
| `sapMaterialNo` | String? | | — | — | **SAP anchor**：W4 入库时由 SAP 反写的物料号；未来 ERP 集成 |
| `metadata` | Json | | — | — | 兜底，非定型字段 |
| 标准字段 | — | — | — | — | createdAt/updatedAt/createdById/deletedAt/version |

**索引**：`(organizationId, ffsn)` unique、`(organizationId, modelId)`、`(organizationId, purchaseOrderId)`、`(organizationId, deletedAt)`

### 5.4 RobotUnitSnapshot（物化"当前状态"，1:1 RobotUnit）

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `robotUnitId` | UUID PK FK | ✓ | — | 1:1 |
| `currentStage` | enum `RobotLifecycleStage` | ✓ | **Lifecycle Status** #14 + **Status (Build)** #8 + **Delivery Status** #39 | 当前生命周期阶段（21 个 stage）。**三个状态合一**：v5 原本分三列（Lifecycle / Build / Delivery）实际是同一状态机的不同视角；schema 只存一个 `currentStage`，Build/Delivery 显示从 stage 派生。前缀含部门归属：SUPPLY_/LOGISTICS_/WAREHOUSE_/SALES_/DELIVERY_/AFTERSALES_ |
| `isHeld` | Boolean | ✓ | (从 Status Build "Hold") | 默认 false；当前 stage 被搁置时 true（如 "WAREHOUSE_PDI" + isHeld=true 表示 PDI 卡在问题待解决）|
| `holdReason` | String? | | (从 Notes/Comments) | isHeld=true 时填具体原因 |
| `currentLocationId` | UUID FK Location | | **Location** #15 | 当前位置 |
| `currentCustomerId` | UUID FK Customer | | **Customer** #5 | 进入 SALES_RESERVED 后 |
| `currentSalesOrderId` | UUID FK | | — | 进入 SALES_RESERVED 后 |
| `currentDeliveryRequestId` | UUID FK | | — | 进入 DELIVERY_READY 后 |
| `currentSpecialistId` | UUID FK User | | **Specialist** #11 | 进入 WAREHOUSE_BRANDED_READY 时分配 |
| `physicalProductStatus` | enum `RobotPhysicalStatus` | | **Physical Product Status** #34 | Stored/Delivered/... |
| `daysReadyForDelivery` | Int? | | **Days Ready for Delivery** #35 | computed: now() - readinessRecord.readyAt |
| `warrantyStatus` | enum `RobotWarrantyStatus` | | **Warranty Status** #54 | 进入 DELIVERY_DELIVERED 时激活 |
| `lastEventId` | UUID FK | | — | 最后一条 LifecycleEvent |
| `lastEventAt` | DateTime | | — | |
| `derivedAt` | DateTime | | — | 物化时间 |
| `version` | Int @default(0) | ✓ | — | **乐观锁**：并发 changeStage 时 service 层 `WHERE version=:v` 阻止竞态 |

**索引**：`(currentStage)`、`(currentLocationId)`、`(currentCustomerId)`、`(currentSalesOrderId)`

> **性质豁免**：Snapshot 是 derived data（事件流的物化投影），按 [standard 04 §存量豁免] 规则豁免完整标准字段（无 `createdAt/createdById/organizationId`）。`derivedAt` 充当时间戳。
>
> 同事务刷新策略见 [standard 16 §6](../../../standards/16-data-layering-and-metadata-policy.md#6-robotunitsnapshot-模式l2-状态物化的标准做法)；事件 → Snapshot 字段映射见 §5.5。

### 5.5 事件 → Snapshot 字段投影表（service 层实现该 map）

每种 `RobotLifecycleEventType` 触发时，service 层必须刷新对应 Snapshot 字段：

| eventType | 刷新 Snapshot 字段 |
|---|---|
| `stage_changed` | `currentStage`, `lastEventId`, `lastEventAt`, `derivedAt`, `version++` |
| `held` / `unheld` | `isHeld`, `holdReason`, `lastEventId`, `lastEventAt`, `version++` |
| `location_moved` | `currentLocationId`, `lastEventId`, `lastEventAt`, `version++` |
| `sn_activated` | （主表 `RobotUnit.supplierSn`，不刷 snapshot；event 即审计）|
| `usage_type_changed` | （主表 `RobotUnit.usageType`，不刷 snapshot；event 即审计）|
| `label_applied` | （不刷 snapshot，进 QualityLabelRecord）|
| `inspection_logged` | （不刷 snapshot，进 InspectionRecord）|
| `readiness_completed` | `currentSpecialistId`, `physicalProductStatus`, `daysReadyForDelivery`（重新派生）, `version++` |
| `payment_collected` | （不刷 snapshot，进 PaymentRecord）|
| `delivery_signed` | `currentCustomerId`, `currentSalesOrderId`, `currentDeliveryRequestId`, `warrantyStatus=ACTIVE`, `physicalProductStatus=DELIVERED`, `version++` |
| `service_opened` / `service_closed` | （不刷 snapshot，附带 stage_changed 时刷 currentStage）|
| `imported_from_v5` | snapshot 完整重建（初始物化）|
| `note_added` | （仅 event.notes 写入，不刷 snapshot）|

**实现位置**：`backend/src/modules/robot-manager/services/snapshot-projection.service.ts`（同事务调用 `prisma.$transaction([event.create, snapshot.upsert with WHERE version=:v])`）。

---

## 6. L3 业务表

### 6.1 PurchaseOrder + PurchaseOrderLine

**`PurchaseOrder`**（采购单头）

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | 01 | |
| `poNo` | String unique | ✓ | **PO (merged)** #18 | 01 | 业务单号 |
| `sapPoNo` | String? unique | | — | — | **SAP anchor**：生产 SAP 反写的 PO 单号，未来 ERP 集成 |
| `supplierId` | UUID FK Supplier | ✓ | **Partner / Supplier** #20 | 01 | 平台层 |
| `currency` | String(3) FK | ✓ | — | 01 | |
| `totalAmount` | Decimal | ✓ | — | 01 | 派生：sum(lines) |
| `status` | enum `PurchaseOrderStatus` | ✓ | — | 01 | DRAFT/ORDERED/PARTIAL/RECEIVED/CLOSED |
| `orderedAt` | DateTime | ✓ | **Purchase Date** #19 | 01 | |
| `expectedAt` | DateTime? | | — | 01 | |
| `closedAt` | DateTime? | | — | — | |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

**`PurchaseOrderLine`**

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `purchaseOrderId` | UUID FK | ✓ | — | |
| `lineNo` | Int | ✓ | — | |
| `skuId` | UUID FK | ✓ | — | |
| `quantity` | Int | ✓ | — | |
| `unitPrice` | Decimal | ✓ | **Purchase Price** #21 | 单台采购价 |
| `totalPrice` | Decimal | ✓ | — | computed: qty × price |
| `currency` | String(3) FK | ✓ | — | |
| `defaultUsageType` | enum `RobotUsageType`? | | — | 本批次默认用途，创建 RobotUnit 占位时继承到 `RobotUnit.usageType` |
| `placeholderPattern` | String? | | — | `{poNo}-LINE-{NNN}` 用于 PO 占位 |
| `expectedAt` | DateTime? | | — | |

### 6.2 SalesOrder + SalesOrderLine + 合约附件

**`SalesOrder`**

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | 12 | |
| `soNo` | String unique | ✓ | **Sales Order ID** #24 | 12 | D365 SO |
| `d365SoId` | String? unique | | — | — | **D365 anchor**：D365 SO 主键，未来集成同步 |
| `customerId` | UUID FK Customer | ✓ | **Customer** #5 | 12 | |
| `salesPersonId` | UUID FK User | | — | 12 | 内部销售 |
| `mentorId` | UUID FK Partner | | **Mentor** #28 | 12 | 外部导师 |
| `menteeId` | UUID FK Partner | | **Mentee** #29 | 12 | 外部学员 |
| `currency` | String(3) FK | ✓ | — | 12 | |
| `totalAmount` | Decimal | ✓ | — | 12 | 派生 |
| `contractStatus` | enum `SalesContractStatus` | | **Contract Status** #30 | 12 | DRAFT/SIGNED/VOID |
| `status` | enum `SalesOrderStatus` | ✓ | — | 12 | RESERVED/PENDING/APPROVED/FULFILLING/COMPLETED/CANCELLED |
| `signedAt` | DateTime? | | — | 12 | |
| `closedAt` | DateTime? | | — | — | |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

**`SalesOrderLine`**

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `salesOrderId` | UUID FK | ✓ | — | |
| `lineNo` | Int | ✓ | — | |
| `robotUnitId` | UUID FK? | | — | 12 RESERVED 时确定 |
| `lineType` | enum `SalesLineType` | ✓ | — | **HARDWARE** / **SOFTWARE** / **COCREATION** |
| `unitPrice` | Decimal | ✓ | **Sales Price - Hardware** #26 / **Sales Price - Software** #27 | 按 `lineType` 区分 |
| `discount` | Decimal? | | — | |
| `netAmount` | Decimal | ✓ | — | computed |
| `currency` | String(3) FK | ✓ | — | |
| `expectedDeliveryDate` | Date? | | — | |

**SalesAgreement 走 Attachment 多态**（合约 4 个链接）

| v5 列名 | 中文 | 落地 |
|---|---|---|
| **Robotics Sales Agreement Link** #57 | 销售协议链接 | `Attachment(ownerType='sales_order', category='SALES_AGREEMENT')` |
| **Superone Cocreation Contract** #60 | 共创合约 Superone | `Attachment(ownerType='sales_order', category='COCREATION_SUPERONE')` |
| **Robotic Cocreation Contract** #61 | 机器人共创合约 | `Attachment(ownerType='sales_order', category='COCREATION_ROBOTIC')` |

> Deposit Transfer 两个链接归 PaymentRecord（§6.5）。

### 6.3 DeliveryRequest

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | 14 | |
| `deliveryNo` | String unique | ✓ | **Delivery Number** #25 | 14 | |
| `salesOrderId` | UUID FK | ✓ | — | 14 | |
| `customerId` | UUID FK | ✓ | — | 14 | |
| `requestType` | enum `DeliveryRequestType` | ✓ | **Delivery Request Type** #36 | 14 | **WILL_CALL** / **THREE_PL** / **WHITE_GLOVE** |
| `expectedDate` | Date | ✓ | **Expected Delivery Date** #37 | 14 | |
| `status` | enum `DeliveryRequestStatus` | ✓ | — | 14 | REQUESTED/APPROVED/FULFILLING/COMPLETED |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

### 6.4 DeliveryFulfillment

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | 15 | |
| `deliveryRequestId` | UUID FK | ✓ | — | 15 | |
| `robotUnitId` | UUID FK | ✓ | — | 15 | |
| `sapPgiDocNo` | String? | | — | — | **SAP anchor**：PGI 过账凭证号，未来集成同步 |
| `deliveredAt` | DateTime | ✓ | **Delivery Date** #38 | 15 | PGI 时间点 |
| `signedAt` | DateTime? | | — | DA | 签字时间 |
| `signedFormStatus` | enum `FormStatus` | | **Delivery Signed Form** #40 | DA | DRAFT/SIGNED |
| `acceptanceFormStatus` | enum `FormStatus` | | **Delivery Acceptance Form Status (Sales)** #42 | DA | |
| `acceptanceFormPreDel` | enum `FormStatus` | | **Delivery Acceptance Form (Pre-Del)** #41 | DA | |
| `acceptanceFormAttachmentId` | UUID FK Attachment | | **Deliver Acception Form Hyperlink** #43 | DA | 链接走 Attachment |
| `notePreDel` | String? | | **Note (Pre-Del)** #45 | DA | |
| `additionalShippingNotes` | String? | | **Add'l Notes (Shipping)** #44 | 04 | 物流补充备注 |
| `cost` | Decimal? | | **Cost** #50 | 15 | 派生：PGI 时确认 |
| `grossMargin` | Decimal? | | **Gross Margin** #51 | 15 | 派生 |
| `revenueRecognition` | enum `RevenueRecognitionStatus` | | **Revenue Recognition** #52 | 15 | PENDING/RECOGNIZED |
| `invoiceStatus` | enum `InvoiceStatus` | | **Invoice Status** #53 | 15 | PENDING/ISSUED/PAID |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

### 6.5 PaymentRecord

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | P6.1/13 | |
| `paymentNo` | String unique | ✓ | — | P6.1 | |
| `relatedType` | enum `PaymentRelatedType` | ✓ | — | — | **SALES_ORDER** / **PURCHASE_ORDER** |
| `relatedId` | UUID | ✓ | — | — | |
| `robotUnitId` | UUID FK? | | — | — | 单笔可关联到机器人个体 |
| `direction` | enum `PaymentDirection` | ✓ | — | — | **INBOUND**（收）/ **OUTBOUND**（付）|
| `sapClearingDocNo` | String? | | — | — | **SAP anchor**：清账凭证号，未来集成同步 |
| `paymentMethod` | enum `PaymentMethod` | | **Payment Method** #31 | P6.1 | CASH/WIRE/LEASE/CHECK |
| `paymentStatus` | enum `PaymentStatus` | | **Payment** #32 | P6.1 | PAID / NOT_PAID_YET / PARTIAL |
| `preDelContractStatus` | enum `PreDelContractStatus` | | **Payment & Contract (PreDel)** #33 | 13 | 交付前付款合同状态 |
| `amount` | Decimal | ✓ | — | — | |
| `currency` | String(3) FK | ✓ | — | — | |
| `paidAt` | DateTime? | | — | — | |
| `depositTransferFormAttachmentId` | UUID FK | | **Deposit Transfer Form Link** #58 | P6.1 | Attachment |
| `depositTransferContractAttachmentId` | UUID FK | | **Deposit Transfer Contract Link - Car** #59 | P6.1 | Attachment |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

### 6.6 QualityLabelRecord（7 行/台，09 UNDER_MODIFICATION）

每台机器人 7 行（一种标签一行）。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `robotUnitId` | UUID FK | ✓ | — | |
| `labelTypeCode` | String FK LabelType | ✓ | — | 7 种之一，见下表 |
| `status` | enum `LabelStatus` | ✓ | — | PENDING/APPLIED/VERIFIED/REJECTED |
| `appliedAt` | DateTime? | | — | 09 节点 |
| `verifiedAt` | DateTime? | | — | 11 节点 |
| `appliedById` | UUID FK User | | — | |
| `verifiedById` | UUID FK User | | — | |
| `photoAttachmentId` | UUID FK | | — | 标签照片 |

**LabelType 枚举**（v5 #63-#69 → 入 `Dictionary(category='label_type')`，按 standard 16 §5.1 "无专属字段、被 FK 引用"判据归口）：

| code | v5 列名 | 中文（labelZh） |
|---|---|---|
| `BODY_FCC` | Label, body - FCC | 机身 FCC 标 |
| `BODY_MADE_IN_CN` | Label, body Made in CN | 机身 Made in CN 标 |
| `BODY_SN` | Label, SN Body | 机身 SN 标 |
| `REMOTE_SN` | Label, SN remote | 遥控器 SN 标 |
| `BATTERY_SN` | Label, Battery SN | 电池 SN 标 |
| `INSPECTION_SHEET` | Label, Inspection Sheet | 检验单标 |
| `SHIPPING_BOX` | Label, Shipping box | 出货箱标 |

> 调整理由：原方案 LabelType 独立表只有 code/name/scope 三列且 scope 只有 2 个值（可枚举），跟 PaymentMethod / DeclarationType 一致归 Dictionary 更一致。

**唯一约束**：`(robotUnitId, labelTypeCode)` — 一台一种 label 一行。

### 6.7 RobotPackageReadiness（1 行/台，11 BRANDED_READY）

每台机器人最多 1 行（配件清点 + 入库就绪信息）。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `robotUnitId` | UUID FK unique | ✓ | — | 1:1 |
| `specialistId` | UUID FK User | | **Specialist** #11 | 入库负责人（也物化在 Snapshot）|
| `physicalProductStatus` | enum `RobotPhysicalStatus` | | **Physical Product Status** #34 | 也物化 Snapshot |
| `readyAt` | DateTime? | | — | 11 BRANDED_READY 进入时间，配 `daysReadyForDelivery` 派生 |
| `hasRobot` | Boolean | ✓ | **Robot** #70 | 机器人主机 |
| `hasBattery` | Boolean | ✓ | **Battery** #71 | 电池 |
| `hasRemote` | Boolean | ✓ | **Remote** #72 | 遥控器 |
| `hasUsaPowerCable` | Boolean | ✓ | **USA Power cable** #73 | 美式电源线 |
| `hasCnCableWithAdapter` | Boolean | ✓ | **CN cable w adapter** #74 | 中式带转接线 |
| `hasPowerSupply` | Boolean | ✓ | **Power supply** #75 | 电源 |
| `hasChargingDock` | Boolean | ✓ | **Charging dock** #76 | 充电底座 |
| `hasFootPads` | Boolean | ✓ | **foot pads** #77 | 脚垫 |
| `hasToolsKit` | Boolean | ✓ | **tools/ spare kit** #78 | 工具/备件包 |
| `hasPaperwork` | Boolean | ✓ | **paperwork** #79 | 文件资料 |
| `completedAt` | DateTime? | | — | 10/10 配齐时间 |

> 是否拆 `RobotAccessoryItem` 子表（多行 × 10 种配件）vs 单表 10 列？10 个固定配件品类，**用单表 10 列**更直接（无需关联查询、配件品类不会动态加）。如果将来配件品类要扩到 50+，再拆子表。

### 6.8 InspectionRecord（08 AT_W1_PDI）

每台机器人 0-N 行（多次 PDI 检查）。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `robotUnitId` | UUID FK | ✓ | — | |
| `inspectionNo` | Int | ✓ | — | 第 N 次检查 |
| `inspectedAt` | DateTime | ✓ | — | |
| `inspectorId` | UUID FK User | | — | |
| `issue` | String? | | **Issue** #9 | 自由文本描述 |
| `issueTag` | String? FK Dictionary | | **Issue Tag** #17 | 字典 |
| `resolvedAt` | DateTime? | | — | |
| `resolvedById` | UUID FK User | | — | |

### 6.9 LogisticsLeg（04 IN_TRANSIT）

每台机器人 1-N 行（出厂 → FTZ → HQ → ... 多段物流）。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `robotUnitId` | UUID FK | ✓ | — | |
| `legNo` | Int | ✓ | — | 第 N 段 |
| `fromLocationId` | UUID FK Location | | — | |
| `toLocationId` | UUID FK Location | | — | |
| `departedAt` | DateTime? | | — | |
| `arrivedAt` | DateTime? | | **Arrival Date** #22 | |
| `logisticsStatus` | enum `LogisticsStatus` | ✓ | **Logistics Status** #23 | SHIPPED/IN_TRANSIT/ARRIVED/DELAYED/CUSTOMS_HOLD |
| `importDeclarationTypeCode` | String? FK Dictionary | | **Import Declaration Type** #47 | |
| `tariffTypeCode` | String? FK Dictionary | | **Tariff Type** #48 | |
| `shippingReceiptAttachmentId` | UUID FK | | **Shipping Receipts** #62 | Attachment |
| `additionalNotes` | String? | | — | （Add'l Notes Shipping 实际归 DeliveryFulfillment，见 §6.4）|

> v5 #44 **Add'l Notes (Shipping)** 主写节点是 04 但跨用到 15 节点（DeliveryFulfillment），最终归位到 DeliveryFulfillment.additionalShippingNotes。

### 6.10 ComplianceCheck（03 READY_TO_SHIP / 05 BONDED / 06 CUSTOMS_CLEARED）

每台机器人 1 行（合规阶段汇总）。

| 列名 | 类型 | 必填 | v5 | 主写节点 | 说明 |
|---|---|---|---|---|---|
| `id` | UUID | ✓ | — | — | |
| `robotUnitId` | UUID FK unique | ✓ | — | — | 1:1 |
| `dateReady` | Date? | | **Date ready** #13 | 03 | 工厂可发运日期 |
| `stickerStatus` | enum `StickerStatus` | | **sticker** #12 | 03 | PARTIAL/COMPLETE（部分 03 / 剩余 09）|
| `fccStatus` | enum `FccStatus` | | **FCC Status** #46 | 06 | PENDING/CLEARED/REJECTED |
| `complianceNotes` | String? | | **Compliance Notes** #49 | 05 | 保税阶段备注 |
| `lastUpdatedAt` | DateTime | ✓ | — | — | |

### 6.11 RentalAgreement + RentalPaymentSchedule（租赁子模型）

**`RentalAgreement`**（每台租赁的机器人 1 行）

| 列名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | UUID | ✓ | |
| `robotUnitId` | UUID FK | ✓ | |
| `customerId` | UUID FK Customer | ✓ | 承租客户 |
| `startAt` | DateTime | ✓ | 租期开始 |
| `endAt` | DateTime | ✓ | 租期结束 |
| `periodMonths` | Int | ✓ | 租期月数（冗余便利） |
| `monthlyRate` | Decimal | ✓ | 月租金 |
| `currency` | String(3) FK | ✓ | |
| `status` | enum `RentalAgreementStatus` | ✓ | ACTIVE / EXPIRED / TERMINATED |
| `contractAttachmentId` | UUID FK Attachment | | 合同附件 |
| `organizationId` + 标准字段 | — | ✓ | |

**`RentalPaymentSchedule`**（租金分期计划，每期 1 行）

| 列名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | UUID | ✓ | |
| `rentalAgreementId` | UUID FK | ✓ | |
| `dueDate` | Date | ✓ | 应收日期 |
| `amount` | Decimal | ✓ | 本期金额 |
| `currency` | String(3) FK | ✓ | |
| `paidPaymentRecordId` | UUID FK? | | 已收款 PaymentRecord（NULL = 未收） |
| `paidAt` | DateTime? | | |

**为什么必须建表**：业务报表 R5/R6（按租赁切片应收 / 实际收款 / 逾期）必须依赖结构化 schema。仅靠 `PaymentRecord(paymentMethod=LEASE)` 无法回答"这台租到什么时候/本月应收多少/谁负责催还"。

新增 enum：

```prisma
enum RentalAgreementStatus { ACTIVE EXPIRED TERMINATED }
```

（添加到 §9 enum 列表中）
```

### 6.12 ServiceTicket（ST Support_Ticket）

每台机器人 0-N 行。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `ticketNo` | String unique | ✓ | — | |
| `robotUnitId` | UUID FK | ✓ | — | |
| `customerId` | UUID FK | | — | |
| `issueType` | String FK Dictionary | ✓ | — | |
| `severity` | enum `TicketSeverity` | ✓ | — | P0/P1/P2/P3 |
| `status` | enum `ServiceTicketStatus` | ✓ | — | NEW/IN_PROGRESS/AWAITING_CUSTOMER/RESOLVED/CLOSED |
| `serviceRecord` | String? | | **Service Records** #55 | 处理记录 |
| `customerFeedback` | String? | | **Customer Feedback** #56 | |
| `openedAt` | DateTime | ✓ | — | |
| `openedById` | UUID FK User | ✓ | — | |
| `closedAt` | DateTime? | | — | |
| `resolvedById` | UUID FK User | | — | |
| `organizationId` + 标准字段 | — | ✓ | — | — | |

**子表 `ServiceTicketActivity`**（工单跟进记录）：

| 列 | 类型 | 说明 |
|---|---|---|
| `id` | UUID | |
| `ticketId` | UUID FK | |
| `activityType` | enum | COMMENT / STATUS_CHANGE / HANDOVER / RESOLVED |
| `payload` | Json | 详情 |
| `actorId` | UUID FK User | |
| `occurredAt` | DateTime | |

---

## 7. RobotLifecycleEvent（事件源，跨节点 multi 字段 + 通用事件流）

> **标准字段约束**：LifecycleEvent 是业务表（不是日志），按 [standard 04 §标准字段](../../../standards/04-database-architecture.md) 必须含 `organizationId` / `createdById` / `createdAt`（`recordedAt` 改名 `createdAt`）/ `deletedAt`。`occurredAt` 是业务时间，独立保留。

跨节点字段（multi 主写节点）走事件流，不挂业务表：

| v5 列名 | # | 处理方式 |
|---|---|---|
| **Lifecycle Status** + **Status (Build)** + **Delivery Status** | 14 + 8 + 39 | **三状态合一**：LifecycleEvent.eventType=`stage_changed` + Snapshot.currentStage；前端如需 Build / Delivery 视角，从 currentStage 派生显示 |
| **Location** | 15 | LifecycleEvent.eventType=`location_moved` + Snapshot.currentLocationId |
| **Notes / Comments** | 10 | LifecycleEvent.notes（任何事件可附带备注，不另立表）|

**RobotLifecycleEvent 表**：

| 列名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | UUID | ✓ | |
| `robotUnitId` | UUID FK | ✓ | |
| `eventType` | enum `RobotLifecycleEventType` | ✓ | 见下 |
| `fromStage` / `toStage` | enum `RobotLifecycleStage`? | | 仅 stage_changed |
| `fromLocationId` / `toLocationId` | UUID FK? | | 仅 location_moved |
| `actorUserId` | UUID FK User? | | 操作人 |
| `partnerId` | UUID FK Partner? | | 交接到 partner |
| `customerId` | UUID FK Customer? | | 交付给客户 |
| `relatedType` | enum `RobotEventRelatedType`? | | PO/SO/Delivery/Service/Inspection/Compliance/Readiness/Label/Logistics/Payment |
| `relatedId` | UUID? | | 关联业务表行 |
| `payload` | Json | ✓ | 事件专属负载 + Notes/Comments |
| `notes` | String? | | 自由备注（v5 Notes/Comments）|
| `occurredAt` | DateTime | ✓ | 业务时间（≠ createdAt 录入时间）|
| `organizationId` | UUID | ✓ | 标准字段，继承自 RobotUnit |
| `createdAt` | DateTime | ✓ | 录入时间（替代 recordedAt 命名）|
| `createdById` | UUID FK User | ✓ | 标准字段（= actorUserId 或 system）|
| `deletedAt` | DateTime? | | 软删除（异常事件可撤销）|

**`RobotLifecycleEventType` enum**：
- `stage_changed` — 21 stage 之间切换（含 SUPPLY/LOGISTICS/WAREHOUSE/SALES/DELIVERY/AFTERSALES 前缀切换 = 跨部门移交）
- `held` / `unheld` — 设置/取消 isHeld 标记（如 PDI 卡住）
- `location_moved`
- `sn_activated` — 占位 SN → 正式 SN
- `label_applied` — 贴标
- `inspection_logged`
- `readiness_completed`
- `payment_collected`
- `delivery_signed`
- `service_opened` / `service_closed`
- `imported_from_v5` — 历史导入审计（决策 10）
- `note_added` — 仅添加备注（multi #10 Notes/Comments）

**索引**：`(robotUnitId, occurredAt DESC)`、`(eventType, occurredAt)`、`(relatedType, relatedId)`

---

## 8. RobotImportAudit（v5 GLOBAL 系统字段专用）

> **标准字段约束**：ImportAudit 业务表必须含 `organizationId` / `createdById` / `createdAt` / `updatedAt`（按 standard 04）。

v5 Master 三个 GLOBAL 字段（Sources / Conflict Count / Record Status）是**数据治理元信息**，反映"这条主数据从 4 个上游 Excel 融合时的来源 + 冲突"。导入后不再变动；进 RobotUnit 是污染。

| 列名 | 类型 | 必填 | v5 | 说明 |
|---|---|---|---|---|
| `id` | UUID | ✓ | — | |
| `robotUnitId` | UUID FK unique | ✓ | — | 1:1（仅 v5 导入的机器人有）|
| `sources` | String[] | ✓ | **Sources** #80 | 上游来源列表（如 `['Robot_Unit_Lifecycle_Tracker', 'Robotics_Inventory_Readiness']`）|
| `conflictCount` | Int | ✓ | **Conflict Count** #81 | 跨上游冲突字段数 |
| `recordStatus` | enum `ImportRecordStatus` | ✓ | **Record Status** #82 | OK / CONFLICT / PHANTOM（v3 幻影丢弃）|
| `conflictDetail` | Json? | | — | 哪几个字段冲突 + 选了哪个上游 |
| `importedAt` | DateTime | ✓ | — | （= `createdAt`）|
| `importBatch` | String? | | — | 导入批次（v5-2026-05）|
| `organizationId` + `createdById` + `updatedAt` + `deletedAt` | — | ✓ | — | 标准字段 |

> 新机器人（PR-A merge 后通过正常 PO 流程进来的）不进此表。本表只服务于审计 v5 历史导入。

---

## 9. 状态枚举一览（替代 L4 StatusDefinition）

按 [standard 16 §4.5](../../../standards/16-data-layering-and-metadata-policy.md#45-默认规则) 默认规则，全部用 Prisma enum：

```prisma
// 30 stage 生命周期 — 含部门前缀，一眼看出归属
// SUPPLY_*    采购+供应商 (01-03)
// LOGISTICS_* 物流+合规  (04-06)
// WAREHOUSE_* 仓储+PDI+改装 (07-11 + 分支 16)
// SALES_*     销售+财务校验 (12-13)
// DELIVERY_*  交付+财务收款 (14-15 + DA + P6.1)
// RENTAL_*    租赁分支 (17)
// AFTERSALES_* 售后 (ST + RI + RR + W6 + 18 + 19 + QA)
// 终态：CLOSED（含 disposal: Scrapped / Swapped-to-AGIBOT / Cancelled）
// 例外态：CANCELLED / RETURNED
// **控制门不立 stage**（FT W3_FUNCTION_TEST / CONVERSION_VALIDATED / DV）→ service 层 guard 函数，见 §9b
enum RobotLifecycleStage {
  // 采购供应链
  SUPPLY_PO_CREATED                // 01 采购下单
  SUPPLY_IN_PRODUCTION             // 02 供应商在制
  SUPPLY_READY_TO_SHIP             // 03 工厂可发运
  // 国际物流
  LOGISTICS_IN_TRANSIT             // 04 物流在途
  LOGISTICS_BONDED                 // 05 保税阶段
  LOGISTICS_CUSTOMS_CLEARED        // 06 清关完成
  // 仓储 / PDI / 改装
  WAREHOUSE_RECEIVED               // 07 W1 收货（扫码激活占位 SN）
  WAREHOUSE_AT_W1_PDI              // 08 W1 入库前检验
  WAREHOUSE_MODIFICATION           // 09 改装/贴标
  WAREHOUSE_AT_W2                  // 10 W2 入库暂存
  WAREHOUSE_AT_W2_RLE              // 16 W2 RLE 外采库（分支：跳改装）
  WAREHOUSE_BRANDED_READY          // 11 配件齐套、可售
  // 销售
  SALES_RESERVED                   // 12 预留客户
  SALES_PAYMENT_VALIDATED          // 13 销售订单付款校验完成
  // 交付
  DELIVERY_APPROVAL                // DA 交付前审批
  DELIVERY_PAYMENT_COLLECTED       // P6.1 交付前款项收齐
  DELIVERY_READY                   // 14 待交付
  DELIVERY_DELIVERED               // 15 已交付
  // 租赁分支（DELIVERED 后转）
  RENTAL_ACTIVE                    // 17 租赁中（租期满转 AFTERSALES_AT_W6）
  // 售后
  AFTERSALES_TICKET                // ST 售后工单中
  AFTERSALES_RETURN_INITIATED      // RI RMA 发起
  AFTERSALES_RETURN_RECEIVED       // RR FFHQ 签收退货
  AFTERSALES_AT_W6                 // W6 售后仓库归集
  AFTERSALES_UNDER_REPAIR          // 18 维修中（Warranty / OOW）
  AFTERSALES_QUOTE_APPROVAL        // QA OOW 报价 + 客户审批
  AFTERSALES_REPAIRED              // 19 维修完成（回 DELIVERY_DELIVERED 或 WAREHOUSE_BRANDED_READY）
  // 终态
  CLOSED                           // 21 资产注销（disposal: Scrapped / Swapped-to-AGIBOT / Cancelled）
  // 例外态
  CANCELLED                        // PO/订单中途取消（区别于 CLOSED 的"已退役"）
  RETURNED                         // 退回供应商（D2 退货后到供应商）
}

// 按 v5 业务方实际取值定义（不用 SAP 通用术语），导入时直接 1:1 映射
enum RobotUsageType {
  SALES           // 商业销售（v5 Sales）
  RND             // 研发用（v5 R&D）
  MARKETING       // 市场推广（v5 Marketing）
  CAPITAL         // 资本性使用（v5 Capital）
  PRODUCT         // 产品内部使用（v5 Product）
  TO_AGIBOT       // 移交至 AGIBOT（v5 To AGIBOT，特殊用途）
  LAUNCH_EVENT    // 发布会用机（v5 Launch Event）
}
enum RobotDisposalType {
  SCRAPPED            // 报废
  SWAPPED_TO_AGIBOT   // 换给 AGIBOT
  CANCELLED           // 取消订单
}
// 删除：RobotBuildStatus / RobotDeliveryStatus
// 理由：v5 #8 Status (Build) 与 #39 Delivery Status 实为同一状态机的视角化重复，
// 合并到 RobotLifecycleStage。前端如需 Build 视角（Ordered/WIP/Ready/Received/Hold）
// 或 Delivery 视角（Pending/InTransit/Delivered/Returned），从 currentStage + isHeld 派生。
enum RobotPhysicalStatus { STORED IN_TRANSIT DELIVERED IN_SERVICE RETIRED }
enum RobotWarrantyStatus { INACTIVE ACTIVE EXPIRED VOIDED }

enum PurchaseOrderStatus { DRAFT ORDERED PARTIAL_RECEIVED RECEIVED CLOSED CANCELLED }
enum SalesOrderStatus { RESERVED PENDING APPROVED FULFILLING COMPLETED CANCELLED }
enum SalesContractStatus { DRAFT SIGNED VOID }
enum SalesLineType { HARDWARE SOFTWARE COCREATION }
enum DeliveryRequestType { WILL_CALL THREE_PL WHITE_GLOVE }
enum DeliveryRequestStatus { REQUESTED APPROVED FULFILLING COMPLETED CANCELLED }
enum FormStatus { DRAFT SIGNED REJECTED }
enum RevenueRecognitionStatus { PENDING RECOGNIZED ADJUSTED }
enum InvoiceStatus { PENDING ISSUED PAID OVERDUE }

enum PaymentRelatedType { SALES_ORDER PURCHASE_ORDER }
enum PaymentDirection { INBOUND OUTBOUND }
// 按 v5 业务方实际取值（8 种），不强行收敛到通用 5 种
enum PaymentMethod {
  DEPOSIT_TRANSFER       // 定金转账（v5 Deposit Transfer，最常用）
  CHECK                  // 支票
  ACH                    // ACH 转账
  LEASE                  // 租赁
  FULL_PAYMENT_CHECK     // 全款支票
  FULL_PAYMENT_WIRE      // 全款电汇
  CO_CREATION_CREDIT     // 共创信用（v5 Co-Creation Credit）
  WIRE_TRANSFER          // 通用电汇（v5 transfer 归口）
}
enum PaymentStatus {
  NOT_PAID_YET           // 未付
  PARTIAL                // 部分付款
  PAID                   // 已付
  CANCELLED              // 取消（v5 Canceled）
  DRAFT                  // 草稿（v5 Draft）
  // v5 "Paid in April" 这种带日期的非规范取值在 PR5 导入时清洗为 PAID + 实际日期入 paidAt
}
enum PreDelContractStatus { PENDING SIGNED COMPLETED }

enum LabelStatus { PENDING APPLIED VERIFIED REJECTED }
enum LogisticsStatus { SHIPPED IN_TRANSIT ARRIVED DELAYED CUSTOMS_HOLD }
enum StickerStatus { NONE PARTIAL COMPLETE }
enum FccStatus { PENDING CLEARED REJECTED }

enum TicketSeverity { P0 P1 P2 P3 }
enum ServiceTicketStatus { NEW IN_PROGRESS AWAITING_CUSTOMER RESOLVED CLOSED }

enum RobotEventRelatedType { PO SO DELIVERY_REQUEST SERVICE_TICKET INSPECTION COMPLIANCE_CHECK READINESS LABEL LOGISTICS_LEG PAYMENT }
enum RobotLifecycleEventType {
  stage_changed held unheld
  location_moved sn_activated usage_type_changed
  label_applied inspection_logged readiness_completed
  payment_collected delivery_signed
  service_opened service_closed
  imported_from_v5 note_added
}

enum ImportRecordStatus { OK CONFLICT PHANTOM }
```

---

## 9b. 控制门 / Guard 函数清单（service 层硬编码，不立表）

按 [standard 16 §4.5](../../../standards/16-data-layering-and-metadata-policy.md#45-默认规则) 默认规则，控制门**不立 stage 也不立 GuardDefinition 表**——业务上是 stage 间的"切换条件"，工程上是 service 层方法。

### 9b.1 状态转换图（service 层常量）

```typescript
const STAGE_TRANSITIONS: Record<RobotLifecycleStage, RobotLifecycleStage[]> = {
  SUPPLY_PO_CREATED: [SUPPLY_IN_PRODUCTION, CANCELLED],
  SUPPLY_IN_PRODUCTION: [SUPPLY_READY_TO_SHIP, CANCELLED],
  SUPPLY_READY_TO_SHIP: [LOGISTICS_IN_TRANSIT, CANCELLED],
  LOGISTICS_IN_TRANSIT: [LOGISTICS_BONDED, LOGISTICS_CUSTOMS_CLEARED],
  LOGISTICS_BONDED: [LOGISTICS_CUSTOMS_CLEARED],
  LOGISTICS_CUSTOMS_CLEARED: [WAREHOUSE_RECEIVED],
  WAREHOUSE_RECEIVED: [WAREHOUSE_AT_W1_PDI],
  // 反向边：PDI 严重问题 → 退回供应商
  WAREHOUSE_AT_W1_PDI: [WAREHOUSE_MODIFICATION, WAREHOUSE_AT_W2_RLE, WAREHOUSE_AT_W2, RETURNED],
  // 反向边：改装失败 → 回 PDI 重检
  WAREHOUSE_MODIFICATION: [WAREHOUSE_AT_W2, WAREHOUSE_BRANDED_READY, WAREHOUSE_AT_W1_PDI],
  WAREHOUSE_AT_W2: [WAREHOUSE_BRANDED_READY],
  WAREHOUSE_AT_W2_RLE: [WAREHOUSE_BRANDED_READY],
  WAREHOUSE_BRANDED_READY: [SALES_RESERVED, CANCELLED],
  // 反向边：客户违约取消订单 → 机器人回库（机器人本身无毛病）
  SALES_RESERVED: [SALES_PAYMENT_VALIDATED, CANCELLED, WAREHOUSE_BRANDED_READY],
  SALES_PAYMENT_VALIDATED: [DELIVERY_APPROVAL, DELIVERY_PAYMENT_COLLECTED],
  DELIVERY_PAYMENT_COLLECTED: [DELIVERY_APPROVAL],
  // 反向边：审批文件不全 → 退回等补件
  DELIVERY_APPROVAL: [DELIVERY_READY, SALES_PAYMENT_VALIDATED],
  DELIVERY_READY: [DELIVERY_DELIVERED],
  // 反向边：D2 退货（交付 7 天内客户不收，机器人未使用）→ RETURNED
  DELIVERY_DELIVERED: [AFTERSALES_TICKET, RENTAL_ACTIVE, RETURNED, CLOSED],
  // 反向边：租赁中报修但不归还 → AFTERSALES_TICKET（修完回租赁）
  RENTAL_ACTIVE: [AFTERSALES_AT_W6, AFTERSALES_TICKET, CLOSED],
  AFTERSALES_TICKET: [AFTERSALES_RETURN_INITIATED, AFTERSALES_UNDER_REPAIR, DELIVERY_DELIVERED, RENTAL_ACTIVE],
  AFTERSALES_RETURN_INITIATED: [AFTERSALES_RETURN_RECEIVED],
  AFTERSALES_RETURN_RECEIVED: [AFTERSALES_AT_W6],
  AFTERSALES_AT_W6: [AFTERSALES_UNDER_REPAIR, AFTERSALES_QUOTE_APPROVAL, RETURNED, CLOSED],
  AFTERSALES_UNDER_REPAIR: [AFTERSALES_REPAIRED, AFTERSALES_QUOTE_APPROVAL],
  // 反向边：客户拒绝报价 → 原样取回（不修了）
  AFTERSALES_QUOTE_APPROVAL: [AFTERSALES_UNDER_REPAIR, RETURNED, CLOSED, DELIVERY_DELIVERED],
  AFTERSALES_REPAIRED: [DELIVERY_DELIVERED, WAREHOUSE_BRANDED_READY],
  // CLOSED 真终态 = 资产退役（disposalType 决定）
  CLOSED: [],
  // 反向边：CANCELLED 重新定义为"业务单据取消"——机器人个体可复用
  // 如 PO 中途取消但机器人已生产完成 → 回库重新销售
  CANCELLED: [SUPPLY_READY_TO_SHIP, WAREHOUSE_BRANDED_READY],
  // RETURNED 真终态 = 已退给供应商/客户不再是 FF 资产
  RETURNED: [],
}
```

**CANCELLED vs RETURNED vs CLOSED 三态语义边界**：
- **CANCELLED** = **业务单据**（PO/SO）取消，机器人个体可复用回库；非资产终态
- **RETURNED** = 机器人退回供应商或客户拒收（D2 7 天内），不再是 FF 资产
- **CLOSED** = 资产退役（含 disposalType: SCRAPPED / SWAPPED_TO_AGIBOT / CANCELLED），FF 不再持有

### 9b.2 Guard 函数清单（13 条）

每个 guard 是一个 service 层方法，返回 `GuardResult { ok: boolean, reasons?: string[] }`：

**A. 流程图揭示的控制门（7 条）**

| Guard | 检查时机 | 检查内容 |
|---|---|---|
| `checkFunctionTest` (FT) | 08 AT_W1_PDI → 09 UNDER_MODIFICATION 前 | 跑改装前功能测试；结果记录到 `InspectionRecord(category=FUNCTION_TEST)` |
| `checkConversionValidated` (CONVERSION_VALIDATED) | 09 UNDER_MODIFICATION → 11 BRANDED_READY 前 | 7 个 `QualityLabelRecord` 全 VERIFIED + 配件清点完成（`RobotPackageReadiness.completedAt` 非空） |
| `checkDeliveryValidation` (DV) | 14 DELIVERY_READY → 15 DELIVERY_DELIVERED 前 | SN/客户匹配 + `DeliveryFulfillment.signedFormStatus=SIGNED` |
| `checkG5Payment` (**G5 硬阻塞**) | 12 SALES_RESERVED → 13 SALES_PAYMENT_VALIDATED 前 | `PaymentRecord.paymentStatus=PAID`（全款）OR 金融审批通过 |
| `checkPGIReady` (PGI) | 14 → 15 前（叠加 DV）| `acceptanceFormStatus=SIGNED` + `paymentStatus=PAID` → 触发 ERP/SAP 收入确认 |
| `checkRMAEligible` | 15 DELIVERY_DELIVERED → AFTERSALES_RETURN_INITIATED 前 | 售后期内 + 客户提交 RMA 申请 |
| `checkQuoteApproved` | 18 UNDER_REPAIR → 19 REPAIRED 前（仅 OOW 路径）| `AFTERSALES_QUOTE_APPROVAL` 已通过 |

**B. 业务实体硬约束 Guard（6 条新增）**

| Guard | 检查时机 | 检查内容 |
|---|---|---|
| `checkPOHasSupplier` | 创建 `PurchaseOrder` | `supplierId` 非空 + `Supplier.deletedAt IS NULL` |
| `checkSONeedsCustomer` | 创建 `SalesOrder` | `customerId` 非空 + Customer 未删除 + `currency = SalesOrderLine.currency` 一致 |
| `checkReserveRequiresBranded` | RobotUnit 进入 SALES_RESERVED 前 | 来源 stage ∈ `{WAREHOUSE_BRANDED_READY, WAREHOUSE_AT_W2_RLE}` 且 SalesOrderLine 的 robotUnitId 已绑定 |
| `checkClosedNeedsDisposal` | 进入 CLOSED 前 | `RobotUnit.disposalType` 非空 + `retiredAt` 非空 |
| `checkRentalNeedsContract` | 进入 RENTAL_ACTIVE 前 | 存在 `PaymentRecord(paymentMethod=LEASE, robotUnitId=...)` |
| `checkD2EligibleWindow` | DELIVERY_DELIVERED → RETURNED 前（D2 退货专用）| `now() - deliveredAt ≤ 7 天` + 客户书面退货意愿 |

**实现位置**：`backend/src/modules/robot-manager/guards/lifecycle-guards.service.ts`
**测试位置**：`testing/backend/integration/robot-manager/lifecycle-guards.test.ts`

Guard 失败的行为：抛业务异常 + 前端显示原因 + 不切换 stage + 不写 event。

**编排原则**：service 层 `default-deny + 显式 allow`——每个 stage 转换前先查 `STAGE_TRANSITIONS`（合法路径白名单），再触发对应 Guard。两层都通过才 `prisma.$transaction([event.create, snapshot.upsert])`。

---

## 10. v5 82 字段完整归位矩阵（速查表）

| # | v5 列名 | 目标表 | 目标列 | 类型 |
|---|---|---|---|---|
| 1 | FF SN (key) | `RobotUnit` | `ffsn` (unique) | String |
| 2 | Supplier SN | `RobotUnit` | `supplierSn` | String? |
| 3 | FF SN | `RobotUnit` | `ffsnDisplay` | String? |
| 4 | Placeholder SN (orig) | `RobotUnit` | `placeholderSnOrig` | String? |
| 5 | Customer | `RobotUnitSnapshot` + `SalesOrder` | `currentCustomerId` / `customerId` | UUID FK |
| 6 | Model (full) | `RobotUnit` → `Model` | `modelId` (FK) | UUID FK |
| 7 | Variant (aux) | `Sku` | `variant` | String? |
| 8 | Status (Build) | **合并到** `currentStage` | — | 从 currentStage + isHeld 派生显示，不独立存 |
| 9 | Issue | `InspectionRecord` | `issue` | String? |
| 10 | Notes / Comments | `RobotLifecycleEvent` | `notes` | String? |
| 11 | Specialist | `RobotPackageReadiness` + `Snapshot` | `specialistId` / `currentSpecialistId` | UUID FK |
| 12 | sticker | `ComplianceCheck` | `stickerStatus` | enum |
| 13 | Date ready | `ComplianceCheck` | `dateReady` | Date? |
| 14 | Lifecycle Status | `RobotLifecycleEvent` + `Snapshot` | `currentStage` (派生) | enum |
| 15 | Location | `RobotLifecycleEvent` + `Snapshot` | `currentLocationId` (派生) | UUID FK |
| 16 | Usage Type | `RobotUnit` | `usageType` | enum（同型号每台可不同）|
| 17 | Issue Tag | `InspectionRecord` | `issueTag` | String? (Dict) |
| 18 | PO (merged) | `PurchaseOrder` | `poNo` (unique) | String |
| 19 | Purchase Date | `PurchaseOrder` | `orderedAt` | DateTime |
| 20 | Partner / Supplier | `RobotUnit` + `PurchaseOrder` | `originalSupplierId` / `supplierId` | UUID FK |
| 21 | Purchase Price | `PurchaseOrderLine` | `unitPrice` | Decimal |
| 22 | Arrival Date | `LogisticsLeg` | `arrivedAt` | DateTime? |
| 23 | Logistics Status | `LogisticsLeg` | `logisticsStatus` | enum |
| 24 | Sales Order ID | `SalesOrder` | `soNo` (unique) | String |
| 25 | Delivery Number | `DeliveryRequest` | `deliveryNo` (unique) | String |
| 26 | Sales Price - Hardware | `SalesOrderLine` (lineType=HARDWARE) | `unitPrice` | Decimal |
| 27 | Sales Price - Software | `SalesOrderLine` (lineType=SOFTWARE) | `unitPrice` | Decimal |
| 28 | Mentor | `SalesOrder` | `mentorId` | UUID FK Partner |
| 29 | Mentee | `SalesOrder` | `menteeId` | UUID FK Partner |
| 30 | Contract Status | `SalesOrder` | `contractStatus` | enum |
| 31 | Payment Method | `PaymentRecord` | `paymentMethod` | enum |
| 32 | Payment | `PaymentRecord` | `paymentStatus` | enum |
| 33 | Payment & Contract (PreDel) | `PaymentRecord` | `preDelContractStatus` | enum |
| 34 | Physical Product Status | `RobotPackageReadiness` + `Snapshot` | `physicalProductStatus` | enum |
| 35 | Days Ready for Delivery | `RobotUnitSnapshot` (computed) | `daysReadyForDelivery` | Int? |
| 36 | Delivery Request Type | `DeliveryRequest` | `requestType` | enum |
| 37 | Expected Delivery Date | `DeliveryRequest` | `expectedDate` | Date |
| 38 | Delivery Date | `DeliveryFulfillment` | `deliveredAt` | DateTime |
| 39 | Delivery Status | **合并到** `currentStage` | — | 从 currentStage 派生显示，不独立存 |
| 40 | Delivery Signed Form | `DeliveryFulfillment` | `signedFormStatus` | enum |
| 41 | Delivery Acceptance Form (Pre-Del) | `DeliveryFulfillment` | `acceptanceFormPreDel` | enum |
| 42 | Delivery Acceptance Form Status (Sales) | `DeliveryFulfillment` | `acceptanceFormStatus` | enum |
| 43 | Deliver Acception Form Hyperlink | `DeliveryFulfillment` → `Attachment` | `acceptanceFormAttachmentId` | UUID FK |
| 44 | Add'l Notes (Shipping) | `DeliveryFulfillment` | `additionalShippingNotes` | String? |
| 45 | Note (Pre-Del) | `DeliveryFulfillment` | `notePreDel` | String? |
| 46 | FCC Status | `ComplianceCheck` | `fccStatus` | enum |
| 47 | Import Declaration Type | `LogisticsLeg` | `importDeclarationTypeCode` | String? (Dict) |
| 48 | Tariff Type | `LogisticsLeg` | `tariffTypeCode` | String? (Dict) |
| 49 | Compliance Notes | `ComplianceCheck` | `complianceNotes` | String? |
| 50 | Cost | `DeliveryFulfillment` | `cost` | Decimal? |
| 51 | Gross Margin | `DeliveryFulfillment` | `grossMargin` | Decimal? |
| 52 | Revenue Recognition | `DeliveryFulfillment` | `revenueRecognition` | enum |
| 53 | Invoice Status | `DeliveryFulfillment` | `invoiceStatus` | enum |
| 54 | Warranty Status | `RobotUnitSnapshot` | `warrantyStatus` | enum |
| 55 | Service Records | `ServiceTicket` | `serviceRecord` | String? |
| 56 | Customer Feedback | `ServiceTicket` | `customerFeedback` | String? |
| 57 | Robotics Sales Agreement Link | `Attachment(ownerType=sales_order, category=SALES_AGREEMENT)` | — | — |
| 58 | Deposit Transfer Form Link | `PaymentRecord` → `Attachment` | `depositTransferFormAttachmentId` | UUID FK |
| 59 | Deposit Transfer Contract Link - Car | `PaymentRecord` → `Attachment` | `depositTransferContractAttachmentId` | UUID FK |
| 60 | Superone Cocreation Contract | `Attachment(ownerType=sales_order, category=COCREATION_SUPERONE)` | — | — |
| 61 | Robotic Cocreation Contract | `Attachment(ownerType=sales_order, category=COCREATION_ROBOTIC)` | — | — |
| 62 | Shipping Receipts | `LogisticsLeg` → `Attachment` | `shippingReceiptAttachmentId` | UUID FK |
| 63 | Label, body - FCC | `QualityLabelRecord(labelTypeCode=BODY_FCC)` | — | row |
| 64 | Label, body Made in CN | `QualityLabelRecord(labelTypeCode=BODY_MADE_IN_CN)` | — | row |
| 65 | Label, SN Body | `QualityLabelRecord(labelTypeCode=BODY_SN)` | — | row |
| 66 | Label, SN remote | `QualityLabelRecord(labelTypeCode=REMOTE_SN)` | — | row |
| 67 | Label, Battery SN | `QualityLabelRecord(labelTypeCode=BATTERY_SN)` | — | row |
| 68 | Label, Inspection Sheet | `QualityLabelRecord(labelTypeCode=INSPECTION_SHEET)` | — | row |
| 69 | Label, Shipping box | `QualityLabelRecord(labelTypeCode=SHIPPING_BOX)` | — | row |
| 70 | Robot | `RobotPackageReadiness` | `hasRobot` | Bool |
| 71 | Battery | `RobotPackageReadiness` | `hasBattery` | Bool |
| 72 | Remote | `RobotPackageReadiness` | `hasRemote` | Bool |
| 73 | USA Power cable | `RobotPackageReadiness` | `hasUsaPowerCable` | Bool |
| 74 | CN cable w adapter | `RobotPackageReadiness` | `hasCnCableWithAdapter` | Bool |
| 75 | Power supply | `RobotPackageReadiness` | `hasPowerSupply` | Bool |
| 76 | Charging dock | `RobotPackageReadiness` | `hasChargingDock` | Bool |
| 77 | foot pads | `RobotPackageReadiness` | `hasFootPads` | Bool |
| 78 | tools/ spare kit | `RobotPackageReadiness` | `hasToolsKit` | Bool |
| 79 | paperwork | `RobotPackageReadiness` | `hasPaperwork` | Bool |
| 80 | Sources | `RobotImportAudit` | `sources` | String[] |
| 81 | Conflict Count | `RobotImportAudit` | `conflictCount` | Int |
| 82 | Record Status | `RobotImportAudit` | `recordStatus` | enum |

---

## 11. 待你拍板的归位选择

1. **`RobotPackageReadiness` 单表 10 列 vs 子表多行**（§6.7）：当前 10 个固定配件品类，建议单表 10 列；将来扩到 50+ 再拆。
2. **`LabelType` 字典 vs 字面 enum**：当前 7 个标签类型，可以做 enum 也可以做 Dictionary 行。Dictionary 让运营加新标签不用改代码——但按 standard 16 默认不立 L4 / 默认强类型，**建议 enum**。
3. **`PreDelContractStatus` vs 合并到 `SalesContractStatus`**：第 33 列 "Payment & Contract (PreDel)" 跟第 30 "Contract Status" 是不同维度（前者是付款 + 合同综合状态，后者是合同本身）；保持独立 enum。
4. **`ImportDeclarationType` / `TariffType` / `IssueTag` / `IssueType` 用 Dictionary 合并表 vs 单独表**：纯枚举无专属字段——按决策 6 入 `Dictionary(category, code, label)`。
5. **`RobotUnit.ffsnDisplay`** 是否要立独立字段？v5 #1 和 #3 都叫 FF SN（同值场景下），但 v5 mapping 给了两列说明（key 是主键 / 普通 FF SN 是显示）——保留独立字段方便处理"占位 SN 时 key 是占位、display 是空"的情景。
6. **`ServiceRecord` vs `ServiceTicketActivity`**：v5 #55 "Service Records" 是单行文本字符串。建议沿用单字段 + 跟进活动靠 `ServiceTicketActivity` 子表记录多次操作。

---

## 12. 跟当前 prisma 的 gap

当前 `robot_manager.prisma` 11 model → 目标 ~22 model（不含 L1 平台层）：

| 当前 | → 目标 | 动作 |
|---|---|---|
| RobotFieldDef | 删除 | PR3 |
| RobotSystemConfig | 保留 | — |
| RobotModel | Model | 改名 + 加 specSummary/retiredAt |
| RobotSku | Sku | 改名 + 加 currency（**不**加 usageType——Usage Type 是 Unit 级，归 RobotUnit）|
| RobotSupplier | → platform_master.Supplier | PR2 迁出 |
| RobotCustomer | → platform_master.Customer | PR2 |
| RobotLocation | → platform_master.Location | PR2 |
| RobotUnit | RobotUnit（瘦身）+ RobotUnitSnapshot（新）| PR3 拆分 |
| RobotServiceRecord | ServiceTicket + ServiceTicketActivity | PR4 |
| RobotStatusChangeLog | RobotLifecycleEvent | PR3 升级 |
| RobotAttachment | → platform_master.Attachment | PR2 多态化 |
| — | **+ PurchaseOrder + Line** | PR4 |
| — | **+ SalesOrder + Line** | PR4 |
| — | **+ DeliveryRequest** | PR4 |
| — | **+ DeliveryFulfillment** | PR4 |
| — | **+ PaymentRecord** | PR4 |
| — | **+ QualityLabelRecord** | PR4 |
| — | **+ RobotPackageReadiness** | PR4 |
| — | **+ InspectionRecord** | PR4 |
| — | **+ LogisticsLeg** | PR4 |
| — | **+ ComplianceCheck** | PR4 |
| — | **+ RobotImportAudit** | PR5（仅 v5 导入用）|

---

## 13. 下一步

1. **本文档 review + 11 项拍板**（§11）→ 修订定稿
2. **PR-B 提交**（含本文档 + 数据分层方案.md/.html + 配套 HTML）
3. **PR1 实施开始**：L1 字典 + enum 扩 → 按本文档 §9 enum 清单写 prisma
