Files
shanxia2.0/docs/crm-migration/plans/contract/05-cross-module-calls.md
T
2026-06-01 08:21:21 +08:00

10 KiB
Raw Blame History

合同管理 — 跨模块调用梳理(步骤 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。

  1. 查询 crm_payment:contract_id = ? 且 del_flag = 0
  2. 对所有记录 amount 求和 → receivedAmount(不过滤 status)
  3. 读合同 amount
  4. 更新合同:received_amount = 求和,unreceived_amount = amount - received_amount
  5. @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。

  1. 查 crm_invoice:contract_id = ?,del_flag = 0
  2. 对 amount(不含税) 求和
  3. 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 默认 0
  • unreceived_amount = amount - received_amount
  • uninvoice_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(合同后端重写)

  1. 在 crud/crm/contract.py 实现 update_received_amount(db, contract_id),逻辑对齐 §2.1
  2. 同文件实现 update_invoice_amount(db, contract_id)(对齐 §3.1,预留接口)
  3. insert_by_bo / update_by_bo 按 §4 维护四个金额字段
  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 / 开票双轨 / 入站出站 / 门户策略