Appearance
16:合同:编号、关联明细、审批与客户联动
1. 合同业务的定位
合同是客户产品跟进进入成交阶段后的核心单据。它必须连接客户、跟进产品、联系人、结算规则、垫资用途、业务模式和附件,并通过审批结果反向影响跟进截止时间与客户首单成交日期。
2. 关键代码与表
| 内容 | 当前代码 | 表 |
|---|---|---|
| 合同主业务 | ContractServiceImpl | contract |
| 合同联系人 | ContractContactServiceImpl | contract_contact |
| 合同关联客户 | ContractCustomerRelationServiceImpl | contract_customer_relation |
| 结算规则 | ContractSettlementRuleServiceImpl | contract_settlement_rule |
| 垫资用途 | ContractAdvancePaymentPurposeServiceImpl | contract_advance_payment_purpose |
| 业务模式 | ContractBusinessModelServiceImpl | contract_business_model |
| 编号序列 | ContractNumberSequenceServiceImpl | contract_number_sequence |
| 附件 | SysFileServiceImpl | sys_file |
3. 合同新增的真实业务结构
入口:ContractController -> Form -> ContractDto -> ContractServiceImpl.insertContract。
真实业务的总体顺序:
text
1. 校验关联的客户产品跟进是否允许提交合同
2. 补齐当前用户、部门、客户、产品等上下文
3. 根据产品前缀与年份获取安全递增的合同编号
4. 保存 contract 主表
5. 批量保存合同联系人
6. 批量保存除主客户以外的关联客户
7. 保存结算规则、垫资用途、业务模式
8. 按来源类型保存合同附件
9. 发起 BPM 审批,回写 flowId、流程状态、开始时间
10. 审批状态变化时应用跟进截止时间规则它比项目评估多了“编号并发安全”和“客户首单时间联动”两个重点。
4. 合同编号为什么不能简单用 count + 1
ContractNumberSequenceServiceImpl.getNextSequenceNumber(productPrefix, year) 使用唯一键冲突重试和乐观锁语义来获取下一个序号。
错误实现:
text
SELECT COUNT(*) -> 100
生成编号 -> 101两个请求并发时可能都生成 101。
正确目标:同一产品前缀和年份下,每个并发请求只能得到一个不同序号。Demo 可以先用数据库唯一索引 + 重试,理解并发下的唯一性。
5. 合同关联明细的共同模式
多个 Service 都遵循:
text
batchInsertXxx(contractId, list)
batchUpdateXxx(contractId, list)
getXxxByContractId(contractId)其中 ContractCustomerRelationServiceImpl 有特殊规则:主客户已存在于 contract 主表中,关联客户表只保存非主客户,且去重。
这类规则应放在关联 Service,不应让 Controller 自己过滤。
6. 审批回调和客户联动
ContractServiceImpl.updateFlowProgress(flowId, status) 接收流程变化。审批通过后,典型联动包括:
text
更新合同流程状态
-> 对关联产品跟进调用 applyDeadlineRule
-> 首次成交时调用 CustomerInfoService.updateSigningDateOnFirstContractApproved
-> customer_info.signing_date 只在为空时写入,保留最早成交日期这意味着合同审批不是合同模块内部事件;它会改变客户和跟进模块的数据。
7. Demo 的合同实现顺序
第一版只实现:
text
demo_contract
id
contract_no
customer_id
product_follow_id
status
signing_date
demo_contract_contact业务步骤:
text
1. 只能针对 FOLLOWING 状态的产品跟进创建合同
2. 生成唯一 contractNo
3. 保存合同和联系人,使用事务
4. 模拟审批通过
5. 将产品跟进 phase 改为 VIP
6. 若 customer.signingDate 为空,写入合同签约日期第二版再增加结算规则、垫资用途和多客户关联。
8. 必测用例
- 同一并发场景下合同编号不重复;
- 跟进不存在或不在跟进中时不能创建合同;
- 联系人保存失败时合同主表回滚;
- 审批通过后跟进状态/阶段按规则变化;
- 第二份合同通过时不会覆盖客户更早的首次成交日期;
- 详情能查到联系人、规则、附件和关联客户。
9. 源码阅读顺序
text
ContractController
ContractAddForm / ContractDto / ContractVo
ContractServiceImpl.insertContract
ContractServiceImpl.updateContract
ContractServiceImpl.findById
ContractServiceImpl.updateFlowProgress
ContractServiceImpl.applyDeadlineRule
ContractNumberSequenceServiceImpl.getNextSequenceNumber
ContractContactServiceImpl
ContractCustomerRelationServiceImpl
pginit.sql 中 contract 和全部 contract_* 表