权威源码:
shanxia/crm/backend/ruoyi-modules/ruoyi-crm/
日期:2026-05-31
目的:Python 重写CrmContractServiceImpl时,明确哪些金额字段由合同自身维护、哪些由回款/开票模块回调,避免漏实现或双轨不一致。
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) |
开票模块未移植;同上只读 |
updateReceivedAmount(contractId)位置:CrmContractServiceImpl.java L1264–1295。
crm_payment:contract_id = ? 且 del_flag = 0amount 求和 → receivedAmount(不过滤 status)amountreceived_amount = 求和,unreceived_amount = amount - received_amount@Transactional;合同不存在时 log error 并返回 false与回款统计的差异:PaymentServiceImpl.statisticsOverview 仅把 status = confirmed 计入金额;updateReceivedAmount 包含 pending / confirmed / invalid / rejected 等所有未删记录。新增 pending 回款时也会立刻回调,而 reject() 不回调(注释写「待确认未计入」,与 insert 行为矛盾)。Python 重写须原样复刻此行为,除非产品单独立项修正。
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。
module-status.md:回款管理(crm_payment + 计划表)未开始updateReceivedAmount 函数供将来回款模块 import;合同 CRUD 不主动重算(除非 Java 同等逻辑在 insert 初始化 0)quote.to_contract 后,新合同 received_amount = 0 即可updateInvoiceAmount(contractId) — 接口存在、无调用方位置:CrmContractServiceImpl.java L1981–2012。
crm_invoice:contract_id = ?,del_flag = 0amount(不含税) 求和invoice_amount = 求和,uninvoice_amount = contract.amount - 求和全库 grep:无任何 Service 调用此方法(仅接口 + 实现定义)。
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 时改调全量重算,除非产品确认统一crm_invoice / crm_invoice_item:未移植crm_invoice_info 是客户开票信息,与合同已开票金额无关insertByBoreceived_amount / invoice_amount 默认 0unreceived_amount = amount - received_amountuninvoice_amount = amount - invoice_amountpaymentPlans → paymentPlanService.batchGenerate(contractId, plans)(回款计划,非实收)updateByBo(仅 draft)amount 时:保留原 received_amount / invoice_amountunreceived_amount / uninvoice_amount = 新 amount − 原已收/已开renewContractreceived_amount = 0,invoice_amount = 0,unreceived/uninvoice = amountstatisticsOverview / statisticsTrend 等对列表合同字段 SUM,不实时查 payment/invoice 子表。
| 调用方 | 方法 | 用途 |
|---|---|---|
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 状态。
| 依赖 | 用途 |
|---|---|
ICrmContractProductService |
产品明细 CRUD、续签复制 |
CrmContractProductPriceTierMapper |
框架合同调价区间 |
ICrmContractApprovalService |
canUserApprove |
ICrmPaymentPlanService |
新建合同时 batchGenerate |
CrmPaymentMapper |
仅 updateReceivedAmount 读表 |
CrmInvoiceMapper |
仅 updateInvoiceAmount 读表(当前无外部触发) |
CrmQuoteMapper / CrmOpportunityMapper |
createFrom* / 商机赢单 |
CrmContractTemplateMapper |
createFromTemplate |
ICrmSupplierInfoService |
供方简称、编号规则 |
ICrmProductService |
商机转合同自动建产品 |
UserService |
负责人姓名 |
| Customer / Contact Mapper | 冗余名称填充 |
| 位置 | 字段 | 说明 |
|---|---|---|
contract/index.vue 列表/详情 |
receivedAmount、unreceivedAmount、invoiceAmount、uninvoiceAmount |
来自合同 VO,非子表实时查询 |
ExecuteDialog.vue |
同上 + 回款率 | 打开执行弹窗前读合同详情 |
InvalidDialog.vue L111 |
res.data.invoicedAmount |
字段名错误:Java VO 为 invoiceAmount;作废前开票提示可能始终为 0,步骤 27 UI 适配时修正 |
crud/crm/contract.py 实现 update_received_amount(db, contract_id),逻辑对齐 §2.1update_invoice_amount(db, contract_id)(对齐 §3.1,预留接口)insert_by_bo / update_by_bo 按 §4 维护四个金额字段| 模块 | 必须回调 |
|---|---|
| 回款 | insert / update / delete / confirm / invalid → update_received_amount(不含 reject) |
| 开票 | issue / invalid → 增量更新(对齐 §3.2),或封装为 shared helper |
恢复 quote.to_contract 时调用新 insert_by_bo,无需触发回款/开票回调。
update_received_amount:payment 求和 无 status 过滤,del_flag=0reject 不回调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 / 开票双轨 / 入站出站 / 门户策略 |