# Robot Manager — 数据库结构参考

> **适用环境**：FFAI Workspace 生产（`ffws_pro`，schema: `robot_manager`）
> **数据库版本**：PostgreSQL 16
> **最后同步**：2026-04-24（直接从生产库提取）
> **当前数据量**：robot_units 89 条

---

## 实体关系图

![Robot Manager ER Diagram](robot-er.png)

> 注：表间关联为应用层逻辑关联，数据库未建外键约束。

---

## 快速上手

```sql
-- 设置默认 schema，省去每次写前缀
SET search_path TO robot_manager;

-- 查看所有设备
SELECT * FROM robot_units LIMIT 10;

-- 连接设备与型号、客户
SELECT
  u.ffsn,
  u.current_status,
  m.name  AS model_name,
  s.name  AS sku_name,
  c.name  AS customer_name,
  l.name  AS location_name
FROM robot_units u
LEFT JOIN robot_models    m ON m.id = u.model_id
LEFT JOIN robot_skus      s ON s.id = u.sku_id
LEFT JOIN robot_customers c ON c.id = u.customer_id
LEFT JOIN robot_locations l ON l.id = u.location_id
WHERE u.deleted_at IS NULL;
```

> ⚠️ **注意**：表之间没有数据库级外键约束，关联关系由应用层维护。查询时用 `LEFT JOIN` 防止关联缺失导致漏行。

---

## 总体结构

```
robot_field_defs          ← 定义 metadata 里有哪些业务字段
robot_system_config       ← 系统配置（FFSN 规则、状态联动等）

robot_models              ← 型号目录
  └── robot_skus          ← SKU（挂在 Model 下）

robot_suppliers           ← 供应商档案
robot_customers           ← 客户档案
  └── robot_locations     ← 位置/仓库（可关联客户）

robot_units               ← 核心表：每行 = 一台机器人
  ├── robot_service_records      ← 维修/服务记录（1:N）
  ├── robot_status_change_logs   ← 状态变更历史（1:N）
  └── robot_attachments          ← 附件（1:N）
```

---

## 核心表：robot_units

每行代表一台机器人，是整个 schema 的核心。

| 列名 | 类型 | 可空 | 说明 |
|------|------|------|------|
| `id` | uuid | NO | 主键 |
| `organization_id` | uuid | NO | 所属组织 |
| `ffsn` | text | NO | FF 内部序列号，系统自动生成，全局唯一 |
| `current_status` | enum | NO | 当前生命周期状态（见下方枚举值） |
| `model_id` | uuid | NO | → robot_models.id |
| `sku_id` | uuid | NO | → robot_skus.id |
| `supplier_id` | uuid | YES | → robot_suppliers.id（可空） |
| `customer_id` | uuid | YES | → robot_customers.id（已交付时有值） |
| `location_id` | uuid | YES | → robot_locations.id（当前物理位置） |
| `metadata` | jsonb | NO | 所有动态业务字段（见下方字段清单） |
| `version` | integer | NO | 乐观锁版本号 |
| `created_by` | uuid | NO | 创建者 userId |
| `updated_by` | uuid | NO | 最后修改者 userId |
| `created_at` | timestamptz | NO | 创建时间 |
| `updated_at` | timestamptz | NO | 最后更新时间 |
| `deleted_at` | timestamptz | YES | 软删除时间戳；**IS NULL 表示未删除** |

### current_status 枚举值

| 值 | 含义 |
|----|------|
| `ORDERED` | 已下单 |
| `IN_TRANSIT` | 运输中 |
| `BONDED` | 保税仓 |
| `IN_STOCK` | 在库 |
| `RESERVED` | 已预留（待交付） |
| `SOLD` | 已销售 |
| `DELIVERED` | 已交付 |
| `REPAIR` | 维修中 |
| `REPAIRED` | 维修完成 |
| `CANCELLED` | 已取消 |

### metadata 动态字段清单

`robot_units.metadata` 是一个 JSONB 字段，存储所有业务字段。字段定义来自 `robot_field_defs` 表。

