docs: define persisted business events

This commit is contained in:
malei 2026-08-01 22:29:20 +08:00
parent 32b073feaa
commit 554063b40c
17 changed files with 260 additions and 83 deletions

View File

@ -45,7 +45,7 @@
项目领域知识以本体论框架建模为 SSOT位于 `docs/ontology/` 项目领域知识以本体论框架建模为 SSOT位于 `docs/ontology/`
- `objects.md` — 实体(`store_customer` 已落为 JPA `StoreCustomer``admin_console`/`workbench` 为后台读模型P1/P2 gap 占位见 membership 等) - `objects.md` — 实体(`store_customer`、`business_event` 已落为 JPA`admin_console`/`workbench` 为后台读模型P1/P2 gap 占位见 membership 等)
- `actions.md` — 动作controller 接口 + 客户端埋点 + **admin_* 后台动作 documented** - `actions.md` — 动作controller 接口 + 客户端埋点 + **admin_* 后台动作 documented**
- `events.md` — 事件(含 `workbench_viewed` gap - `events.md` — 事件(含 `workbench_viewed` gap
- `rules.md` — 规则(含 `rule:BR-ADMIN-001`…`004` 后台门禁与范围闸门) - `rules.md` — 规则(含 `rule:BR-ADMIN-001`…`004` 后台门禁与范围闸门)

View File

@ -59,6 +59,7 @@
| **A12** | **最小测试基线**:补 `SessionTokenServiceTest`、`ReportServiceTest`、`ReportControllerTest`、`AppointmentServiceTest` | `mvn test` 不再输出 `No tests to run`,新增用例全部通过 | Core Flow QA / System Architect | ✅ 已实现Batch D | `mvn -f backend/pom.xml test` | 对应 P0-10持续回归数量以 Surefire 当次统计为准 | | **A12** | **最小测试基线**:补 `SessionTokenServiceTest`、`ReportServiceTest`、`ReportControllerTest`、`AppointmentServiceTest` | `mvn test` 不再输出 `No tests to run`,新增用例全部通过 | Core Flow QA / System Architect | ✅ 已实现Batch D | `mvn -f backend/pom.xml test` | 对应 P0-10持续回归数量以 Surefire 当次统计为准 |
| **A13** | **预约/报告身份语义拆分**:客户、创建人、服务技师、报告作者不得复用同一语义字段 | customer 自约身份从 session 派生门店代约必须提供真实宠主宠主报告按预约客户归属查询legacy 字段只做兼容 | System Architect / Backend Core / Report Media Backend | ✅ 已实现Phase 0 RC1 后) | `mvn -f backend/pom.xml test` + H5/小程序构建 | 迁移见 `backend/db/migrations/20260801_split_service_identity.sql` | | **A13** | **预约/报告身份语义拆分**:客户、创建人、服务技师、报告作者不得复用同一语义字段 | customer 自约身份从 session 派生门店代约必须提供真实宠主宠主报告按预约客户归属查询legacy 字段只做兼容 | System Architect / Backend Core / Report Media Backend | ✅ 已实现Phase 0 RC1 后) | `mvn -f backend/pom.xml test` + H5/小程序构建 | 迁移见 `backend/db/migrations/20260801_split_service_identity.sql` |
| **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` | | **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` |
--- ---
@ -102,8 +103,8 @@
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 | | ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|----|------|----------|-------|------|----------|------| |----|------|----------|-------|------|----------|------|
| **E1** | **分享卡片元信息规则** | 小程序 `onShareAppMessage`:标题「宠物名 + 服务 + 门店」;封面优先级 成片封面 > 服务后首图 > Logo | Customer Experience FE | ✅ 已实现(`reportShare.js` | 手工 smoke + 代码审查 | 见 `frontend/src/utils/reportShare.js` | | **E1** | **分享卡片元信息规则** | 小程序 `onShareAppMessage`:标题「宠物名 + 服务 + 门店」;封面优先级 成片封面 > 服务后首图 > Logo | Customer Experience FE | ✅ 已实现(`reportShare.js` | 手工 smoke + 代码审查 | 见 `frontend/src/utils/reportShare.js` |
| **E2** | **埋点:报告打开** | 上报:`report_token` 匿名 hash、首次/再次、时间戳;落地页 H5 亦要能报 | Report Share QA / Backend Ops | 🟡 部分实现:日志已有,缺事件 schema 与统计落地 | 见《本期主线》埋点 schema 表 | 对应 P0-10、P1-4 | | **E2** | **埋点:报告打开** | 前端提交 token 定位报告;事件表仅存 report ID、首次/再次和时间,不存 token/hashH5/小程序均上报 | Report Share QA / Backend Ops | ✅ 已落 BusinessEvent 并进入工作台去重统计(待生产迁移) | `BusinessEventServiceTest` + admin build | 对应 P0-10、P1-4 |
| **E3** | **埋点事件 schema**(最小集) | 定义 `report_open`、`report_reopen`、`highlight_success`、`highlight_failed`、`video_play`、`video_save`、`lead_submit` 字段、来源、统计窗口 | Report Share QA / Product Design | ✅ 已冻结Batch A 文档) | 见《本期主线-下一步产品优化点》埋点 schema 表 | 对应 P1-4 | | **E3** | **埋点事件 schema**(最小集) | 定义 `report_opened`、`report_reopened`、`highlight_succeeded`、`highlight_failed`、`video_play`、`video_save`、`lead_submitted` 字段、来源、统计窗口 | Report Share QA / Product Design | ✅ 已冻结;核心履约/打开/留资已落库,成片与客户端播放事件待接入 | 见《本期主线-下一步产品优化点》埋点 schema 表 | 对应 P1-4 |
--- ---

View File

@ -3,7 +3,7 @@
> 本体条目 ↔ 文档章节 ↔ 代码路径 ↔ 测试用例 三方对齐审计。 > 本体条目 ↔ 文档章节 ↔ 代码路径 ↔ 测试用例 三方对齐审计。
> 证据强度:`anchored`(三处对齐)/ `documented`(仅文档)/ `implemented`(仅代码)/ `gap`(缺失)。 > 证据强度:`anchored`(三处对齐)/ `documented`(仅文档)/ `implemented`(仅代码)/ `gap`(缺失)。
> >
> **刷新时间**2026-07-10Batch 补全domain 回填、敏感字段规则、寄语/退订/周期规则、客户端埋点 Action、P1/P2 gap 占位 > **刷新时间**2026-08-01BusinessEvent 落库、工作台事件漏斗、StoreCustomer 主档与身份语义收口后刷新
## 审计方法 ## 审计方法
@ -18,14 +18,14 @@
| 类型 | 数量 | | 类型 | 数量 |
|------|------| |------|------|
| Entity | 20 | | Entity | 25 |
| Action | 56 | | Action | 64 |
| Event | 23 | | Event | 26 |
| Rule | 34 | | Rule | 40 |
| Relation | 56 | | Relation | 81 |
| **合计** | **189** | | **合计** | **236** |
证据强度:`anchored` 15883.6%/ `documented` 18 / `gap` 11 / `implemented` 2。 证据强度:`anchored` 18578.4%/ `documented` 37 / `gap` 12 / `implemented` 2。
> anchored 占比相对 Batch 2 下降,是因为主动录入了 P1/P2 `gap` 占位与客户端埋点 `documented` 条目;端点漂移仍为 0。 > anchored 占比相对 Batch 2 下降,是因为主动录入了 P1/P2 `gap` 占位与客户端埋点 `documented` 条目;端点漂移仍为 0。
@ -36,7 +36,7 @@ python3 docs/graph/validate_ontology.py docs
python3 docs/graph/audit_drift.py python3 docs/graph/audit_drift.py
``` ```
当前结果校验通过HTTP 端点 52/52、JPA Entity 11/11 对齐;非 JPA 领域实体 9 个(含 HighlightVideo、SessionToken、CurrentUser、HighlightFailReason 与 5 个 roadmap gap 当前结果校验通过HTTP 端点 55/55、JPA Entity 13/13 对齐;非 JPA 领域实体 12 个
## 元数据卫生 ## 元数据卫生
@ -64,11 +64,13 @@ python3 docs/graph/audit_drift.py
| session_token | ✅ | ✅ | SessionTokenServiceTest | anchored | jpa:false | | session_token | ✅ | ✅ | SessionTokenServiceTest | anchored | jpa:false |
| report_testimonial | ✅ | ✅ | ReportTestimonialServiceTest | anchored | | | report_testimonial | ✅ | ✅ | ReportTestimonialServiceTest | anchored | |
| service_interval | ✅ | ✅ | 间接 ReportLeadControllerTest | anchored | | | service_interval | ✅ | ✅ | 间接 ReportLeadControllerTest | anchored | |
| store_customer | ✅ | ✅ | StoreCustomerServiceTest / IdentityOwnershipQueryTest | anchored | 稳定门店客户关系 |
| business_event | ✅ | ✅ | BusinessEventServiceTest / MigrationTest | anchored | 不可变低敏事件;工作台去重统计 |
| highlight_fail_reason | ✅ | ✅ 枚举 | ReportHighlightVideoServiceTest | anchored | jpa:false | | highlight_fail_reason | ✅ | ✅ 枚举 | ReportHighlightVideoServiceTest | anchored | jpa:false |
| current_user | ✅ | ✅ record | SessionTokenServiceTest | anchored | jpa:false | | current_user | ✅ | ✅ record | SessionTokenServiceTest | anchored | jpa:false |
| membership 等 5 个 | 方向库 | — | — | gap | roadmap 占位 | | membership 等 5 个 | 方向库 | — | — | gap | roadmap 占位 |
## 规则覆盖度(34 条) ## 规则覆盖度40 条)
| 规则 | 测试 | 证据 | | 规则 | 测试 | 证据 |
|------|------|------| |------|------|------|
@ -88,6 +90,7 @@ python3 docs/graph/audit_drift.py
| BR-CONFIG-001/002 | —(配置) | anchored | | BR-CONFIG-001/002 | —(配置) | anchored |
| BR-TST-001 一报告一寄语 | ReportTestimonialServiceTest | anchored | | BR-TST-001 一报告一寄语 | ReportTestimonialServiceTest | anchored |
| BR-UNSUB-001 / BR-INT-001 | ReportLeadControllerTest | anchored | | BR-UNSUB-001 / BR-INT-001 | ReportLeadControllerTest | anchored |
| BR-SC-001 / BR-BE-001 | StoreCustomerServiceTest / BusinessEventServiceTest / MigrationTest | anchored |
## 待补 gap 清单 ## 待补 gap 清单
@ -100,7 +103,7 @@ python3 docs/graph/audit_drift.py
| ~~高~~ | ~~寄语幂等独立用例~~ | ✅ `ReportTestimonialServiceTest` | | ~~高~~ | ~~寄语幂等独立用例~~ | ✅ `ReportTestimonialServiceTest` |
| ~~中~~ | ~~`rule:BR-STORE-003` 公开 Store 字段裁剪~~ | ✅ `toPublicStoreView` | | ~~中~~ | ~~`rule:BR-STORE-003` 公开 Store 字段裁剪~~ | ✅ `toPublicStoreView` |
| 中 | `uk_report_appointment` 上线迁移 | 🟡 运维 | | 中 | `uk_report_appointment` 上线迁移 | 🟡 运维 |
| 中 | 埋点事件可统计落地 | 🟡 P1 | | ~~中~~ | ~~预约/履约/报告打开/留资事件可统计落地~~ | ✅ BusinessEvent成片/播放/保存事件另行接入 |
| 中 | production profile 实测启动 | 🟡 | | 中 | production profile 实测启动 | 🟡 |
| 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg | 🟡 单测已覆盖状态机与归类 | | 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg | 🟡 单测已覆盖状态机与归类 |
| 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap | | 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap |
@ -114,14 +117,14 @@ python3 docs/graph/audit_drift.py
本期不实现,仅 `evidence=gap` + `jpa=false`,避免本体膨胀成未实现产品说明书。受 `rule:BR-ADMIN-004` 约束,**不得写入 Phase A 门店后台设计**。 本期不实现,仅 `evidence=gap` + `jpa=false`,避免本体膨胀成未实现产品说明书。受 `rule:BR-ADMIN-004` 约束,**不得写入 Phase A 门店后台设计**。
## 门店 Web 后台 Phase A 预录入2026-07-10 ## 门店 Web 后台 Phase A 状态2026-08-01
> 产品:`docs/门店管理后台升级方案-对标宠老板.md`。全部 `documented` / `gap`**尚未编码** > 产品:`docs/门店管理后台升级方案-对标宠老板.md`。后台工程已建立;各对象按当前 anchored/documented/gap 证据维护
| ID | 类型 | 用途 | | ID | 类型 | 用途 |
|----|------|------| |----|------|------|
| `entity:admin_console` | Entity | Web 管理端壳 | | `entity:admin_console` | Entity | Web 管理端壳 |
| `entity:workbench` / `workbench_todo_item` | Entity | 今日工作台与待办 | | `entity:workbench` / `workbench_todo_item` | Entity | 今日工作台与待办;报告事件漏斗已 anchored |
| `entity:store_customer` | Entity | 门店客户稳定主档(非会员卡) | | `entity:store_customer` | Entity | 门店客户稳定主档(非会员卡) |
| `action:view_workbench` 等 8 个 admin_* | Action | 工作台/列表/设置/排班 | | `action:view_workbench` 等 8 个 admin_* | Action | 工作台/列表/设置/排班 |
| `rule:BR-ADMIN-001`…`004` | Rule | 角色、店范围、内部字段、范围闸门 | | `rule:BR-ADMIN-001`…`004` | Rule | 角色、店范围、内部字段、范围闸门 |
@ -131,4 +134,4 @@ python3 docs/graph/audit_drift.py
**页面清单 PRD已写**`docs/门店管理后台-PhaseA-页面清单PRD.md`A0A7 全页字段/筛选/API/验收)。 **页面清单 PRD已写**`docs/门店管理后台-PhaseA-页面清单PRD.md`A0A7 全页字段/筛选/API/验收)。
当前 validate 规模以脚本输出为准(约 222 条目)。 当前 validate 规模以脚本输出为准(236 条目)。

