docs: define explicit report delivery state
This commit is contained in:
parent
05f7c9c7ec
commit
66b844a1c6
@ -61,6 +61,7 @@
|
||||
| **A14** | **StoreCustomer 门店客户主档**:客户列表不再临时拼成无 ID 投影 | 预约/留资自动建档;同店账号与手机号唯一;API 返回 storeCustomerId 且手机脱敏;历史数据可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现(待生产迁移) | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` | 迁移见 `backend/db/migrations/20260801_create_store_customer.sql` |
|
||||
| **A15** | **BusinessEvent 业务事件落库**:经营漏斗不再依赖日志或前端猜测 | 首批 7 类事实统一落库;稳定事件幂等;metadata 不含 token/手机号/IP;工作台已打开可统计;历史可靠事实可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现(待生产迁移) | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_business_event.sql` |
|
||||
| **A16** | **真实预约容量与连续排班占用**:服务时长、门店并发容量和预约时长快照共同决定号源 | 长服务跨半小时桶占号;`walk_in` 消耗一份容量,`blocked` 关闭全部;并发创建不超卖 | Backend Core / Store Admin FE / Data Model | ✅ 已实现(待生产迁移) | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_booking_capacity.sql`;不等同排队/准点承诺 |
|
||||
| **A17** | **报告显式确认发送状态**:传播动作与发送事实分离 | 历史为 `unknown`;新报告为 `unsent`;首次确认写不可覆盖回执与幂等 `report_sent`;工作台仅提醒新报告待发送 | Report Media Backend / Store Miniapp FE / Store Admin FE / Data Model | ✅ 已实现(待生产迁移) | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_report_send_status.sql`;不宣称微信投递成功 |
|
||||
|
||||
---
|
||||
|
||||
@ -69,7 +70,7 @@
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **B1** | **「已完成」Tab 列表默认不含已取消** | Tab「已完成」下默认仅展示 `status===done`;`cancel` 通过次级筛选或单独子 TAB 实现 | Store Miniapp FE | 🟡 部分实现(以现状为准做回归) | `npm --prefix frontend run build:h5` + 手工 smoke | 当前 `Home.vue` 需对照 |
|
||||
| **B2** | **发送报告成功页**:同屏 报告链接 + 二维码 + 两条可复制话术 | 话术支持变量:`{宠物名}{服务类型}{门店名}{链接}`;一键复制单条话术 | Store Miniapp FE | ✅ 已实现(`ReportShareModal.vue`) | `npm --prefix frontend run build:h5` + 手工 smoke | 对齐《本期主线》A |
|
||||
| **B2** | **发送报告成功页**:同屏 报告链接 + 二维码 + 两条可复制话术 + 显式确认发送 | 话术支持变量;复制/预览不自动确认;宠主实际收到后选择微信/扫码/其他并确认 | Store Miniapp FE | ✅ 已实现(`ReportShareModal.vue`) | `npm --prefix frontend run build:h5` + 手工 smoke | 对齐 A17;不得把剪贴板动作冒充发送 |
|
||||
| **B3** | **报告提交锁死提示** | 提交前弹窗固定文案:「提交后不可修改,请确认照片与备注无误」 | Store Miniapp FE | 🟡 部分实现:核对文案与位置 | 手工 smoke | 与产品 v1 锁死策略一致 |
|
||||
| **B4** | **「我的」老板端分组标题** | 至少两组:`店铺与经营` / `团队与账号`;高频在上;使用 `.module-title` | Store Miniapp FE | ✅ 已实现 | `npm --prefix frontend run build:h5` + 手工 smoke | 见《产品全方位优化说明》§2.2 |
|
||||
| **B5** | **报告前后照片必填前端校验** | 无 before 提示「请至少上传1张服务前照片」;无 after 提示「请至少上传1张服务后照片」 | Store Miniapp FE / Customer Experience FE | ✅ 已实现(Batch A) | `npm --prefix frontend run build:h5` + `npm --prefix frontend run build:mp-weixin` | 对应 A7、P0-4 |
|
||||
@ -131,7 +132,7 @@
|
||||
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **H1** | **后端可重复发布门禁** | README + production profile + 配置校验 + schema/15 项只读预检 + liveness/readiness + 只读 smoke + systemd/Nginx 模板 | Backend Ops | ✅ 仓库能力已实现;待真实环境执行 | backend 全测/打包 + `bash -n deploy/*.sh` + Runbook | 不把模板/本地通过写成已上线 |
|
||||
| **H1** | **后端可重复发布门禁** | README + production profile + 配置校验 + schema/18 项只读预检 + liveness/readiness + 只读 smoke + systemd/Nginx 模板 | Backend Ops | ✅ 仓库能力已实现;待真实环境执行 | backend 全测/打包 + `bash -n deploy/*.sh` + Runbook | 不把模板/本地通过写成已上线 |
|
||||
| **H2** | **前端可重复发布门禁** | admin/H5/小程序真实域名配置先门禁再构建;仓库占位值不能进入正式包;微信合法域名、体验版、静态回滚路径明确 | Frontend Release Ops | ✅ 仓库能力已实现;待真实环境执行 | admin/frontend preflight + 三端构建 + Runbook | 真实配置不得提交 |
|
||||
| **H3** | **P0 通过条件** | 见下方「P0 通过条件」块 | System Architect / Core Flow QA | 🟡 待人工 smoke 复核 | 见下方 | 对应 P0-10 |
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ python3 docs/graph/audit_drift.py
|
||||
| ~~高~~ | ~~寄语幂等独立用例~~ | ✅ `ReportTestimonialServiceTest` |
|
||||
| ~~中~~ | ~~`rule:BR-STORE-003` 公开 Store 字段裁剪~~ | ✅ `toPublicStoreView` |
|
||||
| 中 | `uk_report_appointment` 上线迁移 | 🟡 运维 |
|
||||
| ~~中~~ | ~~预约/履约/报告打开/留资事件可统计落地~~ | ✅ BusinessEvent;成片/播放/保存事件另行接入 |
|
||||
| ~~中~~ | ~~预约/履约/报告提交/显式发送/打开/留资事件可统计落地~~ | ✅ BusinessEvent;成片/播放/保存事件另行接入 |
|
||||
| 中 | production profile + 只读预检在真实生产配置实测 | 🟡 发布门禁 |
|
||||
| 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg) | 🟡 单测已覆盖状态机与归类 |
|
||||
| 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap |
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{"id":"entity:store","type":"Entity","name":"Store","nameZh":"门店","domain":"store","fields":["id","name","logo","phone","address","latitude","longitude","intro","owner_id","invite_code","booking_day_start","booking_last_slot_start","booking_capacity","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/Store.java","doc":"docs/产品设计文档.md §三 t_store","test":"StoreControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:user","type":"Entity","name":"User","nameZh":"用户","domain":"user","fields":["id","username","password","name","phone","avatar","store_id","role","wechat_openid","wechat_unionid","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/User.java","doc":"docs/产品设计文档.md §三 t_user; docs/.agents/common-context.md 术语规则","test":"UserServiceLoginStrategyTest; UserJsonIgnoreTest","evidence":"anchored"}
|
||||
{"id":"entity:appointment","type":"Entity","name":"Appointment","nameZh":"预约","domain":"appointment","fields":["id","pet_name","pet_type","service_type","appointment_time","duration_minutes","status","store_id","user_id","customer_user_id","created_by_user_id","assigned_user_id","pet_id","remark","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/Appointment.java; backend/src/main/java/com/petstore/service/AppointmentService.java","doc":"docs/产品设计文档.md §三 t_appointment §5.2; docs/架构决策-真实预约容量模型-2026-08-01.md","test":"AppointmentServiceTest; BookingCapacityServiceTest; AppointmentControllerDetailTest; AppointmentControllerIdentityTest; BookingCapacityMigrationTest","evidence":"anchored"}
|
||||
{"id":"entity:report","type":"Entity","name":"Report","nameZh":"服务报告","domain":"report","fields":["id","appointment_id","remark","user_id","customer_user_id","author_staff_id","store_id","report_token","pet_name","service_type","appointment_time","staff_name","highlight_video_url","highlight_video_status","highlight_video_error","highlight_duration_sec","highlight_compose_mode","highlight_fail_reason","highlight_share_cover_url","create_time","update_time","deleted"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/Report.java; backend/src/main/java/com/petstore/service/ReportService.java","doc":"docs/产品设计文档.md §三 t_report §5.3; docs/API-报告成片字段契约.md","test":"ReportServiceTest; ReportControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:report","type":"Entity","name":"Report","nameZh":"服务报告","domain":"report","fields":["id","appointment_id","remark","user_id","customer_user_id","author_staff_id","store_id","report_token","pet_name","service_type","appointment_time","staff_name","send_status","sent_at","sent_by_user_id","send_channel","highlight_video_url","highlight_video_status","highlight_video_error","highlight_duration_sec","highlight_compose_mode","highlight_fail_reason","highlight_share_cover_url","create_time","update_time","deleted"],"systems":["Report Media Backend","Store Miniapp FE","Store Admin FE"],"code":"backend/src/main/java/com/petstore/entity/Report.java; backend/src/main/java/com/petstore/service/ReportService.java","doc":"docs/产品设计文档.md §三 t_report §5.3; docs/架构决策-报告确认发送状态-2026-08-02.md","test":"ReportServiceTest; ReportControllerTest; ReportSendMigrationTest","evidence":"anchored"}
|
||||
{"id":"entity:report_lead","type":"Entity","name":"ReportLead","nameZh":"留资线索","domain":"lead","fields":["id","report_id","store_id","phone","pet_name","service_type","wechat_openid","wechat_unionid","reminder_type","remind_date","remind_status","unsubscribe_token","client_ip","create_time","update_time","deleted"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java; backend/src/main/java/com/petstore/entity/ReportLead.java","doc":"docs/本期主线-下一步产品优化点.md §C 留资","test":"ReportLeadControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:business_event","type":"Entity","name":"BusinessEvent","nameZh":"不可变业务事件","domain":"platform","jpa":true,"fields":["id","event_id","event_type","event_version","store_id","store_customer_id","aggregate_type","aggregate_id","actor_user_id","actor_role","source","occurred_at","metadata_json","idempotency_key","create_time"],"systems":["Backend Core","Report Media Backend","Store Admin FE"],"code":"backend/src/main/java/com/petstore/entity/BusinessEvent.java; backend/src/main/java/com/petstore/service/BusinessEventService.java","doc":"docs/ontology/objects.md#entity:business_event; docs/架构决策-业务事件落库-2026-08-01.md","test":"BusinessEventServiceTest; BusinessEventMigrationTest","evidence":"anchored"}
|
||||
{"id":"action:register_boss","type":"Action","name":"registerBoss","nameZh":"老板入驻","inputs":["storeName","bossName","phone","password"],"outputs":["entity:store","entity:user","session_token"],"entrypoints":["POST /api/user/register-boss","UserService#registerBoss"],"appliesRules":["rule:BR-AUTH-001"],"code":"backend/src/main/java/com/petstore/service/UserService.java#registerBoss","evidence":"anchored","domain":"user"}
|
||||
@ -17,6 +17,7 @@
|
||||
{"id":"action:get_appointment_detail","type":"Action","name":"getAppointmentDetail","nameZh":"预约详情","inputs":["id"],"outputs":["entity:appointment"],"entrypoints":["GET /api/appointment/detail","AppointmentController#detail"],"appliesRules":["rule:BR-APPT-002","rule:BR-APPT-003"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#detail","test":"AppointmentControllerDetailTest","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:list_appointments","type":"Action","name":"listAppointments","nameZh":"预约列表","inputs":["status","page","pageSize"],"outputs":["entity:appointment"],"entrypoints":["GET /api/appointment/list","AppointmentController#list"],"appliesRules":["rule:BR-APPT-002","rule:BR-APPT-003"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#list","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:submit_report","type":"Action","name":"submitReport","nameZh":"提交报告","inputs":["appointmentId","remark","images"],"outputs":["reportToken","reportId"],"entrypoints":["POST /api/report/create","ReportController#create","ReportService#create"],"appliesRules":["rule:BR-RPT-001","rule:BR-RPT-002","rule:BR-RPT-003","rule:BR-RPT-004","rule:BR-RPT-005","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create; backend/src/main/java/com/petstore/service/ReportService.java#create; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportSubmitted","test":"ReportControllerTest; ReportServiceTest; BusinessEventServiceTest","evidence":"anchored","domain":"report","note":"authorStaffId 从登录上下文派生,customerUserId 从预约派生;报告提交与事件同事务"}
|
||||
{"id":"action:confirm_report_sent","type":"Action","name":"confirmReportSent","nameZh":"显式确认报告已发送","inputs":["reportId","channel","entity:current_user"],"outputs":["sendStatus","sentAt","sendChannel","alreadyConfirmed","entity:business_event"],"entrypoints":["POST /api/report/confirm-sent","ReportController#confirmSent","ReportService#confirmSent"],"appliesRules":["rule:BR-RPT-005","rule:BR-RPT-008","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#confirmSent; backend/src/main/java/com/petstore/service/ReportService.java#confirmSent; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportSent; frontend/src/components/report/ReportShareModal.vue; admin/src/views/ReportsView.vue","test":"ReportControllerTest; ReportServiceTest; BusinessEventServiceTest; ReportSendMigrationTest","evidence":"anchored","domain":"report","note":"复制链接、二维码和预览不算发送;首次确认不可覆盖,重复确认幂等成功"}
|
||||
{"id":"action:get_report_by_token","type":"Action","name":"getReportByToken","nameZh":"公开报告查询","inputs":["token"],"outputs":["entity:report"],"entrypoints":["GET /api/report/get","ReportController#getByAppointmentId"],"appliesRules":["rule:BR-RPT-006"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#getByAppointmentId","test":"ReportControllerTest#publicTokenQueryHidesInternalFields","evidence":"anchored","domain":"report"}
|
||||
{"id":"action:track_report_open","type":"Action","name":"trackReportOpen","nameZh":"报告打开埋点","inputs":["token","visitType"],"outputs":["entity:business_event"],"entrypoints":["POST /api/report/open-track","ReportController#trackReportOpen"],"appliesRules":["rule:BR-RPT-007","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportOpenByToken","test":"ReportControllerTest; BusinessEventServiceTest","evidence":"anchored","domain":"report"}
|
||||
{"id":"action:delete_report","type":"Action","name":"deleteReport","nameZh":"删除报告","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/report/delete","ReportController#delete"],"appliesRules":["rule:BR-RPT-005"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#delete","evidence":"anchored","domain":"report"}
|
||||
@ -31,6 +32,7 @@
|
||||
{"id":"event:service_started","type":"Event","name":"ServiceStarted","nameZh":"服务开始","domain":"appointment","payload":["storeId","storeCustomerId","appointmentId","actorUserId","actorRole","occurredAt"],"emittedBy":["action:start_service"],"appliesRules":["rule:BR-APPT-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged","test":"BusinessEventServiceTest#statusToDoingRecordsGenericAndServiceStartedFacts","evidence":"anchored"}
|
||||
{"id":"event:service_completed","type":"Event","name":"ServiceCompleted","nameZh":"服务完成","domain":"appointment","payload":["storeId","storeCustomerId","appointmentId","actorUserId","actorRole","occurredAt"],"emittedBy":["action:transition_appointment_status","action:submit_report"],"appliesRules":["rule:BR-APPT-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged; backend/src/main/java/com/petstore/service/ReportService.java#create","test":"AppointmentServiceTest; ReportServiceTest","evidence":"anchored"}
|
||||
{"id":"event:report_submitted","type":"Event","name":"ReportSubmitted","nameZh":"报告提交","domain":"report","payload":["storeId","storeCustomerId","reportId","actorUserId","actorRole","occurredAt"],"emittedBy":["action:submit_report"],"appliesRules":["rule:BR-RPT-001","rule:BR-RPT-002","rule:BR-RPT-004","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/ReportService.java#create; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportSubmitted","test":"ReportServiceTest; BusinessEventServiceTest","evidence":"anchored"}
|
||||
{"id":"event:report_sent","type":"Event","name":"ReportSent","nameZh":"报告已确认发送","domain":"report","payload":["storeId","storeCustomerId","reportId","actorUserId","actorRole","channel","occurredAt"],"emittedBy":["action:confirm_report_sent"],"appliesRules":["rule:BR-RPT-008","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/ReportService.java#confirmSent; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportSent","test":"ReportServiceTest; BusinessEventServiceTest","evidence":"anchored","note":"idempotencyKey=report_sent:{reportId};无 token/手机号/URL"}
|
||||
{"id":"event:report_opened","type":"Event","name":"ReportOpened","nameZh":"报告首次打开","domain":"report","payload":["storeId","storeCustomerId","reportId","visitType","occurredAt"],"emittedBy":["action:track_report_open"],"appliesRules":["rule:BR-RPT-007","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportOpenByToken","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"ReportControllerTest; BusinessEventServiceTest","evidence":"anchored"}
|
||||
{"id":"event:report_reopened","type":"Event","name":"ReportReopened","nameZh":"报告再次打开","domain":"report","payload":["storeId","storeCustomerId","reportId","visitType","occurredAt"],"emittedBy":["action:track_report_open"],"appliesRules":["rule:BR-RPT-007","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java#recordReportOpenByToken","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"BusinessEventServiceTest#reportOpenStoresReportIdButNeverToken","evidence":"anchored"}
|
||||
{"id":"event:lead_submitted","type":"Event","name":"LeadSubmitted","nameZh":"留资提交","domain":"lead","payload":["storeId","storeCustomerId","leadId","repeatSubmit","occurredAt"],"emittedBy":["action:submit_lead"],"appliesRules":["rule:BR-LEAD-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/ReportLeadService.java#submit; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordLeadSubmitted","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","test":"ReportLeadServiceStoreCustomerTest; BusinessEventMigrationTest","evidence":"anchored"}
|
||||
@ -49,6 +51,7 @@
|
||||
{"id":"rule:BR-RPT-005","type":"Rule","name":"报告跨店 403","domain":"report","appliesTo":["action:submit_report","action:delete_report"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create; #delete","test":"ReportControllerTest#createCrossStoreReturnsForbidden; createNonStaffReturnsForbidden","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-006","type":"Rule","name":"公开报告字段裁剪","domain":"report","appliesTo":["action:get_report_by_token"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#getByAppointmentId","test":"ReportControllerTest#publicTokenQueryHidesInternalFields","frontend":"frontend/src/pages/report-view/reportView.vue","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-007","type":"Rule","name":"token hash 日志","domain":"report","appliesTo":["action:track_report_open"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#shortTokenHash; #trackReportOpen","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-008","type":"Rule","name":"报告发送显式确认且首次回执不可覆盖","domain":"report","appliesTo":["action:confirm_report_sent"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#confirmSent; backend/src/main/java/com/petstore/service/ReportService.java#confirmSent; backend/src/main/java/com/petstore/mapper/ReportMapper.java#findByIdAndDeletedFalseForUpdate","test":"ReportControllerTest; ReportServiceTest; ReportSendMigrationTest","doc":"docs/架构决策-报告确认发送状态-2026-08-02.md","evidence":"anchored"}
|
||||
{"id":"rule:BR-LEAD-001","type":"Rule","name":"留资去重","domain":"lead","appliesTo":["action:submit_lead"],"code":"backend/src/main/java/com/petstore/service/ReportLeadService.java#submit","evidence":"anchored","test":"ReportLeadControllerTest#submitLeadRepeatSubmitReturnsAlreadySubmitted"}
|
||||
{"id":"rule:BR-SC-001","type":"Rule","name":"门店客户主档唯一性与合并","domain":"admin","appliesTo":["action:create_appointment","action:submit_lead","action:admin_list_service_customers"],"code":"backend/src/main/java/com/petstore/service/StoreCustomerService.java","doc":"docs/ontology/rules.md#rule:BR-SC-001; docs/架构决策-门店客户主档-2026-08-01.md","test":"StoreCustomerServiceTest; ReportLeadServiceStoreCustomerTest; IdentityOwnershipQueryTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-BE-001","type":"Rule","name":"业务事件不可变幂等与低敏","domain":"platform","appliesTo":["action:create_appointment","action:start_service","action:transition_appointment_status","action:submit_report","action:track_report_open","action:submit_lead","action:view_workbench"],"code":"backend/src/main/java/com/petstore/service/BusinessEventService.java; backend/src/main/java/com/petstore/service/AdminBusinessEventService.java","doc":"docs/ontology/rules.md#rule:BR-BE-001; docs/架构决策-业务事件落库-2026-08-01.md","test":"BusinessEventServiceTest; BusinessEventMigrationTest; AdminBusinessEventControllerTest","evidence":"anchored"}
|
||||
@ -79,6 +82,7 @@
|
||||
{"id":"rel:submit_report_emits_report_submitted","type":"Relation","subject":"action:submit_report","predicate":"EMITS","object":"event:report_submitted","evidence":"anchored","domain":"report"}
|
||||
{"id":"rel:submit_report_emits_status_changed","type":"Relation","subject":"action:submit_report","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","doc":"doing→done","domain":"report"}
|
||||
{"id":"rel:submit_report_emits_service_completed","type":"Relation","subject":"action:submit_report","predicate":"EMITS","object":"event:service_completed","evidence":"anchored","domain":"report"}
|
||||
{"id":"rel:confirm_report_sent_emits_report_sent","type":"Relation","subject":"action:confirm_report_sent","predicate":"EMITS","object":"event:report_sent","evidence":"anchored","domain":"report"}
|
||||
{"id":"rel:store_has_business_events","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:business_event","evidence":"anchored","cardinality":"1—N","domain":"platform","doc":"BusinessEvent.store_id"}
|
||||
{"id":"rel:store_customer_has_business_events","type":"Relation","subject":"entity:store_customer","predicate":"HAS","object":"entity:business_event","evidence":"anchored","cardinality":"1—N","domain":"platform","doc":"BusinessEvent.store_customer_id 可空"}
|
||||
{"id":"rel:track_report_open_emits_report_opened","type":"Relation","subject":"action:track_report_open","predicate":"EMITS","object":"event:report_opened","evidence":"anchored","domain":"report"}
|
||||
@ -135,7 +139,7 @@
|
||||
{"id":"rule:BR-USER-002","type":"Rule","name":"用户更新仅本人","domain":"user","appliesTo":["action:update_user","action:get_user_info"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser; #info","evidence":"anchored"}
|
||||
{"id":"rule:BR-CONFIG-001","type":"Rule","name":"生产配置环境变量","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml; application-example.yml","doc":"backend/README.md 环境变量表","evidence":"anchored"}
|
||||
{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile 强制只读 schema 安全配置","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile; backend/src/main/java/com/petstore/config/ProductionConfigurationValidator.java; backend/src/main/java/com/petstore/PetstoreApplication.java","doc":"backend/README.md production profile","test":"ProductionConfigurationValidatorTest","evidence":"anchored","note":"生产禁用自动建表/show-sql/SMS 万能码/默认数据初始化,并拒绝弱密钥、占位域名和宽 CORS"}
|
||||
{"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","evidence":"anchored","note":"备份和四迁移后运行 schema validate + 15 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读"}
|
||||
{"id":"rule:BR-CONFIG-003","type":"Rule","name":"生产发布只读门禁","domain":"config","appliesTo":[],"code":"backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java; backend/src/main/java/com/petstore/config/PetstoreRuntimeHealthIndicator.java; backend/deploy/release-preflight.sh; backend/deploy/production-smoke.sh","doc":"docs/生产发布与回滚Runbook-2026-08-01.md; docs/生产监控与告警基线-2026-08-01.md","evidence":"anchored","note":"备份和五迁移后运行 schema validate + 18 项只读不变量;readiness 覆盖 DB/磁盘/上传/FFmpeg;自动 smoke 只读"}
|
||||
{"id":"rel:user_owns_pets","type":"Relation","subject":"entity:user","predicate":"OWNS","object":"entity:pet","cardinality":"1—N","evidence":"anchored","doc":"Pet.owner_user_id → User.id","domain":"user"}
|
||||
{"id":"rel:pet_has_appointments","type":"Relation","subject":"entity:pet","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.pet_id(可空)","domain":"pet"}
|
||||
{"id":"rel:store_has_service_types","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:service_type","cardinality":"1—N","evidence":"anchored","doc":"ServiceType.store_id(NULL=系统默认)","domain":"store"}
|
||||
@ -202,8 +206,8 @@
|
||||
{"id":"rel:br_int_001_applies","type":"Relation","subject":"rule:BR-INT-001","predicate":"APPLIES_TO","object":"action:get_report_suggestion","domain":"service_type","evidence":"anchored"}
|
||||
{"id":"rel:br_store_003_applies","type":"Relation","subject":"rule:BR-STORE-003","predicate":"APPLIES_TO","object":"action:get_store","domain":"store","evidence":"anchored"}
|
||||
{"id":"entity:admin_console","type":"Entity","name":"AdminConsole","nameZh":"门店 Web 管理后台","domain":"admin","jpa":false,"fields":["surface","navModules","storeScope","roleGate"],"systems":["Store Admin FE","Backend Core"],"code":"","doc":"docs/ontology/objects.md#entity:admin_console","test":"","evidence":"documented","note":"Phase A 产品预录入;独立 Web 端,与 uni-app 小程序并存。对标宠老板「店铺概况」入口,不做收银/库存。详见 docs/门店管理后台升级方案-对标宠老板.md","status":"planned"}
|
||||
{"id":"entity:workbench","type":"Entity","name":"Workbench","nameZh":"今日行动工作台","domain":"admin","jpa":false,"fields":["todayAppointmentCount","todayDoneCount","overdueAppointmentCount","inServiceCount","upcomingTodayCount","highlightAnomalyCount","dueFollowUpCount","actionGroups","reportFunnel"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"admin/src/views/WorkbenchView.vue; backend/src/main/java/com/petstore/service/AdminWorkbenchService.java; backend/src/main/java/com/petstore/service/AdminBusinessEventService.java","doc":"docs/ontology/objects.md#entity:workbench","test":"AdminWorkbenchServiceTest; AdminBusinessEventServiceTest","evidence":"anchored","note":"服务端按完整本店数据准确聚合行动;每组最多 3 条低敏预览;报告发送与转预约未跟踪时显示未知。","status":"implemented"}
|
||||
{"id":"entity:workbench_todo_item","type":"Entity","name":"WorkbenchTodoItem","nameZh":"工作台行动组","domain":"admin","jpa":false,"fields":["kind","urgency","count","items","drilldownAction","filter"],"systems":["Store Admin FE","Backend Core"],"code":"backend/src/main/java/com/petstore/service/AdminWorkbenchService.java; admin/src/views/WorkbenchView.vue","doc":"docs/ontology/objects.md#entity:workbench_todo_item","test":"AdminWorkbenchServiceTest","evidence":"anchored","note":"kind: overdue_appointment | in_service | upcoming_today | highlight_anomaly | follow_up_due;items 不含手机号/token/媒体 URL","status":"implemented"}
|
||||
{"id":"entity:workbench","type":"Entity","name":"Workbench","nameZh":"今日行动工作台","domain":"admin","jpa":false,"fields":["todayAppointmentCount","todayDoneCount","overdueAppointmentCount","inServiceCount","upcomingTodayCount","highlightAnomalyCount","unsentReportCount","dueFollowUpCount","actionGroups","reportFunnel"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"admin/src/views/WorkbenchView.vue; backend/src/main/java/com/petstore/service/AdminWorkbenchService.java; backend/src/main/java/com/petstore/service/AdminBusinessEventService.java","doc":"docs/ontology/objects.md#entity:workbench","test":"AdminWorkbenchServiceTest; AdminBusinessEventServiceTest","evidence":"anchored","note":"服务端按完整本店数据准确聚合行动;每组最多 3 条低敏预览;新报告待发送有行动组,发送漏斗为真实显式确认,仅转预约未知。","status":"implemented"}
|
||||
{"id":"entity:workbench_todo_item","type":"Entity","name":"WorkbenchTodoItem","nameZh":"工作台行动组","domain":"admin","jpa":false,"fields":["kind","urgency","count","items","drilldownAction","filter"],"systems":["Store Admin FE","Backend Core"],"code":"backend/src/main/java/com/petstore/service/AdminWorkbenchService.java; admin/src/views/WorkbenchView.vue","doc":"docs/ontology/objects.md#entity:workbench_todo_item","test":"AdminWorkbenchServiceTest","evidence":"anchored","note":"kind: overdue_appointment | in_service | upcoming_today | highlight_anomaly | unsent_report | follow_up_due;items 不含手机号/token/媒体 URL","status":"implemented"}
|
||||
{"id":"entity:store_customer","type":"Entity","name":"StoreCustomer","nameZh":"门店客户主档(瘦身)","domain":"admin","jpa":true,"fields":["id","store_id","customer_user_id","phone","display_name","source","first_contact_at","last_contact_at","create_time","update_time","deleted"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/StoreCustomer.java; backend/src/main/java/com/petstore/service/StoreCustomerService.java; backend/src/main/java/com/petstore/service/AdminServiceCustomerService.java","doc":"docs/ontology/objects.md#entity:store_customer; docs/架构决策-门店客户主档-2026-08-01.md","test":"StoreCustomerServiceTest; AdminServiceCustomerServiceTest; IdentityOwnershipQueryTest","evidence":"anchored","note":"门店与客户的稳定关系实体;API 动态聚合宠物/到店/报告/留资,不含余额/次卡/积分。","status":"implemented"}
|
||||
{"id":"rule:BR-ADMIN-001","type":"Rule","name":"adminRoleGate","nameZh":"门店后台角色门禁(boss/staff 可读写)","domain":"admin","appliesTo":["action:view_workbench","action:admin_list_appointments","action:admin_list_reports","action:admin_list_leads","action:admin_list_service_customers","action:admin_update_settings"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-001; docs/门店管理后台-PhaseA-页面清单PRD.md","test":"","evidence":"documented","note":"boss 与 staff 均可进入 admin_console 并编辑本店数据;customer 禁止。删店/部分设置仍仅 boss(BR-STORE-001/BR-USER-001)。产品决策 2026-07-10。"}
|
||||
{"id":"rule:BR-ADMIN-002","type":"Rule","name":"adminStoreScope","nameZh":"后台数据仅本店 storeId","domain":"admin","appliesTo":["action:view_workbench","action:admin_list_appointments","action:admin_list_reports","action:admin_list_leads","action:admin_list_service_customers","action:admin_update_settings"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-002","test":"","evidence":"documented","note":"所有后台查询/写操作强制 current.storeId;忽略跨店请求参数。连锁同步不在 Phase A。"}
|
||||
|
||||
@ -157,6 +157,19 @@
|
||||
- **测试**:`ReportControllerTest#publicTokenQueryHidesInternalFields`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:confirm_report_sent
|
||||
|
||||
门店员工在宠主实际收到报告后显式确认发送。复制链接、生成/展示二维码、打开 H5 或预览报告都不自动计为发送;首次确认写入不可覆盖的回执,重复确认幂等成功。
|
||||
|
||||
- **入口**:`POST /api/report/confirm-sent` → `ReportController#confirmSent` → `ReportService#confirmSent`
|
||||
- **输入**:`reportId`、`channel=wechat|qr|other`;门店、员工与角色均从会话派生
|
||||
- **输出**:`sendStatus`、`sentAt`、`sendChannel`、`alreadyConfirmed`
|
||||
- **适用规则**:`rule:BR-RPT-005`、`rule:BR-RPT-008`、`rule:BR-BE-001`
|
||||
- **触发事件**:`event:report_sent`(仅首次确认)
|
||||
- **代码**:`ReportController#confirmSent`、`ReportService#confirmSent`、`BusinessEventService#recordReportSent`
|
||||
- **测试**:`ReportControllerTest`、`ReportServiceTest`、`BusinessEventServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:track_report_open
|
||||
|
||||
报告打开埋点:服务端用 token 定位 Report 后写入低敏 BusinessEvent;事件表不保存 token/hash,日志只记 SHA-256 前 8 位 hash。
|
||||
@ -588,7 +601,7 @@
|
||||
|
||||
### action:view_workbench
|
||||
|
||||
查看今日行动工作台(登录落点)。服务端按完整本店数据聚合逾期预约、服务中、今日待开始、成片异常和到期回访,返回准确总数与低敏预览;报告提交、打开与留资通过 BusinessEvent 汇总,未建模的报告发送/转预约保持未知。
|
||||
查看今日行动工作台(登录落点)。服务端按完整本店数据聚合逾期预约、服务中、今日待开始、成片异常、新报告待发送和到期回访,返回准确总数与低敏预览;报告提交、显式确认发送、打开与留资通过 BusinessEvent 汇总,仅未建模的转预约保持未知。
|
||||
|
||||
- **入口**:Admin UI `/workbench`;`GET /api/admin/workbench/actions`;兼容 `GET /api/admin/workbench/report-funnel?date=yyyy-MM-dd`
|
||||
- **输出**:`entity:workbench`、`entity:workbench_todo_item`
|
||||
@ -617,11 +630,11 @@
|
||||
|
||||
### action:admin_list_reports
|
||||
|
||||
后台报告列表(成片三态、复制公开链接、触发重生成)。
|
||||
后台报告列表(成片三态、发送三态、复制公开链接、显式确认发送、触发重生成)。
|
||||
|
||||
- **入口**:Admin UI `/reports`(planned);复用 `GET /api/report/list` + `action:generate_highlight`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-003`、`rule:BR-RPT-005`
|
||||
- **证据**:`documented`
|
||||
- **入口**:Admin UI `/reports`;复用 `GET /api/report/list` + `action:confirm_report_sent` + `action:generate_highlight`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-003`、`rule:BR-RPT-005`、`rule:BR-RPT-008`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:admin_list_leads
|
||||
|
||||
|
||||
@ -103,6 +103,18 @@
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`anchored`(事件已落库并进入工作台去重统计;日志仅保留短 hash)
|
||||
|
||||
### event:report_sent
|
||||
|
||||
门店员工首次显式确认宠主已经收到报告。历史报告不猜测、不回填该事件。
|
||||
|
||||
- **触发动作**:`action:confirm_report_sent`
|
||||
- **持久字段**:`aggregateId=reportId`、`storeId`、`storeCustomerId?`、`actorUserId/role`、`occurredAt=sentAt`、`channel`;不保存 report token、手机号或 URL
|
||||
- **幂等键**:`report_sent:{reportId}`
|
||||
- **适用规则**:`rule:BR-RPT-008`、`rule:BR-BE-001`
|
||||
- **代码**:`ReportService#confirmSent`、`BusinessEventService#recordReportSent`
|
||||
- **测试**:`ReportServiceTest#firstConfirmSentPersistsImmutableReceiptAndEvent`、`BusinessEventServiceTest#reportSentIsIdempotentAndStoresOnlyChannelMetadata`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:report_reopened
|
||||
|
||||
报告页再次打开(非首次)。
|
||||
|
||||
@ -102,6 +102,10 @@
|
||||
| service_type | VARCHAR | 服务类型(冗余) |
|
||||
| appointment_time | DATETIME | 服务时间(冗余) |
|
||||
| staff_name | VARCHAR | 服务技师名快照(优先取预约 assigned_user_id) |
|
||||
| send_status | VARCHAR(16) | unknown(历史不可确认)/ unsent(新报告待发送)/ sent(已显式确认) |
|
||||
| sent_at | DATETIME | 首次显式确认发送时间 |
|
||||
| sent_by_user_id | BIGINT | 首次确认发送的门店员工 |
|
||||
| send_channel | VARCHAR(16) | wechat / qr / other;首次确认后不可覆盖 |
|
||||
| highlight_video_url | VARCHAR(500) | 成片 MP4 相对 URL |
|
||||
| highlight_video_status | VARCHAR(32) | processing / done / failed |
|
||||
| highlight_video_error | VARCHAR(500) | 错误信息 |
|
||||
@ -392,9 +396,9 @@
|
||||
|------|------|
|
||||
| todayAppointmentCount / todayDoneCount | 今日预约 / 今日完成 |
|
||||
| overdueAppointmentCount / inServiceCount / upcomingTodayCount | 逾期未开始 / 服务中 / 今日待开始 |
|
||||
| dueFollowUpCount / highlightAnomalyCount | 到期回访 / 成片失败或处理超时 |
|
||||
| dueFollowUpCount / highlightAnomalyCount / unsentReportCount | 到期回访 / 成片失败或处理超时 / 新报告待发送 |
|
||||
| actionGroups | `entity:workbench_todo_item[]`(每组最多 3 条低敏预览) |
|
||||
| reportFunnel | 已提交/已打开/已留资/报告转预约;未跟踪的发送与转预约返回未知 |
|
||||
| reportFunnel | 已提交/已确认发送/已打开/已留资/报告转预约;仅转预约仍返回未知 |
|
||||
| anomalyList | 成片失败或待回访最近 N 条 |
|
||||
|
||||
- **代码**:`admin/src/views/WorkbenchView.vue`、`AdminBusinessEventController`、`AdminWorkbenchService`、`AdminBusinessEventService`
|
||||
@ -408,7 +412,7 @@
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| kind | `overdue_appointment` / `in_service` / `upcoming_today` / `highlight_anomaly` / `follow_up_due` |
|
||||
| kind | `overdue_appointment` / `in_service` / `upcoming_today` / `highlight_anomaly` / `unsent_report` / `follow_up_due` |
|
||||
| urgency | critical / high / normal |
|
||||
| count | 数量 |
|
||||
| items | 最多 3 条 resource ID + 宠物/服务/时间低敏预览;无手机号、token、媒体 URL |
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
| `action:start_service` | `event:appointment_status_changed`(new→doing)、`event:service_started` |
|
||||
| `action:transition_appointment_status` | `event:appointment_status_changed` |
|
||||
| `action:submit_report` | `event:report_submitted`、`event:appointment_status_changed`(doing→done)、`event:service_completed` |
|
||||
| `action:confirm_report_sent` | `event:report_sent`(仅首次确认) |
|
||||
| `action:track_report_open` | `event:report_opened` / `event:report_reopened` |
|
||||
| `action:submit_lead` | `event:lead_submitted` |
|
||||
|
||||
|
||||
@ -156,6 +156,16 @@
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#shortTokenHash`/`#trackReportOpen`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-RPT-008
|
||||
|
||||
**报告发送必须显式确认且首次回执不可覆盖**:复制链接、二维码、预览和打开报告均不自动改变发送状态。迁移前历史报告为 `unknown`,迁移后新报告为 `unsent`;仅本店 boss/staff 可选择 `wechat|qr|other` 确认为 `sent`。报告行以悲观锁串行确认;首次写入 `sent_at/sent_by_user_id/send_channel` 和幂等 `report_sent` 事件,重复确认返回 `alreadyConfirmed=true` 并保留首次回执。
|
||||
|
||||
- **适用动作**:`action:confirm_report_sent`
|
||||
- **代码**:`ReportController#confirmSent`、`ReportService#confirmSent`、`ReportMapper#findByIdAndDeletedFalseForUpdate`
|
||||
- **测试**:`ReportControllerTest`、`ReportServiceTest`、`ReportSendMigrationTest`
|
||||
- **文档**:`docs/架构决策-报告确认发送状态-2026-08-02.md`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## ReportLead
|
||||
|
||||
### rule:BR-LEAD-001
|
||||
@ -316,7 +326,7 @@
|
||||
|
||||
### rule:BR-CONFIG-003
|
||||
|
||||
**生产发布只读门禁**:正式切流前必须按固定顺序完成备份和四个版本化迁移,再以最终生产配置运行 JPA schema `validate` 与 15 项只读数据不变量检查。运行期 readiness 必须同时覆盖数据库、磁盘、上传目录和 FFmpeg/ffprobe;上线自动 smoke 只允许 GET,不创建或修改业务数据。
|
||||
**生产发布只读门禁**:正式切流前必须按固定顺序完成备份和五个版本化迁移,再以最终生产配置运行 JPA schema `validate` 与 18 项只读数据不变量检查。运行期 readiness 必须同时覆盖数据库、磁盘、上传目录和 FFmpeg/ffprobe;上线自动 smoke 只允许 GET,不创建或修改业务数据。
|
||||
|
||||
- **适用动作**:—(发布全局约束)
|
||||
- **代码**:`backend/src/main/java/com/petstore/config/ProductionDatabasePreflightRunner.java`、`PetstoreRuntimeHealthIndicator.java`、`backend/deploy/release-preflight.sh`、`production-smoke.sh`
|
||||
|
||||
59
qa-reports/phase0-report-send-status-2026-08-02.md
Normal file
59
qa-reports/phase0-report-send-status-2026-08-02.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Phase 0 报告显式确认发送:交付与验收记录
|
||||
|
||||
日期:2026-08-02
|
||||
状态:Code Complete / Ready for Release(生产迁移与真实环境 smoke 待执行)
|
||||
|
||||
## 角色与范围
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| Owner | Report Media Backend / Store Miniapp FE / Store Admin FE |
|
||||
| Paired QA | Report Share QA |
|
||||
| Data Model Review | Yes |
|
||||
| Architecture Review | Yes |
|
||||
| Access / Privacy Review | Yes |
|
||||
| 范围 | Report 发送字段与迁移、确认 API、BusinessEvent、工作台、漏斗、小程序分享弹层、Admin 报告列表、本体与发布文档 |
|
||||
| 明确排除 | 微信投递回执、自动发送、历史发送猜测、再次预约归因、生产数据库变更 |
|
||||
|
||||
## 冻结口径
|
||||
|
||||
- 复制链接、复制话术、二维码、预览或打开报告都不自动计为发送。
|
||||
- 历史报告迁移为 `unknown`;迁移后新报告默认 `unsent`。
|
||||
- 仅本店 boss/staff 可选择 `wechat|qr|other` 显式确认为 `sent`。
|
||||
- 首次回执不可覆盖;重复确认幂等成功,不重复写 `report_sent`。
|
||||
- 工作台只提醒 `unsent`,不把历史 `unknown` 冒充未发送。
|
||||
- 公开 token 响应不返回发送状态、回执或确认员工。
|
||||
|
||||
## 自动验收结果
|
||||
|
||||
| 检查 | 结果 |
|
||||
|---|---|
|
||||
| Backend `mvn test` | PASS:175 tests,0 failures,0 errors |
|
||||
| Backend `mvn -DskipTests package` | PASS |
|
||||
| 迁移 H2 MySQL mode | PASS:历史 unknown、新记录默认 unsent、回执一致性 |
|
||||
| Admin `npm run build` | PASS |
|
||||
| Frontend `npm run build:h5` | PASS |
|
||||
| Frontend `npm run build:mp-weixin` | PASS;`verify-mp-app-json` OK |
|
||||
| Release shell syntax | PASS:`release-preflight.sh`、`production-smoke.sh` |
|
||||
| Ontology validate | PASS:242 条目、引用完整 |
|
||||
| Ontology drift | PASS:57/57 HTTP endpoints,13/13 JPA entities,0 drift |
|
||||
| `git diff --check` | PASS(四仓) |
|
||||
|
||||
## 关键自动化覆盖
|
||||
|
||||
- 新报告强制 `unsent`,忽略请求体伪造的发送字段。
|
||||
- 首次确认写 `sentAt/sentByUserId/sendChannel` 和低敏 `report_sent`。
|
||||
- 重复确认保留首次回执且不重复保存/发事件。
|
||||
- customer、跨店员工、非法渠道与非法筛选状态均拒绝。
|
||||
- 发送确认不改写 `Report.updateTime`,避免重置成片处理超时计时。
|
||||
- Admin 工作台新增 `unsent_report`,准确总数、低敏预览和服务端发送状态筛选均覆盖。
|
||||
- 公开报告响应继续隐藏内部身份、token 和发送回执。
|
||||
|
||||
## 发布前仍需人工完成
|
||||
|
||||
- [ ] 备份并在隔离库恢复验证。
|
||||
- [ ] 按固定顺序执行第五个迁移 `20260801_create_report_send_status.sql`,三项验证均为 0。
|
||||
- [ ] 以最终生产配置运行 JPA `validate` + 18 项只读不变量预检。
|
||||
- [ ] boss/staff/customer 三角色 smoke:复制不变更、三渠道确认、重复确认、跨店拒绝、公开页裁剪。
|
||||
- [ ] 核对工作台 `unsent_report` 与当日 `report_sent` 漏斗数字。
|
||||
- [ ] 生产变更另行授权;本交付未连接或修改生产数据库。
|
||||
30
产品设计文档.md
30
产品设计文档.md
@ -45,15 +45,16 @@
|
||||
## 二、核心功能闭环(v1)
|
||||
|
||||
```
|
||||
预约创建(宠主/老板/员工,下单即生效)→ 待开始(new) → 开始服务(剪毛/正式开做时点击,谁点谁技师)→ 进行中(doing) → 填写报告 → 发送报告 → 已完成(done)
|
||||
预约创建(宠主/老板/员工,下单即生效)→ 待开始(new) → 开始服务(剪毛/正式开做时点击,谁点谁技师)→ 进行中(doing) → 提交报告并完成(done) → 发送给宠主 → 显式确认发送
|
||||
↘ 已取消(cancel) → 在「已完成」Tab 与完成单并列展示
|
||||
```
|
||||
|
||||
**报告分享流程(v1 重点):**
|
||||
```
|
||||
填写报告(照片+备注)→ 点击「发送报告」→ 选择发送方式
|
||||
├── 复制链接(发给宠主微信)
|
||||
└── 下载二维码(现场扫码)
|
||||
填写并提交报告(照片+备注)→ 选择传播方式 → 宠主实际收到 → 员工「确认已发送」
|
||||
├── 复制链接/话术(发给宠主微信)
|
||||
├── 当面展示二维码(现场扫码)
|
||||
└── 其他可核实方式
|
||||
```
|
||||
|
||||
---
|
||||
@ -151,6 +152,10 @@
|
||||
| service_type | VARCHAR | 服务类型(冗余) |
|
||||
| appointment_time | DATETIME | 服务时间(冗余) |
|
||||
| staff_name | VARCHAR | 服务技师名快照(冗余) |
|
||||
| send_status | VARCHAR(16) | unknown(历史不可确认)/ unsent(新报告待发送)/ sent(已显式确认) |
|
||||
| sent_at | DATETIME | 首次显式确认发送时间 |
|
||||
| sent_by_user_id | BIGINT | 首次确认发送员工 |
|
||||
| send_channel | VARCHAR(16) | wechat / qr / other;首次确认后不可覆盖 |
|
||||
| create_time | DATETIME | 创建时间 |
|
||||
| update_time | DATETIME | 更新时间 |
|
||||
|
||||
@ -272,11 +277,18 @@
|
||||
|
||||
### 5.4 发送报告(v1 核心功能)
|
||||
|
||||
**操作入口:** 报告填写完成后,点击「发送报告」按钮
|
||||
**操作入口:** 报告提交成功后进入「发送给宠主」弹层;后台报告列表也可继续处理待发送报告。
|
||||
|
||||
**发送方式:**
|
||||
1. **复制链接** — 生成报告链接,一键复制到剪贴板,可发给宠主微信
|
||||
2. **下载二维码** — 生成报告二维码图片,保存到相册,可打印或现场扫码
|
||||
2. **当面扫码** — 展示报告二维码,宠主现场扫码打开
|
||||
3. **其他方式** — 仅在员工能够确认宠主实际收到时使用
|
||||
|
||||
**确认口径:**
|
||||
- 复制链接、复制话术、生成/展示二维码、预览或打开报告,**都不自动等于已发送**。
|
||||
- 宠主实际收到后,由本店 boss/staff 选择真实渠道并点击「确认已发送」。
|
||||
- 首次确认写入 `sent_at/sent_by_user_id/send_channel` 和 `report_sent` 事件;重复点击幂等成功,不覆盖首次回执。
|
||||
- 历史报告没有可靠发送证据,迁移后统一为 `unknown`;只有迁移后新报告默认 `unsent` 并进入工作台待办。
|
||||
|
||||
**报告链接:**
|
||||
- URL:`https://域名/report.html?token=xxx`
|
||||
@ -393,6 +405,12 @@ ALTER TABLE t_appointment ADD COLUMN duration_minutes INT NOT NULL DEFAULT 60;
|
||||
ALTER TABLE t_schedule_block ADD COLUMN duration_minutes INT NOT NULL DEFAULT 30;
|
||||
ALTER TABLE t_store ADD COLUMN booking_capacity INT NOT NULL DEFAULT 1;
|
||||
|
||||
-- 报告显式确认发送字段(完整历史兼容与验证见 backend/db/migrations/20260801_create_report_send_status.sql)
|
||||
ALTER TABLE t_report ADD COLUMN send_status VARCHAR(16) NOT NULL DEFAULT 'unsent';
|
||||
ALTER TABLE t_report ADD COLUMN sent_at DATETIME NULL;
|
||||
ALTER TABLE t_report ADD COLUMN sent_by_user_id BIGINT NULL;
|
||||
ALTER TABLE t_report ADD COLUMN send_channel VARCHAR(16) NULL;
|
||||
|
||||
-- 报告媒体表(前后图 + 过程素材)
|
||||
CREATE TABLE t_report_image (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
|
||||
| 事件名 | 触发时机 | 关键字段 | 来源 | 统计窗口 | Owner | 落地方式 |
|
||||
|--------|----------|----------|------|----------|-------|----------|
|
||||
| `report_sent` | 门店员工在宠主实际收到后首次显式确认 | `reportId`、`storeId`、`storeCustomerId?`、`actorUserId/role`、`channel`、`occurredAt`;不存 token/手机号/URL | 门店小程序 / Admin | 日 / 周 | Report Media Backend | `POST /api/report/confirm-sent` → BusinessEvent |
|
||||
| `report_opened` | 报告页首次打开(本地 storage 标记首次) | 持久化:`reportId`、`storeId`、`storeCustomerId?`、`visitType=first`、`occurredAt` | H5 报告页 / 小程序报告页 | 日 / 周 | Report Share QA | `POST /api/report/open-track` → BusinessEvent |
|
||||
| `report_reopened` | 报告页再次打开(非首次) | 同上,`visitType=repeat` | 同上 | 日 / 周 | Report Share QA | 同上 |
|
||||
| `highlight_success` | 成片生成成功 | `reportTokenHash`、`appointmentId`、`durationSec`、`composeMode`、`ts` | 后端成片任务 | 日 | Report Media Backend | 任务回调日志 |
|
||||
|
||||
@ -47,10 +47,11 @@
|
||||
| `service_started` | `new -> doing` | appointment ID |
|
||||
| `service_completed` | `doing -> done` | appointment ID |
|
||||
| `report_submitted` | 报告及图片保存成功 | report ID |
|
||||
| `report_sent` | 门店员工首次显式确认宠主实际收到 | report ID;`report_sent:{reportId}` |
|
||||
| `report_opened` / `report_reopened` | 公开报告打开埋点 | 每次有效打开;统计按 report ID 去重 |
|
||||
| `lead_submitted` | 每次留资提交成功 | 可重复记录;漏斗按 lead ID 去重 |
|
||||
|
||||
`report_sent`、`follow_up_completed`、`rebook_created` 尚无可靠状态机或归因字段,本阶段明确不伪造;对应工作台指标返回 `null + trackingReady=false`。
|
||||
> 2026-08-02 增量决策:`report_sent` 已通过显式确认状态机可靠落地;复制链接、二维码和预览不构成发送。详见《架构决策-报告确认发送状态-2026-08-02》。`follow_up_completed`、`rebook_created` 仍无可靠状态机或归因字段,本阶段明确不伪造。
|
||||
|
||||
## 4. 隐私边界
|
||||
|
||||
|
||||
84
架构决策-报告确认发送状态-2026-08-02.md
Normal file
84
架构决策-报告确认发送状态-2026-08-02.md
Normal file
@ -0,0 +1,84 @@
|
||||
# 架构决策:报告显式确认发送状态
|
||||
|
||||
日期:2026-08-02
|
||||
状态:Accepted / Implemented(生产迁移待执行)
|
||||
Owner:Report Media Backend / Store Miniapp FE / Store Admin FE / Data Model
|
||||
Paired QA:Report 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/小程序构建及本体校验/漂移审计通过。
|
||||
@ -29,7 +29,7 @@
|
||||
| Priority | P0 |
|
||||
| Repo/Path | `backend/deploy/**`、前端发布配置、`docs/**` |
|
||||
| Service Lock | Petstore backend unit + Petstore 专用 Nginx server blocks + Petstore static roots |
|
||||
| Depends On | 冻结 RC、真实域名/TLS、备份、四个迁移、微信合法域名 |
|
||||
| Depends On | 冻结 RC、真实域名/TLS、备份、五个迁移、微信合法域名 |
|
||||
| Acceptance | 预检、构建、readiness、只读 smoke、三角色人工 smoke、回滚演练均有证据 |
|
||||
| RC Included | Yes |
|
||||
| Due Date | TBD |
|
||||
@ -47,7 +47,7 @@
|
||||
- 四仓 commit、构建产物或校验和与 RC manifest 不一致;
|
||||
- 生产域名仍为 localhost、`.invalid`、`api.s-good.com` 或 `www.s-good.com` 等非本项目域名;
|
||||
- 数据库备份未完成、校验和缺失或未在隔离库验证可恢复;
|
||||
- 任一迁移末尾验证查询非 0,或预检的 15 项只读不变量非 0;
|
||||
- 任一迁移末尾验证查询非 0,或预检的 18 项只读不变量非 0;
|
||||
- `nginx -t` 失败,或 diff 显示将修改 Gitea/GitLab 的 server block;
|
||||
- readiness 非 `UP`,上传目录/FFmpeg/数据库任一依赖失败;
|
||||
- 发现凭据、完整 token、手机号或私密媒体地址进入日志/工单/截图;
|
||||
@ -107,6 +107,7 @@ shasum -a 256 /secure/backup/petstore-before-<rc>-<timestamp>.sql
|
||||
2. `backend/db/migrations/20260801_create_store_customer.sql`
|
||||
3. `backend/db/migrations/20260801_create_business_event.sql`
|
||||
4. `backend/db/migrations/20260801_create_booking_capacity.sql`
|
||||
5. `backend/db/migrations/20260801_create_report_send_status.sql`
|
||||
|
||||
禁止跳步、交换顺序或让应用自动补表。任一验证项非 0,停止发布并由 Data Model/业务 owner 判断修复方案;不得猜测性回填客户身份、报告作者或事件。
|
||||
|
||||
@ -127,7 +128,7 @@ systemd-run --wait --pipe --collect \
|
||||
./deploy/release-preflight.sh
|
||||
```
|
||||
|
||||
通过条件:静态配置检查通过、JPA `validate` 通过、15 项数据不变量全部为 0,并以退出码 0 结束。预检不会执行迁移或修复数据。
|
||||
通过条件:静态配置检查通过、JPA `validate` 通过、18 项数据不变量全部为 0,并以退出码 0 结束。预检不会执行迁移或修复数据。
|
||||
|
||||
## 6. 构建发布产物
|
||||
|
||||
@ -205,7 +206,7 @@ API_ORIGIN=https://<api-domain> ./deploy/production-smoke.sh
|
||||
|
||||
### 9.2 数据库回滚边界
|
||||
|
||||
本批四个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration,也不把已产生的新业务数据覆盖回旧备份。
|
||||
本批五个迁移以新增列/表/索引和兼容回填为主,旧应用应忽略新增结构。因此常规回滚只回应用,不删除列、表、索引,不执行自动 down migration,也不把已产生的新业务数据覆盖回旧备份。
|
||||
|
||||
只有确认发生灾难性数据破坏、已冻结所有写入、明确接受备份时间点之后的数据丢失,并取得用户/业务负责人、DBA 和隐私责任人的书面授权后,才可执行整库恢复。恢复必须先在隔离库验证,再制定专门变更单;本 Runbook 不授权该操作。
|
||||
|
||||
@ -215,7 +216,7 @@ API_ORIGIN=https://<api-domain> ./deploy/production-smoke.sh
|
||||
|
||||
- 四仓冻结 commit 与产物校验和;
|
||||
- 备份及隔离恢复验证;
|
||||
- 四个迁移执行和末尾验证结果;
|
||||
- 五个迁移执行和末尾验证结果;
|
||||
- 最终配置门禁与后端只读预检;
|
||||
- `nginx -t`、readiness、自动只读 smoke;
|
||||
- boss/staff/customer 三角色人工 smoke;
|
||||
|
||||
@ -51,12 +51,13 @@
|
||||
|
||||
### 业务漏斗
|
||||
|
||||
每天按门店聚合 `appointment_created → service_started → service_completed/report_submitted → report_opened → lead_submitted → rebooked`。试点初期不把业务漏斗波动直接当系统故障,但需检查:
|
||||
每天按门店聚合 `appointment_created → service_started → service_completed/report_submitted → report_sent → report_opened → lead_submitted → rebooked`。试点初期不把业务漏斗波动直接当系统故障,但需检查:
|
||||
|
||||
- 有 done 无报告、报告无客户/作者、重复报告;
|
||||
- BusinessEvent 缺失或幂等键重复;
|
||||
- 服务时长/预约快照非法、容量超卖;
|
||||
- 报告已提交但长期未打开;
|
||||
- 新报告已提交但长期未确认发送;
|
||||
- 已确认发送但长期未打开;
|
||||
- 回访后没有明确状态或再次预约归因。
|
||||
|
||||
发布前的 `release-preflight.sh` 已覆盖前四类结构/数据不变量;运行期应每天做等价的只读审计或报表,不自动修复。
|
||||
@ -82,7 +83,7 @@
|
||||
- [ ] 外部 HTTPS 探测覆盖 liveness/readiness、admin、H5 报告入口;
|
||||
- [ ] 5xx、延迟、DB 池、磁盘、证书和进程重启已配置告警;
|
||||
- [ ] FFmpeg/上传失败有可聚合字段,而非只能搜索自由文本;
|
||||
- [ ] BusinessEvent 日漏斗和 15 项只读数据审计有每日结果;
|
||||
- [ ] BusinessEvent 日漏斗和 18 项只读数据审计有每日结果;
|
||||
- [ ] 通知渠道、Primary/Backup 值班人和升级联系人完成真实测试;
|
||||
- [ ] 日志脱敏抽查通过,访问权限和保留周期已批准;
|
||||
- [ ] 发布标记能关联四仓 commit 与 RC;
|
||||
|
||||
@ -96,7 +96,8 @@
|
||||
| 完整服务数 | 符合上方五项的服务 | 判断样本进度 |
|
||||
| 预约完成率 | done / 有效预约 | 判断主链路稳定性 |
|
||||
| 报告提交率 | 有报告的 done / done | 判断员工执行成本 |
|
||||
| 报告打开率 | 首次打开报告 / 已提交报告 | 判断宠主价值 |
|
||||
| 报告确认发送率 | 显式确认发送 / 已提交报告 | 判断门店触达执行 |
|
||||
| 报告打开率 | 首次打开报告 / 已确认发送报告 | 判断宠主价值;历史 unknown 不纳入分母 |
|
||||
| 留资率 | 去重留资 / 首次打开报告 | 判断回访意愿 |
|
||||
| 再次预约率 | 报告后窗口内产生新预约的 StoreCustomer / 可观察客户 | 判断闭环价值 |
|
||||
| 容量冲突率 | 因无容量被拒绝的请求 / 可约请求 | 校准容量而非承诺零等待 |
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
| 模块 | 状态 | 主要缺口 |
|
||||
|------|------|----------|
|
||||
| A0 壳层登录 | ~95% | 生产微信登录策略另议 |
|
||||
| A1 工作台 | ~98% | 服务端准确行动聚合 + 低敏预览已做;发送状态与转预约归因仍待后续模型,真实环境 smoke 待补 |
|
||||
| A1 工作台 | ~99% | 服务端准确行动聚合、低敏预览、报告待发送行动与显式发送漏斗已做;仅转预约归因和真实环境 smoke 待补 |
|
||||
| A2 预约 | ~95% | 详情/筛选/待出报告/宠主脱敏已做 |
|
||||
| A6 排班 | ~90% | 与小程序对照验收 |
|
||||
| A3 报告 | ~95% | 详情抽屉+失败文案+H5 |
|
||||
|
||||
@ -45,9 +45,10 @@
|
||||
② 服务中等待收尾 n 最近 3 条 …………………… [全部处理]
|
||||
③ 今天接下来到店 n 最近 3 条 …………………… [全部处理]
|
||||
④ 成片异常 n 最近 3 条 …………………… [全部处理]
|
||||
⑤ 回访日期已到 n 最近 3 条 …………………… [全部处理]
|
||||
⑤ 报告尚未发送 n 最近 3 条 …………………… [全部处理]
|
||||
⑥ 回访日期已到 n 最近 3 条 …………………… [全部处理]
|
||||
|
||||
报告漏斗 已提交 | 确认发送— | 已打开 | 已留资 | 转预约—
|
||||
报告漏斗 已提交 | 已确认发送 | 已打开 | 已留资 | 转预约—
|
||||
```
|
||||
|
||||
**主操作**:点指标、行动组或具体低敏预览下钻;工作台常驻,不用先关弹窗。
|
||||
|
||||
@ -105,7 +105,7 @@ python3 /Users/apple/_src/petstore/docs/graph/audit_drift.py
|
||||
| ontologyRefs | `[entity:admin_console, entity:workbench, entity:business_event, action:view_workbench, action:start_service, rule:BR-ADMIN-001, rule:BR-APPT-001, rule:BR-BE-001]` |
|
||||
| Access / Privacy Review Required | Yes |
|
||||
|
||||
**验收**:对齐 PRD §1–3 与线框 A0–A2;staff/boss 可开始服务;customer 进不去;待办下钻参数正确;报告提交/打开/留资来自本店 BusinessEvent 汇总,未跟踪的转预约显示未知。
|
||||
**验收**:对齐 PRD §1–3 与线框 A0–A2;staff/boss 可开始服务;customer 进不去;待办下钻参数正确;报告提交/显式确认发送/打开/留资来自本店 BusinessEvent 汇总,未跟踪的转预约显示未知。
|
||||
**验证**:`AdminBusinessEventControllerTest` + `AdminBusinessEventServiceTest` + admin 构建;发布后按 PRD §3.6 / §2.6 手工冒烟。
|
||||
|
||||
---
|
||||
|
||||
@ -92,7 +92,7 @@ entity:admin_console
|
||||
常驻行动队列(不再每日强弹)
|
||||
报告漏斗四格(提交/打开/留资已由 BusinessEvent 汇总;转预约仍为「—」)
|
||||
每组最多 3 条低敏预览 + 准确总数 + 列表下钻
|
||||
报告漏斗(提交 / 发送未知 / 打开 / 留资 / 再预约未知)
|
||||
报告漏斗(提交 / 显式确认发送 / 打开 / 留资 / 再预约未知)
|
||||
```
|
||||
|
||||
### 2.2 顶栏指标卡
|
||||
@ -112,9 +112,10 @@ entity:admin_console
|
||||
| `in_service` | 服务中,等待收尾 | 所有 `doing`;提交报告后自动退出 | 预约列表 `status=doing&missingReport=1` |
|
||||
| `upcoming_today` | 今天接下来要到店 | 今日尚未到时刻的 `new` | 预约列表 `status=new&date=today` |
|
||||
| `highlight_anomaly` | 成片异常 | `failed` + `processing` 超过 30 分钟 | 报告列表 `highlightStatus=anomaly` |
|
||||
| `unsent_report` | 报告尚未发送 | 仅 `sendStatus=unsent`;历史 `unknown` 不进入待办 | 报告列表 `sendStatus=unsent` |
|
||||
| `follow_up_due` | 回访日期已到 | `pending` 且 `remindDate≤今天` 或日期缺失 | 线索列表 `due=today` |
|
||||
|
||||
优先级固定为:逾期预约 → 服务中 → 今日待开始 → 成片异常 → 到期回访。每组只返回最多 3 条预览,预览不含手机号、报告 token、媒体 URL 或微信标识;准确总数由服务端按完整数据计算。工作台不再每日强弹,避免值班人员为了关弹窗而忽略真正行动。
|
||||
优先级固定为:逾期预约 → 服务中 → 今日待开始 → 成片异常 → 报告待发送 → 到期回访。每组只返回最多 3 条预览,预览不含手机号、报告 token、媒体 URL 或微信标识;准确总数由服务端按完整数据计算。工作台不再每日强弹,避免值班人员为了关弹窗而忽略真正行动。
|
||||
|
||||
**不对标宠老板**:寄养、临期商品、库存、待发货、售后。
|
||||
|
||||
@ -122,7 +123,8 @@ entity:admin_console
|
||||
|
||||
| 格 | 口径 | 当前实现 |
|
||||
|----|------|----------|
|
||||
| 今日已提交报告 | `report_submitted` 按 report 去重 | BusinessEvent 汇总;发送状态尚未建模,不写“已发” |
|
||||
| 今日已提交报告 | `report_submitted` 按 report 去重 | BusinessEvent 汇总 |
|
||||
| 已确认发送 | `report_sent` 按 report 去重 | 仅员工显式确认事实;复制/二维码/预览不计入 |
|
||||
| 已打开 | `report_opened/reopened` 按 report 去重 | BusinessEvent 汇总 |
|
||||
| 已留资 | `lead_submitted` 按 lead 去重 | BusinessEvent 汇总 |
|
||||
| 报告转预约 | `rebook_created` | 尚无可靠归因,显示「—」 |
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
|------|------|------|
|
||||
| 预约状态机 / 号源 / 排班占用 | ✅ | 小程序门店端 |
|
||||
| 服务报告 + 前后照片 + 成片 | ✅ 差异化核心 | 小程序 + 公开 H5 |
|
||||
| 报告分享 / 打开埋点 / 留资回访 | ✅ 部分 | 公开页 + Leads |
|
||||
| 报告分享 / 显式发送确认 / 打开埋点 / 留资回访 | ✅ | 小程序 + Admin + 公开页 + Leads |
|
||||
| 门店设置 / 员工 / 服务类型 | ✅ 基础 | 小程序「我的」 |
|
||||
| 会员卡 / 储值 / 套餐 | ❌ gap 占位 | 本体未展开 |
|
||||
| 收银 / 库存 / 寄养 / 提成 / 进货 | ❌ | 明确不在 P0 |
|
||||
@ -443,7 +443,7 @@ Web 后台主攻:**经营、检索、批量、报表、设置**;**允许「
|
||||
└──────────────────┴──────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
**报告漏斗四格**(替代微信商城四格):`今日已提交报告` · `已打开` · `已留资` · `从报告发起的预约`——前三项已由 BusinessEvent 汇总;报告发送和转预约归因未建模前显示「—」,不以提交冒充发送。
|
||||
**报告漏斗**(替代微信商城状态):`今日已提交报告` · `已确认发送` · `已打开` · `已留资` · `从报告发起的预约`——前四项已由 BusinessEvent 汇总;发送只认员工显式确认,不以提交、复制链接或展示二维码冒充;仅转预约归因仍显示「—」。
|
||||
|
||||
### 11.7 截图覆盖度与仍缺清单(2026-07-10 刷新)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user