docs: add design for contract no date following sign date

Capture agreed rules: keep sequence when sign date changes, frontend preview plus backend authoritative align on save, and sync name parentheses.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-03 11:39:26 +08:00
1 parent 40fc54942b
commit 1c38d82079
1 file changed
+96
@@ -0,0 +1,96 @@
# CRM 合同编号日期段跟随签订日期 — 设计
**日期**:2026-08-03
**范围**:`web-admin` 合同管理新增/编辑;`backend` 合同 create/update(草稿)
**状态**:已评审,待实现
## 背景
合同编号格式为 `{供方编码}/X-JT{yyyyMMdd}{3位序号}`(例:`SX/X-JT20250803001`)。
日期段来自签订日期,但当前改签订日期时编号不更新;选报价单带入 `quoteDate` 会改签订日,编号仍可能与日期不一致。
## 目标
1. 签订日期变更后,标准格式合同编号中的 **日期段** 跟随更新。
2. **前端即时预览** + **后端保存时校验/校正**;撞号时由后端定最终号。
3. 合同名称末尾括号内编号与合同编号同步。
## 非目标
- 不新增合同编号预览 API。
- 不改合同编号租户内唯一索引 / 既有迁移脚本。
- 不改续签命名规则。
- 不做历史数据批量重刷。
- 非草稿合同仍不可编辑(沿用现有约束)。
## 行为规则
### 标准格式
```
^([A-Z]+)/X-JT(\d{8})(\d{3})$
```
仅当当前编号匹配该格式时才联动;手改成非标准格式则 **不联动**。
### 改号策略(方案 A + 权威后端)
1. 用签订日的 `yyyyMMdd` 替换日期段,**保留原 3 位序号**。
2. 后端若候选号已被占用(租户内,**含软删**;编辑时排除本条),则序号 +1 直至可用(重试上限与现有 `generate_contract_no` 一致,约 50 次)。
3. 编号为空:仍走现有「保存时按签订日全新生成」。
4. 编号日期段已与签订日一致:前后端均 no-op。
### 名称同步
编号变更后,合同名称末尾 `(…)` / `(…)` 替换为新编号;与现有 `generate_contract_name`、前端名称重建习惯一致。
### 触发点
| 场景 | 行为 |
|------|------|
| 表单改签订日期 | 前端即时替换日期段 + 同步名称 |
| 选报价单带入 `quoteDate` | 经同一签订日变更路径,自然联动 |
| 换交易主体 | 仍只换供方编码前缀(现有逻辑),不因本需求改策略 |
| create / update(草稿)保存 | 后端 align;撞号顺延;再生成/校正合同名称 |
## 实现要点
### 前端
主路径:`web-admin/src/views/crm/contract/components/ContractFormDialog.vue`
必要时对齐遗留页:`form.vue`。
- 在 `handleSignDateChange` 中,更新合同期限后调用 `syncContractNoDateFromSignDate`。
- 匹配标准正则则替换 8 位日期,保留序号;再同步名称括号内编号。
- 前端 **不** 做撞号查询;预览号可能与保存结果不同,以接口返回为准。
### 后端
文件:`backend/app/crud/crm/contract.py`
- 新增 `align_contract_no_to_sign_date(...)`:标准格式换日期段;再按 `contract_no_exists` 撞号顺延。
- **create**:有编号则先 align 再查重/落库;无编号则 `generate_contract_no(..., sign_date=…)`。
- **update**:对提交编号(或原号)align,写回 `contract_no`,再用 `generate_contract_name` 校正名称。
- 唯一性口径不变:租户内含软删、删除后不复用。
## 边界与错误
- 签订日清空:不改编号;合同期限按现有逻辑清空。
- 同时改签订日 + 手改编号:后端按 **最终提交的编号** + **最终签订日** align;已非标准则不 align。
- 序号顺延耗尽:抛错「生成合同编号失败,请稍后重试」,不静默写脏数据。
- 非草稿 update:仍拒绝修改。
## 验收用例
1. 改签订日 → 编号日期段变、序号不变;名称括号同步。
2. 目标号已占用 → 保存后序号顺延为可用号。
3. 手改非标准编号 → 改签订日编号不变。
4. 选报价单改签订日 → 编号日期段跟随(标准格式时)。
5. 换交易主体 → 仅供方前缀变化,日期/序号策略与现网一致。
6. 编号为空新增保存 → 仍按签订日生成完整新号。
## 决策记录
- 序号:保留原序号,撞号再顺延(A)。
- 职责:前端预览 + 后端权威校正(两边都做)。
- 名称:编号变更时名称内编号同步更新。