# 机器人全生命周期管理 - 产品需求文档

> **module**: robot-manager
> **doc_type**: PRD
> **status**: Active
> **owner**: FFOA Team
> **upstream_docs**: 无（PRD 是依赖链起点）
> **last_verified**: 2026-04-14
>
> **事实源**: 本文档基于 RLE V1 需求文档整理，定义当前版本业务范围与验收边界

---

## 目标与问题

### 要解决的问题

- 数据分散：采购/库存/销售/财务数据散落在多个系统和表格中
- 缺乏单机维度追踪：无法做到"一机一档"，无法按 SN 追溯完整履历
- SN 体系未系统化：FFSN / Supplier SN 没有统一管理和唯一性校验
- 状态定义不统一：库存/在途/销售等状态混用，无法准确判断设备当前位置
- 无法支撑财务披露：缺乏 10K / Earnings 所需的单机级成本、收入、毛利数据

### 项目目标

构建一个系统，实现：**"一机一档 + 全生命周期追踪 + 财务与合规可追溯"**

### 成功标准

- 每台设备可追溯：SN → 客户 → 成本，完整链路可查
- 库存与销售一致：系统内库存数据与实际销售状态实时对齐
- 支撑财务披露：可直接输出 Total Revenue / Total Cost / Total Margin
- 人工对账减少 ≥ 50%

---

## 功能边界

**In-scope（V1）**：

- 机器人 Unit 管理（新建/编辑/查看，一机一档）
- SN 管理（FFSN 自动生成 + 唯一校验，Supplier SN 手动输入）
- 状态管理（字典表下拉选择 + 状态机合法性校验）
- 销售绑定（一台设备仅绑定一个客户）
- 自动计算（Gross_Margin = Sales_Price - Cost）
- 数据校验（FFSN 唯一、Enum 限制、数值/日期格式校验）
- 库存报表（In Stock 数量、在途数量）
- 销售报表（Delivered 数量、客户分布）
- 财务报表（Total Revenue / Total Cost / Total Margin）
- Excel 导入导出
- 多维筛选（状态/客户/型号）
- 权限控制（按角色 × 模块 × 操作）

**Out-of-scope（后续版本）**：

- ERP/SAP 对接
- 美国合规功能（详见 [us-compliance.md](us-compliance.md)）

---

## 术语表

| 术语 | 说明 |
|------|------|
| Unit | 单台机器人，系统中每一行数据代表一台设备 |
| FFSN | FF 内部序列号，系统按 `RobotSystemConfig.ffsn_rule` 自动生成，组织内唯一（service 层保证） |
| Supplier SN | 供应商序列号，手动录入 |
| currentStatus | 设备当前生命周期状态（详见 [04-state-machine.md](04-state-machine.md)） |
| locationId | 设备当前物理位置 FK → RobotLocation（详见 [06-data-model.md](06-data-model.md)） |
| usageTypeCode | 设备用途类型 → RobotOption(USAGE_TYPE).code |
| purchasePrice | 采购 FOB 价格（供应链口径） |
| cost | 到岸成本 = 采购价 + 运费 + 关税等（财务口径） |
| grossMargin | 毛利 = salesPrice - cost（查询时计算，不存表） |

---

## 版本口径

本文档基于 RLE V1 需求文档编写，v2 重构 + Phase 3 字段级权限已完成实施。后续如需调整，应先更新本文档再修改代码。

---

## 数据粒度

- **每一行 = 一台机器人（Unit Level）**
- 所有模块字段都挂在同一台设备上，不做订单级聚合

---

## 6 大数据模块

系统围绕单台机器人，划分为 6 个数据模块（严格按此结构开发）：

| 模块 | 定义 | 回答的问题 |
|------|------|-----------|
| RobotInfo | 设备身份 | 设备"是什么"（静态属性） |
| RobotStatus | 状态 | 设备"现在在干嘛" |
| SC (Supply Chain) | 供应链 | 设备"怎么来的" |
| Sales | 销售 | 设备"卖给谁" |
| Finance | 财务 | 设备"赚多少钱" |
| After-sales | 售后 | 设备"出问题后" |

> 各模块字段定义和字典表取值见 [06-data-model.md](06-data-model.md)（唯一事实源）。

---

## 核心功能

### F1: Unit 管理

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F1.1 新建机器人 | P0 | 录入设备基本信息，创建一机一档。必填：modelId、skuId（FK，从 `/admin/models` 和 `/admin/skus` 下拉选择，sku 按 model 联动过滤）。FFSN 由系统按 `RobotSystemConfig.ffsn_rule` 自动生成。初始状态 ORDERED |
| F1.2 编辑字段 | P0 | 按角色权限修改设备各模块字段 |
| F1.3 查看详情 | P0 | 查看单台设备的完整信息（6 大模块） |
| F1.4 设备列表 | P0 | 分页展示，支持多维筛选（状态/客户/型号） |
| F1.5 批量状态变更 | P0 | 选中多台设备执行同一状态变更（如到货批量 IN_TRANSIT → IN_STOCK），每台独立校验，失败的跳过并汇总报告 |

