Files
shanxia2.0/docs/crm-migration/plans/quote/05-special.md
T
2026-06-01 08:21:21 +08:00

6.8 KiB
Raw Blame History

报价单管理模块迁移方案 — 05 业务逻辑与特殊场景

原则:本节为开发门禁。未在本节逐条对照 Java 快照实现前,不得修改 backend/app/crud/crm/quote*.py 或新增报价相关路由。
Java 快照:docs/crm-migration/reference/java/quote/service/impl/


1. 定时任务 / 触发器

类型 报价单模块 说明
MySQL 触发器 无
定时 Job 无 过期状态可在访问分享链时懒更新,与 Java viewByShareCode 一致
Spring 事件 无

2. 状态机

draft(草稿)
  → submitted(已提交)           # submitQuote
  → invalid(已作废)             # invalidQuote(draft 也可作废)

submitted
  → draft                         # withdrawApproval(删 pending 审批)
  → accepted(审批通过)          # 全部 approver approved
  → rejected(审批失败)          # 任一 approver rejected
  → expired(已过期)             # expire_date 过期 / 分享访问时懒更新
  → invalid                       # invalidQuote

accepted
  → contract(已转合同)        # toContract
  → invalid

contract / invalid → 终态(除 invalid 重复作废报错)
规则 Java 行为(须对齐)
编辑 仅 draft(updateByBo)
删除 仅 draft;已 contract_id 不可删
提交 仅 draft;提交后创建审批 + 尝试生成 PDF(失败不阻断)
转合同 仅 accepted;无产品明细拒绝;不可重复转
作废 非 invalid 均可;remark 追加 \n【作废原因】{remark}
分享访问 可能设 viewed(字典无此项,保留 Java 行为)

状态 dict_value 禁止修改:draft / submitted / accepted / rejected / expired / contract / invalid


3. 报价单编号 generateQuoteNo

格式:{供方编码}DD-{yyyyMMdd}{3位序号},例 SXDD-20251017001

规则 Java
默认供方编码 SX(仅 supplier_id 为空时;P0 供方模块上线后须从 crm_supplier_info.supplier_code 读取)
序号范围 同前缀 + 同 quote_date 下递增
冲突 最多重试 10 次
新增 空号或含 *** 预览标记时生成
改报价日期 整号重生成,标题同步更新
改供方 只替换编码前缀,保留 DD-yyyyMMdd001 段
改客户/供方 按 quoteType 重算 quoteTitle

4. 报价主题自动生成

quoteType 格式
sales(默认) {供方简称}-{客户简称}({quoteNo})
purchase {客户简称}-{供方简称}({quoteNo})

供方简称默认 山峡;客户/供方简称来自主数据 shortName。


5. 金额计算 calculateAmount

行小计 = price × quantity × (1 - line_discount/100)
total_amount = Σ 行小计
discount_amount = total_amount × discount_rate/100
final_amount = total_amount - discount_amount

无产品时:三项金额均为 0。


6. 新增 insertByBo 默认值

字段 默认
version V1.0
parent_id 0
is_latest 1
status draft
quote_type sales
validity_days 30
quote_date 今天
expire_date quote_date + validity_days
owner_user_id 当前用户

保存前:resolveQuoteProductIds → calculateAmount → insert 主表 → batchInsert 明细。


7. 修改 updateByBo

  • 校验旧单 status == draft
  • 产品明细:先 deleteByQuoteId 再 batchInsert
  • 客户/供方/报价日期变更时的编号与标题规则见 §3、§4

8. 提交 submitQuote

  1. status: draft → submitted
  2. createApprovalRecords(id, approverIds) — step 从 1 递增
  3. 调用 PDF 生成;成功则 pdf_url、pdf_generated=1(异常仅 log)

9. 审批 approveQuote

approvalResult 主单 status
approved 若该 quote 无剩余 pending → accepted
rejected 立即 rejected,reject_reason = "审批拒绝: " + opinion

审批记录:pending → approved/rejected,写 approval_time。


10. 撤回 withdrawApproval(审批 Service)

  • 主单须 submitted
  • 物理删除(或软删,与 Java delete 一致)所有 pending 审批
  • 主单 status → draft

11. 新版本 createNewVersion

  • 复制主表字段 + 产品明细
  • 新单:status=draft,is_latest=1,parent_id=旧 id,新 version 字符串
  • 旧单:is_latest=0
  • 同 quote_no

12. 转合同 toContract

  1. 校验 accepted、无 contract_id、有产品
  2. 构建 CrmContractBo(contract_type=sale,金额=final_amount,备注含 quoteNo)
  3. 复制产品 → CrmContractProductBo(delivery_status=pending)
  4. 调用合同 insertByBo
  5. 报价单:status=contract,contract_id=新合同 id

依赖:P1 合同 insertByBo 已实现(见 plans/contract/01-prerequisite-create-for-quote.md)。


13. 分享 generateShare / viewByShareCode

项 Java
share_code 8 位随机
share_link 配置域名 + shareCode(Java 硬编码 localhost,门户改配置)
密码 可选明文存 share_password
过期 share_expire_time = now + validityDays
公开访问 无 JWT;校验 share_code、密码、分享过期、报价 expire_date
访问副作用 view_count+1、last_view_time、写 share_log、可能改 status

14. PDF / 模板

API 行为
saveTemplate 仅更新 template_content(LambdaUpdate 避免乐观锁)
getTemplate 返回 longtext HTML
generatePdf HTML 渲染 → 存文件(Java 实为 HTML,扩展名 .pdf)→ MinIO URL

模板文件见快照:resources/templates/quote-pdf.html


15. 产品 ID 解析(外部)

CrmProductServiceImpl.resolveProductIdForQuoteItem:按名称/规格/分类/单位/税率匹配;未命中则自动新建产品。

门户须在 product.py 实现同名语义,或在 quote.py 内对照 Java 内联。


16. 开发对照打勾表

实施每个 Python 函数前,在 Java 快照中定位方法并在此打勾:

  • buildQueryWrapper
  • insertByBo / updateByBo / deleteWithValidByIds
  • queryById(含 productList、冗余填充)
  • submitQuote
  • approveQuote / withdrawApproval / canUserApprove
  • createNewVersion / queryVersions
  • toContract
  • generateShare / viewByShareCode
  • invalidQuote / acceptQuote / rejectQuote
  • statisticsOverview / statisticsStatus / statisticsTrend
  • saveTemplate / getTemplate / generatePdf
  • resolveProductIdForQuoteItem(产品模块)