Files
craftlabs-authorization-sdk/docs/engineering/iterations/I3_DESIGN.md
T
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

21 KiB
Raw Blame History

迭代 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-F01I3 末同步「合同状态枚举与非法迁移码」「M10-F01 字段」。
轨道 A(后端 + Webhook docs/engineering/tracks/01-backend-platform-webhook.md I3:合同+行、状态机;审计;Webhook 事件 DTO 规范化、与平台枚举对齐;契约:合同/行 id 供 Callback 关联。
产品模块与功能点 docs/chuangfei-platform-product-modules.md M2-F01F04P0:登记编辑、状态机、标的摘要、行项;M10-F01P0:关键字段变更日志(旧值/新值/人/时间)。

现有 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 必填 见 §3API 与 JSON 仅使用英文枚举名
createdAt / updatedAt timestamp 系统字段 审计展示可与 M10-F01 互补。

2.3 状态枚举(对齐 BPM 语义)

BPM 语义(展示/字典) API 枚举值 ContractStatus
草稿 DRAFT
待生效 PENDING_EFFECTIVE
生效 EFFECTIVE
变更中 CHANGING
终止 TERMINATED

字典表可增加 dict_type = CONTRACT_STATUScode 与上表一致,label_zh 为左列中文。

2.4 编辑规则(与状态机联动)

  • DRAFT 状态允许对合同头字段(§2.2 中除 idstatuscreatedAtupdatedAt 外,是否含 contractNumber 由产品确认;P0 建议 DRAFT 下编号可改,一旦进入 PENDING_EFFECTIVE 则编号只读)及行项做增删改。
  • DRAFT 下对合同头或行项的 PUT/POST/DELETE409 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 条件 **skuCodeproductName 至少填一个**(另一个可为 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_EFFECTIVETERMINATED
PENDING_EFFECTIVE EFFECTIVEDRAFT(撤回至草稿)、TERMINATED
EFFECTIVE CHANGINGTERMINATED
CHANGING EFFECTIVETERMINATED
TERMINATED (终态,不允许任何迁出)

说明

  • PENDING_EFFECTIVEDRAFT:用于「待生效前撤回修改」;撤回后恢复 §2.4 头行可编辑。
  • CHANGINGEFFECTIVE:变更完成、回到生效。
  • P0 实现变更子版本表(M2-F07 为 P1)。**CHANGING 下合同头与行项与普通非草稿状态相同:禁止 PUT/POST/DELETE 行与头**,仅允许 POST .../transition(例如转至 EFFECTIVETERMINATED);若业务需要「变更中改行」,留待后续迭代专用变更 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_logaction = 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 分页列表;查询参数:pagesizecustomerId?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 + LocationP0 建议 200 + 完整合同 JSON)。

请求体

{
  "targetStatus": "PENDING_EFFECTIVE"
}

成功200body 为合同资源(含 lines 若详情惯例如此)。


6. M10-F01audit_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) 如:CREATEUPDATEDELETESTATUS_TRANSITIONSTATUS_TRANSITION_DENIED
field_name varchar(128) 可选;字段级变更时记录英文名,如 effectiveAtstatus
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)、每次成功 transitionfield_name=statusold/new 为枚举字符串)。
  • 合同行:创建、更新、删除(entity_type=CONTRACT_LINEentity_id=lineId)。
  • 拒绝的非法迁移:可选记 STATUS_TRANSITION_DENIEDnew_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_idCONTRACT_LINE 时为父合同 idCUSTOMER 可为空。

响应项示例

{
  "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_valueJSON text(字符串加引号、对象则序列化),解析由前端或工具完成。


7. Webhook 事件 DTO v0.1Callback 关联预备)

目标:与平台合同/行主键对齐,便于 I5 Inbox 关联与幂等;在本迭代要求完整比特 payload,仅 最小信封

7.1 建议信封字段(v0.1

字段 类型 必填 说明
schemaVersion string 固定 **0.1**(后续 0.21.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 文档 §3schemaVersion / 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 SALESORDER_SUPPORTSYS_ADMIN(若开放业务写)
合同只读列表/详情 contract:order:rw 或细拆 contract:order:readMid 上表 + DELIVERYLICENSE_OPSFINANCE_VIEWEXEC_VIEW 等只读列
GET /api/v1/audit audit:search COMPLIANCEDEV_SUPPORTFINANCE_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 引用。