| key | 英文名 | 分组 | 类型 | 说明 |
|-----|--------|------|------|------|
| `supplierSn` | Supplier SN | supply-chain | text | 供应商序列号 |
| `trackingId` | Tracking ID | supply-chain | text | 物流跟踪号 |
| `importType` | Import Type | supply-chain | select | 进口类型 |
| `poNumber` | PO Number | supply-chain | text | 采购订单号 |
| `purchaseDate` | Purchase Date | supply-chain | date | 采购日期 |
| `purchasePrice` | Purchase Price | supply-chain | money | 采购价（FOB） |
| `arrivalDate` | Arrival Date | supply-chain | date | 到货日期 |
| `logisticsStatus` | Logistics Status | supply-chain | select | 物流状态 |
| `contractStatus` | Contract Status | supply-chain | select | 合同状态 |
| `contactLink` | Contact Link | supply-chain | url | 联系链接 |
| `importDeclarationType` | Import Declaration Type | supply-chain | select | 报关类型 |
| `tariffType` | Tariff Type | supply-chain | select | 关税类型 |
| `usageType` | Usage Type | identity | select | 设备用途类型 |
| `currency` | Currency | identity | select | 货币 |
| `salesOrderId` | Sales Order ID | sales | text | 销售订单号 |
| `salesPriceHardware` | Sales Price - Hardware | sales | money | 硬件售价 |
| `salesPriceSoftware` | Sales Price - Software | sales | money | 软件售价 |
| `paymentMethod` | Payment Method | sales | select | 付款方式 |
| `paymentStatus` | Payment Status | sales | select | 付款状态 |
| `deliveryDate` | Delivery Date | sales | date | 交付日期 |
| `deliverySignedForm` | Delivery Signed Form | sales | select | 签收单状态 |
| `deliveryStatus` | Delivery Status | sales | select | 交付状态 |
| `cost` | Cost | finance | money | 到岸成本（采购+运费+关税等） |
| `grossMargin` | Gross Margin | finance | money | 毛利（salesPrice - cost） |
| `revenueRecognition` | Revenue Recognition | finance | money | 收入确认金额 |
| `invoiceStatus` | Invoice Status | finance | select | 发票状态 |
| `fccStatus` | FCC Status | compliance | select | FCC 认证状态 |
| `complianceNotes` | Compliance Notes | compliance | text | 合规备注 |
| `warrantyStatus` | Warranty Status | after-sales | select | 保修状态 |
| `issueTag` | Issue Tag | after-sales | select | 问题标签 |
| `serviceRecords` | Service Records | after-sales | text | 售后记录摘要 |
| `customerFeedback` | Customer Feedback | after-sales | text | 客户反馈 |
| `serviceType` | Service Type | — | select | 服务类型 |
| `locationType` | Location Type | — | select | 位置类型 |

**读取 metadata 字段示例**：

```sql
-- 读取单个字段
SELECT ffsn, metadata->>'supplierSn' AS supplier_sn
FROM robot_manager.robot_units;

-- 按采购价筛选（money 类型存为字符串，需转换）
SELECT ffsn, (metadata->>'purchasePrice')::numeric AS purchase_price
FROM robot_manager.robot_units
WHERE (metadata->>'purchasePrice') IS NOT NULL;

-- 按状态+日期范围查
SELECT ffsn, metadata->>'deliveryDate' AS delivery_date
FROM robot_manager.robot_units
WHERE current_status = 'DELIVERED'
  AND (metadata->>'deliveryDate')::date >= '2026-01-01';
```

---

## 型号与 SKU

### robot_models（型号目录）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `code` | text | 型号代码，唯一 |
| `name` | text | 型号名称 |
| `brand` | text | 品牌 |
| `description` | text | 描述 |
| `image_url` | text | 图片链接 |
| `enabled` | boolean | 是否启用 |
| `metadata` | jsonb | 扩展字段 |

### robot_skus（SKU）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `model_id` | uuid | → robot_models.id |
| `code` | text | SKU 代码，唯一 |
| `name` | text | SKU 名称 |
| `variant` | text | 变体描述（如颜色/配置） |
| `default_price` | numeric | 默认售价 |
| `default_cost` | numeric | 默认成本 |
| `enabled` | boolean | 是否启用 |

---

## 供应商、客户、位置

### robot_suppliers（供应商）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `code` | text | 供应商代码 |
| `name` | text | 供应商名称 |
| `contact_name` | text | 联系人 |
| `contact_phone` | text | 联系电话 |
| `contact_email` | text | 联系邮箱 |
| `payment_terms` | text | 付款条款 |
| `lead_time_days` | integer | 交货周期（天） |
| `enabled` | boolean | 是否启用 |

