docs(08-05-product-interaction-consistency): 固化交互治理任务规划与规范
这个提交包含在:
@@ -0,0 +1,133 @@
|
||||
# 设计说明
|
||||
|
||||
## 1. 总体结构
|
||||
|
||||
合同任务分为四个同批兼容层:
|
||||
|
||||
1. Prisma 增量字段与关系。
|
||||
2. 合同详情/终止/历史 API 与事务审计。
|
||||
3. 合同列表、详情、新建/编辑和响应式动作 UI。
|
||||
4. 状态机、权限、迁移和端到端验证。
|
||||
|
||||
本任务不建设合同附件库。证据区只消费现有台账字段和 `ConfidentialDocumentRef` 的最小摘要。
|
||||
|
||||
## 2. 数据模型
|
||||
|
||||
在 `ContractRegistry` 增加:
|
||||
|
||||
```prisma
|
||||
terminated_at DateTime?
|
||||
terminated_reason String? @db.VarChar(1000)
|
||||
terminated_by String? @db.Uuid
|
||||
terminator Employee? @relation("ContractRegistryTerminator", fields: [terminated_by], references: [id], onDelete: Restrict)
|
||||
```
|
||||
|
||||
在 `Employee` 增加反向关系 `terminated_contract_registries`。
|
||||
|
||||
迁移只增加三个可空列、外键和必要索引,不回填历史行,不修改 `status` 列类型。`terminated_reason` 请求 trim 后长度 10–1000。
|
||||
|
||||
数据库暂不增加跨列 CHECK;服务层与测试强制:
|
||||
|
||||
```text
|
||||
status = terminated <=> terminated_at/reason/by 全部非空
|
||||
status != terminated <=> terminated_at/reason/by 全部为空
|
||||
```
|
||||
|
||||
`terminated` 是终态,本轮不允许恢复、续约或再归档,避免单一状态列丢失终止事实。
|
||||
|
||||
## 3. 状态值域与写入边界
|
||||
|
||||
- `CONTRACT_REGISTRY_PERSISTED_STATUS_VALUES` 增加 `terminated`,筛选和派生状态自动识别。
|
||||
- `resolveContractRegistryEffectiveStatus` 继续只对持久状态 `active` 派生 `expired`;terminated 天然不被覆盖。
|
||||
- 到期提醒继续硬过滤 `active`,无需特殊排除逻辑,但增加回归断言。
|
||||
- 不把通用 create/PATCH schema 直接开放为可写 `terminated`。新增一般写状态集合 `draft/active/archived`,终止只能走专用接口,防止绕过三字段不变量。
|
||||
- 通用 PATCH 对 `terminated` 和 `archived` 主记录执行状态锁:terminated 不可编辑;archived 普通字段不可覆盖。
|
||||
|
||||
## 4. API 合同
|
||||
|
||||
### 4.1 详情
|
||||
|
||||
`GET /api/contracts-registry/:id`
|
||||
|
||||
- 使用与列表相同的数据范围条件查询,未授权与不存在统一安全 404。
|
||||
- 返回合同摘要、owner、project、terminator、三个终止字段、最小涉密资料索引和白名单操作历史。
|
||||
- `ConfidentialDocumentRef` 不返回完整 `storage_path`、access rule raw JSON 或其他敏感原文;只返回 ID、标题、资料编号、敏感级别、存储类型、离线档案号、责任人和时间等必要摘要。
|
||||
- 历史从 `AuditLog` 按 `target_type=contract_registry + target_id` 查询,只映射允许展示的 action、actor、time 和安全摘要,不直出 raw `extra`。
|
||||
|
||||
### 4.2 终止
|
||||
|
||||
`POST /api/contracts-registry/:id/terminate`
|
||||
|
||||
请求:`{ reason: string }`,trim 后 10–1000 字。
|
||||
|
||||
- 仅当前合同管理权限可调用。
|
||||
- 只允许持久状态 `active`(包含页面派生 expired);draft 应删除/取消拟办,archived 与 terminated 拒绝。
|
||||
- 在一个 Prisma transaction 中锁定/读取当前行、校验状态、写 `status + terminated_*`、创建 `CONTRACT_REGISTRY_TERMINATED` 审计。
|
||||
- 审计创建失败时整个事务回滚;不调用现有会吞错的 best-effort helper。
|
||||
- 审计 extra 只记录合同编号、from/to 状态和必要原因摘要,不写合同正文或原始 payload。
|
||||
|
||||
### 4.3 归档、恢复与修正
|
||||
|
||||
- 现有归档/恢复 UI 迁入详情。服务端至少阻止 archived 普通 PATCH 和 terminated 的任何一般状态写入。
|
||||
- 归档/恢复属于状态变更,改为事务内 required audit;不得状态成功而审计失败。
|
||||
- 归档后修正使用追加式 correction 记录,不覆盖主合同普通字段。可复用 `AuditLog` 新 action 与白名单修正 payload;详情把它显示为追加历史。
|
||||
- 续约保持 active/expired → 新 draft + 原合同 archived,并继续事务内审计。
|
||||
|
||||
## 5. 前端信息架构
|
||||
|
||||
### 5.1 路由与页面
|
||||
|
||||
- 列表:`/contracts-registry`
|
||||
- 详情:`/contracts-registry/:contractId`
|
||||
- 新建可使用独立 `/contracts-registry/new` 或明确 Dialog;实施时优先选择复用现有表单且 dirty guard 最清晰的独立新建工作区。
|
||||
- 编辑在详情工作区展开或进入明确编辑态,不再使列表页面整体变形。
|
||||
|
||||
### 5.2 信息顺序
|
||||
|
||||
1. 状态与下一步。
|
||||
2. 业务摘要:编号、类型、相对方、金额、日期、负责人、敏感级别。
|
||||
3. 证据区:归档位置、离线档案号、涉密资料索引;显示“涉密资料请登记线下归档位置”。
|
||||
4. 变更/操作历史。
|
||||
5. 动作面板:PC 右侧,窄屏/移动底部安全区,复用低风险子任务的共享动作容器。
|
||||
|
||||
不渲染空附件模块,不在列表增加附件完整性列。版式分区保持可扩展,但不显示六类空占位。
|
||||
|
||||
## 6. 动作矩阵
|
||||
|
||||
| 状态 | 编辑 | 续约 | 归档 | 恢复 | 终止 | 修正追加 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| draft | 允许 | 禁止 | 按现有规则 | - | 禁止 | 禁止 |
|
||||
| active / expired | 允许 | 允许 | 允许 | - | 允许 | 禁止 |
|
||||
| archived | 禁止覆盖 | 禁止 | - | 允许 | 禁止 | 授权角色允许 |
|
||||
| terminated | 禁止 | 禁止 | 禁止 | 禁止 | 禁止 | 只读 |
|
||||
|
||||
终止面板始终显示至少 10 字要求;不足时按钮禁用且邻近显示原因。确认弹窗展示合同编号、相对方、金额以及“合同将标记已终止、编号不释放、普通编辑/续约不可用”的后果。
|
||||
|
||||
## 7. 跨模块与权限
|
||||
|
||||
- 本轮详情只稳定展示已有 project 关联并跳转项目详情。
|
||||
- 采购/付款没有现成合同外键或可靠查询时不显示空模块,不为页面完整性新增错误关联。
|
||||
- 合同审批和付款写动作不进入详情;未来只显示授权摘要与归属模块入口。
|
||||
- 所有详情数据由服务端范围查询裁剪,前端隐藏不替代权限。
|
||||
|
||||
## 8. 迁移、发布与回滚
|
||||
|
||||
- 本地:先 `prisma:validate`,生成 client,运行聚焦 route 测试和迁移状态只读检查;不得 reset 数据库。
|
||||
- migration 是向后兼容的可空列。部署新 schema 后、首次产生 terminated 行前,旧应用可暂时忽略新列;不主动 drop。
|
||||
- 一旦生产产生 terminated 行,旧应用不认识该状态且无法展示终止事实,应用回滚应以 roll-forward 或兼容 hotfix 为主,不能简单回到旧客户端/服务端。
|
||||
- drop 三列会保留审计但使 terminated 行与字段不变量失配,因此不作为自动回滚。任何生产 schema drop、状态翻译或数据修复都需另行批准。
|
||||
- 部署后显式运行 migration status,并验证 `/ready`、合同列表/详情、终止负向路径和权限;init 日志不能替代迁移状态证据。
|
||||
|
||||
## 9. 只读 Review
|
||||
|
||||
实施后 reviewer 只读检查:
|
||||
|
||||
- terminated 与三字段全有/全空不变量。
|
||||
- 通用 create/PATCH 无法绕过专用终止接口。
|
||||
- audit failure 能回滚状态写入。
|
||||
- expired/expiring/renew 行为未误含 terminated。
|
||||
- `terminated_by` Restrict 外键和 migration 兼容。
|
||||
- 详情 403/404、document refs 和 history 不泄露敏感字段。
|
||||
- archived 锁定与修正追加不覆盖主记录。
|
||||
|
||||
reviewer 不修改代码,发现问题由主会话修复后再复查。
|
||||
在新工单中引用
屏蔽一个用户