### F2: SN 管理

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F2.1 FFSN 自动生成 | P0 | 按规则自动生成 FFSN |
| F2.2 Supplier SN 输入 | P0 | 手动录入供应商序列号 |
| F2.3 FFSN 唯一校验 | P0 | 双层保证：service 层事务串行 + DB partial unique index（`WHERE deleted_at IS NULL`），由 seed 脚本创建（Prisma DSL 不支持 partial unique） |

### F3: 状态管理

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F3.1 状态变更 | P0 | 通过专用 API 触发，非普通字段编辑 |
| F3.2 状态合法性校验 | P0 | 按状态机规则校验，含前置条件检查 |
| F3.3 Location 自动联动 | P0 | 状态变更时按 `RobotSystemConfig.status_default_location` 配置自动设置默认 Location（v2 配置化，可由管理员调整）。locationId 为 null 时由 UI 依据 status 推断显示。 |

> 状态机详细规则见 [04-state-machine.md](04-state-machine.md)

### F4: 销售绑定

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F4.1 客户绑定 | P0 | 一台设备仅绑定一个客户 |
| F4.2 状态约束 | P0 | In Stock（绑定后可转 Reserved）/ Reserved / Delivered 状态可绑定客户 |

### F5: 自动计算

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F5.1 毛利计算 | P0 | `grossMargin = salesPrice - cost`，查询时计算不存表。任一为空则结果为 null |

### F6: 数据校验

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F6.1 FFSN 唯一 | P0 | service + DB 双层保证（详见 F2.3） |
| F6.2 Enum 字段限制 | P0 | 所有分类字段只能从字典表取值（`RobotOption` 管 10 类：USAGE_TYPE/IMPORT_TYPE/DELIVERY_STATUS/CONTRACT_STATUS/PAYMENT_METHOD/PAYMENT_STATUS/WARRANTY_STATUS/SERVICE_TYPE/ISSUE_TAG/LOCATION_TYPE）。仅 `RobotStatus` 仍为代码 enum |
| F6.3 数值字段校验 | P0 | 价格、成本不可为负，使用 Decimal 精度 |
| F6.4 日期格式校验 | P0 | 统一 ISO 8601 |

### F7: 报表

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F7.1 库存报表 | P0 | 可分配库存（IN_STOCK）、已预留库存（RESERVED）、物理库存（IN_STOCK + RESERVED）、在途数量（IN_TRANSIT + BONDED） |
| F7.2 销售报表 | P0 | Delivered 数量、客户分布 |
| F7.3 财务报表 | P0 | **v2 简化口径**：Revenue = SUM(salesPrice)，Cost = SUM(cost)，Margin = SUM(salesPrice - cost)。**不按 currentStatus 过滤**（任何状态都计入，包括 REPAIR/CANCELLED）。**仅当 salesPrice 和 cost 都不为 null 时才计入**。软删除不计入。 |

### F8: 导入导出

| 功能点 | 优先级 | 说明 |
|--------|--------|------|
| F8.1 Excel 导出 | P0 | 按筛选条件导出设备列表 |
| F8.2 Excel 导入 | P0 | 批量导入设备数据，含校验（FFSN 重复、非法 Enum、数值非负） |
| F8.3 多维筛选 | P0 | 按状态/客户/型号筛选 |

---

## 核心业务约束

- **一机一档**：每台机器人在系统中是唯一记录，以 FFSN 为业务标识（技术主键为 UUID）
- **FFSN 组织内唯一**：系统自动生成，同一组织内不可重复，不可手工修改
- **状态不可跳跃**：状态转换必须遵循状态机规则，禁止跨状态操作
- **状态不可直编**：currentStatus 只能通过状态机 API 变更，不是普通可编辑字段
- **单客户绑定**：一台设备仅可绑定一个客户
- **销售绑定状态约束**：In Stock / Reserved / Delivered 状态可绑定客户；绑定客户是 In Stock → Reserved 的前置条件
- **毛利查询计算**：grossMargin = salesPrice - cost，查询时计算不存表，任一输入为 null 则结果为 null
- **财务口径**：毛利使用 cost（到岸成本）而非 purchasePrice（采购 FOB），确保财务披露准确
- **Cancelled 是终态**：设备取消或报废后不可恢复，需记录原因
- **Enum 强约束**：所有 Enum 字段必须从字典表取值

---

## 边界规则

### Always Do（始终执行，无需确认）

- 所有查询应带 `organizationId` 过滤（多租户隔离的长期目标；当前 v2 service 层暂未实现，所有查询默认单组织 = nil UUID，详见下方"假设"章节）
- FFSN 唯一性校验在新建和导入时均执行
- 状态变更必须经过合法性校验和前置条件检查
- Enum 字段只接受字典表定义的值
- 金额计算使用 Decimal 类型：单笔金额字段用精度 (12,2)，客户信用额度用 (14,2)
- 删除操作使用软删除（设置 deletedAt）