### robot_customers（客户）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `code` | text | 客户代码 |
| `name` | text | 客户名称 |
| `industry` | text | 所属行业 |
| `contact_name` | text | 联系人 |
| `contact_phone` | text | 联系电话（⚠️ 敏感） |
| `contact_email` | text | 联系邮箱（⚠️ 敏感） |
| `address` | text | 地址 |
| `credit_limit` | numeric | 授信额度 |
| `enabled` | boolean | 是否启用 |

### robot_locations（位置/仓库）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `code` | text | 位置代码 |
| `name` | text | 位置名称 |
| `type_code` | text | 位置类型（仓库/客户站点等） |
| `address` | text | 地址 |
| `customer_id` | uuid | 关联客户（可空，客户站点时有值） |
| `enabled` | boolean | 是否启用 |

---

## 历史记录表

### robot_service_records（服务/维修记录）

每台设备可有多条，记录每次维修或服务事件。

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `robot_unit_id` | uuid | → robot_units.id |
| `service_type_code` | text | 服务类型代码 |
| `description` | text | 问题描述 |
| `resolution` | text | 解决方案 |
| `service_date` | date | 服务日期 |
| `completed_date` | date | 完成日期 |
| `customer_feedback` | text | 客户反馈 |
| `created_by` | uuid | 创建者 userId |

### robot_status_change_logs（状态变更历史）

每次状态流转自动记录一条。

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `robot_unit_id` | uuid | → robot_units.id |
| `from_status` | enum | 变更前状态（首次创建时为 NULL） |
| `to_status` | enum | 变更后状态 |
| `changed_by` | uuid | 操作者 userId |
| `changed_at` | timestamptz | 变更时间 |
| `remark` | text | 备注 |

### robot_attachments（附件）

| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | uuid | 主键 |
| `robot_unit_id` | uuid | → robot_units.id |
| `filename` | text | 文件名 |
| `mime_type` | text | MIME 类型 |
| `size` | integer | 文件大小（字节） |
| `storage_path` | text | 对象存储路径（MinIO） |
| `uploaded_by` | uuid | 上传者 userId |
| `uploaded_at` | timestamptz | 上传时间 |

---

## 常用查询示例

```sql
-- 1. 库存统计（按状态分组）
SELECT current_status, COUNT(*) AS count
FROM robot_manager.robot_units
WHERE deleted_at IS NULL
GROUP BY current_status
ORDER BY count DESC;

-- 2. 已交付设备清单（含客户信息）
SELECT
  u.ffsn,
  m.name AS model,
  s.name AS sku,
  c.name AS customer,
  metadata->>'deliveryDate' AS delivery_date,
  (metadata->>'salesPriceHardware')::numeric +
  COALESCE((metadata->>'salesPriceSoftware')::numeric, 0) AS total_price
FROM robot_manager.robot_units u
JOIN robot_manager.robot_models m ON m.id = u.model_id
JOIN robot_manager.robot_skus   s ON s.id = u.sku_id
LEFT JOIN robot_manager.robot_customers c ON c.id = u.customer_id
WHERE u.current_status = 'DELIVERED'
  AND u.deleted_at IS NULL;

-- 3. 某台设备的完整状态变更历史
SELECT from_status, to_status, changed_at, remark
FROM robot_manager.robot_status_change_logs
WHERE robot_unit_id = '<uuid>'
ORDER BY changed_at;

-- 4. 按供应商统计采购数量和总金额
SELECT
  sup.name AS supplier,
  COUNT(*) AS unit_count,
  SUM((u.metadata->>'purchasePrice')::numeric) AS total_purchase
FROM robot_manager.robot_units u
JOIN robot_manager.robot_suppliers sup ON sup.id = u.supplier_id
WHERE u.deleted_at IS NULL
GROUP BY sup.name
ORDER BY unit_count DESC;
```

---

## 注意事项

1. **软删除**：`robot_units` 使用软删除，查询时务必加 `WHERE deleted_at IS NULL`，否则会查出已删除记录。
2. **无 FK 约束**：表间没有数据库级外键约束，用 `LEFT JOIN` 更安全，避免漏行。
3. **metadata 类型转换**：JSONB 字段取出的值均为字符串，`money` 类型字段需转 `::numeric`，`date` 类型需转 `::date`。
4. **敏感字段**：`robot_customers.contact_phone` 和 `contact_email` 含客户联系信息，请按需取用，避免大范围导出。
