docs(i4): add I4 design for M3 delivery and M4 license SN

Describe REST contracts, validation, routing, and I4 sync checklist
aligned with V4 schema and parallel iteration index.

Made-with: Cursor
This commit is contained in:
2026-04-06 21:48:55 +08:00
parent 7f8e7b7e7c
commit df91ab0673
+299
View File
@@ -0,0 +1,299 @@
# 迭代 I4 设计说明 — M3 交付批次与清单、M4 许可 SN 台账
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I4** 一致 — 平台后端 **M3 交付** + **M4 SN 录入/绑定/状态/手工回写**;前端 **交付页 + SN 页**;本仓库(SDK 工作区)以 **OpenAPI 契约与文档口径** 与 BP-10 对齐。
> **分支**`develop`。
> **已有实现锚点**(勿从零重设计,仅对齐与补全):Flyway `V4__delivery_batch_and_license_sn.sql``cn.craftlabs.platform.api.domain.DeliveryBatchStatus` / `LicenseSnStatus``web/dto` 下 `Delivery*`、`LicenseSn*`;审计常量 `AuditEntityTypes`、`AuditActions` 已含 `DELIVERY_BATCH`、`LICENSE_SN` 及对应动作。
---
## 1. I4 范围与 I3 / I5 边界
### 1.1 I4 **纳入**(本迭代 DoD
| 域 | 说明 |
|----|------|
| **M3 P0** | 交付批次(项目/可选合同、批次号、计划日、备注);交付清单行(描述、数量、可选合同行关联);批次状态 **PENDING → DELIVERED / CANCELLED** 及完成时间等侧写。对应产品:[M3-F01F05 P0](../../chuangfei-platform-product-modules.md#4-m3-交付管理)。 |
| **M4 P0** | SN 台账:全局唯一 `sn_code`**`project_id` 与/或 `contract_line_id` 绑定路径**;生命周期状态子集;激活备注/手工回写字段。对应产品:[M4-F01F05 P0](../../chuangfei-platform-product-modules.md#5-m4-授权与许可运营)。 |
| **M10-F01** | 交付批次、交付行、SN 的关键变更与状态迁移写入审计(与 I3 合同审计模式一致;实体类型见 §4)。 |
| **跨轨口径** | [I4 末同步点](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐):**SN 绑定与「孤儿 SN」规则**文档化并三轨对齐;**交付门禁(M3-F07)与「孤儿 SN」强校验(M4-F02)** 在 **M11-F20 系统参数** 中预留为 **未来可配置项**(I4 可实现默认策略 + 配置占位,**不阻塞** I4 闭环)。 |
### 1.2 I3 **留给上游的契约**(I4 只消费,不重复建设)
- **合同 / 合同行**`project_id``contract_id`、行项主键;合同状态机已在 I3 冻结。交付行上的 `contract_line_id` 必须解析到合法合同行及其所属项目。
- **客户 / 项目**:批次必填 `project_id`;可选 `contract_id` 须属于同一项目。
### 1.3 I5 **明确不纳入 I4**(避免范围蔓延)
- **M5 Callback Inbox**、**M6 集成配置** 的持久化与页面(I5 起)。
- Webhook **生产级** 投递、幂等落库与平台 Inbox 全链路 E2E。
- **设备(M7)**、**比特控制台摘要链接(M4-F06)** 等可后续挂接;I4 仅保证 SN 主数据与绑定字段可关联到合同行/项目。
---
## 2. 数据模型锚点(与迁移一致)
表与字段以 `services/delivery-platform-api/src/main/resources/db/migration/V4__delivery_batch_and_license_sn.sql` 为准:
- **`platform_delivery_batch`**`project_id`(必填)、`contract_id`(可选)、`batch_code`(唯一)、`planned_delivery_date``status`(默认 `PENDING`)、`finished_at``remarks`
- **`platform_delivery_line`**:归属 `batch_id``description``quantity``contract_line_id`(可选),`sort_order`
- **`platform_license_sn`**`sn_code`(全局唯一)、`project_id` / `contract_line_id`(均可空于 DB 层,**业务校验见 §4**)、`status`(默认 `REGISTERED`)、`activation_remark`
### 2.1 状态枚举(API JSON 使用枚举名字符串)
**交付批次** `DeliveryBatchStatus`
| 值 | 说明 |
|----|------|
| `PENDING` | 未交付(默认) |
| `DELIVERED` | 已交付 |
| `CANCELLED` | 已取消 |
**许可 SN** `LicenseSnStatus`P0 子集,与代码枚举一致):
| 值 | 说明 |
|----|------|
| `REGISTERED` | 已登记 |
| `ISSUED` | 已发放 |
| `ACTIVATED` | 已激活 |
| `SUSPENDED` | 已冻结 |
| `REVOKED` | 已回收 |
非法状态迁移返回 **409**,错误码建议与合同类似:`DELIVERY_BATCH_ILLEGAL_STATUS``LICENSE_SN_ILLEGAL_STATUS`(具体以 OpenAPI 与实现为准)。
---
## 3. REST API 提案(前缀 `/api/v1`
与现有 Controller 风格一致:**`@RequestMapping("/api/v1/...")`**。下列路径为 I4 计划形态;JSON 字段名与当前 DTO **camelCase** 对齐(`projectId``contractId``batchCode` 等)。
### 3.1 交付批次 `delivery-batches`
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/delivery-batches` | 分页列表;查询参数建议:`projectId``contractId``status``keyword`(批次号)、`page``size`。 |
| `POST` | `/api/v1/delivery-batches` | 创建批次(体见下);**不含**行时可后续用行接口追加。 |
| `GET` | `/api/v1/delivery-batches/{id}` | 详情;可通过 `?includeLines=true` 或默认嵌套返回 `lines`(与 `DeliveryBatchResponse` 一致)。 |
| `PUT` | `/api/v1/delivery-batches/{id}` | 更新计划交付日、备注等非状态字段(`DeliveryBatchUpdateRequest`)。 |
| `PATCH` | `/api/v1/delivery-batches/{id}/status` | **仅**变更状态:`PENDING``DELIVERED``CANCELLED`;服务端可在此写入 `finishedAt`(如 `DELIVERED`)。 |
**嵌套 — 交付行 `lines`**
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/delivery-batches/{batchId}/lines` | 清单列表。 |
| `POST` | `/api/v1/delivery-batches/{batchId}/lines` | 新增一行。 |
| `PUT` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 更新行。 |
| `DELETE` | `/api/v1/delivery-batches/{batchId}/lines/{lineId}` | 删除行。 |
**创建批次请求体示例**
```json
{
"projectId": 1001,
"contractId": 2002,
"batchCode": "DLV-2026-0001",
"plannedDeliveryDate": "2026-04-15",
"remarks": "首批现场交付"
}
```
**更新批次请求体示例**`PUT /api/v1/delivery-batches/{id}`
```json
{
"plannedDeliveryDate": "2026-04-20",
"remarks": "延期一周"
}
```
**状态 PATCH 示例**`PATCH /api/v1/delivery-batches/{id}/status`
```json
{
"status": "DELIVERED"
}
```
**交付行写入示例**`POST` / `PUT` body`DeliveryLineRequest`
```json
{
"sortOrder": 1,
"description": "AI 推理节点 × 生产环境",
"quantity": 2,
"contractLineId": 3003
}
```
**详情响应片段**`DeliveryBatchResponse`,含行)
```json
{
"id": 1,
"projectId": 1001,
"contractId": 2002,
"batchCode": "DLV-2026-0001",
"plannedDeliveryDate": "2026-04-15",
"status": "PENDING",
"finishedAt": null,
"remarks": "首批现场交付",
"createdAt": "2026-04-06T08:00:00Z",
"updatedAt": "2026-04-06T08:00:00Z",
"lines": [
{
"id": 10,
"batchId": 1,
"sortOrder": 1,
"description": "AI 推理节点 × 生产环境",
"quantity": 2,
"contractLineId": 3003,
"createdAt": "2026-04-06T08:05:00Z",
"updatedAt": "2026-04-06T08:05:00Z"
}
]
}
```
### 3.2 许可 SN `license-sns`
| 方法 | 路径 | 说明 |
|------|------|------|
| `GET` | `/api/v1/license-sns` | 分页列表;建议查询:`projectId``contractLineId``status``snCode`(精确或前缀按产品定)。 |
| `POST` | `/api/v1/license-sns` | 创建 SN`LicenseSnCreateRequest`);须满足 §4 绑定规则。 |
| `GET` | `/api/v1/license-sns/{id}` | 详情。 |
| `PUT` | `/api/v1/license-sns/{id}` | **全量/部分更新绑定字段**`projectId``contractLineId``activationRemark``LicenseSnUpdateRequest`);用于纠正绑定或手工回写备注。 |
| `PATCH` | `/api/v1/license-sns/{id}/status` | 变更 `LicenseSnStatus``LicenseSnStatusPatchRequest`);须校验合法迁移。 |
**创建 SN 请求体示例**
```json
{
"snCode": "SN-CRAFT-8F3A-0001",
"projectId": 1001,
"contractLineId": 3003,
"activationRemark": null
}
```
**更新绑定 / 备注示例**`PUT`
```json
{
"projectId": 1001,
"contractLineId": 3003,
"activationRemark": "客户现场激活成功,凭证号 xxx"
}
```
**状态 PATCH 示例**
```json
{
"status": "ACTIVATED"
}
```
**详情响应示例**`LicenseSnResponse`
```json
{
"id": 501,
"snCode": "SN-CRAFT-8F3A-0001",
"projectId": 1001,
"contractLineId": 3003,
"status": "ACTIVATED",
"activationRemark": "客户现场激活成功,凭证号 xxx",
"createdAt": "2026-04-06T09:00:00Z",
"updatedAt": "2026-04-06T10:00:00Z"
}
```
---
## 4. 校验规则与审计
### 4.1 交付批次
- **`projectId`**:必填;项目须存在。
- **`contractId`**:可选;若提供,合同须存在且 **`contract.project_id == batch.project_id`**。
- **`batchCode`**:全平台唯一(与表 `uq_platform_delivery_batch_code` 一致);冲突 **409**
- **状态**:仅允许自 `PENDING` 转至 `DELIVERED``CANCELLED``DELIVERED`/`CANCELLED` 视为终态,**禁止**再次变更(除非产品后续另定「重开」流程,不在 I4 P0)。
### 4.2 交付行
- **`contractLineId`**:可选;若提供,合同行须存在,且其所属合同的 **`project_id` 须与父批次 `project_id` 一致**(从而与批次可选 `contract_id` 兼容:若批次已指定合同,可额外校验行所属合同与批次合同一致,建议 **强一致**:行上合同行必须属于 `batch.contract_id``contract_id` 非空时)。
- **`quantity`**> 0(与 `DeliveryLineRequest``@DecimalMin` 一致)。
### 4.3 许可 SN
- **`snCode`**:必填;**全局唯一**;冲突 **409**
- **绑定路径****至少具备 `projectId``contractLineId` 之一**(可同时具备)。
- 若仅提供 **`contractLineId`**:服务端**派生** `project_id` = 该合同行所属合同的 `project_id`,并持久化(便于列表按项目过滤)。
- 若同时提供两者:须校验 **`contractLine` 派生出的 `project_id` 与请求 `projectId` 一致**,否则 **400**
- **孤儿 SN(与 I4 末同步对齐)**:
- **产品理想态(M4-F02)**:禁止无项目且无合同行路径的「裸 SN」。
- **M11-F20P1**:「孤儿 SN」**强校验**开关、**交付门禁**(M3-F07,例如仅已交付范围可发放/绑定)作为**系统参数**在架构上预留;I4 建议 **默认策略**:创建/更新时 **拒绝** 零绑定(与 P0 一致);若需「先录入后绑定」,可通过 **配置** 降级为 **警告 + 允许保存**(实现可放在应用服务层读取参数表,**表结构可 Mid 再做**,I4 先在文档与 OpenAPI `description` 中固定语义)。
- **状态迁移**:按 §2.1 枚举定义允许边(细表可在实现中维护;**禁止**随意跳转到任意状态)。
### 4.4 审计(M10-F01
沿用 I3 模式:**实体类型 + 动作 + 旧值/新值摘要 + 操作者 + 时间**。常量已存在于:
- `AuditEntityTypes.DELIVERY_BATCH``AuditEntityTypes.LICENSE_SN`
- `AuditActions``DELIVERY_BATCH_CREATED` / `UPDATED` / `STATUS_CHANGED``DELIVERY_LINE_ADDED` / `UPDATED` / `DELETED``LICENSE_SN_CREATED` / `UPDATED` / `STATUS_CHANGED`
若持久化审计行需扩展子类型或 payload 结构,**保持与合同审计同一表结构**,仅扩展 `entity_type` / `action` 枚举值;**无需**为 I4 另起实体类型常量,除非后续拆分「交付行」为独立可检索实体(当前可用 `DELIVERY_LINE_*` 动作挂 `entity_id` = line id`batch_id` 放上下文 JSON)。
---
## 5. 前端路由(Vue 3,布局子路由)
与 [轨道 B — I4](../tracks/02-frontend-platform-ui.md) 一致,路径挂在 **AppLayout** 之下(懒加载、`meta.title` / 权限码略)。
| 路由 | 页面职责 |
|------|----------|
| `/deliveries` | 交付批次列表、筛选、跳转新建/详情。 |
| `/deliveries/new` | 新建批次(项目/可选合同、批次号、计划日、备注);可内嵌或分步添加行。 |
| `/deliveries/:id` | 批次详情:行清单 CRUD;**状态按钮** 调用 `PATCH .../status`PENDING → DELIVERED/CANCELLED)。 |
| `/licenses/sn` | SN 台账列表;**孤儿 SN** 列表筛选或醒目标记(与后端配置/字段一致)。 |
| `/licenses/sn/new` | 新建 SN(录入 `snCode`、绑定项目/合同行)。 |
| `/licenses/sn/:id` | SN 详情:**PUT** 调整绑定与 `activationRemark`;**PATCH** 调整状态;展示简要时间线(可仅读审计或本地状态历史 Mid 增强)。 |
**契约顺序提醒**[轨道 B §4](../tracks/02-frontend-platform-ui.md)):Auth → Customer/Project → Contract → **Delivery/SN** → CallbackI4 页面依赖 I2/I3 主数据与合同行选择器。
---
## 6. I4 末同步点 Checklist(后端 + 前端 + SDK 文档)
以下为 [并行索引 I4 末](../PARALLEL_ITERATION_INDEX.md#3-跨轨同步点必须对齐) 的落地核对项。
### 6.1 后端(platform-api
- [ ] Flyway V4 表与索引已在各环境应用;DTO 与 OpenAPI 字段一致。
- [ ] §3 路径已实现或通过兼容别名暴露;错误码与 409 语义与合同模块一致。
- [ ] §4.1~§4.3 校验全覆盖(含合同行与项目一致性、SN 全局唯一、绑定派生)。
- [ ] **孤儿 SN**:默认策略与(可选)M11-F20 配置占位行为**文档化并在代码注释或配置类中可定位**。
- [ ] **交付门禁(M3-F07**:与 SN 创建/绑定相关的规则在代码中**可插拔**或明确「I4 硬编码默认 + I5+ 读配置」的 TODO 与 Owner。
- [ ] 审计:`DELIVERY_BATCH` / `LICENSE_SN` / 交付行动作写入 M10-F01 存储。
- [ ] `contracts/openapi/delivery-platform-api.json` 更新并通过 `OpenApiContractSnapshotTest`
### 6.2 前端(delivery-platform-ui
- [ ] §5 路由与菜单可达;RBAC 权限码与后端对齐。
- [ ] 交付详情状态操作仅展示合法迁移;错误态与 409 提示一致。
- [ ] SN 新建/编辑:合同行选择器与项目联动;**孤儿**场景 UI 与后端策略一致(禁止或警告)。
- [ ] E2E P0`交付 → SN 录入/绑定 → 状态/备注回写`(与轨道 B I4 DoD 一致)。
### 6.3 客户端 SDK 工作区(本仓库)
- [ ] OpenAPI 快照与 [contracts/README.md](../../contracts/README.md) 说明含 Delivery/SN 标签。
- [ ] [tracks/03-client-sdk.md](../tracks/03-client-sdk.md) 或等价文档中 **BP-10 与平台对象口径** 补充:交付批次、SN 在集成叙事中的位置(**不实现**平台 REST 客户端亦可,但**文档**须与 I4 契约一致)。
- [ ] **M11-F20**:在 SDK/集成文档中标注为 **后续配置项**(门禁、孤儿强校验),避免集成方误假设 I4 已暴露该 HTTP API。
---
## 7. 修订记录
| 日期 | 说明 |
|------|------|
| 2026-04-06 | 初版:I4 范围与边界、REST 提案、校验与审计、前端路由、I4 末三轨 checklist。 |