Files
cma-management/docs/manuals/cash-plan-alerts.md

8.1 KiB
Raw Permalink Blame History

CMA 模块操作手册:资金收付款计划预警(cash-plan-alerts)

用途:每开发/验证完一个 CMA 模块,产出/更新本手册。防重复修改(先查手册再动手)、防重复验证(照清单执行)。 更新触发:每次该模块 bug 修复/功能变更 → 同步更新本手册 ⑤修改记录。

① 模块定位

  • 模块名:资金收付款计划预警(cash_plan 预警引擎)
  • 解决什么问题:把 cash_plans(收付款计划)里「到期未收款」的计划变成预警中心的待办(kpi_alerts.alert_type='cash_plan'),另有「未来30天资金缺口」预测预警(alert_type='forecast')。
  • 所属流程环节:资金管理 → 现金预测 / 预警中心 → 处置
  • 主要角色:finance(财务,处置预警)、ceo(查看)
  • 生成入口:POST /api/cma/admin/alerts/check(手动/admin)、cron、/api/cma/cash/* 相关端点

② 操作步骤

  • 财务:预警中心 /alerts 查看「到期未收款」pending 预警 → 处理(联系客户/创建改善计划)→ 置 resolved。
  • 运维/Bot:POST /api/cma/admin/alerts/check(需 Bearer token);可带 ?entity_id=N 只跑单个账套。
  • 余额快照批次口径开关(环境变量,改后需重启后端):
    • CMA_CASH_SNAPSHOT_MODE = skip(默认,批次行不生成逐户预警)| aggregate(按批次聚合 1 条)| per_customer(旧行为,逐户)
    • CMA_CASH_SNAPSHOT_SOURCES = 逗号分隔的 source 名单(覆盖内置名单)
  • 展示口径 = 落库口径(F4b):POST /api/cma/admin/alerts/check 响应里 cash.details[*].summary 的 overdue_receive_count/amount 与写库同源(快照批次按 mode 处理),并附 overdue_receive_count_raw/amount_raw(全集)、snapshot_rows_excluded(_amount)(被剔除量)、 snapshot_batches(批次明细)、snapshot_mode ⇒ 读数字不必再猜「为什么写入 0 条却显示 216 笔」。

③ 验证路径

  • 自动脚本:backend/scripts/verify_cashplan_alert_engine_fix.py(51 项验收电池;F4b 后由 43 增至 51)
    • 在生产库副本(mysqldump → cma_cashvfy)上跑,零生产写入;唯一生产访问是只读计数;跑完自动删副本。
    • 覆盖:KPI 归属(433/434)/兜底 ORDER BY 确定性/entity_id 归属/去重刷新/快照批次三档开关/端点 401 与 entity 过滤/F4b 展示=落库口径(count=new_alerts、raw/excluded/batches 可见)。
  • pytest 断言:backend/tests/test_cash_summary_snapshot_alignment_20260913.py(11 项,SQLite 内存库,无需生产库)
    • skip/per_customer/aggregate 三档口径矩阵 + suggestions 文案 + upcoming_7d 同源 + 实体隔离。
  • 期望输出:汇总: 51/51 PASS + [cleanup] cma_cashvfy 已删除,退出码 0。
  • 已测基线:2026-09-13 → 43/43 PASS(副本,F1-F5)、51/51 PASS(含 F4b)、pytest 11/11;生产端点实测 ?entity_id=2 200、new_alerts=0、summary.overdue_receive_count=0 / raw=216 / excluded=216。
  • 手工复核 SQL(只读):
    -- 博海 pending 红色"到期未收款"应为 0
    SELECT COUNT(*) FROM cma.kpi_alerts WHERE entity_id=2 AND alert_type='cash_plan' AND status='pending';
    -- 新预警归属:应收应挂 433(BH_AR_BALANCE),现金应挂 434(BH_CASH_BALANCE)
    SELECT kpi_id, entity_id, COUNT(*) FROM cma.kpi_alerts WHERE alert_type='cash_plan' GROUP BY kpi_id, entity_id;
    

④ 已知边界 / 坑

  • KPI 选择顺序:候选标准码(酣客口径 AR_001/F_AR_DAYS/C_AR_BALANCE…)→ 实体前缀后缀码(_AR_BALANCE / _CASH_BALANCE / _AP_BALANCE,命中博海 BH_*)→ 兜底(finance 维度,ORDER BY id)。任何兜底查询都必须带 ORDER BY,否则同一条件会出现两种答案(实测 idx_entity→318 / uk_entity_kpi→437)。
  • entity_id 必须显式写:KPIAlert(entity_id=...)。该列 default=1,漏传会把博海的预警挂到酣客工作台(2026-09-10 实测 589/590)。
  • 去重键 = suggestion.plan_id(不是 alert_message,后者含动态"已逾期X天")。命中后必须刷新文本/等级/天数/归属,不能 continue。
  • 余额快照批次(source ∈ bohai_ar_20260630 / bohai_ap_20260630 / bohai_prepay_20260630 / receivables_migrate):plan_date 是导入基准日(期末日),不是到期日 → 逐户生成就会永远 overdue(伪逾期)。默认 skip 不逐户生成。
  • 展示与落库必须同源:forecast_cash_flow_with_plans() 的 summary/suggestions/upcoming 与 check_cash_alerts() 写库路径共用 split_snapshot_plans()。 任何一侧新增过滤条件,另一侧必须同步修改,否则同一次响应会出现「写入 0 条 / 展示 N 笔」自相矛盾(F4b 修的就是这个)。
  • skip 模式不接管存量快照预警:历史遗留的 pending 快照预警需财务手工置 resolved(引擎不会自动改它们的状态)。
  • ⚠️ 易踩点:
    • LAST_INSERT_ID() 是连接级的——用「新连接」的 raw 查询取不到刚插入的自增 id(验证脚本曾因此把 plan_id 记成 0)。
    • 金额单位是万元:cash_plans.amount 注释=万元,导入票据/台账的「元」值必须先 ÷10000,否则预警文本放大 10000 倍。
    • FastAPI HTTPBearer(auto_error=True) 对「无 Authorization 头」返回 403;本端点用 require_auth_401 显式返回 401(验收要求)。
  • 历史 bug:2026-08-29 博海 216 条伪"到期未收款"洪水(元当万元 + 基准日当到期日 + 兜底挂 BH_ADM_EXPENSE 437 + 错挂 entity 1)→ 2026-09-13 修复(本手册⑤)。

⑤ 修改记录

日期 修改人 改动 commit 影响
2026-09-13 wecom-fullstack 手册创建;F1 显式写 entity_id(引擎2处+deviation_engine/alert_generator/deviation_push/bot_bridge/bot_bridge_v2);F2 候选码加实体前缀后缀 + 兜底 ORDER BY;F3 去重命中改为刷新文本/等级/天数/归属;F4 余额快照批次开关(默认 skip,另备 aggregate/per_customer);F5 admin 端点补鉴权(无 token 401)+ 可选 entity_id 59e65a6 博海不再重建伪逾期预警;预警 KPI 归属 433/434;不再跨账套错挂
2026-09-13 wecom-fullstack F4b 展示口径与落库口径同源:新增 split_snapshot_plans() / summarize_overdue_receives();forecast_cash_flow_with_plans() 的 summary/suggestions/upcoming_7d 按 mode 过滤快照批次(skip 不计 / aggregate 计批次 / per_customer 逐户),并在 summary 暴露 *_raw/snapshot_rows_excluded(_amount)/snapshot_batches/snapshot_mode;写库侧复用同一 helper;验收电池 43→51 项 + 新增 pytest 11 项 e159db0 消除「同一次响应写入 0 条却展示 216 笔 / 200.72 万」;数据不隐藏、可追溯

⑥ 关联

  • 依赖表:cash_plans、kpi_alerts、kpi_definitions、kpi_values、system_config
  • 依赖接口:POST /api/cma/admin/alerts/check[?entity_id=N]、GET /api/cma/cash/*
  • 关联模块:预警中心(alerts)、预警规则(alert-rules)、资金预测(cash-flow)
  • 代码:backend/app/utils/cash_forecast_engine.py::check_cash_alerts、::forecast_cash_flow_with_plans、::split_snapshot_plans、::summarize_overdue_receives、backend/app/main.py::admin_check_alerts
  • 验收:backend/scripts/verify_cashplan_alert_engine_fix.py(51 项电池)、backend/tests/test_cash_summary_snapshot_alignment_20260913.py
  • 遗留(P2,仅观察):forecast 逐日现金流投影仍用 get_cash_plans() 日期窗口取待执行计划(未按快照批次过滤)。 仅当某快照批次的 plan_date 落在 [today, today+30] 窗口内才会被计入投影;2026-09-13 实况快照批次 plan_date=2026-06-30(历史)⇒ 窗口内 0 行,无实际影响。若未来导入「未来基准日」快照需一并收口。