### Ask First（需确认后执行）

- 修改状态机流转规则
- 新增或修改字典表枚举值
- 修改 FFSN 生成规则
- 新增数据库表或字段

### Never Do（硬性禁止）

- 不得允许 FFSN 重复
- 不得跳跃状态（如 Ordered → Delivered）
- 不得允许 Delivered 后直接回到 In Stock（必须经过 Repair）
- 不得通过普通编辑 API 修改 currentStatus
- 不得允许一台设备绑定多个客户
- 不得硬删除设备记录
- 不得将 grossMargin 作为字段存入数据库（查询时由 salesPrice - cost 计算）

---

## 权限（V2 - Phase 3 字段级权限）

### 角色与 section 级字段写权限

| 操作 | Administrator | RLE | SupplyChain | Sales | Finance |
|------|---|-----|-----|-------|---------|
| 读取所有字段 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 创建 Unit（`robot-manager:create`） | ✅ | ✅ | ✅ | ❌ | ❌ |
| 全量更新（legacy `robot-manager:update`） | ✅ | ✅ | ❌ | ❌ | ❌ |
| 写入身份 section（`write:identity`） | ✅ | ✅ | ✅ | ❌ | ❌ |
| 写入供应链 section（`write:supply-chain`） | ✅ | ✅ | ✅ | ❌ | ❌ |
| 写入销售 section（`write:sales`） | ✅ | ✅ | ❌ | ✅ | ❌ |
| 写入财务 section（`write:finance`） | ✅ | ✅ | ❌ | ❌ | ✅ |
| 写入售后 section（`write:after-sales`） | ✅ | ✅ | ❌ | ❌ | ❌ |
| 状态变更（`change-status`） | ✅ | ✅ | ✅ | ✅ | ❌ |
| 删除 Unit（`delete`） | ✅ | ✅ | ❌ | ❌ | ❌ |
| Excel 导入（`import`） | ✅ | ✅ | ✅ | ❌ | ❌ |
| Excel 导出（`export`） | ✅ | ✅ | ✅ | ✅ | ✅ |
| 管理字典/实体/配置（`admin`） | ✅ | ✅ | ❌ | ❌ | ❌ |

### Section → 字段归属

| Section | 对应字段 | 对应端点 |
|---|---|---|
| `identity` | modelId, skuId, supplierSn, usageTypeCode, importTypeCode | `PUT /robot-manager/:id/identity` |
| `supply-chain` | supplierId, poNumber, purchaseDate, purchasePrice, eta, arrivalDate, deliveryStatusCode | `PUT /robot-manager/:id/supply-chain` |
| `sales` | customerId, salesOrderId, salesPrice, contractStatusCode, contractLink | `PUT /robot-manager/:id/sales` |
| `finance` | cost, paymentMethodCode, paymentStatusCode | `PUT /robot-manager/:id/finance` |
| `after-sales` | warrantyStatusCode, issueTagCode | `PUT /robot-manager/:id/after-sales` |

> **说明**：
> - Administrator 角色由 PermissionsGuard 自动绕过所有权限检查
> - RLE 是模块管理员，持有所有 section 级写权限
> - 其他角色只能写自己 section 的字段；状态变更由独立的 `change-status` 权限控制，不走 section
> - 前端详情页按用户角色渲染 Tab 切换视图（6 个）：全部 / 设备身份 / 供应链 / 销售 / 财务 / 售后
> - 所有角色均可查看所有字段（只读），读取级别不隔离

---

## 风险与假设

### 假设

- 组织架构模块已就绪，用户角色数据可用于权限判断
- 当前版本面向单一组织（service 层暂硬编码 organizationId 为 nil UUID，多租户未实际接入）
- 币种为单一货币（USD）
- FFSN 生成规则已配置化：默认 `FF-YYYYMM-NNNNN` 月度重置，管理员可在 `/admin/settings` 调整

### 风险

- 当前 FFSN 唯一性仅由 service 层保证，缺 DB 级 partial unique 兜底（见 F2.3 / 06-data-model.md TODO）
- Excel 导入的业务实体校验策略：要求所有 FK（Model/Sku/Supplier/Customer/Location）必须先在管理页建档，否则行级报错
- `cost` 与 `purchasePrice` 区分：当前设计为两个独立字段（采购 FOB vs 到岸成本），若业务不区分可合并

---

## 关键依赖

| 依赖模块 | 集成点 |
|----------|--------|
| organization | 用户角色、权限数据 |
| notification-engine | 状态变更通知。触发规则：→ DELIVERED（通知 Sales 角色）、→ REPAIR（通知 RLE 角色）、→ CANCELLED（通知 RLE + SC 角色）。其他状态变更不触发通知 |
| audit-system | **关键字段变更的审计日志**。以下字段的修改必须通过 audit-system 记录变更前后值：salesPrice、cost、customerId、poNumber、salesOrderId。状态变更由 StatusChangeLog 自行记录，不依赖 audit-system |
