feat: add reconciliation and quality reporting
This commit is contained in:
@@ -194,10 +194,10 @@
|
||||
|
||||
### 4.7 通道签名报备
|
||||
|
||||
1. 运营端先在“报备字段库”维护字段编码、名称、类型、是否必填等标准定义;字段代码只允许阿拉伯数字和英文大小写字母,字段类型只允许字符串、图片、文件三种。通道报备详情只能从字段库选择字段,并指定用途为签名报备、引流信息报备或两者共用,不得在通道内另建同名孤立字段。
|
||||
2. 通道组配置通道,企业应用通过路由规则选择通道组。企业签名和引流信息编辑时,系统必须沿“企业应用 -> 生效路由规则 -> 通道组 -> 组内通道 -> 通道报备字段”实时解析字段合集。
|
||||
1. 运营端先在“报备字段库”维护字段编码、名称、类型、是否必填等标准定义;字段代码只允许阿拉伯数字和英文大小写字母,字段类型只允许字符串、图片、文件三种。字段库支持将任意字段分别配置为“通用签名报备资料”或“通用引流信息报备资料”,通用配置可真实新增和删除。通道报备详情仍可选择包括通用字段在内的任意字段库字段,并指定用途为签名报备、引流信息报备或两者共用,不得在通道内另建同名孤立字段。
|
||||
2. 通道组配置通道,企业应用通过路由规则选择通道组。企业签名和引流信息添加/编辑时,系统必须合并“全局通用字段”和“企业应用 -> 生效路由规则 -> 通道组 -> 组内通道 -> 通道报备字段”;即使签名暂未绑定应用,也必须展示并校验对应类型的通用字段。
|
||||
3. 同一字段被多个通道引用时按字段库记录去重;任一通道将该字段配置为必填,则企业资料中按必填处理,并保留该字段来源的全部通道用于后续分别报备。
|
||||
4. 企业签名弹窗只展示签名报备/两者共用字段;每条引流信息只展示引流信息报备/两者共用字段。文件字段走真实对象存储上传,其他字段保存真实值,必填校验同时在前端和 NestJS API 执行。
|
||||
4. 企业签名弹窗不再维护固定的签名依据、资质凭证、企业信息或责任人信息分组,所有签名报备资料均由通用字段和通道字段动态生成;每条引流信息同样只使用动态引流报备字段。文件字段走真实对象存储上传,其他字段保存真实值,必填校验同时在运营端、客户端和 NestJS API 执行。
|
||||
5. 签名动态资料保留在签名记录;每条引流信息必须保存为独立 `SmsDrainageInfo` PostgreSQL 实体,包含所属企业、签名、应用、站点、地址、动态字段、审核状态和驳回原因,不得再以签名 JSON 数组作为引流审核事实来源。
|
||||
6. 客户端上传签名资料后进入签名审核;签名审核通过后方可新增引流信息。客户端新建或修改引流信息均自动进入 `pending`,运营端必须在独立“引流信息审核”页面查看资料后通过或带原因驳回。运营端在企业签名管理中新增或修改引流信息视为运营操作,自动审核通过并写审核记录。
|
||||
7. 引流信息审核通过前不得创建新的通道报备任务、写入可导出的引流报备材料或人工修改通道报备状态;已报备引流信息再次修改时,原通道任务冻结为 `waiting_review` 且旧材料停止使用。审核通过后系统按应用当前真实路由通道生成/重置 `reportType=drainage` 任务与材料,并写报备记录。
|
||||
@@ -527,6 +527,22 @@
|
||||
- “今日返还金额”按当天实际恢复到企业可用余额的消息级流水汇总:包含已扣费后的 `refunded`,以及提交前失败后按 `sms_message_record` 释放的 `released`;任务冻结转扣费过程中按 `sms_batch_task` 产生的内部释放不得计入返还。
|
||||
- 计费口径可配置为按提交成功计费或按回执成功计费。
|
||||
|
||||
#### 5.19.1 报表对账
|
||||
|
||||
- 在“数据详单”之后增加“报表对账”一级菜单,包含“对账单”和“利润报表”两个二级菜单;页面必须读取真实 NestJS API 与 PostgreSQL 报表表,不得在前端按明细临时拼接或使用静态数据。
|
||||
- 对账单按发送日期、企业、企业应用汇总日发送条数和成功条数。发送条数、成功条数均按短信计费条数 `billingUnits` 统计,成功以最终 `delivered` 状态为准。
|
||||
- 利润报表按发送日期汇总日发送条数、成功条数、消费金额、成本金额、利润和利润率,支持在“企业应用”和“通道”两个统计维度间切换。
|
||||
- 企业应用维度的消费金额只统计仍为 `charged` 的客户账单,最终失败并退款的短信不再形成收入;成本金额统计该应用短信所有上游 `accepted` 提交的通道成本,包括补发产生的真实额外成本。
|
||||
- 通道维度按实际上游 `accepted` 提交统计发送量和成本,按同一 Gateway 消息回执统计成功量;客户收入只归属最终有效提交,避免补发时重复计算收入。通道成本单价和成本金额必须在提交记录创建时快照,后续修改通道单价不得改写历史成本。
|
||||
- 利润等于消费金额减成本金额;利润率等于利润除以消费金额,消费金额为 0 时利润率按 0 展示。所有金额继续使用整数分持久化并按三位小数展示。
|
||||
- 报表按北京时间 T+1 生成,不生成当天未完整数据;每日刷新时必须在同一事务内重新生成 T-4 至 T-1 四个完整自然日,使 72 小时内到达或变化的回执能够修正发送成功和利润结果。
|
||||
- API 启动后自动补生成最近四个完整自然日,并按日执行滚动刷新;报表查询支持服务端日期、企业、应用、通道和维度过滤及分页。
|
||||
- “报表对账”增加“发送质量报表”,包含企业应用、通道、签名、引流信息四个 Tab。每个 Tab 按发送日期和对应维度展示发送条数、成功条数、成功率、平均到达时长,并默认按发送条数从大到小排序。
|
||||
- 发送质量的发送和成功条数均按 `billingUnits` 统计,成功以 delivered 为准;企业应用、签名和引流信息按短信最终状态统计,通道按真实 accepted 提交及同一 Gateway 消息的 delivered 回执统计。
|
||||
- 平均到达时长只使用成功且时间有效的短信,按 `deliveredAt - submittedAt` 计算;每个日期、每个维度组先计算 P95,剔除大于 P95 的最慢 5% 样本后再求平均。无成功或无有效时间样本时展示为空,不以 0 冒充。
|
||||
- `SmsMessageRecord` 必须固化实际使用的 `drainageInfoId`。新短信在同签名已审核通过的引流信息中按正文精确包含 URL 匹配,优先最长 URL;最长 URL 出现多个同长度候选时视为歧义并不关联。历史短信使用同一规则回填,未命中或歧义统一归入“未关联引流信息”,不得把一条短信复制到签名下所有引流信息造成重复统计。
|
||||
- 发送质量报表与对账、利润报表共用 T+1 及 T-4 至 T-1 滚动重算任务,每次重算在同一日期事务内重建四个质量维度。
|
||||
|
||||
### 5.20 数据保存与清理
|
||||
|
||||
- 发送记录保存 12 个月。
|
||||
|
||||
@@ -45,6 +45,8 @@ OPERATION_LOG_ARCHIVE_INTERVAL_MS=86400000
|
||||
SMS_RECEIPT_TIMEOUT_SCAN_ENABLED=true
|
||||
SMS_RECEIPT_TIMEOUT_HOURS=72
|
||||
SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS=300000
|
||||
REPORT_DAILY_REFRESH_ENABLED=true
|
||||
REPORT_REFRESH_INTERVAL_MS=3600000
|
||||
CMPP_DOWNSTREAM_ACK_TIMEOUT_SECONDS=30
|
||||
GATEWAY_CMPP_ADDR=0.0.0.0:17890
|
||||
OBJECT_STORAGE_DRIVER=minio
|
||||
@@ -62,6 +64,8 @@ PROD_ADMIN_PASSWORD='change-me'
|
||||
|
||||
`API_ENABLE_SEND_WORKER=true` 是生产发送链路必填项。后续发布脚本会在构建和迁移前校验该开关以及正整数 `API_SEND_WORKER_CONCURRENCY`;缺失时直接终止发布,防止 API/Gateway 健康但 BullMQ 短信队列无人消费。
|
||||
|
||||
日报任务默认启用,并由 `REPORT_REFRESH_INTERVAL_MS` 每小时检查一次北京时间业务日是否变化;每个业务日只执行一次 T-4 至 T-1 重算。服务重启后也会自动补跑最近四个完整自然日,确保 72 小时回执更新反映到对账和利润报表。
|
||||
|
||||
如生产验证服务器临时无法稳定下载 MinIO,可显式传入 `OBJECT_STORAGE_DRIVER=local`,文件会通过真实 API 保存到服务器本地目录 `OBJECT_STORAGE_LOCAL_ROOT`,`cmpp-minio` 服务会跳过安装和启动。该模式只建议用于验证环境;正式生产建议恢复 `OBJECT_STORAGE_DRIVER=minio`。
|
||||
|
||||
## 后续发布
|
||||
|
||||
@@ -328,12 +328,14 @@
|
||||
- 前置条件:存在两个 active 通道、一个包含这两个通道的通道组,以及绑定该通道组的企业应用。
|
||||
- 步骤:
|
||||
1. 在报备字段库创建图片字段“营业执照”和字符串字段“网站主体”,并直接调用 API 尝试创建整数、网址、电话、日期等其他类型。
|
||||
2. 在通道一将营业执照配置为签名报备必填,在通道二将同一字段配置为两者共用非必填,并将网站主体配置为引流信息报备必填。
|
||||
3. 打开该企业应用下的企业签名编辑弹窗和引流信息编辑弹窗。
|
||||
2. 将营业执照配置为通用签名报备必填,将网站主体配置为通用引流信息报备必填;再在通道一选择同一个营业执照字段配置为签名报备,在通道二配置其他通道专用字段。
|
||||
3. 分别打开绑定应用和不绑定应用的企业签名添加/编辑弹窗,以及对应引流信息添加/编辑弹窗;同时在客户端执行新增签名和新增/修改引流信息。
|
||||
4. 分别尝试缺少必填值保存,再补齐文件和值后保存。
|
||||
5. 查询 PostgreSQL 中签名、签名报备材料和引流报备材料记录;删除该引流项后再次查询。
|
||||
- 预期结果:
|
||||
- 营业执照按字段库 ID 合并为一个字段,且因任一目标通道必填而整体必填;签名弹窗展示营业执照,引流弹窗展示网站主体及两者共用字段。
|
||||
- 通用字段和通道字段按字段库 ID 合并去重;营业执照在所有签名添加/编辑表单中出现,网站主体在所有引流信息添加/编辑表单中出现,不绑定应用也不能绕过通用必填校验。
|
||||
- 企业签名弹窗不再展示固定签名依据、资质凭证、企业信息和责任人信息;资料全部来自动态配置。
|
||||
- 通用字段仍可在通道报备字段选择器中选中;删除通用配置不删除字段定义和历史资料,字段仍被通道或通用配置引用时禁止删除字段定义。
|
||||
|
||||
### TC-ADMIN-005B 引流信息按通道报备及三入口同步
|
||||
|
||||
@@ -3259,6 +3261,20 @@ npm run verify:phase8
|
||||
| TC-BILLING-013 | 准备已提交扣费但 72 小时完全无回执的 `submitted` 短信,以及有 `UNKNOWN` 回执且超过 72 小时的短信;启动 API 定时扫描并模拟重复扫描。 | 两类短信都转为 timeout 并退款;任务进度刷新;同一短信只退款一次;定时扫描默认启用且每 5 分钟执行。 |
|
||||
| TC-SEC-006 | 安装 API 生产依赖并执行 `npm audit`;使用缺文件、多文件、超大文件、超量字段和正常单文件调用认证后的 multipart 上传接口。 | NestJS/Multer/Hono 已升级或锁定到修复版本,生产依赖 audit 为 0;接口只接受一个不超过 20MB 的文件,并限制字段、part、字段名、字段值和 header pair 数量;异常请求返回受控 4xx,正常文件仍写入真实 MinIO 和 `FileObject`。 |
|
||||
|
||||
### 17.5.1 报表对账细化
|
||||
|
||||
| 用例 | 细化执行点 | 必查断言 |
|
||||
| --- | --- | --- |
|
||||
| TC-REPORT-001 | 在同一发送日准备多个企业和应用的单条、长短信,覆盖 delivered、failed、unknown;次日执行报表刷新并按日期、企业、应用查询对账单。 | 只生成 T-1 及更早完整日期;发送和成功均按 `billingUnits` 汇总;成功只包含最终 delivered;企业与应用隔离正确;API 使用 PostgreSQL 报表表和服务端分页。 |
|
||||
| TC-REPORT-002 | 准备已扣费成功、最终失败退款、同通道成功和跨通道补发成功短信,分别按企业应用和通道查看利润报表。 | 企业应用消费只包含 charged;退款不算收入;所有 accepted 尝试均按成本快照计入成本;通道维度收入只归属最终提交且不重复;利润=消费-成本,利润率计算正确,收入为 0 时显示 0%。 |
|
||||
| TC-REPORT-003 | 首次生成后,在 T-3 短信上补录 delivered 回执并将另一条 T-2 短信最终失败退款,再执行次日定时刷新。 | 每次刷新准确覆盖 T-4、T-3、T-2、T-1;对应日期旧行在事务内重建,成功数、消费、利润同步修正;T-5 及更早报表不被本次任务改写。 |
|
||||
| TC-REPORT-004 | 先按成本价发送并 accepted,再修改通道单价,随后生成和重复刷新报表。 | `SmsSubmitRecord.costUnitPrice/costAmountCents` 保存提交时快照;历史成本不随通道当前单价变化;新提交使用新单价。 |
|
||||
| TC-REPORT-005 | 打开运营端菜单和两张报表,切换日期、企业、应用及通道维度并翻页。 | “报表对账”位于“数据详单”之后且包含两个二级菜单;筛选和分页调用真实 `/admin/reports/*` API;页面展示生成时间及 T+1/T-4~T-1 口径,不使用 mock、静态数组或 localStorage 数据。 |
|
||||
| TC-QUALITY-001 | 为同一日期、同一维度准备 100 条 delivered 短信,构造不同的 `submittedAt/deliveredAt`,其中最慢 5 条显著偏大。 | 发送量和成功量按 billingUnits 汇总;成功率精确;平均到达时长先按组计算 P95,只平均小于等于 P95 的样本,最慢 5% 不进入均值;无有效成功时间时返回空值。 |
|
||||
| TC-QUALITY-002 | 分别准备多个企业应用、通道、签名和引流信息的短信,并制造 accepted 补发及不同 Gateway 回执。 | 四个 Tab 分组正确;通道只使用对应 submit/receipt;每个 Tab 服务端按 sentUnits 降序,相同数量再按日期和名称稳定排序;T-4~T-1 重算同步更新四类质量行。 |
|
||||
| TC-QUALITY-003 | 同一签名配置短 URL、包含短 URL 的长 URL、两个同长度 URL;发送正文分别命中长 URL、唯一 URL、同长度歧义和完全未命中,再对历史记录执行 migration。 | 新短信与历史短信都优先关联唯一最长 approved URL;歧义和未命中不写伪造 ID 并归入“未关联引流信息”;每条短信在引流维度只统计一次。 |
|
||||
| TC-QUALITY-004 | 打开“发送质量报表”,依次切换企业应用、通道、签名、引流信息 Tab,使用日期、企业、应用、通道筛选并翻页。 | 菜单位于“报表对账”下;页面调用真实 `/admin/reports/quality`;展示发送量、成功量、成功率、P95 截尾平均时长和生成时间,不使用前端明细聚合。 |
|
||||
|
||||
### 17.6 系统日志细化
|
||||
|
||||
| 用例 | 细化执行点 | 必查断言 |
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# 第一版系统化测试进度
|
||||
|
||||
## 2026-07-15 发送质量报表(未提交、未部署)
|
||||
|
||||
- “报表对账”新增“发送质量报表”,包含企业应用、通道、签名、引流信息四个真实 Tab;统一展示日发送条数、成功条数、成功率、平均到达时长、生成时间,并由 API 按发送条数降序分页。
|
||||
- 新增 `DailyQualityReport` 和第 46 条 migration;质量日报与现有日报任务共同按北京时间 T+1 生成并每日重算 T-4 至 T-1。企业应用/签名/引流按短信最终状态聚合,通道按 accepted submit 与对应 Gateway delivered 回执聚合。
|
||||
- 平均到达时长使用成功短信的 `deliveredAt - submittedAt`,按日期和维度计算 PostgreSQL `PERCENTILE_CONT(0.95)`,只对小于等于 P95 的样本求平均;无有效成功时间时保存 null。
|
||||
- `SmsMessageRecord` 新增真实 `drainageInfoId`。新短信在同签名 approved 引流信息中按正文 URL 精确匹配并取唯一最长 URL;migration 对历史短信用相同规则回填,歧义或未命中统一进入“未关联引流信息”,不重复展开。
|
||||
- 本地真实 PostgreSQL 已应用 46 条 migration,并实际执行四个质量维度的 T-4~T-1 生成 SQL;现有真实短信成功生成企业应用、未关联签名和未关联引流信息质量行,未关联行按企业应用隔离并可正常筛选。Prisma validate/generate/migrate status、报表与发送链路定向 2 suites/58 项、API 全量 18 suites/183 项、API build、前端 build、Gateway 全量 Go 测试、`npm audit` 0 漏洞和 `git diff --check` 通过;前端仅有既有 Vite chunk size warning。
|
||||
|
||||
## 2026-07-15 通用报备字段与签名资料动态化(未提交、未部署)
|
||||
|
||||
- 新增真实 PostgreSQL `CommonReportField` 配置表,字段库可将字段分别配置为通用签名报备资料或通用引流信息报备资料,并支持真实新增、删除;删除配置不删除字段定义或历史材料。字段被通道或通用配置引用时,字段库定义均不能直接删除。
|
||||
- 应用报备资料字段统一合并“通用字段 + 当前应用生效通道组/通道字段”,按字段库 ID 去重并合并必填规则。未绑定应用的签名和引流信息仍返回、展示并由 NestJS 强制校验通用字段;通用字段仍保留在通道报备字段的字段库选择项中。
|
||||
- 运营端企业签名添加/编辑弹窗移除固定签名依据、资质凭证、企业信息和责任人信息,所有资料改由动态字段生成。运营端和客户端的签名、引流信息添加/编辑均加载对应通用字段;客户端新增签名不再走旧固定材料上传,改为把动态值写入真实签名资料 payload,文件继续上传 MinIO/FileObject。
|
||||
- migration `20260715170000_add_common_report_fields` 已在本地 PostgreSQL 成功应用,当前 45 条 migration 全部齐全。Prisma format/generate/validate、API 定向 2 suites/39 项、API 全量 18 suites/181 项、API build、前端 build 和 `git diff --check` 通过;前端仅有既有 Vite chunk size warning。尚未提交、push 或部署。
|
||||
|
||||
## 2026-07-15 报表对账与利润报表(未提交、未部署)
|
||||
|
||||
- 在“数据详单”后新增“报表对账”一级菜单及“对账单”“利润报表”二级菜单;两页调用真实 `/api/admin/reports/reconciliation`、`/api/admin/reports/profit`,提供日期、企业、企业应用、通道、统计维度和服务端分页。
|
||||
- 新增 `DailyReconciliationReport`、`DailyProfitReport` 真实 PostgreSQL 表和第 44 条 migration。报表按北京时间 T+1 生成,API 启动后及每日任务重算 T-4 至 T-1;每个日期在独立事务内删除旧聚合并重建,覆盖 72 小时回执更新窗口。
|
||||
- 发送量和成功量按 `billingUnits` 统计;成功取最终 delivered。企业应用利润的消费取当前 charged 账单,退款不计收入;成本累计所有 accepted 上游提交,因此补发成本不会漏算。通道利润按实际 accepted 尝试及对应 Gateway delivered 回执汇总,收入只归属最终提交。
|
||||
- `SmsSubmitRecord` 新增成本单价和金额快照,创建提交记录时写入;migration 按当时现有通道价回填历史记录。SubmitResult 更新同时收窄为优先按 `submitId` 更新,避免一次补发结果覆盖同一短信的其他尝试并污染成本。
|
||||
- 本地真实 PostgreSQL 已成功应用 migration 并实际执行 2026-07-11 至 2026-07-14 的 T-4~T-1 生成 SQL;Prisma schema validate/generate 和 migrate status 通过,44 条 migration 全部齐全。报表与发送链路定向 2 suites/56 项、API 全量 18 suites/176 项、API build、前端 build、Gateway 全量 Go 测试和 `git diff --check` 全部通过;前端仅有既有 Vite chunk size warning。本批未修改依赖,`npm audit` 延续上一批 0 漏洞锁文件;本轮在线复查因 npm registry TLS 建链连续两次失败,未将网络失败误记为 audit 成功。
|
||||
|
||||
## 2026-07-15 依赖安全与今日返还修复(已提交、已部署)
|
||||
|
||||
- 生产数据库只读核查确认目标企业当天共有两笔真实返还:提交前路由失败产生消息级 `released=5` 分,最终失败产生 `refunded=5` 分,正确合计为 10 分(页面应显示 `¥0.100`)。原 Dashboard 和企业列表只聚合 `refunded`,因此少算前一笔并显示 5 分。
|
||||
|
||||
Reference in New Issue
Block a user