petstore-docs/架构决策-报告确认发送状态-2026-08-02.md

85 lines
4.0 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.

# 架构决策:报告显式确认发送状态
日期2026-08-02
状态Accepted / Implemented生产迁移待执行
OwnerReport Media Backend / Store Miniapp FE / Store Admin FE / Data Model
Paired QAReport Share QA
## 1. 决策背景
报告提交后,门店可以复制链接、复制话术、展示二维码或预览公开页,但这些动作都不能证明宠主已经收到。若把任一传播准备动作自动记为“已发送”,工作台待办、发送率与后续打开率都会被系统性高估,也无法支持真实门店试点。
当前系统没有微信消息投递回执,因此只能记录门店员工能够负责确认的业务事实:**宠主已通过某种方式实际收到报告**。
## 2. 决策
### 2.1 状态模型
`Report.send_status` 只有三态:
- `unknown`:迁移前历史报告,无法可靠判断是否发送;
- `unsent`:迁移后新报告,尚未被员工显式确认发送;
- `sent`:本店 boss/staff 已显式确认宠主收到。
历史报告统一回填 `unknown`,不回填 `report_sent` 事件,也不进入“报告尚未发送”行动组。迁移后新报告由应用和数据库默认值共同保证为 `unsent`
### 2.2 首次确认回执
首次确认同时写入:
- `sent_at`:确认发生时间;
- `sent_by_user_id`:当前会话员工;
- `send_channel``wechat | qr | other`
- 不可变 `BusinessEvent(event_type=report_sent)`,幂等键 `report_sent:{reportId}`
报告行以悲观写锁串行确认。状态进入 `sent` 后不可撤销,也不可用后续点击覆盖首次时间、员工或渠道;重复确认返回成功并带 `alreadyConfirmed=true`,不重复保存、不重复发事件。
确认发送不改写 `Report.update_time`:该字段目前参与成片 `processing` 超时判断,发送回执以独立 `sent_at` 表达,避免传播动作重置成片异常计时。
### 2.3 不构成发送的动作
以下动作永远不自动改变发送状态:
- 复制公开链接;
- 复制话术;
- 生成或展示二维码;
- 预览或打开 H5
- 宠主打开埋点。
产品文案不得宣称“微信发送成功”或第三方投递成功,只能表达“已记录员工确认”。
## 3. 权限与隐私
- `POST /api/report/confirm-sent` 是受保护接口,仅 boss/staff 可调用;`storeId/userId/role` 全部从会话派生。
- 服务层再次校验报告属于当前门店,跨店返回 403。
- 公开 token 报告响应不返回 `sendStatus/sentAt/sentByUserId/sendChannel`
- `report_sent.metadata_json` 只保存低敏 `channel`,不保存 token、链接、手机号、微信标识或请求体。
## 4. 工作台与漏斗
- 工作台新增 `unsent_report`,只统计 `send_status=unsent`,按报告创建时间升序提醒;历史 `unknown` 不进入行动队列。
- `reportSentCount` 按当日 `report_sent` 的 report ID 去重,`reportSentTrackingReady=true`。
- 漏斗口径为 `report_submitted → report_sent → report_opened/reopened → lead_submitted`;再次预约仍保持未知,直到可靠归因模型落地。
## 5. 数据迁移与发布门禁
迁移脚本:`backend/db/migrations/20260801_create_report_send_status.sql`。
固定迁移顺序中本脚本排在预约容量之后。生产只读预检新增三项不变量:
1. 发送状态只能是 `unknown|unsent|sent`
2. `sent` 必须有完整首次回执;
3. `unknown|unsent` 不得残留回执字段。
因此生产预检从 15 项增至 18 项;本批版本化迁移从四个增至五个。常规回滚仍只回应用,不自动删除新增列和索引。
## 6. 验收
- 新报告创建后为 `unsent`;历史迁移后为 `unknown`
- 复制链接、二维码、预览均不调用确认接口。
- 首次确认写回执和 `report_sent`;重复确认不覆盖、不重复写事件。
- customer 与跨店员工均不能确认。
- Admin 和门店小程序都要求用户选择真实发送方式并再次确认。
- 工作台只提醒 `unsent`;漏斗发送数来自 `report_sent`
- backend 全测、admin/H5/小程序构建及本体校验/漂移审计通过。