10 KiB
合同管理 — 跨模块调用梳理(步骤 6)
权威源码:
shanxia/crm/backend/ruoyi-modules/ruoyi-crm/
日期:2026-05-31
目的:Python 重写CrmContractServiceImpl时,明确哪些金额字段由合同自身维护、哪些由回款/开票模块回调,避免漏实现或双轨不一致。
1. 总览
flowchart LR
subgraph inbound["入站:谁调用合同 Service"]
Q[QuoteServiceImpl]
O[OpportunityServiceImpl]
P[PaymentServiceImpl]
CC[CrmContractController]
end
subgraph contract["CrmContractServiceImpl"]
insert[insertByBo / updateByBo]
recv[updateReceivedAmount]
inv[updateInvoiceAmount ⚠️ 未被调用]
end
subgraph outbound["出站:合同调用谁"]
CP[ContractProductService]
PP[PaymentPlanService.batchGenerate]
SI[SupplierInfo / Customer / Contact / User]
QT[Quote / Opportunity Mapper 写回]
end
subgraph invoice["CrmInvoiceServiceImpl"]
inline[updateContractInvoiceAmount 私有方法]
end
Q -->|insertByBo| insert
O -->|insertByBo + generateContractNo| insert
P -->|queryById 校验| contract
P -->|5 处回调| recv
CC --> contract
insert --> CP
insert --> PP
insert --> SI
invoice -->|issue / invalid| inline
inline -->|直接 UPDATE crm_contract| contract
| 合同金额字段 | 写入方式 | 当前门户 shanxia2.0 |
|---|---|---|
received_amount / unreceived_amount |
回款模块回调 updateReceivedAmount |
回款模块未移植;列表/详情只读展示 DB 字段 |
invoice_amount / uninvoice_amount |
开票模块私有 updateContractInvoiceAmount(非 updateInvoiceAmount) |
开票模块未移植;同上只读 |
2. updateReceivedAmount(contractId)
2.1 实现逻辑(Java)
位置:CrmContractServiceImpl.java L1264–1295。
- 查询
crm_payment:contract_id = ?且del_flag = 0 - 对所有记录
amount求和 →receivedAmount(不过滤status) - 读合同
amount - 更新合同:
received_amount = 求和,unreceived_amount = amount - received_amount @Transactional;合同不存在时 log error 并返回false
与回款统计的差异:PaymentServiceImpl.statisticsOverview 仅把 status = confirmed 计入金额;updateReceivedAmount 包含 pending / confirmed / invalid / rejected 等所有未删记录。新增 pending 回款时也会立刻回调,而 reject() 不回调(注释写「待确认未计入」,与 insert 行为矛盾)。Python 重写须原样复刻此行为,除非产品单独立项修正。
2.2 触发点(CrmPaymentServiceImpl)
| 方法 | 条件 | 说明 |
|---|---|---|
insertByBo |
entity.contractId != null |
插入后立即回调(状态为 pending) |
updateByBo |
更新成功且 entity.contractId != null |
仅更新新 contractId;若改绑合同,不刷新旧合同 |
deleteWithValidByIds |
删除成功后,收集到的每个 contractId |
仅允许删 pending |
confirm |
确认成功且有关联合同 | |
invalid |
作废成功且有关联合同 | |
reject |
— | 不调用 updateReceivedAmount |
插入前校验:queryById(contractId) 读 unreceivedAmount(空则取 amount),要求 bo.amount <= unreceivedAmount。
2.3 门户现状
module-status.md:回款管理(crm_payment+ 计划表)未开始- 合同重写阶段:实现
updateReceivedAmount函数供将来回款模块 import;合同 CRUD 不主动重算(除非 Java 同等逻辑在 insert 初始化 0) - 步骤 26 恢复
quote.to_contract后,新合同received_amount = 0即可
3. 开票金额:两套逻辑并存
3.1 updateInvoiceAmount(contractId) — 接口存在、无调用方
位置:CrmContractServiceImpl.java L1981–2012。
- 查
crm_invoice:contract_id = ?,del_flag = 0 - 对
amount(不含税) 求和 invoice_amount = 求和,uninvoice_amount = contract.amount - 求和
全库 grep:无任何 Service 调用此方法(仅接口 + 实现定义)。
3.2 CrmInvoiceServiceImpl.updateContractInvoiceAmount — 实际生效路径
私有方法 L838–873;直接 contractMapper.updateById,不经过 ICrmContractService。
| 发票方法 | 何时更新合同 | 累加字段 | 方向 |
|---|---|---|---|
insertByBo |
否 | — | 申请中不计入 |
updateByBo |
否 | — | |
approve |
否 | — | 审批通过仍不计入 |
issue |
是 | totalAmount(价税合计) |
isAdd = true |
invalid |
是(状态为 invoiced/delivered/received) | totalAmount |
isAdd = false |
express / receive |
否 | — |
新增/审批前校验:读合同 uninvoiceAmount,用本次 totalAmount 与剩余可开比较(insertByBo L131–141)。
与 updateInvoiceAmount 的不一致:
| 维度 | updateInvoiceAmount |
updateContractInvoiceAmount |
|---|---|---|
| 汇总字段 | invoice.amount |
开票/作废时的 totalAmount |
| 触发时机 | 全表重算(若被调用) | 增量加减 |
| 是否在用 | 否 | 是 |
Python 重写建议:
- 合同模块保留
update_invoice_amount()(全量重算版,对齐 Java 接口,供运维/迁移脚本) - 开票模块移植时实现
update_contract_invoice_amount_increment,行为对齐CrmInvoiceServiceImpl私有方法 - 不要在开票
issue/invalid时改调全量重算,除非产品确认统一
3.3 门户现状
crm_invoice/crm_invoice_item:未移植- 已有
crm_invoice_info是客户开票信息,与合同已开票金额无关
4. 合同模块内部对金额字段的处理
4.1 insertByBo
received_amount/invoice_amount默认0unreceived_amount = amount - received_amountuninvoice_amount = amount - invoice_amount- 若 BO 带
paymentPlans→paymentPlanService.batchGenerate(contractId, plans)(回款计划,非实收)
4.2 updateByBo(仅 draft)
- 修改
amount时:保留原received_amount/invoice_amount - 重算
unreceived_amount/uninvoice_amount= 新amount− 原已收/已开
4.3 renewContract
- 新合同:
received_amount = 0,invoice_amount = 0,unreceived/uninvoice = amount
4.4 统计 API
statisticsOverview / statisticsTrend 等对列表合同字段 SUM,不实时查 payment/invoice 子表。
5. 其他入站调用(非金额)
| 调用方 | 方法 | 用途 |
|---|---|---|
CrmQuoteServiceImpl.toContract |
contractService.insertByBo |
报价转合同(非 HTTP createFromQuote) |
CrmOpportunityServiceImpl 赢单 |
generateContractNo + insertByBo |
自动建 draft 合同,回写 opp.contract_id |
CrmContractController |
全套 REST | 见 04-api-spec.md |
CrmPaymentServiceImpl |
queryById |
回款金额校验 |
createFromQuote / createFromOpportunity(Controller 入口)内部也是 insertByBo + 更新 quote/opportunity 状态。
6. 合同模块出站依赖(重写时一并注册)
| 依赖 | 用途 |
|---|---|
ICrmContractProductService |
产品明细 CRUD、续签复制 |
CrmContractProductPriceTierMapper |
框架合同调价区间 |
ICrmContractApprovalService |
canUserApprove |
ICrmPaymentPlanService |
新建合同时 batchGenerate |
CrmPaymentMapper |
仅 updateReceivedAmount 读表 |
CrmInvoiceMapper |
仅 updateInvoiceAmount 读表(当前无外部触发) |
CrmQuoteMapper / CrmOpportunityMapper |
createFrom* / 商机赢单 |
CrmContractTemplateMapper |
createFromTemplate |
ICrmSupplierInfoService |
供方简称、编号规则 |
ICrmProductService |
商机转合同自动建产品 |
UserService |
负责人姓名 |
| Customer / Contact Mapper | 冗余名称填充 |
7. 前端关联(步骤 7+ 适配时注意)
| 位置 | 字段 | 说明 |
|---|---|---|
contract/index.vue 列表/详情 |
receivedAmount、unreceivedAmount、invoiceAmount、uninvoiceAmount |
来自合同 VO,非子表实时查询 |
ExecuteDialog.vue |
同上 + 回款率 | 打开执行弹窗前读合同详情 |
InvalidDialog.vue L111 |
res.data.invoicedAmount |
字段名错误:Java VO 为 invoiceAmount;作废前开票提示可能始终为 0,步骤 27 UI 适配时修正 |
8. shanxia2.0 实施策略
阶段 D(合同后端重写)
- 在
crud/crm/contract.py实现update_received_amount(db, contract_id),逻辑对齐 §2.1 - 同文件实现
update_invoice_amount(db, contract_id)(对齐 §3.1,预留接口) insert_by_bo/update_by_bo按 §4 维护四个金额字段- 不在合同路由内实现回款/开票 CRUD
回款/开票模块移植后
| 模块 | 必须回调 |
|---|---|
| 回款 | insert / update / delete / confirm / invalid → update_received_amount(不含 reject) |
| 开票 | issue / invalid → 增量更新(对齐 §3.2),或封装为 shared helper |
步骤 26
恢复 quote.to_contract 时调用新 insert_by_bo,无需触发回款/开票回调。
9. Python 重写检查清单
update_received_amount:payment 求和 无 status 过滤,del_flag=0- 回款 5 个触发点与 Java 一致;
reject不回调 update_invoice_amount全量重算实现但默认不被开票模块调用- 开票模块:
issue/invalid用 totalAmount 增量更新(将来实现) update_by_bo改amount时不覆盖已收/已开累计值renew_contract新合同金额字段清零insert_by_bo可选payment_plans→ 调用 payment_plan 模块(若未移植则跳过或 stub)- 前端
InvalidDialog:invoicedAmount→invoiceAmount
变更记录
| 日期 | 说明 |
|---|---|
| 2026-05-31 | 步骤 6:梳理 updateReceivedAmount / 开票双轨 / 入站出站 / 门户策略 |