View File

@ -3,6 +3,7 @@
{"id":"entity:appointment","type":"Entity","name":"Appointment","nameZh":"预约","domain":"appointment","fields":["id","pet_name","pet_type","service_type","appointment_time","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","test":"AppointmentServiceTest; AppointmentControllerDetailTest; AppointmentControllerIdentityTest","evidence":"anchored"} {"id":"entity:appointment","type":"Entity","name":"Appointment","nameZh":"预约","domain":"appointment","fields":["id","pet_name","pet_type","service_type","appointment_time","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","test":"AppointmentServiceTest; AppointmentControllerDetailTest; AppointmentControllerIdentityTest","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","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_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: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"} {"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"}
{"id":"action:update_store","type":"Action","name":"updateStore","nameZh":"更新店铺","inputs":["entity:store"],"outputs":["entity:store"],"entrypoints":["PUT /api/store/update","StoreController#update","StoreService#update"],"appliesRules":["rule:BR-STORE-001"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#update","test":"StoreControllerTest","evidence":"anchored","domain":"store"} {"id":"action:update_store","type":"Action","name":"updateStore","nameZh":"更新店铺","inputs":["entity:store"],"outputs":["entity:store"],"entrypoints":["PUT /api/store/update","StoreController#update","StoreService#update"],"appliesRules":["rule:BR-STORE-001"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#update","test":"StoreControllerTest","evidence":"anchored","domain":"store"}
{"id":"action:delete_store","type":"Action","name":"deleteStore","nameZh":"删除店铺","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/store/delete","StoreController#delete"],"appliesRules":["rule:BR-STORE-001"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#delete","test":"StoreControllerTest","evidence":"anchored","domain":"store"} {"id":"action:delete_store","type":"Action","name":"deleteStore","nameZh":"删除店铺","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/store/delete","StoreController#delete"],"appliesRules":["rule:BR-STORE-001"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#delete","test":"StoreControllerTest","evidence":"anchored","domain":"store"}
@ -10,27 +11,29 @@
{"id":"action:login","type":"Action","name":"login","nameZh":"手机号验证码登录","inputs":["phone","code"],"outputs":["entity:user","entity:store","session_token"],"entrypoints":["POST /api/user/login","UserService#login"],"appliesRules":["rule:BR-AUTH-001","rule:BR-AUTH-002"],"code":"backend/src/main/java/com/petstore/service/UserService.java#login","test":"UserServiceLoginStrategyTest","evidence":"anchored","domain":"auth"} {"id":"action:login","type":"Action","name":"login","nameZh":"手机号验证码登录","inputs":["phone","code"],"outputs":["entity:user","entity:store","session_token"],"entrypoints":["POST /api/user/login","UserService#login"],"appliesRules":["rule:BR-AUTH-001","rule:BR-AUTH-002"],"code":"backend/src/main/java/com/petstore/service/UserService.java#login","test":"UserServiceLoginStrategyTest","evidence":"anchored","domain":"auth"}
{"id":"action:wx_phone_login","type":"Action","name":"wxPhoneLogin","nameZh":"微信手机号授权登录","inputs":["phoneCode","loginCode"],"outputs":["entity:user","entity:store","session_token"],"entrypoints":["POST /api/user/wx-phone-login","UserController#wxPhoneLogin","UserService#loginByVerifiedPhone"],"appliesRules":["rule:BR-AUTH-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#wxPhoneLogin","evidence":"anchored","domain":"auth"} {"id":"action:wx_phone_login","type":"Action","name":"wxPhoneLogin","nameZh":"微信手机号授权登录","inputs":["phoneCode","loginCode"],"outputs":["entity:user","entity:store","session_token"],"entrypoints":["POST /api/user/wx-phone-login","UserController#wxPhoneLogin","UserService#loginByVerifiedPhone"],"appliesRules":["rule:BR-AUTH-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#wxPhoneLogin","evidence":"anchored","domain":"auth"}
{"id":"action:bind_wechat","type":"Action","name":"bindWechat","nameZh":"绑定微信标识","inputs":["userId","openid","unionid"],"outputs":["entity:user"],"entrypoints":["UserService#bindWechatMiniIdentity"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity","evidence":"anchored","domain":"auth"} {"id":"action:bind_wechat","type":"Action","name":"bindWechat","nameZh":"绑定微信标识","inputs":["userId","openid","unionid"],"outputs":["entity:user"],"entrypoints":["UserService#bindWechatMiniIdentity"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity","evidence":"anchored","domain":"auth"}
{"id":"action:create_appointment","type":"Action","name":"createAppointment","nameZh":"创建预约","inputs":["petName","petType","serviceType","appointmentTime","storeId","remark","petId","customerUserId","customerPhone","customerName"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/create","AppointmentController#create","AppointmentService#createBooking"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-003","rule:BR-APPT-004","rule:BR-SC-001"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#create; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchBooking","test":"AppointmentControllerIdentityTest; AppointmentServiceTest; StoreCustomerServiceTest","evidence":"anchored","domain":"appointment"} {"id":"action:create_appointment","type":"Action","name":"createAppointment","nameZh":"创建预约","inputs":["petName","petType","serviceType","appointmentTime","storeId","remark","petId","customerUserId","customerPhone","customerName"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/create","AppointmentController#create","AppointmentService#createBooking"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-003","rule:BR-APPT-004","rule:BR-SC-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#create; backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchBooking; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentCreated","test":"AppointmentControllerIdentityTest; AppointmentServiceTest; StoreCustomerServiceTest; BusinessEventServiceTest","evidence":"anchored","domain":"appointment"}
{"id":"action:start_service","type":"Action","name":"startService","nameZh":"开始服务","inputs":["appointmentId"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/start","AppointmentController#start","AppointmentService#startService"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-002"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#start","test":"AppointmentServiceTest","evidence":"anchored","domain":"appointment"} {"id":"action:start_service","type":"Action","name":"startService","nameZh":"开始服务","inputs":["appointmentId"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/start","AppointmentController#start","AppointmentService#startService"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-002","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#start; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged","test":"AppointmentServiceTest; BusinessEventServiceTest","evidence":"anchored","domain":"appointment"}
{"id":"action:transition_appointment_status","type":"Action","name":"transitionAppointmentStatus","nameZh":"更新预约状态","inputs":["id","status"],"outputs":["entity:appointment"],"entrypoints":["PUT /api/appointment/status","AppointmentController#updateStatus","AppointmentService#transitionStatus"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-002","rule:BR-APPT-003"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#updateStatus","test":"AppointmentServiceTest","evidence":"anchored","domain":"appointment"} {"id":"action:transition_appointment_status","type":"Action","name":"transitionAppointmentStatus","nameZh":"更新预约状态","inputs":["id","status"],"outputs":["entity:appointment"],"entrypoints":["PUT /api/appointment/status","AppointmentController#updateStatus","AppointmentService#transitionStatus"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-002","rule:BR-APPT-003","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#updateStatus; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged","test":"AppointmentServiceTest; BusinessEventServiceTest","evidence":"anchored","domain":"appointment"}
{"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: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: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"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create","test":"ReportControllerTest; ReportServiceTest","evidence":"anchored","domain":"report","note":"authorStaffId 从登录上下文派生customerUserId 从预约派生"} {"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: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: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":[],"entrypoints":["POST /api/report/open-track","ReportController#trackReportOpen"],"appliesRules":["rule:BR-RPT-007"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen","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"} {"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"}
{"id":"action:submit_lead","type":"Action","name":"submitLead","nameZh":"宠主留资","inputs":["token","phone","consent","loginCode"],"outputs":["leadId","remindDate","unsubscribeToken","wechatBound"],"entrypoints":["POST /api/report/{token}/reminder","ReportLeadController#submitReminder","ReportLeadService#submit"],"appliesRules":["rule:BR-LEAD-001","rule:BR-SC-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchLead","evidence":"anchored","test":"ReportLeadControllerTest; ReportLeadServiceStoreCustomerTest; StoreCustomerServiceTest","domain":"lead"} {"id":"action:submit_lead","type":"Action","name":"submitLead","nameZh":"宠主留资","inputs":["token","phone","consent","loginCode"],"outputs":["leadId","remindDate","unsubscribeToken","wechatBound"],"entrypoints":["POST /api/report/{token}/reminder","ReportLeadController#submitReminder","ReportLeadService#submit"],"appliesRules":["rule:BR-LEAD-001","rule:BR-SC-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder; backend/src/main/java/com/petstore/service/ReportLeadService.java#submit; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchLead; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordLeadSubmitted","evidence":"anchored","test":"ReportLeadControllerTest; ReportLeadServiceStoreCustomerTest; StoreCustomerServiceTest; BusinessEventServiceTest","domain":"lead"}
{"id":"action:unsubscribe","type":"Action","name":"unsubscribe","nameZh":"一键退订","inputs":["unsubscribeToken"],"outputs":[],"entrypoints":["POST /api/report/unsubscribe","ReportLeadController#unsubscribe"],"appliesRules":["rule:BR-UNSUB-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe","evidence":"anchored","domain":"lead","test":"ReportLeadControllerTest"} {"id":"action:unsubscribe","type":"Action","name":"unsubscribe","nameZh":"一键退订","inputs":["unsubscribeToken"],"outputs":[],"entrypoints":["POST /api/report/unsubscribe","ReportLeadController#unsubscribe"],"appliesRules":["rule:BR-UNSUB-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe","evidence":"anchored","domain":"lead","test":"ReportLeadControllerTest"}
{"id":"action:list_leads","type":"Action","name":"listLeads","nameZh":"回访池列表","inputs":["status"],"outputs":["entity:report_lead"],"entrypoints":["GET /api/report/leads","ReportLeadController#leads"],"appliesRules":["rule:BR-LEAD-002","rule:BR-LEAD-003"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads","evidence":"anchored","test":"ReportLeadControllerTest","domain":"lead"} {"id":"action:list_leads","type":"Action","name":"listLeads","nameZh":"回访池列表","inputs":["status"],"outputs":["entity:report_lead"],"entrypoints":["GET /api/report/leads","ReportLeadController#leads"],"appliesRules":["rule:BR-LEAD-002","rule:BR-LEAD-003"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads","evidence":"anchored","test":"ReportLeadControllerTest","domain":"lead"}
{"id":"event:store_registered","type":"Event","name":"StoreRegistered","nameZh":"门店入驻","domain":"store","payload":["storeId","bossUserId","inviteCode"],"emittedBy":["action:register_boss"],"evidence":"anchored"} {"id":"event:store_registered","type":"Event","name":"StoreRegistered","nameZh":"门店入驻","domain":"store","payload":["storeId","bossUserId","inviteCode"],"emittedBy":["action:register_boss"],"evidence":"anchored"}
{"id":"event:session_issued","type":"Event","name":"SessionIssued","nameZh":"签发会话令牌","domain":"auth","payload":["userId","storeId","role","exp"],"emittedBy":["action:register_boss","action:login","action:wx_phone_login"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java#issue","test":"SessionTokenServiceTest","evidence":"anchored"} {"id":"event:session_issued","type":"Event","name":"SessionIssued","nameZh":"签发会话令牌","domain":"auth","payload":["userId","storeId","role","exp"],"emittedBy":["action:register_boss","action:login","action:wx_phone_login"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java#issue","test":"SessionTokenServiceTest","evidence":"anchored"}
{"id":"event:wx_bound","type":"Event","name":"WxBound","nameZh":"微信标识绑定","domain":"auth","payload":["userId","openid","unionid"],"emittedBy":["action:bind_wechat"],"code":"backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity","evidence":"anchored"} {"id":"event:wx_bound","type":"Event","name":"WxBound","nameZh":"微信标识绑定","domain":"auth","payload":["userId","openid","unionid"],"emittedBy":["action:bind_wechat"],"code":"backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity","evidence":"anchored"}
{"id":"event:appointment_created","type":"Event","name":"AppointmentCreated","nameZh":"预约创建","domain":"appointment","payload":["appointmentId","storeId","customerUserId","createdByUserId","appointmentTime"],"emittedBy":["action:create_appointment"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking","evidence":"anchored"} {"id":"event:appointment_created","type":"Event","name":"AppointmentCreated","nameZh":"预约创建","domain":"appointment","payload":["storeId","storeCustomerId","aggregateId","actorUserId","actorRole","bookingOrigin","occurredAt"],"emittedBy":["action:create_appointment"],"appliesRules":["rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentCreated","test":"AppointmentServiceTest; BusinessEventServiceTest","evidence":"anchored"}
{"id":"event:appointment_status_changed","type":"Event","name":"AppointmentStatusChanged","nameZh":"预约状态迁移","domain":"appointment","payload":["appointmentId","fromStatus","toStatus","assignedUserId"],"emittedBy":["action:start_service","action:transition_appointment_status","action:submit_report"],"appliesRules":["rule:BR-APPT-001"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#startService; #transitionStatus; ReportService#create","test":"AppointmentServiceTest; ReportServiceTest#createDoingAppointmentMarksDone","evidence":"anchored"} {"id":"event:appointment_status_changed","type":"Event","name":"AppointmentStatusChanged","nameZh":"预约状态迁移","domain":"appointment","payload":["storeId","storeCustomerId","aggregateId","fromStatus","toStatus","actorUserId","actorRole","occurredAt"],"emittedBy":["action:start_service","action:transition_appointment_status","action:submit_report"],"appliesRules":["rule:BR-APPT-001","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#startService; #transitionStatus; backend/src/main/java/com/petstore/service/ReportService.java#create; backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged","test":"AppointmentServiceTest; ReportServiceTest#createDoingAppointmentMarksDone; BusinessEventServiceTest","evidence":"anchored"}
{"id":"event:report_submitted","type":"Event","name":"ReportSubmitted","nameZh":"报告提交","domain":"report","payload":["reportId","appointmentId","storeId","reportToken"],"emittedBy":["action:submit_report"],"appliesRules":["rule:BR-RPT-001","rule:BR-RPT-002","rule:BR-RPT-004"],"code":"backend/src/main/java/com/petstore/service/ReportService.java#create","test":"ReportServiceTest","evidence":"anchored"} {"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:report_opened","type":"Event","name":"ReportOpened","nameZh":"报告首次打开","domain":"report","payload":["reportTokenHash","isFirstOpen","source","ts"],"emittedBy":["action:track_report_open"],"appliesRules":["rule:BR-RPT-007"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","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_reopened","type":"Event","name":"ReportReopened","nameZh":"报告再次打开","domain":"report","payload":["reportTokenHash","isFirstOpen","source","ts"],"emittedBy":["action:track_report_open"],"doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"} {"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:lead_submitted","type":"Event","name":"LeadSubmitted","nameZh":"留资提交","domain":"lead","payload":["reportTokenHash","storeId","hasPhone","hasPetName","repeatSubmit","ts"],"emittedBy":["action:submit_lead"],"appliesRules":["rule:BR-LEAD-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"} {"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"}
{"id":"rule:BR-AUTH-001","type":"Rule","name":"HMAC session token 鉴权","domain":"auth","appliesTo":["action:register_boss","action:login","action:wx_phone_login","action:update_store","action:delete_store","action:get_store_by_invite_code","action:create_appointment","action:start_service","action:transition_appointment_status","action:get_appointment_detail","action:list_appointments","action:submit_report","action:get_report_by_token","action:track_report_open","action:delete_report","action:list_leads"],"code":"backend/src/main/java/com/petstore/auth/AuthInterceptor.java; SessionTokenService.java; CurrentUserContext.java","test":"SessionTokenServiceTest","evidence":"anchored"} {"id":"rule:BR-AUTH-001","type":"Rule","name":"HMAC session token 鉴权","domain":"auth","appliesTo":["action:register_boss","action:login","action:wx_phone_login","action:update_store","action:delete_store","action:get_store_by_invite_code","action:create_appointment","action:start_service","action:transition_appointment_status","action:get_appointment_detail","action:list_appointments","action:submit_report","action:get_report_by_token","action:track_report_open","action:delete_report","action:list_leads"],"code":"backend/src/main/java/com/petstore/auth/AuthInterceptor.java; SessionTokenService.java; CurrentUserContext.java","test":"SessionTokenServiceTest","evidence":"anchored"}
{"id":"rule:BR-AUTH-002","type":"Rule","name":"生产登录策略:仅微信授权","domain":"auth","appliesTo":["action:login","action:wx_phone_login"],"code":"backend/src/main/java/com/petstore/service/UserService.java#login; SmsController.java","test":"UserServiceLoginStrategyTest","evidence":"anchored"} {"id":"rule:BR-AUTH-002","type":"Rule","name":"生产登录策略:仅微信授权","domain":"auth","appliesTo":["action:login","action:wx_phone_login"],"code":"backend/src/main/java/com/petstore/service/UserService.java#login; SmsController.java","test":"UserServiceLoginStrategyTest","evidence":"anchored"}
{"id":"rule:BR-STORE-001","type":"Rule","name":"店铺更新/删除仅 boss + 同店","domain":"store","appliesTo":["action:update_store","action:delete_store"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#update; #delete","test":"StoreControllerTest","evidence":"anchored"} {"id":"rule:BR-STORE-001","type":"Rule","name":"店铺更新/删除仅 boss + 同店","domain":"store","appliesTo":["action:update_store","action:delete_store"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#update; #delete","test":"StoreControllerTest","evidence":"anchored"}
@ -48,6 +51,7 @@
{"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-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-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-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-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"}
{"id":"rule:BR-LEAD-002","type":"Rule","name":"留资标识脱敏","domain":"lead","appliesTo":["action:list_leads"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#maskIdentifier; #leads","frontend":"frontend/src/pages/mine/Leads.vue","evidence":"anchored"} {"id":"rule:BR-LEAD-002","type":"Rule","name":"留资标识脱敏","domain":"lead","appliesTo":["action:list_leads"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#maskIdentifier; #leads","frontend":"frontend/src/pages/mine/Leads.vue","evidence":"anchored"}
{"id":"rule:BR-LEAD-003","type":"Rule","name":"回访池跨店 403","domain":"lead","appliesTo":["action:list_leads"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads","evidence":"anchored"} {"id":"rule:BR-LEAD-003","type":"Rule","name":"回访池跨店 403","domain":"lead","appliesTo":["action:list_leads"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads","evidence":"anchored"}
{"id":"rel:store_has_users","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:user","cardinality":"1—N","evidence":"anchored","doc":"User.store_id → Store.id","domain":"store"} {"id":"rel:store_has_users","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:user","cardinality":"1—N","evidence":"anchored","doc":"User.store_id → Store.id","domain":"store"}
@ -70,9 +74,13 @@
{"id":"rel:wx_phone_login_emits_wx_bound","type":"Relation","subject":"action:wx_phone_login","predicate":"EMITS","object":"event:wx_bound","evidence":"anchored","domain":"auth"} {"id":"rel:wx_phone_login_emits_wx_bound","type":"Relation","subject":"action:wx_phone_login","predicate":"EMITS","object":"event:wx_bound","evidence":"anchored","domain":"auth"}
{"id":"rel:create_appointment_emits_appointment_created","type":"Relation","subject":"action:create_appointment","predicate":"EMITS","object":"event:appointment_created","evidence":"anchored","domain":"appointment"} {"id":"rel:create_appointment_emits_appointment_created","type":"Relation","subject":"action:create_appointment","predicate":"EMITS","object":"event:appointment_created","evidence":"anchored","domain":"appointment"}
{"id":"rel:start_service_emits_status_changed","type":"Relation","subject":"action:start_service","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","domain":"appointment"} {"id":"rel:start_service_emits_status_changed","type":"Relation","subject":"action:start_service","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","domain":"appointment"}
{"id":"rel:start_service_emits_service_started","type":"Relation","subject":"action:start_service","predicate":"EMITS","object":"event:service_started","evidence":"anchored","domain":"appointment"}
{"id":"rel:transition_status_emits_status_changed","type":"Relation","subject":"action:transition_appointment_status","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","domain":"appointment"} {"id":"rel:transition_status_emits_status_changed","type":"Relation","subject":"action:transition_appointment_status","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","domain":"appointment"}
{"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_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_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: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"} {"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"}
{"id":"rel:track_report_open_emits_report_reopened","type":"Relation","subject":"action:track_report_open","predicate":"EMITS","object":"event:report_reopened","evidence":"documented","domain":"report"} {"id":"rel:track_report_open_emits_report_reopened","type":"Relation","subject":"action:track_report_open","predicate":"EMITS","object":"event:report_reopened","evidence":"documented","domain":"report"}
{"id":"rel:submit_lead_emits_lead_submitted","type":"Relation","subject":"action:submit_lead","predicate":"EMITS","object":"event:lead_submitted","evidence":"documented","domain":"lead"} {"id":"rel:submit_lead_emits_lead_submitted","type":"Relation","subject":"action:submit_lead","predicate":"EMITS","object":"event:lead_submitted","evidence":"documented","domain":"lead"}
@ -192,14 +200,14 @@
{"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_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":"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: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","pendingLeadCount","highlightFailedCount","todoItems","reportFunnel","anomalyList"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"","doc":"docs/ontology/objects.md#entity:workbench","test":"","evidence":"documented","note":"登录落点;含今日待办弹层与指标卡。聚合 appointment/report/lead/highlight 现有数据,非独立表。","status":"planned"} {"id":"entity:workbench","type":"Entity","name":"Workbench","nameZh":"今日工作台","domain":"admin","jpa":false,"fields":["todayAppointmentCount","todayDoneCount","pendingLeadCount","highlightFailedCount","todoItems","reportFunnel","anomalyList"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"admin/src/views/WorkbenchView.vue; backend/src/main/java/com/petstore/service/AdminBusinessEventService.java","doc":"docs/ontology/objects.md#entity:workbench","test":"AdminBusinessEventServiceTest","evidence":"anchored","note":"登录落点;待办复用现有接口,报告提交/打开/留资来自 BusinessEvent发送与转预约未跟踪时显示未知。","status":"implemented"}
{"id":"entity:workbench_todo_item","type":"Entity","name":"WorkbenchTodoItem","nameZh":"工作台待办项","domain":"admin","jpa":false,"fields":["kind","count","drilldownAction","filter"],"systems":["Store Admin FE"],"code":"","doc":"docs/ontology/objects.md#entity:workbench_todo_item","test":"","evidence":"documented","note":"kind: today_appointments | pending_report | highlight_anomaly | pending_lead","status":"planned"} {"id":"entity:workbench_todo_item","type":"Entity","name":"WorkbenchTodoItem","nameZh":"工作台待办项","domain":"admin","jpa":false,"fields":["kind","count","drilldownAction","filter"],"systems":["Store Admin FE"],"code":"","doc":"docs/ontology/objects.md#entity:workbench_todo_item","test":"","evidence":"documented","note":"kind: today_appointments | pending_report | highlight_anomaly | pending_lead","status":"planned"}
{"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":"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 禁止。删店/部分设置仍仅 bossBR-STORE-001/BR-USER-001。产品决策 2026-07-10。"} {"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 禁止。删店/部分设置仍仅 bossBR-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。"} {"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。"}
{"id":"rule:BR-ADMIN-003","type":"Rule","name":"adminInternalFieldsAllowed","nameZh":"后台可看内部字段(与公开裁剪对称)","domain":"admin","appliesTo":["action:admin_list_reports","action:admin_update_settings","action:admin_list_service_customers"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-003","test":"","evidence":"documented","note":"经 BR-ADMIN-001 鉴权后,后台可返回 inviteCode、完整报告内部字段等公开 API 仍受 BR-STORE-003/BR-RPT-006 约束。手机号默认仍脱敏,明文需更高权限(预留)。"} {"id":"rule:BR-ADMIN-003","type":"Rule","name":"adminInternalFieldsAllowed","nameZh":"后台可看内部字段(与公开裁剪对称)","domain":"admin","appliesTo":["action:admin_list_reports","action:admin_update_settings","action:admin_list_service_customers"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-003","test":"","evidence":"documented","note":"经 BR-ADMIN-001 鉴权后,后台可返回 inviteCode、完整报告内部字段等公开 API 仍受 BR-STORE-003/BR-RPT-006 约束。手机号默认仍脱敏,明文需更高权限(预留)。"}
{"id":"rule:BR-ADMIN-004","type":"Rule","name":"adminPhaseAOutOfScope","nameZh":"Phase A 后台范围闸门","domain":"admin","appliesTo":["entity:admin_console","entity:workbench"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-004","test":"","evidence":"documented","note":"禁止将 membership/stored_value/service_package/收银/库存/寄养/押金/点餐/支付宝渠道 写入 Phase A 后台范围或设计稿。上述保持 evidence=gap。"} {"id":"rule:BR-ADMIN-004","type":"Rule","name":"adminPhaseAOutOfScope","nameZh":"Phase A 后台范围闸门","domain":"admin","appliesTo":["entity:admin_console","entity:workbench"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-004","test":"","evidence":"documented","note":"禁止将 membership/stored_value/service_package/收银/库存/寄养/押金/点餐/支付宝渠道 写入 Phase A 后台范围或设计稿。上述保持 evidence=gap。"}
{"id":"action:view_workbench","type":"Action","name":"viewWorkbench","nameZh":"查看今日工作台","domain":"admin","inputs":["entity:current_user"],"outputs":["entity:workbench","entity:workbench_todo_item"],"entrypoints":["Admin UI /workbench (planned)"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-004"],"code":"","doc":"docs/ontology/actions.md#action:view_workbench","evidence":"documented","note":"登录落点;聚合现有 appointment/report/lead/highlight 统计。可弹今日待办。"} {"id":"action:view_workbench","type":"Action","name":"viewWorkbench","nameZh":"查看今日工作台","domain":"admin","inputs":["date","entity:current_user"],"outputs":["entity:workbench","entity:workbench_todo_item"],"entrypoints":["GET /api/admin/workbench/report-funnel","Admin UI /workbench"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-004","rule:BR-BE-001"],"code":"backend/src/main/java/com/petstore/controller/AdminBusinessEventController.java#reportFunnel; backend/src/main/java/com/petstore/service/AdminBusinessEventService.java; admin/src/views/WorkbenchView.vue","doc":"docs/ontology/actions.md#action:view_workbench","test":"AdminBusinessEventControllerTest; AdminBusinessEventServiceTest","evidence":"anchored","note":"事件漏斗按会话 storeId 与日期汇总;待办继续复用现有 appointment/report/lead 接口。"}
{"id":"action:drilldown_workbench_todo","type":"Action","name":"drilldownWorkbenchTodo","nameZh":"工作台待办下钻","domain":"admin","inputs":["entity:workbench_todo_item"],"outputs":[],"entrypoints":["Admin UI workbench todo click (planned)"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002"],"code":"","doc":"docs/ontology/actions.md#action:drilldown_workbench_todo","evidence":"documented","note":"按 kind 跳转 admin_list_appointments / reports / leads 并带筛选。"} {"id":"action:drilldown_workbench_todo","type":"Action","name":"drilldownWorkbenchTodo","nameZh":"工作台待办下钻","domain":"admin","inputs":["entity:workbench_todo_item"],"outputs":[],"entrypoints":["Admin UI workbench todo click (planned)"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002"],"code":"","doc":"docs/ontology/actions.md#action:drilldown_workbench_todo","evidence":"documented","note":"按 kind 跳转 admin_list_appointments / reports / leads 并带筛选。"}
{"id":"action:admin_list_appointments","type":"Action","name":"adminListAppointments","nameZh":"后台预约列表","domain":"admin","inputs":["status","dateFrom","dateTo","assignedUserId","page","pageSize"],"outputs":["entity:appointment"],"entrypoints":["Admin UI /appointments (planned)","reuses GET /api/appointment/list"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-APPT-001","rule:BR-APPT-002"],"code":"backend/.../AppointmentController.java#list (reuse)","doc":"docs/ontology/actions.md#action:admin_list_appointments","evidence":"documented","note":"Web 列表增强筛选;业务规则复用现有 Appointment*不另造状态机。Web 允许调用 start_service产品决策 2026-07-10"} {"id":"action:admin_list_appointments","type":"Action","name":"adminListAppointments","nameZh":"后台预约列表","domain":"admin","inputs":["status","dateFrom","dateTo","assignedUserId","page","pageSize"],"outputs":["entity:appointment"],"entrypoints":["Admin UI /appointments (planned)","reuses GET /api/appointment/list"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-APPT-001","rule:BR-APPT-002"],"code":"backend/.../AppointmentController.java#list (reuse)","doc":"docs/ontology/actions.md#action:admin_list_appointments","evidence":"documented","note":"Web 列表增强筛选;业务规则复用现有 Appointment*不另造状态机。Web 允许调用 start_service产品决策 2026-07-10"}
{"id":"action:admin_list_reports","type":"Action","name":"adminListReports","nameZh":"后台报告列表","domain":"admin","inputs":["dateFrom","dateTo","highlightStatus","page","pageSize"],"outputs":["entity:report","entity:highlight_video"],"entrypoints":["Admin UI /reports (planned)","reuses GET /api/report/list"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-003","rule:BR-RPT-005"],"code":"backend/.../ReportController.java#list (reuse)","doc":"docs/ontology/actions.md#action:admin_list_reports","evidence":"documented","note":"含成片三态、复制公开链接、触发重生成(复用 generate_highlight。"} {"id":"action:admin_list_reports","type":"Action","name":"adminListReports","nameZh":"后台报告列表","domain":"admin","inputs":["dateFrom","dateTo","highlightStatus","page","pageSize"],"outputs":["entity:report","entity:highlight_video"],"entrypoints":["Admin UI /reports (planned)","reuses GET /api/report/list"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-003","rule:BR-RPT-005"],"code":"backend/.../ReportController.java#list (reuse)","doc":"docs/ontology/actions.md#action:admin_list_reports","evidence":"documented","note":"含成片三态、复制公开链接、触发重生成(复用 generate_highlight。"}
@ -213,6 +221,7 @@
{"id":"rel:workbench_reads_appointments","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:appointment","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"今日预约指标"} {"id":"rel:workbench_reads_appointments","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:appointment","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"今日预约指标"}
{"id":"rel:workbench_reads_reports","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:report","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"待出报告/成片异常"} {"id":"rel:workbench_reads_reports","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:report","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"待出报告/成片异常"}
{"id":"rel:workbench_reads_leads","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:report_lead","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"待回访"} {"id":"rel:workbench_reads_leads","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:report_lead","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"待回访"}
{"id":"rel:workbench_reads_business_events","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:business_event","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"报告提交/打开/留资去重漏斗"}
{"id":"rel:store_has_service_customers","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:store_customer","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"StoreCustomer.store_id"} {"id":"rel:store_has_service_customers","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:store_customer","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"StoreCustomer.store_id"}
{"id":"rel:service_customer_is_user","type":"Relation","subject":"entity:store_customer","predicate":"LINKS","object":"entity:user","domain":"admin","evidence":"anchored","cardinality":"N—0..1","doc":"StoreCustomer.customer_user_id留资客户可先为空"} {"id":"rel:service_customer_is_user","type":"Relation","subject":"entity:store_customer","predicate":"LINKS","object":"entity:user","domain":"admin","evidence":"anchored","cardinality":"N—0..1","doc":"StoreCustomer.customer_user_id留资客户可先为空"}
{"id":"rel:service_customer_has_pets","type":"Relation","subject":"entity:store_customer","predicate":"HAS","object":"entity:pet","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"customer_user_id -> Pet.owner_user_id动态读模型"} {"id":"rel:service_customer_has_pets","type":"Relation","subject":"entity:store_customer","predicate":"HAS","object":"entity:pet","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"customer_user_id -> Pet.owner_user_id动态读模型"}

View File

@ -159,11 +159,12 @@
### action:track_report_open ### action:track_report_open
报告打开埋点:日志只记 token SHA-256 前 8 位 hash。 报告打开埋点:服务端用 token 定位 Report 后写入低敏 BusinessEvent事件表不保存 token/hash日志只记 SHA-256 前 8 位 hash。
- **入口**`POST /api/report/open-track` → `ReportController#trackReportOpen`(公开) - **入口**`POST /api/report/open-track` → `ReportController#trackReportOpen`(公开)
- **适用规则**`rule:BR-RPT-007`token hash 日志) - **适用规则**`rule:BR-RPT-007`token hash 日志)、`rule:BR-BE-001`(不可变与低敏事件)
- **代码**`backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen` - **代码**`ReportController#trackReportOpen`、`BusinessEventService#recordReportOpenByToken`
- **测试**`ReportControllerTest`、`BusinessEventServiceTest`
- **证据**`anchored` - **证据**`anchored`
### action:delete_report ### action:delete_report
@ -184,7 +185,7 @@
- **入口**`POST /api/report/{token}/reminder` → `ReportLeadController#submitReminder``ReportLeadService#submit`(公开) - **入口**`POST /api/report/{token}/reminder` → `ReportLeadController#submitReminder``ReportLeadService#submit`(公开)
- **输入**`phone`、`consent`、`loginCode?` - **输入**`phone`、`consent`、`loginCode?`
- **输出**`leadId`、`remindDate`、`unsubscribeToken`、`wechatBound` - **输出**`leadId`、`remindDate`、`unsubscribeToken`、`wechatBound`
- **适用规则**`rule:BR-LEAD-001`(去重)、`rule:BR-SC-001`(门店客户建档/绑定) - **适用规则**`rule:BR-LEAD-001`(去重)、`rule:BR-SC-001`(门店客户建档/绑定)、`rule:BR-BE-001`(低敏事件)
- **代码**`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder` - **代码**`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder`
- **测试**`ReportLeadControllerTest`、`ReportLeadServiceStoreCustomerTest` - **测试**`ReportLeadControllerTest`、`ReportLeadServiceStoreCustomerTest`
- **证据**`anchored` - **证据**`anchored`
@ -581,19 +582,21 @@
--- ---
## 门店 Web 后台Phase A · documented ## 门店 Web 后台Phase A
> 产品:`docs/门店管理后台升级方案-对标宠老板.md`。优先复用现有 `/api/**`,不另造状态机。 > 产品:`docs/门店管理后台升级方案-对标宠老板.md`。优先复用现有 `/api/**`,不另造状态机。
### action:view_workbench ### action:view_workbench
查看今日工作台(登录落点;可弹今日待办)。 查看今日工作台(登录落点;可弹今日待办)。报告提交、打开与留资三项通过服务端事件汇总,未建模的报告发送/转预约保持未知。
- **入口**Admin UI `/workbench`planned - **入口**Admin UI `/workbench``GET /api/admin/workbench/report-funnel?date=yyyy-MM-dd`
- **输出**`entity:workbench`、`entity:workbench_todo_item` - **输出**`entity:workbench`、`entity:workbench_todo_item`
- **适用规则**`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-004` - **适用规则**`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-004`、`rule:BR-BE-001`
- **触发事件**`event:workbench_viewed`可选gap - **触发事件**`event:workbench_viewed`可选gap
- **证据**`documented` - **代码**`AdminBusinessEventController`、`AdminBusinessEventService`、`admin/src/views/WorkbenchView.vue`
- **测试**`AdminBusinessEventControllerTest`、`AdminBusinessEventServiceTest`
- **证据**`anchored`(事件漏斗已落地;其他待办仍复用现有接口聚合)
### action:drilldown_workbench_todo ### action:drilldown_workbench_todo

View File

@ -40,8 +40,9 @@
预约创建成功(`status=new`,下单即占号生效)。 预约创建成功(`status=new`,下单即占号生效)。
- **触发动作**`action:create_appointment` - **触发动作**`action:create_appointment`
- **payload**`appointmentId`、`storeId`、`customerUserId`、`createdByUserId`、`appointmentTime` - **持久字段**`storeId`、`storeCustomerId`、`aggregateType=appointment`、`aggregateId`、`actorUserId/role`、`bookingOrigin`
- **代码**`backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking` - **代码**`backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking`、`BusinessEventService#recordAppointmentCreated`
- **测试**`AppointmentServiceTest`、`BusinessEventServiceTest`
- **证据**`anchored` - **证据**`anchored`
### event:appointment_status_changed ### event:appointment_status_changed
@ -49,12 +50,34 @@
预约状态迁移(`new→doing`、`new→cancel`、`doing→done`、`doing→cancel`)。 预约状态迁移(`new→doing`、`new→cancel`、`doing→done`、`doing→cancel`)。
- **触发动作**`action:start_service``new→doing`)、`action:transition_appointment_status`、`action:submit_report``doing→done` - **触发动作**`action:start_service``new→doing`)、`action:transition_appointment_status`、`action:submit_report``doing→done`
- **payload**`appointmentId`、`fromStatus`、`toStatus`、`assignedStaffId?` - **持久字段**`aggregateId=appointmentId`、`fromStatus`、`toStatus`、`actorUserId/role`、`storeCustomerId?`
- **适用规则**`rule:BR-APPT-001` - **适用规则**`rule:BR-APPT-001`
- **代码**`backend/src/main/java/com/petstore/service/AppointmentService.java#startService`/`#transitionStatus`、`backend/src/main/java/com/petstore/service/ReportService.java#create`(置 done - **代码**`AppointmentService#startService`/`#transitionStatus`、`ReportService#create`、`BusinessEventService#recordAppointmentStatusChanged`
- **测试**`AppointmentServiceTest`、`ReportServiceTest#createDoingAppointmentMarksDone` - **测试**`AppointmentServiceTest`、`ReportServiceTest#createDoingAppointmentMarksDone`
- **证据**`anchored` - **证据**`anchored`
### event:service_started
服务正式开始(`new -> doing`),是履约时长与服务漏斗的开始事实。
- **触发动作**`action:start_service`
- **持久字段**`aggregateId=appointmentId`、`storeId`、`storeCustomerId?`、`actorUserId/role`、`occurredAt`
- **幂等键**`service_started:{appointmentId}`
- **代码**`backend/src/main/java/com/petstore/service/BusinessEventService.java#recordAppointmentStatusChanged`
- **测试**`BusinessEventServiceTest#statusToDoingRecordsGenericAndServiceStartedFacts`
- **证据**`anchored`
### event:service_completed
服务完成(`doing -> done`),可由报告提交或合法手工完成触发。
- **触发动作**`action:transition_appointment_status`、`action:submit_report`
- **持久字段**:同 `event:service_started`
- **幂等键**`service_completed:{appointmentId}`
- **代码**`BusinessEventService#recordAppointmentStatusChanged`、`ReportService#create`
- **测试**`AppointmentServiceTest`、`ReportServiceTest`
- **证据**`anchored`
## Report 相关 ## Report 相关
### event:report_submitted ### event:report_submitted
@ -62,9 +85,9 @@
报告提交成功,关联预约置 `done` 报告提交成功,关联预约置 `done`
- **触发动作**`action:submit_report` - **触发动作**`action:submit_report`
- **payload**`reportId`、`appointmentId`、`storeId`、`reportToken` - **持久字段**`aggregateId=reportId`、`storeId`、`storeCustomerId?`、`actorUserId/role`;不保存 report token/hash
- **适用规则**`rule:BR-RPT-001`、`rule:BR-RPT-002`、`rule:BR-RPT-004` - **适用规则**`rule:BR-RPT-001`、`rule:BR-RPT-002`、`rule:BR-RPT-004`
- **代码**`backend/src/main/java/com/petstore/service/ReportService.java#create` - **代码**`backend/src/main/java/com/petstore/service/ReportService.java#create`、`BusinessEventService#recordReportSubmitted`
- **测试**`ReportServiceTest` - **测试**`ReportServiceTest`
- **证据**`anchored` - **证据**`anchored`
@ -73,19 +96,21 @@
报告页首次打开(本地 storage 标记首次)。 报告页首次打开(本地 storage 标记首次)。
- **触发动作**`action:track_report_open` - **触发动作**`action:track_report_open`
- **payload**`reportTokenHash`SHA-256 前 8 位)、`isFirstOpen`、`source`、`ts` - **持久字段**`aggregateId=reportId`、`storeId`、`storeCustomerId?`、`visitType=first`、`occurredAt`;事件表不保存 token/hash
- **适用规则**`rule:BR-RPT-007`token hash 日志) - **适用规则**`rule:BR-RPT-007`token hash 日志)
- **代码**`backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen` - **代码**`ReportController#trackReportOpen`、`BusinessEventService#recordReportOpenByToken`
- **测试**`ReportControllerTest#reportOpenPersistsLowSensitivityBusinessEvent`、`BusinessEventServiceTest#reportOpenStoresReportIdButNeverToken`
- **文档**`docs/本期主线-下一步产品优化点.md 埋点事件 schema` - **文档**`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
- **证据**`anchored`日志埋点已实现;统计落地 `documented` - **证据**`anchored`事件已落库并进入工作台去重统计;日志仅保留短 hash
### event:report_reopened ### event:report_reopened
报告页再次打开(非首次)。 报告页再次打开(非首次)。
- **触发动作**`action:track_report_open` - **触发动作**`action:track_report_open`
- **payload**:同 `event:report_opened``isFirstOpen=false` - **持久字段**:同 `event:report_opened``visitType=repeat`
- **证据**`documented`schema 已冻结,前端 `isFirstOpen` 判断已实现) - **代码/测试**:同 `event:report_opened`
- **证据**`anchored`
## ReportLead 相关 ## ReportLead 相关
@ -94,11 +119,12 @@
宠主留资提交成功(含重复提交幂等更新)。 宠主留资提交成功(含重复提交幂等更新)。
- **触发动作**`action:submit_lead` - **触发动作**`action:submit_lead`
- **payload**`reportTokenHash`、`storeId`、`hasPhone`、`hasPetName`、`repeatSubmit`、`ts` - **持久字段**`aggregateId=leadId`、`storeId`、`storeCustomerId?`、`repeatSubmit`不复制手机号、IP、openid 或 token
- **适用规则**`rule:BR-LEAD-001` - **适用规则**`rule:BR-LEAD-001`
- **代码**`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder` - **代码**`ReportLeadService#submit`、`BusinessEventService#recordLeadSubmitted`
- **测试**`ReportLeadServiceStoreCustomerTest`、`BusinessEventMigrationTest`
- **文档**`docs/本期主线-下一步产品优化点.md 埋点事件 schema` - **文档**`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
- **证据**`documented`(埋点 schema 已冻结,事件上报与统计落地 `gap` - **证据**`anchored`
--- ---

View File

@ -144,6 +144,29 @@
- **测试**暂无Batch D 未补 `ReportLeadControllerTest`,列为 `documented` gap - **测试**暂无Batch D 未补 `ReportLeadControllerTest`,列为 `documented` gap
- **证据**`anchored`(脱敏与跨店校验已实现并测试覆盖;去重逻辑 `documented` - **证据**`anchored`(脱敏与跨店校验已实现并测试覆盖;去重逻辑 `documented`
## entity:business_event
**不可变业务事件**。预约、履约、报告打开与留资等经营事实的统一持久化入口,为报告漏斗和 StoreCustomer 时间线提供可统计事实源。
| 字段 | 类型 | 说明 |
|------|------|------|
| id / event_id | BIGINT / VARCHAR(36) | 内部主键 / 随机事件 ID |
| event_type / event_version | VARCHAR(64) / INT | 事件类型与 schema 版本 |
| store_id / store_customer_id | BIGINT | 门店范围 / 可选客户关系 |
| aggregate_type / aggregate_id | VARCHAR(32) / BIGINT | 关联业务对象 |
| actor_user_id / actor_role | BIGINT / VARCHAR(16) | 操作人快照,匿名事件可空 |
| source | VARCHAR(32) | customer / admin / public_report / system / migration |
| occurred_at / create_time | DATETIME | 发生时间 / 入库时间 |
| metadata_json | TEXT | 服务端白名单低敏标量 JSON |
| idempotency_key | VARCHAR(128) | 可选幂等键 |
- **隐私**:不保存手机号、报告 token/hash、媒体 URL、IP、openid/unionid、备注或原始请求体
- **不变量**:只追加,不更新、不软删;通用事件明细不直接暴露给前端
- **归属系统**Backend Core跨 Report Media Backend 写入点)
- **代码**`backend/src/main/java/com/petstore/entity/BusinessEvent.java`、`BusinessEventService.java`
- **测试**`BusinessEventServiceTest`、`BusinessEventMigrationTest`
- **证据**`anchored`
--- ---
## Batch 2 录入实体 ## Batch 2 录入实体
@ -337,10 +360,10 @@
--- ---
## Batch门店 Web 后台预录入Phase A · documented ## Batch门店 Web 后台Phase A
> 产品依据:`docs/门店管理后台升级方案-对标宠老板.md`。 > 产品依据:`docs/门店管理后台升级方案-对标宠老板.md`。
> **尚未编码**;设计与实现须引用下列 ID。收银/会员资产仍见下方 gap 占位。 > 各对象按实际证据标记;收银/会员资产仍见下方 gap 占位。
## entity:admin_console ## entity:admin_console
@ -359,17 +382,19 @@
## entity:workbench ## entity:workbench
**今日工作台**(非 JPA 聚合视图)。指标卡 + 今日待办 + 报告漏斗摘要;数据来自 appointment/report/lead/highlight**无独立表**。 **今日工作台**(非 JPA 聚合视图)。指标卡 + 今日待办 + 报告漏斗摘要;待办来自 appointment/report/lead/highlight报告漏斗来自 BusinessEvent**无独立工作台表**。
| 字段(概念) | 说明 | | 字段(概念) | 说明 |
|------|------| |------|------|
| todayAppointmentCount / todayDoneCount | 今日预约 / 今日完成 | | todayAppointmentCount / todayDoneCount | 今日预约 / 今日完成 |
| pendingLeadCount / highlightFailedCount | 待回访 / 成片失败 | | pendingLeadCount / highlightFailedCount | 待回访 / 成片失败 |
| todoItems | `entity:workbench_todo_item[]` | | todoItems | `entity:workbench_todo_item[]` |
| reportFunnel | 已发/已打开/已留资/报告转预约(缺埋点时可显示 —) | | reportFunnel | 已提交/已打开/已留资/报告转预约;未跟踪的发送与转预约返回未知 |
| anomalyList | 成片失败或待回访最近 N 条 | | anomalyList | 成片失败或待回访最近 N 条 |
- **证据**`documented` - **代码**`admin/src/views/WorkbenchView.vue`、`AdminBusinessEventController`、`AdminBusinessEventService`
- **测试**`AdminBusinessEventControllerTest`、`AdminBusinessEventServiceTest`
- **证据**`anchored`
- **动作**`action:view_workbench` - **动作**`action:view_workbench`
## entity:workbench_todo_item ## entity:workbench_todo_item

View File

@ -16,6 +16,8 @@
| `Report` 1—N `ReportImage` | 报告含多张照片before/after/during | `ReportImage.report_id``Report.id` | | `Report` 1—N `ReportImage` | 报告含多张照片before/after/during | `ReportImage.report_id``Report.id` |
| `Report` 1—N `ReportLead` | 一份报告可多次留资(同手机号去重) | `ReportLead.report_id``Report.id` | | `Report` 1—N `ReportLead` | 一份报告可多次留资(同手机号去重) | `ReportLead.report_id``Report.id` |
| `ReportLead` N—1 `Store` | 留资归属门店(回访池范围) | `ReportLead.store_id``Store.id` | | `ReportLead` N—1 `Store` | 留资归属门店(回访池范围) | `ReportLead.store_id``Store.id` |
| `Store` 1—N `BusinessEvent` | 所有可统计事实强制归属门店 | `BusinessEvent.store_id``Store.id` |
| `StoreCustomer` 1—N `BusinessEvent` | 客户时间线;无客户语义事件可空 | `BusinessEvent.store_customer_id``StoreCustomer.id` |
## 动作—事件(触发) ## 动作—事件(触发)
@ -25,9 +27,9 @@
| `action:login` | `event:session_issued` | | `action:login` | `event:session_issued` |
| `action:wx_phone_login` | `event:session_issued`、`event:wx_bound`(若 loginCode 有) | | `action:wx_phone_login` | `event:session_issued`、`event:wx_bound`(若 loginCode 有) |
| `action:create_appointment` | `event:appointment_created` | | `action:create_appointment` | `event:appointment_created` |
| `action:start_service` | `event:appointment_status_changed`new→doing | | `action:start_service` | `event:appointment_status_changed`new→doing、`event:service_started` |
| `action:transition_appointment_status` | `event:appointment_status_changed` | | `action:transition_appointment_status` | `event:appointment_status_changed` |
| `action:submit_report` | `event:report_submitted`、`event:appointment_status_changed`doing→done | | `action:submit_report` | `event:report_submitted`、`event:appointment_status_changed`doing→done、`event:service_completed` |
| `action:track_report_open` | `event:report_opened` / `event:report_reopened` | | `action:track_report_open` | `event:report_opened` / `event:report_reopened` |
| `action:submit_lead` | `event:lead_submitted` | | `action:submit_lead` | `event:lead_submitted` |
@ -53,6 +55,7 @@
| `rule:BR-LEAD-001` | `action:submit_lead` | | `rule:BR-LEAD-001` | `action:submit_lead` |
| `rule:BR-LEAD-002` | `action:list_leads` | | `rule:BR-LEAD-002` | `action:list_leads` |
| `rule:BR-LEAD-003` | `action:list_leads` | | `rule:BR-LEAD-003` | `action:list_leads` |
| `rule:BR-BE-001` | 所有已落库事件触发动作、`action:view_workbench` |
## 实体—归属系统 ## 实体—归属系统
@ -63,6 +66,7 @@
| `entity:appointment` | Backend Core | | `entity:appointment` | Backend Core |
| `entity:report` | Report Media Backend | | `entity:report` | Report Media Backend |
| `entity:report_lead` | Report Media Backend | | `entity:report_lead` | Report Media Backend |
| `entity:business_event` | Backend Core跨域写入底座 |
--- ---

View File

@ -373,3 +373,12 @@
- **代码**`backend/src/main/java/com/petstore/service/StoreCustomerService.java` - **代码**`backend/src/main/java/com/petstore/service/StoreCustomerService.java`
- **测试**`StoreCustomerServiceTest`、`ReportLeadServiceStoreCustomerTest`、`IdentityOwnershipQueryTest` - **测试**`StoreCustomerServiceTest`、`ReportLeadServiceStoreCustomerTest`、`IdentityOwnershipQueryTest`
- **证据**`anchored` - **证据**`anchored`
### rule:BR-BE-001
**业务事件不可变、幂等与低敏**BusinessEvent 只追加,不更新/软删;稳定业务结果使用 idempotencyKey 去重。metadata 只允许服务端白名单标量,不得保存手机号、报告 token/hash、媒体 URL、IP、openid/unionid、备注、内容全文、密码、密钥或原始请求体。管理端仅返回当前门店聚合不开放通用事件明细。
- **适用动作**`action:create_appointment`、`action:start_service`、`action:transition_appointment_status`、`action:submit_report`、`action:track_report_open`、`action:submit_lead`、`action:view_workbench`
- **代码**`backend/src/main/java/com/petstore/service/BusinessEventService.java`、`AdminBusinessEventService.java`
- **测试**`BusinessEventServiceTest`、`BusinessEventMigrationTest`、`AdminBusinessEventControllerTest`
- **证据**`anchored`

View File

@ -101,6 +101,19 @@
| create_time / update_time | DATETIME | 创建/更新时间 | | create_time / update_time | DATETIME | 创建/更新时间 |
| deleted | TINYINT | 软删除标记 | | deleted | TINYINT | 软删除标记 |
### t_business_event不可变业务事件
| 字段 | 类型 | 说明 |
|------|------|------|
| id / event_id | BIGINT / VARCHAR(36) | 内部主键 / 随机事件 ID |
| event_type / event_version | VARCHAR(64) / INT | 过去时事件类型 / schema 版本 |
| store_id / store_customer_id | BIGINT | 门店范围 / 可选客户关系 |
| aggregate_type / aggregate_id | VARCHAR(32) / BIGINT | 关联预约、报告或留资 |
| actor_user_id / actor_role | BIGINT / VARCHAR(16) | 操作人快照,匿名事件可空 |
| source | VARCHAR(32) | customer / admin / public_report / system / migration |
| occurred_at / create_time | DATETIME(3) | 业务发生 / 入库时间 |
| metadata_json | TEXT | 服务端白名单低敏维度,不存原始请求 |
| idempotency_key | VARCHAR(128) | 可选稳定幂等键 |
### t_appointment预约表 ### t_appointment预约表
| 字段 | 类型 | 说明 | | 字段 | 类型 | 说明 |
|------|------|------| |------|------|------|

View File

@ -33,20 +33,20 @@
### 埋点事件 schema最小集 · 本期冻结) ### 埋点事件 schema最小集 · 本期冻结)
> 范围:本期 P0 仅要求事件**可统计**(日志或入库均可),不要求看板。字段建议统一小驼峰;时间戳一律毫秒;`reportTokenHash` 为 token SHA-256 前 8 位 hex不输出原始 token > 范围:事件已统一落 `BusinessEvent`。客户端仍以 token 调公开接口,但事件表仅保存内部 aggregate ID、门店/客户关系和低敏维度token/hash 只用于现有短日志,不进入事件 metadata
| 事件名 | 触发时机 | 关键字段 | 来源 | 统计窗口 | Owner | 落地方式 | | 事件名 | 触发时机 | 关键字段 | 来源 | 统计窗口 | Owner | 落地方式 |
|--------|----------|----------|------|----------|-------|----------| |--------|----------|----------|------|----------|-------|----------|
| `report_open` | 报告页首次打开(本地 storage 标记首次) | `reportTokenHash`、`storeId`、`petName?`、`serviceType?`、`isFirstOpen`、`ts`、`source``link`/`qrcode`/`share` | H5 报告页 / 小程序报告页 | 日 / 周 | Report Share QA | `POST /api/report/open-track` 或像素 | | `report_opened` | 报告页首次打开(本地 storage 标记首次) | 持久化:`reportId`、`storeId`、`storeCustomerId?`、`visitType=first`、`occurredAt` | H5 报告页 / 小程序报告页 | 日 / 周 | Report Share QA | `POST /api/report/open-track` → BusinessEvent |
| `report_reopen` | 报告页再次打开(非首次) | 同上,`isFirstOpen=false` | 同上 | 日 / 周 | Report Share QA | 同上 | | `report_reopened` | 报告页再次打开(非首次) | 同上,`visitType=repeat` | 同上 | 日 / 周 | Report Share QA | 同上 |
| `highlight_success` | 成片生成成功 | `reportTokenHash`、`appointmentId`、`durationSec`、`composeMode`、`ts` | 后端成片任务 | 日 | Report Media Backend | 任务回调日志 | | `highlight_success` | 成片生成成功 | `reportTokenHash`、`appointmentId`、`durationSec`、`composeMode`、`ts` | 后端成片任务 | 日 | Report Media Backend | 任务回调日志 |
| `highlight_failed` | 成片生成失败 | `reportTokenHash`、`appointmentId`、`failReason``NETWORK`/`ASSET`/`BUSY`/`UNKNOWN`)、`ts` | 同上 | 日 | Report Media Backend | 同上 | | `highlight_failed` | 成片生成失败 | `reportTokenHash`、`appointmentId`、`failReason``NETWORK`/`ASSET`/`BUSY`/`UNKNOWN`)、`ts` | 同上 | 日 | Report Media Backend | 同上 |
| `video_play` | 报告页成片点击播放 | `reportTokenHash`、`ts`、`source` | 报告页 | 日 / 周 | Customer Experience FE | 前端日志或接口 | | `video_play` | 报告页成片点击播放 | `reportTokenHash`、`ts`、`source` | 报告页 | 日 / 周 | Customer Experience FE | 前端日志或接口 |
| `video_save` | 报告页成片保存到相册 | `reportTokenHash`、`ts` | 同上 | 日 / 周 | Customer Experience FE | 同上 | | `video_save` | 报告页成片保存到相册 | `reportTokenHash`、`ts` | 同上 | 日 / 周 | Customer Experience FE | 同上 |
| `lead_submit` | 留资提交成功 | `reportTokenHash`、`storeId`、`hasPhone`、`hasPetName`、`ts` | H5 报告页 / 小程序 | 日 / 周 | Report Share QA | `POST /api/report/{token}/reminder` | | `lead_submitted` | 留资提交成功 | 持久化:`leadId`、`storeId`、`storeCustomerId?`、`repeatSubmit`、`occurredAt`;不复制手机号/IP/token | H5 报告页 / 小程序 | 日 / 周 | Report Share QA | `POST /api/report/{token}/reminder` → BusinessEvent |
字段口径: 字段口径:
- `reportTokenHash``SHA-256(token)` 前 8 位 hex**不输出原始 token** - `reportTokenHash`仅旧日志使用 `SHA-256(token)` 前 8 位 hexBusinessEvent 不保存 token 或 hash
- `source`:枚举 `link` / `qrcode` / `share` / `menu` / `push`,缺失填 `unknown` - `source`:枚举 `link` / `qrcode` / `share` / `menu` / `push`,缺失填 `unknown`
- `isFirstOpen`:本地 storage 标记,不依赖后端。 - `isFirstOpen`:本地 storage 标记,不依赖后端。
- 统计窗口默认日/周;老板端日报或最小看板放到 P1。 - 统计窗口默认日/周;老板端日报或最小看板放到 P1。

View File

@ -0,0 +1,84 @@
# 架构决策BusinessEvent 业务事件落库
> 日期2026-08-01
>
> 状态:已采纳,待生产数据迁移
>
> Owner AgentBackend Core报告/留资写入点联动 Report Media Backend
>
> Service Lock`BusinessEvent*`、预约/报告/留资事件调用点、工作台报告漏斗
>
> Paired QACore Flow QA + Report Share QA
>
> Data Model Review RequiredYes
>
> Access / Privacy Review RequiredYes
## 1. 决策背景
此前本体中的 Event 多数只表达“动作发生后应存在的事实”并没有统一事件表。报告打开仅写应用日志工作台只能由前端临时拼预约、报告和留资无法可靠支持经营漏斗、StoreCustomer 时间线、回访归因和复购统计。
## 2. 冻结模型
`BusinessEvent` 是不可变事实,不是业务对象当前状态,也不是消息队列。
| 字段 | 语义 |
|---|---|
| `event_id` | 随机事件 ID不使用数据库自增 ID 做外部关联 |
| `event_type` / `event_version` | 过去时事件名与 schema 版本 |
| `store_id` | 强制门店数据范围 |
| `store_customer_id` | 可选门店客户稳定关系,供客户时间线使用 |
| `aggregate_type` / `aggregate_id` | 事件对应的 Appointment、Report 或 ReportLead |
| `actor_user_id` / `actor_role` | 操作人快照;匿名公开事件可空 |
| `source` | customer / admin / public_report / system / migration |
| `occurred_at` | 业务事实发生时间 |
| `metadata_json` | 服务端白名单低敏维度,不接受原始请求体 |
| `idempotency_key` | 可选稳定键,保证一个业务结果只落一条 |
| `create_time` | 事件入库时间 |
事件表只追加,不提供更新、软删除或通用明细 API。
## 3. 第一批真实事件
| 事件 | 可靠触发点 | 幂等口径 |
|---|---|---|
| `appointment_created` | 预约保存成功 | appointment ID |
| `appointment_status_changed` | 合法状态迁移保存成功 | appointment ID + 目标状态 |
| `service_started` | `new -> doing` | appointment ID |
| `service_completed` | `doing -> done` | appointment ID |
| `report_submitted` | 报告及图片保存成功 | report ID |
| `report_opened` / `report_reopened` | 公开报告打开埋点 | 每次有效打开;统计按 report ID 去重 |
| `lead_submitted` | 每次留资提交成功 | 可重复记录;漏斗按 lead ID 去重 |
`report_sent`、`follow_up_completed`、`rebook_created` 尚无可靠状态机或归因字段,本阶段明确不伪造;对应工作台指标返回 `null + trackingReady=false`
## 4. 隐私边界
- 事件表不得保存手机号、报告 token 或 token hash、媒体 URL、IP、openid/unionid、备注、内容全文、密码、密钥或任意请求体。
- 报告打开接口只用 token 在服务端定位 Report事件仅保存内部 report ID日志继续只记 SHA-256 前 8 位 hex。
- 管理端只开放按当前会话 `storeId` 聚合的漏斗结果,不开放事件明细。
## 5. 一致性与迁移
- 预约、状态迁移、报告提交与事件写入处于同一数据库事务。
- 留资沿用现有并发幂等事务边界;重复提交可产生动作事件,漏斗按 lead ID 去重,不放大转化数。
- 历史迁移只回填可从数据库确定的预约创建、报告提交、由报告确认的服务完成和留资提交。
- 历史报告打开与开始服务时间无法可靠恢复,不做猜测性回填。
迁移顺序:
1. `20260801_split_service_identity.sql`
2. `20260801_create_store_customer.sql`
3. `20260801_create_business_event.sql`
## 6. BusinessEvent 与 AuditLog 边界
BusinessEvent 记录“经营上发生了什么”AuditLog 记录“谁对受保护配置或数据做了什么变更”。本阶段先完成服务闭环事实,后续在门店开通、员工权限、设置修改和敏感操作进入生产化时建立独立 AuditLog不把安全审计字段混入经营事件。
## 7. 验收
- `mvn test` 全绿,迁移 SQL 集成测试通过。
- `npm run build`admin通过工作台“已打开”来自事件汇总。
- customer 访问工作台事件汇总为 403boss/staff 只能读取本店。
- 事件 metadata 隐私单测证明不含原始 token、手机号或 IP。
- 本体校验通过且 controller/entity 0 漂移。

View File

@ -20,7 +20,7 @@
| 模块 | 状态 | 主要缺口 | | 模块 | 状态 | 主要缺口 |
|------|------|----------| |------|------|----------|
| A0 壳层登录 | ~95% | 生产微信登录策略另议 | | A0 壳层登录 | ~95% | 生产微信登录策略另议 |
| A1 工作台 | ~90% | 待办弹层已做;漏斗「已打开/转预约」仍占位 | | A1 工作台 | ~95% | 待办弹层已做;漏斗提交/打开/留资已接 BusinessEvent转预约仍待归因模型 |
| A2 预约 | ~95% | 详情/筛选/待出报告/宠主脱敏已做 | | A2 预约 | ~95% | 详情/筛选/待出报告/宠主脱敏已做 |
| A6 排班 | ~90% | 与小程序对照验收 | | A6 排班 | ~90% | 与小程序对照验收 |
| A3 报告 | ~95% | 详情抽屉+失败文案+H5 | | A3 报告 | ~95% | 详情抽屉+失败文案+H5 |

View File

@ -42,7 +42,7 @@
今日待办 [待开始 n] [服务中 n] [待出报告 n] [成片异常 n] [待回访 n] 今日待办 [待开始 n] [服务中 n] [待出报告 n] [成片异常 n] [待回访 n]
报告漏斗 已发 | 已打开— | 已留资 | 转预约— 报告漏斗 已提交 | 已打开 | 已留资 | 转预约—
异常最近 5 条 …………………… [去处理] 异常最近 5 条 …………………… [去处理]

View File

@ -35,7 +35,7 @@ Workstream 建议名:`Store Admin Console`
| Paired QA | Core Flow QA | | Paired QA | Core Flow QA |
| Workstream | Store Admin Console | | Workstream | Store Admin Console |
| Track | Coding | | Track | Coding |
| Status | Ready待授权 | | Status | Done工作台事件漏斗已接入 |
| Repo/Path | `admin/**` | | Repo/Path | `admin/**` |
| Service Lock | `admin-scaffold` | | Service Lock | `admin-scaffold` |
| Depends On | 用户授权编码;选型确认 Vue3+Element Plus | | Depends On | 用户授权编码;选型确认 Vue3+Element Plus |
@ -101,12 +101,12 @@ python3 /Users/apple/_src/petstore/docs/graph/audit_drift.py
| Status | Ready待授权 | | Status | Ready待授权 |
| Repo/Path | `admin/src/**`shell/workbench/appointments | | Repo/Path | `admin/src/**`shell/workbench/appointments |
| Service Lock | `admin-a1-pages` | | Service Lock | `admin-a1-pages` |
| Depends On | T1 | | Depends On | T1`entity:business_event` |
| ontologyRefs | `[entity:admin_console, entity:workbench, action:view_workbench, action:start_service, rule:BR-ADMIN-001, rule:BR-APPT-001]` | | 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 | | Access / Privacy Review Required | Yes |
**验收**:对齐 PRD §13 与线框 A0A2staff/boss 可开始服务customer 进不去;待办下钻参数正确 **验收**:对齐 PRD §13 与线框 A0A2staff/boss 可开始服务customer 进不去;待办下钻参数正确;报告提交/打开/留资来自本店 BusinessEvent 汇总,未跟踪的转预约显示未知。
**验证**:按 PRD §3.6 / §2.6 手工冒烟 + 构建通过 **验证**`AdminBusinessEventControllerTest` + `AdminBusinessEventServiceTest` + admin 构建;发布后按 PRD §3.6 / §2.6 手工冒烟。
--- ---

View File

@ -90,7 +90,7 @@ entity:admin_console
```text ```text
顶栏 4 指标卡 顶栏 4 指标卡
今日待办条(或登录弹层关闭后的常驻摘要) 今日待办条(或登录弹层关闭后的常驻摘要)
报告漏斗四格(可先占位「—」) 报告漏斗四格(提交/打开/留资已由 BusinessEvent 汇总;转预约仍为「—」)
异常列表(成片失败 / 待回访 最近 5 条) 异常列表(成片失败 / 待回访 最近 5 条)
右侧:门店卡片(店名、营业时段摘要、员工数)+ 帮助链接 右侧:门店卡片(店名、营业时段摘要、员工数)+ 帮助链接
``` ```
@ -119,16 +119,16 @@ entity:admin_console
### 2.4 报告漏斗四格(差异化) ### 2.4 报告漏斗四格(差异化)
| 格 | 口径 | A 期无埋点时 | | 格 | 口径 | 当前实现 |
|----|------|----------------| |----|------|----------|
| 今日已发报告 | 今日新建报告数 | 可算 | | 今日已提交报告 | `report_submitted` 按 report 去重 | BusinessEvent 汇总;发送状态尚未建模,不写“已发” |
| 已打开 | `report_open` 去重 | 显示「—」,链到 B1 说明 | | 已打开 | `report_opened/reopened` 按 report 去重 | BusinessEvent 汇总 |
| 已留资 | 今日新增 lead | 可算 | | 已留资 | `lead_submitted` 按 lead 去重 | BusinessEvent 汇总 |
| 报告转预约 | 从报告页带来的预约 | 无可靠归因则「—」 | | 报告转预约 | `rebook_created` | 尚无可靠归因,显示「—」 |
### 2.5 API / 聚合 ### 2.5 API / 聚合
优先前端或 BFF 聚合现有接口;若延迟高,再补 `GET /api/admin/workbench`(实现时同步本体) 待办与异常仍复用现有接口;报告漏斗使用 `GET /api/admin/workbench/report-funnel?date=yyyy-MM-dd``storeId` 从会话派生
复用候选:`/api/appointment/list`、`/api/report/list`、`/api/report/leads`、`/api/store/get`(后台可用内部字段,`BR-ADMIN-003`)。 复用候选:`/api/appointment/list`、`/api/report/list`、`/api/report/leads`、`/api/store/get`(后台可用内部字段,`BR-ADMIN-003`)。

View File

@ -443,7 +443,7 @@ Web 后台主攻:**经营、检索、批量、报表、设置****允许「
└──────────────────┴──────────────────────────┴──────────────────┘ └──────────────────┴──────────────────────────┴──────────────────┘
``` ```
**报告漏斗四格**(替代微信商城四格):`今日已发报告` · `已打开` · `已留资` · `从报告发起的预约`——直接打在我们差异化上;缺埋点统计时先显示「—」并链到 B1 **报告漏斗四格**(替代微信商城四格):`今日已提交报告` · `已打开` · `已留资` · `从报告发起的预约`——前三项已由 BusinessEvent 汇总;报告发送和转预约归因未建模前显示「—」,不以提交冒充发送
### 11.7 截图覆盖度与仍缺清单2026-07-10 刷新) ### 11.7 截图覆盖度与仍缺清单2026-07-10 刷新)