# Robot Manager Seed Operations

> **范围**：把 Master_Metadata_v5.xlsx 真实主数据（172 台机器人 + 关联 L1/L2/L3 全链）seed 到本地 / UAT 测试环境的完整操作流程。
> **何时读**：要在某个环境放/补/重置 master 测试数据；Excel 改了要回灌；做完一轮验证想交付给团队测试。
>
> 操作步骤本身在这里写清；**事实源 / 字段映射逻辑** 见 `backend/prisma/seeds/robot-manager-master-seed.ts` 顶部注释和 `backend/prisma/seeds/fixtures/build-robot-master-fixture.mjs`。

---

## 1. 这是什么

```
docs/modules/robot-manager/business-analysis/Master_Metadata_v5.xlsx
     │  (172 行 × 82 列真实业务数据)
     ▼
build-robot-master-fixture.mjs   ← 解析 + 归一化（人工跑一次）
     │
     ▼
backend/prisma/seeds/fixtures/robot-master-v5.json   ← 派生 fixture（committed）
     │
     ▼
robot-manager-master-seed.ts     ← npm run db:seed:robot-master（幂等）
     │
     ▼
DB（按 L1/L2/L3 分层）：
  L1 主数据   Customer 41 / Supplier 3 / Location 4
  L2 主数据   RobotModel 9 / RobotSku 14
  L2 核心     RobotUnit 172 + Snapshot 172 + 起点 LifecycleEvent 172
  L3 交易    PurchaseOrder 4 (lines 11) / SalesOrder 32 (lines 92)
              DeliveryRequest 24 (fulfillments 38)
```

幂等：以 `code` / `ffsn` / `poNo` / `soNo` / `deliveryNo` 为唯一键 upsert。重跑 N 次结果一致；已有相同 code 的记录**不会覆盖**已有内容（只补 L3 FK）。

---

## 2. 本地（slot / dev）seeding

前置：slot 已经 `pool-init` 起好，PG 容器在跑，schema 已 `db:push`，`itadmin` 已存在。

```bash
cd /home/chentao/Code/ffworkspace-wt/.agent-pool/slot-3   # 替换为自己的 slot
cd backend

# 第一次 seed：
npm run db:seed:robot-master

# 校验（28 个不变量逐项过）：
npx dotenv -e .env -- node prisma/seeds/fixtures/verify-robot-master.mjs
# 预期：PASS 28  FAIL 0
```

---

## 3. UAT seeding（团队测试）

### 3.1 前置确认

```bash
# 1. fixture JSON 已经合到 develop / staging / production
ls backend/prisma/seeds/fixtures/robot-master-v5.json

# 2. seed 脚本也已合并
ls backend/prisma/seeds/robot-manager-master-seed.ts

# 3. develop → staging → production 走标准 FF-only promote
#    (不要 web UI 手点；详见 CLAUDE.md § 环境升级合并策略)
python3 scripts/ops/gitea promote uat       # develop → staging
# CI 部署到 UAT 服务器
```

### 3.2 在 UAT 服务器跑 seed

```bash
# 1. SSH 到 UAT
ssh ffworkspace_dev@<uat-host>

# 2. 切到 UAT 部署根目录
cd /srv/apps/ffworkspace-test/backend

# 3. 确认环境（.env 应软链到 ../.env.uat）
ls -l .env
cat .env | grep -E '^DATABASE_URL=' | head -1     # 别打印密码

# 4. 确认前置依赖：TEST_ORG + itadmin
npx dotenv -e .env -- npx prisma db execute --stdin <<'SQL'
SELECT
  (SELECT count(*) FROM platform_iam.organizations WHERE code='TEST_ORG') AS org,
  (SELECT count(*) FROM platform_iam.users WHERE username='itadmin') AS itadmin;
SQL
# 都应为 1。若 itadmin = 0：先 `npm run init:itadmin` 再继续。

# 5. 跑 seed（幂等，安全）
npm run db:seed:robot-master

# 6. 在 UAT DB 上跑 verify
npx dotenv -e .env -- node prisma/seeds/fixtures/verify-robot-master.mjs
```

