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

71 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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**(只读):
```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 行,无实际影响。若未来导入「未来基准日」快照需一并收口。
---
<!-- 注意:更新本手册时必须同时更新 ⑤修改记录;队列状态见 /root/WB_WS/_registry/cma-module-queue.json -->