# 新增:合同方案补充仲裁机构/管辖法院字段 + 12301 dispute 节点完整化 **类型**: 后端 FEAT(新增字段+老 BUG 修复) **关联**: 工单 #1561 / PR #1565 (合并到 dev sha `d3190de5`) **日期**: 2026-04-30 **前端处理者**: mmg **影响范围**: 管理后台 → 合同方案管理 → 编辑合同方案弹窗 → 争议解决方式 --- ## 业务背景 管理后台合同方案编辑页(`web.test.1814.love:9443/contract/scheme`),用户截图反馈:**选择"仲裁/诉讼"时必须填对应地点,合同 PDF 模板里要用**。 排查发现: - 12301 平台 `dispute.tribunalName` 字段已支持,但 `ContractCreateService.assembleParam` **从未把 scheme.tribunalName 透传过去** → 合同 PDF 仲裁条款常年是空模板(老 BUG) - 合同方案表 `contract_scheme` 没有诉讼地点字段 - `disputeResolution` 注释在不同代码处自相矛盾(`ContractSchemeRequest` 1=仲裁/2=诉讼 vs `ContractApplyParam` 1=诉讼/2=仲裁) 经过 4 轮 PM 整改 + 4 轮评审委员会评审(综合 9.925/10)+ 架构师勘误 + dev 编码,本次 PR 一并解决。 --- ## ⚠️ 关键:disputeResolution 语义统一为 1=诉讼 / 2=仲裁 **原 `ContractSchemeRequest` 注释 "1=仲裁 2=诉讼" 写反了**,本次后端统一以 12301 平台真值为准: | 值 | 含义 | |---|---| | 1 | 诉讼 (litigation) | | 2 | 仲裁 (arbitration) | **前端 hl-ui 字典 `dispute_resolution` 渲染如原按 "1=仲裁" 请翻转**,否则用户选错语义。 测试服 DB 现存方案数据已经按 1=诉讼/2=仲裁 自洽(scheme_id=3 dispute=1 国内冬季诉讼;scheme_id=2046840448906940417 dispute=2 仲裁,均合理)— 确认 ContractApplyParam 注释才是真值。 --- ## 后端改动概要 ### 1. 字段 `contract_scheme` 表增量加 2 列(已在测试服 DDL 落地): ```sql ALTER TABLE contract_scheme ADD COLUMN tribunal_name VARCHAR(100) NULL COMMENT '仲裁机构名称(disputeResolution=2 仲裁时必填)', ADD COLUMN litigation_court VARCHAR(100) NULL COMMENT '管辖法院(disputeResolution=1 诉讼时必填)'; ``` ### 2. ReqVO / RespVO `ContractSchemeRequest` / `ContractSchemeVO` 加 2 字段: ```java @ApiModelProperty(value = "仲裁机构名称(disputeResolution=2 时必填)", example = "北京仲裁委员会") @Size(max = 100, message = "仲裁机构名称长度不能超过 100 个字符") private String tribunalName; @ApiModelProperty(value = "管辖法院(disputeResolution=1 时必填)", example = "北京市朝阳区人民法院") @Size(max = 100, message = "管辖法院长度不能超过 100 个字符") private String litigationCourt; ``` ### 3. 校验 联合校验在 Service 层(VO 层不加 `@NotBlank`,因为依赖 disputeResolution 值组合): | 场景 | 校验 | 错误码 | 业务码 | |---|---|---|---| | disputeResolution=2(仲裁)缺 tribunalName | 拦截 | `SCHEME_TRIBUNAL_NAME_REQUIRED` | **510216** | | disputeResolution=1(诉讼)缺 litigationCourt | 拦截 | `SCHEME_LITIGATION_COURT_REQUIRED` | **510217** | | 反向字段(选仲裁但传了诉讼地点等)| 不强制清空 | - | - | ### 4. 合同条款渲染 `supplementaryClause` 拼接顺序改为: ``` [1] safetyNotice (安全告知书,平台标准条款) [2] disputeClause (争议解决条款,本次新增) [3] userClause (用户自定义补充约定) ``` `disputeClause` 文本格式(本期固定模板): - 仲裁: "双方因履行本合同发生争议的,提交{tribunalName}仲裁解决。" - 诉讼: "双方因履行本合同发生争议的,提交{litigationCourt}诉讼解决。" 12301 dispute 节点也会按 resolution 路由对应字段: - 2=仲裁 → 输出 `dispute.tribunalName` - 1=诉讼 → 输出 `dispute.litigationCourt` 腾讯电子签链路通过共享 `param.supplementaryClause` 自动同步获得 disputeClause 文本(后端零改动)。 ### 5. 字符净化 `tribunalName` / `litigationCourt` 拼入条款文本时净化 `\n / \r / \t` 为空格(防 12301 errcode=301025,经验来自 PR #1551 reason 字段事故)。 --- ## 错误响应示例 创建/修改合同方案时,选择仲裁但未填仲裁机构: ```jsonc HTTP 200 (走业务错误码,非协议错误) { "code": 510216, "message": "争议解决方式为仲裁时,仲裁机构名称不能为空", "data": null } ``` 诉讼缺管辖法院: ```jsonc { "code": 510217, "message": "争议解决方式为诉讼时,管辖法院不能为空", "data": null } ``` --- ## 前端要做的改动 ⚠️ ### 1. 字典 `dispute_resolution` 翻转(关键) 原可能渲染: `1=仲裁, 2=诉讼` → 改为 **`1=诉讼, 2=仲裁`**(与后端一致)。 如不翻转,用户选"仲裁" UI 传 1,后端按"1=诉讼"处理 → 数据完全反向。 ### 2. 编辑合同方案弹窗 — 条件输入框 争议解决方式单选(仲裁/诉讼)下方,**动态显示**对应输入框: | 选中 | 显示输入框 | 必填 | maxlength | placeholder | |---|---|---|---|---| | 仲裁(value=2)| 「仲裁机构名称」 | ✅ 必填 | 100 | 请填写仲裁委员会全称,例:北京仲裁委员会 | | 诉讼(value=1)| 「管辖法院」 | ✅ 必填 | 100 | 请填写约定管辖法院,例:北京市朝阳区人民法院 | ### 3. 切换 resolution 不清空已填值 后端两列独立存,允许保留(用户切换"仲裁→诉讼→仲裁"时,仲裁地点会保留)。 ### 4. 提交字段名 - `disputeResolution` (Integer) - `tribunalName` (String) - `litigationCourt` (String) ### 5. 编辑旧方案兼容 旧方案 `tribunalName/litigationCourt` 都为 null: - 加载时不拦截,允许列表正常显示 - 用户**点击保存时**按新规校验拦截(后端会返 510216/510217) ### 6. 错误码字典 | 业务码 | 含义 | |---|---| | 510216 | 仲裁机构名称必填 | | 510217 | 管辖法院必填 | --- ## 部署状态 - ✅ PR #1565 合并到 dev (sha `d3190de5`,2026-04-30 15:46) - ✅ 测试服 DDL 已落 (V20260501 ALTER TABLE 添加 2 列) - ⏳ Deploy Panel 部署 hl-order-service-v2 测试服(进行中,管理者跟踪) - ⏳ 测试服 12301 + 腾讯电子签 round-trip 验证待执行 - ⏳ Release dev → main 待用户拍板 --- ## 测试服 round-trip 计划 **经网关 https://api.test.1814.love:9443**: 1. POST `/admin/contract/scheme` `disputeResolution=2` 不带 `tribunalName` → 期望 510216 2. POST `/admin/contract/scheme` `disputeResolution=1` 不带 `litigationCourt` → 期望 510217 3. POST `/admin/contract/scheme` 仲裁带 tribunalName → 200 success,GET 详情验证字段回显 4. PUT 同一方案改诉讼带 litigationCourt → 200,字段独立保留 5. 真实下单走 12301 报送(仲裁场景)→ 验 dispute.tribunalName 输出 + supplementaryClause 含仲裁文本 6. 真实下单走腾讯电子签(仲裁场景)→ 验 supplementaryClause form 字段含仲裁文本(跨平台一致性) --- ## 单测 `mvn -pl hl-order-service-v2 -Dtest='ContractCreateServiceTest,TencentEsignRequestBuilderTest' test`: - 84 tests,0 failures,0 errors - 17 个新单测 (4 校验 + 8 corner case + 4 边界净化顺序隐藏改点 + 1 跨平台) --- ## 不动 - ❌ 腾讯电子签模板 widget(运营在腾讯控制台改模板时联动) - ❌ disputeResolution `int → Integer` 重构(本期不动) - ❌ MP / 字典服务 / Feign / MQ / 状态机 / 缓存(本期纯 admin 配置项) - ❌ 历史合同回填(运营 TODO SQL 附 PR body) --- ## 运营 TODO(可选) 历史方案补齐(运营评估后由运维管理员执行,**本 PR 不自动跑**): ```sql -- 仲裁方案缺仲裁委员会 SELECT scheme_id, name FROM contract_scheme WHERE deleted_at IS NULL AND dispute_resolution = 2 AND (tribunal_name IS NULL OR tribunal_name = ''); -- 诉讼方案缺管辖法院 SELECT scheme_id, name FROM contract_scheme WHERE deleted_at IS NULL AND dispute_resolution = 1 AND (litigation_court IS NULL OR litigation_court = ''); ```