Document REST shape, state machine, audit query, and Webhook DTO v0.1 alignment for iteration I3 (parallel tracks + product M2 P0). Made-with: Cursor
21 KiB
迭代 I3 设计说明 — M2 合同与行项 P0、M10-F01 审计、Webhook 事件 DTO v0.1
迭代定位:与 并行迭代索引 中 I3 一致 — 平台后端 M2 合同 + 行项、M10-F01 关键字段变更日志;Webhook 侧 事件 DTO v0.1 与平台主键/枚举对齐,便于 I5 Callback 关联。
分支:develop(本仓库为契约与 SDK 工作区;平台运行时实现可在delivery-platform仓库,路径风格须与本仓 OpenAPI 一致)。
1. 上下文与引用文档
| 文档 | 路径 | 本迭代取用要点 |
|---|---|---|
| 并行迭代索引 | docs/engineering/PARALLEL_ITERATION_INDEX.md | I3 范围:M2 + M10-F01;I3 末同步「合同状态枚举与非法迁移码」「M10-F01 字段」。 |
| 轨道 A(后端 + Webhook) | docs/engineering/tracks/01-backend-platform-webhook.md | I3:合同+行、状态机;审计;Webhook 事件 DTO 规范化、与平台枚举对齐;契约:合同/行 id 供 Callback 关联。 |
| 产品模块与功能点 | docs/chuangfei-platform-product-modules.md | M2-F01~F04(P0):登记编辑、状态机、标的摘要、行项;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)示例:
{
"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 可合并为一个码)。
{
"code": "CONTRACT_NOT_EDITABLE_IN_STATUS",
"message": "仅草稿状态可编辑合同及行项",
"status": "EFFECTIVE"
}
5. REST API 设计
前缀:/api/v1(与 CustomerController 一致)。
认证:与现有 /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)。 |
创建请求示例:
{
"contractNumber": "HT-2026-0001",
"customerId": 1001,
"projectId": 2002,
"signedAt": "2026-04-01",
"effectiveAt": "2026-04-15",
"endAt": "2027-04-14"
}
详情响应示例(内嵌行):
{
"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。 |
创建/更新行请求体示例:
{
"lineNo": 2,
"skuCode": null,
"productName": "企业旗舰包",
"quantity": 5,
"unitPrice": null,
"termNotes": "按项目交付"
}
5.3 状态迁移
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/v1/contracts/{contractId}/transition |
请求体指定目标状态;校验 §4.1;成功返回更新后的合同 DTO(或 204 + Location,P0 建议 200 + 完整合同 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)的记录;实现上可用 SQLUNION或冗余contract_id列(推荐冗余contract_id可空 于audit_log以简化查询:合同与行写入时均填contract_id,客户实体则只填entity_*)。
冗余列(可选但强烈推荐):
| 列名 | 说明 |
|---|---|
contract_id |
可空;CONTRACT 时等于 entity_id;CONTRACT_LINE 时为父合同 id;CUSTOMER 可为空。 |
响应项示例:
{
"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.1(Callback 关联预备)
目标:与平台合同/行主键对齐,便于 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) | 否 | 事件发生时间。 |
示例:
{
"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 的 schemaVersion / X-Event-Schema-Version 一致)。
8. 安全与 RBAC(对齐产品粗粒度矩阵)
产品文档 §13.3 模块矩阵中 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 引用。 |