**预期输出**：
- seed：`robot_units: created 172`（首次）或 `created 0, updated L3 FKs on 172`（已存在）
- verify：`PASS 28  FAIL 0`

### 3.3 浏览器侧确认

1. 浏览器开 `https://ffworkspace-test.<domain>/robot-manager`
2. 登录任意 TEST_ORG 用户（用 `itadmin` 或团队普通账号）
3. 列表页应该看到 **172 台 + v3 demo 100 台 = 272** 行机器人
4. 切到「我的工作台」/ stage 看板，应看到分布：
   - LOGISTICS_IN_TRANSIT 61
   - DELIVERY_DELIVERED 34
   - WAREHOUSE_BRANDED_READY 33
   - SALES_RESERVED 31
   - SUPPLY_PO_CREATED 8
   - AFTERSALES_UNDER_REPAIR 3
   - AFTERSALES_REPAIRED 2
5. 抽 1 台 `FFM1226030000002`：详情应显示 model `Master Ultra` / customer `Alcorn Capital` / PO `4900000714` / SO `FF-RB-20260002` / delivered `2026-03-08`

如果以上对不上，找 [TZ bug 历史](#已知坑) 段。

---

## 4. 影响范围与隔离

| 资源 | UAT 已有? | seed 行为 | 风险 |
|---|---|---|---|
| Organization `TEST_ORG` | 通常有 | upsert by code | 无 |
| Customer `CUST-*`（41 个）| 可能部分有 | upsert by code，不更新名字 | 不会覆盖 |
| Supplier `SUPP-AGIBOT/WEILAN/ZHISHEN` | 可能有 | upsert | 不会覆盖 |
| Location `LOC-HQ/CHINA/UAE/CLIENT-SITE` | 可能有 | upsert | 不会覆盖 |
| RobotModel/Sku | 跟 demo seed 共用 code 命名空间 | upsert by code | 不会覆盖 |
| RobotUnit ffsn | UAT 不会有真 Excel SN | 全新建 | 无冲突 |
| PurchaseOrder `4900000714` 等 4 个 | UAT 不应有 | 全新建 | 若冲突 → 改 fixture poNo |
| SalesOrder `FF-RB-2026*` 等 32 个 | UAT 不应有 | 全新建 | 同上 |
| DeliveryRequest `DEL-*` | UAT 不应有 | 全新建 | 无 |

**安全保证**：所有 robot 都打 `metadata.importedFrom='Master_Metadata_v5'` 标签，便于事后筛选 / 清理。

---

## 5. UAT 清理（团队测完想下架）

```bash
ssh ffworkspace_dev@<uat-host>
cd /srv/apps/ffworkspace-test/backend

# 5.1 dry-run 看看会删什么
npx dotenv -e .env -- npx prisma db execute --stdin <<'SQL'
SELECT 'robots' AS k, count(*) v FROM robot_manager.robot_units WHERE metadata->>'importedFrom'='Master_Metadata_v5'
UNION ALL SELECT 'POs', count(*) FROM robot_manager.purchase_orders WHERE po_no IN ('4900000714','4900000741','PO47-675','PO47-676')
UNION ALL SELECT 'SOs', count(*) FROM robot_manager.sales_orders WHERE so_no LIKE 'FF-RB-2026%' OR so_no LIKE 'ORD-%'
UNION ALL SELECT 'DRs', count(*) FROM robot_manager.delivery_requests WHERE delivery_no LIKE 'DEL-%';
SQL

# 5.2 真删（按 FK 顺序，事务包起来）
npx dotenv -e .env -- npx prisma db execute --stdin <<'SQL'
BEGIN;
DELETE FROM robot_manager.delivery_fulfillments
  WHERE robot_unit_id IN (SELECT id FROM robot_manager.robot_units WHERE metadata->>'importedFrom'='Master_Metadata_v5');
DELETE FROM robot_manager.delivery_requests WHERE delivery_no LIKE 'DEL-%';
DELETE FROM robot_manager.sales_order_lines
  WHERE sales_order_id IN (SELECT id FROM robot_manager.sales_orders WHERE so_no LIKE 'FF-RB-2026%' OR so_no LIKE 'ORD-%');
DELETE FROM robot_manager.sales_orders WHERE so_no LIKE 'FF-RB-2026%' OR so_no LIKE 'ORD-%';
DELETE FROM robot_manager.purchase_order_lines
  WHERE purchase_order_id IN (SELECT id FROM robot_manager.purchase_orders WHERE po_no IN ('4900000714','4900000741','PO47-675','PO47-676'));
DELETE FROM robot_manager.purchase_orders WHERE po_no IN ('4900000714','4900000741','PO47-675','PO47-676');
DELETE FROM robot_manager.robot_units WHERE metadata->>'importedFrom'='Master_Metadata_v5';
COMMIT;
SQL
```

L1 主数据（41 customers / 3 suppliers / 4 locations）默认**不清**——可能跟其他测试数据共用。要清额外加 `DELETE FROM platform_master.customers WHERE code LIKE 'CUST-%' ...`。

---

## 6. Excel 更新后回灌

```bash
# 1. 在本地把新 Excel 放回原位（或直接 git pull 拿新版本）
ls docs/modules/robot-manager/business-analysis/Master_Metadata_v5.xlsx

# 2. 重新派生 fixture
cd backend
node prisma/seeds/fixtures/build-robot-master-fixture.mjs
# 检查输出：unknown stages / locations / usages 必须为空

# 3. 本地 verify
npm run db:seed:robot-master
npx dotenv -e .env -- node prisma/seeds/fixtures/verify-robot-master.mjs

# 4. PR 把更新后的 robot-master-v5.json 合到 develop → 走 promote 到 UAT
# （Excel 本身也要 commit，但不在 UAT 服务器使用——服务器只读 JSON）
```

**警告**：如果 Excel 新增了未知 `Lifecycle Status` / `Location` / `Usage Type`，build 脚本会打印 `⚠️ unknown ...` 并使用兜底值。需要先更新 `STAGE_MAP` / `LOCATION_MAP` / `USAGE_MAP`，否则数据会被错误分类。

---

## 7. 已知坑

### TZ bug（2026-05-19 修）
Excel cell 「Delivery Date」类型经常是字符串 `"YYYY-MM-DD HH:mm:ss"`。`new Date(str).toISOString()` 在 UTC+8 时区会让日期倒退 1 天。修复方案：`toIsoDate` 直接抽 `YYYY-MM-DD` 前缀，不走 `new Date()` 解析。如果未来在 UTC-N 时区跑 seed 又看到日期偏差，先复核 `fixtures/build-robot-master-fixture.mjs` 的 `ymd()` + `toIsoDate()` 函数。

### Customer 名字相似不去重
Excel 里 "Falrano Motors" 和 "Falrano Motors Inc" 是两条记录，"Joshua Auto Service LLC" 有 3 个拼写变体。当前 fixture 按原样导入产生 41 个 customer。若需合并，改 `build-robot-master-fixture.mjs` 的 `normalizeCustomerName` 增加 fuzzy match。

### 2 个 PO 没填 supplier
PO47-675 / PO47-676 在 Excel 的 `Partner / Supplier` 列空。fixture 默认兜底为 `SUPP-AGIBOT`。如果业务上不正确，更新 fixture builder 或 Excel 数据本身。

---

## 8. 相关文档

- 字段映射逻辑：[`backend/prisma/seeds/fixtures/build-robot-master-fixture.mjs`](../../../backend/prisma/seeds/fixtures/build-robot-master-fixture.mjs) §STAGE_MAP/USAGE_MAP/LOCATION_MAP/SUPPLIER_MAP
- 数据分层依据：[`docs/standards/16-data-layering-and-metadata-policy.md`](../../standards/16-data-layering-and-metadata-policy.md)
- v3 demo seed（合成 30 台覆盖各 stage）：[`backend/prisma/seeds/robot-manager-v3-seed.ts`](../../../backend/prisma/seeds/robot-manager-v3-seed.ts)
- 部署环境拓扑：[`docs/ops/01-server-infrastructure.md`](../../ops/01-server-infrastructure.md)
- env 软链规则：[`docs/ops/07-env-and-secrets.md`](../../ops/07-env-and-secrets.md)
