Files
huangping 5b50bf0fd8 docs(i3): add I3 design for M2 contracts and M10-F01 audit
Document REST shape, state machine, audit query, and Webhook DTO v0.1
alignment for iteration I3 (parallel tracks + product M2 P0).

Made-with: Cursor
2026-04-06 21:29:17 +08:00

398 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 迭代 I3 设计说明 — M2 合同与行项 P0、M10-F01 审计、Webhook 事件 DTO v0.1
> **迭代定位**:与 [并行迭代索引](../PARALLEL_ITERATION_INDEX.md) 中 **I3** 一致 — 平台后端 **M2 合同 + 行项**、**M10-F01 关键字段变更日志**Webhook 侧 **事件 DTO v0.1** 与平台主键/枚举对齐,便于 I5 Callback 关联。
> **分支**`develop`(本仓库为契约与 SDK 工作区;平台运行时实现可在 `delivery-platform` 仓库,路径风格须与本仓 OpenAPI 一致)。
---
## 1. 上下文与引用文档
| 文档 | 路径 | 本迭代取用要点 |
| ------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| 并行迭代索引 | [docs/engineering/PARALLEL_ITERATION_INDEX.md](../PARALLEL_ITERATION_INDEX.md) | I3 范围:M2 + M10-F01**I3 末**同步「合同状态枚举与非法迁移码」「M10-F01 字段」。 |
| 轨道 A(后端 + Webhook | [docs/engineering/tracks/01-backend-platform-webhook.md](../tracks/01-backend-platform-webhook.md) | I3:合同+行、状态机;审计;Webhook **事件 DTO 规范化**、与平台枚举对齐;契约:**合同/行 id** 供 Callback 关联。 |
| 产品模块与功能点 | [docs/chuangfei-platform-product-modules.md](../../chuangfei-platform-product-modules.md) | **M2-F01F04P0**:登记编辑、状态机、标的摘要、行项;**M10-F01(P0)**:关键字段变更日志(旧值/新值/人/时间)。 |
**现有 API 路径约定**(须保持一致):`services/delivery-platform-api` 中 Controller 使用 `**@RequestMapping("/api/v1/...")`**,例如 `/api/v1/customers``/api/v1/projects`。合同相关接口统一前缀 `**/api/v1/contracts`**。
**OpenAPI 单一事实来源**:契约快照路径为仓库根下 `[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`;新增/变更接口须更新该快照,并与 `OpenApiContractSnapshotTest` 对齐。
---
## 2. 领域模型:合同(Contract
### 2.1 实体职责
合同是「卖什么」的权威来源之一,关联 M1 客户与项目;行项为履约/授权上游锚点(与后续 M3/M4 衔接)。
### 2.2 字段(P0
| 字段 | 类型 | 约束 | 说明 |
| ------------------------- | --------------- | ------- | ------------------------------------------------------------------------ |
| `id` | int64 | 主键 | 与现有 `customers`/`projects` 一致用雪花或序列,API 中为 string 或 number 以 OpenAPI 为准。 |
| `contractNumber` | string | 必填,业务唯一 | 合同编号;唯一索引。 |
| `customerId` | int64 | 必填,FK | 指向客户。 |
| `projectId` | int64 | 必填,FK | 指向项目;须属于同一 `customerId`(服务端校验)。 |
| `signedAt` | date (ISO-8601) | 必填 | 签订日。 |
| `effectiveAt` | date | 必填 | 生效日。 |
| `endAt` | date | 可选 | 结束/到期日;与「终止」语义可并存(终止优先于到期展示逻辑由前端/报表约定)。 |
| `status` | enum | 必填 | 见 §3;**API 与 JSON 仅使用英文枚举名**。 |
| `createdAt` / `updatedAt` | timestamp | 系统字段 | 审计展示可与 M10-F01 互补。 |
### 2.3 状态枚举(对齐 BPM 语义)
| BPM 语义(展示/字典) | API 枚举值 `ContractStatus` |
| ------------- | ------------------------ |
| 草稿 | `DRAFT` |
| 待生效 | `PENDING_EFFECTIVE` |
| 生效 | `EFFECTIVE` |
| 变更中 | `CHANGING` |
| 终止 | `TERMINATED` |
字典表可增加 `dict_type = CONTRACT_STATUS``code` 与上表一致,`label_zh` 为左列中文。
### 2.4 编辑规则(与状态机联动)
- **仅 `DRAFT` 状态**允许对**合同头字段**(§2.2 中除 `id``status``createdAt``updatedAt` 外,是否含 `contractNumber` 由产品确认;P0 建议 **DRAFT 下编号可改**,一旦进入 `PENDING_EFFECTIVE` 则编号只读)及**行项**做增删改。
-`DRAFT` 下对合同头或行项的 `PUT`/`POST`/`DELETE`**409 Conflict**,错误码见 §4.3。
- 状态变更**仅允许**通过 `**POST .../transition`**(§5.4),禁止在普通 `PUT` 请求体中直接改 `status`(若传入与当前相同可忽略或 400,建议 **忽略** 幂等)。
---
## 3. 领域模型:合同行(Contract Line
### 3.1 字段(P0
| 字段 | 类型 | 约束 | 说明 |
| ------------- | --------------- | ----- | ------------------------------------------------ |
| `id` | int64 | 主键 | |
| `contractId` | int64 | 必填,FK | |
| `lineNo` | int32 | 必填 | 行号,从 1 递增;**同一合同内唯一**;用于展示与排序。 |
| `skuCode` | string | 条件 | `**skuCode``productName` 至少填一个**(另一个可为 null)。 |
| `productName` | string | 条件 | 无 SKU 时的产品/包名称。 |
| `quantity` | decimal 或 int64 | 必填 | 数量;小数与否由产品线约定,P0 建议 `number` JSON。 |
| `unitPrice` | decimal | 可选 | 单价;敏感字段可按角色脱敏(见 §9)。 |
| `termNotes` | string | 可选 | 期限/席位/交付与授权口径等说明(与 M2-F03 摘要同源数据)。 |
### 3.2 排序
列表接口默认按 `lineNo` 升序;`lineNo` 可由客户端指定,冲突时 **409**,错误码建议 `CONTRACT_LINE_NO_CONFLICT`
---
## 4. 状态机
### 4.1 允许迁移(P0
以下「当前状态 → 目标状态」为 **允许**;未列出的单向迁移视为 **禁止**
| 当前状态 | 允许的目标状态 |
| ------------------- | --------------------------------------- |
| `DRAFT` | `PENDING_EFFECTIVE``TERMINATED` |
| `PENDING_EFFECTIVE` | `EFFECTIVE``DRAFT`(撤回至草稿)、`TERMINATED` |
| `EFFECTIVE` | `CHANGING``TERMINATED` |
| `CHANGING` | `EFFECTIVE``TERMINATED` |
| `TERMINATED` | (终态,不允许任何迁出) |
**说明**
- `PENDING_EFFECTIVE``DRAFT`:用于「待生效前撤回修改」;撤回后恢复 §2.4 头行可编辑。
- `CHANGING``EFFECTIVE`:变更完成、回到生效。
- P0 **不**实现变更子版本表(M2-F07 为 P1)。`**CHANGING` 下合同头与行项与普通非草稿状态相同:禁止 `PUT`/`POST`/`DELETE` 行与头**,仅允许 `POST .../transition`(例如转至 `EFFECTIVE``TERMINATED`);若业务需要「变更中改行」,留待后续迭代专用变更 API。
### 4.2 非法迁移响应
- HTTP `**409 Conflict`**。
- 响应体(与平台统一错误结构对齐;若尚无 RFC 7807,则用 JSON)示例:
```json
{
"code": "CONTRACT_ILLEGAL_STATUS_TRANSITION",
"message": "不允许从当前状态转换到目标状态",
"currentStatus": "EFFECTIVE",
"targetStatus": "DRAFT"
}
```
- `code` 固定 `**CONTRACT_ILLEGAL_STATUS_TRANSITION**`,便于前端与 SDK 分支处理。
- 日志与 M10:建议记一条 `audit_log``action = STATUS_TRANSITION_DENIED`(见 §6)。
### 4.3 非草稿编辑冲突
在不允许编辑的状态下修改头或行:
- HTTP `**409 Conflict**`
- `code`: `**CONTRACT_NOT_EDITABLE_IN_STATUS**`(或细分头/行码,P0 可合并为一个码)。
```json
{
"code": "CONTRACT_NOT_EDITABLE_IN_STATUS",
"message": "仅草稿状态可编辑合同及行项",
"status": "EFFECTIVE"
}
```
---
## 5. REST API 设计
**前缀**`/api/v1`(与 [CustomerController](../../../services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/customer/CustomerController.java) 一致)。
**认证**:与现有 `/api/v1/customers` 相同(JWT 等);未登录 **401**
**分页**:列表接口与 customers 对齐:`page`(从 0 默认)、`size`(默认 20,最大 200)。
### 5.1 合同
| 方法 | 路径 | 说明 |
| ------ | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `GET` | `/api/v1/contracts` | 分页列表;查询参数:`page``size``customerId?``projectId?``status?``keyword?`(匹配 `contractNumber`)。 |
| `POST` | `/api/v1/contracts` | 创建;**初始状态必须为 `DRAFT`**(请求体可不传 `status`,服务端默认 `DRAFT`;若传其它值 **400**)。 |
| `GET` | `/api/v1/contracts/{contractId}` | 详情;含行项可内嵌或仅用行接口拉取(P0 建议详情 **内嵌 `lines`** 减少往返)。 |
| `PUT` | `/api/v1/contracts/{contractId}` | 更新头字段;**仅 `DRAFT`****不得**携带 `status` 变更(忽略或 400,建议 **400** `CONTRACT_STATUS_USE_TRANSITION_ENDPOINT`)。 |
**创建请求示例**
```json
{
"contractNumber": "HT-2026-0001",
"customerId": 1001,
"projectId": 2002,
"signedAt": "2026-04-01",
"effectiveAt": "2026-04-15",
"endAt": "2027-04-14"
}
```
**详情响应示例(内嵌行)**
```json
{
"id": 3001,
"contractNumber": "HT-2026-0001",
"customerId": 1001,
"projectId": 2002,
"signedAt": "2026-04-01",
"effectiveAt": "2026-04-15",
"endAt": "2027-04-14",
"status": "DRAFT",
"lines": [
{
"id": 4001,
"lineNo": 1,
"skuCode": "SKU-PRO-01",
"productName": null,
"quantity": 10,
"unitPrice": 1999.00,
"termNotes": "1 年订阅,100 席位"
}
],
"createdAt": "2026-04-06T10:00:00Z",
"updatedAt": "2026-04-06T10:00:00Z"
}
```
### 5.2 合同行 CRUD
| 方法 | 路径 | 说明 |
| -------- | ----------------------------------------------- | ---------------------------- |
| `GET` | `/api/v1/contracts/{contractId}/lines` | 行列表(可选,若详情已内嵌则可与产品取舍)。 |
| `POST` | `/api/v1/contracts/{contractId}/lines` | 新增行;**仅合同为 `DRAFT`**。 |
| `GET` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 单行。 |
| `PUT` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 更新;**仅 `DRAFT`**。 |
| `DELETE` | `/api/v1/contracts/{contractId}/lines/{lineId}` | 删除;**仅 `DRAFT`**;成功 **204**。 |
**创建/更新行请求体示例**
```json
{
"lineNo": 2,
"skuCode": null,
"productName": "企业旗舰包",
"quantity": 5,
"unitPrice": null,
"termNotes": "按项目交付"
}
```
### 5.3 状态迁移
| 方法 | 路径 | 说明 |
| ------ | ------------------------------------------- | ----------------------------------------------------------------------------- |
| `POST` | `/api/v1/contracts/{contractId}/transition` | 请求体指定目标状态;校验 §4.1;成功返回更新后的合同 DTO(或 204 + LocationP0 建议 **200 + 完整合同 JSON**)。 |
**请求体**
```json
{
"targetStatus": "PENDING_EFFECTIVE"
}
```
**成功**`200`body 为合同资源(含 `lines` 若详情惯例如此)。
---
## 6. M10-F01`audit_log` 表与读 API
### 6.1 表设计(建议名 `audit_log`
| 列名 | 类型 | 说明 |
| --------------- | ------------ | --------------------------------------------------------------------------------------------------------------- |
| `id` | bigserial PK | |
| `entity_type` | varchar(32) | 枚举:`**CUSTOMER`**、`**CONTRACT`**、`**CONTRACT_LINE**`(与产品 M10-F01「客户、合同、SN…」对齐;I3 落地三类,SN 后续迭代加 `LICENSE_SN` 等)。 |
| `entity_id` | int8 | 业务主键,与 `entity_type` 对应实体 id。 |
| `action` | varchar(64) | 如:`CREATE``UPDATE``DELETE``STATUS_TRANSITION``STATUS_TRANSITION_DENIED`。 |
| `field_name` | varchar(128) | 可选;字段级变更时记录英文名,如 `effectiveAt``status`。 |
| `old_value` | text | JSON 字符串;无则 NULL。 |
| `new_value` | text | JSON 字符串;无则 NULL。 |
| `actor_user_id` | int8 | 操作人用户 id。 |
| `created_at` | timestamptz | 不可改。 |
**索引**
- `(entity_type, entity_id, created_at DESC)` — 按对象拉时间线。
- 可选:`(actor_user_id, created_at DESC)` — 按人审计(M10-F02 预备)。
**写入时机(I3 最小集)**
- 合同:创建、头字段更新(`DRAFT`)、每次成功 `transition``field_name=status`,old/new 为枚举字符串)。
- 合同行:创建、更新、删除(`entity_type=CONTRACT_LINE``entity_id=lineId`)。
- 拒绝的非法迁移:可选记 `STATUS_TRANSITION_DENIED``new_value` 可存目标状态 JSON。
### 6.2 读 API
| 方法 | 路径 | 说明 |
| ----- | --------------- | ------- |
| `GET` | `/api/v1/audit` | 分页审计列表。 |
**查询参数(组合过滤)**
- `entityType` + `entityId`:精确到单一实体(如某合同、某行、某客户)。
- `**contractId`**:便捷范围 — 返回 `entity_type IN ('CONTRACT','CONTRACT_LINE')` 且(合同 id = contractId **或** 行所属 contractId = contractId)的记录;实现上可用 SQL `UNION` 或冗余 `contract_id` 列(**推荐冗余 `contract_id` 可空** 于 `audit_log` 以简化查询:合同与行写入时均填 `contract_id`,客户实体则只填 `entity_`*)。
**冗余列(可选但强烈推荐)**
| 列名 | 说明 |
| ------------- | ---------------------------------------------------------------------- |
| `contract_id` | 可空;`CONTRACT` 时等于 `entity_id``CONTRACT_LINE` 时为父合同 id`CUSTOMER` 可为空。 |
**响应项示例**
```json
{
"id": 900001,
"entityType": "CONTRACT",
"entityId": 3001,
"contractId": 3001,
"action": "STATUS_TRANSITION",
"fieldName": "status",
"oldValue": "\"DRAFT\"",
"newValue": "\"PENDING_EFFECTIVE\"",
"actorUserId": 42,
"createdAt": "2026-04-06T12:00:00Z"
}
```
说明:`old_value`/`new_value`**JSON text**(字符串加引号、对象则序列化),解析由前端或工具完成。
---
## 7. Webhook 事件 DTO v0.1Callback 关联预备)
**目标**:与平台合同/行主键对齐,便于 I5 Inbox 关联与幂等;**不**在本迭代要求完整比特 payload,仅 **最小信封**
### 7.1 建议信封字段(v0.1
| 字段 | 类型 | 必填 | 说明 |
| --------------- | ----------------- | --- | ----------------------------------------------- |
| `schemaVersion` | string | 是 | 固定 `**0.1`**(后续 `0.2``1.0` 递增)。 |
| `contractId` | int64 | 条件 | 与平台合同 id 一致;若事件仅到行级,仍建议带父 `contractId`。 |
| `lineIds` | array of int64 | 否 | 涉及的合同行 id 列表;无行级时可 `[]` 或省略。 |
| `eventType` | string | 否 | 预留,如 `contract.status.changed`(与 M5 字典统一可在 I5)。 |
| `occurredAt` | string (ISO-8601) | 否 | 事件发生时间。 |
**示例**
```json
{
"schemaVersion": "0.1",
"contractId": 3001,
"lineIds": [4001, 4002],
"eventType": "contract.status.changed",
"occurredAt": "2026-04-06T12:00:00Z"
}
```
**版本策略**Webhook 与平台共用 `schemaVersion` 语义;**I3 归档** JSON Schema 或本仓库 `contracts/` 下示例文件(与 [轨道 A 文档 §3](../tracks/01-backend-platform-webhook.md) 的 `schemaVersion` / `X-Event-Schema-Version` 一致)。
---
## 8. 安全与 RBAC(对齐产品粗粒度矩阵)
产品文档 [§13.3](../../chuangfei-platform-product-modules.md) 模块矩阵中 **M2 合同**、**M10 审计** 与角色关系如下(**R** 查看,**W** 新建编辑,**X** 导出)。用户提到的 `**DEVELOPER`** 在产品预置角色中为 `**DEV_SUPPORT`(研发/集成支撑)** — 实施时角色码二选一须与 IAM 统一,下文按矩阵描述 `**DEV_SUPPORT`**,若代码命名为 `DEVELOPER` 则与之对齐。
### 8.1 `SYS_ADMIN`
- **M2 合同**:矩阵为 **M / RWDX** — 含模块管理语义;企业可配置是否开放业务写。建议:**生产默认** `SYS_ADMIN` **仅 M11 管理面**,业务合同与 `**SALES`/`ORDER_SUPPORT`** 同权或按企业策略单独开 `contract:`* 权限码。
- **M10 审计**:矩阵为 **M**(管理面);若启用审计检索,建议单独挂 `audit:search`
### 8.2 `DEV_SUPPORT`(研发支撑 / 可与 `DEVELOPER` 对齐)
- **M2 合同**:**R**(只读)— 无合同创建/编辑/删除/导出,除非临时提权。
- **M10 审计**:**R** — 可检索审计(与矩阵「研发支撑」列一致)。
### 8.3 I3 API 权限码映射(建议)
| 接口 | 建议权限码 | 典型角色 |
| ---------------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
| 合同与行 CRUD、`transition` | `contract:order:rw` | `SALES``ORDER_SUPPORT``SYS_ADMIN`(若开放业务写) |
| 合同只读列表/详情 | `contract:order:rw` 或细拆 `contract:order:read`Mid | 上表 + `DELIVERY``LICENSE_OPS``FINANCE_VIEW``EXEC_VIEW` 等只读列 |
| `GET /api/v1/audit` | `audit:search` | `COMPLIANCE``DEV_SUPPORT``FINANCE_VIEW`(导出另加 `audit:export` P1 |
**说明**:粗粒度阶段可将「读合同」与「写合同」合并为同一码,但 `**transition`** 必须与写权限同级或单独 `contract:order:transition`,避免只读角色误调;P0 可与 `contract:order:rw` 绑定。
---
## 9. OpenAPI 与交付物
- **快照路径**`[contracts/openapi/delivery-platform-api.json](../../../contracts/openapi/delivery-platform-api.json)`
- I3 完成定义:**上述路径、枚举、主要 DTO** 均须出现在该 OpenAPI 文件中,并与 `services/delivery-platform-api` 运行时 `/v3/api-docs``**OpenApiContractSnapshotTest`** 校验一致。
---
## 10. 修订记录
| 日期 | 说明 |
| ---------- | ---------------------------------------------------------- |
| 2026-04-06 | 初版:I3 合同/行项、状态机、REST、M10-F01、Webhook v0.1、RBAC、OpenAPI 引用。 |