2026-10-15-protocol-save-auto-submit-and-v1-to-v2-migration-design.md 5.7 KB

采集协议单 · 保存自动提交财务(+ 历史 v1→v2 迁移备忘)

日期: 2026-10-15
范围:

  • 本期交付: web-admin 采集协议单新增/编辑弹窗 — 保存时自动提交财务
  • 历史迁移: 独立脚本,不纳入本期开发/上线必跑;后续专门处理生产数据(可含人工拆分)
    状态: 已确认设计(迁移策略见备忘,本期只做自动提交)

1. 背景与目标

  1. 预签约订单保存时会把 dfj 意向金转账自动提交财务确认;采集协议单目前不会,需对齐。
  2. 历史协议多为 v1(整单缴费),新单为 v2(按样本)。统一到 v2 有价值,但生产数据可能需手动拆分,故迁移与本期功能解耦。

2. 已确认决策

项 决策
保存自动提交 本期做;对齐预签约
自动提交范围 v2 transfer + v1 payment_detail 中 payStatus === 'dfj' 且已有 id 的笔
后端 save 顺带改状态 不做;仍走独立「提交财务确认」接口(前端编排)
v1→v2 迁移脚本 独立后续事项;不阻塞本期;不作为本次发版必跑 SQL
多样本拆分原则(迁移备忘) 合同金额 平均分;缴费记录金额 平均分(见 §4)
有退费 / 复杂单 迁移时优先人工处理
本需求是否下线 v1 UI 否

3. 本期交付 — 保存时自动提交财务

改动点: web-admin/src/views/cell-storage/protocol/ProtocolFormDialog.vue 的保存成功路径。

流程:

  1. 校验并通过 create/update 保存协议(含缴费草稿落库)。
  2. 取得 protocolId(编辑用已有 id;新建用接口返回的 id)。
  3. 按 paymentModelVersion 回查可提交笔:
    • v2: 回查 sample-payments bundle,展平 schedules[].transfers,筛 id 且 payStatus === 'dfj'。
    • v1: 回查 paymentDetails,筛 id 且 payStatus === 'dfj'。
  4. 逐笔调用:
    • v2:fetchSubmitPaymentTransferFinance(关闭单笔成功 toast)。
    • v1:fetchSubmitReceiptFinance(关闭单笔成功 toast)。
  5. 复用/泛化 presign-save-auto-submit.ts:
    • submitDfjTransfersOnSave / collectDfjTransferIds 保持通用。
    • formatAutoSubmitFinanceFailureMessage 支持文案主体(「订单」/「协议」)。
  6. 部分失败:协议仍算保存成功;警告
    协议已保存,但有 N 笔提交财务确认失败:…;关弹窗并 emit('saved')。
  7. 行上「提交财务确认 / 撤回」保留。

不做:

  • 不在协议 create/update 后端内自动改 pay_status。
  • 不提交非 dfj 笔;不提交无 id 的纯前端草稿(保存后回查即可)。
  • 本期不写、不跑 v1→v2 迁移 SQL。

测试要点(本期)

  • 新建 v2 带完整 dfj 转账 → 保存后变 jfqrz,财务确认列表可见
  • 编辑已有 dfj → 自动提交;jfqrz/yjf 不重复提交
  • 缺转账方式/金额 → 协议保存成功 + 失败警告
  • 仍有的 v1 单编辑保存 → dfj detail 自动提交

4. 后续备忘 — 独立迁移脚本(生产专用)

本节仅作产品/技术备忘,不纳入本期实现计划。后续单独开任务:清单导出、脚本、人工拆分流程。

4.1 交付形态(已落地)

  • Python:backend/scripts/migrate_protocol_payment_v1_to_v2.py
    支持 --list-only / --dry-run / 默认单样本迁移 / --multi-average 多样本均分。
  • 只读清单 SQL:docs/sql/migrations/2026-10-15-protocol-payment-v1-to-v2-checklist.sql
  • 迁后保留 v1 表数据(不物理删),改 payment_model_version=2。
  • 不上线自动跑;生产由人工在窗口期执行。

4.2 多样本推荐拆分规则(平均分)

相对「全部挂第一样本」,合同金额与缴费金额都按样本数平均分更符合「按样本缴费」语义,后续建议采用。

设有效样本数 N,金额用「分」运算,余数落在最后一个样本,保证合计不变。

字段 规则
各样本 contract_amount floor(总合同×100/N)/100,末样本吃余数
各期 due_amount 同上,按期对总应缴均分到 N 个 schedule
每笔转账 transfer_amount 原金额均分为 N 笔(每样本一期各一笔),状态/方式/时间/凭证 URL 复制;remark 注明 拆自原明细:{old_detail_id}

注意(财务凭证):

  • 银行侧往往是 一笔到账;均分后库内变成 N 笔同凭证、金额 1/N,适合「历史已确认数据的展示与合计」,不适合再拿去当新的财务确认依据。
  • 因此生产执行时建议:
    • 已确认(yjf):可按平均分批量生成(展示/统计对齐)。
    • 待缴/确认中(dfj/jfqrz):优先人工拆分或确认后再迁,避免财务按拆后多笔重复审核。
    • 有退费:一律人工清单,不自动拆。

4.3 单样本 / 退费

  • 单样本无退费:可脚本直迁(1:1 映射 schedule/transfer)。
  • 有退费痕迹(tfqrz/ytf、退费金额或附件非空等):只进人工清单。

5. 错误处理(本期自动提交)

场景 行为
保存成功、部分自动提交失败 警告;不回滚协议
回查无 dfj 笔 静默成功关窗

6. 非目标

  • 不在发版流水线中自动执行迁移;生产需人工确认后跑脚本
  • 不删除 v1 表、不下线 v1 表单分支
  • 不改小程序付款模型

7. 参考实现

  • 预签约自动提交:PresignOrderFormDialog.vue + presign-save-auto-submit.ts
  • v1 按期归组:ProtocolViewDialog.vue → v1PayPeriods
  • 表结构:docs/sql/biz_per_sample_payment_presign.sql