Compare commits
2 Commits
abe1a95255
...
1ffbe17d27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ffbe17d27 | ||
|
|
0d62b8fa4a |
57
.agents/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# 宠小它 Agent Team
|
||||
|
||||
本目录是宠小它项目的 agent 协作规则源头,迁移自 xxxfresh 的固定团队模型,并按当前宠物门店 SaaS 业务边界改写。
|
||||
|
||||
## 使用顺序
|
||||
|
||||
1. 先读 [common-context.md](common-context.md)。
|
||||
2. 再读 [fixed-team-operating-model.md](fixed-team-operating-model.md),确认 `Owner Agent`、`Paired QA`、`Workstream` 和 `Service Lock`。
|
||||
3. 涉及接口、数据、权限、报告 token、媒体文件、成片任务或发布时,读取 [rules/api-contract-boundary.md](rules/api-contract-boundary.md)。
|
||||
4. 按任务类型读取 [roles](roles) 下的角色规则。
|
||||
5. 用 [templates/task-brief.md](templates/task-brief.md) 编写任务说明。
|
||||
6. 跨模块方案先用 [templates/system-architect-review-checklist.md](templates/system-architect-review-checklist.md) 做架构评审。
|
||||
7. agent 完成后用 [templates/review-report.md](templates/review-report.md) 输出结果。
|
||||
|
||||
## 当前角色
|
||||
|
||||
新任务默认只分派给固定 Owner Agent。专项支持 agent 可被固定 owner 拉入,但不默认持有固定队列。
|
||||
|
||||
### 固定 Owner Agent
|
||||
|
||||
| Owner Agent | Role 字段 | 规则文件 | 主要职责 |
|
||||
|---|---|---|---|
|
||||
| Store Miniapp FE | `Store Miniapp Frontend` | [store-miniapp-frontend-agent.md](roles/store-miniapp-frontend-agent.md) | 门店端老板/员工页面、预约、报告填写、我的、服务类型、员工管理 |
|
||||
| Store Admin FE | `Store Admin Frontend` | [store-admin-frontend-agent.md](roles/store-admin-frontend-agent.md) | 门店 Web 管理后台 `admin/**` |
|
||||
| Customer Experience FE | `Customer Experience Frontend` | [customer-experience-frontend-agent.md](roles/customer-experience-frontend-agent.md) | 宠主预约、报告 H5、分享落地、留资、宠物档案与公开体验 |
|
||||
| Backend Core | `Backend Core` | [backend-core-agent.md](roles/backend-core-agent.md) | 预约、门店、用户、宠物、日程、服务类型、微信/短信基础能力 |
|
||||
| Report Media Backend | `Report Media Backend` | [report-media-backend-agent.md](roles/report-media-backend-agent.md) | 报告、图片/视频素材、成片任务、报告公开页数据、留资、评价 |
|
||||
| Core Flow QA | `Core Flow QA` | [qa-core-flow-agent.md](roles/qa-core-flow-agent.md) | 门店端和宠主端预约主链路验收 |
|
||||
| Report Share QA | `Report Share QA` | [qa-report-share-agent.md](roles/qa-report-share-agent.md) | 报告填写、分享、H5、成片三态、隐私提示和埋点验收 |
|
||||
| Backend Ops | `Backend Ops` | [ops-backend-agent.md](roles/ops-backend-agent.md) | 后端构建、配置、Nginx API、服务启动、回滚和 smoke |
|
||||
| Frontend Release Ops | `Frontend Release Ops` | [ops-frontend-release-agent.md](roles/ops-frontend-release-agent.md) | 微信小程序、H5、静态构建、项目配置和前端 smoke |
|
||||
| PM Assistant | `PM Assistant` | [pm-assistant-agent.md](roles/pm-assistant-agent.md) | 任务源维护、RC 管家、依赖、阻塞、验收证据和同步材料 |
|
||||
| System Architect | `System Architect` | [system-architect-agent.md](roles/system-architect-agent.md) | 跨模块边界、接口契约、状态机、数据一致性和方案拆解 |
|
||||
|
||||
### 专项支持 Agent
|
||||
|
||||
| Agent | 规则文件 | 使用场景 |
|
||||
|---|---|---|
|
||||
| Data Model | [data-model-agent.md](roles/data-model-agent.md) | JPA 实体、表结构、索引、软删除、状态字段、幂等与迁移顺序 |
|
||||
| Product Design | [product-design-agent.md](roles/product-design-agent.md) | 产品说明、用户流程、信息架构、交互文案、验收标准 |
|
||||
| Docs | [docs-agent.md](roles/docs-agent.md) | 知识库同步、架构文档、研发计划、状态说明 |
|
||||
| Progress Digest | [progress-digest-agent.md](roles/progress-digest-agent.md) | 项目进展摘要、实现度看板、日报素材 |
|
||||
|
||||
## 项目级边界规则
|
||||
|
||||
| Rule | 文件 | 使用场景 |
|
||||
|---|---|---|
|
||||
| API / 数据 / 隐私边界 | [api-contract-boundary.md](rules/api-contract-boundary.md) | 任一任务触碰后端 controller/service/entity/repository、`frontend/src/api`、报告 token、媒体 URL、用户手机号、门店数据范围或发布配置 |
|
||||
|
||||
## 分工原则
|
||||
|
||||
- 一个 agent 一次只负责一个清晰目标。
|
||||
- 写入范围必须在任务开始前声明。
|
||||
- 不同 agent 的写入范围应尽量互斥;同一 `Service Lock` 同时只允许一个 owner 修改。
|
||||
- 主控 agent 负责集成、review 和最终决策。
|
||||
- agent 不允许回滚自己职责范围外的修改。
|
||||
- 产品说明类任务默认只写 `docs/`;只有负责人明确要求“改代码 / 实现功能”时才进入编码类任务。
|
||||
102
.agents/common-context.md
Normal file
@ -0,0 +1,102 @@
|
||||
# 宠小它 Agent Common Context
|
||||
|
||||
## 项目路径
|
||||
|
||||
| 名称 | 路径 |
|
||||
|---|---|
|
||||
| 工作区 | `/Users/apple/_src/petstore` |
|
||||
| 知识库 | `/Users/apple/_src/petstore/docs` |
|
||||
| 后端工程 | `/Users/apple/_src/petstore/backend` |
|
||||
| 前端工程 | `/Users/apple/_src/petstore/frontend` |
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 当前产品是“宠小它”宠物门店服务 SaaS。
|
||||
- v1 核心用户:宠物店老板、员工、宠主。
|
||||
- v1 核心闭环:预约创建 -> 待开始 -> 开始服务 -> 填写报告 -> 发送报告 -> 宠主查看/分享。
|
||||
- 本期重点以现有文档为准:预约状态机、服务报告、短视频成片、报告分享、宠主预约、留资和最小埋点。
|
||||
- 不把会员卡、储值、复杂排班、复杂数据看板、支付结算、技师分成等 P1/P2 能力写成 P0 已完成。
|
||||
- 不把 demo、mock、临时 H5 或 dev-only 配置描述成生产能力。
|
||||
|
||||
## 术语规则
|
||||
|
||||
| 术语 | 含义 | 使用规则 |
|
||||
|---|---|---|
|
||||
| 宠小它 | 当前产品品牌 | 对外文档和页面统一使用 |
|
||||
| Store / 门店 | 宠物店主体 | 后端数据范围以 `store_id` / `storeId` 为核心 |
|
||||
| boss | 老板角色 | 可管理员工、服务类型、店铺设置等 |
|
||||
| staff | 员工角色 | 可处理预约、开始服务、填写/发送报告 |
|
||||
| customer / 宠主 | 报告查看者和预约发起者 | 公开页和预约页不得泄漏无关门店/员工数据 |
|
||||
| Appointment | 预约 | 状态仅使用 `new`、`doing`、`done`、`cancel` |
|
||||
| Report | 服务报告 | 一约一份报告,提交后默认锁死 |
|
||||
| report_token | 报告公开访问令牌 | 视为敏感公开链接,不打印、不随意暴露到日志 |
|
||||
| Highlight Video / 成片 | 服务过程短视频自动成片 | 必须有 processing / failed / success 三态和可理解错误文案 |
|
||||
| Lead / 留资 | 宠主手机号等转化线索 | 需要去重、隐私提示和数据范围控制 |
|
||||
|
||||
## 技术基线
|
||||
|
||||
- 后端:Java 17、Spring Boot 3.2.3、Spring Data JPA、MySQL、Lombok。
|
||||
- 前端:uni-app、Vue 3、Vite,支持微信小程序和 H5。
|
||||
- 本地 H5 开发代理:`frontend/vite.config.js` 将 `/api` 转发到 `http://localhost:8080`。
|
||||
- 前端 API 入口优先通过 `frontend/src/api/index.js` 和业务 utils 统一封装。
|
||||
- 后端 controller 只做入口和响应映射;领域规则必须落在 service 层。
|
||||
|
||||
## 知识本体(Ontology)
|
||||
|
||||
项目领域知识以本体论框架建模为 SSOT,位于 `docs/ontology/`:
|
||||
|
||||
- `objects.md` — 实体(含 `admin_console`/`workbench`/`service_customer` Phase A 预录入;P1/P2 gap 占位见 membership 等)
|
||||
- `actions.md` — 动作(controller 接口 + 客户端埋点 + **admin_* 后台动作 documented**)
|
||||
- `events.md` — 事件(含 `workbench_viewed` gap)
|
||||
- `rules.md` — 规则(含 `rule:BR-ADMIN-001`…`004` 后台门禁与范围闸门)
|
||||
- `relations.md` — ER 关系 + 动作-事件 + 规则-动作适用
|
||||
- `graph/ontology.jsonl` — 机器可读知识图谱
|
||||
- `graph/validate_ontology.py` — 引用完整性校验
|
||||
- `graph/audit_drift.py` — 本体—代码漂移审计(端点 + 实体类对齐)
|
||||
- `coverage/ontology-coverage-audit.md` — 覆盖度审计(与 jsonl 同步刷新)
|
||||
- 产品对标:`docs/门店管理后台升级方案-对标宠老板.md`
|
||||
- Phase A 页面 PRD:`docs/门店管理后台-PhaseA-页面清单PRD.md`
|
||||
- Phase A 交付目标:`docs/门店管理后台-PhaseA-交付目标.md`
|
||||
- Phase A 冒烟:`docs/qa-reports/门店后台-PhaseA-冒烟清单.md`
|
||||
- Phase A 线框:`docs/门店管理后台-PhaseA-线框说明.md`
|
||||
- 技术选型(Q5):`docs/门店管理后台-技术选型草案.md`
|
||||
- 编码任务 brief:`docs/门店管理后台-PhaseA-编码任务brief.md`
|
||||
|
||||
**协作约定**:
|
||||
- 改动 controller/service/entity 时,同步更新对应本体条目(`docs/ontology/` + `graph/ontology.jsonl`)。
|
||||
- 任务 brief 可在 `ontologyRefs` 字段引用本体 ID(如 `[entity:report, rule:BR-RPT-002]`),让接手 agent 直接定位上下文。
|
||||
- 冲突时以本体为准;本体与代码漂移由 `audit_drift.py` 守护。
|
||||
- 校验:`python3 docs/graph/validate_ontology.py docs && python3 docs/graph/audit_drift.py`
|
||||
|
||||
## 协作规则
|
||||
|
||||
- 开工前说明读哪些文件、写哪些文件。
|
||||
- 写代码前确认写入范围,避免多个 agent 修改同一组文件。
|
||||
- 固定团队任务先读 `.agents/fixed-team-operating-model.md`,确认 `Owner Agent`、`Paired QA`、`Workstream`、`Service Lock`、`Data Model Review Required`、`Access / Privacy Review Required`。
|
||||
- 固定 agent 只领取字段完整且 `Status = Ready` 的任务;不要从长文档、聊天上下文或历史 prompt 自行扩大范围。
|
||||
- 任务名称不加角色前缀;角色归属写入任务字段。
|
||||
- 不要回滚、删除或格式化职责范围外的文件。
|
||||
- 发现未提交或未跟踪文件时,默认认为是他人工作成果。
|
||||
- 输出必须包含修改文件、验证命令、验证结果、风险点。
|
||||
- 没有运行验证命令时,不得声称“已验证通过”。
|
||||
|
||||
## 固定团队写入锁
|
||||
|
||||
- `frontend/src/pages/home`、`frontend/src/pages/appointment`、`frontend/src/pages/report`、`frontend/src/pages/mine` 的门店端体验归 Store Miniapp FE。
|
||||
- `admin/**`(建成后)门店 Web 后台归 Store Admin FE。
|
||||
- `frontend/src/pages/report-view`、`frontend/src/views/ReportView.vue`、`frontend/src/pages/video-player`、宠主预约、分享落地和留资体验归 Customer Experience FE。
|
||||
- 共享组件、`frontend/src/api`、`frontend/src/utils` 需要声明具体 `Service Lock`,并让相关 FE owner review。
|
||||
- 预约、门店、用户、宠物、日程、服务类型后端能力归 Backend Core。
|
||||
- 报告、媒体、成片、报告公开页、留资、评价后端能力归 Report Media Backend。
|
||||
- JPA entity、索引、表结构、状态字段和迁移顺序需要 Data Model 复核。
|
||||
- QA 不改业务代码;Ops 不改业务逻辑;PM Assistant 不改产品代码。
|
||||
- System Architect 不改业务代码,不持有前后端业务写入锁;架构结论需要落地时,必须拆给对应 owner agent 执行。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要把未实现的容量模型写成“排队/不排队”能力。
|
||||
- 不要让公开报告页暴露内部 user id、store id 以外的敏感明细、手机号或调试字段。
|
||||
- 不要绕过预约状态机直接赋值状态。
|
||||
- 不要绕过报告提交流程制造 `done` 终态。
|
||||
- 不要在业务页面继续扩散硬编码品牌主色;优先使用全局 token。
|
||||
- 不要把 mock 支付、测试域名、临时 token 或本地文件路径写成生产方案。
|
||||
152
.agents/fixed-team-operating-model.md
Normal file
@ -0,0 +1,152 @@
|
||||
# 宠小它 Fixed Agent Team Operating Model
|
||||
|
||||
本文件定义宠小它固定 agent 编制、持续队列和产品研发流程规范。目标是让 agent 按固定角色从 Ready 队列持续拉活,而不是每次临时重拆边界。
|
||||
|
||||
## 执行槽位
|
||||
|
||||
固定队形描述的是角色和责任,不等同于工具层可同时运行的子 agent 数量。实际执行采用“固定角色 + 动态并行 + 及时关闭”:
|
||||
|
||||
- 主控 PM 不占子 agent 槽位,负责拆任务、review、集成、提交、发布协调和阻塞归因。
|
||||
- agent 完成、阻塞、取消或仅用于探测后,必须及时关闭会话,释放队列资源。
|
||||
- 优先并行互不冲突的写入边界,例如 `docs` + `frontend` + `backend`;同一 `Service Lock` 同时只允许一个 owner 修改。
|
||||
- 如果创建新 agent 返回 thread limit,先清理已完成或已阻塞 agent,再按优先级重试。
|
||||
|
||||
## 固定队形
|
||||
|
||||
| Group | Owner Agent | Role 字段 | 常驻职责 | 主写入边界 | Paired QA |
|
||||
|---|---|---|---|---|---|
|
||||
| 门店端前端 | Store Miniapp FE | `Store Miniapp Frontend` | 老板/员工小程序页面、预约列表、开始服务、报告填写、我的、员工/服务类型管理 | `frontend/src/pages/home`、`appointment`、`report`、`mine`、门店端共享组件 | Core Flow QA / Report Share QA |
|
||||
| 门店 Web 后台 | Store Admin FE | `Store Admin Frontend` | 门店 Web 管理后台(工作台/预约/报告/线索/客户/排班/设置) | `admin/**`(建成后) | Core Flow QA / Report Share QA |
|
||||
| 宠主体验前端 | Customer Experience FE | `Customer Experience Frontend` | 宠主预约、报告 H5、分享落地、视频播放、留资、宠物档案和公开体验 | `frontend/src/pages/report-view`、`video-player`、宠主预约页、公开体验 utils/components | Core Flow QA / Report Share QA |
|
||||
| 核心后端 | Backend Core | `Backend Core` | 预约状态机、门店、用户、宠物、日程、服务类型、微信/短信基础能力 | `backend/src/main/java/com/petstore/controller` 和 `service` 中核心域文件 | Core Flow QA |
|
||||
| 报告媒体后端 | Report Media Backend | `Report Media Backend` | 服务报告、报告图片/视频、成片任务、公开报告数据、留资、评价 | `Report*`、`FileController`、`ReportHighlightVideoService` 等报告媒体域 | Report Share QA |
|
||||
| 核心链路 QA | Core Flow QA | `Core Flow QA` | 老板/员工/宠主预约主链路、状态机、门店数据范围验收 | `docs/qa-reports`、必要 smoke 脚本 | Store Miniapp FE / Customer Experience FE / Backend Core |
|
||||
| 报告分享 QA | Report Share QA | `Report Share QA` | 报告填写、分享页、H5、成片三态、隐私提示、埋点验收 | `docs/qa-reports`、必要 smoke 脚本 | Report Media Backend / Customer Experience FE |
|
||||
| 后端运维 | Backend Ops | `Backend Ops` | 后端 jar、数据库配置、Nginx API、启动、回滚、API smoke | `backend/deploy`、运维报告;不改业务逻辑 | 关联业务 QA |
|
||||
| 前端发布运维 | Frontend Release Ops | `Frontend Release Ops` | 微信小程序体验版、H5 构建、项目配置、前端 smoke | `frontend/project*.json`、构建/发布配置、运维报告;不改业务逻辑 | 关联业务 QA |
|
||||
| PM 助手 | PM Assistant | `PM Assistant` | 任务源维护、RC 管家、依赖跟踪、验收证据归档、阻塞归因、进展同步 | `docs`;不改产品代码 | 主控 PM |
|
||||
| 架构治理 | System Architect | `System Architect` | 跨模块边界、接口契约、状态机、数据一致性、owner 拆分 | `docs`;不改业务代码 | 关联业务 QA / Docs |
|
||||
|
||||
专项支持 agent:
|
||||
|
||||
| Agent | Role 字段 | 职责 | 写入边界 |
|
||||
|---|---|---|---|
|
||||
| Data Model | `Data Model` | JPA entity、表结构、索引、软删除、状态字段、幂等和迁移顺序 | `docs`、必要时 backend entity/repository review;默认不持锁 |
|
||||
| Product Design | `Product Design` | 产品说明、用户流程、IA、文案、验收标准 | `docs` |
|
||||
| Docs | `Docs` | 知识库、架构文档、实施计划、状态同步 | `docs` |
|
||||
| Progress Digest | `Progress Digest` | 实现度看板、日报素材、阶段摘要 | `docs` |
|
||||
|
||||
## 队列字段
|
||||
|
||||
每个 Owner Agent 维护一个持续队列。队列项必须具备以下字段,缺一不可:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `Task Name` | 以业务结果命名,不以角色前缀命名 |
|
||||
| `Role` | 使用上表 Role 字段之一 |
|
||||
| `Owner Agent` | 固定 agent 名称,例如 `Backend Core` |
|
||||
| `Paired QA` | 对应 QA,例如 `Core Flow QA` |
|
||||
| `Workstream` | `Core Booking Flow`、`Report Sharing`、`Release`、`PM`、`Architecture` |
|
||||
| `Track` | `Product`、`Coding`、`QA`、`Ops`、`Hotfix`、`Planning`、`Review` |
|
||||
| `Status` | `Backlog`、`Ready`、`Doing / In Progress`、`Review`、`QA`、`Deploying`、`Blocked`、`Ready for Release`、`Released`、`Done` |
|
||||
| `Priority` | `P0`、`P1`、`P2` |
|
||||
| `Repo/Path` | 主要写入路径 |
|
||||
| `Service Lock` | 后端 domain、前端 page group、docs 或发布资源锁 |
|
||||
| `Depends On` | 依赖任务或接口 |
|
||||
| `Acceptance` | QA 可执行的验收节点 |
|
||||
| `RC Included` | 是否进入当前 Release Candidate |
|
||||
| `Due Date` | 目标完成日期;没有明确日期时写 `TBD` |
|
||||
| `Output Link` | commit、报告、发布记录、截图或其他证据链接 |
|
||||
| `Blocker Owner` | 阻塞责任 owner;未阻塞时写 `None` |
|
||||
| `Commit / RC` | 相关提交号、RC id 或 `Not Frozen` |
|
||||
| `Architecture Review Required` | 是否需要 System Architect 在开发前评审 |
|
||||
| `Data Model Review Required` | 是否涉及 entity、表结构、索引、状态字段、幂等、软删除或数据迁移 |
|
||||
| `Access / Privacy Review Required` | 是否涉及登录、门店数据范围、手机号、报告 token、媒体 URL、公开页或隐私提示 |
|
||||
|
||||
## 拉活策略
|
||||
|
||||
- 固定 agent 完成当前任务后,从自己 `Role + Owner Agent + Status = Ready` 的队列中取最高优先级任务。
|
||||
- QA agent 不等发布后才工作。开发阶段先写验收清单和 smoke 草案,RC 固定后再做发布后验证。
|
||||
- Ops agent 不等所有开发结束才工作。后端/前端完成可部署单元后,Ops 提前准备发布脚本、回滚点和 smoke 命令。
|
||||
- PM Assistant 每轮负责把 agent 输出转换为固定任务源状态、依赖、阻塞和下一步任务。
|
||||
- System Architect 只在跨模块边界、状态机、公开链接、媒体异步任务、数据一致性或服务拆分争议出现时拉活。
|
||||
|
||||
## 运行门禁
|
||||
|
||||
### 产品门禁
|
||||
|
||||
- 产品说明类任务默认只写 `docs/`;只有负责人明确要求“改代码 / 实现功能”时才进入编码类任务。
|
||||
- 编码前必须确认需求来源:产品说明、P0 落地清单、bug 复现、QA 报告或用户直接指令。
|
||||
- 不确定的功能状态必须标记为“假设 / 待确认 / 未实现”,不得写成已完成。
|
||||
|
||||
### API 契约门禁
|
||||
|
||||
- 任一任务触碰后端 controller/service/entity/repository 或 `frontend/src/api`,必须读取 [rules/api-contract-boundary.md](rules/api-contract-boundary.md)。
|
||||
- API 变更必须同时说明:请求参数、响应 shape、错误码/业务码、前端调用点、回归页面。
|
||||
- 公开报告页和宠主入口必须明确是否允许匿名访问、token 口径和隐私提示。
|
||||
|
||||
### Data / Model 门禁
|
||||
|
||||
- 涉及 JPA entity、表结构、索引、软删除、状态字段、幂等、report token、手机号、媒体 URL 或异步任务状态时,`Data Model Review Required` 必须为 `Yes`。
|
||||
- 预约状态只允许 `new`、`doing`、`done`、`cancel`;新增状态必须先过 System Architect 和 Product Design。
|
||||
- 成片任务必须保留 processing / failed / success 状态,以及失败原因的用户可读映射。
|
||||
|
||||
### Access / Privacy 门禁
|
||||
|
||||
- 涉及登录、门店数据范围、boss/staff 权限、宠主公开页、手机号、报告 token、媒体 URL 或留资时,`Access / Privacy Review Required` 必须为 `Yes`。
|
||||
- 报告公开访问 token 视为敏感公开链接;日志、报告和截图中不要暴露真实 token 全量值。
|
||||
- 在真实权限未生产化前,报告必须明确标记 demo identity、mock login 或 pending,不得写成生产权限能力已完成。
|
||||
|
||||
### Release Candidate 门禁
|
||||
|
||||
- PM Assistant 兼任 RC Steward,负责维护 RC manifest、前后端提交号、纳入/排除范围、dirty diff 分类、发布状态和 QA 结论。
|
||||
- Backend Ops 和 Frontend Release Ops 只部署 RC Steward 冻结的提交号或明确归档的构建产物。
|
||||
- QA 只验收当前 RC;发现阻塞问题时由 PM Assistant 建 Hotfix 任务并关联原 RC。
|
||||
- 任一仓库存在未提交改动时,RC manifest 必须说明是否纳入、排除或等待 owner confirmation。
|
||||
|
||||
## 写入锁规则
|
||||
|
||||
- 同一时间只允许一个后端 owner 修改同一个 domain/service 组合。
|
||||
- `frontend/src/api/index.js` 是共享契约入口,改动必须通知受影响的 FE owner 和后端 owner。
|
||||
- 共享 report 组件、report share utils、public URL utils 同时影响门店端和宠主端,必须声明实际 owner 和 paired reviewer。
|
||||
- QA 不改业务代码;Ops 不改业务逻辑;PM Assistant 不改产品代码。
|
||||
- System Architect 不改业务代码,不持有前后端写入锁;架构结论需要落地时,由对应 owner agent 持锁执行。
|
||||
|
||||
## 三条业务主线
|
||||
|
||||
### Core Booking Flow
|
||||
|
||||
目标:门店端和宠主端预约主链路稳定,状态机不产生脏数据。
|
||||
|
||||
常驻 owner:
|
||||
- Store Miniapp FE
|
||||
- Customer Experience FE
|
||||
- Backend Core
|
||||
- Core Flow QA
|
||||
|
||||
### Report Sharing
|
||||
|
||||
目标:服务报告、公开 H5、分享卡片、成片三态、留资和隐私提示形成闭环。
|
||||
|
||||
常驻 owner:
|
||||
- Store Miniapp FE
|
||||
- Customer Experience FE
|
||||
- Report Media Backend
|
||||
- Report Share QA
|
||||
|
||||
### Release / Operations
|
||||
|
||||
目标:后端 jar、API、微信小程序、H5 构建和回滚路径可重复。
|
||||
|
||||
常驻 owner:
|
||||
- Backend Ops
|
||||
- Frontend Release Ops
|
||||
- PM Assistant
|
||||
- 关联业务 QA
|
||||
|
||||
## 状态同步节奏
|
||||
|
||||
- 每个 agent 输出必须包含:状态、修改文件、验证命令、结果、风险、需要主控 review 的问题。
|
||||
- PM Assistant 每次同步时更新:任务状态、阻塞原因、依赖 owner、下一动作。
|
||||
- Progress Digest 在用户要求或自动化任务触发时,基于固定任务源、git 状态、QA/Ops 报告和实现度看板生成摘要。
|
||||
- System Architect 负责提出架构结论、拆解 owner 任务和标记缺口;主控 PM 负责跨域冲突、优先级、RC 冻结和最终边界决策。
|
||||
34
.agents/roles/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 宠小它 Agent Role Index
|
||||
|
||||
本目录保存宠小它 agent 角色规则。新任务以固定 Owner Agent 为准。
|
||||
|
||||
## 固定 Owner Agent
|
||||
|
||||
| Owner Agent | Role 字段 | 规则文件 | 写入边界摘要 |
|
||||
|---|---|---|---|
|
||||
| Store Miniapp FE | `Store Miniapp Frontend` | [store-miniapp-frontend-agent.md](store-miniapp-frontend-agent.md) | 门店端老板/员工页面、预约、报告填写、我的、员工/服务类型管理 |
|
||||
| Store Admin FE | `Store Admin Frontend` | [store-admin-frontend-agent.md](store-admin-frontend-agent.md) | 门店 Web 管理后台 `admin/**` |
|
||||
| Customer Experience FE | `Customer Experience Frontend` | [customer-experience-frontend-agent.md](customer-experience-frontend-agent.md) | 宠主预约、报告 H5、分享落地、视频播放、留资 |
|
||||
| Backend Core | `Backend Core` | [backend-core-agent.md](backend-core-agent.md) | 预约、门店、用户、宠物、日程、服务类型、微信/短信基础能力 |
|
||||
| Report Media Backend | `Report Media Backend` | [report-media-backend-agent.md](report-media-backend-agent.md) | 报告、媒体、成片、公开报告数据、留资、评价 |
|
||||
| Core Flow QA | `Core Flow QA` | [qa-core-flow-agent.md](qa-core-flow-agent.md) | 预约主链路、状态机、门店数据范围验收 |
|
||||
| Report Share QA | `Report Share QA` | [qa-report-share-agent.md](qa-report-share-agent.md) | 报告填写、分享、H5、成片三态、隐私提示和埋点验收 |
|
||||
| Backend Ops | `Backend Ops` | [ops-backend-agent.md](ops-backend-agent.md) | 后端构建、配置、Nginx API、启动、回滚、API smoke |
|
||||
| Frontend Release Ops | `Frontend Release Ops` | [ops-frontend-release-agent.md](ops-frontend-release-agent.md) | 微信小程序、H5、项目配置、前端 smoke |
|
||||
| PM Assistant | `PM Assistant` | [pm-assistant-agent.md](pm-assistant-agent.md) | 固定任务源、RC 管家、依赖、阻塞、验收证据 |
|
||||
| System Architect | `System Architect` | [system-architect-agent.md](system-architect-agent.md) | 架构评审、方案拆解、跨模块一致性 |
|
||||
|
||||
## 专项支持 Agent
|
||||
|
||||
| Agent | 规则文件 | 使用场景 |
|
||||
|---|---|---|
|
||||
| Data Model | [data-model-agent.md](data-model-agent.md) | JPA entity、表结构、索引、状态字段、幂等与迁移顺序 |
|
||||
| Product Design | [product-design-agent.md](product-design-agent.md) | 用户流程、信息架构、交互文案、验收标准 |
|
||||
| Docs | [docs-agent.md](docs-agent.md) | 知识库同步、架构文档、实施计划 |
|
||||
| Progress Digest | [progress-digest-agent.md](progress-digest-agent.md) | 项目进展摘要、实现度看板、日报素材 |
|
||||
|
||||
## 命名规则
|
||||
|
||||
- 任务表字段使用 `fixed-team-operating-model.md` 中的 `Owner Agent` 和 `Role 字段`。
|
||||
- 任务名称不要加角色前缀;角色归属由字段表达。
|
||||
- 新增固定 owner 文件时,文件名使用 owner slug。
|
||||
62
.agents/roles/backend-core-agent.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Backend Core Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责宠小它核心后端能力:预约状态机、门店、用户、宠物、日程、服务类型、微信/短信基础能力。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 预约状态只能使用 `new`、`doing`、`done`、`cancel`。
|
||||
- 状态迁移必须校验前置状态,不能无条件直接赋值。
|
||||
- 门店数据范围以 `storeId` / `store_id` 为核心,boss/staff 不得越权读写其他门店数据。
|
||||
- 报告、成片、留资、评价归 Report Media Backend;若核心接口依赖这些领域,先声明边界。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
- `/Users/apple/_src/petstore/frontend/src/api`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/AdminServiceCustomerController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/AdminStoreController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/AppointmentController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/ScheduleController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/StoreController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/UserController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/PetController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/ServiceTypeController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/service`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/entity`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/mapper`
|
||||
|
||||
修改 entity、repository query 或 schema 语义时必须拉 Data Model。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改报告媒体域逻辑而不通知 Report Media Backend。
|
||||
- 不要在 controller 中实现复杂领域规则。
|
||||
- 不要绕过状态机、数据范围或软删除口径。
|
||||
- 不要把 demo login 写成生产权限能力。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 后端改动清单。
|
||||
- API 契约变化和前端影响。
|
||||
- 数据模型或权限影响。
|
||||
- 验证命令与结果。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn test
|
||||
```
|
||||
|
||||
如当前无测试或环境不完整,至少运行:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
61
.agents/roles/customer-experience-frontend-agent.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Customer Experience FE Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责宠主侧体验:宠主预约、报告 H5、分享落地、视频播放、留资、宠物档案、公开报告页和传播相关前端能力。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 宠主公开体验可以匿名访问时,必须以 report token 或明确公开参数为边界。
|
||||
- 报告页必须有隐私提示,避免鼓励公开转发含宠物服务影像的页面。
|
||||
- 成片展示必须支持 processing / failed / success 三态,以及失败重试或可理解文案。
|
||||
- 分享卡片元信息遵循产品文档:宠物名、服务、门店、封面优先级。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/report-view`
|
||||
- `/Users/apple/_src/petstore/frontend/src/views/ReportView.vue`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/video-player`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/appointment/CustAppointmentCreate.vue`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/mine/MyPets.vue`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/mine/MyReports.vue`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/mine/Leads.vue`
|
||||
- `/Users/apple/_src/petstore/frontend/src/components/report`
|
||||
- `/Users/apple/_src/petstore/frontend/src/utils`
|
||||
- `/Users/apple/_src/petstore/frontend/src/api`
|
||||
|
||||
共享 report 组件、utils、api 写入前必须声明受影响 owner。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要打印或展示完整 `report_token`。
|
||||
- 不要展示后端堆栈、SQL、Java exception 或未翻译错误。
|
||||
- 不要把手机号、门店内部数据、员工内部字段带到公开页。
|
||||
- 不要修改门店端报告填写流程而不通知 Store Miniapp FE。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 公开体验页面改动清单。
|
||||
- 隐私、token、分享、留资影响说明。
|
||||
- H5/小程序构建或页面 smoke 结果。
|
||||
- Report Share QA 验收路径。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
```
|
||||
|
||||
涉及小程序分享、路由或 pages 配置时:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
49
.agents/roles/data-model-agent.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Data Model Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责 JPA entity、表结构、索引、状态字段、软删除、幂等和数据迁移顺序的评审与设计。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 当前后端以 Spring Data JPA entity / repository query 为主要数据模型入口。
|
||||
- 预约状态、报告 token、手机号、媒体 URL、成片任务状态和门店数据范围属于高风险字段。
|
||||
- 数据模型文档必须区分“已落库字段”“前端语义”“计划新增字段”。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/entity`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/mapper`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/resources`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- 必要时可修改 entity/repository,但必须由对应 Backend owner 持锁或确认。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要擅自改业务 service 逻辑。
|
||||
- 不要新增状态字段而不更新产品状态机和 QA 验收口径。
|
||||
- 不要把未迁移的表字段写成生产已存在。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 字段语义、索引、幂等、回滚或兼容口径。
|
||||
- 影响表/entity/repository query 清单。
|
||||
- 需要 owner 落地的任务拆分。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
rg -n "t_|status|report_token|store_id|phone|highlight" /Users/apple/_src/petstore/docs
|
||||
```
|
||||
|
||||
如修改后端:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
47
.agents/roles/docs-agent.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Docs Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责 docs 知识库维护、架构文档、实施计划、设计决策和状态同步。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- docs 是团队规则和项目知识的源头。
|
||||
- 文档必须反映当前真实工程状态,不能停留在旧计划。
|
||||
- 不确定的内容要标记为缺口、假设或待确认。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改业务代码。
|
||||
- 不要把未落地功能写成已完成。
|
||||
- 不要让文档术语和 common-context 冲突。
|
||||
- 不要删除他人新增的未跟踪文档或图片。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 新增或更新的文档。
|
||||
- 当前状态、已完成、仍缺口、下一步。
|
||||
- 引用到具体代码路径或提交号。
|
||||
- 文档检查结果。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
```
|
||||
|
||||
如涉及关键词状态同步:
|
||||
|
||||
```bash
|
||||
rg -n "<keyword>" /Users/apple/_src/petstore/docs
|
||||
```
|
||||
41
.agents/roles/ops-backend-agent.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Backend Ops Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责后端构建、运行配置、Nginx API、服务启动、回滚和 API smoke,不修改业务逻辑。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- Ops 只部署 RC Steward 冻结的提交或明确产物。
|
||||
- 配置改动必须记录环境、风险和回滚方式。
|
||||
- 生产/公网 smoke 结果必须写明域名、时间和命令。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/backend/deploy`
|
||||
- `/Users/apple/_src/petstore/docs/ops-reports`
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改业务 service、controller、entity 逻辑。
|
||||
- 不要打印或提交真实密钥、密码、token。
|
||||
- 不要部署未冻结或未说明 dirty diff 的提交。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 构建产物、提交号、环境、启动命令。
|
||||
- smoke 命令与结果。
|
||||
- 回滚点和风险。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
44
.agents/roles/ops-frontend-release-agent.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Frontend Release Ops Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责微信小程序、H5、项目配置、静态构建和前端 smoke,不修改业务逻辑。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 只发布 RC Steward 冻结的提交或明确产物。
|
||||
- 小程序和 H5 发布前必须记录构建命令、目标环境、版本说明和回滚方式。
|
||||
- 前端发布配置改动需要对应 FE owner review。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/frontend/project.config.json`
|
||||
- `/Users/apple/_src/petstore/frontend/project.private.config.json`
|
||||
- `/Users/apple/_src/petstore/frontend/scripts`
|
||||
- `/Users/apple/_src/petstore/docs/ops-reports`
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改页面业务逻辑。
|
||||
- 不要提交真实 AppSecret、私钥或账号凭据。
|
||||
- 不要把本地预览写成体验版/生产发布成功。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 构建命令、产物、版本和目标环境。
|
||||
- smoke 路径和结果。
|
||||
- 回滚方式与风险。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
42
.agents/roles/pm-assistant-agent.md
Normal file
@ -0,0 +1,42 @@
|
||||
# PM Assistant Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责固定任务源、RC 管家、依赖跟踪、验收证据归档、阻塞归因和进展同步,不改产品代码。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- PM Assistant 维护状态,不替代业务 owner 写代码。
|
||||
- RC Steward 负责冻结提交、纳入/排除范围、dirty diff 分类、QA 结论和发布状态。
|
||||
- 任务字段必须完整,缺字段不得进入 Ready。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改业务代码。
|
||||
- 不要把未完成任务移到 Done。
|
||||
- 不要忽略 dirty diff 或未提交改动。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 任务状态更新。
|
||||
- 依赖、阻塞、owner、下一动作。
|
||||
- RC manifest 或进展摘要。
|
||||
- 验收证据链接。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
git -C /Users/apple/_src/petstore/frontend status --short
|
||||
git -C /Users/apple/_src/petstore/backend status --short
|
||||
```
|
||||
40
.agents/roles/product-design-agent.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Product Design Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责产品说明、用户流程、信息架构、交互文案和验收标准,不默认写代码。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 产品设计需要服务当前 P0:预约状态机、报告分享、成片三态、宠主预约、留资和最小埋点。
|
||||
- 未明确进入本期的会员卡、储值、复杂排班、复杂看板、分成等,进入 P1/P2 或方向库。
|
||||
- 文案不得承诺未实现能力,例如无容量模型时不写“排队/不排队”。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要改代码,除非负责人明确要求。
|
||||
- 不要把假设写成已确认。
|
||||
- 不要覆盖已有评审结论;冲突时新增“变更说明/待确认”。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 产品说明或验收标准。
|
||||
- 用户流程、状态、页面入口、文案口径。
|
||||
- P0/P1/P2 范围切分。
|
||||
- 需要研发确认的 open questions。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
rg -n "TODO|待确认|P0|P1|P2|排队|不排队" /Users/apple/_src/petstore/docs
|
||||
```
|
||||
41
.agents/roles/progress-digest-agent.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Progress Digest Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责项目进展摘要、实现度看板、日报素材和阶段性状态同步。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 摘要必须基于固定任务源、git 状态、QA/Ops 报告和实际文档证据。
|
||||
- 未验证的内容必须标记为未验证。
|
||||
- 不发送外部消息,除非用户明确要求。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改业务代码。
|
||||
- 不要把计划写成完成。
|
||||
- 不要隐藏阻塞或 dirty diff。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 当前进展、完成项、阻塞项、下一步。
|
||||
- 关联任务、owner、证据链接。
|
||||
- 风险和待确认。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs status --short
|
||||
git -C /Users/apple/_src/petstore/frontend status --short
|
||||
git -C /Users/apple/_src/petstore/backend status --short
|
||||
```
|
||||
47
.agents/roles/qa-core-flow-agent.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Core Flow QA Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责门店端和宠主端预约主链路验收:创建预约、可约时段、开始服务、状态迁移、取消、门店数据范围和异常提示。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 验收重点是主链路可用和状态机不产生脏数据。
|
||||
- QA 可以写验收报告、smoke 步骤和必要测试脚本,但不改业务代码。
|
||||
- 发现阻塞问题必须写清复现步骤、实际结果、期望结果、owner 和证据。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs/qa-reports`
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改前后端业务代码。
|
||||
- 不要把未跑过的路径写成通过。
|
||||
- 不要跳过异常态、越权态和重复提交。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- QA 报告。
|
||||
- 复现步骤、账号/环境、数据准备。
|
||||
- 通过/失败项和阻塞 owner。
|
||||
- 截图、日志或命令证据路径。
|
||||
|
||||
## 推荐验证
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
```
|
||||
47
.agents/roles/qa-report-share-agent.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Report Share QA Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责报告填写、报告分享、H5 公开页、成片三态、隐私提示、留资和埋点验收。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 报告分享链路必须覆盖门店端生成、宠主打开、弱网/失败态、重新加载、隐私提示。
|
||||
- 成片必须覆盖 processing / failed / success 三态和失败中文文案。
|
||||
- 留资必须覆盖重复提交、手机号格式、成功态和隐私边界。
|
||||
- QA 不改业务代码。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs/qa-reports`
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要在报告中粘贴完整真实 `report_token`、手机号或私密媒体 URL。
|
||||
- 不要只验收成功路径。
|
||||
- 不要把测试 H5 域名结果写成生产域名通过。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- QA 报告。
|
||||
- 分享入口、公开页 URL 脱敏、成片状态、隐私提示和留资结果。
|
||||
- 失败项 owner、严重级别和复现证据。
|
||||
|
||||
## 推荐验证
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
```
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
61
.agents/roles/report-media-backend-agent.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Report Media Backend Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责服务报告、报告图片/视频素材、自动成片、公开报告页数据、留资和评价相关后端能力。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 报告提交默认一约一份,提交后锁死。
|
||||
- 成片任务必须有 processing / failed / success 三态和失败原因映射。
|
||||
- 公开报告接口必须做字段裁剪,只返回宠主查看和分享需要的数据。
|
||||
- 留资需要考虑同一 `report_token` + 手机号的去重或“已提交”语义。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
- `/Users/apple/_src/petstore/frontend/src/api`
|
||||
- `/Users/apple/_src/petstore/frontend/src/components/report`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/ReportController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/FileController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller/ReportLeadController.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/service/ReportService.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/service/ReportHighlightVideoService.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/service/ReportLeadService.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/service/ReportTestimonialService.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/entity/Report*.java`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/mapper/Report*.java`
|
||||
|
||||
修改 appointment 终态或宠主可见字段时必须通知 Backend Core 和 Customer Experience FE。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要在公开接口返回完整手机号、内部 user id、调试字段或完整敏感 token。
|
||||
- 不要只依赖日志表达成片失败原因。
|
||||
- 不要让报告创建绕过预约状态机约束。
|
||||
- 不要把本地文件路径写入生产可见 URL。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 报告/媒体域改动清单。
|
||||
- API 契约变化和公开字段说明。
|
||||
- 隐私、token、媒体 URL、异步任务影响。
|
||||
- Report Share QA 验收路径。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn test
|
||||
```
|
||||
|
||||
如当前无测试或环境不完整,至少运行:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn -DskipTests package
|
||||
```
|
||||
50
.agents/roles/store-admin-frontend-agent.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Store Admin Frontend Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责门店 **Web 管理后台**(`admin/`)的页面、路由、鉴权壳与对 `/api/**` 的调用封装;不改小程序履约页,不改后端业务规则。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 范围以 `docs/门店管理后台-PhaseA-页面清单PRD.md` 与线框为准。
|
||||
- 遵守 `rule:BR-ADMIN-001`…`004`:boss/staff 可编辑;强制本店;禁止会员/收银/库存偷渡。
|
||||
- Web 可调用「开始服务」;拍照/填报告引导回小程序。
|
||||
- 客户列表以 `GET /api/admin/service-customers` 为准,不以多接口前端拼装为正式方案。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/admin`(建成后)
|
||||
- `/Users/apple/_src/petstore/frontend/src/api`(只读对齐契约)
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller`(只读)
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/admin/**`(工程建成后)
|
||||
- 相关产品说明仅在用户要求同步文档时写 `docs/`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改 `frontend/src/pages/**` 小程序页面(归 Store Miniapp FE / Customer Experience FE)。
|
||||
- 不要在前端实现第二套预约状态机或绕过 service 规则。
|
||||
- 不要引入收银、库存、会员资产页面。
|
||||
- 未获用户「改代码 / 实现后台」指令前,不得创建 `admin/` 工程。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 可登录的壳层 + Phase A 页面(按迭代 A.1→A.3)。
|
||||
- API 封装与错误码展示。
|
||||
- 与 PRD 验收项对应的自测说明。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
# 工程建成后
|
||||
cd /Users/apple/_src/petstore/admin && npm run build
|
||||
# 联调时按任务 brief 中的 smoke
|
||||
```
|
||||
|
||||
## Paired QA
|
||||
|
||||
- 主:`Core Flow QA`(预约/权限/本店范围)
|
||||
- 报告成片/链接:`Report Share QA`
|
||||
58
.agents/roles/store-miniapp-frontend-agent.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Store Miniapp FE Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责宠小它门店端老板/员工小程序体验:预约列表、开始服务、报告填写、发送报告、我的、员工管理、服务类型和店铺设置。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- 门店端服务对象是 boss / staff。
|
||||
- 预约状态文案必须和产品文档保持一致:`new` 待开始、`doing` 服务中、`done` 已完成、`cancel` 已取消。
|
||||
- 报告提交后默认锁死,不要在前端暗示可随意修改。
|
||||
- 与宠主公开页共享组件时,必须通知 Customer Experience FE。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend/src/main/java/com/petstore/controller`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/home`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/appointment`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/report`
|
||||
- `/Users/apple/_src/petstore/frontend/src/pages/mine`
|
||||
- `/Users/apple/_src/petstore/frontend/src/components`
|
||||
- `/Users/apple/_src/petstore/frontend/src/utils`
|
||||
- `/Users/apple/_src/petstore/frontend/src/api`
|
||||
|
||||
共享 `components`、`utils`、`api` 写入前必须声明受影响 owner。
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改后端业务逻辑。
|
||||
- 不要绕过统一 API 封装散落请求。
|
||||
- 不要新增硬编码品牌主色;使用全局 CSS token。
|
||||
- 不要在未确认容量模型前写“排队/不排队”承诺。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 页面或组件改动清单。
|
||||
- 受影响 API 与后端 owner。
|
||||
- 小程序/H5 构建或页面 smoke 结果。
|
||||
- 仍需 QA 覆盖的路径。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
```
|
||||
|
||||
涉及微信小程序配置或页面路由时:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
41
.agents/roles/system-architect-agent.md
Normal file
@ -0,0 +1,41 @@
|
||||
# System Architect Agent
|
||||
|
||||
## 角色定位
|
||||
|
||||
负责跨模块边界、接口契约、状态机、数据一致性、隐私边界和 owner 任务拆分。
|
||||
|
||||
## 业务边界
|
||||
|
||||
- System Architect 评审和拆解方案,不替代业务 owner 写代码。
|
||||
- 跨预约、报告、成片、宠主公开页、留资和发布的方案必须先明确 owner、接口、数据和验证方式。
|
||||
- 结论必须能落到可执行任务,不只停留在原则。
|
||||
|
||||
## 可读路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
- `/Users/apple/_src/petstore/frontend`
|
||||
- `/Users/apple/_src/petstore/backend`
|
||||
|
||||
## 可写路径
|
||||
|
||||
- `/Users/apple/_src/petstore/docs`
|
||||
|
||||
## 禁止事项
|
||||
|
||||
- 不要修改业务代码。
|
||||
- 不要持有前后端 service lock。
|
||||
- 不要输出没有 owner 和验收方式的抽象建议。
|
||||
|
||||
## 标准交付物
|
||||
|
||||
- 架构评审结论。
|
||||
- Owner 拆分、写入范围、接口/表/状态影响。
|
||||
- 必跑验证命令和 QA 验收口径。
|
||||
- 风险和待确认项。
|
||||
|
||||
## 必跑验证命令
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
rg -n "Owner Agent|Service Lock|Data Model Review Required|Access / Privacy" /Users/apple/_src/petstore/docs/.agents
|
||||
```
|
||||
89
.agents/rules/api-contract-boundary.md
Normal file
@ -0,0 +1,89 @@
|
||||
# API / 数据 / 隐私边界规则
|
||||
|
||||
本规则适用于任一任务触碰后端 controller/service/entity/repository、`frontend/src/api`、报告 token、媒体 URL、手机号、门店数据范围或发布配置。
|
||||
|
||||
> **本体对齐**:本规则的边界约束与 `docs/ontology/rules.md` 的规则条目对齐:
|
||||
> - 公开报告字段裁剪 → `rule:BR-RPT-006`
|
||||
> - 公开门店字段裁剪 → `rule:BR-STORE-003`
|
||||
> - 跨店/跨用户 403 → `rule:BR-APPT-002`/`rule:BR-APPT-003`/`rule:BR-RPT-005`/`rule:BR-LEAD-003`/`rule:BR-SCH-001`/`rule:BR-ST-001`/`rule:BR-PET-001`
|
||||
> - session token 鉴权 → `rule:BR-AUTH-001`
|
||||
> - token hash 日志 → `rule:BR-RPT-007`
|
||||
> - 留资脱敏 → `rule:BR-LEAD-002`
|
||||
> 改动这些规则时同步更新 `docs/ontology/rules.md` + `graph/ontology.jsonl`。
|
||||
|
||||
## 归属边界
|
||||
|
||||
| 领域 | Owner Agent | 典型路径 |
|
||||
|---|---|---|
|
||||
| 预约、日程、门店、用户、宠物、服务类型 | Backend Core | `Appointment*`、`Schedule*`、`Store*`、`User*`、`Pet*`、`ServiceType*` |
|
||||
| 报告、媒体、成片、留资、评价 | Report Media Backend | `Report*`、`FileController`、`ReportHighlightVideoService` |
|
||||
| 门店端页面调用 | Store Miniapp FE | `frontend/src/pages/home`、`appointment`、`report`、`mine` |
|
||||
| 宠主公开体验调用 | Customer Experience FE | `frontend/src/pages/report-view`、`video-player`、宠主预约页、公开分享 utils |
|
||||
| 表结构、索引、状态字段、幂等 | Data Model | JPA entity、repository query、数据说明文档 |
|
||||
|
||||
## Controller / Service 规则
|
||||
|
||||
- Controller 只做入口、参数校验、登录/上下文读取和响应映射。
|
||||
- Service 承载领域规则:状态机、数据范围、幂等、权限判断、异步任务状态。
|
||||
- 不要在 Controller 中直接拼复杂业务状态或绕过 Service 改库。
|
||||
- 任何状态更新都必须校验前置状态;预约状态不得直接无条件赋值。
|
||||
- 公开报告接口必须只返回公开页需要的字段,不得透出调试字段、内部权限字段或不必要手机号。
|
||||
|
||||
## Frontend API 规则
|
||||
|
||||
- 新增或修改 API 时,优先在 `frontend/src/api/index.js` 或对应业务 utils 中统一封装。
|
||||
- 页面不直接散落硬编码 URL、token 拼接或错误码判断。
|
||||
- 前端错误态必须有用户可理解文案;不能展示后端堆栈、SQL、Java exception 或未翻译错误。
|
||||
- H5 与小程序若共享报告语义,字段名、三态文案和重试行为必须一致。
|
||||
|
||||
## Data / Privacy 规则
|
||||
|
||||
- `report_token` 是敏感公开链接,不在日志、报告、截图中展示完整值。
|
||||
- 手机号、宠主留资、门店数据范围和媒体 URL 必须有最小化返回原则。
|
||||
- 软删除字段、`store_id` 数据范围和状态字段变更必须通过 Data Model review。
|
||||
- 成片任务需要记录任务状态、失败原因和用户可读映射;不能只依赖日志排查。
|
||||
|
||||
## 变更清单
|
||||
|
||||
接口或数据变更的任务 brief 必须写清:
|
||||
|
||||
- 受影响 endpoint / 前端调用点。
|
||||
- 请求参数和响应字段变化。
|
||||
- 错误码或业务码变化。
|
||||
- 数据表 / entity / repository query 变化。
|
||||
- 是否影响公开访问、report token、手机号、媒体 URL、门店数据范围。
|
||||
- 必跑验证命令和手工验收路径。
|
||||
|
||||
## 推荐验证
|
||||
|
||||
后端:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend
|
||||
mvn test
|
||||
```
|
||||
|
||||
前端:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/frontend
|
||||
npm run build:h5
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
|
||||
文档:
|
||||
|
||||
```bash
|
||||
git -C /Users/apple/_src/petstore/docs diff --check
|
||||
rg -n "report_token|手机号|状态机|预约|成片" /Users/apple/_src/petstore/docs
|
||||
```
|
||||
|
||||
本体(改了 controller/service/entity 后必跑):
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore
|
||||
python3 docs/graph/validate_ontology.py docs # 引用完整性
|
||||
python3 docs/graph/audit_drift.py # 本体—代码漂移审计
|
||||
```
|
||||
|
||||
期望:`validate_ontology` 通过 + `audit_drift` 无 code-only / ontology-only 漂移。
|
||||
33
.agents/templates/review-report.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Agent Review Report
|
||||
|
||||
## 状态
|
||||
|
||||
DONE / DONE_WITH_CONCERNS / BLOCKED
|
||||
|
||||
## 修改文件
|
||||
|
||||
-
|
||||
|
||||
## 读取文件
|
||||
|
||||
-
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
# command
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
## 交付内容
|
||||
|
||||
-
|
||||
|
||||
## 风险与剩余问题
|
||||
|
||||
-
|
||||
|
||||
## 需要主控处理
|
||||
|
||||
-
|
||||
49
.agents/templates/system-architect-review-checklist.md
Normal file
@ -0,0 +1,49 @@
|
||||
# System Architect Review Checklist
|
||||
|
||||
## 方案摘要
|
||||
|
||||
- 背景:
|
||||
- 目标:
|
||||
- 非目标:
|
||||
|
||||
## Owner 拆分
|
||||
|
||||
| Owner Agent | Service Lock | 写入范围 | Paired QA |
|
||||
|---|---|---|---|
|
||||
| | | | |
|
||||
|
||||
## API 契约
|
||||
|
||||
- Endpoint / 调用点:
|
||||
- 请求参数:
|
||||
- 响应字段:
|
||||
- 错误码 / 业务码:
|
||||
- 前端影响:
|
||||
|
||||
## 数据模型
|
||||
|
||||
- Entity / 表:
|
||||
- 状态字段:
|
||||
- 索引:
|
||||
- 幂等:
|
||||
- 迁移 / 兼容:
|
||||
|
||||
## Access / Privacy
|
||||
|
||||
- 登录态:
|
||||
- 门店数据范围:
|
||||
- 手机号 / report token / 媒体 URL:
|
||||
- 公开页字段裁剪:
|
||||
|
||||
## QA 验收
|
||||
|
||||
- 成功路径:
|
||||
- 异常路径:
|
||||
- 越权 / 隐私:
|
||||
- 回归范围:
|
||||
|
||||
## 结论
|
||||
|
||||
- Go / No Go:
|
||||
- 必须先完成:
|
||||
- 可后置:
|
||||
53
.agents/templates/task-brief.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Task Brief
|
||||
|
||||
## 基本信息
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| Task Name | |
|
||||
| Priority | P0 / P1 / P2 |
|
||||
| Workstream | Core Booking Flow / Report Sharing / Release / PM / Architecture |
|
||||
| Track | Product / Coding / QA / Ops / Hotfix / Planning / Review |
|
||||
| Owner Agent | |
|
||||
| Role | |
|
||||
| Paired QA | |
|
||||
| Status | Ready |
|
||||
| Repo/Path | |
|
||||
| Service Lock | |
|
||||
| Depends On | |
|
||||
| Due Date | TBD |
|
||||
| RC Included | Yes / No / TBD |
|
||||
|
||||
## 背景
|
||||
|
||||
- 需求来源:
|
||||
- 相关文档:
|
||||
- 当前问题:
|
||||
|
||||
## 写入范围
|
||||
|
||||
- 允许写:
|
||||
- 只读:
|
||||
- 不允许写:
|
||||
|
||||
## 验收标准
|
||||
|
||||
-
|
||||
|
||||
## 门禁
|
||||
|
||||
| 门禁 | Yes / No | 说明 |
|
||||
|---|---|---|
|
||||
| Architecture Review Required | | |
|
||||
| Data Model Review Required | | |
|
||||
| Access / Privacy Review Required | | |
|
||||
|
||||
## 必跑验证
|
||||
|
||||
```bash
|
||||
# commands
|
||||
```
|
||||
|
||||
## 风险 / 待确认
|
||||
|
||||
-
|
||||
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.local
|
||||
*.tmp
|
||||
*.bak
|
||||
12
AGENTS.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Petstore Docs Agent Instructions
|
||||
|
||||
本 docs 仓库使用 `.agents/` 作为 agent 角色定义和产品研发流程规范源头。
|
||||
|
||||
## 必读顺序
|
||||
|
||||
1. `.agents/common-context.md`
|
||||
2. `.agents/fixed-team-operating-model.md`
|
||||
3. 按任务类型读取 `.agents/roles/` 下的角色文件。
|
||||
4. 涉及接口、数据、权限、隐私、报告 token、媒体 URL 或发布时,读取 `.agents/rules/api-contract-boundary.md`。
|
||||
|
||||
产品说明类任务默认只写 `docs/`。只有用户明确要求“改代码 / 实现功能”时,才进入 `frontend` 或 `backend` 编码任务。
|
||||
@ -1,5 +1,7 @@
|
||||
# API 契约:报告详情 / 列表中的「服务回顾短片」字段
|
||||
|
||||
> **本体对齐**:本文档与 [`ontology/objects.md#entity:report`](./ontology/objects.md) 的 `highlight_*` 字段对齐。冲突以本体为准。
|
||||
>
|
||||
> 与 `ReportController.putHighlightFields`、`ReportHighlightVideoService` 实现一致;前端 `getReportByToken`、`getReportList` 及 `utils/reportShare.js` 依赖本契约。
|
||||
|
||||
## 出现位置
|
||||
|
||||
69
Cursor首轮开发提示词.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Cursor 首轮开发提示词
|
||||
|
||||
把下面整段直接发给 Cursor:
|
||||
|
||||
```text
|
||||
你现在在 /Users/apple/_src/petstore 工作区开发“宠小它 P0 稳定化”的第一批任务。
|
||||
|
||||
请先阅读:
|
||||
1. docs/superpowers/plans/2026-07-05-cursor-p0-handoff.md
|
||||
2. docs/架构与产品评估报告-2026-07-05.md
|
||||
|
||||
本轮只执行 handoff 文档里的 Batch A:文档口径 + 前端低风险修正。
|
||||
|
||||
开始前先运行并汇报:
|
||||
- git -C docs status --short
|
||||
- git -C backend status --short
|
||||
- git -C frontend status --short
|
||||
|
||||
重要边界:
|
||||
1. 只允许修改 Batch A 指定文件:
|
||||
- docs/产品设计文档.md
|
||||
- docs/P0-研发落地清单.md
|
||||
- docs/宠主端预约体验优化-产品说明.md
|
||||
- docs/产品全方位优化说明-v1.md
|
||||
- docs/本期主线-下一步产品优化点.md
|
||||
- docs/公众号协同与全链路触达-产品说明.md
|
||||
- frontend/src/pages/report-view/reportView.vue
|
||||
- frontend/src/pages/report/Report.vue
|
||||
- frontend/src/pages/appointment/CustAppointmentCreate.vue
|
||||
- frontend/src/pages/login/Login.vue
|
||||
- frontend/src/utils/session.js
|
||||
2. 本轮不要修改 backend。
|
||||
3. 不要回退用户已有改动,不要删除 .agents/、AGENTS.md、.DS_Store 等既存变更。
|
||||
4. 不要引入会员、储值、套餐、复杂 CRM、历史报告汇总。
|
||||
5. 不要写“到店不排队”等无容量模型承诺,统一改成“在线选时段,到店更省心”。
|
||||
|
||||
本轮目标:
|
||||
1. 冻结 P0 产品口径:
|
||||
- 本期包含宠主预约、公开报告、留资、基础埋点、报告分享和门店服务闭环。
|
||||
- 会员、储值、套餐、复杂 CRM、历史报告汇总放到 P1/P2。
|
||||
2. 修正报告页角色判断:
|
||||
- 只有 boss/staff 是员工态。
|
||||
- 已登录 customer 打开公开报告页仍要看到留资/预约 CTA。
|
||||
3. 报告填写前端增加必填校验:
|
||||
- 服务前照片至少 1 张。
|
||||
- 服务后照片至少 1 张。
|
||||
- 服务过程中素材可选。
|
||||
4. 宠主预约支持:
|
||||
- 普通 storeId query。
|
||||
- 小程序码 scene 解码。
|
||||
- 未登录可填写,提交时登录,登录后恢复 guest 草稿。
|
||||
|
||||
请按 handoff 文档 Batch A 的 Task A1-A4 顺序执行。每完成一个 task,先自查再继续。
|
||||
|
||||
完成后必须运行:
|
||||
- git -C docs diff --check
|
||||
- npm --prefix frontend install
|
||||
- npm --prefix frontend run build:h5
|
||||
- npm --prefix frontend run build:mp-weixin
|
||||
|
||||
最后请按这个格式汇报:
|
||||
1. 修改了哪些文件
|
||||
2. 每个文件改了什么
|
||||
3. 验收命令和结果
|
||||
4. 还有哪些未覆盖风险
|
||||
5. 是否建议进入 Batch B
|
||||
```
|
||||
*** End Patch
|
||||
|
||||
148
P0-研发落地清单.md
@ -1,10 +1,36 @@
|
||||
# 宠小它 — P0 研发落地清单
|
||||
|
||||
> **本体对齐**:任务条目引用 [`ontology/`](./ontology/) 中的实体/规则 ID(如 `entity:report`、`rule:BR-RPT-002`)。冲突以本体为准。
|
||||
>
|
||||
> 用途:发给研发拆任务与验收;**仅含 P0**(本期质量与主线闭环必需,不含 P1/P2 增强)。
|
||||
> 依据:《产品全方位优化说明 v1》《本期主线》《宠主端预约体验优化》《产品设计文档》及当前仓库实现粗对齐。
|
||||
> 更新:2026-04-19
|
||||
> 更新:2026-07-05(P0 稳定化轮次:冻结本期范围,重新标记状态并绑定 Owner 与验收命令)
|
||||
|
||||
**关联**: [产品全方位优化说明-v1](./产品全方位优化说明-v1.md) · [本期主线-下一步产品优化点](./本期主线-下一步产品优化点.md) · [宠主端预约体验优化-产品说明](./宠主端预约体验优化-产品说明.md)
|
||||
**关联**: [产品全方位优化说明-v1](./产品全方位优化说明-v1.md) · [本期主线-下一步产品优化点](./本期主线-下一步产品优化点.md) · [宠主端预约体验优化-产品说明](./宠主端预约体验优化-产品说明.md) · [架构与产品评估报告-2026-07-05](./架构与产品评估报告-2026-07-05.md) · [Cursor P0 Handoff](./superpowers/plans/2026-07-05-cursor-p0-handoff.md)
|
||||
|
||||
---
|
||||
|
||||
## 本期 P0 范围(冻结口径)
|
||||
|
||||
本期 P0 范围:
|
||||
- 门店端:预约列表、开始服务、填写报告、发送报告、回访池。
|
||||
- 宠主端:预约填单、公开报告页、下次服务提醒留资、报告打开基础埋点。
|
||||
- 报告能力:服务前/服务后照片、服务过程素材、短片生成与下载、分享链接/二维码/话术。
|
||||
|
||||
不进入 P0:
|
||||
- 会员、储值、套餐、复杂 CRM。
|
||||
- 宠主账号内历史报告汇总。
|
||||
- 完整经营分析看板。
|
||||
- 「到店不排队」等无容量模型承诺,统一文案为「在线选时段,到店更省心」。
|
||||
|
||||
---
|
||||
|
||||
## 状态标记说明
|
||||
|
||||
- ✅ 已实现:仓库已实现且通过本次构建/验收命令。
|
||||
- 🟡 部分实现:主路径已落,但仍有 gap(已列出),需按验收命令闭环。
|
||||
- ❌ 未实现:本期必须补齐。
|
||||
- 🧪 待测试:实现可能已存在,但缺自动化测试或人工 smoke 证据。
|
||||
|
||||
---
|
||||
|
||||
@ -17,71 +43,93 @@
|
||||
|
||||
## A. 后端(Spring Boot)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **A1** | **预约状态机校验**:`PUT /appointment/status`(及任意改状态入口)禁止非法迁移 | 仅允许:`new→doing`、`new→cancel`、`doing→done`(若报告提交会改 done 则接口侧与之一致)、`doing→cancel`(若产品允许,需与前端一致;**若产品规定仅 new 可取消**则 `doing→cancel` 返回 `CANCEL_NOT_ALLOWED`) | 当前 `AppointmentService.updateStatus` / `startService` 偏「直接赋值」,需按《产品设计文档》矩阵校验;非法迁移返回 **4xx + 明确业务码**(如 `INVALID_STATUS`) |
|
||||
| **A2** | **开始服务**仅当 `status=new` | `startService` 非 `new` 时拒绝,勿静默改库 | 与「谁点谁服务」一致 |
|
||||
| **A3** | **取消预约**(若独立接口)校验:仅允许目标状态为 `cancel` 且前置状态符合产品规则 | 与 A1 同一套状态机;前端取消按钮仅 `new` 时展示则后端仍要防篡改 | 防恶意请求 |
|
||||
| **A4** | (可选 P0,若本期上留资)**留资去重**:同一 `report_token` + 同一手机号 | 二次提交返回成功态或「已提交」语义,不重复插库 | 口径见《本期主线》C |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **A1** | **预约状态机校验**:`PUT /appointment/status`(及任意改状态入口)禁止非法迁移 | 仅允许:`new→doing`、`new→cancel`、`doing→done`、`doing→cancel`(与产品最终规则一致);非法迁移返回 **4xx + 明确业务码**(如 `INVALID_STATUS`、`CANCEL_NOT_ALLOWED`) | Backend Core | 🟡 部分实现:`AppointmentService.updateStatus`/`startService` 已集中校验,但缺自动化测试 | `mvn -f backend/pom.xml test`(待补 `AppointmentServiceTest`) | 与「谁点谁服务」一致;Batch D 补测试 |
|
||||
| **A2** | **开始服务**仅当 `status=new` | `startService` 非 `new` 时拒绝,勿静默改库 | Backend Core | 🟡 部分实现:服务层已校验,需补测试 | 同 A1 | 与 A1 同一套状态机 |
|
||||
| **A3** | **取消预约**校验:仅允许目标状态为 `cancel` 且前置状态符合产品规则 | 与 A1 同一套状态机;前端取消按钮仅 `new` 时展示则后端仍要防篡改 | Backend Core | 🟡 部分实现:状态机已收口,需补测试 | 同 A1 | 防恶意请求 |
|
||||
| **A4** | **留资去重**:同一 `report_token` + 同一手机号 | 二次提交返回成功态或「已提交」语义,不重复插库 | Report Media Backend | 🟡 部分实现:去重逻辑已存在,需补测试用例 | `mvn -f backend/pom.xml test`(待补 `ReportLeadControllerTest`) | 口径见《本期主线》C |
|
||||
| **A5** | **公开报告字段裁剪**:`GET /api/report/get?token=` 不返回 top-level `userId/storeId/reportToken`;日志只记 token hash | 公开 token 请求只返回页面必要字段;store 对象可返回 `id` 用于预约跳转 | System Architect / Report Media Backend | ✅ 已实现(Batch B) | 手工 curl + `mvn test` | 对应 P0-2 |
|
||||
| **A6** | **报告一约一份**:`Report.appointment_id` 唯一约束;服务层拒绝同预约重复提交 | 同一 `appointmentId` 第二次提交返回 409 / `REPORT_ALREADY_EXISTS`;非 `doing` 预约不能创建报告;无预约提交返回 `APPOINTMENT_REQUIRED` | System Architect / Data Model / Report Media Backend | ✅ 已实现(Batch B/D,含 `ReportServiceTest`/`ReportControllerTest`) | `mvn -f backend/pom.xml test` | 对应 P0-3;上线前需清理重复数据 |
|
||||
| **A7** | **报告前后照片必填后端兜底** | 缺 before 返回 `BEFORE_PHOTO_REQUIRED`;缺 after 返回 `AFTER_PHOTO_REQUIRED` | Report Media Backend | ✅ 已实现(Batch B,含 `ReportControllerTest`) | `mvn -f backend/pom.xml test` | 与前端 B5 同步落地 |
|
||||
| **A8** | **统一鉴权上下文**:HMAC session token + `CurrentUserContext`,protected API 不再信任请求里的身份字段 | 未登录返回 401;A 店用户访问 B 店预约/报告/线索返回 403;customer 不能用任意 `userId` 创建他人宠物/预约 | System Architect / Backend Core | ✅ 已实现(Batch C,含 `SessionTokenServiceTest`) | `mvn -f backend/pom.xml test` + 跨店/跨用户接口验证 | 对应 P0-1;不引入完整 Spring Security |
|
||||
| **A9** | **线索脱敏**:`/report/leads` 默认不返回完整 `wechatOpenid/wechatUnionid` | 回包只有 `wechatBound`、`wechatOpenidMasked`、`wechatUnionidMasked` | Report Media Backend | ✅ 已实现(Batch B) | 手工 curl + `mvn test` | 对应 P0-2 |
|
||||
| **A10** | **配置安全与启动迁移收口**:`application.yml` 不保留真实敏感值;生产 profile 禁用 `ddl-auto:update`/`show-sql`;移除启动期 DDL/历史 UPDATE | 仓库不含真实 DB/微信密钥;生产启动只读环境变量 | Backend Ops / System Architect | ✅ 已实现(Batch D) | `rg` 扫描 + 启动验证 | 对应 P0-8;已暴露凭据需轮换 |
|
||||
| **A11** | **上传路径与类型保护**:`FileController` 归一化路径 + 扩展名/MIME 校验 | `/api/upload/image/../...`、伪装扩展名、超大文件均被拒绝 | Backend Core / System Architect | ✅ 已实现(Batch D) | 手工 curl + `mvn test` | 对应 P0-9 |
|
||||
| **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;26 个用例全部通过 |
|
||||
|
||||
---
|
||||
|
||||
## B. 小程序 — 门店端(老板/员工)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **B1** | **「已完成」Tab 列表默认不含已取消** | Tab「已完成」下默认仅展示 `status===done`;`cancel` 通过 **次级筛选**(如「包含已取消」开关)或 **单独子 Tab「已取消」** 二选一实现 | 当前 `Home.vue` 约 `currentStatus==='done'` 时含 `cancel`,需改 |
|
||||
| **B2** | **发送报告成功页**:同屏 **报告链接 + 二维码 + 两条可复制话术** | 话术支持变量:`{宠物名}{服务类型}{门店名}{链接}`;一键复制单条话术;二维码与链接不拆多页 | 对齐《本期主线》A「员工动作成本」 |
|
||||
| **B3** | **报告提交锁死提示** | 填写报告页底部或提交前弹窗固定文案:**「提交后不可修改,请确认照片与备注无误」**(与产品 v1 锁死策略一致) | 若已有则核对文案 |
|
||||
| **B4** | **「我的」老板端分组标题** | 至少两组:`店铺与经营` / `团队与账号`(或等价命名),高频在上;使用现有 `.module-title` | 降低入口迷失,见《产品全方位优化说明》§2.2 |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **B1** | **「已完成」Tab 列表默认不含已取消** | Tab「已完成」下默认仅展示 `status===done`;`cancel` 通过次级筛选或单独子 TAB 实现 | Store Miniapp FE | 🟡 部分实现(以现状为准做回归) | `npm --prefix frontend run build:h5` + 手工 smoke | 当前 `Home.vue` 需对照 |
|
||||
| **B2** | **发送报告成功页**:同屏 报告链接 + 二维码 + 两条可复制话术 | 话术支持变量:`{宠物名}{服务类型}{门店名}{链接}`;一键复制单条话术 | Store Miniapp FE | ✅ 已实现(`ReportShareModal.vue`) | `npm --prefix frontend run build:h5` + 手工 smoke | 对齐《本期主线》A |
|
||||
| **B3** | **报告提交锁死提示** | 提交前弹窗固定文案:「提交后不可修改,请确认照片与备注无误」 | Store Miniapp FE | 🟡 部分实现:核对文案与位置 | 手工 smoke | 与产品 v1 锁死策略一致 |
|
||||
| **B4** | **「我的」老板端分组标题** | 至少两组:`店铺与经营` / `团队与账号`;高频在上;使用 `.module-title` | Store Miniapp FE | ✅ 已实现 | `npm --prefix frontend run build:h5` + 手工 smoke | 见《产品全方位优化说明》§2.2 |
|
||||
| **B5** | **报告前后照片必填前端校验** | 无 before 提示「请至少上传1张服务前照片」;无 after 提示「请至少上传1张服务后照片」 | Store Miniapp FE / Customer Experience FE | ✅ 已实现(Batch A) | `npm --prefix frontend run build:h5` + `npm --prefix frontend run build:mp-weixin` | 对应 A7、P0-4 |
|
||||
|
||||
---
|
||||
|
||||
## C. 小程序 — 宠主端(customer)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **C1** | **单一主预约路径** | 首屏仅保留 **一个**主级「预约服务」入口;**不再同屏强并存**「大按钮 + 空态大按钮 + FAB +」等效重复(保留 FAB 则须显著弱于主按钮或仅老用户) | 见《宠主端预约体验优化》§9;需对照当前 `Home.vue` 是否仍重复 |
|
||||
| **C2** | **扫码 / 分享落地 `storeId`** | `CustAppointmentCreate` 支持 `onLoad` 读 `storeId`(已有则做 **全入口** 回归:普通二维码、小程序码 scene、分享 path/query) | 预填门店且可「更换门店」 |
|
||||
| **C3** | **登录时机与草稿** | 业务规则二选一落地并写 README:**(1)** 未登录可填单,提交时再登录,返回后 **草稿恢复**;**(2)** 进入预约页即要求登录。推荐 (1) | `uni.storage` 或状态恢复;避免白填 |
|
||||
| **C4** | **异常可理解** | 无可用时段、网络失败、未登录:均有 **toast / 空态 / 重试**,禁止静默失败 | 专文 §7 |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **C1** | **单一主预约路径** | 首屏仅保留一个主级「预约服务」入口;不再同屏强并存「大按钮 + 空态大按钮 + FAB +」等效重复 | Customer Experience FE | 🟡 部分实现:需对照当前 `Home.vue` 复核 | 手工 smoke | 见《宠主端预约体验优化》§9 |
|
||||
| **C2** | **扫码 / 分享落地 `storeId`**(含小程序码 `scene` 解码) | 普通 `storeId` query、小程序码 `scene=storeId%3D1`、分享 path/query 均预填门店且可「更换门店」 | Customer Experience FE | ✅ 已实现(Batch A) | `npm --prefix frontend run build:h5` + `npm --prefix frontend run build:mp-weixin` + 手工 smoke | 对应 P0-6、P1-1 |
|
||||
| **C3** | **登录时机与草稿**(冻结策略:提交时登录 + guest 草稿恢复) | 未登录可填单,提交时跳登录,登录后草稿恢复;草稿 key 不依赖 `userInfo.id`;不会产生 `userId: undefined` | Customer Experience FE / Backend Core | ✅ 已实现(Batch A) | 手工 smoke:未登录填写 → 提交跳登录 → 草稿恢复 | 与 `Login.vue` redirect 联动 |
|
||||
| **C4** | **异常可理解** | 无可用时段、网络失败、未登录:均有 toast / 空态 / 重试,禁止静默失败 | Customer Experience FE | 🟡 部分实现:需逐场景回归 | 手工 smoke | 专文 §7 |
|
||||
| **C5** | **报告页角色判断**:`isStaff` 改为角色判断(boss/staff) | customer 登录态打开公开报告页仍显示留资/预约 CTA;boss/staff 才显示内部分享/保存 | Customer Experience FE | ✅ 已实现(Batch A) | 手工 smoke | 对应 P0-7 |
|
||||
|
||||
---
|
||||
|
||||
## D. H5 报告页(`report-view` / 独立 H5)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **D1** | **隐私提示(短期策略)** | 首屏或固定条:**「本页含宠物服务影像,请勿公开转发或发群」**(文案可微调,须合规友好) | 对齐《本期主线》A token 风险 |
|
||||
| **D2** | **短片三态与文案对齐小程序** | `processing / failed / success` 与 `ReportHighlightBlock` 一致;失败展示 **归类中文**,不暴露堆栈;成功展示可变时长说明 | H5 与小程序报告页行为一致 |
|
||||
| **D3** | **弱网 / 失败重试** | 报告主接口失败:**按钮「重新加载」**;超时阈值产品建议 **8~10s** 后出失败态 | 与《产品全方位优化说明》§3.3 |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **D1** | **隐私提示(短期策略)** | 首屏或固定条:「本页含宠物服务影像,请勿公开转发或发群」 | Customer Experience FE / Product Design | 🟡 部分实现:核对文案与位置 | 手工 smoke | 对齐《本期主线》A token 风险 |
|
||||
| **D2** | **短片三态与文案对齐小程序** | `processing / failed / success` 与 `ReportHighlightBlock` 一致;失败展示归类中文,不暴露堆栈 | Customer Experience FE | 🟡 部分实现:需对照三态文案 | 手工 smoke | H5 与小程序报告页行为一致 |
|
||||
| **D3** | **弱网 / 失败重试** | 报告主接口失败:按钮「重新加载」;超时阈值 8~10s 后出失败态 | Customer Experience FE | 🟡 部分实现 | 手工 smoke | 与《产品全方位优化说明》§3.3 |
|
||||
| **D4** | **公开报告 token 使用方式**:不依赖回包 `reportToken`,只用路由 token 做留资/分享 | 公开报告页 `reminderToken` = 路由 token;不读 `reportData.reportToken` | Customer Experience FE | ✅ 已实现(Batch A) | 手工 smoke + 代码审查 | 与 A5 联动 |
|
||||
|
||||
---
|
||||
|
||||
## E. 传播与埋点(P0 最小集)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **E1** | **分享卡片元信息规则** | 小程序 `onShareAppMessage`:标题 **宠物名 + 服务 + 门店**;封面优先级 **成片封面 > 服务后首图 > Logo**(与 `reportShare.js` 逻辑对齐) | 见 `frontend/src/utils/reportShare.js` |
|
||||
| **E2** | **埋点:报告打开** | 至少上报:`report_token` 或匿名 hash、**首次/再次**(可用本地 storage 记首次)、时间戳;落地页 H5 亦要能报(像素/接口二选一,研发定方案) | 不要求看板,要求 **可统计**《本期主线》 |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **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 |
|
||||
| **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 |
|
||||
|
||||
---
|
||||
|
||||
## F. 视觉与规范(工程债)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **F1** | **禁止业务页硬编码主色** | 主色/品牌浅底统一 `var(--c-brand)`、`var(--c-brand-light)` 等;全局 grep `#16a34a`、`#2db96d`(除 `global.css` / 极少量渐变定义)**业务 vue 内为 0** | 《前端 UI 规范》已对齐 `--c-*` |
|
||||
| **F2** | **空态禁用 emoji 作主视觉**(若违反现有 UI 禁止项) | 宠主空态等用 `AppIcon` 或插图占位,不用 📋 等作为主视觉 | 见 `Home.vue` 空态 slot |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **F1** | **禁止业务页硬编码主色** | 主色/品牌浅底统一 `var(--c-brand)` 等;`rg "#16a34a|#2db96d"` 业务 vue 内为 0(除 `global.css`/极少量渐变定义) | Store Miniapp FE / Customer Experience FE | 🟡 部分实现 | `rg "#16a34a|#2db96d" frontend/src/pages` | 《前端 UI 规范》已对齐 `--c-*` |
|
||||
| **F2** | **空态禁用 emoji 作主视觉** | 宠主空态等用 `AppIcon` 或插图占位,不用 📋 等作为主视觉 | Customer Experience FE | 🟡 部分实现 | 代码审查 | 见 `Home.vue` 空态 slot |
|
||||
|
||||
---
|
||||
|
||||
## G. 文案与对外承诺(配置级)
|
||||
|
||||
| ID | 需求 | 验收标准 | 备注 |
|
||||
|----|------|----------|------|
|
||||
| **G1** | **无容量模型不写「排队」** | 全局检索「排队」「不排队」;宠主副标题等改为不承诺排队能力的表述(如「在线选时段」) | 与《产品全方位优化说明》D2 一致 |
|
||||
| **G2** | **对内录单表述(可选)** | 门店端新建预约入口文案是否改为「代客预约」由产品拍板;若改则全局统一 | 降低「预约」歧义 |
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **G1** | **无容量模型不写「排队」** | 全局检索「排队」「不排队」;宠主副标题等改为「在线选时段,到店更省心」 | Product Design / Customer Experience FE | ✅ 已实现(Batch A 文档冻结) | `rg "到店不排队|不排队" docs frontend/src` | 与《产品全方位优化说明》D2 一致 |
|
||||
| **G2** | **对内录单表述(可选)** | 门店端新建预约入口文案是否改为「代客预约」由产品拍板;若改则全局统一 | Product Design | 🟡 待产品拍板 | — | 降低「预约」歧义 |
|
||||
|
||||
---
|
||||
|
||||
## H. 发布 RC 门禁(Batch D 收口)
|
||||
|
||||
| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|
||||
|----|------|----------|-------|------|----------|------|
|
||||
| **H1** | **backend README RC 清单** | 含 `ffmpeg -version`、`ffprobe -version`、`curl -I $APP_BASE_URL/api/upload/image/<known-file>`、`mvn test`;写明 `APP_BASE_URL`、`HIGHLIGHT_*`、`PETSTORE_SESSION_SECRET`、上传目录权限、Nginx `client_max_body_size` | Backend Ops | ✅ 已实现(Batch D) | 代码审查 | 对应 P0-10 |
|
||||
| **H2** | **frontend README RC 清单** | 含 `npm install`、`build:h5`、`build:mp-weixin`;写明 `VITE_API_ORIGIN`、`VITE_REPORT_PUBLIC_ORIGIN`、微信 request/download 合法域名、报告二维码链接检查 | Frontend Release Ops | ✅ 已实现(Batch D) | 代码审查 | 对应 P0-10 |
|
||||
| **H3** | **P0 通过条件** | 见下方「P0 通过条件」块 | System Architect / Core Flow QA | 🟡 待人工 smoke 复核 | 见下方 | 对应 P0-10 |
|
||||
|
||||
---
|
||||
|
||||
@ -90,15 +138,33 @@
|
||||
1. **A1~A3**(后端状态机)— 防脏数据,优先合。
|
||||
2. **B1**(已完成 Tab)— 前端小改、用户感知强。
|
||||
3. **B2、D1、D2、E1**(传播与报告体验)— 主线门面。
|
||||
4. **C1~C4**(宠主)— 与登录/路由并行可排。
|
||||
5. **E2、F1、F2、G1** — 可并行或插空。
|
||||
4. **C1~C5**(宠主)— 与登录/路由并行可排。
|
||||
5. **E2、E3、F1、F2、G1** — 可并行或插空。
|
||||
6. **A5~A12**(后端边界、隐私、配置、上传、测试)— Batch B/C/D。
|
||||
7. **H1~H3**(发布 RC 门禁)— Batch D 收口。
|
||||
|
||||
---
|
||||
|
||||
## P0 通过条件(最终门禁)
|
||||
|
||||
- 后端 `mvn -f backend/pom.xml test` 有实际测试用例并通过(不再 `No tests to run`)。
|
||||
- 前端 `npm --prefix frontend run build:h5` 与 `npm --prefix frontend run build:mp-weixin` 均通过。
|
||||
- 公开报告页 token 匿名可打开,且不返回 top-level `userId/storeId/reportToken`。
|
||||
- boss/staff 登录后可生成报告、发送报告、查看回访池。
|
||||
- customer 登录态打开报告仍显示留资和预约 CTA。
|
||||
- 同一 `appointmentId` 第二次提交报告返回 `REPORT_ALREADY_EXISTS`。
|
||||
- 报告前后照片缺失返回对应业务码。
|
||||
- A 店 boss/staff 不能查 B 店 leads/list/report;customer 不能用任意 `userId` 创建他人宠物/预约。
|
||||
- 生产配置扫描不含真实 DB/微信密钥,无 `ddl-auto:update`,无宽 CORS。
|
||||
- 文档口径扫描:`rg "到店不排队|会员储值 \| 路线图 P0|宠主通过微信查看服务报告(v2)|无限制,随便选" docs` 无命中。
|
||||
|
||||
---
|
||||
|
||||
## 不在 P0(勿与本清单混排)
|
||||
|
||||
- 会员卡 / 储值 / 次卡
|
||||
- 报告 token **作废重发**(中期,见 D3 决策)
|
||||
- 会员卡 / 储值 / 次卡 / 套餐(P1/P2)
|
||||
- 宠主账号内历史报告汇总(P2)
|
||||
- 报告 token 作废重发(中期,见 D3 决策)
|
||||
- 多技师分成、排班深度联动
|
||||
- 复杂数据看板、漏斗大屏
|
||||
- 分包优化(可 P1)
|
||||
|
||||
134
coverage/ontology-coverage-audit.md
Normal file
@ -0,0 +1,134 @@
|
||||
# 本体覆盖度审计(Ontology Coverage Audit)
|
||||
|
||||
> 本体条目 ↔ 文档章节 ↔ 代码路径 ↔ 测试用例 三方对齐审计。
|
||||
> 证据强度:`anchored`(三处对齐)/ `documented`(仅文档)/ `implemented`(仅代码)/ `gap`(缺失)。
|
||||
>
|
||||
> **刷新时间**:2026-07-10(Batch 补全:domain 回填、敏感字段规则、寄语/退订/周期规则、客户端埋点 Action、P1/P2 gap 占位)
|
||||
|
||||
## 审计方法
|
||||
|
||||
对 `graph/ontology.jsonl` 的每个条目,核对:
|
||||
1. **文档**:`doc` 字段指向的 docs 路径是否存在且描述一致
|
||||
2. **代码**:`code` 字段指向的源码路径是否存在且实现一致
|
||||
3. **测试**:`test` 字段指向的测试用例是否存在且覆盖该条目
|
||||
|
||||
任一缺失则降级证据强度。
|
||||
|
||||
## 当前规模(以 validate 为准)
|
||||
|
||||
| 类型 | 数量 |
|
||||
|------|------|
|
||||
| Entity | 20 |
|
||||
| Action | 56 |
|
||||
| Event | 23 |
|
||||
| Rule | 34 |
|
||||
| Relation | 56 |
|
||||
| **合计** | **189** |
|
||||
|
||||
证据强度:`anchored` 158(83.6%)/ `documented` 18 / `gap` 11 / `implemented` 2。
|
||||
|
||||
> anchored 占比相对 Batch 2 下降,是因为主动录入了 P1/P2 `gap` 占位与客户端埋点 `documented` 条目;端点漂移仍为 0。
|
||||
|
||||
校验:
|
||||
|
||||
```bash
|
||||
python3 docs/graph/validate_ontology.py docs
|
||||
python3 docs/graph/audit_drift.py
|
||||
```
|
||||
|
||||
当前结果:校验通过;HTTP 端点 52/52、JPA Entity 11/11 对齐;非 JPA 领域实体 9 个(含 HighlightVideo、SessionToken、CurrentUser、HighlightFailReason 与 5 个 roadmap gap)。
|
||||
|
||||
## 元数据卫生
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| Action / Relation `domain` | ✅ 已回填(0 缺失) |
|
||||
| gap 入口 `deprecated` | ✅ `register_store`、`wechat_authorize`、`wechat_callback` |
|
||||
| 寄语事件独立化 | ✅ `event:testimonial_saved`(不再误挂 `report_submitted`) |
|
||||
| `video_*` emittedBy | ✅ 挂到 `action:track_video_play` / `track_video_save` |
|
||||
|
||||
## 核心实体覆盖
|
||||
|
||||
| 实体 | 文档 | 代码 | 测试 | 证据 | 备注 |
|
||||
|------|------|------|------|------|------|
|
||||
| store | ✅ | ✅ | StoreControllerTest | anchored | BR-STORE-003 公开字段裁剪已落地 |
|
||||
| user | ✅ | ✅ | UserServiceLoginStrategyTest + UserJsonIgnoreTest | anchored | password/openid/unionid 均 @JsonIgnore |
|
||||
| appointment | ✅ | ✅ | AppointmentServiceTest / DetailTest | anchored | |
|
||||
| report | ✅ | ✅ | ReportServiceTest / ControllerTest | anchored | uk 上线需清重 |
|
||||
| report_lead | ✅ | ✅ | ReportLeadControllerTest | anchored | |
|
||||
| pet | ✅ | ✅ | PetControllerTest | anchored | |
|
||||
| service_type | ✅ | ✅ | ServiceTypeControllerTest | anchored | |
|
||||
| schedule_block | ✅ | ✅ | ScheduleControllerTest | anchored | |
|
||||
| report_image | ✅ | ✅ | 间接 ReportServiceTest | anchored | 建议补独立类型用例 |
|
||||
| highlight_video | ✅ | ✅ | ReportHighlightVideoServiceTest | anchored | 状态机/归类单测;真 FFmpeg E2E 仍可选 |
|
||||
| session_token | ✅ | ✅ | SessionTokenServiceTest | anchored | jpa:false |
|
||||
| report_testimonial | ✅ | ✅ | ReportTestimonialServiceTest | anchored | |
|
||||
| service_interval | ✅ | ✅ | 间接 ReportLeadControllerTest | anchored | |
|
||||
| highlight_fail_reason | ✅ | ✅ 枚举 | ReportHighlightVideoServiceTest | anchored | jpa:false |
|
||||
| current_user | ✅ | ✅ record | SessionTokenServiceTest | anchored | jpa:false |
|
||||
| membership 等 5 个 | 方向库 | — | — | gap | roadmap 占位 |
|
||||
|
||||
## 规则覆盖度(34 条)
|
||||
|
||||
| 规则 | 测试 | 证据 |
|
||||
|------|------|------|
|
||||
| BR-AUTH-001/002 | SessionTokenServiceTest / UserServiceLoginStrategyTest | anchored |
|
||||
| BR-STORE-001/002 | StoreControllerTest | anchored |
|
||||
| BR-STORE-003 公开门店字段裁剪 | StoreControllerTest#publicGet/List… | anchored |
|
||||
| BR-APPT-001..003 | Appointment*Test | anchored |
|
||||
| BR-APPT-004 时段校验 | 间接 | anchored |
|
||||
| BR-RPT-001..006 | Report*Test | anchored |
|
||||
| BR-RPT-007 token hash | 间接 | anchored |
|
||||
| BR-LEAD-001..003 | ReportLeadControllerTest(001 显式;002/003 建议加强断言) | anchored |
|
||||
| BR-UPLOAD-001/002 | 间接 | anchored |
|
||||
| BR-HL-001/002 | ReportHighlightVideoServiceTest | anchored |
|
||||
| BR-SCH-001 / BR-ST-001 / BR-PET-001 | 对应 ControllerTest | anchored |
|
||||
| BR-USER-001/002 | 间接 | anchored |
|
||||
| BR-USER-003 敏感字段 JsonIgnore | UserJsonIgnoreTest | anchored |
|
||||
| BR-CONFIG-001/002 | —(配置) | anchored |
|
||||
| BR-TST-001 一报告一寄语 | ReportTestimonialServiceTest | anchored |
|
||||
| BR-UNSUB-001 / BR-INT-001 | ReportLeadControllerTest | anchored |
|
||||
|
||||
## 待补 gap 清单
|
||||
|
||||
| 优先级 | gap | 状态 |
|
||||
|--------|-----|------|
|
||||
| ~~高~~ | ~~User.password @JsonIgnore~~ | ✅ |
|
||||
| ~~高~~ | ~~domain 回填 / 覆盖审计过期~~ | ✅ |
|
||||
| ~~中~~ | ~~寄语/退订/周期规则连通~~ | ✅ |
|
||||
| ~~高~~ | ~~成片三态 / 失败归类单测~~ | ✅ `ReportHighlightVideoServiceTest` |
|
||||
| ~~高~~ | ~~寄语幂等独立用例~~ | ✅ `ReportTestimonialServiceTest` |
|
||||
| ~~中~~ | ~~`rule:BR-STORE-003` 公开 Store 字段裁剪~~ | ✅ `toPublicStoreView` |
|
||||
| 中 | `uk_report_appointment` 上线迁移 | 🟡 运维 |
|
||||
| 中 | 埋点事件可统计落地 | 🟡 P1 |
|
||||
| 中 | production profile 实测启动 | 🟡 |
|
||||
| 低 | 成片真 FFmpeg 端到端集成测(本机有 ffmpeg) | 🟡 单测已覆盖状态机与归类 |
|
||||
| 低 | `action:logout` / `event:session_revoked` 服务端吊销 | 🟡 gap |
|
||||
| 低 | 废弃 `register_store` / wechat OAuth demo 代码路径 | 🟡 deprecated 已标 |
|
||||
| — | 会员/储值/套餐/历史报告/看板 | gap 占位,不展开 |
|
||||
|
||||
## P1/P2 方向(jsonl 已占位)
|
||||
|
||||
- `entity:membership` / `stored_value` / `service_package` — P1/P2
|
||||
- `entity:report_history` / `analytics_dashboard` — P2
|
||||
|
||||
本期不实现,仅 `evidence=gap` + `jpa=false`,避免本体膨胀成未实现产品说明书。受 `rule:BR-ADMIN-004` 约束,**不得写入 Phase A 门店后台设计**。
|
||||
|
||||
## 门店 Web 后台 Phase A 预录入(2026-07-10)
|
||||
|
||||
> 产品:`docs/门店管理后台升级方案-对标宠老板.md`。全部 `documented` / `gap`,**尚未编码**。
|
||||
|
||||
| ID | 类型 | 用途 |
|
||||
|----|------|------|
|
||||
| `entity:admin_console` | Entity | Web 管理端壳 |
|
||||
| `entity:workbench` / `workbench_todo_item` | Entity | 今日工作台与待办 |
|
||||
| `entity:service_customer` | Entity | 瘦身客户主档(非会员卡) |
|
||||
| `action:view_workbench` 等 8 个 admin_* | Action | 工作台/列表/设置/排班 |
|
||||
| `rule:BR-ADMIN-001`…`004` | Rule | 角色、店范围、内部字段、范围闸门 |
|
||||
| `event:workbench_viewed` | Event | 可选埋点(gap) |
|
||||
|
||||
设计阶段每个页面 brief 应带 `ontologyRefs`(例:`[entity:workbench, action:view_workbench, rule:BR-ADMIN-001]`)。
|
||||
|
||||
**页面清单 PRD(已写)**:`docs/门店管理后台-PhaseA-页面清单PRD.md`(A0–A7 全页字段/筛选/API/验收)。
|
||||
|
||||
当前 validate 规模以脚本输出为准(约 222 条目)。
|
||||
296
graph/audit_drift.py
Executable file
@ -0,0 +1,296 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
宠小它 本体—代码漂移审计脚本。
|
||||
|
||||
扫描 backend/src/main/java/com/petstore/controller/*.java,提取所有 HTTP 端点
|
||||
(类级 @RequestMapping + 方法级 @GetMapping/@PostMapping/@PutMapping/@DeleteMapping),
|
||||
对比 docs/graph/ontology.jsonl 中 Action 的 entrypoints,报告:
|
||||
|
||||
1. code-only:代码里有但本体未录入的端点(drift:应补录本体或确认废弃)
|
||||
2. ontology-only:本体里有但代码里找不到的端点(drift:可能是已删除接口或路径写错)
|
||||
3. entity-coverage:扫描 backend/src/main/java/com/petstore/entity/*.java,对比 ontology.jsonl 的 Entity,
|
||||
报告未录入的实体类。
|
||||
|
||||
用法:
|
||||
python3 docs/graph/audit_drift.py [repo_root]
|
||||
|
||||
默认 repo_root = . (当前工作目录,应能访问 backend/ 和 docs/)。
|
||||
退出码:0 = 无漂移;1 = 有漂移(warning);2 = 解析错误。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
VALID_TYPES = {"Entity", "Action"}
|
||||
|
||||
|
||||
def find_controller_files(repo_root):
|
||||
ctrl_dir = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore", "controller")
|
||||
if not os.path.isdir(ctrl_dir):
|
||||
return []
|
||||
return sorted(
|
||||
os.path.join(ctrl_dir, f) for f in os.listdir(ctrl_dir) if f.endswith(".java")
|
||||
)
|
||||
|
||||
|
||||
def find_entity_files(repo_root):
|
||||
ent_dir = os.path.join(repo_root, "backend", "src", "main", "java", "com", "petstore", "entity")
|
||||
if not os.path.isdir(ent_dir):
|
||||
return []
|
||||
return sorted(
|
||||
os.path.join(ent_dir, f) for f in os.listdir(ent_dir) if f.endswith(".java")
|
||||
)
|
||||
|
||||
|
||||
# 类级 @RequestMapping("/api/xxx")
|
||||
CLASS_MAPPING_RE = re.compile(r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?"([^"]+)"', re.MULTILINE)
|
||||
# 方法级 mapping 注解
|
||||
METHOD_MAPPING_RE = re.compile(
|
||||
r'@(GetMapping|PostMapping|PutMapping|DeleteMapping)\s*\(\s*(?:value\s*=\s*)?"([^"]*)"',
|
||||
re.MULTILINE,
|
||||
)
|
||||
# 方法签名(紧跟 mapping 注解后的 public 方法)
|
||||
METHOD_SIG_RE = re.compile(
|
||||
r'public\s+\S+\s+(\w+)\s*\(',
|
||||
re.MULTILINE,
|
||||
)
|
||||
# 类名
|
||||
CLASS_NAME_RE = re.compile(r'class\s+(\w+)\b')
|
||||
|
||||
|
||||
def extract_endpoints(java_path):
|
||||
"""从单个 controller 文件提取 HTTP 端点:返回 [(http_method, full_path, java_method_name), ...]"""
|
||||
with open(java_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
class_name_match = CLASS_NAME_RE.search(content)
|
||||
class_name = class_name_match.group(1) if class_name_match else os.path.basename(java_path)
|
||||
|
||||
# 类级基础路径
|
||||
class_path = ""
|
||||
cm = CLASS_MAPPING_RE.search(content)
|
||||
if cm:
|
||||
class_path = cm.group(1)
|
||||
|
||||
endpoints = []
|
||||
|
||||
# 按方法级 mapping 注解切分
|
||||
# 找所有方法级 mapping 注解的位置
|
||||
for m in METHOD_MAPPING_RE.finditer(content):
|
||||
annotation = m.group(1)
|
||||
method_path = m.group(2)
|
||||
http_method = {
|
||||
"GetMapping": "GET",
|
||||
"PostMapping": "POST",
|
||||
"PutMapping": "PUT",
|
||||
"DeleteMapping": "DELETE",
|
||||
}[annotation]
|
||||
|
||||
# 在该注解之后找最近的方法签名
|
||||
rest = content[m.end():]
|
||||
sig = METHOD_SIG_RE.search(rest)
|
||||
java_method = sig.group(1) if sig else "?"
|
||||
|
||||
# 拼接完整路径
|
||||
full = class_path.rstrip("/") + "/" + method_path.lstrip("/") if method_path else class_path
|
||||
# 去掉多余斜杠
|
||||
full = re.sub(r"/+", "/", full)
|
||||
# 处理通配符路径(如 /api/upload/image/**)——保留通配符用于匹配
|
||||
endpoints.append((http_method, full, java_method, class_name))
|
||||
|
||||
return endpoints
|
||||
|
||||
|
||||
def extract_entity_classes(java_path):
|
||||
"""从实体文件提取类名"""
|
||||
with open(java_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
m = re.search(r'@Entity\b', content)
|
||||
if not m:
|
||||
return None
|
||||
cm = CLASS_NAME_RE.search(content)
|
||||
return cm.group(1) if cm else None
|
||||
|
||||
|
||||
def load_ontology(repo_root):
|
||||
"""加载 ontology.jsonl,返回 actions 与 entities 的端点/类名集合"""
|
||||
jsonl_path = os.path.join(repo_root, "docs", "graph", "ontology.jsonl")
|
||||
if not os.path.exists(jsonl_path):
|
||||
print(f"ERROR: {jsonl_path} not found")
|
||||
sys.exit(2)
|
||||
|
||||
action_endpoints = set() # (HTTP_METHOD, path_pattern)
|
||||
action_endpoints_by_id = defaultdict(list)
|
||||
entity_class_names = set() # PascalCase 类名
|
||||
entity_ids = {} # class_name -> entity_id
|
||||
non_jpa_entity_class_names = set() # 本体里标 jpa:false 的(不要求代码有 @Entity 类)
|
||||
|
||||
with open(jsonl_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if obj.get("type") == "Action":
|
||||
entrypoints = obj.get("entrypoints", [])
|
||||
eid = obj.get("id", "?")
|
||||
for ep in entrypoints:
|
||||
if isinstance(ep, str) and (" " in ep or ep.startswith(("GET", "POST", "PUT", "DELETE"))):
|
||||
# 格式 "POST /api/report/create" 或 "ReportController#create"
|
||||
parts = ep.split(" ", 1)
|
||||
if len(parts) == 2 and parts[0] in ("GET", "POST", "PUT", "DELETE"):
|
||||
action_endpoints.add((parts[0], parts[1]))
|
||||
action_endpoints_by_id[eid].append((parts[0], parts[1]))
|
||||
elif obj.get("type") == "Entity":
|
||||
eid = obj.get("id", "")
|
||||
# 从 entity:store 推 PascalCase Store
|
||||
if eid.startswith("entity:"):
|
||||
class_name = "".join(p.capitalize() for p in eid[7:].split("_"))
|
||||
# jpa:false 表示本体内建模为领域实体但代码里不是 JPA @Entity(如字段承载或 record)
|
||||
if obj.get("jpa") is False:
|
||||
non_jpa_entity_class_names.add(class_name)
|
||||
else:
|
||||
entity_class_names.add(class_name)
|
||||
entity_ids[class_name] = eid
|
||||
|
||||
return action_endpoints, action_endpoints_by_id, entity_class_names, entity_ids, non_jpa_entity_class_names
|
||||
|
||||
|
||||
def path_matches_ontology_pattern(code_path, ontology_path):
|
||||
"""代码路径是否匹配本体里的路径模式(支持 ** 通配符)"""
|
||||
if code_path == ontology_path:
|
||||
return True
|
||||
# 处理 ** 通配符
|
||||
if "**" in ontology_path:
|
||||
# 转换为正则:** → .*, * → [^/]*
|
||||
pattern = re.escape(ontology_path).replace(r"\*\*", ".*").replace(r"\*", r"[^/]*")
|
||||
return re.fullmatch(pattern, code_path) is not None
|
||||
# 处理单 * 通配符(如 /api/report/*/suggestion)
|
||||
if "*" in ontology_path:
|
||||
pattern = re.escape(ontology_path).replace(r"\*", r"[^/]*")
|
||||
return re.fullmatch(pattern, code_path) is not None
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
repo_root = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
repo_root = os.path.abspath(repo_root)
|
||||
|
||||
action_endpoints, action_endpoints_by_id, entity_class_names, entity_ids, non_jpa_entity_class_names = load_ontology(repo_root)
|
||||
|
||||
# === 1. Controller 端点漂移 ===
|
||||
controller_files = find_controller_files(repo_root)
|
||||
code_endpoints = [] # (http_method, path, java_method, class_name, file)
|
||||
for cf in controller_files:
|
||||
for http_method, path, java_method, class_name in extract_endpoints(cf):
|
||||
code_endpoints.append((http_method, path, java_method, class_name, cf))
|
||||
|
||||
# 对比
|
||||
code_only = [] # 代码里有,本体没有
|
||||
for http_method, path, java_method, class_name, cf in code_endpoints:
|
||||
found = False
|
||||
for ont_method, ont_path in action_endpoints:
|
||||
if ont_method == http_method and path_matches_ontology_pattern(path, ont_path):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
code_only.append((http_method, path, java_method, class_name, cf))
|
||||
|
||||
ontology_only = [] # 本体里有,代码没有
|
||||
for ont_method, ont_path in action_endpoints:
|
||||
found = False
|
||||
for http_method, path, java_method, class_name, cf in code_endpoints:
|
||||
if http_method == ont_method and path_matches_ontology_pattern(path, ont_path):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
ontology_only.append((ont_method, ont_path))
|
||||
|
||||
# === 2. Entity 类覆盖 ===
|
||||
entity_files = find_entity_files(repo_root)
|
||||
code_entity_classes = set()
|
||||
for ef in entity_files:
|
||||
cn = extract_entity_classes(ef)
|
||||
if cn:
|
||||
code_entity_classes.add(cn)
|
||||
|
||||
entities_missing_in_ontology = code_entity_classes - entity_class_names
|
||||
entities_missing_in_code = entity_class_names - code_entity_classes
|
||||
|
||||
# non-jpa 本体实体(如 HighlightVideo 字段承载、SessionToken record)不算 extra drift
|
||||
# 它们在本体里是领域实体,但代码里不是 @Entity 类,单独列出作为信息项
|
||||
non_jpa_in_ontology = non_jpa_entity_class_names
|
||||
|
||||
# === 输出报告 ===
|
||||
print("=" * 60)
|
||||
print("宠小它 本体—代码漂移审计")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\n扫描 controller 文件: {len(controller_files)}")
|
||||
print(f"代码 HTTP 端点数: {len(code_endpoints)}")
|
||||
print(f"本体 Action 端点数: {len(action_endpoints)}")
|
||||
print(f"扫描 entity 文件: {len(entity_files)}")
|
||||
print(f"代码 Entity 类数: {len(code_entity_classes)}")
|
||||
print(f"本体 Entity 数(JPA): {len(entity_class_names)}")
|
||||
print(f"本体 Entity 数(非 JPA/领域实体): {len(non_jpa_in_ontology)}")
|
||||
|
||||
print("\n--- Controller 端点漂移 ---")
|
||||
if code_only:
|
||||
print(f"\n⚠️ code-only(代码里有,本体未录入): {len(code_only)} 个")
|
||||
for http_method, path, java_method, class_name, cf in sorted(code_only):
|
||||
rel = os.path.relpath(cf, repo_root)
|
||||
print(f" {http_method:6s} {path:40s} {class_name}#{java_method} [{rel}]")
|
||||
else:
|
||||
print("\n✅ 无 code-only 漂移(所有代码端点都已录入本体)")
|
||||
|
||||
if ontology_only:
|
||||
print(f"\n⚠️ ontology-only(本体里有,代码找不到): {len(ontology_only)} 个")
|
||||
for ont_method, ont_path in sorted(ontology_only):
|
||||
print(f" {ont_method:6s} {ont_path}")
|
||||
else:
|
||||
print("\n✅ 无 ontology-only 漂移(所有本体端点都有对应代码)")
|
||||
|
||||
print("\n--- Entity 类覆盖 ---")
|
||||
if entities_missing_in_ontology:
|
||||
print(f"\n⚠️ 代码里有 Entity 类但本体未录入: {len(entities_missing_in_ontology)} 个")
|
||||
for cn in sorted(entities_missing_in_ontology):
|
||||
print(f" {cn}")
|
||||
else:
|
||||
print("\n✅ 所有代码 Entity 类都已录入本体")
|
||||
|
||||
if entities_missing_in_code:
|
||||
print(f"\n⚠️ 本体里有 Entity 但代码找不到: {len(entities_missing_in_code)} 个")
|
||||
for cn in sorted(entities_missing_in_code):
|
||||
print(f" {cn} (本体 ID: {entity_ids.get(cn, '?')})")
|
||||
else:
|
||||
print("✅ 所有本体 Entity 都有对应代码类")
|
||||
|
||||
if non_jpa_in_ontology:
|
||||
print(f"\nℹ️ 本体非 JPA 领域实体(代码里不是 @Entity 类,正常): {len(non_jpa_in_ontology)} 个")
|
||||
for cn in sorted(non_jpa_in_ontology):
|
||||
print(f" {cn}")
|
||||
|
||||
# === 退出码 ===
|
||||
has_drift = bool(code_only or ontology_only or entities_missing_in_ontology or entities_missing_in_code)
|
||||
if has_drift:
|
||||
print("\n" + "=" * 60)
|
||||
print("漂移汇总: code-only={} ontology-only={} entity-missing={} entity-extra={}".format(
|
||||
len(code_only), len(ontology_only), len(entities_missing_in_ontology), len(entities_missing_in_code)
|
||||
))
|
||||
print("=" * 60)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 无漂移:本体与代码完全对齐")
|
||||
print("=" * 60)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
222
graph/ontology.jsonl
Normal file
@ -0,0 +1,222 @@
|
||||
{"id":"entity:store","type":"Entity","name":"Store","nameZh":"门店","domain":"store","fields":["id","name","logo","phone","address","latitude","longitude","intro","owner_id","invite_code","booking_day_start","booking_last_slot_start","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/Store.java","doc":"docs/产品设计文档.md §三 t_store","test":"StoreControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:user","type":"Entity","name":"User","nameZh":"用户","domain":"user","fields":["id","username","password","name","phone","avatar","store_id","role","wechat_openid","wechat_unionid","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/User.java","doc":"docs/产品设计文档.md §三 t_user; docs/.agents/common-context.md 术语规则","test":"UserServiceLoginStrategyTest; UserJsonIgnoreTest","evidence":"anchored"}
|
||||
{"id":"entity:appointment","type":"Entity","name":"Appointment","nameZh":"预约","domain":"appointment","fields":["id","pet_name","pet_type","service_type","appointment_time","status","store_id","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","evidence":"anchored"}
|
||||
{"id":"entity:report","type":"Entity","name":"Report","nameZh":"服务报告","domain":"report","fields":["id","appointment_id","remark","user_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":"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: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:get_store_by_invite_code","type":"Action","name":"getStoreByInviteCode","nameZh":"按邀请码查门店","inputs":["code"],"outputs":["entity:store"],"entrypoints":["GET /api/store/invite-code","StoreController#getByInviteCode"],"appliesRules":["rule:BR-STORE-002"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#getByInviteCode","test":"StoreControllerTest","evidence":"anchored","domain":"store"}
|
||||
{"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: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"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/create","AppointmentController#create","AppointmentService#createBooking"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-004"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#create","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: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:get_appointment_detail","type":"Action","name":"getAppointmentDetail","nameZh":"预约详情","inputs":["id"],"outputs":["entity:appointment"],"entrypoints":["GET /api/appointment/detail","AppointmentController#detail"],"appliesRules":["rule:BR-APPT-002","rule:BR-APPT-003"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#detail","test":"AppointmentControllerDetailTest","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:list_appointments","type":"Action","name":"listAppointments","nameZh":"预约列表","inputs":["status","page","pageSize"],"outputs":["entity:appointment"],"entrypoints":["GET /api/appointment/list","AppointmentController#list"],"appliesRules":["rule:BR-APPT-002","rule:BR-APPT-003"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#list","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:submit_report","type":"Action","name":"submitReport","nameZh":"提交报告","inputs":["entity:report"],"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"}
|
||||
{"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: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"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder","evidence":"anchored","test":"ReportLeadControllerTest","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: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: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:appointment_created","type":"Event","name":"AppointmentCreated","nameZh":"预约创建","domain":"appointment","payload":["appointmentId","storeId","userId","appointmentTime"],"emittedBy":["action:create_appointment"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking","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: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: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: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: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":"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-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-002","type":"Rule","name":"邀请码查询仅 boss/staff + 同店","domain":"store","appliesTo":["action:get_store_by_invite_code"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#getByInviteCode","test":"StoreControllerTest#inviteCodeCrossStoreForbidden","evidence":"anchored"}
|
||||
{"id":"rule:BR-APPT-001","type":"Rule","name":"预约状态机","domain":"appointment","appliesTo":["action:start_service","action:transition_appointment_status","action:submit_report"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#startService; #transitionStatus","test":"AppointmentServiceTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-APPT-002","type":"Rule","name":"预约跨店 403","domain":"appointment","appliesTo":["action:list_appointments","action:start_service","action:transition_appointment_status","action:get_appointment_detail"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java","test":"AppointmentControllerDetailTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-APPT-003","type":"Rule","name":"预约跨用户 403","domain":"appointment","appliesTo":["action:get_appointment_detail","action:transition_appointment_status"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#detail; #updateStatus","test":"AppointmentControllerDetailTest#customerCannotViewOthersAppointment","evidence":"anchored"}
|
||||
{"id":"rule:BR-APPT-004","type":"Rule","name":"预约时段校验:半小时档 + 营业时间 + 占用 + 已过","domain":"appointment","appliesTo":["action:create_appointment"],"code":"backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking","doc":"docs/产品设计文档.md §5.2","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-001","type":"Rule","name":"报告必须绑定预约","domain":"report","appliesTo":["action:submit_report"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create; service/ReportService.java#create","test":"ReportControllerTest#createMissingAppointmentIdReturnsBizCode; ReportServiceTest#createWithNullAppointmentIdThrows","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-002","type":"Rule","name":"一约一份报告","domain":"report","appliesTo":["action:submit_report"],"code":"backend/src/main/java/com/petstore/entity/Report.java; mapper/ReportMapper.java; service/ReportService.java#create; controller/ReportController.java#create","test":"ReportServiceTest#createDuplicateReportThrows; ReportControllerTest#createDuplicateReportReturnsAlreadyExists","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-003","type":"Rule","name":"报告前后照片必填","domain":"report","appliesTo":["action:submit_report"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#hasPhotoType; frontend/src/pages/report/Report.vue#submitReport","test":"ReportControllerTest#createMissingBeforePhotoReturnsBizCode; createMissingAfterPhotoReturnsBizCode","doc":"docs/产品设计文档.md §5.3 报告提交必填项","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-004","type":"Rule","name":"报告仅 doing 预约可提交","domain":"report","appliesTo":["action:submit_report"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create; service/ReportService.java#create","test":"ReportServiceTest#createDoingAppointmentMarksDone","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-005","type":"Rule","name":"报告跨店 403","domain":"report","appliesTo":["action:submit_report","action:delete_report"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#create; #delete","test":"ReportControllerTest#createCrossStoreReturnsForbidden; createNonStaffReturnsForbidden","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-006","type":"Rule","name":"公开报告字段裁剪","domain":"report","appliesTo":["action:get_report_by_token"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#getByAppointmentId","test":"ReportControllerTest#publicTokenQueryHidesInternalFields","frontend":"frontend/src/pages/report-view/reportView.vue","evidence":"anchored"}
|
||||
{"id":"rule:BR-RPT-007","type":"Rule","name":"token hash 日志","domain":"report","appliesTo":["action:track_report_open"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#shortTokenHash; #trackReportOpen","evidence":"anchored"}
|
||||
{"id":"rule:BR-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-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":"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:user_belongs_to_store","type":"Relation","subject":"entity:user","predicate":"BELONGS_TO","object":"entity:store","cardinality":"N—1","evidence":"anchored","doc":"boss/staff 归属一个门店;customer 可无门店","domain":"user"}
|
||||
{"id":"rel:store_has_appointments","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.store_id","domain":"store"}
|
||||
{"id":"rel:user_creates_appointments","type":"Relation","subject":"entity:user","predicate":"CREATES","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.user_id","domain":"user"}
|
||||
{"id":"rel:user_serves_appointments","type":"Relation","subject":"entity:user","predicate":"SERVES","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.assigned_user_id(谁点谁服务)","domain":"user"}
|
||||
{"id":"rel:appointment_has_report","type":"Relation","subject":"entity:appointment","predicate":"HAS","object":"entity:report","cardinality":"1—1","evidence":"anchored","doc":"Report.appointment_id(uk_report_appointment 唯一约束)","domain":"appointment"}
|
||||
{"id":"rel:report_belongs_to_store","type":"Relation","subject":"entity:report","predicate":"BELONGS_TO","object":"entity:store","cardinality":"N—1","evidence":"anchored","doc":"Report.store_id","domain":"report"}
|
||||
{"id":"rel:report_served_by_user","type":"Relation","subject":"entity:report","predicate":"SERVED_BY","object":"entity:user","cardinality":"N—1","evidence":"anchored","doc":"Report.user_id(技师)","domain":"report"}
|
||||
{"id":"rel:report_has_leads","type":"Relation","subject":"entity:report","predicate":"HAS","object":"entity:report_lead","cardinality":"1—N","evidence":"anchored","doc":"ReportLead.report_id(同手机号去重)","domain":"report"}
|
||||
{"id":"rel:lead_belongs_to_store","type":"Relation","subject":"entity:report_lead","predicate":"BELONGS_TO","object":"entity:store","cardinality":"N—1","evidence":"anchored","doc":"ReportLead.store_id(回访池范围)","domain":"lead"}
|
||||
{"id":"rel:register_boss_emits_store_registered","type":"Relation","subject":"action:register_boss","predicate":"EMITS","object":"event:store_registered","evidence":"anchored","domain":"user"}
|
||||
{"id":"rel:register_boss_emits_session_issued","type":"Relation","subject":"action:register_boss","predicate":"EMITS","object":"event:session_issued","evidence":"anchored","domain":"user"}
|
||||
{"id":"rel:login_emits_session_issued","type":"Relation","subject":"action:login","predicate":"EMITS","object":"event:session_issued","evidence":"anchored","domain":"auth"}
|
||||
{"id":"rel:wx_phone_login_emits_session_issued","type":"Relation","subject":"action:wx_phone_login","predicate":"EMITS","object":"event:session_issued","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:start_service_emits_status_changed","type":"Relation","subject":"action:start_service","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_status_changed","type":"Relation","subject":"action:submit_report","predicate":"EMITS","object":"event:appointment_status_changed","evidence":"anchored","doc":"doing→done","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:submit_lead_emits_lead_submitted","type":"Relation","subject":"action:submit_lead","predicate":"EMITS","object":"event:lead_submitted","evidence":"documented","domain":"lead"}
|
||||
{"id":"entity:pet","type":"Entity","name":"Pet","nameZh":"宠物档案","domain":"pet","fields":["id","name","pet_type","breed","avatar","owner_user_id","remark","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/Pet.java; controller/PetController.java; service/PetService.java","doc":"docs/产品设计文档.md §三 t_pet","test":"PetControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:service_type","type":"Entity","name":"ServiceType","nameZh":"服务类型","domain":"service_type","fields":["id","store_id","name","create_time"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/ServiceType.java; controller/ServiceTypeController.java","doc":"docs/产品设计文档.md §三 t_service_type §5.2","test":"ServiceTypeControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:schedule_block","type":"Entity","name":"ScheduleBlock","nameZh":"门店排班占用","domain":"schedule","fields":["id","store_id","slot_start","block_type","note","created_by_user_id","create_time","update_time","deleted"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/entity/ScheduleBlock.java; controller/ScheduleController.java; service/ScheduleService.java","doc":"docs/产品设计文档.md §5.2 预约时段占用","test":"ScheduleControllerTest","evidence":"anchored"}
|
||||
{"id":"entity:report_image","type":"Entity","name":"ReportImage","nameZh":"报告媒体","domain":"report","fields":["id","report_id","photo_url","photo_type","media_type","sort_order","create_time"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/ReportImage.java; mapper/ReportImageMapper.java","doc":"docs/产品设计文档.md §三 t_report_image","test":"ReportServiceTest#createWithImagesSavesEachImage","evidence":"anchored"}
|
||||
{"id":"entity:highlight_video","type":"Entity","name":"HighlightVideo","nameZh":"服务回顾短片","domain":"report","fields":["report_id","highlight_video_url","highlight_video_status","highlight_video_error","highlight_duration_sec","highlight_compose_mode","highlight_fail_reason","highlight_share_cover_url"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/Report.java; service/ReportHighlightVideoService.java","doc":"docs/API-报告成片字段契约.md; docs/洗美报告短视频成片方案.md","test":"ReportHighlightVideoServiceTest","evidence":"anchored","jpa":false}
|
||||
{"id":"entity:session_token","type":"Entity","name":"SessionToken","nameZh":"会话令牌","domain":"auth","fields":["userId","storeId","role","exp"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/auth/SessionTokenService.java; CurrentUser.java; CurrentUserContext.java; AuthInterceptor.java","doc":"docs/ontology/rules.md#rule:BR-AUTH-001","test":"SessionTokenServiceTest","evidence":"anchored","jpa":false}
|
||||
{"id":"action:list_pets","type":"Action","name":"listPets","nameZh":"宠物列表","inputs":[],"outputs":["entity:pet"],"entrypoints":["GET /api/pet/list","PetController#list"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#list","evidence":"anchored","test":"PetControllerTest","domain":"pet"}
|
||||
{"id":"action:create_pet","type":"Action","name":"createPet","nameZh":"创建宠物","inputs":["entity:pet"],"outputs":["entity:pet"],"entrypoints":["POST /api/pet/create","PetController#create","PetService#create"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#create","evidence":"anchored","test":"PetControllerTest","domain":"pet"}
|
||||
{"id":"action:update_pet","type":"Action","name":"updatePet","nameZh":"更新宠物","inputs":["id","name","petType","breed","avatar","remark"],"outputs":["entity:pet"],"entrypoints":["PUT /api/pet/update","PetController#update","PetService#update"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#update","evidence":"anchored","test":"PetControllerTest","domain":"pet"}
|
||||
{"id":"action:delete_pet","type":"Action","name":"deletePet","nameZh":"删除宠物","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/pet/delete","PetController#delete","PetService#delete"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#delete","evidence":"anchored","test":"PetControllerTest","domain":"pet"}
|
||||
{"id":"action:get_pet_history","type":"Action","name":"getPetHistory","nameZh":"宠物服务时间线","inputs":["petId"],"outputs":[],"entrypoints":["GET /api/pet/history","PetController#history","PetService#getServiceHistory"],"appliesRules":["rule:BR-PET-001"],"code":"backend/src/main/java/com/petstore/controller/PetController.java#history","evidence":"anchored","test":"PetControllerTest","domain":"pet"}
|
||||
{"id":"action:list_service_types","type":"Action","name":"listServiceTypes","nameZh":"服务类型列表","inputs":["storeId"],"outputs":["entity:service_type"],"entrypoints":["GET /api/service-type/list","ServiceTypeController#list"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java#list","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"action:create_service_type","type":"Action","name":"createServiceType","nameZh":"新增服务类型","inputs":["name"],"outputs":["entity:service_type"],"entrypoints":["POST /api/service-type/create","ServiceTypeController#create","ServiceTypeService#create"],"appliesRules":["rule:BR-ST-001"],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java#create","test":"ServiceTypeControllerTest","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"action:update_service_type","type":"Action","name":"updateServiceType","nameZh":"编辑服务类型","inputs":["id","name"],"outputs":["entity:service_type"],"entrypoints":["PUT /api/service-type/update","ServiceTypeController#update"],"appliesRules":["rule:BR-ST-001"],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java#update","test":"ServiceTypeControllerTest","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"action:delete_service_type","type":"Action","name":"deleteServiceType","nameZh":"删除服务类型","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/service-type/delete","ServiceTypeController#delete"],"appliesRules":["rule:BR-ST-001"],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java#delete","test":"ServiceTypeControllerTest","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"action:init_service_types","type":"Action","name":"initServiceTypes","nameZh":"初始化系统默认服务类型","inputs":[],"outputs":[],"entrypoints":["POST /api/service-type/init","ServiceTypeController#init"],"appliesRules":["rule:BR-ST-001"],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java#init","test":"ServiceTypeControllerTest","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"action:day_agenda","type":"Action","name":"dayAgenda","nameZh":"门店某日日程","inputs":["date"],"outputs":["entity:appointment","entity:schedule_block"],"entrypoints":["GET /api/schedule/day","ScheduleController#day","ScheduleService#dayAgenda"],"appliesRules":["rule:BR-SCH-001"],"code":"backend/src/main/java/com/petstore/controller/ScheduleController.java#day","test":"ScheduleControllerTest","evidence":"anchored","domain":"schedule"}
|
||||
{"id":"action:create_schedule_block","type":"Action","name":"createScheduleBlock","nameZh":"手动占用时段","inputs":["slotStart","blockType","note"],"outputs":["entity:schedule_block"],"entrypoints":["POST /api/schedule/block","ScheduleController#createBlock","ScheduleService#createBlock"],"appliesRules":["rule:BR-SCH-001"],"code":"backend/src/main/java/com/petstore/controller/ScheduleController.java#createBlock","test":"ScheduleControllerTest","evidence":"anchored","domain":"schedule"}
|
||||
{"id":"action:delete_schedule_block","type":"Action","name":"deleteScheduleBlock","nameZh":"取消手动占用","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/schedule/block","ScheduleController#deleteBlock","ScheduleService#deleteBlock"],"appliesRules":["rule:BR-SCH-001"],"code":"backend/src/main/java/com/petstore/controller/ScheduleController.java#deleteBlock; service/ScheduleService.java#deleteBlock","test":"ScheduleControllerTest","evidence":"anchored","domain":"schedule"}
|
||||
{"id":"action:upload_image","type":"Action","name":"uploadImage","nameZh":"上传图片/视频","inputs":["file"],"outputs":["url"],"entrypoints":["POST /api/upload/image","FileController#uploadImage"],"appliesRules":["rule:BR-UPLOAD-001","rule:BR-UPLOAD-002"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#uploadImage","evidence":"anchored","domain":"upload"}
|
||||
{"id":"action:get_upload_image","type":"Action","name":"getUploadImage","nameZh":"读取上传资源","inputs":["path"],"outputs":[],"entrypoints":["GET /api/upload/image/**","FileController#getImage"],"appliesRules":["rule:BR-UPLOAD-001"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#getImage","evidence":"anchored","domain":"upload"}
|
||||
{"id":"action:generate_highlight","type":"Action","name":"generateHighlight","nameZh":"触发成片生成","inputs":["reportId","composeMode"],"outputs":["entity:highlight_video"],"entrypoints":["POST /api/report/highlight/start","ReportController#startHighlight","ReportHighlightVideoService#requestGenerate"],"appliesRules":["rule:BR-HL-001","rule:BR-HL-002"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#startHighlight; service/ReportHighlightVideoService.java#requestGenerate","doc":"docs/洗美报告短视频成片方案.md","evidence":"anchored","domain":"report","test":"ReportHighlightVideoServiceTest"}
|
||||
{"id":"action:create_staff","type":"Action","name":"createStaff","nameZh":"老板创建员工","inputs":["name","phone"],"outputs":["entity:user"],"entrypoints":["POST /api/user/create-staff","UserController#createStaff","UserService#createStaff"],"appliesRules":["rule:BR-USER-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#createStaff","evidence":"anchored","domain":"user"}
|
||||
{"id":"action:list_staff","type":"Action","name":"listStaff","nameZh":"员工列表","inputs":[],"outputs":["entity:user"],"entrypoints":["GET /api/user/staff-list","UserController#staffList"],"appliesRules":["rule:BR-USER-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#staffList","evidence":"anchored","domain":"user"}
|
||||
{"id":"action:delete_staff","type":"Action","name":"deleteStaff","nameZh":"删除员工","inputs":["staffId"],"outputs":[],"entrypoints":["DELETE /api/user/staff","UserController#deleteStaff"],"appliesRules":["rule:BR-USER-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#deleteStaff","evidence":"anchored","domain":"user"}
|
||||
{"id":"action:update_user","type":"Action","name":"updateUser","nameZh":"更新当前用户信息","inputs":["name","phone","avatar","code"],"outputs":["entity:user"],"entrypoints":["PUT /api/user/update","UserController#updateUser","UserService#updateUser"],"appliesRules":["rule:BR-USER-002","rule:BR-AUTH-002"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser","evidence":"anchored","domain":"user"}
|
||||
{"id":"action:get_user_info","type":"Action","name":"getUserInfo","nameZh":"获取当前用户信息","inputs":[],"outputs":["entity:user"],"entrypoints":["GET /api/user/info","UserController#info"],"appliesRules":["rule:BR-USER-002","rule:BR-USER-003"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#info","evidence":"anchored","domain":"user"}
|
||||
{"id":"event:highlight_succeeded","type":"Event","name":"HighlightSucceeded","nameZh":"成片生成成功","domain":"report","payload":["reportTokenHash","appointmentId","durationSec","composeMode","ts"],"emittedBy":["action:generate_highlight"],"appliesRules":["rule:BR-HL-002"],"code":"backend/src/main/java/com/petstore/service/ReportHighlightVideoService.java","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"}
|
||||
{"id":"event:highlight_failed","type":"Event","name":"HighlightFailed","nameZh":"成片生成失败","domain":"report","payload":["reportTokenHash","appointmentId","failReason","ts"],"emittedBy":["action:generate_highlight"],"appliesRules":["rule:BR-HL-002"],"code":"backend/src/main/java/com/petstore/service/ReportHighlightVideoService.java","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"}
|
||||
{"id":"event:video_play","type":"Event","name":"VideoPlay","nameZh":"成片点击播放","domain":"report","payload":["reportTokenHash","ts","source"],"emittedBy":["action:track_video_play"],"doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"}
|
||||
{"id":"event:video_save","type":"Event","name":"VideoSave","nameZh":"成片保存到相册","domain":"report","payload":["reportTokenHash","ts"],"emittedBy":["action:track_video_save"],"doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented"}
|
||||
{"id":"event:upload_completed","type":"Event","name":"UploadCompleted","nameZh":"上传完成","domain":"upload","payload":["url","mediaType","size","ts"],"emittedBy":["action:upload_image"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#uploadImage","evidence":"implemented"}
|
||||
{"id":"event:schedule_block_created","type":"Event","name":"ScheduleBlockCreated","nameZh":"排班占用创建","domain":"schedule","payload":["blockId","storeId","slotStart","blockType","createdByUserId"],"emittedBy":["action:create_schedule_block"],"code":"backend/src/main/java/com/petstore/service/ScheduleService.java#createBlock","evidence":"anchored"}
|
||||
{"id":"event:schedule_block_deleted","type":"Event","name":"ScheduleBlockDeleted","nameZh":"排班占用取消","domain":"schedule","payload":["blockId","storeId"],"emittedBy":["action:delete_schedule_block"],"code":"backend/src/main/java/com/petstore/service/ScheduleService.java#deleteBlock","evidence":"anchored"}
|
||||
{"id":"event:service_type_created","type":"Event","name":"ServiceTypeCreated","nameZh":"服务类型创建","domain":"service_type","payload":["serviceTypeId","storeId","name"],"emittedBy":["action:create_service_type"],"code":"backend/src/main/java/com/petstore/service/ServiceTypeService.java#create","evidence":"anchored"}
|
||||
{"id":"event:service_type_deleted","type":"Event","name":"ServiceTypeDeleted","nameZh":"服务类型删除","domain":"service_type","payload":["serviceTypeId","storeId"],"emittedBy":["action:delete_service_type"],"code":"backend/src/main/java/com/petstore/service/ServiceTypeService.java#delete","evidence":"anchored"}
|
||||
{"id":"event:pet_created","type":"Event","name":"PetCreated","nameZh":"宠物档案创建","domain":"pet","payload":["petId","ownerUserId","name","petType"],"emittedBy":["action:create_pet"],"code":"backend/src/main/java/com/petstore/service/PetService.java#create","evidence":"anchored"}
|
||||
{"id":"event:staff_created","type":"Event","name":"StaffCreated","nameZh":"员工创建","domain":"user","payload":["staffId","storeId","name","phone"],"emittedBy":["action:create_staff"],"code":"backend/src/main/java/com/petstore/service/UserService.java#createStaff","evidence":"anchored"}
|
||||
{"id":"event:user_updated","type":"Event","name":"UserUpdated","nameZh":"用户信息更新","domain":"user","payload":["userId","updatedFields"],"emittedBy":["action:update_user"],"code":"backend/src/main/java/com/petstore/service/UserService.java#updateUser","evidence":"anchored"}
|
||||
{"id":"rule:BR-UPLOAD-001","type":"Rule","name":"上传路径归一化","domain":"upload","appliesTo":["action:upload_image","action:get_upload_image"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#resolveSafePath; #uploadRoot","evidence":"anchored"}
|
||||
{"id":"rule:BR-UPLOAD-002","type":"Rule","name":"上传扩展名/MIME 校验","domain":"upload","appliesTo":["action:upload_image"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#isAllowedMediaType; #hasMediaExtension","evidence":"anchored"}
|
||||
{"id":"rule:BR-HL-001","type":"Rule","name":"成片仅 boss/staff 可触发","domain":"report","appliesTo":["action:generate_highlight"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#startHighlight","evidence":"anchored","test":"ReportHighlightVideoServiceTest#requestGenerateRejectsCustomerWithoutAppointmentOwnershipPath"}
|
||||
{"id":"rule:BR-HL-002","type":"Rule","name":"成片三态 + composeMode","domain":"report","appliesTo":["action:generate_highlight"],"code":"backend/src/main/java/com/petstore/entity/Report.java; service/ReportHighlightVideoService.java","doc":"docs/洗美报告短视频成片方案.md; docs/API-报告成片字段契约.md","evidence":"anchored","test":"ReportHighlightVideoServiceTest"}
|
||||
{"id":"rule:BR-SCH-001","type":"Rule","name":"排班仅 boss/staff + 跨店 403","domain":"schedule","appliesTo":["action:day_agenda","action:create_schedule_block","action:delete_schedule_block"],"code":"backend/src/main/java/com/petstore/controller/ScheduleController.java; service/ScheduleService.java#deleteBlock","test":"ScheduleControllerTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-ST-001","type":"Rule","name":"服务类型仅 boss + 同店 + 系统默认不可改删","domain":"service_type","appliesTo":["action:create_service_type","action:update_service_type","action:delete_service_type","action:init_service_types"],"code":"backend/src/main/java/com/petstore/controller/ServiceTypeController.java","test":"ServiceTypeControllerTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-PET-001","type":"Rule","name":"宠物跨用户/跨店","domain":"pet","appliesTo":["action:list_pets","action:create_pet","action:update_pet","action:delete_pet","action:get_pet_history"],"code":"backend/src/main/java/com/petstore/controller/PetController.java","evidence":"anchored","test":"PetControllerTest"}
|
||||
{"id":"rule:BR-USER-001","type":"Rule","name":"员工管理仅 boss + 同店","domain":"user","appliesTo":["action:create_staff","action:list_staff","action:delete_staff"],"code":"backend/src/main/java/com/petstore/controller/UserController.java","evidence":"anchored"}
|
||||
{"id":"rule:BR-USER-002","type":"Rule","name":"用户更新仅本人","domain":"user","appliesTo":["action:update_user","action:get_user_info"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#updateUser; #info","evidence":"anchored"}
|
||||
{"id":"rule:BR-CONFIG-001","type":"Rule","name":"生产配置环境变量","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml; application-example.yml","doc":"backend/README.md 环境变量表","evidence":"anchored"}
|
||||
{"id":"rule:BR-CONFIG-002","type":"Rule","name":"production profile:ddl=validate + show-sql=false + SMS 禁用","domain":"config","appliesTo":[],"code":"backend/src/main/resources/application.yml production profile","doc":"backend/README.md production profile","evidence":"anchored"}
|
||||
{"id":"rel:user_owns_pets","type":"Relation","subject":"entity:user","predicate":"OWNS","object":"entity:pet","cardinality":"1—N","evidence":"anchored","doc":"Pet.owner_user_id → User.id","domain":"user"}
|
||||
{"id":"rel:pet_has_appointments","type":"Relation","subject":"entity:pet","predicate":"HAS","object":"entity:appointment","cardinality":"1—N","evidence":"anchored","doc":"Appointment.pet_id(可空)","domain":"pet"}
|
||||
{"id":"rel:store_has_service_types","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:service_type","cardinality":"1—N","evidence":"anchored","doc":"ServiceType.store_id(NULL=系统默认)","domain":"store"}
|
||||
{"id":"rel:store_has_schedule_blocks","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:schedule_block","cardinality":"1—N","evidence":"anchored","doc":"ScheduleBlock.store_id","domain":"store"}
|
||||
{"id":"rel:user_creates_schedule_blocks","type":"Relation","subject":"entity:user","predicate":"CREATES","object":"entity:schedule_block","cardinality":"1—N","evidence":"anchored","doc":"ScheduleBlock.created_by_user_id","domain":"user"}
|
||||
{"id":"rel:report_has_highlight_video","type":"Relation","subject":"entity:report","predicate":"HAS","object":"entity:highlight_video","cardinality":"1—1","evidence":"anchored","doc":"Report.highlight_* 字段承载","domain":"report"}
|
||||
{"id":"rel:user_has_session_tokens","type":"Relation","subject":"entity:user","predicate":"HAS","object":"entity:session_token","cardinality":"1—N","evidence":"anchored","doc":"token payload userId","domain":"user"}
|
||||
{"id":"rel:report_has_images","type":"Relation","subject":"entity:report","predicate":"HAS","object":"entity:report_image","cardinality":"1—N","evidence":"anchored","doc":"ReportImage.report_id(before/after/during)","domain":"report"}
|
||||
{"id":"rel:generate_highlight_emits_succeeded","type":"Relation","subject":"action:generate_highlight","predicate":"EMITS","object":"event:highlight_succeeded","evidence":"documented","domain":"report"}
|
||||
{"id":"rel:generate_highlight_emits_failed","type":"Relation","subject":"action:generate_highlight","predicate":"EMITS","object":"event:highlight_failed","evidence":"documented","domain":"report"}
|
||||
{"id":"rel:upload_image_emits_upload_completed","type":"Relation","subject":"action:upload_image","predicate":"EMITS","object":"event:upload_completed","evidence":"implemented","domain":"upload"}
|
||||
{"id":"rel:create_schedule_block_emits_block_created","type":"Relation","subject":"action:create_schedule_block","predicate":"EMITS","object":"event:schedule_block_created","evidence":"anchored","domain":"schedule"}
|
||||
{"id":"rel:delete_schedule_block_emits_block_deleted","type":"Relation","subject":"action:delete_schedule_block","predicate":"EMITS","object":"event:schedule_block_deleted","evidence":"anchored","domain":"schedule"}
|
||||
{"id":"rel:create_service_type_emits_type_created","type":"Relation","subject":"action:create_service_type","predicate":"EMITS","object":"event:service_type_created","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"rel:delete_service_type_emits_type_deleted","type":"Relation","subject":"action:delete_service_type","predicate":"EMITS","object":"event:service_type_deleted","evidence":"anchored","domain":"service_type"}
|
||||
{"id":"rel:create_pet_emits_pet_created","type":"Relation","subject":"action:create_pet","predicate":"EMITS","object":"event:pet_created","evidence":"anchored","domain":"pet"}
|
||||
{"id":"rel:create_staff_emits_staff_created","type":"Relation","subject":"action:create_staff","predicate":"EMITS","object":"event:staff_created","evidence":"anchored","domain":"user"}
|
||||
{"id":"rel:update_user_emits_user_updated","type":"Relation","subject":"action:update_user","predicate":"EMITS","object":"event:user_updated","evidence":"anchored","domain":"user"}
|
||||
{"id":"rel:bind_wechat_emits_wx_bound","type":"Relation","subject":"action:bind_wechat","predicate":"EMITS","object":"event:wx_bound","evidence":"anchored","domain":"auth"}
|
||||
{"id":"entity:report_testimonial","type":"Entity","name":"ReportTestimonial","nameZh":"宠主寄语","domain":"report","fields":["id","report_id","store_id","content","is_public","create_time","update_time"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/ReportTestimonial.java; service/ReportTestimonialService.java","doc":"docs/ontology/objects.md#entity:report_testimonial","test":"ReportTestimonialServiceTest","evidence":"anchored"}
|
||||
{"id":"entity:service_interval","type":"Entity","name":"ServiceInterval","nameZh":"服务建议周期配置","domain":"service_type","fields":["id","store_id","service_name","pet_type","interval_days_min","interval_days_max","create_time"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/ServiceInterval.java","doc":"docs/ontology/objects.md#entity:service_interval","test":"","evidence":"anchored"}
|
||||
{"id":"action:list_stores","type":"Action","name":"listStores","nameZh":"门店列表","inputs":[],"outputs":["entity:store"],"entrypoints":["GET /api/store/list","StoreController#list"],"appliesRules":["rule:BR-STORE-003"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#list","evidence":"anchored","domain":"store","test":"StoreControllerTest#publicListHidesOwnerIdAndInviteCode"}
|
||||
{"id":"action:get_store","type":"Action","name":"getStore","nameZh":"门店详情","inputs":["id"],"outputs":["entity:store"],"entrypoints":["GET /api/store/get","StoreController#get"],"appliesRules":["rule:BR-STORE-003"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#get","evidence":"anchored","domain":"store","test":"StoreControllerTest#publicGetHidesOwnerIdAndInviteCode"}
|
||||
{"id":"action:get_available_slots","type":"Action","name":"getAvailableSlots","nameZh":"可预约时段","inputs":["storeId","date"],"outputs":[],"entrypoints":["GET /api/appointment/available-slots","AppointmentController#availableSlots"],"appliesRules":["rule:BR-APPT-004"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#availableSlots","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:get_report_suggestion","type":"Action","name":"getReportSuggestion","nameZh":"报告下次建议日期","inputs":["token"],"outputs":[],"entrypoints":["GET /api/report/{token}/suggestion","ReportLeadController#suggestion"],"appliesRules":["rule:BR-INT-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#suggestion","evidence":"anchored","domain":"lead"}
|
||||
{"id":"action:submit_testimonial","type":"Action","name":"submitTestimonial","nameZh":"宠主寄语提交","inputs":["token","content","isPublic"],"outputs":["entity:report_testimonial"],"entrypoints":["POST /api/report/{token}/testimonial","ReportController#submitTestimonial"],"appliesRules":["rule:BR-TST-001"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#submitTestimonial","evidence":"anchored","domain":"report","test":"ReportTestimonialServiceTest"}
|
||||
{"id":"action:get_legacy_upload","type":"Action","name":"getLegacyUpload","nameZh":"兼容旧路径读取上传资源","inputs":["path"],"outputs":[],"entrypoints":["GET /api/upload/legacy/**","FileController#getLegacyImage"],"appliesRules":["rule:BR-UPLOAD-001"],"code":"backend/src/main/java/com/petstore/controller/FileController.java#getLegacyImage","evidence":"anchored","domain":"upload"}
|
||||
{"id":"action:register_staff","type":"Action","name":"registerStaff","nameZh":"员工注册(邀请码)","inputs":["phone","password","name","inviteCode"],"outputs":["entity:user","entity:store","session_token"],"entrypoints":["POST /api/user/register-staff","UserController#registerStaff","UserService#registerStaff"],"appliesRules":["rule:BR-AUTH-001"],"code":"backend/src/main/java/com/petstore/controller/UserController.java#registerStaff","evidence":"anchored","domain":"user"}
|
||||
{"id":"action:send_sms","type":"Action","name":"sendSms","nameZh":"发送短信验证码","inputs":["phone"],"outputs":[],"entrypoints":["POST /api/sms/send","SmsController#send"],"appliesRules":["rule:BR-AUTH-002"],"code":"backend/src/main/java/com/petstore/controller/SmsController.java#send","evidence":"anchored","domain":"auth"}
|
||||
{"id":"action:register_store","type":"Action","name":"registerStore","nameZh":"店铺注册(旧入口)","inputs":["entity:store"],"outputs":["entity:store"],"entrypoints":["POST /api/store/register","StoreController#register","StoreService#create"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#register","evidence":"gap","domain":"store","status":"deprecated","deprecated":true,"doc":"deprecated:勿作为新集成入口"}
|
||||
{"id":"action:delete_appointment","type":"Action","name":"deleteAppointment","nameZh":"删除预约","inputs":["id"],"outputs":[],"entrypoints":["DELETE /api/appointment/delete","AppointmentController#delete"],"appliesRules":["rule:BR-APPT-002"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#delete","evidence":"anchored","domain":"appointment"}
|
||||
{"id":"action:list_reports","type":"Action","name":"listReports","nameZh":"报告列表","inputs":["page","pageSize"],"outputs":["entity:report"],"entrypoints":["GET /api/report/list","ReportController#list"],"appliesRules":["rule:BR-RPT-005"],"code":"backend/src/main/java/com/petstore/controller/ReportController.java#list","evidence":"anchored","domain":"report"}
|
||||
{"id":"action:wechat_authorize","type":"Action","name":"wechatAuthorize","nameZh":"获取微信授权跳转URL","inputs":[],"outputs":[],"entrypoints":["GET /api/wechat/authorize","WechatController#getAuthorizeUrl"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/controller/WechatController.java#getAuthorizeUrl","evidence":"gap","domain":"auth","status":"deprecated","deprecated":true,"doc":"deprecated:勿作为新集成入口"}
|
||||
{"id":"action:wechat_callback","type":"Action","name":"wechatCallback","nameZh":"微信授权回调","inputs":["code","state"],"outputs":[],"entrypoints":["GET /api/wechat/callback","WechatController#callback"],"appliesRules":[],"code":"backend/src/main/java/com/petstore/controller/WechatController.java#callback","evidence":"gap","domain":"auth","status":"deprecated","deprecated":true,"doc":"deprecated:勿作为新集成入口"}
|
||||
{"id":"rel:report_has_testimonial","type":"Relation","subject":"entity:report","predicate":"HAS","object":"entity:report_testimonial","cardinality":"1—1","evidence":"anchored","doc":"ReportTestimonial.report_id(唯一约束)","domain":"report"}
|
||||
{"id":"rel:testimonial_belongs_to_store","type":"Relation","subject":"entity:report_testimonial","predicate":"BELONGS_TO","object":"entity:store","cardinality":"N—1","evidence":"anchored","doc":"ReportTestimonial.store_id","domain":"report"}
|
||||
{"id":"rel:service_interval_belongs_to_store","type":"Relation","subject":"entity:service_interval","predicate":"BELONGS_TO","object":"entity:store","cardinality":"N—1","evidence":"anchored","doc":"ServiceInterval.store_id(NULL=系统默认)","domain":"service_type"}
|
||||
{"id":"rel:submit_testimonial_emits_testimonial_saved","type":"Relation","subject":"action:submit_testimonial","predicate":"EMITS","object":"event:testimonial_saved","evidence":"documented","doc":"寄语提交触发独立事件 event:testimonial_saved","domain":"report"}
|
||||
{"id":"entity:highlight_fail_reason","type":"Entity","name":"HighlightFailReason","nameZh":"成片失败原因归类","domain":"report","jpa":false,"fields":["MATERIAL","SERVICE","NETWORK","UNKNOWN"],"systems":["Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/HighlightFailReason.java","doc":"docs/ontology/objects.md#entity:highlight_fail_reason","test":"ReportHighlightVideoServiceTest#failReasonUserMessagesHaveNoTechJargon; #classifyFailureMapsReasons","evidence":"anchored"}
|
||||
{"id":"entity:current_user","type":"Entity","name":"CurrentUser","nameZh":"当前登录用户上下文","domain":"auth","jpa":false,"fields":["userId","storeId","role"],"systems":["Backend Core"],"code":"backend/src/main/java/com/petstore/auth/CurrentUser.java; CurrentUserContext.java; AuthInterceptor.java","doc":"docs/ontology/objects.md#entity:current_user","test":"SessionTokenServiceTest","evidence":"anchored"}
|
||||
{"id":"entity:membership","type":"Entity","name":"Membership","nameZh":"会员卡","domain":"growth","jpa":false,"fields":[],"systems":[],"code":"","doc":"docs/ontology/objects.md#待录入(P1/P2 方向,gap 证据)","test":"","evidence":"gap","status":"roadmap","note":"Phase A 门店后台明确不做;对标宠老板会员卡/升降级。仅 roadmap 占位。"}
|
||||
{"id":"entity:stored_value","type":"Entity","name":"StoredValue","nameZh":"储值","domain":"growth","jpa":false,"fields":[],"systems":[],"code":"","doc":"docs/ontology/objects.md#待录入(P1/P2 方向,gap 证据)","test":"","evidence":"gap","status":"roadmap","note":"Phase A 明确不做;依赖收银流水。"}
|
||||
{"id":"entity:service_package","type":"Entity","name":"ServicePackage","nameZh":"套餐","domain":"growth","jpa":false,"fields":[],"systems":[],"code":"","doc":"docs/ontology/objects.md#待录入(P1/P2 方向,gap 证据)","test":"","evidence":"gap","status":"roadmap","note":"Phase A 明确不做;次卡/套餐属 C 期。"}
|
||||
{"id":"entity:report_history","type":"Entity","name":"ReportHistory","nameZh":"宠主历史报告汇总","domain":"report","jpa":false,"fields":[],"systems":[],"code":"","doc":"docs/ontology/objects.md#待录入(P1/P2 方向,gap 证据)","test":"","evidence":"gap","status":"roadmap","note":"P2 方向库占位,不展开字段"}
|
||||
{"id":"entity:analytics_dashboard","type":"Entity","name":"AnalyticsDashboard","nameZh":"经营分析看板","domain":"analytics","jpa":false,"fields":[],"systems":[],"code":"","doc":"docs/ontology/objects.md#待录入(P1/P2 方向,gap 证据)","test":"","evidence":"gap","status":"roadmap","note":"B 期经营看板;A 期工作台仅轻量指标卡,不展开本实体。"}
|
||||
{"id":"rule:BR-USER-003","type":"Rule","name":"userSensitiveFieldsJsonIgnore","nameZh":"User 敏感字段不序列化","domain":"user","appliesTo":["action:login","action:wx_phone_login","action:get_user_info","action:update_user","action:register_boss","action:register_staff","action:list_staff"],"code":"backend/src/main/java/com/petstore/entity/User.java","doc":"docs/ontology/rules.md#rule:BR-USER-003","test":"UserJsonIgnoreTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-STORE-003","type":"Rule","name":"publicStoreFieldTrim","nameZh":"公开门店字段裁剪","domain":"store","appliesTo":["action:get_store","action:list_stores"],"code":"backend/src/main/java/com/petstore/controller/StoreController.java#toPublicStoreView","doc":"docs/ontology/rules.md#rule:BR-STORE-003","test":"StoreControllerTest#publicGetHidesOwnerIdAndInviteCode; StoreControllerTest#publicListHidesOwnerIdAndInviteCode","evidence":"anchored","note":"公开 get/list 经 toPublicStoreView 裁剪,不含 ownerId/inviteCode/deleted"}
|
||||
{"id":"rule:BR-TST-001","type":"Rule","name":"oneReportOneTestimonial","nameZh":"一报告一寄语(重复提交更新)","domain":"report","appliesTo":["action:submit_testimonial"],"code":"backend/src/main/java/com/petstore/service/ReportTestimonialService.java","doc":"docs/ontology/rules.md#rule:BR-TST-001","test":"ReportTestimonialServiceTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-UNSUB-001","type":"Rule","name":"unsubscribeByToken","nameZh":"凭 unsubscribeToken 一键退订","domain":"lead","appliesTo":["action:unsubscribe"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe","doc":"docs/ontology/rules.md#rule:BR-UNSUB-001","test":"ReportLeadControllerTest","evidence":"anchored"}
|
||||
{"id":"rule:BR-INT-001","type":"Rule","name":"serviceIntervalSuggestion","nameZh":"服务建议周期推算下次日期","domain":"service_type","appliesTo":["action:get_report_suggestion"],"code":"backend/src/main/java/com/petstore/service/ReportLeadService.java#suggestNext","doc":"docs/ontology/rules.md#rule:BR-INT-001","test":"ReportLeadControllerTest","evidence":"anchored"}
|
||||
{"id":"action:track_video_play","type":"Action","name":"trackVideoPlay","nameZh":"成片点击播放埋点","domain":"report","inputs":["reportTokenHash","source"],"outputs":["event:video_play"],"entrypoints":["frontend reportView / videoPlayer (client-emitted)"],"appliesRules":[],"code":"","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented","note":"前端客户端事件;上报与统计落地 gap"}
|
||||
{"id":"action:track_video_save","type":"Action","name":"trackVideoSave","nameZh":"成片保存相册埋点","domain":"report","inputs":["reportTokenHash"],"outputs":["event:video_save"],"entrypoints":["frontend reportView / videoPlayer (client-emitted)"],"appliesRules":[],"code":"","doc":"docs/本期主线-下一步产品优化点.md 埋点事件 schema","evidence":"documented","note":"前端客户端事件;上报与统计落地 gap"}
|
||||
{"id":"action:logout","type":"Action","name":"logout","nameZh":"退出登录 / 吊销会话","domain":"auth","inputs":["sessionToken"],"outputs":["event:session_revoked"],"entrypoints":[],"appliesRules":["rule:BR-AUTH-001"],"code":"","doc":"docs/ontology/actions.md#action:logout","evidence":"gap","note":"当前仅有签发无服务端吊销;前端清本地 token"}
|
||||
{"id":"event:testimonial_saved","type":"Event","name":"testimonialSaved","nameZh":"宠主寄语已保存","domain":"report","emittedBy":["action:submit_testimonial"],"payload":["reportTokenHash","testimonialId","isPublic","ts"],"code":"backend/src/main/java/com/petstore/service/ReportTestimonialService.java","doc":"docs/ontology/events.md#event:testimonial_saved","evidence":"documented","note":"代码已写库;独立事件上报/统计未落地"}
|
||||
{"id":"event:session_revoked","type":"Event","name":"sessionRevoked","nameZh":"会话吊销","domain":"auth","emittedBy":["action:logout"],"payload":["userId","ts"],"code":"","doc":"docs/ontology/events.md#event:session_revoked","evidence":"gap"}
|
||||
{"id":"rel:highlight_has_fail_reason","type":"Relation","subject":"entity:highlight_video","predicate":"HAS","object":"entity:highlight_fail_reason","cardinality":"0—1","domain":"report","evidence":"anchored","doc":"Report.highlight_fail_reason 使用 HighlightFailReason 归类"}
|
||||
{"id":"rel:session_token_resolves_to_current_user","type":"Relation","subject":"entity:session_token","predicate":"RESOLVES_TO","object":"entity:current_user","cardinality":"1—1","domain":"auth","evidence":"anchored","doc":"AuthInterceptor 解析 token → CurrentUserContext"}
|
||||
{"id":"rel:track_video_play_emits_video_play","type":"Relation","subject":"action:track_video_play","predicate":"EMITS","object":"event:video_play","domain":"report","evidence":"documented"}
|
||||
{"id":"rel:track_video_save_emits_video_save","type":"Relation","subject":"action:track_video_save","predicate":"EMITS","object":"event:video_save","domain":"report","evidence":"documented"}
|
||||
{"id":"rel:logout_emits_session_revoked","type":"Relation","subject":"action:logout","predicate":"EMITS","object":"event:session_revoked","domain":"auth","evidence":"gap"}
|
||||
{"id":"rel:br_user_003_applies","type":"Relation","subject":"rule:BR-USER-003","predicate":"APPLIES_TO","object":"action:get_user_info","domain":"user","evidence":"anchored","doc":"同样适用于 login/register/list_staff 等返回 User 的动作"}
|
||||
{"id":"rel:br_tst_001_applies","type":"Relation","subject":"rule:BR-TST-001","predicate":"APPLIES_TO","object":"action:submit_testimonial","domain":"report","evidence":"anchored"}
|
||||
{"id":"rel:br_unsub_001_applies","type":"Relation","subject":"rule:BR-UNSUB-001","predicate":"APPLIES_TO","object":"action:unsubscribe","domain":"lead","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":"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_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:service_customer","type":"Entity","name":"ServiceCustomer","nameZh":"服务客户主档(瘦身)","domain":"admin","jpa":false,"fields":["userId","displayName","phoneMasked","pets","lastVisitAt","lastReportId","leadStatus","source","storeId"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"","doc":"docs/ontology/objects.md#entity:service_customer","test":"","evidence":"documented","note":"服务经营视角客户投影。列表经 GET /api/admin/service-customers(planned)。不含余额/次卡/积分。","status":"planned"}
|
||||
{"id":"rule:BR-ADMIN-001","type":"Rule","name":"adminRoleGate","nameZh":"门店后台角色门禁(boss/staff 可读写)","domain":"admin","appliesTo":["action:view_workbench","action:admin_list_appointments","action:admin_list_reports","action:admin_list_leads","action:admin_list_service_customers","action:admin_update_settings"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-001; docs/门店管理后台-PhaseA-页面清单PRD.md","test":"","evidence":"documented","note":"boss 与 staff 均可进入 admin_console 并编辑本店数据;customer 禁止。删店/部分设置仍仅 boss(BR-STORE-001/BR-USER-001)。产品决策 2026-07-10。"}
|
||||
{"id":"rule:BR-ADMIN-002","type":"Rule","name":"adminStoreScope","nameZh":"后台数据仅本店 storeId","domain":"admin","appliesTo":["action:view_workbench","action:admin_list_appointments","action:admin_list_reports","action:admin_list_leads","action:admin_list_service_customers","action:admin_update_settings"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-002","test":"","evidence":"documented","note":"所有后台查询/写操作强制 current.storeId;忽略跨店请求参数。连锁同步不在 Phase A。"}
|
||||
{"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":"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: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_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_leads","type":"Action","name":"adminListLeads","nameZh":"后台回访线索列表","domain":"admin","inputs":["remindStatus","remindDateTo","page","pageSize"],"outputs":["entity:report_lead"],"entrypoints":["Admin UI /leads (planned)","reuses GET /api/report/leads"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-LEAD-002","rule:BR-LEAD-003"],"code":"backend/.../ReportLeadController.java#leads (reuse)","doc":"docs/ontology/actions.md#action:admin_list_leads","evidence":"documented"}
|
||||
{"id":"action:admin_list_service_customers","type":"Action","name":"adminListServiceCustomers","nameZh":"后台服务客户列表","domain":"admin","inputs":["source","hasPendingLead","lastVisitFrom","lastVisitTo","q","page","pageSize"],"outputs":["entity:service_customer"],"entrypoints":["GET /api/admin/service-customers","Admin UI /customers"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-003","rule:BR-ADMIN-004"],"code":"","doc":"docs/ontology/actions.md#action:admin_list_service_customers; docs/门店管理后台-PhaseA-页面清单PRD.md §7.6","evidence":"anchored","note":"产品决策:新建聚合 API,不以前端拼装为正式方案。Query: source,hasPendingLead,lastVisitFrom,lastVisitTo,q,page,pageSize;2026-07-10 已实现聚合 API","code_refs":["backend/src/main/java/com/petstore/controller/AdminServiceCustomerController.java#listServiceCustomers","backend/src/main/java/com/petstore/service/AdminServiceCustomerService.java"]}
|
||||
{"id":"action:admin_update_settings","type":"Action","name":"adminUpdateSettings","nameZh":"后台门店设置读写","domain":"admin","inputs":["entity:store","entity:service_type","entity:user"],"outputs":["entity:store","entity:service_type","entity:user"],"entrypoints":["GET /api/admin/store","Admin UI /settings","PUT /api/store/update"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-ADMIN-003","rule:BR-STORE-001","rule:BR-ST-001","rule:BR-USER-001"],"code":"","doc":"docs/ontology/actions.md#action:admin_update_settings","evidence":"anchored","note":"后台读本店含 inviteCode(GET /api/admin/store);写资料复用 PUT /api/store/update(仅 boss);服务类型/员工复用既有 API。","code_refs":["backend/src/main/java/com/petstore/controller/AdminStoreController.java#currentStore","backend/src/main/java/com/petstore/controller/StoreController.java#update"]}
|
||||
{"id":"action:admin_manage_schedule","type":"Action","name":"adminManageSchedule","nameZh":"后台排班与占用","domain":"admin","inputs":["date","entity:schedule_block"],"outputs":["entity:schedule_block","entity:appointment"],"entrypoints":["Admin UI /schedule","GET /api/schedule/day","POST /api/schedule/block","DELETE /api/schedule/block"],"appliesRules":["rule:BR-ADMIN-001","rule:BR-ADMIN-002","rule:BR-SCH-001"],"code":"","doc":"docs/ontology/actions.md#action:admin_manage_schedule","evidence":"anchored","note":"Web 排班日视图;增删占用复用 ScheduleController。"}
|
||||
{"id":"event:workbench_viewed","type":"Event","name":"workbenchViewed","nameZh":"工作台已打开","domain":"admin","emittedBy":["action:view_workbench"],"payload":["storeId","userId","ts"],"code":"","doc":"docs/ontology/events.md#event:workbench_viewed","evidence":"gap","note":"可选埋点;A 期可不实现上报。"}
|
||||
{"id":"rel:admin_console_has_workbench","type":"Relation","subject":"entity:admin_console","predicate":"HAS","object":"entity:workbench","domain":"admin","evidence":"documented","cardinality":"1—1","doc":"登录默认工作台"}
|
||||
{"id":"rel:workbench_has_todo_items","type":"Relation","subject":"entity:workbench","predicate":"HAS","object":"entity:workbench_todo_item","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_leads","type":"Relation","subject":"entity:workbench","predicate":"AGGREGATES","object":"entity:report_lead","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"待回访"}
|
||||
{"id":"rel:service_customer_is_user","type":"Relation","subject":"entity:service_customer","predicate":"PROJECTS","object":"entity:user","domain":"admin","evidence":"documented","cardinality":"N—1","doc":"客户投影自 User(customer)"}
|
||||
{"id":"rel:service_customer_has_pets","type":"Relation","subject":"entity:service_customer","predicate":"HAS","object":"entity:pet","domain":"admin","evidence":"documented","cardinality":"1—N","doc":"一人多宠"}
|
||||
{"id":"rel:view_workbench_emits_viewed","type":"Relation","subject":"action:view_workbench","predicate":"EMITS","object":"event:workbench_viewed","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:br_admin_001_applies_workbench","type":"Relation","subject":"rule:BR-ADMIN-001","predicate":"APPLIES_TO","object":"action:view_workbench","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:br_admin_002_applies_workbench","type":"Relation","subject":"rule:BR-ADMIN-002","predicate":"APPLIES_TO","object":"action:view_workbench","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:br_admin_004_gates_console","type":"Relation","subject":"rule:BR-ADMIN-004","predicate":"CONSTRAINS","object":"entity:admin_console","domain":"admin","evidence":"documented","doc":"Phase A 范围闸门"}
|
||||
{"id":"rel:admin_list_appts_reads_appointment","type":"Relation","subject":"action:admin_list_appointments","predicate":"READS","object":"entity:appointment","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:admin_list_reports_reads_report","type":"Relation","subject":"action:admin_list_reports","predicate":"READS","object":"entity:report","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:admin_list_leads_reads_lead","type":"Relation","subject":"action:admin_list_leads","predicate":"READS","object":"entity:report_lead","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:admin_list_customers_reads_sc","type":"Relation","subject":"action:admin_list_service_customers","predicate":"READS","object":"entity:service_customer","domain":"admin","evidence":"documented"}
|
||||
{"id":"rel:drilldown_todo_to_list_appts","type":"Relation","subject":"action:drilldown_workbench_todo","predicate":"ROUTES_TO","object":"action:admin_list_appointments","domain":"admin","evidence":"documented","doc":"kind=today_appointments|pending_report 等"}
|
||||
197
graph/validate_ontology.py
Executable file
@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
宠小它 知识本体校验脚本。
|
||||
|
||||
校验 docs/graph/ontology.jsonl 的:
|
||||
1. JSONL 格式合法(每行一个 JSON 对象)
|
||||
2. ID 唯一且符合命名规则(entity:/action:/event:/rule:/rel:)
|
||||
3. 引用完整性:每个被引用的 ID(subject/object/inputs/outputs/appliesTo/appliesRules/emittedBy/appliesTo)都存在
|
||||
4. 证据强度分布统计
|
||||
5. 类型必填字段检查(Entity/Action/Event/Rule/Relation 各自的最小字段集)
|
||||
|
||||
用法:
|
||||
python3 docs/graph/validate_ontology.py [docs_root]
|
||||
|
||||
默认 docs_root = docs(相对当前工作目录)。
|
||||
退出码:0 = 全部通过;1 = 有错误。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
VALID_EVIDENCE = {"anchored", "documented", "implemented", "gap"}
|
||||
VALID_TYPES = {"Entity", "Action", "Event", "Rule", "Relation"}
|
||||
ID_PREFIX_BY_TYPE = {
|
||||
"Entity": "entity:",
|
||||
"Action": "action:",
|
||||
"Event": "event:",
|
||||
"Rule": "rule:",
|
||||
"Relation": "rel:",
|
||||
}
|
||||
|
||||
REQUIRED_FIELDS_BY_TYPE = {
|
||||
"Entity": {"id", "type", "name", "fields", "evidence"},
|
||||
"Action": {"id", "type", "name", "entrypoints", "evidence"},
|
||||
"Event": {"id", "type", "name", "domain", "emittedBy", "evidence"},
|
||||
"Rule": {"id", "type", "name", "appliesTo", "evidence"},
|
||||
"Relation": {"id", "type", "subject", "predicate", "object", "evidence"},
|
||||
}
|
||||
|
||||
# 引用字段:这些字段里看起来像本体 ID 的字符串(以 entity:/action:/event:/rule:/rel: 开头)必须存在
|
||||
# 原始字段名(如 phone、code、token)不视为引用,跳过
|
||||
REFERENCE_FIELDS = {
|
||||
"inputs", "outputs", "appliesTo", "appliesRules", "emittedBy",
|
||||
"subject", "object",
|
||||
}
|
||||
ID_PREFIXES = ("entity:", "action:", "event:", "rule:", "rel:")
|
||||
|
||||
|
||||
def is_id_like(s):
|
||||
return isinstance(s, str) and s.startswith(ID_PREFIXES)
|
||||
|
||||
|
||||
def load_jsonl(path):
|
||||
entries = []
|
||||
errors = []
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
for lineno, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
obj["_lineno"] = lineno
|
||||
entries.append(obj)
|
||||
except json.JSONDecodeError as e:
|
||||
errors.append(f"line {lineno}: invalid JSON - {e}")
|
||||
return entries, errors
|
||||
|
||||
|
||||
def validate_ids(entries):
|
||||
errors = []
|
||||
ids = set()
|
||||
for e in entries:
|
||||
eid = e.get("id")
|
||||
if not eid:
|
||||
errors.append(f"line {e['_lineno']}: missing id")
|
||||
continue
|
||||
if eid in ids:
|
||||
errors.append(f"line {e['_lineno']}: duplicate id {eid}")
|
||||
ids.add(eid)
|
||||
etype = e.get("type")
|
||||
expected_prefix = ID_PREFIX_BY_TYPE.get(etype)
|
||||
if expected_prefix and not eid.startswith(expected_prefix):
|
||||
errors.append(f"line {e['_lineno']}: id {eid} should start with {expected_prefix} for type {etype}")
|
||||
return ids, errors
|
||||
|
||||
|
||||
def validate_required_fields(entries):
|
||||
errors = []
|
||||
for e in entries:
|
||||
etype = e.get("type")
|
||||
if etype not in VALID_TYPES:
|
||||
errors.append(f"line {e['_lineno']}: unknown type {etype}")
|
||||
continue
|
||||
required = REQUIRED_FIELDS_BY_TYPE.get(etype, set())
|
||||
missing = required - set(e.keys())
|
||||
if missing:
|
||||
errors.append(f"line {e['_lineno']}: {etype} {e.get('id')} missing fields {sorted(missing)}")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_references(entries, all_ids):
|
||||
errors = []
|
||||
for e in entries:
|
||||
eid = e.get("id", "?")
|
||||
for field in REFERENCE_FIELDS:
|
||||
if field not in e:
|
||||
continue
|
||||
val = e[field]
|
||||
if val is None:
|
||||
continue
|
||||
if isinstance(val, str):
|
||||
refs = [val]
|
||||
elif isinstance(val, list):
|
||||
refs = val
|
||||
else:
|
||||
errors.append(f"line {e['_lineno']}: {eid}.{field} must be string or list, got {type(val).__name__}")
|
||||
continue
|
||||
for ref in refs:
|
||||
if not ref:
|
||||
continue
|
||||
# 只校验看起来像本体 ID 的引用;原始字段名(phone/code/token)跳过
|
||||
if not is_id_like(ref):
|
||||
continue
|
||||
if ref not in all_ids:
|
||||
errors.append(f"line {e['_lineno']}: {eid}.{field} references unknown id {ref}")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_evidence(entries):
|
||||
errors = []
|
||||
for e in entries:
|
||||
ev = e.get("evidence")
|
||||
if ev is None:
|
||||
errors.append(f"line {e['_lineno']}: {e.get('id')} missing evidence")
|
||||
continue
|
||||
if ev not in VALID_EVIDENCE:
|
||||
errors.append(f"line {e['_lineno']}: {e.get('id')} invalid evidence '{ev}', must be one of {VALID_EVIDENCE}")
|
||||
return errors
|
||||
|
||||
|
||||
def print_stats(entries):
|
||||
type_counts = Counter(e.get("type", "?") for e in entries)
|
||||
evidence_counts = Counter(e.get("evidence", "?") for e in entries)
|
||||
print("\n=== 本体统计 ===")
|
||||
print(f"总条目数: {len(entries)}")
|
||||
print("\n按类型:")
|
||||
for t in sorted(type_counts):
|
||||
print(f" {t:10s} {type_counts[t]}")
|
||||
print("\n按证据强度:")
|
||||
for ev in sorted(evidence_counts):
|
||||
print(f" {ev:12s} {evidence_counts[ev]}")
|
||||
total = len(entries)
|
||||
if total:
|
||||
anchored = evidence_counts.get("anchored", 0)
|
||||
print(f"\nanchored 占比: {anchored}/{total} = {anchored/total*100:.1f}%")
|
||||
|
||||
|
||||
def main():
|
||||
docs_root = sys.argv[1] if len(sys.argv) > 1 else "docs"
|
||||
jsonl_path = os.path.join(docs_root, "graph", "ontology.jsonl")
|
||||
if not os.path.exists(jsonl_path):
|
||||
print(f"ERROR: {jsonl_path} not found")
|
||||
sys.exit(1)
|
||||
|
||||
entries, errors = load_jsonl(jsonl_path)
|
||||
if errors:
|
||||
print("=== JSONL 解析错误 ===")
|
||||
for err in errors:
|
||||
print(f" {err}")
|
||||
sys.exit(1)
|
||||
|
||||
all_ids, id_errors = validate_ids(entries)
|
||||
field_errors = validate_required_fields(entries)
|
||||
ref_errors = validate_references(entries, all_ids)
|
||||
ev_errors = validate_evidence(entries)
|
||||
|
||||
all_errors = id_errors + field_errors + ref_errors + ev_errors
|
||||
if all_errors:
|
||||
print("=== 校验失败 ===")
|
||||
for err in all_errors:
|
||||
print(f" {err}")
|
||||
print(f"\n共 {len(all_errors)} 个错误")
|
||||
print_stats(entries)
|
||||
sys.exit(1)
|
||||
|
||||
print("=== 校验通过 ===")
|
||||
print(f" {jsonl_path}: {len(entries)} 条目,引用完整,证据强度合法")
|
||||
print_stats(entries)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
81
ontology/README.md
Normal file
@ -0,0 +1,81 @@
|
||||
# 宠小它 — 知识本体(Ontology)
|
||||
|
||||
> 知识库的单一事实源(SSOT)。所有产品文档与代码改动都应向本本体对齐;冲突以本本体为准。
|
||||
|
||||
## 这是什么
|
||||
|
||||
本目录用本体论框架把宠小它的领域知识建模为 5 类原子:
|
||||
|
||||
| 原子类型 | 文件 | 含义 |
|
||||
|---------|------|------|
|
||||
| **Object(实体)** | `objects.md` | 持久名词 + 字段 + 归属系统 |
|
||||
| **Action(动作)** | `actions.md` | 读写实体的命令/用例 |
|
||||
| **Event(事件)** | `events.md` | 动作或状态变更后发生的事实(过去时) |
|
||||
| **Rule(规则/不变量)** | `rules.md` | 业务约束、决策条件、权限边界 |
|
||||
| **Relation(关系)** | `relations.md` | 实体/动作/事件/规则之间的边 |
|
||||
|
||||
机器可读版本在 `../graph/ontology.jsonl`,三方对齐审计在 `../coverage/ontology-coverage-audit.md`。
|
||||
|
||||
## 为什么要本体论
|
||||
|
||||
现有文档是主题/文档类型导向的,同一概念(如「报告」)的规则散落在 `产品设计文档.md §5.3`、`P0-研发落地清单.md`、`架构与产品评估报告`、代码里的 `ReportController`/`ReportService`/`Report.vue` 等多处,导致:
|
||||
|
||||
1. 文档与代码状态不同步(架构评估报告已直接指出)。
|
||||
2. 改一处要全仓 grep 才能确认是否同步。
|
||||
3. Agent 协作靠人肉读长文档找上下文,无机器可读索引。
|
||||
|
||||
本体论把这些概念收口为 SSOT,做**双向引用**:本体条目写「落地于 `ReportController#create` / `docs/产品设计文档.md §5.3` / 测试 `ReportControllerTest`」,反过来文档/代码改动时回溯本体 ID。
|
||||
|
||||
## 证据强度
|
||||
|
||||
每条本体条目标注证据强度,用于覆盖度审计:
|
||||
|
||||
| 强度 | 含义 |
|
||||
|------|------|
|
||||
| `anchored` | 有代码路径 + 测试 + 文档三处对齐 |
|
||||
| `documented` | 只在文档里,代码未实现或未对齐 |
|
||||
| `implemented` | 只在代码里,文档未更新 |
|
||||
| `gap` | 明确缺失(如真实短信服务商、production profile 实测) |
|
||||
|
||||
## ID 规则
|
||||
|
||||
稳定的、可读的 ID:
|
||||
|
||||
```text
|
||||
entity:snake_case_name # entity:appointment
|
||||
action:snake_case_name # action:create_appointment
|
||||
event:snake_case_name # event:appointment_created
|
||||
rule:BR-DOM-001 # rule:BR-RPT-001
|
||||
rel:subject_predicate_object # rel:report_belongs_to_appointment
|
||||
```
|
||||
|
||||
每个 JSONL 条目里引用的 `subject`/`object`/`inputs`/`outputs`/`appliesTo` ID 必须在 `ontology.jsonl` 中存在。
|
||||
|
||||
## 与现有体系的关系
|
||||
|
||||
- **既有文档不删除、不重写**:只在顶部加「本文档与 `ontology/objects.md#xxx` 对齐,冲突以本体为准」。
|
||||
- **`.agents/` 协作体系不动**:本体论是它的知识基座;`common-context.md` 的术语表、`rules/api-contract-boundary.md` 的规则可引用本体 ID。
|
||||
- **角色写入锁**:`ontology/` 归 `Docs Agent` + `System Architect` 共建,其他角色只读引用。
|
||||
|
||||
## 范围
|
||||
|
||||
已覆盖 P0 主闭环实体与动作,并补录:`Pet`、`ServiceType`、`ScheduleBlock`、`HighlightVideo`、`SessionToken`、`ReportImage`、`ReportTestimonial`、`ServiceInterval`、`HighlightFailReason`、`CurrentUser`,以及成片/上传/鉴权/寄语/退订相关规则。
|
||||
|
||||
**门店 Web 后台 Phase A(documented 预录入)**:`AdminConsole`、`Workbench`、`WorkbenchTodoItem`、`ServiceCustomer` 与 `BR-ADMIN-001`…`004`、对应 admin actions。设计须引用这些 ID;收银/会员资产仍为 gap。
|
||||
|
||||
P1/P2 方向(会员/储值/套餐/历史报告/看板)以 `gap` 证据条目录入,不展开字段;受 `rule:BR-ADMIN-004` 约束不得偷渡进 Phase A 设计。
|
||||
|
||||
当前规模以 `python3 docs/graph/validate_ontology.py docs` 输出为准;覆盖审计见 `../coverage/ontology-coverage-audit.md`。
|
||||
|
||||
## 校验
|
||||
|
||||
```bash
|
||||
python3 docs/graph/validate_ontology.py docs
|
||||
python3 docs/graph/audit_drift.py
|
||||
```
|
||||
|
||||
校验:
|
||||
- JSONL 引用完整性(每个被引用的 ID 都存在)
|
||||
- 证据强度分布
|
||||
- 本体—代码端点/实体漂移
|
||||
- 文档反向引用(本体条目里写的 doc 路径存在)
|
||||
655
ontology/actions.md
Normal file
@ -0,0 +1,655 @@
|
||||
# Actions(动作)
|
||||
|
||||
> 读写实体的命令/用例。每个动作标注入口(controller/service 方法)、输入输出实体、适用规则。
|
||||
|
||||
## Store 相关
|
||||
|
||||
### action:register_boss
|
||||
|
||||
老板入驻:创建 `Store` + `boss` `User`,签发 `session_token`。
|
||||
|
||||
- **入口**:`POST /api/user/register-boss` → `UserService#registerBoss`(公开,在 AuthInterceptor allowlist)
|
||||
- **输入**:`storeName`、`bossName`、`phone`、`password`
|
||||
- **输出**:`entity:store`、`entity:user`、`session_token`
|
||||
- **适用规则**:`rule:BR-AUTH-001`(session token 签发)
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#registerBoss`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:update_store
|
||||
|
||||
更新店铺信息:仅 boss 且只能改自己所属门店。
|
||||
|
||||
- **入口**:`PUT /api/store/update` → `StoreController#update` → `StoreService#update`
|
||||
- **输入**:`entity:store`(id 必须等于 `current.storeId`)
|
||||
- **输出**:`entity:store`
|
||||
- **适用规则**:`rule:BR-STORE-001`(仅 boss + 同店)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#update`
|
||||
- **测试**:`StoreControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_store
|
||||
|
||||
删除店铺:仅 boss 且只能删自己所属门店(软删)。
|
||||
|
||||
- **入口**:`DELETE /api/store/delete` → `StoreController#delete`
|
||||
- **适用规则**:`rule:BR-STORE-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#delete`
|
||||
- **测试**:`StoreControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_store_by_invite_code
|
||||
|
||||
按邀请码查门店:仅 boss/staff 且邀请码对应门店必须为当前用户所属门店。
|
||||
|
||||
- **入口**:`GET /api/store/invite-code` → `StoreController#getByInviteCode`
|
||||
- **适用规则**:`rule:BR-STORE-002`(邀请码同店校验)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#getByInviteCode`
|
||||
- **测试**:`StoreControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## User / 鉴权相关
|
||||
|
||||
### action:login
|
||||
|
||||
手机号 + 短信验证码登录。**生产禁用**,仅 dev 模式(万能验证码 123456)可用。
|
||||
|
||||
- **入口**:`POST /api/user/login` → `UserService#login`(公开)
|
||||
- **输入**:`phone`、`code`
|
||||
- **输出**:`entity:user`、`entity:store`、`session_token`
|
||||
- **适用规则**:`rule:BR-AUTH-001`、`rule:BR-AUTH-002`(生产 SMS 禁用)
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#login`
|
||||
- **测试**:`UserServiceLoginStrategyTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:wx_phone_login
|
||||
|
||||
微信小程序手机号授权登录:`getPhoneNumber` 的 phoneCode + `uni.login` 的 loginCode 换手机号并沉淀 openid。
|
||||
|
||||
- **入口**:`POST /api/user/wx-phone-login` → `UserController#wxPhoneLogin` → `UserService#loginByVerifiedPhone`(公开)
|
||||
- **输出**:`entity:user`、`entity:store`、`session_token`、可能绑定 `wechat_openid`/`wechat_unionid`
|
||||
- **适用规则**:`rule:BR-AUTH-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#wxPhoneLogin`
|
||||
- **证据**:`anchored`(生产登录主路径)
|
||||
|
||||
### action:bind_wechat
|
||||
|
||||
绑定小程序 openid/unionid;若标识曾绑定其他账号则先解绑。
|
||||
|
||||
- **入口**:`UserController#wxPhoneLogin` 内调用 `UserService#bindWechatMiniIdentity`
|
||||
- **适用规则**:—
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Appointment 相关
|
||||
|
||||
### action:create_appointment
|
||||
|
||||
创建预约:半小时占号,取消不占号;`userId` 从当前登录用户派生。
|
||||
|
||||
- **入口**:`POST /api/appointment/create` → `AppointmentController#create` → `AppointmentService#createBooking`
|
||||
- **输入**:`petName`、`petType`、`serviceType`、`appointmentTime`、`storeId`、`remark?`、`petId?`
|
||||
- **输出**:`entity:appointment`
|
||||
- **适用规则**:`rule:BR-APPT-001`(状态机)、`rule:BR-APPT-004`(半小时档 + 营业时间 + 占用校验)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#create`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:start_service
|
||||
|
||||
开始服务:仅 `new → doing`,指定技师(谁点谁服务);仅 boss/staff 且同店。
|
||||
|
||||
- **入口**:`POST /api/appointment/start` → `AppointmentController#start` → `AppointmentService#startService`
|
||||
- **适用规则**:`rule:BR-APPT-001`、`rule:BR-APPT-002`(仅 boss/staff + 同店)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#start`
|
||||
- **测试**:`AppointmentServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:transition_appointment_status
|
||||
|
||||
更新预约状态:合法迁移见 `AppointmentService#transitionStatus`;customer 仅可取消自己的。
|
||||
|
||||
- **入口**:`PUT /api/appointment/status` → `AppointmentController#updateStatus`
|
||||
- **适用规则**:`rule:BR-APPT-001`、`rule:BR-APPT-003`(customer 仅取消自己的)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#updateStatus`
|
||||
- **测试**:`AppointmentServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_appointment_detail
|
||||
|
||||
预约详情:customer 仅本人,boss/staff 仅同店。
|
||||
|
||||
- **入口**:`GET /api/appointment/detail` → `AppointmentController#detail`
|
||||
- **适用规则**:`rule:BR-APPT-002`、`rule:BR-APPT-003`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#detail`
|
||||
- **测试**:`AppointmentControllerDetailTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:list_appointments
|
||||
|
||||
预约列表:身份从上下文派生,customer 看自己,boss/staff 看本店。
|
||||
|
||||
- **入口**:`GET /api/appointment/list` → `AppointmentController#list`
|
||||
- **适用规则**:`rule:BR-APPT-002`、`rule:BR-APPT-003`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#list`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Report 相关
|
||||
|
||||
### action:submit_report
|
||||
|
||||
提交报告:仅 boss/staff;`userId` 从上下文派生;报告所属门店必须等于当前用户门店。
|
||||
|
||||
- **入口**:`POST /api/report/create` → `ReportController#create` → `ReportService#create`
|
||||
- **输入**:`entity:report`(含 `appointmentId`、`images`)
|
||||
- **输出**:`reportToken`、`reportId`
|
||||
- **适用规则**:`rule:BR-RPT-001`(必须绑定预约)、`rule:BR-RPT-002`(一约一份)、`rule:BR-RPT-003`(前后照片必填)、`rule:BR-RPT-004`(仅 doing 预约)、`rule:BR-RPT-005`(跨店 403)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#create`
|
||||
- **测试**:`ReportControllerTest`、`ReportServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_report_by_token
|
||||
|
||||
公开报告查询:凭 token 匿名访问,字段裁剪不返回 top-level `userId/storeId/reportToken`。
|
||||
|
||||
- **入口**:`GET /api/report/get?token=` → `ReportController#getByAppointmentId`(公开)
|
||||
- **适用规则**:`rule:BR-RPT-006`(公开字段裁剪)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#getByAppointmentId`
|
||||
- **测试**:`ReportControllerTest#publicTokenQueryHidesInternalFields`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:track_report_open
|
||||
|
||||
报告打开埋点:日志只记 token SHA-256 前 8 位 hash。
|
||||
|
||||
- **入口**:`POST /api/report/open-track` → `ReportController#trackReportOpen`(公开)
|
||||
- **适用规则**:`rule:BR-RPT-007`(token hash 日志)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_report
|
||||
|
||||
删除报告:仅 boss/staff 且同店。
|
||||
|
||||
- **入口**:`DELETE /api/report/delete` → `ReportController#delete`
|
||||
- **适用规则**:`rule:BR-RPT-005`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#delete`
|
||||
- **证据**:`anchored`(同店校验用 `reportService.list(storeId,null)` 遍历,性能非最优,列为已知 gap)
|
||||
|
||||
## ReportLead 相关
|
||||
|
||||
### action:submit_lead
|
||||
|
||||
宠主留资:凭 `reportToken` 匿名提交手机号,同报告同手机号去重。
|
||||
|
||||
- **入口**:`POST /api/report/{token}/reminder` → `ReportLeadController#submitReminder` → `ReportLeadService#submit`(公开)
|
||||
- **输入**:`phone`、`consent`、`loginCode?`
|
||||
- **输出**:`leadId`、`remindDate`、`unsubscribeToken`、`wechatBound`
|
||||
- **适用规则**:`rule:BR-LEAD-001`(去重)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder`
|
||||
- **证据**:`documented`(去重逻辑未补测试用例)
|
||||
|
||||
### action:unsubscribe
|
||||
|
||||
一键退订:凭 `unsubscribeToken`。
|
||||
|
||||
- **入口**:`POST /api/report/unsubscribe` → `ReportLeadController#unsubscribe`(公开)
|
||||
- **适用规则**:`rule:BR-UNSUB-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe`
|
||||
- **测试**:`ReportLeadControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:list_leads
|
||||
|
||||
回访池列表:仅 boss/staff,`storeId` 从上下文派生;返回脱敏 openid/unionid。
|
||||
|
||||
- **入口**:`GET /api/report/leads` → `ReportLeadController#leads`
|
||||
- **适用规则**:`rule:BR-LEAD-002`(标识脱敏)、`rule:BR-LEAD-003`(跨店 403)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads`
|
||||
- **证据**:`anchored`
|
||||
|
||||
---
|
||||
|
||||
## Batch 2 录入动作
|
||||
|
||||
## Pet 相关
|
||||
|
||||
### action:list_pets
|
||||
|
||||
宠物列表:customer 查自己宠物(`ownerUserId=current.userId`);boss/staff 查本店服务过的宠物(`storeId=current.storeId`)。
|
||||
|
||||
- **入口**:`GET /api/pet/list` → `PetController#list`
|
||||
- **适用规则**:`rule:BR-PET-001`(跨用户/跨店)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java#list`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:create_pet
|
||||
|
||||
创建宠物档案:customer 强制 `ownerUserId=current.userId`;boss/staff 用 body.ownerUserId(代客建档)。
|
||||
|
||||
- **入口**:`POST /api/pet/create` → `PetController#create` → `PetService#create`
|
||||
- **适用规则**:`rule:BR-PET-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java#create`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:update_pet
|
||||
|
||||
更新宠物:`operatorUserId`/`role` 从上下文派生。
|
||||
|
||||
- **入口**:`PUT /api/pet/update` → `PetController#update` → `PetService#update`
|
||||
- **适用规则**:`rule:BR-PET-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java#update`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_pet
|
||||
|
||||
删除宠物:`operatorUserId`/`role` 从上下文派生。
|
||||
|
||||
- **入口**:`DELETE /api/pet/delete` → `PetController#delete` → `PetService#delete`
|
||||
- **适用规则**:`rule:BR-PET-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java#delete`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_pet_history
|
||||
|
||||
宠物服务时间线:宠主本人或本店员工/老板可查。
|
||||
|
||||
- **入口**:`GET /api/pet/history` → `PetController#history` → `PetService#getServiceHistory`
|
||||
- **适用规则**:`rule:BR-PET-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java#history`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## ServiceType 相关
|
||||
|
||||
### action:list_service_types
|
||||
|
||||
服务类型列表:公开接口,不传 storeId 返回系统默认;传 storeId 返回系统默认 + 该店自定义。
|
||||
|
||||
- **入口**:`GET /api/service-type/list` → `ServiceTypeController#list`(公开,在 AuthInterceptor allowlist)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java#list`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:create_service_type
|
||||
|
||||
新增服务类型:仅 boss;`storeId=current.storeId`。
|
||||
|
||||
- **入口**:`POST /api/service-type/create` → `ServiceTypeController#create` → `ServiceTypeService#create`
|
||||
- **适用规则**:`rule:BR-ST-001`(仅 boss + 同店)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java#create`
|
||||
- **测试**:`ServiceTypeControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:update_service_type
|
||||
|
||||
编辑服务类型:仅 boss;必须为本店自定义(系统默认 `storeId` 为空不可改、他店不可改)。
|
||||
|
||||
- **入口**:`PUT /api/service-type/update` → `ServiceTypeController#update`
|
||||
- **适用规则**:`rule:BR-ST-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java#update`
|
||||
- **测试**:`ServiceTypeControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_service_type
|
||||
|
||||
删除服务类型:仅 boss;必须为本店自定义(系统默认不可删、他店不可删)。
|
||||
|
||||
- **入口**:`DELETE /api/service-type/delete` → `ServiceTypeController#delete`
|
||||
- **适用规则**:`rule:BR-ST-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java#delete`
|
||||
- **测试**:`ServiceTypeControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:init_service_types
|
||||
|
||||
初始化系统默认服务类型:仅 boss/staff(启动期已自动初始化)。
|
||||
|
||||
- **入口**:`POST /api/service-type/init` → `ServiceTypeController#init`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java#init`
|
||||
- **测试**:`ServiceTypeControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Schedule 相关
|
||||
|
||||
### action:day_agenda
|
||||
|
||||
门店某日日程:仅 boss/staff;`storeId=current.storeId`。
|
||||
|
||||
- **入口**:`GET /api/schedule/day` → `ScheduleController#day` → `ScheduleService#dayAgenda`
|
||||
- **适用规则**:`rule:BR-SCH-001`(仅 boss/staff + 跨店 403)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ScheduleController.java#day`
|
||||
- **测试**:`ScheduleControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:create_schedule_block
|
||||
|
||||
手动占用半小时档:仅 boss/staff;`storeId=current.storeId`、`createdByUserId=current.userId`。
|
||||
|
||||
- **入口**:`POST /api/schedule/block` → `ScheduleController#createBlock` → `ScheduleService#createBlock`
|
||||
- **适用规则**:`rule:BR-SCH-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ScheduleController.java#createBlock`
|
||||
- **测试**:`ScheduleControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_schedule_block
|
||||
|
||||
取消手动占用:仅 boss/staff;service 层做同店校验(`operatorStoreId`)。
|
||||
|
||||
- **入口**:`DELETE /api/schedule/block` → `ScheduleController#deleteBlock` → `ScheduleService#deleteBlock`
|
||||
- **适用规则**:`rule:BR-SCH-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ScheduleController.java#deleteBlock`、`service/ScheduleService.java#deleteBlock`
|
||||
- **测试**:`ScheduleControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Upload / Highlight 相关
|
||||
|
||||
### action:upload_image
|
||||
|
||||
上传图片/视频:扩展名/MIME 校验 + 路径归一化防逃逸;需登录。
|
||||
|
||||
- **入口**:`POST /api/upload/image` → `FileController#uploadImage`
|
||||
- **适用规则**:`rule:BR-UPLOAD-001`(路径归一化)、`rule:BR-UPLOAD-002`(扩展名/MIME 校验)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#uploadImage`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_upload_image
|
||||
|
||||
读取上传资源:公开(在 AuthInterceptor allowlist `GET /api/upload/image/**`);路径归一化防逃逸。
|
||||
|
||||
- **入口**:`GET /api/upload/image/**` → `FileController#getImage`
|
||||
- **适用规则**:`rule:BR-UPLOAD-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#getImage`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:generate_highlight
|
||||
|
||||
触发服务回顾短片生成:仅 boss/staff;`operatorUserId`/`role` 从上下文派生;异步 FFmpeg 拼接。
|
||||
|
||||
- **入口**:`POST /api/report/highlight/start` → `ReportController#startHighlight` → `ReportHighlightVideoService#requestGenerate`
|
||||
- **适用规则**:`rule:BR-HL-001`(仅 boss/staff)、`rule:BR-HL-002`(三态 + composeMode)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#startHighlight`、`service/ReportHighlightVideoService.java#requestGenerate`
|
||||
- **文档**:`docs/洗美报告短视频成片方案.md`
|
||||
- **证据**:`anchored`(接口与上下文派生已实现;FFmpeg 集成测试 `gap`)
|
||||
|
||||
## User 管理相关
|
||||
|
||||
### action:create_staff
|
||||
|
||||
老板创建员工:仅 boss;`storeId=current.storeId`。
|
||||
|
||||
- **入口**:`POST /api/user/create-staff` → `UserController#createStaff` → `UserService#createStaff`
|
||||
- **适用规则**:`rule:BR-USER-001`(仅 boss)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#createStaff`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:list_staff
|
||||
|
||||
员工列表:仅 boss/staff;`storeId=current.storeId`。
|
||||
|
||||
- **入口**:`GET /api/user/staff-list` → `UserController#staffList`
|
||||
- **适用规则**:`rule:BR-USER-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#staffList`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:delete_staff
|
||||
|
||||
删除员工:仅 boss;目标员工必须属于本店。
|
||||
|
||||
- **入口**:`DELETE /api/user/staff` → `UserController#deleteStaff`
|
||||
- **适用规则**:`rule:BR-USER-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#deleteStaff`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:update_user
|
||||
|
||||
更新当前登录用户信息(头像/姓名/手机号):`id=current.userId`(覆盖请求体);手机号变更需 SMS 验证码(生产禁用)。
|
||||
|
||||
- **入口**:`PUT /api/user/update` → `UserController#updateUser` → `UserService#updateUser`
|
||||
- **适用规则**:`rule:BR-USER-002`(仅本人)、`rule:BR-AUTH-002`(生产手机号变更禁用)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#updateUser`
|
||||
- **证据**:`anchored`
|
||||
|
||||
---
|
||||
|
||||
## Batch 2.1 补录动作(audit_drift 发现的遗漏)
|
||||
|
||||
## 公开查询类(在 AuthInterceptor allowlist)
|
||||
|
||||
### action:list_stores
|
||||
|
||||
门店列表:公开接口,经 `toPublicStoreView` 裁剪(不含 ownerId/inviteCode)。
|
||||
|
||||
- **入口**:`GET /api/store/list` → `StoreController#list`(公开)
|
||||
- **适用规则**:`rule:BR-STORE-003`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#list`
|
||||
- **测试**:`StoreControllerTest#publicListHidesOwnerIdAndInviteCode`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_store
|
||||
|
||||
门店详情:公开接口,经 `toPublicStoreView` 裁剪(含 intro/营业时段,不含 ownerId/inviteCode/deleted)。
|
||||
|
||||
- **入口**:`GET /api/store/get` → `StoreController#get`(公开)
|
||||
- **适用规则**:`rule:BR-STORE-003`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#get`
|
||||
- **测试**:`StoreControllerTest#publicGetHidesOwnerIdAndInviteCode`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_available_slots
|
||||
|
||||
门店某日可预约时段:公开接口,半小时档,含已占用/已过/可约。
|
||||
|
||||
- **入口**:`GET /api/appointment/available-slots` → `AppointmentController#availableSlots`(公开)
|
||||
- **适用规则**:`rule:BR-APPT-004`(时段校验)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#availableSlots`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_report_suggestion
|
||||
|
||||
报告下次建议日期:公开接口,凭 token 查询(读取 `entity:service_interval` 推算)。
|
||||
|
||||
- **入口**:`GET /api/report/{token}/suggestion` → `ReportLeadController#suggestion`(公开)
|
||||
- **适用规则**:`rule:BR-INT-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#suggestion`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:submit_testimonial
|
||||
|
||||
宠主寄语提交:公开接口,凭 token 提交;同一报告保留一条,重复提交更新。
|
||||
|
||||
- **入口**:`POST /api/report/{token}/testimonial` → `ReportController#submitTestimonial`(公开)
|
||||
- **输出**:`entity:report_testimonial`
|
||||
- **适用规则**:`rule:BR-TST-001`
|
||||
- **触发事件**:`event:testimonial_saved`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#submitTestimonial`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:get_legacy_upload
|
||||
|
||||
兼容旧路径读取上传资源:公开接口,路径归一化防逃逸。
|
||||
|
||||
- **入口**:`GET /api/upload/legacy/**` → `FileController#getLegacyImage`(公开)
|
||||
- **适用规则**:`rule:BR-UPLOAD-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#getLegacyImage`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## 注册/短信类(公开)
|
||||
|
||||
### action:register_staff
|
||||
|
||||
员工注册(邀请码方式):公开接口,凭邀请码加入对应门店。
|
||||
|
||||
- **入口**:`POST /api/user/register-staff` → `UserController#registerStaff` → `UserService#registerStaff`(公开)
|
||||
- **输出**:`entity:user`(staff)、`entity:store`、`session_token`
|
||||
- **适用规则**:`rule:BR-AUTH-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#registerStaff`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:send_sms
|
||||
|
||||
发送短信验证码:公开接口;**生产禁用**(`universalSmsCode` 为空时返回 403 `SMS_LOGIN_DISABLED`)。
|
||||
|
||||
- **入口**:`POST /api/sms/send` → `SmsController#send`(公开)
|
||||
- **适用规则**:`rule:BR-AUTH-002`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/SmsController.java#send`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:register_store
|
||||
|
||||
店铺注册(旧入口):公开接口,创建 Store。**deprecated**:与 `action:register_boss` 重复,勿作为新集成入口。
|
||||
|
||||
- **入口**:`POST /api/store/register` → `StoreController#register` → `StoreService#create`
|
||||
- **输出**:`entity:store`
|
||||
- **状态**:`deprecated`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#register`
|
||||
- **证据**:`gap`(与 `register_boss` 重复,建议废弃或合并)
|
||||
|
||||
## 删除/列表类(protected)
|
||||
|
||||
### action:delete_appointment
|
||||
|
||||
删除预约:仅 boss/staff 且同店。
|
||||
|
||||
- **入口**:`DELETE /api/appointment/delete` → `AppointmentController#delete`
|
||||
- **适用规则**:`rule:BR-APPT-002`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#delete`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### action:list_reports
|
||||
|
||||
报告列表:boss/staff 查本店,customer 查自己相关报告;身份从上下文派生。
|
||||
|
||||
- **入口**:`GET /api/report/list` → `ReportController#list`
|
||||
- **适用规则**:`rule:BR-RPT-005`(跨店 403)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#list`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## 微信 OAuth(TODO demo,deprecated)
|
||||
|
||||
### action:wechat_authorize
|
||||
|
||||
获取微信授权跳转 URL:公开接口(TODO demo,真实微信登录走 `action:wx_phone_login`)。**deprecated**。
|
||||
|
||||
- **入口**:`GET /api/wechat/authorize` → `WechatController#getAuthorizeUrl`(公开)
|
||||
- **状态**:`deprecated`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/WechatController.java#getAuthorizeUrl`
|
||||
- **证据**:`gap`(TODO demo,未真实实现)
|
||||
|
||||
### action:wechat_callback
|
||||
|
||||
微信授权回调:公开接口(TODO demo,返回 `demo_openid_` + code,未真实换取 access_token)。**deprecated**。
|
||||
|
||||
- **入口**:`GET /api/wechat/callback` → `WechatController#callback`(公开)
|
||||
- **状态**:`deprecated`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/WechatController.java#callback`
|
||||
- **证据**:`gap`(TODO demo,未真实实现;真实微信登录走 `action:wx_phone_login`)
|
||||
|
||||
## 客户端埋点 / 会话生命周期
|
||||
|
||||
### action:track_video_play
|
||||
|
||||
成片点击播放埋点(前端客户端事件)。
|
||||
|
||||
- **入口**:frontend `reportView` / `videoPlayer`(client-emitted)
|
||||
- **触发事件**:`event:video_play`
|
||||
- **证据**:`documented`(schema 已冻结;上报与统计落地 `gap`)
|
||||
|
||||
### action:track_video_save
|
||||
|
||||
成片保存到相册埋点(前端客户端事件)。
|
||||
|
||||
- **入口**:frontend `reportView` / `videoPlayer`(client-emitted)
|
||||
- **触发事件**:`event:video_save`
|
||||
- **证据**:`documented`(schema 已冻结;上报与统计落地 `gap`)
|
||||
|
||||
### action:logout
|
||||
|
||||
退出登录 / 吊销会话:当前仅前端清本地 token,**无服务端吊销**。
|
||||
|
||||
- **入口**:—(未实现)
|
||||
- **触发事件**:`event:session_revoked`
|
||||
- **适用规则**:`rule:BR-AUTH-001`
|
||||
- **证据**:`gap`
|
||||
|
||||
### action:get_user_info
|
||||
|
||||
获取当前登录用户信息:`userId=current.userId`。
|
||||
|
||||
- **入口**:`GET /api/user/info` → `UserController#info`
|
||||
- **适用规则**:`rule:BR-USER-002`、`rule:BR-USER-003`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#info`
|
||||
- **证据**:`anchored`
|
||||
|
||||
---
|
||||
|
||||
## 门店 Web 后台(Phase A · documented)
|
||||
|
||||
> 产品:`docs/门店管理后台升级方案-对标宠老板.md`。优先复用现有 `/api/**`,不另造状态机。
|
||||
|
||||
### action:view_workbench
|
||||
|
||||
查看今日工作台(登录落点;可弹今日待办)。
|
||||
|
||||
- **入口**:Admin UI `/workbench`(planned)
|
||||
- **输出**:`entity:workbench`、`entity:workbench_todo_item`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-004`
|
||||
- **触发事件**:`event:workbench_viewed`(可选,gap)
|
||||
- **证据**:`documented`
|
||||
|
||||
### action:drilldown_workbench_todo
|
||||
|
||||
工作台待办卡片下钻到对应列表并带筛选。
|
||||
|
||||
- **入口**:Admin UI workbench todo click(planned)
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`
|
||||
- **证据**:`documented`
|
||||
|
||||
### action:admin_list_appointments
|
||||
|
||||
后台预约列表(增强筛选;规则复用 Appointment*)。
|
||||
|
||||
- **入口**:Admin UI `/appointments`(planned);数据复用 `GET /api/appointment/list`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-APPT-001`、`rule:BR-APPT-002`
|
||||
- **证据**:`documented`
|
||||
|
||||
### action:admin_list_reports
|
||||
|
||||
后台报告列表(成片三态、复制公开链接、触发重生成)。
|
||||
|
||||
- **入口**:Admin UI `/reports`(planned);复用 `GET /api/report/list` + `action:generate_highlight`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-003`、`rule:BR-RPT-005`
|
||||
- **证据**:`documented`
|
||||
|
||||
### action:admin_list_leads
|
||||
|
||||
后台回访线索列表。
|
||||
|
||||
- **入口**:Admin UI `/leads`(planned);复用 `GET /api/report/leads`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-LEAD-002`、`rule:BR-LEAD-003`
|
||||
- **证据**:`documented`
|
||||
|
||||
### action:admin_list_service_customers
|
||||
|
||||
后台服务客户列表(瘦身主档;姓名/手机/宠物名搜索)。
|
||||
|
||||
- **入口**:`GET /api/admin/service-customers` → `AdminServiceCustomerController#listServiceCustomers` → Admin UI `/customers`
|
||||
- **输出**:`entity:service_customer`
|
||||
- **适用规则**:`rule:BR-ADMIN-001` … `BR-ADMIN-004`
|
||||
- **证据**:`anchored`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AdminServiceCustomerController.java`、`AdminServiceCustomerService`
|
||||
- **产品决策**:新建聚合 API,不以前端拼装为正式方案;不做余额/次卡列、持卡会员导入
|
||||
- **文档**:`docs/门店管理后台-PhaseA-页面清单PRD.md §7.6`
|
||||
|
||||
### action:admin_update_settings
|
||||
|
||||
后台门店设置(资料、营业时段、服务类型瘦身表单、员工)。
|
||||
|
||||
- **入口**:`GET /api/admin/store` → `AdminStoreController#currentStore`;Admin UI `/settings`;写资料 `PUT /api/store/update`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-003`、`rule:BR-STORE-001`、`rule:BR-ST-001`、`rule:BR-USER-001`
|
||||
- **证据**:`anchored`
|
||||
- **代码**:`AdminStoreController`、`StoreController#update`;服务类型/员工复用既有 API
|
||||
|
||||
### action:admin_manage_schedule
|
||||
|
||||
后台排班与手动占用(日视图)。
|
||||
|
||||
- **入口**:Admin UI `/schedule`;`GET /api/schedule/day`;`POST/DELETE /api/schedule/block`
|
||||
- **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-SCH-001`
|
||||
- **证据**:`anchored`
|
||||
251
ontology/events.md
Normal file
@ -0,0 +1,251 @@
|
||||
# Events(事件)
|
||||
|
||||
> 动作或状态变更后发生的事实(过去时)。用于埋点、状态机迁移、领域事件。
|
||||
|
||||
## Store 相关
|
||||
|
||||
### event:store_registered
|
||||
|
||||
老板入驻成功,`Store` + `boss` `User` 创建完成,`session_token` 签发。
|
||||
|
||||
- **触发动作**:`action:register_boss`
|
||||
- **payload**:`storeId`、`bossUserId`、`inviteCode`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## User / 鉴权相关
|
||||
|
||||
### event:session_issued
|
||||
|
||||
登录/注册成功后签发 HMAC session token。
|
||||
|
||||
- **触发动作**:`action:register_boss`、`action:login`、`action:wx_phone_login`、`register_staff`
|
||||
- **payload**:`userId`、`storeId`、`role`、`exp`
|
||||
- **代码**:`backend/src/main/java/com/petstore/auth/SessionTokenService.java#issue`
|
||||
- **测试**:`SessionTokenServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:wx_bound
|
||||
|
||||
微信 openid/unionid 绑定到用户。
|
||||
|
||||
- **触发动作**:`action:bind_wechat`(`action:wx_phone_login` 内)
|
||||
- **payload**:`userId`、`openid`、`unionid?`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#bindWechatMiniIdentity`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Appointment 相关
|
||||
|
||||
### event:appointment_created
|
||||
|
||||
预约创建成功(`status=new`,下单即占号生效)。
|
||||
|
||||
- **触发动作**:`action:create_appointment`
|
||||
- **payload**:`appointmentId`、`storeId`、`userId`、`appointmentTime`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:appointment_status_changed
|
||||
|
||||
预约状态迁移(`new→doing`、`new→cancel`、`doing→done`、`doing→cancel`)。
|
||||
|
||||
- **触发动作**:`action:start_service`(`new→doing`)、`action:transition_appointment_status`、`action:submit_report`(`doing→done`)
|
||||
- **payload**:`appointmentId`、`fromStatus`、`toStatus`、`assignedUserId?`
|
||||
- **适用规则**:`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)
|
||||
- **测试**:`AppointmentServiceTest`、`ReportServiceTest#createDoingAppointmentMarksDone`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Report 相关
|
||||
|
||||
### event:report_submitted
|
||||
|
||||
报告提交成功,关联预约置 `done`。
|
||||
|
||||
- **触发动作**:`action:submit_report`
|
||||
- **payload**:`reportId`、`appointmentId`、`storeId`、`reportToken`
|
||||
- **适用规则**:`rule:BR-RPT-001`、`rule:BR-RPT-002`、`rule:BR-RPT-004`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportService.java#create`
|
||||
- **测试**:`ReportServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:report_opened
|
||||
|
||||
报告页首次打开(本地 storage 标记首次)。
|
||||
|
||||
- **触发动作**:`action:track_report_open`
|
||||
- **payload**:`reportTokenHash`(SHA-256 前 8 位)、`isFirstOpen`、`source`、`ts`
|
||||
- **适用规则**:`rule:BR-RPT-007`(token hash 日志)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#trackReportOpen`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`anchored`(日志埋点已实现;统计落地 `documented`)
|
||||
|
||||
### event:report_reopened
|
||||
|
||||
报告页再次打开(非首次)。
|
||||
|
||||
- **触发动作**:`action:track_report_open`
|
||||
- **payload**:同 `event:report_opened`,`isFirstOpen=false`
|
||||
- **证据**:`documented`(schema 已冻结,前端 `isFirstOpen` 判断已实现)
|
||||
|
||||
## ReportLead 相关
|
||||
|
||||
### event:lead_submitted
|
||||
|
||||
宠主留资提交成功(含重复提交幂等更新)。
|
||||
|
||||
- **触发动作**:`action:submit_lead`
|
||||
- **payload**:`reportTokenHash`、`storeId`、`hasPhone`、`hasPetName`、`repeatSubmit`、`ts`
|
||||
- **适用规则**:`rule:BR-LEAD-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`documented`(埋点 schema 已冻结,事件上报与统计落地 `gap`)
|
||||
|
||||
---
|
||||
|
||||
## Batch 2 录入事件
|
||||
|
||||
## Highlight 相关
|
||||
|
||||
### event:highlight_succeeded
|
||||
|
||||
成片生成成功(`highlight_video_status` 置 `done`)。
|
||||
|
||||
- **触发动作**:`action:generate_highlight`(异步回调)
|
||||
- **payload**:`reportTokenHash`、`appointmentId`、`durationSec`、`composeMode`、`ts`
|
||||
- **适用规则**:`rule:BR-HL-002`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportHighlightVideoService.java`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`documented`(schema 已冻结;事件上报与统计落地 `gap`)
|
||||
|
||||
### event:highlight_failed
|
||||
|
||||
成片生成失败(`highlight_video_status` 置 `failed`,`highlight_fail_reason` 归类)。
|
||||
|
||||
- **触发动作**:`action:generate_highlight`(异步回调)
|
||||
- **payload**:`reportTokenHash`、`appointmentId`、`failReason`(material/service/network/unknown)、`ts`
|
||||
- **适用规则**:`rule:BR-HL-002`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportHighlightVideoService.java`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`documented`
|
||||
|
||||
### event:video_play
|
||||
|
||||
报告页成片点击播放。
|
||||
|
||||
- **触发动作**:`action:track_video_play`(前端客户端)
|
||||
- **payload**:`reportTokenHash`、`ts`、`source`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`documented`(schema 已冻结;上报未实现 `gap`)
|
||||
|
||||
### event:video_save
|
||||
|
||||
报告页成片保存到相册。
|
||||
|
||||
- **触发动作**:`action:track_video_save`(前端客户端)
|
||||
- **payload**:`reportTokenHash`、`ts`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md 埋点事件 schema`
|
||||
- **证据**:`documented`(schema 已冻结;上报未实现 `gap`)
|
||||
|
||||
## Upload 相关
|
||||
|
||||
### event:upload_completed
|
||||
|
||||
上传图片/视频完成。
|
||||
|
||||
- **触发动作**:`action:upload_image`
|
||||
- **payload**:`url`、`mediaType`、`size`、`ts`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#uploadImage`
|
||||
- **证据**:`implemented`(接口返回 url,事件未单独上报)
|
||||
|
||||
## Schedule 相关
|
||||
|
||||
### event:schedule_block_created
|
||||
|
||||
手动占用时段创建成功。
|
||||
|
||||
- **触发动作**:`action:create_schedule_block`
|
||||
- **payload**:`blockId`、`storeId`、`slotStart`、`blockType`、`createdByUserId`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ScheduleService.java#createBlock`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:schedule_block_deleted
|
||||
|
||||
手动占用时段取消。
|
||||
|
||||
- **触发动作**:`action:delete_schedule_block`
|
||||
- **payload**:`blockId`、`storeId`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ScheduleService.java#deleteBlock`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## ServiceType / Pet / Staff 相关
|
||||
|
||||
### event:service_type_created
|
||||
|
||||
服务类型创建(仅门店自定义)。
|
||||
|
||||
- **触发动作**:`action:create_service_type`
|
||||
- **payload**:`serviceTypeId`、`storeId`、`name`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ServiceTypeService.java#create`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:service_type_deleted
|
||||
|
||||
服务类型删除(仅门店自定义)。
|
||||
|
||||
- **触发动作**:`action:delete_service_type`
|
||||
- **payload**:`serviceTypeId`、`storeId`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ServiceTypeService.java#delete`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:pet_created
|
||||
|
||||
宠物档案创建。
|
||||
|
||||
- **触发动作**:`action:create_pet`
|
||||
- **payload**:`petId`、`ownerUserId`、`name`、`petType`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/PetService.java#create`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:staff_created
|
||||
|
||||
老板创建员工。
|
||||
|
||||
- **触发动作**:`action:create_staff`
|
||||
- **payload**:`staffId`、`storeId`、`name`、`phone`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#createStaff`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:user_updated
|
||||
|
||||
当前登录用户信息更新(头像/姓名/手机号)。
|
||||
|
||||
- **触发动作**:`action:update_user`
|
||||
- **payload**:`userId`、`updatedFields`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#updateUser`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### event:testimonial_saved
|
||||
|
||||
宠主寄语已保存(一报告一寄语,重复提交为更新)。
|
||||
|
||||
- **触发动作**:`action:submit_testimonial`
|
||||
- **payload**:`reportTokenHash`、`testimonialId`、`isPublic`、`ts`
|
||||
- **适用规则**:`rule:BR-TST-001`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportTestimonialService.java`
|
||||
- **证据**:`documented`(写库已实现;独立事件上报/统计未落地)
|
||||
|
||||
### event:session_revoked
|
||||
|
||||
会话吊销(服务端 logout)。
|
||||
|
||||
- **触发动作**:`action:logout`
|
||||
- **payload**:`userId`、`ts`
|
||||
- **证据**:`gap`(当前无服务端吊销实现)
|
||||
|
||||
### event:workbench_viewed
|
||||
|
||||
门店后台工作台已打开(可选埋点)。
|
||||
|
||||
- **触发动作**:`action:view_workbench`
|
||||
- **payload**:`storeId`、`userId`、`ts`
|
||||
- **证据**:`gap`(A 期可不实现上报)
|
||||
412
ontology/objects.md
Normal file
@ -0,0 +1,412 @@
|
||||
# Objects(实体)
|
||||
|
||||
> 持久名词 + 字段 + 归属系统。本批覆盖 P0 核心闭环的 5 个实体。
|
||||
|
||||
## entity:store
|
||||
|
||||
**门店**。宠物店主体,所有业务数据的归属范围。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR | 店铺名称 |
|
||||
| logo | VARCHAR | 店铺 Logo |
|
||||
| phone | VARCHAR | 联系电话 |
|
||||
| address | VARCHAR | 地址 |
|
||||
| latitude | Double | 纬度(公开报告页不返回) |
|
||||
| longitude | Double | 经度(公开报告页不返回) |
|
||||
| intro | TEXT | 简介 |
|
||||
| owner_id | BIGINT | 老板用户 ID |
|
||||
| invite_code | VARCHAR(8) | 员工邀请码 |
|
||||
| booking_day_start | LocalTime | 预约首号 |
|
||||
| booking_last_slot_start | LocalTime | 预约末号 |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Backend Core(`StoreController` / `StoreService` / `StoreMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Store.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_store`
|
||||
- **测试**:`StoreControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:user
|
||||
|
||||
**用户**。三角色:`boss`(老板)/ `staff`(员工)/ `customer`(宠主)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| username | VARCHAR | 用户名 |
|
||||
| password | VARCHAR | 密码(`@JsonIgnore` 不返回;登录成功 `setPassword(null)`) |
|
||||
| name | VARCHAR | 姓名/技师名 |
|
||||
| phone | VARCHAR | 手机号 |
|
||||
| avatar | VARCHAR | 头像 |
|
||||
| store_id | BIGINT | 所属店铺(customer 可空) |
|
||||
| role | VARCHAR | boss / staff / customer |
|
||||
| wechat_openid | VARCHAR(64) | 微信小程序 openid(`@JsonIgnore`,仅服务端使用) |
|
||||
| wechat_unionid | VARCHAR(64) | 微信 unionid(`@JsonIgnore`) |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Backend Core(`UserController` / `UserService` / `UserMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/User.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_user`、`docs/.agents/common-context.md 术语规则`
|
||||
- **测试**:`UserServiceLoginStrategyTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:appointment
|
||||
|
||||
**预约**。状态机:`new`(待开始)→ `doing`(服务中)→ `done`(已完成)/ `cancel`(已取消)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| pet_name | VARCHAR | 宠物名称 |
|
||||
| pet_type | VARCHAR | 宠物类型(猫/狗/其他) |
|
||||
| service_type | VARCHAR | 服务类型 |
|
||||
| appointment_time | DATETIME | 预约时间(半小时档,按营业时间/占用/已过校验) |
|
||||
| status | VARCHAR | new / doing / done / cancel |
|
||||
| store_id | BIGINT | 所属门店 |
|
||||
| user_id | BIGINT | 创建人 ID(谁建的) |
|
||||
| assigned_user_id | BIGINT | 技师 ID(开始服务时赋值,谁点谁服务) |
|
||||
| pet_id | BIGINT | 宠物档案 ID |
|
||||
| remark | TEXT | 备注 |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Backend Core(`AppointmentController` / `AppointmentService` / `AppointmentMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Appointment.java`、`backend/src/main/java/com/petstore/service/AppointmentService.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_appointment §5.2 预约管理`
|
||||
- **测试**:`AppointmentServiceTest`、`AppointmentControllerDetailTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:report
|
||||
|
||||
**服务报告**。一约一份,提交后锁死;必须绑定 `appointment_id`。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| appointment_id | BIGINT | 关联预约 ID(唯一约束 `uk_report_appointment`) |
|
||||
| remark | TEXT | 备注 |
|
||||
| user_id | BIGINT | 技师 ID |
|
||||
| store_id | BIGINT | 所属门店 |
|
||||
| report_token | VARCHAR | 公开访问令牌(UUID,不带回包给公开页) |
|
||||
| pet_name | VARCHAR | 宠物名(冗余) |
|
||||
| service_type | VARCHAR | 服务类型(冗余) |
|
||||
| appointment_time | DATETIME | 服务时间(冗余) |
|
||||
| staff_name | VARCHAR | 技师名(冗余) |
|
||||
| highlight_video_url | VARCHAR(500) | 成片 MP4 相对 URL |
|
||||
| highlight_video_status | VARCHAR(32) | processing / done / failed |
|
||||
| highlight_video_error | VARCHAR(500) | 错误信息 |
|
||||
| highlight_duration_sec | INT | 成片时长 |
|
||||
| highlight_compose_mode | VARCHAR(16) | preset / interleave |
|
||||
| highlight_fail_reason | VARCHAR(16) | material / service / network / unknown |
|
||||
| highlight_share_cover_url | VARCHAR(500) | 成片首帧封面 |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportController` / `ReportService` / `ReportMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Report.java`、`backend/src/main/java/com/petstore/service/ReportService.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_report §5.3 服务报告`、`docs/API-报告成片字段契约.md`
|
||||
- **测试**:`ReportServiceTest`、`ReportControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:report_lead
|
||||
|
||||
**留资线索**。宠主在公开报告页提交手机号,进入门店回访池。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| report_id | BIGINT | 关联报告 ID |
|
||||
| store_id | BIGINT | 所属门店(用于回访池范围) |
|
||||
| phone | VARCHAR | 手机号 |
|
||||
| pet_name | VARCHAR | 宠物名(冗余) |
|
||||
| service_type | VARCHAR | 服务类型(冗余) |
|
||||
| wechat_openid | VARCHAR(64) | 微信 openid(脱敏返回 `wechatOpenidMasked`) |
|
||||
| wechat_unionid | VARCHAR(64) | 微信 unionid(脱敏返回 `wechatUnionidMasked`) |
|
||||
| reminder_type | VARCHAR | 提醒类型 |
|
||||
| remind_date | DATE | 下次建议日期 |
|
||||
| remind_status | VARCHAR | pending / sent / canceled / unsubscribed |
|
||||
| unsubscribe_token | VARCHAR | 退订令牌 |
|
||||
| client_ip | VARCHAR | 留资 IP(合规留证) |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportLeadController` / `ReportLeadService`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java`、`backend/src/main/java/com/petstore/entity/ReportLead.java`
|
||||
- **文档**:`docs/本期主线-下一步产品优化点.md §C 留资`
|
||||
- **测试**:暂无(Batch D 未补 `ReportLeadControllerTest`,列为 `documented` gap)
|
||||
- **证据**:`anchored`(脱敏与跨店校验已实现并测试覆盖;去重逻辑 `documented`)
|
||||
|
||||
---
|
||||
|
||||
## Batch 2 录入实体
|
||||
|
||||
## entity:pet
|
||||
|
||||
**宠物档案**。归属某客户(`ownerUserId`);商家/员工通过预约关联查看本店服务过的宠物。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| name | VARCHAR | 昵称 |
|
||||
| pet_type | VARCHAR | 猫/狗/其他 |
|
||||
| breed | VARCHAR | 品种(可选) |
|
||||
| avatar | VARCHAR | 头像相对路径 |
|
||||
| owner_user_id | BIGINT | 所属用户(客户) |
|
||||
| remark | TEXT | 备注 |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Backend Core(`PetController` / `PetService` / `PetMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Pet.java`、`controller/PetController.java`、`service/PetService.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_pet(隐含,预约含 pet_name/pet_type)`
|
||||
- **测试**:暂无 `PetControllerTest`(`gap`,建议补)
|
||||
- **证据**:`anchored`(实体与跨用户/跨店校验已实现;测试覆盖为 `gap`)
|
||||
|
||||
## entity:service_type
|
||||
|
||||
**服务类型**。`storeId` 为空表示系统默认(不可改删);非空为门店自定义。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| store_id | BIGINT | 门店 ID(NULL=系统默认) |
|
||||
| name | VARCHAR | 服务名称 |
|
||||
| create_time | DATETIME | 创建时间 |
|
||||
|
||||
- **归属系统**:Backend Core(`ServiceTypeController` / `ServiceTypeService` / `ServiceTypeMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/ServiceType.java`、`controller/ServiceTypeController.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_service_type §5.2 服务类型`
|
||||
- **测试**:`ServiceTypeControllerTest`(12 用例)
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:schedule_block
|
||||
|
||||
**门店日程手动占用**。与线上预约一样占用半小时档,用于到店客(`walk_in`)或暂停预约(`blocked`)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| store_id | BIGINT | 所属门店(非空) |
|
||||
| slot_start | DATETIME | 半小时档起始时刻 |
|
||||
| block_type | VARCHAR(32) | walk_in / blocked |
|
||||
| note | VARCHAR(500) | 备注 |
|
||||
| created_by_user_id | BIGINT | 创建人(从上下文派生) |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
| deleted | TINYINT(1) | 软删 |
|
||||
|
||||
- **归属系统**:Backend Core(`ScheduleController` / `ScheduleService` / `ScheduleBlockMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/ScheduleBlock.java`、`controller/ScheduleController.java`、`service/ScheduleService.java`
|
||||
- **文档**:`docs/产品设计文档.md §5.2(隐含,预约时段占用)`
|
||||
- **测试**:`ScheduleControllerTest`(7 用例)
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:report_image
|
||||
|
||||
**报告媒体**。`photo_type` 分组:`before`(服务前)/ `after`(服务后)/ `during`(服务过程中)。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| report_id | BIGINT | 关联报告 ID |
|
||||
| photo_url | VARCHAR(500) | 媒体 URL |
|
||||
| photo_type | VARCHAR(20) | before / after / during |
|
||||
| media_type | VARCHAR(20) | photo / video(默认 photo) |
|
||||
| sort_order | INT | 同类型内排序 |
|
||||
| create_time | DATETIME | 创建时间 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportImageMapper`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/ReportImage.java`、`mapper/ReportImageMapper.java`
|
||||
- **文档**:`docs/产品设计文档.md §三 t_report_image`
|
||||
- **测试**:间接(`ReportServiceTest#createWithImagesSavesEachImage`)
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:highlight_video
|
||||
|
||||
**服务回顾短片**。当前作为 `entity:report` 的 `highlight_*` 字段承载,非独立 JPA 实体;状态机 `processing` / `done` / `failed`。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| report_id | BIGINT | 关联报告(即 Report.id) |
|
||||
| highlight_video_url | VARCHAR(500) | 成片 MP4 相对 URL |
|
||||
| highlight_video_status | VARCHAR(32) | processing / done / failed |
|
||||
| highlight_video_error | VARCHAR(500) | 错误信息 |
|
||||
| highlight_duration_sec | INT | 成片时长 |
|
||||
| highlight_compose_mode | VARCHAR(16) | preset / interleave |
|
||||
| highlight_fail_reason | VARCHAR(16) | material / service / network / unknown |
|
||||
| highlight_share_cover_url | VARCHAR(500) | 成片首帧封面 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportHighlightVideoService`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Report.java`(字段承载)、`service/ReportHighlightVideoService.java`
|
||||
- **文档**:`docs/API-报告成片字段契约.md`、`docs/洗美报告短视频成片方案.md`
|
||||
- **测试**:暂无独立测试(`gap`,建议补成片三态用例)
|
||||
- **证据**:`anchored`(字段与三态已实现;FFmpeg 集成测试为 `gap`)
|
||||
|
||||
## entity:session_token
|
||||
|
||||
**HMAC session token**。非 JPA 实体,鉴权上下文数据结构;token 格式 `base64url(payload).base64url(sig)`,HMAC-SHA256,默认 7 天。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| userId | Long | 用户 ID |
|
||||
| storeId | Long | 门店 ID(customer 可空) |
|
||||
| role | String | boss / staff / customer |
|
||||
| exp | long | Unix 秒过期时间 |
|
||||
|
||||
- **归属系统**:Backend Core(`SessionTokenService` / `CurrentUser` / `CurrentUserContext` / `AuthInterceptor`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/auth/SessionTokenService.java`、`CurrentUser.java`、`CurrentUserContext.java`、`AuthInterceptor.java`
|
||||
- **文档**:`docs/ontology/rules.md#rule:BR-AUTH-001`
|
||||
- **测试**:`SessionTokenServiceTest`(7 用例)
|
||||
- **证据**:`anchored`
|
||||
|
||||
---
|
||||
|
||||
## Batch 2.1 补录实体(audit_drift 发现的遗漏)
|
||||
|
||||
## entity:report_testimonial
|
||||
|
||||
**宠主寄语**。生成分享海报时填写的一句话(口碑素材);同一报告保留一条,重复提交更新内容与时间。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| report_id | BIGINT | 关联报告(唯一约束,一报告一寄语) |
|
||||
| store_id | BIGINT | 所属门店 |
|
||||
| content | VARCHAR(200) | 寄语内容 |
|
||||
| is_public | TINYINT(1) | 是否公开(默认 1) |
|
||||
| create_time / update_time | DATETIME | 时间戳 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportTestimonialService`)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/ReportTestimonial.java`、`service/ReportTestimonialService.java`
|
||||
- **入口**:`POST /api/report/{token}/testimonial` → `ReportController#submitTestimonial`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## entity:service_interval
|
||||
|
||||
**服务建议周期配置**。用于「下次该洗了」的日期预测;`store_id` 为 null 表示系统默认,老板可针对自家店覆盖。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | BIGINT | 主键 |
|
||||
| store_id | BIGINT | 门店 ID(NULL=系统默认) |
|
||||
| service_name | VARCHAR(64) | 与 `t_service_type.name` 对齐作为匹配键 |
|
||||
| pet_type | VARCHAR(16) | 按宠物类型细分(dog/cat/other),NULL=全部 |
|
||||
| interval_days_min | INT | 建议最短间隔(天) |
|
||||
| interval_days_max | INT | 建议最长间隔(天) |
|
||||
| create_time | DATETIME | 创建时间 |
|
||||
|
||||
- **归属系统**:Report Media Backend(`ReportLeadService#suggestNext` 读取)
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/ServiceInterval.java`
|
||||
- **证据**:`anchored`(实体存在;独立 controller 接口 `gap`,目前由 `ReportLeadService` 内部读取)
|
||||
|
||||
## entity:highlight_fail_reason
|
||||
|
||||
**成片失败原因归类**(非 JPA 枚举)。对宠主展示口径,不含技术堆栈。
|
||||
|
||||
| 枚举值 | code | 说明 |
|
||||
|------|------|------|
|
||||
| MATERIAL | material | 素材缺失/无法读取/编码失败 |
|
||||
| SERVICE | service | 合成服务异常(ffmpeg/磁盘等) |
|
||||
| NETWORK | network | 网络类异常(预留) |
|
||||
| UNKNOWN | unknown | 未归类 |
|
||||
|
||||
- **归属系统**:Report Media Backend
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/HighlightFailReason.java`
|
||||
- **证据**:`anchored`(`jpa: false`)
|
||||
|
||||
## entity:current_user
|
||||
|
||||
**当前登录用户上下文**(非 JPA record)。由 `AuthInterceptor` 从 `entity:session_token` 解析后写入 ThreadLocal,与令牌实体区分。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| userId | Long | 用户 ID |
|
||||
| storeId | Long | 所属门店(customer 可为 null) |
|
||||
| role | String | boss / staff / customer |
|
||||
|
||||
- **归属系统**:Backend Core(auth 包)
|
||||
- **代码**:`backend/src/main/java/com/petstore/auth/CurrentUser.java`、`CurrentUserContext.java`、`AuthInterceptor.java`
|
||||
- **证据**:`anchored`(`jpa: false`)
|
||||
|
||||
---
|
||||
|
||||
## Batch:门店 Web 后台预录入(Phase A · documented)
|
||||
|
||||
> 产品依据:`docs/门店管理后台升级方案-对标宠老板.md`。
|
||||
> **尚未编码**;设计与实现须引用下列 ID。收银/会员资产仍见下方 gap 占位。
|
||||
|
||||
## entity:admin_console
|
||||
|
||||
**门店 Web 管理后台**(非 JPA)。与 uni-app 小程序并存的桌面优先管理端;登录落点为工作台,不做收银/库存首页。
|
||||
|
||||
| 字段(概念) | 说明 |
|
||||
|------|------|
|
||||
| surface | Web admin |
|
||||
| navModules | workbench / appointments / reports / customers / leads / schedule / settings |
|
||||
| storeScope | 单店 `storeId` |
|
||||
| roleGate | Phase A:boss / staff 均可读写本店;customer 禁止 |
|
||||
|
||||
- **归属系统**:Store Admin FE + Backend Core
|
||||
- **证据**:`documented`(`jpa: false`)
|
||||
- **约束**:`rule:BR-ADMIN-001` … `BR-ADMIN-004`
|
||||
|
||||
## entity:workbench
|
||||
|
||||
**今日工作台**(非 JPA 聚合视图)。指标卡 + 今日待办 + 报告漏斗摘要;数据来自 appointment/report/lead/highlight,**无独立表**。
|
||||
|
||||
| 字段(概念) | 说明 |
|
||||
|------|------|
|
||||
| todayAppointmentCount / todayDoneCount | 今日预约 / 今日完成 |
|
||||
| pendingLeadCount / highlightFailedCount | 待回访 / 成片失败 |
|
||||
| todoItems | `entity:workbench_todo_item[]` |
|
||||
| reportFunnel | 已发/已打开/已留资/报告转预约(缺埋点时可显示 —) |
|
||||
| anomalyList | 成片失败或待回访最近 N 条 |
|
||||
|
||||
- **证据**:`documented`
|
||||
- **动作**:`action:view_workbench`
|
||||
|
||||
## entity:workbench_todo_item
|
||||
|
||||
**工作台待办项**。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| kind | `today_appointments` / `pending_report` / `highlight_anomaly` / `pending_lead` |
|
||||
| count | 数量 |
|
||||
| drilldownAction | 下钻到的 admin list action |
|
||||
| filter | 预置筛选 |
|
||||
|
||||
- **证据**:`documented`
|
||||
|
||||
## entity:service_customer
|
||||
|
||||
**服务客户主档(瘦身)**。服务经营视角的客户投影(User + Pet + 预约/报告/留资),**不是** `entity:membership`。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| userId / displayName / phoneMasked | 身份(手机默认脱敏) |
|
||||
| pets | 关联宠物摘要 |
|
||||
| lastVisitAt / lastReportId | 最近到店 / 最近报告 |
|
||||
| leadStatus / source | 留资状态;来源:预约/报告留资 |
|
||||
| storeId | 本店范围 |
|
||||
|
||||
- **不做**:余额、押金、积分、次卡、会员等级、批量导入持卡会员
|
||||
- **证据**:`documented`(可先无独立表,查询聚合)
|
||||
- **动作**:`action:admin_list_service_customers`
|
||||
|
||||
---
|
||||
|
||||
## 待录入(P1/P2 方向,gap 证据)
|
||||
|
||||
以下已在 `graph/ontology.jsonl` 以 `evidence=gap` + `jpa=false` 占位,**不展开字段**;**Phase A 门店后台明确不做**(`rule:BR-ADMIN-004`):
|
||||
|
||||
- `entity:membership`(会员卡)— P1/P2 / C 期
|
||||
- `entity:stored_value`(储值)— P1/P2
|
||||
- `entity:service_package`(套餐/次卡)— P1/P2
|
||||
- `entity:report_history`(宠主历史报告汇总)— P2
|
||||
- `entity:analytics_dashboard`(经营分析看板)— B 期;A 期仅工作台轻量指标
|
||||
|
||||
后续如进入实现,再补完整字段、动作与规则。
|
||||
160
ontology/relations.md
Normal file
@ -0,0 +1,160 @@
|
||||
# Relations(关系)
|
||||
|
||||
> 实体/动作/事件/规则之间的边。ER 关系 + 动作-事件 + 规则-动作适用。
|
||||
|
||||
## ER 关系(实体间)
|
||||
|
||||
| 关系 | 基数 | 说明 |
|
||||
|------|------|------|
|
||||
| `Store` 1—N `User` | 一个门店有多个用户(boss 1 + staff N + customer 不挂门店) | `User.store_id` → `Store.id` |
|
||||
| `User` N—1 `Store` | boss/staff 归属一个门店;customer 可无门店 | — |
|
||||
| `Store` 1—N `Appointment` | 一个门店有多个预约 | `Appointment.store_id` → `Store.id` |
|
||||
| `User` 1—N `Appointment` | 一个用户创建多个预约(`user_id`);技师服务多个预约(`assigned_user_id`) | `Appointment.user_id` / `assigned_user_id` → `User.id` |
|
||||
| `Appointment` 1—1 `Report` | 一约一份报告(`uk_report_appointment` 唯一约束) | `Report.appointment_id` → `Appointment.id` |
|
||||
| `Report` N—1 `Store` | 报告归属门店 | `Report.store_id` → `Store.id` |
|
||||
| `Report` N—1 `User` | 报告技师 | `Report.user_id` → `User.id` |
|
||||
| `Report` 1—N `ReportImage` | 报告含多张照片(before/after/during) | `ReportImage.report_id` → `Report.id` |
|
||||
| `Report` 1—N `ReportLead` | 一份报告可多次留资(同手机号去重) | `ReportLead.report_id` → `Report.id` |
|
||||
| `ReportLead` N—1 `Store` | 留资归属门店(回访池范围) | `ReportLead.store_id` → `Store.id` |
|
||||
|
||||
## 动作—事件(触发)
|
||||
|
||||
| 动作 | 触发事件 |
|
||||
|------|----------|
|
||||
| `action:register_boss` | `event:store_registered`、`event:session_issued` |
|
||||
| `action:login` | `event:session_issued` |
|
||||
| `action:wx_phone_login` | `event:session_issued`、`event:wx_bound`(若 loginCode 有) |
|
||||
| `action:create_appointment` | `event:appointment_created` |
|
||||
| `action:start_service` | `event:appointment_status_changed`(new→doing) |
|
||||
| `action:transition_appointment_status` | `event:appointment_status_changed` |
|
||||
| `action:submit_report` | `event:report_submitted`、`event:appointment_status_changed`(doing→done) |
|
||||
| `action:track_report_open` | `event:report_opened` / `event:report_reopened` |
|
||||
| `action:submit_lead` | `event:lead_submitted` |
|
||||
|
||||
## 规则—动作(适用)
|
||||
|
||||
| 规则 | 适用动作 |
|
||||
|------|----------|
|
||||
| `rule:BR-AUTH-001` | 所有 protected 接口 |
|
||||
| `rule:BR-AUTH-002` | `action:login`、`action:wx_phone_login` |
|
||||
| `rule:BR-STORE-001` | `action:update_store`、`action:delete_store` |
|
||||
| `rule:BR-STORE-002` | `action:get_store_by_invite_code` |
|
||||
| `rule:BR-APPT-001` | `action:start_service`、`action:transition_appointment_status`、`action:submit_report` |
|
||||
| `rule:BR-APPT-002` | `action:list_appointments`、`action:start_service`、`action:transition_appointment_status`、`action:get_appointment_detail`、`delete` |
|
||||
| `rule:BR-APPT-003` | `action:get_appointment_detail`、`action:transition_appointment_status`(仅 cancel) |
|
||||
| `rule:BR-APPT-004` | `action:create_appointment` |
|
||||
| `rule:BR-RPT-001` | `action:submit_report` |
|
||||
| `rule:BR-RPT-002` | `action:submit_report` |
|
||||
| `rule:BR-RPT-003` | `action:submit_report` |
|
||||
| `rule:BR-RPT-004` | `action:submit_report` |
|
||||
| `rule:BR-RPT-005` | `action:submit_report`、`action:delete_report` |
|
||||
| `rule:BR-RPT-006` | `action:get_report_by_token` |
|
||||
| `rule:BR-RPT-007` | `action:track_report_open` |
|
||||
| `rule:BR-LEAD-001` | `action:submit_lead` |
|
||||
| `rule:BR-LEAD-002` | `action:list_leads` |
|
||||
| `rule:BR-LEAD-003` | `action:list_leads` |
|
||||
|
||||
## 实体—归属系统
|
||||
|
||||
| 实体 | 归属系统(Agent 写入锁) |
|
||||
|------|--------------------------|
|
||||
| `entity:store` | Backend Core |
|
||||
| `entity:user` | Backend Core |
|
||||
| `entity:appointment` | Backend Core |
|
||||
| `entity:report` | Report Media Backend |
|
||||
| `entity:report_lead` | Report Media Backend |
|
||||
|
||||
---
|
||||
|
||||
## Batch 2 录入关系
|
||||
|
||||
## ER 关系(实体间,续)
|
||||
|
||||
| 关系 | 基数 | 说明 |
|
||||
|------|------|------|
|
||||
| `User` 1—N `Pet` | 一个客户有多个宠物 | `Pet.owner_user_id` → `User.id` |
|
||||
| `Pet` 1—N `Appointment` | 一只宠物可多次预约 | `Appointment.pet_id` → `Pet.id`(可空) |
|
||||
| `Store` 1—N `ServiceType` | 一个门店有多个自定义服务类型(`store_id` 为空=系统默认) | `ServiceType.store_id` → `Store.id` |
|
||||
| `Store` 1—N `ScheduleBlock` | 一个门店有多个排班占用 | `ScheduleBlock.store_id` → `Store.id` |
|
||||
| `User` 1—N `ScheduleBlock` | 一个用户创建多个排班占用 | `ScheduleBlock.created_by_user_id` → `User.id` |
|
||||
| `Report` 1—1 `HighlightVideo` | 一份报告对应一个成片(当前作为 `Report.highlight_*` 字段承载) | — |
|
||||
| `User` 1—N `SessionToken` | 一个用户可有多个签发的 session token(每次登录/注册签发) | token payload 含 `userId` |
|
||||
|
||||
## 动作—事件(触发,续)
|
||||
|
||||
| 动作 | 触发事件 |
|
||||
|------|----------|
|
||||
| `action:generate_highlight` | `event:highlight_succeeded` / `event:highlight_failed`(异步) |
|
||||
| `action:upload_image` | `event:upload_completed` |
|
||||
| `action:create_schedule_block` | `event:schedule_block_created` |
|
||||
| `action:delete_schedule_block` | `event:schedule_block_deleted` |
|
||||
| `action:create_service_type` | `event:service_type_created` |
|
||||
| `action:delete_service_type` | `event:service_type_deleted` |
|
||||
| `action:create_pet` | `event:pet_created` |
|
||||
| `action:create_staff` | `event:staff_created` |
|
||||
| `action:update_user` | `event:user_updated` |
|
||||
| `action:bind_wechat` | `event:wx_bound`(已在 Batch 1 录入) |
|
||||
| `action:submit_testimonial` | `event:testimonial_saved` |
|
||||
| `action:track_video_play` | `event:video_play` |
|
||||
| `action:track_video_save` | `event:video_save` |
|
||||
| `action:logout` | `event:session_revoked`(gap) |
|
||||
|
||||
## 规则—动作(适用,续)
|
||||
|
||||
| 规则 | 适用动作 |
|
||||
|------|----------|
|
||||
| `rule:BR-UPLOAD-001` | `action:upload_image`、`action:get_upload_image` |
|
||||
| `rule:BR-UPLOAD-002` | `action:upload_image` |
|
||||
| `rule:BR-HL-001` | `action:generate_highlight` |
|
||||
| `rule:BR-HL-002` | `action:generate_highlight` |
|
||||
| `rule:BR-SCH-001` | `action:day_agenda`、`action:create_schedule_block`、`action:delete_schedule_block` |
|
||||
| `rule:BR-ST-001` | `action:create_service_type`、`action:update_service_type`、`action:delete_service_type`、`action:init_service_types` |
|
||||
| `rule:BR-PET-001` | `action:list_pets`、`action:create_pet`、`action:update_pet`、`action:delete_pet`、`action:get_pet_history` |
|
||||
| `rule:BR-USER-001` | `action:create_staff`、`action:list_staff`、`action:delete_staff` |
|
||||
| `rule:BR-USER-002` | `action:update_user`、`action:get_user_info` |
|
||||
| `rule:BR-USER-003` | 返回 `User` 的登录/注册/信息类动作 |
|
||||
| `rule:BR-STORE-003` | `action:get_store`、`action:list_stores` |
|
||||
| `rule:BR-TST-001` | `action:submit_testimonial` |
|
||||
| `rule:BR-UNSUB-001` | `action:unsubscribe` |
|
||||
| `rule:BR-INT-001` | `action:get_report_suggestion` |
|
||||
| `rule:BR-CONFIG-001` | —(全局约束) |
|
||||
| `rule:BR-CONFIG-002` | —(全局约束) |
|
||||
|
||||
## 实体—归属系统(续)
|
||||
|
||||
| 实体 | 归属系统(Agent 写入锁) |
|
||||
|------|--------------------------|
|
||||
| `entity:pet` | Backend Core |
|
||||
| `entity:service_type` | Backend Core |
|
||||
| `entity:schedule_block` | Backend Core |
|
||||
| `entity:report_image` | Report Media Backend |
|
||||
| `entity:highlight_video` | Report Media Backend |
|
||||
| `entity:session_token` | Backend Core(auth 包) |
|
||||
| `entity:report_testimonial` | Report Media Backend |
|
||||
| `entity:service_interval` | Report Media Backend |
|
||||
| `entity:highlight_fail_reason` | Report Media Backend(枚举) |
|
||||
| `entity:current_user` | Backend Core(auth 包) |
|
||||
| `entity:admin_console` / `workbench` / `workbench_todo_item` / `service_customer` | Store Admin FE(planned)+ Backend Core |
|
||||
| P1/P2 gap 占位 | 未实现(membership / stored_value / service_package / report_history / analytics_dashboard);Phase A 后台禁止偷渡 |
|
||||
|
||||
## 其他关系(Batch 补全)
|
||||
|
||||
| 关系 | 说明 |
|
||||
|------|------|
|
||||
| `entity:highlight_video` HAS `entity:highlight_fail_reason` | 失败态归类 |
|
||||
| `entity:session_token` RESOLVES_TO `entity:current_user` | 鉴权解析 |
|
||||
|
||||
## 门店后台关系(Phase A · documented)
|
||||
|
||||
| 关系 | 说明 |
|
||||
|------|------|
|
||||
| `admin_console` HAS `workbench` | 登录默认工作台 |
|
||||
| `workbench` HAS `workbench_todo_item` | 今日待办 |
|
||||
| `workbench` AGGREGATES `appointment` / `report` / `report_lead` | 指标与异常来源 |
|
||||
| `service_customer` PROJECTS `user` | 客户投影 |
|
||||
| `service_customer` HAS `pet` | 一人多宠 |
|
||||
| `view_workbench` EMITS `workbench_viewed` | 可选埋点 |
|
||||
| `BR-ADMIN-001/002` APPLIES_TO `view_workbench` 等 | 角色与店范围 |
|
||||
| `BR-ADMIN-004` CONSTRAINS `admin_console` | Phase A 范围闸门 |
|
||||
| admin list actions READS 对应实体 | 列表读模型 |
|
||||
| `drilldown_workbench_todo` ROUTES_TO `admin_list_*` | 待办下钻 |
|
||||
366
ontology/rules.md
Normal file
@ -0,0 +1,366 @@
|
||||
# Rules(规则 / 不变量)
|
||||
|
||||
> 业务约束、决策条件、权限边界。每条规则标注适用动作、落地代码、测试。
|
||||
|
||||
## 鉴权
|
||||
|
||||
### rule:BR-AUTH-001
|
||||
|
||||
**HMAC session token 鉴权**:protected `/api/**` 要求 `Authorization: Bearer <token>`;token 格式 `base64url(payload).base64url(sig)`,HMAC-SHA256,默认 7 天有效期;public allowlist 见 `AuthInterceptor.PUBLIC_PATTERNS`。
|
||||
|
||||
- **适用动作**:所有 protected 接口
|
||||
- **代码**:`backend/src/main/java/com/petstore/auth/AuthInterceptor.java`、`SessionTokenService.java`、`CurrentUserContext.java`
|
||||
- **测试**:`SessionTokenServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-AUTH-002
|
||||
|
||||
**生产登录策略**:生产只走微信授权(`/api/user/wx-phone-login`);SMS 验证码登录 dev-only(`universalSmsCode` 为空 = 生产禁用);`SmsController#send` 生产返回 403 `SMS_LOGIN_DISABLED`。
|
||||
|
||||
- **适用动作**:`action:login`、`action:wx_phone_login`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/UserService.java#login`、`SmsController.java`
|
||||
- **测试**:`UserServiceLoginStrategyTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Store
|
||||
|
||||
### rule:BR-STORE-001
|
||||
|
||||
**店铺更新/删除仅 boss + 同店**:`update`/`delete` 仅 `isBoss() && current.storeId != null`;`store.id` 必须等于 `current.storeId`。
|
||||
|
||||
- **适用动作**:`action:update_store`、`action:delete_store`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#update`/`#delete`
|
||||
- **测试**:`StoreControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-STORE-002
|
||||
|
||||
**邀请码查询仅 boss/staff + 同店**:按 code 查到的 store.id 必须等于 `current.storeId`;新员工注册预览走公开 `/api/user/register-staff`(请求体含 inviteCode)。
|
||||
|
||||
- **适用动作**:`action:get_store_by_invite_code`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#getByInviteCode`
|
||||
- **测试**:`StoreControllerTest#inviteCodeCrossStoreForbidden`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-STORE-003
|
||||
|
||||
**公开门店字段裁剪**:公开接口 `GET /api/store/get`、`GET /api/store/list` 仅返回预约/展示所需字段(id/name/address/坐标/phone/logo/intro/营业时段),**不得**返回 `ownerId`、`inviteCode`、`deleted` 等内部字段(与 `rule:BR-RPT-006` 对称)。
|
||||
|
||||
- **适用动作**:`action:get_store`、`action:list_stores`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/StoreController.java#toPublicStoreView`
|
||||
- **测试**:`StoreControllerTest#publicGetHidesOwnerIdAndInviteCode`、`#publicListHidesOwnerIdAndInviteCode`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Appointment
|
||||
|
||||
### rule:BR-APPT-001
|
||||
|
||||
**预约状态机**:仅允许 `new→doing`(`startService`)、`new→cancel`、`doing→done`(报告提交触发)、`doing→cancel`(若产品允许);`done`/`cancel` 为终态不可回退;`→doing` 必须走 `/appointment/start`;非法迁移返回 `INVALID_STATUS` / `CANCEL_NOT_ALLOWED` / `USE_START_ENDPOINT`。
|
||||
|
||||
- **适用动作**:`action:start_service`、`action:transition_appointment_status`、`action:submit_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/AppointmentService.java#startService`/`#transitionStatus`
|
||||
- **测试**:`AppointmentServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-APPT-002
|
||||
|
||||
**预约跨店 403**:boss/staff 操作预约(list/start/status/detail/delete)仅限本店 `storeId == current.storeId`,跨店返回 `FORBIDDEN`。
|
||||
|
||||
- **适用动作**:`action:list_appointments`、`action:start_service`、`action:transition_appointment_status`、`action:get_appointment_detail`、`delete`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java`
|
||||
- **测试**:`AppointmentControllerDetailTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-APPT-003
|
||||
|
||||
**预约跨用户 403**:customer 仅可查看/取消自己的预约(`userId == current.userId`),不可操作他人预约。
|
||||
|
||||
- **适用动作**:`action:get_appointment_detail`、`action:transition_appointment_status`(仅 cancel)
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/AppointmentController.java#detail`/`#updateStatus`
|
||||
- **测试**:`AppointmentControllerDetailTest#customerCannotViewOthersAppointment`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-APPT-004
|
||||
|
||||
**预约时段校验**:半小时档,按门店营业时间(`booking_day_start`~`booking_last_slot_start`)、已占用档、已过时间校验;同一门店同一时间档最多一单。
|
||||
|
||||
- **适用动作**:`action:create_appointment`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/AppointmentService.java#createBooking`
|
||||
- **文档**:`docs/产品设计文档.md §5.2`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Report
|
||||
|
||||
### rule:BR-RPT-001
|
||||
|
||||
**报告必须绑定预约**:`appointmentId` 为空返回 `APPOINTMENT_REQUIRED`;无预约报告不作为 P0 主路径。
|
||||
|
||||
- **适用动作**:`action:submit_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#create`、`ReportService.java#create`
|
||||
- **测试**:`ReportControllerTest#createMissingAppointmentIdReturnsBizCode`、`ReportServiceTest#createWithNullAppointmentIdThrows`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-RPT-002
|
||||
|
||||
**一约一份报告**:`Report.appointment_id` 唯一约束 `uk_report_appointment`;服务层 `existsByAppointmentIdAndDeletedFalse` 重复提交抛 `IllegalStateException`,Controller 转 409 `REPORT_ALREADY_EXISTS`。
|
||||
|
||||
- **适用动作**:`action:submit_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Report.java`、`mapper/ReportMapper.java`、`service/ReportService.java#create`、`controller/ReportController.java#create`
|
||||
- **测试**:`ReportServiceTest#createDuplicateReportThrows`、`ReportControllerTest#createDuplicateReportReturnsAlreadyExists`
|
||||
- **证据**:`anchored`(上线前需清理历史重复数据,见 `backend/README.md` 数据库迁移)
|
||||
|
||||
### rule:BR-RPT-003
|
||||
|
||||
**报告前后照片必填**:服务前/服务后至少各 1 张有效照片(`mediaType` 非 video);缺 before 返回 `BEFORE_PHOTO_REQUIRED`,缺 after 返回 `AFTER_PHOTO_REQUIRED`;服务过程中素材可选。
|
||||
|
||||
- **适用动作**:`action:submit_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#hasPhotoType`、`frontend/src/pages/report/Report.vue#submitReport`
|
||||
- **测试**:`ReportControllerTest#createMissingBeforePhotoReturnsBizCode`/`createMissingAfterPhotoReturnsBizCode`
|
||||
- **文档**:`docs/产品设计文档.md §5.3 报告提交必填项`
|
||||
- **证据**:`anchored`(前后端双校验)
|
||||
|
||||
### rule:BR-RPT-004
|
||||
|
||||
**报告仅 doing 预约可提交**:关联预约状态非 `doing` 返回 `INVALID_STATUS`「请先开始服务后再提交报告」;提交后 `doing→done`。
|
||||
|
||||
- **适用动作**:`action:submit_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#create`、`service/ReportService.java#create`
|
||||
- **测试**:`ReportServiceTest#createDoingAppointmentMarksDone`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-RPT-005
|
||||
|
||||
**报告跨店 403**:仅 boss/staff 可创建/删除报告;报告所属门店必须等于 `current.storeId`,跨店返回 `FORBIDDEN`。
|
||||
|
||||
- **适用动作**:`action:submit_report`、`action:delete_report`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#create`/`#delete`
|
||||
- **测试**:`ReportControllerTest#createCrossStoreReturnsForbidden`、`createNonStaffReturnsForbidden`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-RPT-006
|
||||
|
||||
**公开报告字段裁剪**:`GET /api/report/get?token=`(publicTokenRequest)不返回 top-level `userId`/`storeId`/`reportToken`;`store` 对象内可返回 `id`(用于预约跳转)+ `name`/`logo`/`phone`/`address`,不返回 `latitude`/`longitude`;非 public(appointmentId 查询)保留全部字段供门店端使用。
|
||||
|
||||
- **适用动作**:`action:get_report_by_token`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#getByAppointmentId`
|
||||
- **测试**:`ReportControllerTest#publicTokenQueryHidesInternalFields`
|
||||
- **前端**:`frontend/src/pages/report-view/reportView.vue`(`reminderToken` 只读路由 token,`bookingStoreId` 只读 `store.id`)
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-RPT-007
|
||||
|
||||
**token hash 日志**:报告打开埋点日志只记 `tokenHash`(SHA-256 前 8 位 hex),不输出 token 前缀。
|
||||
|
||||
- **适用动作**:`action:track_report_open`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#shortTokenHash`/`#trackReportOpen`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## ReportLead
|
||||
|
||||
### rule:BR-LEAD-001
|
||||
|
||||
**留资去重**:同一 `report_token` + 同一手机号二次提交幂等更新,不重复插库,返回 `repeatSubmit=true`。
|
||||
|
||||
- **适用动作**:`action:submit_lead`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportLeadService.java#submit`
|
||||
- **证据**:`documented`(逻辑已实现,未补测试用例)
|
||||
|
||||
### rule:BR-LEAD-002
|
||||
|
||||
**留资标识脱敏**:`/api/report/leads` 不返回完整 `wechatOpenid`/`wechatUnionid`,只返回 `wechatBound`(boolean)+ `wechatOpenidMasked` + `wechatUnionidMasked`(首4+****+末4,过短返回 `****`)。
|
||||
|
||||
- **适用动作**:`action:list_leads`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#maskIdentifier`/`#leads`
|
||||
- **前端**:`frontend/src/pages/mine/Leads.vue`(只渲染 `wechatBound`/`wechatOpenidMasked`)
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-LEAD-003
|
||||
|
||||
**回访池跨店 403**:`/api/report/leads` 仅 boss/staff,`storeId` 从 `CurrentUserContext` 派生,忽略 query `storeId`。
|
||||
|
||||
- **适用动作**:`action:list_leads`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#leads`
|
||||
- **证据**:`anchored`
|
||||
|
||||
---
|
||||
|
||||
## Batch 2 录入规则
|
||||
|
||||
## Upload
|
||||
|
||||
### rule:BR-UPLOAD-001
|
||||
|
||||
**上传路径归一化**:`FileController` 用 `Paths.get(uploadPath).toAbsolutePath().normalize()`;`resolveSafePath` 反斜杠统一、去前导斜杠、`normalize` 后校验 `target.startsWith(root)`,逃逸返回 400。
|
||||
|
||||
- **适用动作**:`action:upload_image`、`action:get_upload_image`、`getLegacyImage`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#resolveSafePath`/`#uploadRoot`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-UPLOAD-002
|
||||
|
||||
**上传扩展名/MIME 校验**:原文件有扩展名必须在图片/视频白名单内,否则拒绝;无扩展名时 MIME 必须是 `image/*`/`video/*`/`application/octet-stream`/空(小程序兼容),否则拒绝。
|
||||
|
||||
- **适用动作**:`action:upload_image`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/FileController.java#isAllowedMediaType`/`#hasMediaExtension`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Highlight Video
|
||||
|
||||
### rule:BR-HL-001
|
||||
|
||||
**成片仅 boss/staff 可触发**:`operatorUserId`/`role` 从 `CurrentUserContext` 派生,不信任请求体;customer 触发返回 403。
|
||||
|
||||
- **适用动作**:`action:generate_highlight`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportController.java#startHighlight`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-HL-002
|
||||
|
||||
**成片三态 + composeMode**:`highlight_video_status` 枚举 `processing`/`done`/`failed`;`composeMode` 枚举 `preset`/`interleave`;失败原因归类 `material`/`service`/`network`/`unknown`(对宠主展示归类中文,不暴露堆栈);时长随素材变化(配置级上限 `HIGHLIGHT_MAX_DURATION_SEC`)。
|
||||
|
||||
- **适用动作**:`action:generate_highlight`
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/Report.java`(`highlight_*` 字段)、`service/ReportHighlightVideoService.java`
|
||||
- **文档**:`docs/洗美报告短视频成片方案.md`、`docs/API-报告成片字段契约.md`
|
||||
- **证据**:`anchored`(字段与状态已实现;FFmpeg 集成测试 `gap`)
|
||||
|
||||
## Schedule
|
||||
|
||||
### rule:BR-SCH-001
|
||||
|
||||
**排班仅 boss/staff + 跨店 403**:`day`/`block`(create/delete) 仅 `isStoreUser()`;`storeId=current.storeId`(忽略请求体);`deleteBlock` 经 service `operatorStoreId` 校验同店。
|
||||
|
||||
- **适用动作**:`action:day_agenda`、`action:create_schedule_block`、`action:delete_schedule_block`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ScheduleController.java`、`service/ScheduleService.java#deleteBlock`
|
||||
- **测试**:`ScheduleControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## ServiceType
|
||||
|
||||
### rule:BR-ST-001
|
||||
|
||||
**服务类型仅 boss + 同店 + 系统默认不可改删**:`create`/`update`/`delete` 仅 `isBoss()`;`storeId=current.storeId`;`update`/`delete` 时 `existing.storeId` 必须非空且等于 `current.storeId`(系统默认 `storeId=null` 不可改删,他店不可改删);`init` 仅 boss/staff。
|
||||
|
||||
- **适用动作**:`action:create_service_type`、`action:update_service_type`、`action:delete_service_type`、`action:init_service_types`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ServiceTypeController.java`
|
||||
- **测试**:`ServiceTypeControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Pet
|
||||
|
||||
### rule:BR-PET-001
|
||||
|
||||
**宠物跨用户/跨店**:customer `list`/`create` 强制 `ownerUserId=current.userId`;boss/staff `list` 查本店服务过的宠物(`storeId=current.storeId`);`update`/`delete`/`history` 的 `operatorUserId`/`role` 从上下文派生。
|
||||
|
||||
- **适用动作**:`action:list_pets`、`action:create_pet`、`action:update_pet`、`action:delete_pet`、`action:get_pet_history`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/PetController.java`
|
||||
- **测试**:`PetControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## User 管理
|
||||
|
||||
### rule:BR-USER-001
|
||||
|
||||
**员工管理仅 boss + 同店**:`create_staff`/`list_staff`/`delete_staff` 仅 `isBoss()`;`storeId=current.storeId`;`delete_staff` 校验目标员工属于本店。
|
||||
|
||||
- **适用动作**:`action:create_staff`、`action:list_staff`、`action:delete_staff`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-USER-002
|
||||
|
||||
**用户更新仅本人**:`update_user` 的 `id=current.userId`(覆盖请求体);手机号变更需 SMS 验证码(生产 `rule:BR-AUTH-002` 禁用)。
|
||||
|
||||
- **适用动作**:`action:update_user`、`action:get_user_info`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/UserController.java#updateUser`/`#info`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-USER-003
|
||||
|
||||
**User 敏感字段不序列化**:`password`、`wechatOpenid`、`wechatUnionid` 必须 `@JsonIgnore`,禁止随 API JSON 返回。
|
||||
|
||||
- **适用动作**:`action:login`、`action:wx_phone_login`、`action:get_user_info`、`action:update_user`、`action:register_boss`、`action:register_staff`、`action:list_staff`
|
||||
- **代码**:`backend/src/main/java/com/petstore/entity/User.java`
|
||||
- **测试**:`UserJsonIgnoreTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## Config
|
||||
|
||||
### rule:BR-CONFIG-001
|
||||
|
||||
**生产配置环境变量**:`application.yml` 的 `datasource.url/username/password`、`wechat.appid/appsecret`、`auth.session-secret`、`upload.path` 等敏感项全部改为 `${ENV_VAR:默认}`;不在源码写真实密钥。
|
||||
|
||||
- **适用动作**:—(全局约束)
|
||||
- **代码**:`backend/src/main/resources/application.yml`、`application-example.yml`
|
||||
- **文档**:`backend/README.md 环境变量表`
|
||||
- **证据**:`anchored`(已暴露凭据需运维轮换)
|
||||
|
||||
### rule:BR-CONFIG-002
|
||||
|
||||
**production profile**:`--spring.profiles.active=production` 时 `ddl-auto=validate`(不改表)、`show-sql=false`、`SMS_UNIVERSAL_CODE` 默认空(关闭万能验证码 `rule:BR-AUTH-002`)。
|
||||
|
||||
- **适用动作**:—(全局约束)
|
||||
- **代码**:`backend/src/main/resources/application.yml`(multi-document production profile)
|
||||
- **文档**:`backend/README.md production profile`
|
||||
- **证据**:`anchored`(配置已加;production 实测启动 `gap`)
|
||||
|
||||
## 寄语 / 退订 / 建议周期
|
||||
|
||||
### rule:BR-TST-001
|
||||
|
||||
**一报告一寄语**:同一 `report_id` 仅保留一条寄语;重复提交更新 `content`/`is_public`/`update_time`,不新增行。
|
||||
|
||||
- **适用动作**:`action:submit_testimonial`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportTestimonialService.java`
|
||||
- **测试**:`ReportTestimonialServiceTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-UNSUB-001
|
||||
|
||||
**凭 unsubscribeToken 一键退订**:公开接口,令牌无效返回明确错误;成功后 `remind_status=unsubscribed`。
|
||||
|
||||
- **适用动作**:`action:unsubscribe`
|
||||
- **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#unsubscribe`
|
||||
- **测试**:`ReportLeadControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
### rule:BR-INT-001
|
||||
|
||||
**服务建议周期推算**:公开 `suggestion` 按报告服务类型 + 宠物类型读取 `entity:service_interval`(门店覆盖优先于系统默认)推算下次建议日期。
|
||||
|
||||
- **适用动作**:`action:get_report_suggestion`
|
||||
- **代码**:`backend/src/main/java/com/petstore/service/ReportLeadService.java#suggestNext`
|
||||
- **测试**:`ReportLeadControllerTest`
|
||||
- **证据**:`anchored`
|
||||
|
||||
## 门店 Web 后台(Phase A · documented)
|
||||
|
||||
### rule:BR-ADMIN-001
|
||||
|
||||
**门店后台角色门禁**:`entity:admin_console` 仅 `boss` / `staff` 可进且 **均可编辑本店数据**;`customer` 禁止。删店、创建/删除员工等仍遵循既有仅 boss 规则(`rule:BR-STORE-001`、`rule:BR-USER-001`)。
|
||||
|
||||
- **适用动作**:`action:view_workbench`、`action:admin_list_*`、`action:admin_update_settings`、`action:admin_manage_schedule`
|
||||
- **产品决策**:2026-07-10(staff 可编辑,非只读)
|
||||
- **文档**:`docs/门店管理后台-PhaseA-页面清单PRD.md`
|
||||
- **证据**:`documented`
|
||||
|
||||
### rule:BR-ADMIN-002
|
||||
|
||||
**后台数据仅本店**:所有后台查询/写强制 `current.storeId`;忽略跨店参数;连锁规则同步不在 Phase A。
|
||||
|
||||
- **适用动作**:同上
|
||||
- **证据**:`documented`
|
||||
|
||||
### rule:BR-ADMIN-003
|
||||
|
||||
**后台可看内部字段**:经鉴权后可返回公开 API 裁剪掉的字段(如 inviteCode、报告内部字段);与 `rule:BR-STORE-003` / `rule:BR-RPT-006` 对称。手机号默认仍脱敏,明文权限预留。
|
||||
|
||||
- **适用动作**:`action:admin_list_reports`、`action:admin_update_settings`、`action:admin_list_service_customers`
|
||||
- **证据**:`documented`
|
||||
|
||||
### rule:BR-ADMIN-004
|
||||
|
||||
**Phase A 后台范围闸门**:禁止将 `membership` / 储值 / 次卡套餐 / 收银 / 库存 / 寄养 / 押金 / 点餐 / 支付宝渠道 写入 Phase A 后台范围或设计稿;上述保持 `evidence=gap`。
|
||||
|
||||
- **适用**:`entity:admin_console`、`entity:workbench` 及一切 admin Phase A 动作
|
||||
- **文档**:`docs/门店管理后台升级方案-对标宠老板.md`
|
||||
- **证据**:`documented`
|
||||
92
qa-reports/scripts/admin-phase-a-api-smoke.sh
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase A 门店后台 API 冒烟(需 backend :8080 + MySQL)
|
||||
# 用法:
|
||||
# export MYSQL_PASSWORD=... # 先启动 backend
|
||||
# export BOSS_PHONE=13800000001
|
||||
# export SMS_CODE=123456
|
||||
# bash docs/qa-reports/scripts/admin-phase-a-api-smoke.sh
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${API_BASE:-http://127.0.0.1:8080/api}"
|
||||
PHONE="${BOSS_PHONE:-}"
|
||||
CODE="${SMS_CODE:-123456}"
|
||||
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
step() { printf '\n== %s ==\n' "$*"; }
|
||||
|
||||
need() {
|
||||
if [[ -z "${1:-}" ]]; then
|
||||
red "缺少 $2"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
need "$PHONE" "BOSS_PHONE(老板/员工手机号)"
|
||||
|
||||
json_get() {
|
||||
python3 -c 'import json,sys; d=json.load(sys.stdin); print(d'"$1"')'
|
||||
}
|
||||
|
||||
step "0) 健康:公开门店列表"
|
||||
curl -sf "$BASE/store/list" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200; print("ok stores", len(d.get("data") or []))'
|
||||
|
||||
step "1) 登录"
|
||||
LOGIN=$(curl -sf -X POST "$BASE/user/login" -H 'Content-Type: application/json' \
|
||||
-d "{\"phone\":\"$PHONE\",\"code\":\"$CODE\"}")
|
||||
echo "$LOGIN" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; print("role", d["data"]["user"]["role"])'
|
||||
TOKEN=$(echo "$LOGIN" | json_get '["data"]["sessionToken"]')
|
||||
ROLE=$(echo "$LOGIN" | json_get '["data"]["user"]["role"]')
|
||||
AUTH="Authorization: Bearer $TOKEN"
|
||||
|
||||
if [[ "$ROLE" != "boss" && "$ROLE" != "staff" ]]; then
|
||||
red "需要 boss/staff,当前 role=$ROLE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
auth_get() { curl -sf -H "$AUTH" "$@"; }
|
||||
auth_post() { curl -sf -H "$AUTH" -H 'Content-Type: application/json' -X POST "$@"; }
|
||||
|
||||
step "2) customer 门禁:用 customer 应 403(若有)— 跳过;校验 admin store"
|
||||
auth_get "$BASE/admin/store" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; assert "inviteCode" in d["data"]; print("store", d["data"].get("name"), "invite", d["data"].get("inviteCode"))'
|
||||
|
||||
step "3) 预约列表(含 admin enrichment)"
|
||||
auth_get "$BASE/appointment/list?page=1&pageSize=20" | python3 -c '
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
assert d.get("code")==200, d
|
||||
rows=d.get("data") or []
|
||||
print("appointments", len(rows))
|
||||
if rows:
|
||||
r=rows[0]
|
||||
for k in ("hasReport","customerPhoneMasked","staffName"):
|
||||
assert k in r, f"missing {k} in {r.keys()}"
|
||||
print("enrichment ok")
|
||||
'
|
||||
|
||||
step "4) 服务客户聚合"
|
||||
auth_get "$BASE/admin/service-customers?page=1&pageSize=20" | python3 -c '
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
assert d.get("code")==200, d
|
||||
data=d["data"]
|
||||
assert "summary" in data and "list" in data
|
||||
s=data["summary"]
|
||||
for k in ("customerCount","pendingLeadCount","monthVisitCount"):
|
||||
assert k in s
|
||||
assert "totalBalance" not in s
|
||||
print("customers", data.get("total"), "summary", s)
|
||||
'
|
||||
|
||||
step "5) 线索 / 报告 / 排班"
|
||||
TODAY=$(date +%F)
|
||||
auth_get "$BASE/report/leads?status=pending" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; rows=d.get("data") or [];
|
||||
assert all("wechatOpenid" not in r for r in rows); print("leads", len(rows))'
|
||||
auth_get "$BASE/report/list?page=1&pageSize=20" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; rows=d.get("data") or [];
|
||||
assert not rows or "updateTime" in rows[0]; print("reports", len(rows))'
|
||||
auth_get "$BASE/schedule/day?date=$TODAY" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; print("schedule rows", len((d.get("data") or {}).get("rows") or []))'
|
||||
|
||||
step "6) 员工列表"
|
||||
auth_get "$BASE/user/staff-list" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d.get("code")==200, d; print("staff", len(d.get("data") or []))'
|
||||
|
||||
green "API smoke PASS"
|
||||
86
qa-reports/门店后台-PhaseA-冒烟清单.md
Normal file
@ -0,0 +1,86 @@
|
||||
# 门店 Web 后台 · Phase A 冒烟清单
|
||||
|
||||
> Owner:Core Flow QA(主)+ Report Share QA(报告段)
|
||||
> 日期:2026-07-10
|
||||
> 环境:`admin` http://localhost:5174 + backend :8080
|
||||
> 账号:boss / staff(dev 验证码默认 `123456`)
|
||||
> 验收报告:`docs/qa-reports/门店后台-PhaseA-验收报告-2026-07-10.md`
|
||||
|
||||
## 前置
|
||||
|
||||
- [x] 自动化:`admin` build / `mvn test` / ontology drift=0(2026-07-10)
|
||||
- [x] backend 已起:本机 :8080 + 测试服 MySQL(2026-07-10)
|
||||
- [x] `cd admin && npm run dev` → http://127.0.0.1:5174
|
||||
- [x] API 冒烟脚本 PASS(`13800138001` / `123456`)
|
||||
- [ ] `.env.development` 中 `VITE_PUBLIC_H5_ORIGIN` 指向宠主 H5(本地默认 `http://localhost:5173`)— UI 复制链接时核对
|
||||
|
||||
## A0 登录壳
|
||||
|
||||
- [x] 静态:customer 登录后拒绝进壳(LoginView「请使用老板账号登录」)
|
||||
- [x] 联机 API:customer 调 `/api/admin/store` → 403
|
||||
- [ ] 联机 UI:boss / staff 可进,默认落点 `/workbench`(请本地打开确认)
|
||||
- [ ] 联机 UI:顶栏显示店名与角色;退出后需重新登录
|
||||
|
||||
## A1 工作台
|
||||
|
||||
- [x] 静态:四指标下钻参数、待办弹层、「今日不再提醒」
|
||||
- [x] 静态:成片失败含 failed + processing 超 30 分钟
|
||||
- [x] 静态:侧栏无收银/库存/会员
|
||||
- [ ] 联机:数字与列表一致
|
||||
|
||||
## A2 预约
|
||||
|
||||
- [x] 静态:状态/日期/技师/待出报告筛选;详情抽屉;开始服务/取消
|
||||
- [x] 静态:列表 enrichment(宠主脱敏、hasReport)
|
||||
- [ ] 联机:staff/boss 开始服务 `new→doing`
|
||||
- [ ] 联机:跨店数据不可见
|
||||
|
||||
## A6 排班
|
||||
|
||||
- [x] 静态:日视图 + 新增/删除占用
|
||||
- [ ] 联机:与小程序号源一致
|
||||
|
||||
## A3 报告
|
||||
|
||||
- [x] 静态:三态/超时文案、详情、复制链接、重新生成
|
||||
- [ ] 联机:复制链接打开 H5 可用
|
||||
|
||||
## A4 线索
|
||||
|
||||
- [x] 静态:pending/all、到期≤今天、打开报告;无完整 openid 字段契约在 API
|
||||
- [ ] 联机:打开关联报告
|
||||
|
||||
## A5 客户
|
||||
|
||||
- [x] 静态:聚合 API、搜索、无余额汇总、详情抽屉
|
||||
- [ ] 联机:列表与汇总数字
|
||||
|
||||
## A7 设置
|
||||
|
||||
- [x] 静态:admin/store 邀请码、boss 保存、服务类型/员工管理
|
||||
- [ ] 联机:改营业时段后小程序号源变化
|
||||
|
||||
## 回归命令
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/admin && npm run build
|
||||
cd /Users/apple/_src/petstore/backend && mvn -q test
|
||||
python3 /Users/apple/_src/petstore/docs/graph/validate_ontology.py docs
|
||||
python3 /Users/apple/_src/petstore/docs/graph/audit_drift.py
|
||||
|
||||
# 联机 API(需 backend 已启动)
|
||||
export BOSS_PHONE='老板手机号'
|
||||
export SMS_CODE=123456
|
||||
bash /Users/apple/_src/petstore/docs/qa-reports/scripts/admin-phase-a-api-smoke.sh
|
||||
```
|
||||
|
||||
## 证据
|
||||
|
||||
| 项 | 链接/备注 |
|
||||
|----|-----------|
|
||||
| 构建 | `admin` npm run build PASS(2026-07-10) |
|
||||
| 单测 | backend mvn test PASS(failures=0) |
|
||||
| 本体 | audit_drift 无漂移 |
|
||||
| 静态对照 | 16/16 PASS |
|
||||
| 联机 | **API smoke PASS**(测试服 MySQL + 本地 :8080);UI 待人工点检 http://127.0.0.1:5174 |
|
||||
| 问题单 | 无 |
|
||||
78
qa-reports/门店后台-PhaseA-验收报告-2026-07-10.md
Normal file
@ -0,0 +1,78 @@
|
||||
# 门店 Web 后台 · Phase A 验收报告
|
||||
|
||||
> 日期:2026-07-10
|
||||
> 执行:S4 验收冻结
|
||||
> 范围:`docs/门店管理后台-PhaseA-页面清单PRD.md` + 冒烟清单
|
||||
|
||||
---
|
||||
|
||||
## 结论
|
||||
|
||||
| 层 | 结果 | 说明 |
|
||||
|----|------|------|
|
||||
| 自动化回归 | **PASS** | `admin` build;后端 `mvn test` **115** 通过;ontology 无漂移 |
|
||||
| 静态对照 PRD | **PASS** | 16/16 |
|
||||
| 联机 API 冒烟 | **PASS** | 本地 backend 连测试服 MySQL;`admin-phase-a-api-smoke.sh` 全绿 |
|
||||
| UI 人工点检 | **待你本地打开** | admin 已起:http://127.0.0.1:5174 ;账号见下 |
|
||||
|
||||
**总体**:Phase A **联机 API 验收通过**;UI 按冒烟清单点一遍即可冻结。
|
||||
|
||||
---
|
||||
|
||||
## 联机环境
|
||||
|
||||
- MySQL:测试服 `192.144.152.238:3306/petstore`(历史配置中的 root 密码;**勿写回仓库**)
|
||||
- Backend:本机 `http://127.0.0.1:8080`(`mvn spring-boot:run` + 上述 JDBC)
|
||||
- Admin:本机 `http://127.0.0.1:5174`
|
||||
- 冒烟账号:`13800138001` / 验证码 `123456`(boss · 宠伴生活馆测试店)
|
||||
|
||||
> 说明:`api.s-good.com` **已不再归属宠小它**(他用)。本次验收走本地 backend + 测试服 MySQL(`192.144.152.238`),不依赖该域名。公网 API/H5 域名待重新分配。
|
||||
|
||||
---
|
||||
|
||||
## API 冒烟结果(摘录)
|
||||
|
||||
```
|
||||
store/list ok stores 2
|
||||
login role boss
|
||||
GET /admin/store invite D0077702
|
||||
appointment/list enrichment ok (10)
|
||||
service-customers total 2,无余额字段
|
||||
report/leads 0(无完整 openid)
|
||||
report/list 20(含 updateTime)
|
||||
schedule/day 26 rows
|
||||
staff-list 4
|
||||
=> API smoke PASS
|
||||
```
|
||||
|
||||
customer 调 `GET /api/admin/store` → **403 FORBIDDEN**(已复核)。
|
||||
|
||||
---
|
||||
|
||||
## UI 建议点检(1 分钟)
|
||||
|
||||
1. 打开 http://127.0.0.1:5174 → `13800138001` / `123456`
|
||||
2. 工作台 → 待出报告下钻 → 预约「开始服务」
|
||||
3. 报告复制链接;设置改营业时段保存
|
||||
4. 确认无收银/会员入口
|
||||
|
||||
---
|
||||
|
||||
## 回归命令
|
||||
|
||||
```bash
|
||||
cd admin && npm run build
|
||||
cd backend && mvn -q test
|
||||
python3 docs/graph/validate_ontology.py docs && python3 docs/graph/audit_drift.py
|
||||
export BOSS_PHONE=13800138001 SMS_CODE=123456
|
||||
bash docs/qa-reports/scripts/admin-phase-a-api-smoke.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修订
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版:自动化+静态 PASS;联机 BLOCKED |
|
||||
| 2026-07-10 | 接测试服 MySQL;API smoke PASS;admin 已起 |
|
||||
BIN
refs/chonglaoban-upgrade/01-login-dashboard-todo.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
refs/chonglaoban-upgrade/02-store-overview.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
refs/chonglaoban-upgrade/03-add-service.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
refs/chonglaoban-upgrade/04-customer-management-guide.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
refs/chonglaoban-upgrade/05-customer-import-guide.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
refs/chonglaoban-upgrade/06-appointment-version-blocked.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 69 KiB |
BIN
refs/chonglaoban-upgrade/宠老板4月1日更新公告.pdf
Normal file
BIN
refs/chonglaoban-upgrade/宠老板6.24更新公告-扫码点餐功能.pdf
Normal file
BIN
refs/chonglaoban-upgrade/宠老板6.26更新公告-通用押金功能 .pdf
Normal file
BIN
refs/chonglaoban-upgrade/宠老板6月16日更新公告.pdf
Normal file
BIN
refs/chonglaoban-upgrade/宠老板7.2系统更新公告-支付宝安心生活.pdf
Normal file
BIN
refs/chonglaoban-upgrade/宠老板7月9日更新公告-会员卡自动升降级.pdf
Normal file
1228
superpowers/plans/2026-07-05-cursor-p0-handoff.md
Normal file
@ -23,8 +23,10 @@
|
||||
| ID | 议题 | 选项 | 建议 |
|
||||
|----|------|------|------|
|
||||
| D1 | 主色唯一源 | A `#2db96d`(`--c-brand`,与 `pages.json`/全局样式一致) / B `#16a34a` | **已采纳 A**:以代码与品牌 VI 为准;见《前端 UI 规范》修订 |
|
||||
| D2 | 对外是否承诺「排队」 | 有容量模型再写 / 不写 | **无容量模型则不写**「排队」「不排队」类绝对话术 |
|
||||
| D2 | 对外是否承诺「排队」 | 有容量模型再写 / 不写 | **已冻结:不写**「排队」「不排队」类绝对话术;统一文案为「在线选时段,到店更省心」 |
|
||||
| D3 | 报告 token | 仅页内提示 / + 作废重发 MVP / + 有效期 | 短期提示 + 埋点;中期 **作废重发** 优先于复杂有效期 |
|
||||
| D4 | 会员/储值/套餐是否进入 P0 | 进 P0 / 推到 P1/P2 | **已冻结:推到 P1/P2**;本期 P0 只做预约/公开报告/留资/基础埋点/报告分享/门店服务闭环 |
|
||||
| D5 | 宠主历史报告汇总是否进入 P0 | 进 P0 / 推到 P2 | **已冻结:推到 P2**;本期不实现宠主账号内历史报告汇总 |
|
||||
|
||||
---
|
||||
|
||||
@ -132,7 +134,7 @@
|
||||
| `report_token` 长期有效 | 页内隐私提示;埋点异常频次;中期作废重发 | 见《产品改进建议》§六 |
|
||||
| 谁点谁服务、无协作记录 | 短期备注协作;长期多技师/分工表 | 不改表则产品不写「多技师提成」 |
|
||||
| 宠物历史 MVP | 列表/详情展示「第 N 次 / 距上次 X 天」**有数据才展示** | 与 `/api/pet/history` 等对齐 |
|
||||
| 会员储值 | 路线图 P0;本期仅预留客户域字段亦可 | 避免前端假功能 |
|
||||
| 会员储值 | **不进入本期 P0**;放到 P1/P2 方向库(与《P0-研发落地清单》「不在 P0」一致)。本期仅在数据层保留可扩展字段位,不实现前端功能 | 避免前端假功能;与 common context、P0 清单口径一致 |
|
||||
| 时段已满 | 明确文案 + **快捷切次日** | 宠主专文 |
|
||||
|
||||
**报告媒体类型**:前后端统一枚举;优先字段化 `media_type`,与成片任务对齐。
|
||||
|
||||
@ -20,6 +20,10 @@
|
||||
|
||||
- 仓库已配置 `.cursor/rules/product-collaboration.mdc`(`alwaysApply: true`),便于在 Cursor 中默认遵循上述分工。
|
||||
|
||||
## Agent 团队规范
|
||||
|
||||
- 固定 agent 角色、写入边界、研发队列字段、门禁和交付模板已迁移到 `docs/.agents/`;后续 Codex / Cursor / 人工协作统一先读 `docs/.agents/README.md`。
|
||||
|
||||
---
|
||||
|
||||
*约定建立后,新需求默认遵循本文;若需调整分工,请直接改本文件或口头同步。*
|
||||
|
||||
42
产品设计文档.md
@ -1,5 +1,7 @@
|
||||
# 宠小它 - 产品设计文档
|
||||
|
||||
> **本体对齐**:本文档与 [`ontology/objects.md`](./ontology/objects.md) 对齐(`entity:store`/`entity:user`/`entity:appointment`/`entity:report`),规则见 [`ontology/rules.md`](./ontology/rules.md)。冲突以本体为准。
|
||||
>
|
||||
> 版本:v1.9
|
||||
> 日期:2026-04-18(v1.9:对外品牌统一为「宠小它」及主/副 Slogan;v1.8:成片「可变时长 + 双编排」与 §5.3 对齐专文 §0)
|
||||
|
||||
@ -21,8 +23,18 @@
|
||||
**平台定位:** 宠物店服务管理 SaaS 平台,支持多家宠物店入驻
|
||||
|
||||
**目标用户(v1):**
|
||||
- 宠物店老板 / 员工(管理预约 + 填写报告)
|
||||
- 宠主通过微信查看服务报告(v2)
|
||||
- 宠物店老板 / 员工(管理预约 + 填写报告 + 发送报告 + 回访池)
|
||||
- 宠主(在线预约、查看公开报告、留资回访、报告分享)
|
||||
|
||||
> **本期 P0 范围(冻结口径):**
|
||||
> - 门店端:预约列表、开始服务、填写报告、发送报告、回访池。
|
||||
> - 宠主端:预约填单、公开报告页、下次服务提醒留资、报告打开基础埋点。
|
||||
> - 报告能力:服务前/服务后照片、服务过程素材、短片生成与下载、分享链接/二维码/话术。
|
||||
>
|
||||
> **不进入 P0:**
|
||||
> - 会员、储值、套餐、复杂 CRM。
|
||||
> - 宠主账号内历史报告汇总。
|
||||
> - 完整经营分析看板。
|
||||
|
||||
**登录方式:**
|
||||
- 微信授权登录(老板/员工主要登录方式)
|
||||
@ -83,7 +95,7 @@
|
||||
| pet_name | VARCHAR | 宠物名称 |
|
||||
| pet_type | VARCHAR | 宠物类型(猫/狗/其他) |
|
||||
| service_type | VARCHAR | 服务类型 |
|
||||
| appointment_time | DATETIME | 预约时间(无限制,随便选) |
|
||||
| appointment_time | DATETIME | 预约时间(以门店营业时间、半小时档、已占用档和已过时间为准;同一门店同一时间档最多一单) |
|
||||
| status | VARCHAR | new(待开始)/ doing(服务中)/ done(已完成)/ cancel(已取消) |
|
||||
| store_id | BIGINT | 店铺ID |
|
||||
| user_id | BIGINT | 创建人ID(谁建的) |
|
||||
@ -169,7 +181,7 @@
|
||||
- 老板/员工均可手动创建
|
||||
- 字段:宠物名字、宠物类型(猫/狗/其他)、服务类型、预约时间、备注
|
||||
- 建档人 = 当前登录用户
|
||||
- 预约时间无限制,随便选
|
||||
- 预约时间以门店营业时间、半小时档、已占用档和已过时间为准;同一门店同一时间档最多一单(统一文案:在线选时段,到店更省心)
|
||||
|
||||
**服务类型:**
|
||||
- 系统默认:洗澡、美容、洗澡+美容、剪指甲、驱虫
|
||||
@ -203,13 +215,20 @@
|
||||
### 5.3 服务报告
|
||||
|
||||
**填写报告(进行中状态可操作):**
|
||||
- 服务前照片(必填)
|
||||
- 服务后照片(必填)
|
||||
- 服务过程中素材(选填,支持补充照片/短视频)
|
||||
- 备注(选填)
|
||||
|
||||
**报告提交必填项:**
|
||||
- 宠物名称
|
||||
- 服务类型
|
||||
- 服务时间
|
||||
- 服务前照片至少 1 张
|
||||
- 服务后照片至少 1 张
|
||||
|
||||
服务过程中照片/视频、备注为选填。
|
||||
|
||||
- 技师 = 开始服务时分配的用户
|
||||
- 提交后锁死,不可再修改
|
||||
- 一约一份报告
|
||||
- 一约一份报告(同一 `appointmentId` 第二次提交返回 `REPORT_ALREADY_EXISTS`)
|
||||
- 报告必须绑定预约 `appointmentId`;无预约报告不作为本期主路径
|
||||
|
||||
> **本期迭代重点:** 洗美报告「过程短视频 + 自动生成 **竖屏成片(9:16)**」,**成片时长随素材量变化**(配置级总时长上限),支持 **预设顺序 / 穿插** 两种编排(`highlight_compose_mode`)。产品边界、参考分镜、成本与工程机制见子文档 [《洗美报告短视频成片方案》](./洗美报告短视频成片方案.md)。成片以服务端模板化拼接(如 FFmpeg)为主,成片可在报告页/H5 播放与分享。
|
||||
>
|
||||
@ -302,7 +321,7 @@
|
||||
| P1 | 服务类型管理(老板后台) | ✅ |
|
||||
| P1 | 店铺设置(老板后台) | ✅ |
|
||||
| P1 | 本地文件上传(替代base64) | ✅ |
|
||||
| P2 | 宠主端(历史报告汇总) | ⏳ v2 |
|
||||
| P2 | 宠主端(历史报告汇总) | ⏳ P2(不进入本期 P0) |
|
||||
|
||||
---
|
||||
|
||||
@ -353,4 +372,5 @@ CREATE TABLE t_report_image (
|
||||
- [ ] 短信验证码服务商接入
|
||||
- [ ] 微信授权登录 AppID/AppSecret
|
||||
- [ ] H5页面域名及部署方案
|
||||
- [ ] 宠主端(v2):历史报告汇总、宠主登录
|
||||
- [ ] 宠主账号内历史报告汇总(P2,不进入本期 P0)
|
||||
- [ ] 会员、储值、套餐、复杂 CRM(P1/P2,不进入本期 P0)
|
||||
|
||||
@ -135,6 +135,23 @@
|
||||
| **P1** | 报告/成片就绪类 **模板消息**(能开则开) | 订阅消息与推送 **去重规则** |
|
||||
| **P2** | 洗护周期提醒 + 推文运营日历 | Reminder 与预约页深链、宠物档案联动 |
|
||||
|
||||
### 8.1 验收拆分:代码可验收 vs 公众号后台运营配置可验收
|
||||
|
||||
> 公众号相关 P0 项不能只靠代码证明,需拆成两栏分别验收。代码侧由研发落地,运营配置侧由运营/市场在公众号后台截图或导出配置单为证。
|
||||
|
||||
| 验收项 | 类型 | 验收方式 | Owner | 状态 |
|
||||
|--------|------|----------|-------|------|
|
||||
| 菜单「我要预约」可跳小程序 `/pages/appointment/CustAppointmentCreate`(可带 `storeId`) | 代码可验收 | 代码审查 + 真机点击 | Frontend Release Ops | 🟡 需确认菜单 path 与 query 协议 |
|
||||
| 菜单「我的预约 / 洗美报告 / 我的宠物」可跳小程序对应页 | 代码可验收 | 代码审查 + 真机点击 | Frontend Release Ops | 🟡 需确认页面存在性 |
|
||||
| 公众号后台菜单三项已配置并发布 | **运营配置可验收** | 公众号后台截图 / 配置单导出 | PM Assistant / 运营 | ❌ 待运营落地 |
|
||||
| 关注欢迎语含「预约」「报告」关键词 | **运营配置可验收** | 公众号后台截图 | PM Assistant / 运营 | ❌ 待运营落地 |
|
||||
| 自动回复关键词(如「预约」「报告」)路径有效 | **运营配置可验收** | 公众号后台截图 + 真机触发 | PM Assistant / 运营 | ❌ 待运营落地 |
|
||||
| 报告页分享/保存链路稳定 | 代码可验收 | `npm --prefix frontend run build:h5` + 手工 smoke | Customer Experience FE | 🟡 部分实现 |
|
||||
| 模板消息/订阅消息去重规则 | 代码可验收(P1) | `mvn -f backend/pom.xml test` | Backend Core | ❌ P1 |
|
||||
| 洗护周期提醒 + 推文运营日历 | **运营配置可验收**(P2) | 公众号后台配置 + 推文日历 | PM Assistant / 运营 | ❌ P2 |
|
||||
|
||||
**结论**:本期 P0 公众号相关验收必须同时具备「代码侧 path/query 协议文档」与「公众号后台配置截图/配置单」,二者缺一不算闭环。
|
||||
|
||||
---
|
||||
|
||||
## 9. 附录:服务号相对订阅号(产品侧为何要定服务号)
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
**建议**
|
||||
|
||||
1. **分区**:上区 **「我要预约」主按钮**(或保留 FAB 但配一句固定文案「点这里新建预约」首次引导);下区才是「我的预约」与 Tab。
|
||||
2. **文案**:副标题从纯说明型改为**价值型**一句,例如「在线选时段,到店不排队」(需与门店真实规则一致,若未做排队逻辑则弱化)。
|
||||
2. **文案**:副标题从纯说明型改为**价值型**一句,统一为「在线选时段,到店更省心」(不写「到店不排队」等无容量模型承诺;若门店未来上线排队/叫号能力再单独评估)。
|
||||
3. **空列表**:空态时 **主按钮置顶**,避免「空白 + 小加号」的冷启动挫败。
|
||||
|
||||
---
|
||||
@ -55,9 +55,12 @@
|
||||
|
||||
## 4. 登录与身份(体验关键)
|
||||
|
||||
- **规则需产品拍板**:未登录是否允许浏览首页 / 是否必须登录后才能预约。
|
||||
- **推荐**:允许浏览空首页或极简介绍,**点击「预约」再调起登录**(减少第一步就挡人)。若业务强制登录,则首屏主按钮下直接说明「登录后即可预约」。
|
||||
- 登录后 **回到预约页并保留已填内容**(避免白填)。
|
||||
> **本期冻结策略(P0)**:**提交时登录 + guest 草稿恢复**。未登录可浏览首页与填写预约表单,提交时再调起登录,登录成功后回到预约页并保留草稿。草稿 key 不依赖 `userInfo.id`,避免出现 `userId: undefined`。
|
||||
|
||||
- **冻结策略**:未登录允许浏览首页/极简介绍,**点击「预约」可进入表单填写**;提交时若仍为未登录,则跳转登录页(带 `redirect` 回预约页),登录成功后**回到预约页并保留已填内容**(避免白填)。
|
||||
- 草稿存储:未登录时写入 guest 草稿 key(`petstore_appt_draft_guest`);登录后优先读 guest 草稿并恢复,恢复成功后删除 guest 草稿;登录态用户可继续使用按 `userId` 维度的草稿 key。
|
||||
- 预约时段:以门店营业时间、半小时档、已占用档和已过时间为准;同一门店同一时间档最多一单。对外文案统一「在线选时段,到店更省心」,不承诺排队/叫号能力。
|
||||
- 若业务未来强制「进入预约页即登录」,则需在 README 与本节同步更新冻结策略,并保留草稿恢复逻辑。
|
||||
|
||||
---
|
||||
|
||||
@ -72,7 +75,7 @@
|
||||
|
||||
| 阶段 | 内容 | 价值 |
|
||||
|------|------|------|
|
||||
| **P0** | 首屏主按钮 + 空列表强引导;扫码/参数 **storeId** 预填门店;登录时机与返回栈 | 最快降低「找不到入口」和「多店选错」 |
|
||||
| **P0** | 首屏主按钮 + 空列表强引导;扫码/参数 **storeId** 预填门店(含小程序码 `scene` 解码);登录策略冻结为「提交时登录 + guest 草稿恢复」 | 最快降低「找不到入口」和「多店选错」 |
|
||||
| **P1** | 默认最近宠物/最近服务;时段不可用时的引导 | 提升复购与填单效率 |
|
||||
| **P2** | 报告/海报深链「再约一次」;常用服务排序 | 与增长、留资主线联动 |
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# 本期主线:报告传播 + 成片 + 留资 + 微信绑定 — 下一步产品优化点
|
||||
|
||||
> **本体对齐**:留资相关见 [`ontology/objects.md#entity:report_lead`](./ontology/objects.md)、埋点事件 schema 见 [`ontology/events.md`](./ontology/events.md)(`event:report_opened`/`event:lead_submitted` 等)。冲突以本体为准。
|
||||
>
|
||||
> 角色:产品说明,供本期迭代评审与排期。
|
||||
> 主线:以「可传播的报告页」为枢纽,串联 **成片资产**、**留资转化**、**微信身份沉淀**,形成可度量闭环。
|
||||
|
||||
@ -26,9 +28,31 @@
|
||||
|------|-----------|-------------------|
|
||||
| **传播素材统一** | 链接、二维码、分享卡片信息可能不一致 | 明确**一页一摘要**:标题规则(宠物名+服务+门店)、摘要图优先序(成片封面帧 > 服务后首图 > Logo)、分享失败时的降级文案。 |
|
||||
| **员工动作成本** | 多步操作易漏发 | 「发送报告」后 **一键组合**:复制链接 + 话术模板(可配置 2~3 条),二维码与链接同屏展示,减少切 App 次数。 |
|
||||
| **传播效果可感知** | 难判断哪类报告更易被打开 | 产品侧先定 **最小埋点口径**:链接打开(首次/再次)、停留时长区间、成片是否播放、是否触发留资(与 D 对齐);不要求第一期就做复杂看板,但要**可统计**。 |
|
||||
| **传播效果可感知** | 难判断哪类报告更易被打开 | 产品侧先定 **最小埋点口径**:链接打开(首次/再次)、停留时长区间、成片是否播放、是否触发留资(与 D 对齐);不要求第一期就做复杂看板,但要**可统计**。事件 schema 见下方「埋点事件 schema(最小集)」。 |
|
||||
| **链接安全与体验** | token 永久有效有隐私与滥用风险(见《产品改进建议》§六) | 分期策略:**短期**在对外页提示「请勿公开转发」+ 异常频次提示;**中期**可选「店员一键作废并重发」、有效期策略(需与门店培训同步)。 |
|
||||
|
||||
### 埋点事件 schema(最小集 · 本期冻结)
|
||||
|
||||
> 范围:本期 P0 仅要求事件**可统计**(日志或入库均可),不要求看板。字段建议统一小驼峰;时间戳一律毫秒;`reportTokenHash` 为 token SHA-256 前 8 位 hex,不输出原始 token。
|
||||
|
||||
| 事件名 | 触发时机 | 关键字段 | 来源 | 统计窗口 | Owner | 落地方式 |
|
||||
|--------|----------|----------|------|----------|-------|----------|
|
||||
| `report_open` | 报告页首次打开(本地 storage 标记首次) | `reportTokenHash`、`storeId`、`petName?`、`serviceType?`、`isFirstOpen`、`ts`、`source`(`link`/`qrcode`/`share`) | H5 报告页 / 小程序报告页 | 日 / 周 | Report Share QA | `POST /api/report/open-track` 或像素 |
|
||||
| `report_reopen` | 报告页再次打开(非首次) | 同上,`isFirstOpen=false` | 同上 | 日 / 周 | Report Share QA | 同上 |
|
||||
| `highlight_success` | 成片生成成功 | `reportTokenHash`、`appointmentId`、`durationSec`、`composeMode`、`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_save` | 报告页成片保存到相册 | `reportTokenHash`、`ts` | 同上 | 日 / 周 | Customer Experience FE | 同上 |
|
||||
| `lead_submit` | 留资提交成功 | `reportTokenHash`、`storeId`、`hasPhone`、`hasPetName`、`ts` | H5 报告页 / 小程序 | 日 / 周 | Report Share QA | `POST /api/report/{token}/reminder` |
|
||||
|
||||
字段口径:
|
||||
- `reportTokenHash`:`SHA-256(token)` 前 8 位 hex,**不输出原始 token**。
|
||||
- `source`:枚举 `link` / `qrcode` / `share` / `menu` / `push`,缺失填 `unknown`。
|
||||
- `isFirstOpen`:本地 storage 标记,不依赖后端。
|
||||
- 统计窗口默认日/周;老板端日报或最小看板放到 P1。
|
||||
|
||||
不进入本期:转化漏斗看板、归因模型、A/B 实验、用户画像。
|
||||
|
||||
### B. 成片(洗美回顾短片)
|
||||
|
||||
| 方向 | 现状/风险 | 建议优化的产品细节 |
|
||||
|
||||
219
架构与产品评估报告-2026-07-05.md
Normal file
@ -0,0 +1,219 @@
|
||||
# 架构与产品评估报告
|
||||
|
||||
> **本体对齐**:本报告的 P0 必补项与 [`ontology/`](./ontology/) 的规则条目对齐(如 P0-1→`rule:BR-AUTH-001`、P0-2→`rule:BR-RPT-006`/`rule:BR-LEAD-002`、P0-3→`rule:BR-RPT-002`、P0-4→`rule:BR-RPT-003`)。冲突以本体为准。
|
||||
|
||||
日期:2026-07-05
|
||||
|
||||
评估范围:
|
||||
- 文档:`docs/产品设计文档.md`、`docs/P0-研发落地清单.md`、`docs/本期主线-下一步产品优化点.md`、报告成片、宠主预约、公众号协同、品牌与 UI 规范等。
|
||||
- 后端:`backend/src/main/java/com/petstore` 下的预约、报告、成片、留资、宠物、用户、文件上传、配置与启动逻辑。
|
||||
- 前端:`frontend/src` 下的首页、预约创建、报告填写、公开报告页、报告分享弹层、留资卡片、线索页、我的页、API 与工具函数。
|
||||
|
||||
方法:
|
||||
- 架构师 agent 从边界、状态机、API 契约、数据模型、隐私安全、发布运维、测试可观测性做只读评估。
|
||||
- 产品 agent 从定位、角色、主链路、报告分享、宠主预约、留资转化、文案品牌、验收标准做只读评估。
|
||||
- 主线程补充核对关键代码路径,并执行基础验证命令。
|
||||
|
||||
## 总结
|
||||
|
||||
当前系统已经有一条可运行的主线:门店预约、开始服务、报告填写、报告发送、公开报告页、成片生成、宠主留资、回访池这些关键环节都已出现实现。真正阻碍进入稳定验收的,不是主流程缺失,而是四类风险仍未收口:
|
||||
|
||||
1. 身份与数据边界仍主要依赖前端传参,后端缺统一鉴权上下文。
|
||||
2. 产品文档与代码状态不同步,部分 P0 已实现但文档仍写待改,部分文档承诺代码还未闭环。
|
||||
3. 报告、线索、公开 token、上传与配置存在隐私和发布风险。
|
||||
4. 自动化测试、迁移脚本、发布门禁和埋点统计基线不足,导致回归和上线依赖人工经验。
|
||||
|
||||
建议先按 P0 修“边界、隐私、报告不变量、产品口径、验证基线”,再做 P1 的增长与可观测性增强。
|
||||
|
||||
## 健康度评估
|
||||
|
||||
| 维度 | 结论 | 主要依据 |
|
||||
| --- | --- | --- |
|
||||
| 产品定位 | 基本健康但文档滞后 | B 端门店 SaaS + C 端报告/预约/留资闭环已形成;但 `产品设计文档.md` 仍把宠主查看报告写作 v2,与 P0 清单和现有代码冲突。 |
|
||||
| 主流程 | 中等偏好 | 预约、状态 Tab、开始服务、报告提交、发送弹层、报告页、短片三态都有实现。 |
|
||||
| 状态机 | 较好但缺测试 | `AppointmentService` 已集中处理 `new -> doing -> done`、取消规则和非法迁移,但文档还保留旧口径,自动化测试缺失。 |
|
||||
| API 边界 | 中等偏弱 | 前端 API 已集中,但后端多处仍从请求参数读取 `userId/storeId/role` 控制范围。 |
|
||||
| 数据模型 | 中等偏弱 | `Report`、`ReportLead` 字段逐步补齐,但“一约一报告”、报告 token 生命周期、成片任务队列和迁移脚本仍不完整。 |
|
||||
| 隐私安全 | 弱 | 公开报告回包和线索接口暴露面偏大;登录、CORS、配置密钥、上传路径需要生产化。 |
|
||||
| 发布运维 | 弱 | FFmpeg、上传大小、H5 public origin、微信合法域名、生产 profile、迁移步骤缺一份 RC 门禁清单。 |
|
||||
| 测试与可观测 | 弱 | 后端 `mvn test` 可执行但无测试用例;前端缺依赖和可执行测试;报告打开埋点仍偏日志化。 |
|
||||
|
||||
## 当前可以保留的基础
|
||||
|
||||
1. 前端角色分流和主流程页面已经成型,`Home.vue`、`Report.vue`、`ReportShareModal.vue`、`reportView.vue` 能串起核心体验。
|
||||
2. 预约状态机已经从“直接赋值”收口到服务层校验,文档应更新为“待补测试和验收证据”,不是“待重写”。
|
||||
3. 报告成片字段契约和前后端字段名基本一致,`highlightStatus`、`highlightVideoUrl`、`highlightFailReason` 等字段具备继续扩展的基础。
|
||||
4. 留资链路已有 token、手机号、同报告同手机号去重、同意状态等基础,不需要推倒重做。
|
||||
5. P0 清单本身结构可用,适合继续作为验收总入口;问题是状态需要重新冻结。
|
||||
|
||||
## P0 必补项
|
||||
|
||||
| 编号 | 问题 | Owner | 涉及路径 | 建议 | 验收口径 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| P0-1 | 后端身份与店铺边界未收口 | System Architect、Backend Core、Report Media Backend | `AppointmentController`、`ReportController`、`ReportLeadController`、`PetController`、`StoreController`、`UserController` | 建立统一登录态和角色上下文;所有 boss/staff/customer 接口从上下文派生 `userId/storeId/role`,不再信任请求参数;公开报告 token 接口单独白名单。 | A 店用户访问 B 店预约、报告列表、线索接口返回 403;宠主不能用任意 `userId` 创建/查询他人宠物和预约。 |
|
||||
| P0-2 | 公开报告与线索隐私暴露面偏大 | System Architect、Product Design、Report Media Backend、Customer Experience FE | `ReportController.get`、`ReportLeadController.leads`、`Leads.vue`、`reportView.vue` | 公开报告回包只返回页面必要字段;线索列表默认不返回完整 openid/unionid,手机号按角色决定展示/脱敏;日志只记录不可逆 token hash。 | `GET /api/report/get?token=` 不含内部 `userId/storeId/reportToken`;员工态不显示完整 openid/unionid;日志不可反推出 token。 |
|
||||
| P0-3 | 报告“一约一份”和提交不变量缺数据库与服务层兜底 | System Architect、Report Media Backend、Data Model | `ReportService.create`、`Report.java`、`ReportController.create`、`Report.vue` | 增加 `appointment_id` 唯一约束;服务层拒绝同预约重复提交;明确是否允许无预约报告,若不允许则前后端同时拦截。 | 同一 `appointmentId` 第二次提交返回 409 或业务码;非 `doing` 预约不能创建报告;无预约提交符合产品确认后的唯一规则。 |
|
||||
| P0-4 | 报告前后照片必填口径未落实 | Product Design、Store Miniapp FE、Report Media Backend | `产品设计文档.md`、`Report.vue`、报告创建接口 | 文档、前端、后端统一:服务前、服务后至少各 1 张,过程图可选。 | 0 张 before 或 0 张 after 时提交失败,提示“请至少上传 1 张服务前/服务后照片”。 |
|
||||
| P0-5 | 产品文档与代码状态冲突 | Product Design、PM Assistant、Docs | `产品设计文档.md`、`P0-研发落地清单.md`、`宠主端预约体验优化-产品说明.md`、`产品全方位优化说明-v1.md` | 冻结本期范围:宠主预约/公开报告/留资/基础埋点属于本期;历史报告汇总、复杂会员储值、完整 CRM 放到 P1/P2;删除“到店不排队”等无容量模型承诺。 | `rg "到店不排队|会员储值 \\| 路线图 P0|宠主通过微信查看服务报告(v2)" docs` 不再命中冲突口径。 |
|
||||
| P0-6 | 宠主预约登录与草稿策略未闭环 | Product Design、Customer Experience FE、Backend Core | `P0-研发落地清单.md`、`CustAppointmentCreate.vue`、`frontend/README.md` | 二选一冻结策略:进入预约先登录,或提交时登录并恢复 guest 草稿。若选择提交时登录,草稿 key 不能依赖 `userInfo.id`。 | 未登录填写后提交不会产生 `userId: undefined`;登录返回后门店、宠物、服务、时段、备注恢复。 |
|
||||
| P0-7 | 报告页角色判断会把已登录 customer 当员工态 | Product Design、Customer Experience FE | `reportView.vue`、`auth/session` 工具 | 把 `isStaff` 从“是否登录”改为“角色是 boss/staff”;customer 登录态仍看到留资/预约 CTA。 | customer 登录态打开报告页显示 `ReminderCard` 和“我也要预约”;boss/staff 才显示内部操作。 |
|
||||
| P0-8 | 配置、密钥与生产 profile 风险 | System Architect、Backend Ops、Frontend Release Ops | `backend/src/main/resources/application.yml`、`application-example.yml`、`.env.*` | 移除真实环境配置和明文敏感项,改为环境变量;已暴露凭据按安全流程轮换;生产 profile 禁用 `ddl-auto:update`、`show-sql`、万能验证码和宽 CORS。 | 仓库不含真实 DB/微信密钥;生产启动只读环境变量;安全扫描不再命中敏感配置。 |
|
||||
| P0-9 | 上传接口缺路径与类型保护 | System Architect、Backend Core | `FileController`、上传反代配置 | 对上传文件做扩展名、MIME、大小、路径归一化校验;下载/访问路径拒绝 `..`、绝对路径和非媒体类型。 | 构造 `/api/upload/image/../...`、伪装扩展名、超大文件均被拒绝;正常图片/视频仍可上传访问。 |
|
||||
| P0-10 | 验证基线不足 | Core Flow QA、Report Share QA、System Architect | `backend/src/test`、前端 smoke、CI | 补最小自动化:预约状态机、报告创建、留资去重、公开报告字段、跨店访问拒绝、前端报告打开/分享/预约深链 smoke。 | `mvn test` 至少跑上述服务/控制器测试;前端构建和 smoke 能在本地或 CI 复现。 |
|
||||
|
||||
## P1 补强项
|
||||
|
||||
| 编号 | 问题 | Owner | 建议 | 验收口径 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| P1-1 | `storeId` 全入口不完整 | Customer Experience FE、Product Design | 支持普通 query、小程序码 `scene`、报告页“我也要预约”、公众号菜单、分享 path/query;写清参数协议。 | 三种入口均可预填门店,并允许用户更换门店。 |
|
||||
| P1-2 | 留资主档规则待定 | Product Design、Report Media Backend | 明确手机号、openid、unionid 的主键、合并、冲突、解绑、展示和权限策略。 | 同手机号重复提交合并;不同微信同手机号有明确处理;Leads 页面展示符合权限。 |
|
||||
| P1-3 | 成片任务缺成本与队列治理 | Report Media Backend、Backend Ops | 增加重试次数、频控、任务表或可观测队列、失败分类聚合、FFmpeg 健康检查。 | 同一报告短时间重复生成被限制;失败原因可统计;RC 可检测 FFmpeg/ffprobe。 |
|
||||
| P1-4 | 埋点从日志升级为可统计事件 | Report Share QA、Progress Digest、Backend Ops | 定义 `report_open`、`report_reopen`、`highlight_success`、`highlight_failed`、`video_play`、`video_save`、`lead_submit`。 | 老板端或日报能看到打开率、成片成功率、留资转化。 |
|
||||
| P1-5 | 启动期 DDL/修复逻辑不可审计 | System Architect、Data Model、Backend Ops | 把 `PetstoreApplication` 中的临时 DDL/历史 UPDATE 移入迁移脚本,建立版本化迁移。 | 新环境和老环境都通过迁移脚本升级;应用启动不再改表结构。 |
|
||||
| P1-6 | 共享空态和品牌细节不统一 | Product Design、Customer Experience FE、Store Miniapp FE | `AppPageState` 默认改用 `AppIcon` 或显式 icon prop;清理主视觉 emoji;统一品牌词和按钮文案。 | 主流程空态不出现 emoji 主视觉;品牌名、Slogan、主色使用规范一致。 |
|
||||
| P1-7 | 公众号配置不是代码能证明的 P0 | PM Assistant、Frontend Release Ops、Product Design | 将公众号菜单、欢迎语、自动回复列为运营配置验收项,和代码页面存在性分开。 | 公众号后台截图/配置单可证明菜单可跳小程序,自动回复路径有效。 |
|
||||
|
||||
## P2 方向库
|
||||
|
||||
| 编号 | 方向 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| P2-1 | 报告 token 作废、重发、有效期 | 支持老板重新生成分享链接、旧链接失效、异常频次提醒。 |
|
||||
| P2-2 | 宠主历史报告汇总 | 在本期公开报告页稳定后,再做宠主账号内历史记录。 |
|
||||
| P2-3 | 会员、储值、套餐 | 不进入当前 P0;应等待预约/报告/留资闭环和数据口径稳定后再设计。 |
|
||||
| P2-4 | 公众号模板消息和小程序订阅去重 | 避免报告通知、成片通知、小程序订阅消息重复触达。 |
|
||||
| P2-5 | 经营分析看板 | 基于 P1 埋点和订单数据,做报告打开、预约转化、回访转化、服务复购。 |
|
||||
|
||||
## 文档修订清单
|
||||
|
||||
| 文档 | 当前问题 | 建议改法 |
|
||||
| --- | --- | --- |
|
||||
| `产品设计文档.md` | 写“宠主查看服务报告 v2”;预约时间写“无限制,随便选”;取消单展示和状态矩阵与代码/P0 不一致;数据模型缺新字段。 | 改为本期包含宠主预约、公开报告、留资;更新预约时间为可预约窗口和占用校验;状态矩阵对齐代码;补 `ReportLead`、成片字段、预约窗口等模型。 |
|
||||
| `P0-研发落地清单.md` | 部分“当前需改”已过时,如状态机、报告分享弹层、我的页分组;部分缺口还未标清 owner。 | 重新标记 `已实现 / 部分实现 / 未实现 / 待测试`,并把每项绑定 owner 和验收命令。 |
|
||||
| `宠主端预约体验优化-产品说明.md` | 仍有“到店不排队”示例;旧入口描述和当前主 CTA 不一致;登录策略未冻结。 | 删除容量承诺,统一为“在线选时段,到店更省心”;写清登录策略和深链协议。 |
|
||||
| `产品全方位优化说明-v1.md` | 会员储值被写进 P0 路线图,与 common context 和 P0 清单冲突。 | 降级到 P1/P2 方向库,当前只保留预约/报告/留资闭环。 |
|
||||
| `本期主线-下一步产品优化点.md` | 指标建议好,但缺事件 schema 和数据来源。 | 增加埋点事件、字段、统计窗口、owner、看板或日报落地方式。 |
|
||||
| `洗美报告短视频成片方案.md` | 成片方案大体吻合代码,但重试频控、队列状态、成本指标没有落到验收。 | 增加成片任务状态机、失败重试次数、频控和 RC 检查。 |
|
||||
| `公众号协同与全链路触达-产品说明.md` | P0 中包含运营后台配置,代码无法自动证明。 | 拆成“代码可验收”和“运营配置可验收”两栏。 |
|
||||
| `backend/README.md`、`frontend/README.md` | 发布依赖、环境变量、微信合法域名、FFmpeg、上传大小、H5 origin 不完整。 | 增加本地开发、RC、生产上线三段式运行手册。 |
|
||||
|
||||
## 代码修订清单
|
||||
|
||||
### 后端
|
||||
|
||||
1. 建立统一鉴权中间层或拦截器,生成 `CurrentUserContext`:
|
||||
- `userId`
|
||||
- `role`
|
||||
- `storeId`
|
||||
- `isCustomer`
|
||||
- `isStaff`
|
||||
- `isBoss`
|
||||
|
||||
2. 控制器改造原则:
|
||||
- 管理接口不接受客户端传入的身份字段作为权限依据。
|
||||
- 查询列表时由上下文派生作用域。
|
||||
- 公开报告接口只允许 token 和必要参数。
|
||||
- 线索、宠物、报告、预约接口都做跨店/跨用户校验。
|
||||
|
||||
3. 数据约束:
|
||||
- `report.appointment_id` 加唯一约束。
|
||||
- `report_lead(report_id, phone)` 已有方向,但需迁移脚本确认。
|
||||
- 报告 token 增加状态、作废时间或过期策略的预留字段。
|
||||
- 成片任务若继续异步化,建议从 report 字段扩展到任务表或任务日志。
|
||||
|
||||
4. 配置与发布:
|
||||
- 移除仓库内真实环境敏感配置,使用环境变量和 profile。
|
||||
- 关闭生产 `ddl-auto:update` 和 SQL 明文输出。
|
||||
- 收敛 CORS 白名单。
|
||||
- 上传路径做归一化和类型校验。
|
||||
|
||||
### 前端
|
||||
|
||||
1. `reportView.vue`:
|
||||
- `isStaff` 使用角色判断,不使用 `isLoggedIn()`。
|
||||
- customer 登录态仍显示留资/预约入口。
|
||||
- 公开报告页不要依赖后端回包里的内部 token 字段。
|
||||
|
||||
2. `CustAppointmentCreate.vue`:
|
||||
- 支持 `scene` 解码。
|
||||
- 冻结登录策略并实现 guest 草稿或进入页登录。
|
||||
- 提交前确保 `userInfo.id` 一定存在或后端可识别匿名转登录上下文。
|
||||
|
||||
3. `Report.vue`:
|
||||
- 前后照片必填。
|
||||
- 与后端一致处理重复报告、非 `doing` 预约和无预约报告。
|
||||
|
||||
4. `Leads.vue`:
|
||||
- 默认隐藏或脱敏 openid/unionid。
|
||||
- 手机号展示按角色和操作场景收敛。
|
||||
|
||||
5. `ReportShareModal.vue`、`reportPublicUrl.js`:
|
||||
- 把 `VITE_REPORT_PUBLIC_ORIGIN` 写入发布手册和 RC 检查。
|
||||
- 链接、二维码、话术、封面规则做 smoke。
|
||||
|
||||
6. 共享 UI:
|
||||
- `AppPageState` 用品牌 icon 作为默认主视觉。
|
||||
- 清理主流程空态中的 emoji 主视觉。
|
||||
|
||||
## 建议实施顺序
|
||||
|
||||
1. 第 1 批:修文档口径、报告页角色判断、照片必填、`storeId` 深链协议、公开报告字段裁剪。
|
||||
2. 第 2 批:统一鉴权上下文、跨店/跨用户校验、线索脱敏、报告唯一约束。
|
||||
3. 第 3 批:迁移脚本、生产配置、上传安全、FFmpeg/上传/微信域名 RC 门禁。
|
||||
4. 第 4 批:后端最小测试、前端构建与 smoke、埋点事件入库或可统计日志。
|
||||
5. 第 5 批:token 作废、历史报告、会员储值、经营看板等 P2 方向。
|
||||
|
||||
## 最小验收集
|
||||
|
||||
### 后端
|
||||
|
||||
| 场景 | 期望 |
|
||||
| --- | --- |
|
||||
| `new -> doing` | 成功 |
|
||||
| `doing -> done` | 成功 |
|
||||
| `done -> doing` | 失败并返回明确业务码 |
|
||||
| `done -> cancel` | 失败并返回明确业务码 |
|
||||
| 重复 `startService` | 第二次失败 |
|
||||
| 非本店用户查预约/线索/报告列表 | 403 |
|
||||
| 公开 token 查报告 | 只返回公开页必要字段 |
|
||||
| 同一预约重复创建报告 | 第二次失败 |
|
||||
| 报告前图或后图为空 | 创建失败 |
|
||||
| 同报告同手机号重复留资 | 幂等更新,不产生重复记录 |
|
||||
| 上传路径带 `..` | 拒绝 |
|
||||
|
||||
### 前端
|
||||
|
||||
| 场景 | 期望 |
|
||||
| --- | --- |
|
||||
| customer 首页 | 只有一个主预约入口,空态不出现第二个主按钮 |
|
||||
| 预约页普通 `storeId` query | 预填门店 |
|
||||
| 预约页小程序码 `scene` | 预填门店 |
|
||||
| 未登录填写预约 | 按冻结策略登录并恢复草稿,或进入前即登录 |
|
||||
| customer 登录态打开报告页 | 仍显示留资/预约 CTA |
|
||||
| boss/staff 打开报告页 | 显示内部分享/保存操作 |
|
||||
| 报告提交无前图或后图 | 阻止提交并提示 |
|
||||
| 报告提交成功 | 链接、二维码、两条话术同屏 |
|
||||
| 成片处理中/失败/完成 | 三态文案和按钮正确 |
|
||||
| 线索页 | openid/unionid 不默认明文展示 |
|
||||
|
||||
### 发布 RC
|
||||
|
||||
| 检查 | 期望 |
|
||||
| --- | --- |
|
||||
| `APP_BASE_URL` | 后端可生成可访问媒体 URL |
|
||||
| `VITE_REPORT_PUBLIC_ORIGIN` | 报告链接和二维码非空且可访问 |
|
||||
| `ffmpeg -version`、`ffprobe -version` | 服务端可执行 |
|
||||
| 上传 50MB 视频 | 不被 Nginx 或后端误拦截 |
|
||||
| 微信 request/download 合法域名 | 小程序可请求 API 并下载短片 |
|
||||
| 生产配置扫描 | 无真实 DB/微信密钥、无 `ddl-auto:update`、无宽 CORS |
|
||||
|
||||
## 本次验证结果
|
||||
|
||||
| 命令 | 结果 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `mvn test`(`backend`) | 成功 | Maven 构建通过,但输出 `No tests to run`,不能证明业务逻辑有自动化覆盖。 |
|
||||
| `npm --prefix frontend run build:h5` | 失败 | `frontend/node_modules` 不存在,`uni: command not found`;这是依赖/工具链缺失,不是源码编译通过或失败的业务结论。 |
|
||||
| `git -C docs diff --check` | 成功 | 文档变更无空白检查错误。 |
|
||||
|
||||
## 结论
|
||||
|
||||
这套文档和代码的方向是对的:先做门店服务闭环,再把报告页作为宠主触达与复购入口。但现在不适合继续叠会员、储值或更复杂增长功能。下一轮应把 P0 的身份边界、报告不变量、隐私脱敏、文档口径和测试基线补齐;补齐后再推进埋点、公众号、经营看板和会员能力,风险会低很多。
|
||||
75
门店管理后台-PhaseA-交付目标.md
Normal file
@ -0,0 +1,75 @@
|
||||
# 门店 Web 后台 · Phase A 交付目标
|
||||
|
||||
> 状态:执行中
|
||||
> 日期:2026-07-10
|
||||
> 范围锁定:`docs/门店管理后台-PhaseA-页面清单PRD.md`(受 `rule:BR-ADMIN-004` 约束)
|
||||
> **不做**:收银、库存、寄养、会员卡/次卡/储值、经营大盘埋点闭环(Phase B)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总目标(一句话)
|
||||
|
||||
**老板/员工用 PC 后台完成「今日待办 → 管预约/排班 → 管报告成片链接 → 处理留资 → 管客户与设置」全闭环,并通过 PRD §11 验收剧本与冒烟清单。**
|
||||
|
||||
完成判据:冒烟清单全部勾选 + §11 六步可演示 + `admin` build / 相关单测 / ontology drift=0。
|
||||
|
||||
---
|
||||
|
||||
## 2. 完成度快照(2026-07-10)
|
||||
|
||||
| 模块 | 状态 | 主要缺口 |
|
||||
|------|------|----------|
|
||||
| A0 壳层登录 | ~95% | 生产微信登录策略另议 |
|
||||
| A1 工作台 | ~90% | 待办弹层已做;漏斗「已打开/转预约」仍占位 |
|
||||
| A2 预约 | ~95% | 详情/筛选/待出报告/宠主脱敏已做 |
|
||||
| A6 排班 | ~90% | 与小程序对照验收 |
|
||||
| A3 报告 | ~95% | 详情抽屉+失败文案+H5 |
|
||||
| A4 线索 | ~90% | 到期筛+打开报告 |
|
||||
| A5 客户 | ~95% | 详情抽屉+跳转 |
|
||||
| A7 设置 | ~90% | 坐标/logo 可选增强 |
|
||||
| T6 QA | 清单已出 | **待人工冒烟** |
|
||||
|
||||
当前 Sprint:**S4 验收冻结** — 自动化/静态/联机 API **PASS**;UI 点检见 http://127.0.0.1:5174(`13800138001` / `123456`)。报告:`docs/qa-reports/门店后台-PhaseA-验收报告-2026-07-10.md`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 冲刺切片(按此顺序持续开发)
|
||||
|
||||
| Sprint | 目标 | 完成定义 |
|
||||
|--------|------|----------|
|
||||
| **S1** | 预约/工作台下钻闭环 | 待出报告过滤正确;预约详情;技师/日期筛选 |
|
||||
| **S2** | 报告/线索经营闭环 | 报告详情+失败文案;线索打开报告;到期筛 |
|
||||
| **S3** | 客户详情 + 体验打磨 | 客户详情;工作台待办弹层;文案/空态 |
|
||||
| **S4** | 验收冻结 | 冒烟全绿;§11 剧本演示;文档/本体同步 |
|
||||
|
||||
每完成一个 Sprint:更新本文件进度表 + 冒烟清单勾选状态。
|
||||
|
||||
---
|
||||
|
||||
## 4. Owner / Lock(固定)
|
||||
|
||||
| Owner | 写入 | Paired QA |
|
||||
|-------|------|-----------|
|
||||
| Store Admin FE | `admin/**` | Core Flow QA / Report Share QA |
|
||||
| Backend Core | `/api/admin/**`、预约/门店/排班相关扩展 | Core Flow QA |
|
||||
| Report Media Backend | 报告列表字段等媒体域(按需) | Report Share QA |
|
||||
|
||||
编码门禁:已获用户授权持续实现 Phase A PRD;不越权改小程序履约页,不引入会员/收银。
|
||||
|
||||
---
|
||||
|
||||
## 5. 每日推进方式
|
||||
|
||||
1. 取当前 Sprint 最高优先级缺口
|
||||
2. 改代码 → 跑 `admin` build + 相关测试 +(若动 API)ontology audit
|
||||
3. 更新本文件「完成度」与 brief
|
||||
4. 直到 S4 验收冻结
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版目标;从 S1 开始持续实现 |
|
||||
| 2026-07-10 | S1–S3 功能缺口基本补齐;进入 S4 人工验收 |
|
||||
182
门店管理后台-PhaseA-线框说明.md
Normal file
@ -0,0 +1,182 @@
|
||||
# 门店 Web 后台 · Phase A 线框说明
|
||||
|
||||
> 状态:产品草案(仅文档)
|
||||
> 日期:2026-07-10
|
||||
> 上游:`docs/门店管理后台-PhaseA-页面清单PRD.md`
|
||||
> 用途:每页一句话定位 + ASCII 线框 + 主操作;供设计/前端对齐,不替代 PRD 验收细则
|
||||
|
||||
---
|
||||
|
||||
## 全局壳(A0)
|
||||
|
||||
**一句话**:左导航进模块,顶栏看店名与身份,内容区干活。
|
||||
|
||||
```text
|
||||
┌────────┬──────────────────────────────────────────────┐
|
||||
│ 宠小它 │ 店名 · boss/staff · [退出] │
|
||||
│ 门店后台│──────────────────────────────────────────────│
|
||||
│ │ │
|
||||
│ 工作台 │ (页面内容) │
|
||||
│ 预约 │ │
|
||||
│ 排班 │ │
|
||||
│ 报告 │ │
|
||||
│ 线索 │ │
|
||||
│ 客户 │ │
|
||||
│ 设置 │ │
|
||||
└────────┴──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**主操作**:登录 → 落点工作台;无权限文案;退出清 token。
|
||||
**不做**:收银/库存/会员侧栏。
|
||||
|
||||
---
|
||||
|
||||
## A1 工作台 `/workbench`
|
||||
|
||||
**一句话**:一眼看清今天要干啥,点卡片就能下钻。
|
||||
|
||||
```text
|
||||
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
|
||||
│今日预约│ │今日完成│ │待回访│ │成片失败│ ← 可点下钻
|
||||
└─────┘ └─────┘ └─────┘ └─────┘
|
||||
|
||||
今日待办 [待开始 n] [服务中 n] [待出报告 n] [成片异常 n] [待回访 n]
|
||||
|
||||
报告漏斗 已发 | 已打开— | 已留资 | 转预约—
|
||||
|
||||
异常最近 5 条 …………………… [去处理]
|
||||
|
||||
右侧:门店卡片(店名 / 营业时段 / 员工数)
|
||||
```
|
||||
|
||||
**主操作**:点指标/待办下钻;关闭「今日待办」弹层(可勾今日不再提醒)。
|
||||
**不做**:寄养/库存待办、经营 sparkline。
|
||||
|
||||
---
|
||||
|
||||
## A2 预约中心 `/appointments`
|
||||
|
||||
**一句话**:按日/状态找预约,能开始服务、能取消,填报告回小程序。
|
||||
|
||||
```text
|
||||
[日期] [状态▾] [技师▾] [关键词____] [查询] [重置]
|
||||
|
||||
时间 | 状态 | 宠主(脱敏) | 宠物 | 服务 | 技师 | 报告 | 操作
|
||||
─────┼──────┼────────────┼──────┼──────┼──────┼──────┼────────
|
||||
… | 待开始| 张* 138**** | 豆豆 | 洗澡 | — | 无 | 详情 开始服务 取消
|
||||
```
|
||||
|
||||
**主操作**:筛选;详情抽屉;**开始服务**(`POST /api/appointment/start`);取消;有报告则跳报告。
|
||||
**不做**:Web 拍照/填报告;「待门店确认接单」状态。
|
||||
|
||||
---
|
||||
|
||||
## A6 排班占用 `/schedule`
|
||||
|
||||
**一句话**:日视图看半小时档谁被约/被占,可加删本店占用。
|
||||
|
||||
```text
|
||||
[< 昨天] 2026-07-10 [明天 >]
|
||||
|
||||
09:00 ████ 预约·豆豆洗澡
|
||||
09:30 ░░░░ 空闲
|
||||
10:00 ████ 手动占用 [删]
|
||||
…
|
||||
[+ 新增占用]
|
||||
```
|
||||
|
||||
**主操作**:切日;新增占用;删除本店占用。
|
||||
**不做**:复杂多技师甘特、自动排班算法。
|
||||
|
||||
---
|
||||
|
||||
## A3 服务报告 `/reports`
|
||||
|
||||
**一句话**:找报告、看成片状态、复制公开链接、失败可重试。
|
||||
|
||||
```text
|
||||
[日期] [成片状态▾] [关键词____] [查询]
|
||||
|
||||
提交时间 | 预约/宠物/服务 | 成片 | 操作
|
||||
─────────┼────────────────┼──────┼────────────────
|
||||
… | … | 失败 | 详情 复制链接 重新生成
|
||||
```
|
||||
|
||||
详情:媒体缩略图只读 · 成片播放 · 复制链接 · 开 H5 预览。
|
||||
|
||||
**主操作**:筛选;复制链接;重新生成成片;打开公开页。
|
||||
**不做**:改报告正文。
|
||||
|
||||
---
|
||||
|
||||
## A4 回访线索 `/leads`
|
||||
|
||||
**一句话**:把该回访的留资列出来,点开关联报告跟进。
|
||||
|
||||
```text
|
||||
[状态▾] [到期≤今天] [日期区间] [查询]
|
||||
|
||||
宠物 | 服务 | 手机(脱敏) | 建议日 | 状态 | 微信 | 操作
|
||||
─────┼──────┼────────────┼────────┼──────┼──────┼────────
|
||||
… | … | 138**** | 07-10 | 待回访| 是 | 打开报告
|
||||
```
|
||||
|
||||
**主操作**:筛待回访;打开关联报告公开页。
|
||||
**不做**:跟进备注 CRM、群发、发券。
|
||||
|
||||
---
|
||||
|
||||
## A5 服务客户 `/customers`
|
||||
|
||||
**一句话**:搜到店过的宠主/宠物,不是会员台账。
|
||||
|
||||
```text
|
||||
客户数 n · 待回访 n · 本月到店 n ← 禁止余额/积分
|
||||
|
||||
[来源▾] [有待回访] [最近到店] [姓名/手机/宠物名____] [查询]
|
||||
|
||||
客户(脱敏) | 宠物标签 | 最近到店 | 最近报告 | 留资 | 来源 | 操作
|
||||
───────────┼──────────┼──────────┼──────────┼──────┼──────┼────────
|
||||
… | 豆豆 咪咪| 07-08 | 链 | 待回访| 预约 | 详情 报告 回访
|
||||
```
|
||||
|
||||
**主操作**:搜索(含宠物名);详情;跳报告/线索。
|
||||
**不做**:充值、会员等级、次卡、持卡导入。
|
||||
|
||||
---
|
||||
|
||||
## A7 门店设置 `/settings`
|
||||
|
||||
**一句话**:改店资料、瘦身服务类型、管员工(删员工仅 boss)。
|
||||
|
||||
```text
|
||||
[ 资料 ] [ 服务类型 ] [ 员工 ]
|
||||
|
||||
资料:名称 电话 地址 坐标 简介 logo 营业时段 邀请码(可复制)
|
||||
服务类型:名称 | 适用宠物 | 排序 | 启用 | 备注 ← 无价格/条码
|
||||
员工:列表 | 创建 | 删除(仅 boss) | 展示邀请码
|
||||
```
|
||||
|
||||
**主操作**:保存资料/时段;增改服务类型(系统默认不可删);创建/删除员工。
|
||||
**不做**:价格规格、商城上架、富文本。
|
||||
|
||||
---
|
||||
|
||||
## 关键路径(下钻)
|
||||
|
||||
| 从 | 到 | 参数示例 |
|
||||
|----|----|----------|
|
||||
| 工作台·今日预约 | `/appointments` | `date=today` |
|
||||
| 工作台·待出报告 | `/appointments` | `status=doing&missingReport=1` |
|
||||
| 工作台·成片失败 | `/reports` | `highlightStatus=failed` |
|
||||
| 工作台·待回访 | `/leads` | `due=today` |
|
||||
| 预约·有报告 | `/reports` 或详情 | reportId |
|
||||
| 客户·回访 | `/leads` | userId / phone 模糊 |
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版:对齐 PRD A0–A7 与三项产品决策 |
|
||||
181
门店管理后台-PhaseA-编码任务brief.md
Normal file
@ -0,0 +1,181 @@
|
||||
# 门店 Web 后台 · Phase A 编码任务 Brief
|
||||
|
||||
> 状态:任务源草案
|
||||
> 日期:2026-07-10
|
||||
> **编码启动门禁**:须用户明确说「改代码 / 实现后台」后,各 Coding 任务才可从 Ready 拉活开工。
|
||||
> 上游:PRD、线框、技术选型草案、本体 `BR-ADMIN-*`
|
||||
|
||||
Workstream 建议名:`Store Admin Console`
|
||||
|
||||
---
|
||||
|
||||
## 任务总览
|
||||
|
||||
| # | Task Name | Owner | Priority | Status | Depends On |
|
||||
|---|-----------|-------|----------|--------|------------|
|
||||
| T0 | 登记 Store Admin FE 角色与写入锁 | PM Assistant / Docs | P0 | Done(本文+角色文件) | — |
|
||||
| T1 | 脚手架 admin(Vue3+Vite+Element Plus) | Store Admin FE | P0 | **Done** | — |
|
||||
| T2 | 实现 service-customers 聚合 API | Backend Core | P0 | **Done** | — |
|
||||
| T3 | 壳层登录 + 工作台 + 预约中心 | Store Admin FE | P0 | **Done(MVP)** | T1 |
|
||||
| T4 | 报告中心 + 回访线索 | Store Admin FE | P0 | **Done(MVP)** | T1 |
|
||||
| T5 | 客户列表对接新 API + 排班 + 设置 | Store Admin FE | P0 | **Done** | T1、T2 |
|
||||
| T6 | Phase A 端到端 QA 清单与冒烟 | Core Flow QA + Report Share QA | P0 | Ready(清单已出) | T3–T5 |
|
||||
|
||||
\*Ready = 字段完整,可排队;**未授权编码前不得改 `admin/` / `backend` 业务代码**。
|
||||
|
||||
---
|
||||
|
||||
## T1 · 脚手架 admin
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | 初始化门店 Web 后台工程 |
|
||||
| Role | `Store Admin Frontend` |
|
||||
| Owner Agent | Store Admin FE |
|
||||
| Paired QA | Core Flow QA |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | Coding |
|
||||
| Status | Ready(待授权) |
|
||||
| Repo/Path | `admin/**` |
|
||||
| Service Lock | `admin-scaffold` |
|
||||
| Depends On | 用户授权编码;选型确认 Vue3+Element Plus |
|
||||
| Due Date | TBD |
|
||||
| RC Included | TBD |
|
||||
| Output Link | TBD |
|
||||
| Blocker Owner | None |
|
||||
| Commit / RC | Not Frozen |
|
||||
| Architecture Review Required | No(选型文档已评) |
|
||||
| Data Model Review Required | No |
|
||||
| Access / Privacy Review Required | Yes(token 存储) |
|
||||
|
||||
**写入范围**:仅 `admin/` 新建;只读 `docs`、现网 API。
|
||||
**验收**:`npm run build` 通过;空壳可配 proxy 到 `localhost:8080`;侧栏 IA 与 PRD §0.4 一致且无禁入模块。
|
||||
**验证**:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/admin && npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## T2 · service-customers API
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | 新增服务客户聚合列表接口 |
|
||||
| Role | `Backend Core` |
|
||||
| Owner Agent | Backend Core |
|
||||
| Paired QA | Core Flow QA |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | Coding |
|
||||
| Status | Ready(待授权) |
|
||||
| Repo/Path | `backend/.../controller|service`(admin 客户域) |
|
||||
| Service Lock | `admin-service-customers` |
|
||||
| Depends On | 用户授权编码;PRD §7.6 |
|
||||
| ontologyRefs | `[entity:service_customer, action:admin_list_service_customers, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-003, rule:BR-ADMIN-004]` |
|
||||
| Architecture Review Required | Yes(新 `/api/admin/**` 前缀) |
|
||||
| Data Model Review Required | No(投影聚合,无新表优先) |
|
||||
| Access / Privacy Review Required | Yes(手机号脱敏、本店) |
|
||||
|
||||
**验收**:boss/staff 本店 200;customer 403;跨店不可见;无余额/次卡字段;搜索含宠物名;本体 entrypoint 升 anchored 且 `audit_drift` 无漂移。
|
||||
**验证**:
|
||||
|
||||
```bash
|
||||
cd /Users/apple/_src/petstore/backend && mvn -q test
|
||||
python3 /Users/apple/_src/petstore/docs/graph/validate_ontology.py docs
|
||||
python3 /Users/apple/_src/petstore/docs/graph/audit_drift.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## T3 · A.1 壳层 + 工作台 + 预约
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | 后台登录落点与预约中心 |
|
||||
| Role | `Store Admin Frontend` |
|
||||
| Owner Agent | Store Admin FE |
|
||||
| Paired QA | Core Flow QA |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | Coding |
|
||||
| Status | Ready(待授权) |
|
||||
| Repo/Path | `admin/src/**`(shell/workbench/appointments) |
|
||||
| Service Lock | `admin-a1-pages` |
|
||||
| Depends On | T1 |
|
||||
| ontologyRefs | `[entity:admin_console, entity:workbench, action:view_workbench, action:start_service, rule:BR-ADMIN-001, rule:BR-APPT-001]` |
|
||||
| Access / Privacy Review Required | Yes |
|
||||
|
||||
**验收**:对齐 PRD §1–3 与线框 A0–A2;staff/boss 可开始服务;customer 进不去;待办下钻参数正确。
|
||||
**验证**:按 PRD §3.6 / §2.6 手工冒烟 + 构建通过。
|
||||
|
||||
---
|
||||
|
||||
## T4 · A.2 报告 + 线索
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | 报告中心与回访线索页 |
|
||||
| Role | `Store Admin Frontend` |
|
||||
| Owner Agent | Store Admin FE |
|
||||
| Paired QA | Report Share QA |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | Coding |
|
||||
| Status | Ready(待授权) |
|
||||
| Repo/Path | `admin/src/**`(reports/leads) |
|
||||
| Service Lock | `admin-a2-pages` |
|
||||
| Depends On | T1 |
|
||||
| ontologyRefs | `[entity:report, entity:highlight_video, entity:report_lead, rule:BR-HL-001, rule:BR-LEAD-002, rule:BR-ADMIN-003]` |
|
||||
| Access / Privacy Review Required | Yes(report_token、openid 脱敏) |
|
||||
|
||||
**验收**:成片三态与失败文案;复制链接;`processing`>30min 可标异常;线索无完整 openid。
|
||||
**验证**:PRD §5.5 / §6.5 + Report Share QA 清单。
|
||||
|
||||
---
|
||||
|
||||
## T5 · A.3 客户 + 排班 + 设置
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | 客户列表排班与门店设置 |
|
||||
| Role | `Store Admin Frontend` |
|
||||
| Owner Agent | Store Admin FE |
|
||||
| Paired QA | Core Flow QA |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | Coding |
|
||||
| Status | Ready(待授权) |
|
||||
| Repo/Path | `admin/src/**`(customers/schedule/settings) |
|
||||
| Service Lock | `admin-a3-pages` |
|
||||
| Depends On | T1、T2 |
|
||||
| ontologyRefs | `[entity:service_customer, action:admin_list_service_customers, action:admin_manage_schedule, action:admin_update_settings, rule:BR-USER-001, rule:BR-ST-001]` |
|
||||
| Access / Privacy Review Required | Yes |
|
||||
|
||||
**验收**:客户页无余额列;排班与小程序一致;设置改时段生效;删员工仅 boss。
|
||||
**验证**:PRD §4.3 / §7.7 / §8.4。
|
||||
|
||||
---
|
||||
|
||||
## T6 · QA 冒烟
|
||||
|
||||
| 字段 | 内容 |
|
||||
|------|------|
|
||||
| Task Name | Phase A 后台端到端验收 |
|
||||
| Role | `Core Flow QA` / `Report Share QA` |
|
||||
| Owner Agent | Core Flow QA(主)+ Report Share QA(报告段) |
|
||||
| Paired QA | — |
|
||||
| Workstream | Store Admin Console |
|
||||
| Track | QA |
|
||||
| Status | Ready(待授权后随开发并行写清单) |
|
||||
| Repo/Path | `docs/qa-reports/**` |
|
||||
| Service Lock | `docs-qa-admin-phase-a` |
|
||||
| Depends On | T3–T5 可测增量 |
|
||||
| Access / Privacy Review Required | Yes |
|
||||
|
||||
**验收**:执行 PRD §11 剧本;输出证据链接。
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版:T0–T6;编码待用户授权 |
|
||||
458
门店管理后台-PhaseA-页面清单PRD.md
Normal file
@ -0,0 +1,458 @@
|
||||
# 门店 Web 后台 · Phase A 页面清单 PRD
|
||||
|
||||
> 状态:产品草案(仅文档,未编码)
|
||||
> 日期:2026-07-10
|
||||
> 上游:`docs/门店管理后台升级方案-对标宠老板.md`
|
||||
> 本体闸门:`rule:BR-ADMIN-004`(禁止会员卡/储值/收银/库存/寄养/押金/点餐偷渡)
|
||||
> 证据:本 PRD 描述的页面均为 `documented`;实现后升 `anchored`
|
||||
|
||||
---
|
||||
|
||||
## 0. 文档约定
|
||||
|
||||
### 0.1 目标与非目标
|
||||
|
||||
| 目标 | 非目标(Phase A) |
|
||||
|------|-------------------|
|
||||
| 老板用 PC 完成:看今日待办 → 管预约 → 管报告/成片链接 → 处理留资 → 改门店设置 | 收银、库存、寄养、会员升降级、次卡储值、押金、点餐、支付宝渠道、连锁同步 |
|
||||
| 与小程序共用同一套业务规则与 `/api/**` | 另造第二套状态机 |
|
||||
| 每页可追溯 `ontologyRefs` | 经营大盘 sparkline / 埋点统计闭环(属 Phase B) |
|
||||
|
||||
### 0.2 角色与范围
|
||||
|
||||
| 规则 | 本体 |
|
||||
|------|------|
|
||||
| 仅门店角色进入后台;**boss 与 staff 均可读写**(customer 禁止) | `rule:BR-ADMIN-001` |
|
||||
| 全部数据强制 `current.storeId` | `rule:BR-ADMIN-002` |
|
||||
| 后台可看公开 API 裁剪掉的内部字段;手机号默认脱敏 | `rule:BR-ADMIN-003` |
|
||||
| 范围闸门 | `rule:BR-ADMIN-004` |
|
||||
|
||||
`staff` 与 `boss` 在 Phase A **同等可编辑**本店数据;差异仅保留现网已有规则(例:删店、部分设置仅 boss——见设置页与 `rule:BR-STORE-001` / `rule:BR-USER-001`)。
|
||||
|
||||
### 0.3 端分工
|
||||
|
||||
| 场景 | 载体 |
|
||||
|------|------|
|
||||
| 拍照、填报告、现场履约 | **小程序**(既有) |
|
||||
| 开始服务 | **小程序与 Web 均可**(同 `POST /api/appointment/start`) |
|
||||
| 检索、复制链接、回访、改设置、看今日全局 | **Web 后台**(本 PRD) |
|
||||
|
||||
### 0.4 信息架构(锁定)
|
||||
|
||||
```text
|
||||
entity:admin_console
|
||||
├── /workbench 今日工作台(登录落点)
|
||||
├── /appointments 预约中心
|
||||
├── /schedule 排班占用
|
||||
├── /reports 服务报告
|
||||
├── /leads 回访线索
|
||||
├── /customers 服务客户
|
||||
└── /settings 门店设置(资料 / 服务类型 / 员工)
|
||||
```
|
||||
|
||||
侧栏不出现:收银台、库存、寄养、会员卡、次卡、积分、营销中心、微信商城。
|
||||
|
||||
### 0.5 全局交互范式(从对标截图借鉴)
|
||||
|
||||
1. 列表页:筛选区 + 查询/重置 +(可选)汇总条 + 表格
|
||||
2. 搜索合一:姓名 / 手机 / 宠物名(客户页)
|
||||
3. 手机号列表默认脱敏
|
||||
4. 敏感操作弹窗带规则提示(若有)
|
||||
5. 工作台待办可下钻并带筛选
|
||||
|
||||
---
|
||||
|
||||
## 1. 登录与壳层
|
||||
|
||||
**页面 ID**:`A0-shell`
|
||||
**路由**:`/*`(布局)
|
||||
**ontologyRefs**:`[entity:admin_console, entity:current_user, entity:session_token, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-AUTH-001]`
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 登录 | 复用现有 `sessionToken`(老板微信登录 / 现网登录策略);无 token → 登录页 |
|
||||
| 壳层 | 左导航 + 顶栏(店名、角色、退出)+ 内容区 |
|
||||
| 退出 | 清本地 token;服务端吊销为 `action:logout` gap,A 期不做 |
|
||||
| 无权限 | customer token 访问 → 403 文案「请使用老板账号登录」 |
|
||||
|
||||
**API 复用**:`GET /api/user/info`(`action:get_user_info`)
|
||||
|
||||
---
|
||||
|
||||
## 2. 今日工作台
|
||||
|
||||
**页面 ID**:`A1-workbench`
|
||||
**路由**:`/workbench`(默认首页)
|
||||
**ontologyRefs**:`[entity:workbench, entity:workbench_todo_item, action:view_workbench, action:drilldown_workbench_todo, event:workbench_viewed, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-004]`
|
||||
|
||||
### 2.1 布局
|
||||
|
||||
```text
|
||||
顶栏 4 指标卡
|
||||
今日待办条(或登录弹层关闭后的常驻摘要)
|
||||
报告漏斗四格(可先占位「—」)
|
||||
异常列表(成片失败 / 待回访 最近 5 条)
|
||||
右侧:门店卡片(店名、营业时段摘要、员工数)+ 帮助链接
|
||||
```
|
||||
|
||||
### 2.2 顶栏指标卡
|
||||
|
||||
| 卡 | 口径 | 点击下钻 |
|
||||
|----|------|----------|
|
||||
| 今日预约 | 今日 `appointmentTime` 落在当天的预约数(含各状态) | `/appointments?date=today` |
|
||||
| 今日完成 | 今日变为 `done` 的预约数 | `/appointments?status=done&date=today` |
|
||||
| 待回访 | `remind_status=pending` 且 `remindDate≤今天` | `/leads?due=today` |
|
||||
| 成片失败 | 本店报告 `highlight_video_status=failed`(可限近 7 日) | `/reports?highlightStatus=failed` |
|
||||
|
||||
### 2.3 今日待办(弹层 + 常驻)
|
||||
|
||||
| kind | 文案 | count 口径 | 下钻 |
|
||||
|------|------|------------|------|
|
||||
| `today_appointments` | 今日预约 | 同「今日预约」卡,可分子状态:待开始 / 服务中 | 预约列表 |
|
||||
| `pending_report` | 待出报告 | `status=doing` 且尚无报告 | 预约列表 `status=doing&missingReport=1` |
|
||||
| `highlight_anomaly` | 成片异常 | `failed` + 可选 `processing` 超时(阈值产品定,默认 30min) | 报告列表 |
|
||||
| `pending_lead` | 待回访 | 同「待回访」卡 | 线索列表 |
|
||||
|
||||
弹层:首次进入当日可弹;勾选「今日不再提醒」写入 localStorage;「确定」关闭。
|
||||
|
||||
**不对标宠老板**:寄养、临期商品、库存、待发货、售后。
|
||||
|
||||
### 2.4 报告漏斗四格(差异化)
|
||||
|
||||
| 格 | 口径 | A 期无埋点时 |
|
||||
|----|------|----------------|
|
||||
| 今日已发报告 | 今日新建报告数 | 可算 |
|
||||
| 已打开 | `report_open` 去重 | 显示「—」,链到 B1 说明 |
|
||||
| 已留资 | 今日新增 lead | 可算 |
|
||||
| 报告转预约 | 从报告页带来的预约 | 无可靠归因则「—」 |
|
||||
|
||||
### 2.5 API / 聚合
|
||||
|
||||
优先前端或 BFF 聚合现有接口;若延迟高,再补 `GET /api/admin/workbench`(实现时同步本体)。
|
||||
|
||||
复用候选:`/api/appointment/list`、`/api/report/list`、`/api/report/leads`、`/api/store/get`(后台可用内部字段,`BR-ADMIN-003`)。
|
||||
|
||||
### 2.6 验收
|
||||
|
||||
- [ ] 老板登录后默认进入工作台
|
||||
- [ ] 四指标与待办数字与列表下钻一致
|
||||
- [ ] 待办不含库存/寄养类卡片
|
||||
- [ ] customer 无法打开
|
||||
- [ ] **staff 与 boss 均可进入并操作本店工作台**
|
||||
|
||||
---
|
||||
|
||||
## 3. 预约中心
|
||||
|
||||
**页面 ID**:`A2-appointments`
|
||||
**路由**:`/appointments`
|
||||
**ontologyRefs**:`[entity:appointment, action:admin_list_appointments, action:start_service, action:transition_appointment_status, action:get_appointment_detail, rule:BR-APPT-001, rule:BR-APPT-002, rule:BR-APPT-003, rule:BR-ADMIN-001, rule:BR-ADMIN-002]`
|
||||
|
||||
### 3.1 筛选
|
||||
|
||||
| 筛选项 | 说明 |
|
||||
|--------|------|
|
||||
| 日期 | 单日 / 区间;支持 `today` |
|
||||
| 状态 | `new` / `doing` / `done` / `cancel` / 全部 |
|
||||
| 技师 | 本店 staff/boss 列表(可空=全部) |
|
||||
| 关键词 | 宠主名 / 手机 / 宠物名(若现网 list 不支持,A 期可先前端滤或补 query) |
|
||||
| 待出报告 | `doing` 且无报告(工作台下钻) |
|
||||
|
||||
### 3.2 列表列
|
||||
|
||||
| 列 | 说明 |
|
||||
|----|------|
|
||||
| 预约时间 | |
|
||||
| 状态 | 文案:待开始 / 服务中 / 已完成 / 已取消 |
|
||||
| 宠主 | 名 + 脱敏手机 |
|
||||
| 宠物 | 名 / 类型 |
|
||||
| 服务类型 | |
|
||||
| 技师 | `assignedUser` 显示名 |
|
||||
| 报告 | 无 / 有(链到报告详情或报告中心) |
|
||||
| 操作 | 详情;**开始服务**(`new`→调 `POST /api/appointment/start`,Web 可点);`new`/`doing`→取消(规则同现网) |
|
||||
|
||||
> **产品决策(2026-07-10)**:Web 上允许「开始服务」。拍照/填报告仍引导回小程序;开始服务本身不强制跳转。
|
||||
|
||||
### 3.3 详情抽屉/页
|
||||
|
||||
只读为主:预约字段、状态流转记录(若无则只显示当前状态)、关联报告入口。
|
||||
**不在 Web 做拍照填报告**(回小程序)。
|
||||
|
||||
### 3.4 状态机(禁止另造)
|
||||
|
||||
仅允许:`new→doing`、`new→cancel`、`doing→done`(由提交报告触发)、`doing→cancel`(若产品允许)。
|
||||
非法迁移返回现网业务码。本体:`rule:BR-APPT-001`。
|
||||
|
||||
### 3.5 API 复用
|
||||
|
||||
- `GET /api/appointment/list`
|
||||
- `GET /api/appointment/detail`
|
||||
- `POST /api/appointment/start`(**Web 可调用**)
|
||||
- `PUT /api/appointment/status`
|
||||
|
||||
### 3.6 验收
|
||||
|
||||
- [ ] 筛选 + 下钻参数生效
|
||||
- [ ] 跨店数据不可见
|
||||
- [ ] 非法状态迁移被拒
|
||||
- [ ] **staff / boss 均可在 Web 点击「开始服务」并成功 `new→doing`**
|
||||
- [ ] 无「待门店确认接单」状态(与宠老板差异,保持宠小它口径)
|
||||
|
||||
---
|
||||
|
||||
## 4. 排班占用
|
||||
|
||||
**页面 ID**:`A6-schedule`
|
||||
**路由**:`/schedule`
|
||||
**ontologyRefs**:`[entity:schedule_block, entity:appointment, action:admin_manage_schedule, action:day_agenda, action:create_schedule_block, action:delete_schedule_block, rule:BR-SCH-001, rule:BR-ADMIN-001, rule:BR-ADMIN-002]`
|
||||
|
||||
### 4.1 视图
|
||||
|
||||
- 日视图时间轴(半小时档)
|
||||
- 叠加:线上预约占用 + `walk_in` / `blocked` 手动占用
|
||||
- 操作:新增占用、删除本店占用;不可改他店
|
||||
|
||||
### 4.2 API 复用
|
||||
|
||||
- `GET /api/schedule/day`
|
||||
- `POST /api/schedule/block`
|
||||
- `DELETE` 对应删除接口
|
||||
|
||||
### 4.3 验收
|
||||
|
||||
- [ ] 与小程序号源占用一致
|
||||
- [ ] staff/boss 同店可操作;跨店 403
|
||||
|
||||
---
|
||||
|
||||
## 5. 服务报告
|
||||
|
||||
**页面 ID**:`A3-reports`
|
||||
**路由**:`/reports`
|
||||
**ontologyRefs**:`[entity:report, entity:highlight_video, entity:highlight_fail_reason, action:admin_list_reports, action:generate_highlight, action:get_report_by_token, rule:BR-RPT-002, rule:BR-RPT-005, rule:BR-RPT-006, rule:BR-HL-001, rule:BR-HL-002, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-003]`
|
||||
|
||||
### 5.1 筛选
|
||||
|
||||
日期、成片状态(`processing` / `done` / `failed` / 全部)、关键词(宠物名/宠主)
|
||||
|
||||
### 5.2 列表列
|
||||
|
||||
| 列 | 说明 |
|
||||
|----|------|
|
||||
| 提交时间 | |
|
||||
| 预约时间 / 宠物 / 服务 | |
|
||||
| 成片状态 | 三态 + 失败原因短标签(`HighlightFailReason`) |
|
||||
| 操作 | 详情;复制公开链接;失败/可重试时「重新生成」 |
|
||||
|
||||
### 5.3 详情
|
||||
|
||||
- 报告公开页预览入口(新开 H5)
|
||||
- 前后/过程媒体缩略图(只读)
|
||||
- 成片播放(若 `done`)
|
||||
- 复制 `report_token` 链接(完整 token 仅后台展示,日志仍 hash,`BR-RPT-007`)
|
||||
- **不在此页改报告正文**(报告提交后锁死,`BR-RPT-*`)
|
||||
|
||||
### 5.4 API 复用
|
||||
|
||||
- `GET /api/report/list`
|
||||
- `GET /api/report/get`(后台带鉴权,可内部字段)
|
||||
- `POST /api/report/highlight/start`
|
||||
|
||||
### 5.5 验收
|
||||
|
||||
- [ ] 成片三态与失败文案无技术词
|
||||
- [ ] 复制链接可用;公开页仍受 `BR-RPT-006` 裁剪
|
||||
- [ ] 仅 boss/staff 可触发成片(`BR-HL-001`)
|
||||
|
||||
---
|
||||
|
||||
## 6. 回访线索
|
||||
|
||||
**页面 ID**:`A4-leads`
|
||||
**路由**:`/leads`
|
||||
**ontologyRefs**:`[entity:report_lead, action:admin_list_leads, rule:BR-LEAD-001, rule:BR-LEAD-002, rule:BR-LEAD-003, rule:BR-ADMIN-001, rule:BR-ADMIN-002]`
|
||||
|
||||
### 6.1 筛选
|
||||
|
||||
`remindStatus`(pending / 全部)、到期(≤今天)、日期区间
|
||||
|
||||
### 6.2 列表列
|
||||
|
||||
宠物名、服务、脱敏手机、建议日期、状态、微信绑定(是/否 + 脱敏 openid 若有)、操作:查看关联报告公开页
|
||||
|
||||
### 6.3 不做(A 期)
|
||||
|
||||
跟进备注 CRM、短信群发、改等级、发券
|
||||
|
||||
### 6.4 API 复用
|
||||
|
||||
- `GET /api/report/leads`
|
||||
|
||||
### 6.5 验收
|
||||
|
||||
- [ ] 无完整 openid/unionid
|
||||
- [ ] 跨店 403
|
||||
- [ ] 工作台「待回访」下钻数字一致
|
||||
|
||||
---
|
||||
|
||||
## 7. 服务客户
|
||||
|
||||
**页面 ID**:`A5-customers`
|
||||
**路由**:`/customers`
|
||||
**ontologyRefs**:`[entity:service_customer, entity:pet, entity:user, action:admin_list_service_customers, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-003, rule:BR-ADMIN-004]`
|
||||
|
||||
### 7.1 定位
|
||||
|
||||
服务经营客户投影,**不是**会员资产台账。
|
||||
|
||||
### 7.2 筛选与搜索
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 来源 | 全部 / 预约 / 报告留资 |
|
||||
| 有待回访 | 开关 |
|
||||
| 最近到店 | 日期区间 |
|
||||
| 搜索 | 姓名 / 手机 / 宠物名 |
|
||||
|
||||
### 7.3 汇总条
|
||||
|
||||
`客户数` · `待回访数` · `本月到店数`
|
||||
**禁止**:总余额、押金、积分
|
||||
|
||||
### 7.4 列表列
|
||||
|
||||
客户(头像/名/脱敏手机)| 宠物(标签,多宠)| 最近到店 | 最近报告 | 留资状态 | 来源 | 操作(详情 / 报告 / 回访)
|
||||
|
||||
### 7.5 详情
|
||||
|
||||
基础信息、宠物列表、最近预约、最近报告、相关线索。
|
||||
**不做**:充值、改会员等级、导入持卡会员、次卡。
|
||||
|
||||
### 7.6 API(产品决策:新 API)
|
||||
|
||||
**新建**聚合接口(实现时同步本体 `anchored`):
|
||||
|
||||
```text
|
||||
GET /api/admin/service-customers
|
||||
Query: source, hasPendingLead, lastVisitFrom, lastVisitTo, q, page, pageSize
|
||||
Auth: boss | staff,storeId=current.storeId
|
||||
Response: { total, summary: { customerCount, pendingLeadCount, monthVisitCount }, list: ServiceCustomer[] }
|
||||
```
|
||||
|
||||
- 对应动作:`action:admin_list_service_customers`
|
||||
- 对应实体:`entity:service_customer`
|
||||
- **不做**前端拼装多接口作为正式方案(可临时 mock,上线以新 API 为准)
|
||||
|
||||
可选后续:`GET /api/admin/service-customers/{userId}` 详情。
|
||||
|
||||
### 7.7 验收
|
||||
|
||||
- [ ] 无余额/次卡/积分列与汇总
|
||||
- [ ] 搜索含宠物名
|
||||
- [ ] 侧栏无「会员卡/次卡」入口
|
||||
|
||||
---
|
||||
|
||||
## 8. 门店设置
|
||||
|
||||
**页面 ID**:`A7-settings`
|
||||
**路由**:`/settings`(Tab:资料 | 服务类型 | 员工)
|
||||
**ontologyRefs**:`[entity:store, entity:service_type, entity:user, action:admin_update_settings, rule:BR-STORE-001, rule:BR-STORE-003, rule:BR-ST-001, rule:BR-USER-001, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-003]`
|
||||
|
||||
### 8.1 资料 Tab
|
||||
|
||||
字段:名称、电话、地址、坐标、简介、logo、`bookingDayStart` / `bookingLastSlotStart`
|
||||
后台可显示 `inviteCode`(`BR-ADMIN-003`);公开 `GET /api/store/get` 仍裁剪(`BR-STORE-003`)
|
||||
|
||||
API:`GET/PUT` 现有 store 接口
|
||||
|
||||
### 8.2 服务类型 Tab(瘦身表单)
|
||||
|
||||
| 字段 | 必填 |
|
||||
|------|------|
|
||||
| 名称 | ✓ |
|
||||
| 适用宠物:不限/猫/犬 | 若字段未上线,A 期可先只做名称+排序+启用 |
|
||||
| 排序 | |
|
||||
| 预约可选(启用) | |
|
||||
| 备注 | |
|
||||
|
||||
**不做**:多规格、价格、条码、押金、商城上架、富文本(见对标 §11.8)
|
||||
|
||||
系统默认类型不可改删(`BR-ST-001`)
|
||||
|
||||
API:现有 `service-type/*`
|
||||
|
||||
### 8.3 员工 Tab
|
||||
|
||||
列表、创建员工、删除(仅 boss,同店)
|
||||
展示邀请码(来自门店)供复制
|
||||
|
||||
API:`user/staff-list`、`create-staff`、删除接口
|
||||
|
||||
### 8.4 验收
|
||||
|
||||
- [ ] 改营业时段后号源边界变化(与小程序一致)
|
||||
- [ ] 公开门店接口仍无 inviteCode
|
||||
- [ ] 服务类型表单无价格字段
|
||||
|
||||
---
|
||||
|
||||
## 9. 页面—本体—API 总表
|
||||
|
||||
| 页面 | 主 Action | 主 Entity | 主要复用 API |
|
||||
|------|-----------|-----------|--------------|
|
||||
| A0 壳层 | `get_user_info` | `admin_console` | `/api/user/info` |
|
||||
| A1 工作台 | `view_workbench` | `workbench` | list 聚合 |
|
||||
| A2 预约 | `admin_list_appointments` | `appointment` | `/api/appointment/*` |
|
||||
| A6 排班 | `admin_manage_schedule` | `schedule_block` | `/api/schedule/*` |
|
||||
| A3 报告 | `admin_list_reports` | `report` / `highlight_video` | `/api/report/*` |
|
||||
| A4 线索 | `admin_list_leads` | `report_lead` | `/api/report/leads` |
|
||||
| A5 客户 | `admin_list_service_customers` | `service_customer` | **已实现** `GET /api/admin/service-customers` |
|
||||
| A7 设置 | `admin_update_settings` | `store` / `service_type` / `user` | store/service-type/user |
|
||||
|
||||
---
|
||||
|
||||
## 10. 实现分期建议(仍属 Phase A 内)
|
||||
|
||||
| 迭代 | 交付 | 说明 |
|
||||
|------|------|------|
|
||||
| A.1 | 壳层 + 工作台 + 预约列表/详情 | 登录落点可用 |
|
||||
| A.2 | 报告中心 + 线索 | 差异化闭环 |
|
||||
| A.3 | 客户瘦身列表 + 排班 + 设置 | MVP 验收全集 |
|
||||
|
||||
编码前须:声明 Owner(`Store Admin FE` + `Backend Core`)、Service Lock、Paired QA、写入 `.agents` 角色锁。
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收剧本(端到端)
|
||||
|
||||
1. 老板登录 → 默认工作台,见今日待办
|
||||
2. 点「待出报告」→ 预约列表仅 doing 且无报告
|
||||
3. 打开某已完成单的报告 → 复制公开链接 → 无痕浏览器打开,字段符合公开裁剪
|
||||
4. 线索页处理一条待回访(至少能打开关联报告)
|
||||
5. 设置里改营业结束时间并保存 → 小程序号源反映变化
|
||||
6. 全程无会员余额/收银/库存入口
|
||||
|
||||
---
|
||||
|
||||
## 12. 开放问题(已决策)
|
||||
|
||||
| # | 问题 | 决策(2026-07-10) |
|
||||
|---|------|-------------------|
|
||||
| Q1 | staff 是否可登录后台? | **可进且可编辑**(与 boss 同店读写;删店等仍仅 boss) |
|
||||
| Q2 | Web 上是否允许「开始服务」? | **允许**;拍照/填报告仍回小程序 |
|
||||
| Q3 | 客户列表前端聚合还是新 API? | **新 API**:`GET /api/admin/service-customers` |
|
||||
| Q4 | `processing` 超时多久算异常? | **30 分钟**(已锁定) |
|
||||
| Q5 | 技术选型(Vue/React admin)? | **推荐** Vue3+Vite+Element Plus + `admin/`,见 `docs/门店管理后台-技术选型草案.md`(编码前口头确认) |
|
||||
|
||||
---
|
||||
|
||||
## 13. 修订记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版:基于本体 admin 预录入 + 宠老板对标结论 |
|
||||
| 2026-07-10 | 决策落地:staff 可编辑;Web 可开始服务;客户列表新 API |
|
||||
| 2026-07-10 | 线框/选型/编码 brief 已出;Q4=30min;Q5 推荐 Vue3 admin |
|
||||
| 2026-07-10 | 编码启动:`admin/` MVP + `GET /api/admin/service-customers` |
|
||||
91
门店管理后台-技术选型草案.md
Normal file
@ -0,0 +1,91 @@
|
||||
# 门店 Web 后台 · 技术选型草案(Q5)
|
||||
|
||||
> 状态:**推荐方案**(编码前口头确认即可;不阻塞 PRD)
|
||||
> 日期:2026-07-10
|
||||
> 上游:`docs/门店管理后台-PhaseA-页面清单PRD.md`、`docs/门店管理后台升级方案-对标宠老板.md`
|
||||
> 约束:`rule:BR-ADMIN-004`;与小程序共用 `/api/**` 与 service 规则
|
||||
|
||||
---
|
||||
|
||||
## 1. 结论(推荐)
|
||||
|
||||
| 项 | 选择 |
|
||||
|----|------|
|
||||
| 形态 | **独立 SPA**:仓库内新建 `admin/`(与 `frontend/` 小程序并列) |
|
||||
| 框架 | **Vue 3 + Vite + TypeScript** |
|
||||
| UI | **Element Plus**(中后台表格/筛选/抽屉成熟) |
|
||||
| 路由/状态 | Vue Router + Pinia |
|
||||
| 鉴权 | 复用现网 `sessionToken`;请求头与小程序一致 |
|
||||
| API | axios/fetch 封装;**禁止**另造业务状态机 |
|
||||
| 部署 | 静态资源独立域名或 `/admin/` path;Nginx 反代 `/api` → 现网后端 |
|
||||
|
||||
**不推荐**:在 uni-app 里硬撑 PC 后台;用 React 另起技术栈(团队已是 Vue 3);把 admin 塞进 `frontend/` 与小程序同构建。
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案对比
|
||||
|
||||
| 方案 | 优点 | 缺点 | 结论 |
|
||||
|------|------|------|------|
|
||||
| A. monorepo `admin/` + Vue3 + Element Plus | 与现网 Vue 技能复用;桌面组件全;与小程序构建隔离 | 多一个前端工程 | **推荐** |
|
||||
| B. uni-app H5 加宽屏布局 | 少一个仓 | 表格/筛选体验差;与门店端页面耦合 | 否 |
|
||||
| C. React + Ant Design Pro | 中后台模板多 | 与现网 Vue 分裂;学习/规范成本 | 否(除非专招 React) |
|
||||
| D. 纯后端模板(Thymeleaf 等) | 无前端仓 | 交互与迭代慢;难复用 H5 预览 | 否 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 目录与边界(建议)
|
||||
|
||||
```text
|
||||
petstore/
|
||||
├── frontend/ # uni-app 小程序 + H5(既有)
|
||||
├── admin/ # Web 门店后台(新建)
|
||||
│ ├── src/views/ # workbench / appointments / …
|
||||
│ ├── src/api/ # 仅调 /api/**
|
||||
│ └── …
|
||||
└── backend/ # 共用;新增 /api/admin/service-customers 等
|
||||
```
|
||||
|
||||
| Owner | 写入 |
|
||||
|-------|------|
|
||||
| Store Admin FE(新建角色) | `admin/**` |
|
||||
| Backend Core | `GET /api/admin/service-customers` 及工作台聚合(若做) |
|
||||
| Report Media Backend | 报告列表/成片重试相关若需扩展时 |
|
||||
| Store Miniapp FE | **不写** admin;小程序履约页保持原边界 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 与现网契约
|
||||
|
||||
- 登录:`GET /api/user/info` 校验 `boss|staff`;customer → 403 文案。
|
||||
- 预约开始服务:Web 调 `POST /api/appointment/start`(与小程序同规则)。
|
||||
- 客户列表:**新** `GET /api/admin/service-customers`(见 PRD §7.6)。
|
||||
- 公开报告预览:新开现有 H5/`report-view`,不在 admin 内嵌改报告。
|
||||
- 本体:实现时把 planned entrypoint 升 `anchored`,跑 `validate_ontology` + `audit_drift`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 非功能
|
||||
|
||||
| 项 | Phase A 口径 |
|
||||
|----|----------------|
|
||||
| 浏览器 | 近两年版 Chrome / Edge;不做 IE |
|
||||
| 响应式 | 桌面优先;平板可用;不做手机后台 |
|
||||
| 国际化 | 仅中文 |
|
||||
| 主题 | 简洁中后台即可;不对标宠老板视觉像素级 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 开放确认
|
||||
|
||||
- [x] Q5 推荐:**Vue3 + Vite + Element Plus + `admin/`**
|
||||
- [ ] 编码启动前负责人口头确认(或提出改 React / 改目录名)
|
||||
- [x] Q4 成片 `processing` 超时:**30 分钟** 算异常(工作台/报告筛选)
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-07-10 | 首版推荐方案 |
|
||||
605
门店管理后台升级方案-对标宠老板.md
Normal file
@ -0,0 +1,605 @@
|
||||
# 门店管理后台升级方案(对标宠老板 · 差异化落地)
|
||||
|
||||
> 参考:[宠老板介绍页](https://www.chonglaoban.cn/about/chonglaoban) · [宠老板官网](https://www.chonglaoban.cn/)
|
||||
> 状态:产品草案(仅文档,未进入编码)
|
||||
> 日期:2026-07-10
|
||||
> 对齐:`docs/.agents/common-context.md`、本体 P1/P2 gap 占位、`架构与产品评估报告-2026-07-05.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话结论
|
||||
|
||||
**不要照搬宠老板做「全店收银进销存 ERP」**;应做 **「服务经营后台」**:把宠小它已跑通的预约 → 服务 → 报告 → 成片 → 留资闭环,升级为老板可在 **PC/平板 Web 后台** 高效管店,并在此基础上按阶段补 CRM / 经营看板 / 会员套餐。
|
||||
|
||||
宠老板赢在「店内交易与库存」;宠小它应赢在「服务体验与复购触达」。后台是放大器,不是换赛道。
|
||||
|
||||
---
|
||||
|
||||
## 2. 宠老板能力拆解(对标对象)
|
||||
|
||||
来源:官网「系统介绍 / 宠老板介绍」公开卖点。
|
||||
|
||||
| 模块群 | 代表能力 | 本质 |
|
||||
|--------|----------|------|
|
||||
| 基础 | 收银、会员、库存、店铺/分店/店员、积分、寄养、活体/商品销售 | 店内交易 + 进销存 + 多业态 |
|
||||
| 智能 | 统计报表、流水、账本、提醒、预约、提成、决策助手、微信推送 | 经营分析 + 激励 + 触达 |
|
||||
| 增值 | 进货商城、微店/小程序商城、宠付宝、短信、推广工具 | 支付 / 供应链 / 营销平台 |
|
||||
| 形态 | Windows/Mac/iOS/Android 收银与移动端;云端连锁;公众号+小程序 | 多端收银一体 |
|
||||
|
||||
**他们的主叙事**:高效管店、收银结账、库存盘点、寄养结算、连锁调拨、手机看店。
|
||||
|
||||
---
|
||||
|
||||
## 3. 宠小它现状(我们已有什么)
|
||||
|
||||
| 能力 | 现状 | 载体 |
|
||||
|------|------|------|
|
||||
| 预约状态机 / 号源 / 排班占用 | ✅ | 小程序门店端 |
|
||||
| 服务报告 + 前后照片 + 成片 | ✅ 差异化核心 | 小程序 + 公开 H5 |
|
||||
| 报告分享 / 打开埋点 / 留资回访 | ✅ 部分 | 公开页 + Leads |
|
||||
| 门店设置 / 员工 / 服务类型 | ✅ 基础 | 小程序「我的」 |
|
||||
| 会员卡 / 储值 / 套餐 | ❌ gap 占位 | 本体未展开 |
|
||||
| 收银 / 库存 / 寄养 / 提成 / 进货 | ❌ | 明确不在 P0 |
|
||||
| **独立 Web 门店管理后台** | ❌ | 老板只能在小程序里点 |
|
||||
|
||||
痛点:老板在手机小程序里做「经营管理工作台」效率低(列表筛选弱、多开对比难、报表难看、批量操作弱)。这正是「搞一套门店管理后台」的第一性需求,而不是先上收银。
|
||||
|
||||
---
|
||||
|
||||
## 4. 定位对比(必须先定)
|
||||
|
||||
```text
|
||||
宠老板:店内交易操作系统(POS + 进销存 + 会员资产)
|
||||
宠小它:服务体验与复购操作系统(预约履约 + 报告内容 + 触达转化)
|
||||
```
|
||||
|
||||
| 维度 | 宠老板 | 宠小它应坚持 |
|
||||
|------|--------|--------------|
|
||||
| 核心对象 | 订单/SKU/库存/会员余额 | 预约/报告/成片/线索 |
|
||||
| 老板日常 | 收银、盘点、寄养结算 | 排班接单、出报告、看转化、回访 |
|
||||
| 宠主触点 | 微店商城、消费通知 | 报告页、预约、提醒、口碑成片 |
|
||||
| 差异化 | 行业全功能 + 进货商城 + 保险 | **洗美报告成片 + 分享转化**(对方没有的主线) |
|
||||
| 风险 | 功能面极宽,我们短期做不全 | 若跟风收银库存,会稀释主线且工期爆炸 |
|
||||
|
||||
**建议对外话术**:
|
||||
「宠小它门店后台 = 洗美服务经营台;不是又一个宠老板。」
|
||||
|
||||
---
|
||||
|
||||
## 5. 门店管理后台:建议形态
|
||||
|
||||
### 5.1 产品形态
|
||||
|
||||
| 项 | 建议 |
|
||||
|----|------|
|
||||
| 形态 | **Web 管理端**(桌面优先,平板可用);与现有 uni-app 小程序并存 |
|
||||
| 登录 | 复用现有 `sessionToken`;**boss / staff 均可进且可编辑**本店(customer 禁止);删店/员工管理等仍仅 boss |
|
||||
| 数据范围 | 严格 `store_id`;首期单店,连锁/分店放到更后 |
|
||||
| 技术倾向(待架构拍板) | 独立 `admin` 前端(Vue/React + 中后台组件库)调现有 `/api/**`;少造第二套业务规则 |
|
||||
| 不做什么(首期) | 原生收银硬件、钱箱小票机、进货商城、活体交易、保险、宠付宝 |
|
||||
|
||||
### 5.2 信息架构(IA)草案
|
||||
|
||||
```text
|
||||
门店后台
|
||||
├── 今日工作台 # 今日预约、进行中、待出报告、留资待回访、成片失败
|
||||
├── 预约与排班 # 日/周视图、号源、手动占用、改派/取消
|
||||
├── 服务报告 # 列表、详情、成片状态、重新生成、分享链接管理
|
||||
├── 客户与宠物 # 宠主/宠物档案(服务过的)、历史报告入口
|
||||
├── 回访线索 # Leads 池、提醒日期、退订状态、跟进备注(可后补)
|
||||
├── 经营数据 # 打开率、成片成功率、留资转化、预约完成率(依赖埋点落地)
|
||||
├── 门店设置 # 资料、营业时段、服务类型、员工与邀请码
|
||||
└──(后期)会员与资产 # 次卡/储值/套餐 — 对标宠老板会员,但不做库存
|
||||
```
|
||||
|
||||
小程序保留:**到店履约现场操作**(拍照、填报告;亦可开始服务)。
|
||||
Web 后台主攻:**经营、检索、批量、报表、设置**;**允许「开始服务」**(`new→doing`),拍照/填报告仍引导回小程序。
|
||||
|
||||
---
|
||||
|
||||
## 6. 分阶段路线图(强烈建议按此砍)
|
||||
|
||||
### Phase A — 管理台 MVP(优先做,4–8 周量级视人力)
|
||||
|
||||
**目标**:老板愿意每天打开 Web 后台,而不是只在小程序里翻。
|
||||
|
||||
| 模块 | 能力 | 依赖 |
|
||||
|------|------|------|
|
||||
| A1 工作台 | 登录落点;「今日待办」弹层(预约/待报告/成片异常/待回访)+ 顶栏 4 指标卡;卡片下钻列表 | 现有 API 聚合 |
|
||||
| A2 预约中心 | 列表筛选(状态/日期/技师)、详情、取消/开始(权限同现网) | Appointment* |
|
||||
| A3 报告中心 | 报告列表、公开链接复制、成片三态、触发重生成 | Report* / Highlight |
|
||||
| A4 线索中心 | 回访池、脱敏展示、建议日期 | ReportLead* |
|
||||
| A5 设置中心 | 门店资料、营业时段、服务类型、员工 | Store/User/ServiceType |
|
||||
| A6 排班 | 日视图 + 手动占用 | Schedule* |
|
||||
|
||||
**验收**:老板用 PC 完成「看今日单 → 查报告链接 → 处理一条留资 → 改营业时间」,无需打开小程序。
|
||||
|
||||
**刻意不做**:收银、库存、会员储值、提成、连锁。
|
||||
|
||||
### Phase B — 经营增强(对齐原 P1)
|
||||
|
||||
| 模块 | 能力 | 对标宠老板哪块 |
|
||||
|------|------|----------------|
|
||||
| B1 埋点可统计 | 报告打开/再打开、成片成败、播放/保存、留资 | 统计报表(子集) |
|
||||
| B2 经营看板 | 日/周:预约完成率、报告发送率、打开率、留资转化 | 数据分析 |
|
||||
| B3 智能提醒 | 建议洗护到期、留资到期未跟进 | 智能提醒 |
|
||||
| B4 客户档案增强 | 宠主合并规则、宠物疫苗/驱虫字段(可选轻量) | 会员/宠物档案(轻量) |
|
||||
| B5 消息触达 | 订阅消息/模板去重(与公众号文档对齐) | 微信推送 |
|
||||
|
||||
### Phase C — 选择性「宠老板化」(原 P2,需单独立项)
|
||||
|
||||
仅在 A/B 稳定、数据口径清楚后评估:
|
||||
|
||||
| 候选 | 建议 | 理由 |
|
||||
|------|------|------|
|
||||
| 次卡 / 套餐 / 储值 | 可做 | 与洗美复购强相关;本体已有 gap 占位 |
|
||||
| 技师提成 | 谨慎 | 规则复杂,易扯皮;可先「按单人工核算导出」 |
|
||||
| 寄养结算 | 不做或独立产品线 | 业态不同,会拖垮主线 |
|
||||
| 商品库存 / 进销存 | 默认不做 | 与内容/服务差异化无关;竞品极强 |
|
||||
| 收银 / 宠付宝类 | 默认不做 | 支付合规与硬件成本高 |
|
||||
| 连锁分店 / 调拨 | 很后 | 先把单店后台做透 |
|
||||
| 微店商城 | 不做标配 | 我们已有小程序预约+报告;商城是另一场战争 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 功能对标矩阵(决策用)
|
||||
|
||||
| 宠老板能力 | 宠小它态度 | 落点阶段 |
|
||||
|------------|------------|----------|
|
||||
| 收银管理 | 不做 | — |
|
||||
| 会员管理 | 后期轻量(次卡/档案) | C |
|
||||
| 库存 / 进货商城 | 不做 | — |
|
||||
| 寄养管理 | 不做(除非单独立项) | — |
|
||||
| 店员管理 | 已有 → 迁到 Web | A |
|
||||
| 分店 / 连锁 | 暂缓 | C+ |
|
||||
| 积分系统 | 暂缓 | C |
|
||||
| 预约管理 | 已有 → Web 强化 | A |
|
||||
| 统计报表 / 流水 | 做「服务经营指标」非流水账 | B |
|
||||
| 提成计算 | 暂缓 | C |
|
||||
| 微信推送 | 与现有触达文档合并 | B |
|
||||
| 微店小程序商城 | 不跟风;强化报告+预约 | — |
|
||||
| 活体交易 / 保险 | 不做 | — |
|
||||
| **服务报告成片分享** | **我们独有,后台要管好** | A 核心 |
|
||||
| **留资回访** | **我们独有主线,后台要管好** | A 核心 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 与现有文档 / 本体的关系
|
||||
|
||||
| 已有条目 | 后台如何消费 |
|
||||
|----------|--------------|
|
||||
| `entity:appointment` / `report` / `report_lead` | A 期列表与工作台主数据 |
|
||||
| `entity:analytics_dashboard`(gap) | B 期落地为真实看板实体 |
|
||||
| `entity:membership` / `stored_value` / `service_package`(gap) | C 期再展开字段 |
|
||||
| P1-4 埋点可统计 | B1 的前置条件 |
|
||||
| P2-5 经营分析看板 | 并入后台 B2,不再单独飘着 |
|
||||
|
||||
**协作约束**:
|
||||
|
||||
- 本期若启动后台,需新建 Workstream(建议 Owner:`Store Admin FE` + `Backend Core`),写入 `.agents` 角色锁后再编码。
|
||||
- 产品说明默认只改 `docs/`;实现须用户明确「做后台 / 改代码」。
|
||||
- 禁止把收银库存写进 P0 完成口径。
|
||||
|
||||
---
|
||||
|
||||
## 9. 推荐下一步
|
||||
|
||||
**本体预录入(2026-07-10 已完成)**:Phase A 后台概念已写入 ontology(`entity:admin_console` / `workbench` / `service_customer`,`rule:BR-ADMIN-001`…`004`,`action:view_workbench` 等);设计须引用这些 ID,受 `BR-ADMIN-004` 闸门约束。
|
||||
|
||||
下一文档步骤:~~用上述 ontologyRefs 写 Phase A 页面清单 PRD~~ → **已完成**:`docs/门店管理后台-PhaseA-页面清单PRD.md`
|
||||
|
||||
再下一步:
|
||||
|
||||
1. ~~按 PRD 出线框说明~~ → `docs/门店管理后台-PhaseA-线框说明.md`
|
||||
2. ~~评估技术选型~~ → `docs/门店管理后台-技术选型草案.md`(推荐 Vue3 + Element Plus + `admin/`)
|
||||
3. ~~拆编码任务 brief~~ → `docs/门店管理后台-PhaseA-编码任务brief.md`(**待用户授权编码**)
|
||||
4. **坚持要对标收银/库存** — 需单独商业论证(受 `BR-ADMIN-004` 禁止)
|
||||
|
||||
**当前阻塞编码的唯一条件**:用户明确要求「改代码 / 实现后台」。
|
||||
|
||||
---
|
||||
|
||||
## 10. 风险
|
||||
|
||||
| 风险 | 说明 |
|
||||
|------|------|
|
||||
| 范围膨胀 | 「参考宠老板」极易变成两年 ERP;必须用 Phase 闸门 |
|
||||
| 双端规则分叉 | Web 与小程序必须共用同一套 Service 规则与本体 |
|
||||
| 过早做会员储值 | 无稳定预约/报告数据时,储值会变成另一套半成品 |
|
||||
| 忽视差异化 | 后台若只模仿收银,会丢掉成片/留资这个真正壁垒 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 截图研读纪要(2026-07-10 · 宠老板近期升级说明)
|
||||
|
||||
> 素材目录:`docs/refs/chonglaoban-upgrade/`(11 张,来自对方「会员等级」相关升级说明)
|
||||
> 这批图**不是**工作台/预约首页,而是 **客户中心 + 会员等级体系 + 收银台 + 权限** 的深水区。说明对方近期迭代重心在「会员资产与等级规则」,不是服务报告。
|
||||
|
||||
### 11.1 截图覆盖了什么
|
||||
|
||||
| 页面 | 看到的能力 | 对我们的含义 |
|
||||
|------|------------|--------------|
|
||||
| 会员等级配置 | 计算方式(累计消费/积分/储值充值)、滚动窗口(近 12 自然月等)、付费升级/自动升级/自动降级、升降级阈值缓冲、等级优惠/升级礼包、有效期 | **完整 CRM 会员引擎**;规则密度极高,属 Phase C 量级 |
|
||||
| 会员卡模板 + 规则说明 | 有效期重置、礼包只发最高档、退款 7 天回滚校验、每日凌晨批处理、**同步规则至多店** | 连锁配置分发;我们单店期可忽略「同步」 |
|
||||
| 会员等级变更记录 | 门店筛选、日期、姓名/手机/宠物名搜索、变更前后、原因、执行店、操作员;手机号脱敏 | **审计日志 UX 可直接借鉴**(任何敏感变更都该有) |
|
||||
| 客户列表 | 一人多宠、等级徽章、标签、余额拆「充值/赠送」、次卡、累计消费/次数/上次消费、导入导出、批量改等级/升降级开关/发券等 | **客户主档列表范式**可借鉴;资产列(余额/次卡)我们 Phase A 不做 |
|
||||
| 修改会员等级弹窗 | 改前→改后、原因、温馨提示(换卡迁余额、手动不发礼包、可查变更记录) | **人工纠偏 + 规则提示** 模式很好,后期会员模块照抄交互 |
|
||||
| 收银台 | 左导航含「预约服务 / 洗护报告」;结账三栏:客户上下文 / 卡券 / 购物清单;扫码枪 | 证明对方也有洗护报告入口,但主路径仍是收银;**我们不应以收银台为后台首页** |
|
||||
| 权限设置 | 「修改会员等级」「等级变更记录」「会员等级配置」拆成独立权限点 | **敏感操作独立授权** 应写入我们后台权限模型(即使 A 期只有 boss) |
|
||||
|
||||
### 11.2 对方信息架构(从侧栏读出)
|
||||
|
||||
```text
|
||||
店铺概况 / 接待管理
|
||||
店面业务 → 收银台、订单、预约服务、洗护报告、寄养、疫苗驱虫…
|
||||
商品服务 / 库存管理
|
||||
客户中心 → 客户管理、宠物管理、会员卡模板、次卡模板、积分、会员营销
|
||||
营销中心 / 数据统计 / 微信商城
|
||||
```
|
||||
|
||||
启示:宠老板把「洗护报告」放在店面业务里,和收银并列;**客户中心是独立一级**,会员规则很重。我们后台 IA 应对齐「服务经营」而不是「客户资产」做首页。
|
||||
|
||||
### 11.3 可借鉴的交互模式(与是否做会员无关)
|
||||
|
||||
1. **列表页标配**:门店筛选 + 多条件 +「姓名/手机/宠物名」合一搜索 + 查询/重置 + 汇总条(红色小字统计)
|
||||
2. **主从 Tab**:列表 ↔ 变更记录(业务数据与审计并排)
|
||||
3. **批量操作底栏**:勾选后底部浮出批量菜单(改等级、打标签…)
|
||||
4. **敏感弹窗必有规则提示**:改什么、会触发/不触发什么、去哪查日志
|
||||
5. **手机号脱敏展示**:列表默认打码
|
||||
6. **权限粒度**:配置 / 改等级 / 看日志 三分开
|
||||
7. **一人多宠**:列表内嵌宠物标签 +「添加宠物」快捷入口
|
||||
|
||||
### 11.4 明确不要照搬进 Phase A 的
|
||||
|
||||
| 能力 | 原因 |
|
||||
|------|------|
|
||||
| 累计消费升降级 + 滚动窗口 + 保级阈值 | 依赖完整收银流水;我们还没有交易账本 |
|
||||
| 充值余额 / 赠送余额拆分 | 储值财务规则,属 Phase C |
|
||||
| 次卡模板 / 积分 / 发券批量 | 营销资产体系,后置 |
|
||||
| 收银台三栏结账 | 与我们「报告成片」主线抢资源 |
|
||||
| 多店「同步规则至」 | 单店未做透前不做连锁配置分发 |
|
||||
| 退款 7 天降级回滚 | 无退款链路则无此规则 |
|
||||
|
||||
### 11.5 对宠小它路线的修正(看完截图后)
|
||||
|
||||
| 原判断 | 修正 |
|
||||
|--------|------|
|
||||
| Phase C「轻量会员」可能只是等级标签 | 对方会员 = **卡模板 + 等级引擎 + 资产账 + 审计 + 权限**;若要做,必须整包设计,不能只做「银金钻」四个字 |
|
||||
| 客户列表可很快做 | A 期可做 **「服务客户档案」瘦身版**:宠主+宠物+最近预约/报告/留资,**不含余额/次卡/积分** |
|
||||
| 权限 A 期可只有 boss | 仍建议预留权限点命名(改客户、看手机号明文、导出),避免 C 期推倒 |
|
||||
|
||||
**Phase A 客户中心(瘦身)建议字段**:头像/昵称、脱敏手机、宠物列表、最近到店、最近报告、留资状态、来源(预约/报告页);操作:查看详情、去报告、去回访。
|
||||
**不做**:余额、押金、积分、会员卡张数、批量改等级。
|
||||
|
||||
### 11.5.1 客户管理全页(教学标注版,补充)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/04-customer-management-guide.png`
|
||||
> 与此前客户列表截图同模块;本张带产品引导气泡,侧栏为「中台版」变体(标签管理 / 会员卡 / 次卡 平铺)。
|
||||
|
||||
#### 对方页面骨架(可复用到任何中后台列表)
|
||||
|
||||
```text
|
||||
筛选区(多条件 + 姓名/手机/宠物名) → 查询/重置/新增/导入/导出
|
||||
汇总条(会员数、总余额拆分、预存款、押金、积分)
|
||||
表格(会员 | 标签 | 宠物 | 资产列… | 更多操作)
|
||||
```
|
||||
|
||||
引导文案要点:
|
||||
|
||||
1. 按条件筛会员
|
||||
2. 点姓名进详情
|
||||
3. 宠物列可展开 / 添加宠物
|
||||
4. 会员卡/次卡点进卡包
|
||||
5. 「更多」:充值、编辑、冻结小程序等
|
||||
|
||||
#### 筛选维度(对方很全,我们要砍)
|
||||
|
||||
| 对方筛选项 | A 期 | 说明 |
|
||||
|------------|------|------|
|
||||
| 门店 | 单店可隐藏 | 多店再开 |
|
||||
| 会员卡 / 次卡 / 标签 | ❌ | 无资产体系 |
|
||||
| 注册日期 / 生日 | 可选后置 | |
|
||||
| 公众号/小程序绑定 | B 可选 | 有 openid 后再做 |
|
||||
| 性别 / 注册来源 | 可选 | 来源对我们有价值(预约/报告/留资) |
|
||||
| 上次消费 / 累计消费金额 | ❌→改成服务口径 | 改为「上次到店」「累计完成预约」 |
|
||||
| 姓名/手机/宠物名合一搜 | ✅ 必做 | 交互直接抄 |
|
||||
|
||||
#### 汇总条映射
|
||||
|
||||
| 对方 | 宠小它 A 期 |
|
||||
|------|-------------|
|
||||
| 会员数 | 客户数(有过预约或留资的宠主) |
|
||||
| 总余额 / 预存款 / 押金 / 积分 | **整行不做** |
|
||||
| — | 可改为:本月到店、待回访、有报告客户数 |
|
||||
|
||||
#### 行内操作映射
|
||||
|
||||
| 对方「更多」 | 我们 |
|
||||
|--------------|------|
|
||||
| 充值 | ❌ |
|
||||
| 编辑会员信息 | ✅ 编辑备注/昵称(慎改手机) |
|
||||
| 冻结小程序 | 后置 |
|
||||
| — | ✅ 查看报告、去回访、查看预约 |
|
||||
|
||||
#### Phase A「客户管理」定稿草案
|
||||
|
||||
```text
|
||||
筛选:来源(预约/报告留资/全部)| 有待回访 | 最近到店日期
|
||||
搜索:姓名 / 手机 / 宠物名
|
||||
按钮:查询、重置、(导出可 B 期)
|
||||
汇总:客户数 · 待回访数 · 本月到店数
|
||||
|
||||
列:客户(头像+名+脱敏手机)| 宠物(标签+添加)| 最近到店 | 最近报告 | 留资状态 | 来源 | 操作(详情/报告/回访)
|
||||
```
|
||||
|
||||
侧栏:客户管理、宠物管理 即可;**不做**标签管理/会员卡/次卡一级入口(C 期再说)。
|
||||
|
||||
### 11.5.2 客户管理 · 会员信息导入(补充)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/05-customer-import-guide.png`
|
||||
> 连锁版教学弹层:「会员信息导入」五步引导。
|
||||
|
||||
#### 对方导入流程
|
||||
|
||||
1. 先在「会员卡」里建好 **会员卡模板**
|
||||
2. 下载最新 Excel 模板
|
||||
3. 按模板填写(表内「会员卡模板」名称须与系统一致)
|
||||
4. 上传文件
|
||||
5. 确认校验结果后提交
|
||||
|
||||
列表背景还可见排序 Tab:入会时间 / 剩余金额 / 剩余积分 / 消费金额 / 消费次数 / 消费时间——全是 **资产与消费排序**,再次证明客户列表默认站在收银会员视角。
|
||||
|
||||
#### 对我们的含义
|
||||
|
||||
| 点 | 结论 |
|
||||
|----|------|
|
||||
| 导入强依赖「会员卡模板」 | 无卡体系就做不了对方同款导入;硬做会倒逼先建卡 |
|
||||
| 五步引导 UX | **可借鉴**:下载模板 → 填写 → 上传 → 确认;任何批量导入都该有 |
|
||||
| A 期是否做导入 | **不做**。客户应从预约/报告留资自然沉淀;开店冷启动若需要,B 期再做「瘦身导入」(姓名/手机/宠物名,无卡无余额) |
|
||||
| 排序 Tab | A 期最多:最近到店 / 最近注册;不做余额/积分/消费额排序 |
|
||||
|
||||
**风险**:过早做「导入会员」会把产品叙事从「服务经营」拉回「会员资产台账」,并与 Phase C 会员引擎缠在一起。
|
||||
|
||||
### 11.6 登录首屏:店铺概况 +「今日待办」弹层(新增)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/01-login-dashboard-todo.png`
|
||||
|
||||
刚登录默认落在 **店铺概况**,并立刻弹出 **今日待办**——这是对方把「老板每天第一眼」产品化的核心手法。
|
||||
|
||||
#### 布局结构(三栏)
|
||||
|
||||
| 区域 | 内容 |
|
||||
|------|------|
|
||||
| 左导航 | 店铺概况(高亮)→ 接待 → 店面业务 → 商品服务 → 库存 → 客户中心 → 营销 → 数据 → 微信商城 → 新零售 |
|
||||
| 主区(弹层背后) | 顶栏 4 指标卡(今日订单/会员/提醒/预约 + 迷你趋势线)→ 客流分析折线 → 商城/库存状态条 |
|
||||
| 右侧栏 | 店铺信息(店名、绑定手机、到期、短信余额)→ 进货商城广告 → 公告/行业资讯 |
|
||||
| 前景弹层 | **今日待办**:预约 / 提醒 / 寄养 / 临期商品 / 库存不足 / 待发货 / 售后中;「不再提醒」+ 确定 |
|
||||
|
||||
#### 今日待办:对方有什么 vs 我们抄什么
|
||||
|
||||
| 对方卡片 | 我们态度 |
|
||||
|----------|----------|
|
||||
| 今日预约(全部/待确认/我的) | **对标**:全部、待开始、服务中;我们无「待确认接单」(下单即占号) |
|
||||
| 今日提醒 | **对标**:洗护建议到期、留资待回访 |
|
||||
| 寄养 / 临期商品 / 库存 / 待发货 / 售后 | **不对标**(业态外) |
|
||||
|
||||
#### 可直接搬到 Phase A 的模式
|
||||
|
||||
1. **登录落点 = 工作台**,不是收银台、不是客户列表
|
||||
2. **首屏弹「今日待办」**(可「不再提醒」)——先处理例外,再看图表
|
||||
3. **待办卡片可点击下钻**到对应列表
|
||||
4. **背后保留轻量指标卡**(A 期先数字,B 期再加 sparkline)
|
||||
5. 右侧广告位我们不做进货商城,可换成帮助/本周打开率等
|
||||
|
||||
#### 宠小它「今日待办」MVP 草案
|
||||
|
||||
```text
|
||||
今日预约 → 全部 | 待开始(new) | 服务中(doing)
|
||||
待出报告 → doing 且尚无报告
|
||||
成片异常 → processing 超时 | failed
|
||||
待回访线索 → pending 且 remindDate≤今天
|
||||
```
|
||||
|
||||
顶栏 4 指标卡建议:`今日预约` · `今日完成` · `待回访` · `成片失败`。
|
||||
|
||||
此前 IA 里「今日工作台」作为一级入口——**与对方首屏策略一致,锁定**。抄的是「待办驱动」交互,不是抄寄养/库存内容。
|
||||
|
||||
### 11.6.1 店铺概况本体(关掉待办弹层后)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/02-store-overview.png`
|
||||
|
||||
弹层关掉后,店铺概况是一张 **「指标卡 + 趋势图 + 业务状态条 + 右侧店务栏」** 的经营首页。
|
||||
|
||||
#### 对方模块拆解
|
||||
|
||||
| 区块 | 内容 | 我们态度 |
|
||||
|------|------|----------|
|
||||
| 顶栏 4 卡 | 今日订单 / 今日会员 / 今日提醒 / 今日预约(带 sparkline) | **对标结构**;指标换成服务经营口径(见下) |
|
||||
| 客流分析 | 会员数 / 散客数 / 总客流 折线,可选日期范围 | B 期再做;A 期可先不做图,或只做「预约量」单线 |
|
||||
| 微信商城状态 | 待付款 / 待发货 / 待退款 / 待处理预约 | **不对标商城**;可改成「报告漏斗」四格 |
|
||||
| 库存不足表 | 商品 + 当前库存 | **不对标** |
|
||||
| 右侧店信息 | 店名、认证、手机、到期、短信余额 | 轻量保留:店名、营业时段摘要、员工数 |
|
||||
| 右侧广告 | 进货商城 | **不做** |
|
||||
| 右侧公告 | 系统升级说明 / 行业资讯 | 可选:产品更新日志或帮助链接 |
|
||||
|
||||
侧栏相对上一张多了 **工作日历**(在接待与店面业务之间)——A 期可并入「预约与排班」,不必单独一级。
|
||||
|
||||
#### 宠小它「店铺概况 / 工作台」A 期线框(定稿草案)
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┬──────────────────┐
|
||||
│ 今日预约 │ 今日完成 │ 待回访 │ 成片失败 │ 门店卡片 │
|
||||
│ (数字,可点下钻;B 期加 sparkline) │ 店名/时段/员工 │
|
||||
├─────────────────────────────────────────────┤ 帮助 / 更新说明 │
|
||||
│ 今日待办条(或登录弹层关闭后的常驻摘要) │ │
|
||||
│ · 待开始 n · 服务中 n · 待出报告 n │ │
|
||||
│ · 成片失败 n · 待回访 n │ │
|
||||
├─────────────────────────────────────────────┤ │
|
||||
│ 近 7 日预约完成趋势(可选,A 可后置) │ │
|
||||
├──────────────────┬──────────────────────────┤ │
|
||||
│ 报告漏斗(替商城)│ 异常列表(替库存不足) │ │
|
||||
│ 已发送/已打开/ │ 成片失败最近 5 条 │ │
|
||||
│ 已留资/已预约 │ 或 待回访最近 5 条 │ │
|
||||
└──────────────────┴──────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
**报告漏斗四格**(替代微信商城四格):`今日已发报告` · `已打开` · `已留资` · `从报告发起的预约`——直接打在我们差异化上;缺埋点统计时先显示「—」并链到 B1。
|
||||
|
||||
### 11.7 截图覆盖度与仍缺清单(2026-07-10 刷新)
|
||||
|
||||
#### 已有(够用)
|
||||
|
||||
| 主题 | 状态 |
|
||||
|------|------|
|
||||
| 登录今日待办 + 店铺概况 | ✅ |
|
||||
| 客户管理列表 / 导入 / 改等级 / 变更记录 / 权限 | ✅ 偏会员资产,A 期只借交互 |
|
||||
| 会员等级配置 / 卡模板同步 | ✅ 证明 C 期复杂度 |
|
||||
| 新增服务(SKU 级) | ✅ 证明服务目录别做成 POS |
|
||||
| 收银台 | ✅ 明确不对标为首页 |
|
||||
|
||||
#### Phase A 定稿仍缺(优先,按序)
|
||||
|
||||
| 优先级 | 页面 | 为什么要 | 状态 |
|
||||
|--------|------|----------|------|
|
||||
| **P0** | **预约服务**列表或日/周日历 | A2 核心 | ⛔ **当前账号版本无权限**(见下) |
|
||||
| **P0** | **工作日历** | 确认是否=排班 | 待试;可能同属高版本 |
|
||||
| **P0** | **洗护报告**列表 | A3 核心 | 待试(同在店面业务下,或也可打开) |
|
||||
| **P1** | 预约/报告详情 | 定字段与主操作 | 依赖上两项 |
|
||||
| **P1** | **接待管理**首页 | 避免与工作台重复 | 待试 |
|
||||
| **P2** | 数据统计 / 提醒 / 店铺设置 | B 期或 A5 参考 | 可选 |
|
||||
|
||||
#### 11.7.1 预约服务:版本门禁(2026-07-10)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/06-appointment-version-blocked.png`
|
||||
|
||||
点击侧栏 **店面业务 → 预约服务** 后弹出:
|
||||
|
||||
> 版本权限操作:当前版本不可使用该功能,请前往【系统升级】了解更多。
|
||||
|
||||
说明:对方把「预约」做成 **付费/高版本模块**,体验版/低版本账号看不到列表 UI。
|
||||
**不影响我们做 Phase A**——宠小它预约模型已在小程序落地,Web 后台可按自有状态机设计,不必等竞品截图。
|
||||
|
||||
本张仍有价值:展开了 **店面业务** 子菜单完整 IA:
|
||||
|
||||
```text
|
||||
店面业务
|
||||
├── 收银台
|
||||
├── 订单管理
|
||||
├── 预约服务 ← 版本门禁
|
||||
├── 洗护报告 ← 请优先试能否打开
|
||||
├── 寄养管理
|
||||
├── 提醒
|
||||
├── 记账本
|
||||
├── 宠付宝
|
||||
└── 转诊管理
|
||||
```
|
||||
|
||||
与我们的映射:收银台/订单/寄养/记账/宠付宝/转诊 → 不对标;**预约 + 洗护报告 + 提醒** → A/B 对标。
|
||||
|
||||
#### 截图受阻时的推进方式
|
||||
|
||||
1. 若能打开:**洗护报告**、**提醒**、**接待管理**、**工作日历** — 继续发
|
||||
2. 若整片店面业务都门禁 — 停等竞品预约 UI,直接按宠小它现网写 **Phase A PRD**(工作台 / 预约 / 报告 / 线索 / 设置)
|
||||
3. 不建议为截图去开通对方高版本,性价比低
|
||||
4. **已由 PDF(7.2 支付宝安心生活)补全预约状态机与可约模型描述**,见 §11.9
|
||||
|
||||
### 11.8 新增服务表单(商品服务深水区)
|
||||
|
||||
> 素材:`docs/refs/chonglaoban-upgrade/03-add-service.png`
|
||||
|
||||
对方把「服务」做成 **可售 SKU**:分类、多规格、零售价/会员价、时长、含商品、小程序上下架、复购提醒、会员折扣、付费券、预约押金、图文详情。这是收银+商城一体的服务目录,不是我们现在的「预约用服务类型名」。
|
||||
|
||||
#### 对方表单结构
|
||||
|
||||
| 区块 | 字段要点 |
|
||||
|------|----------|
|
||||
| 基础信息 | 名称*、分类(可跳转新增)、排序值、备注 |
|
||||
| 服务项目/规格 | 可多条:规格名*、拼音码、条码、零售价*、会员价、时长、包含商品、服务说明(展示在小程序预约)、上架商城/可预约开关、复购提醒/参与会员折扣/生成服务记录 |
|
||||
| 高级设置 | 适用宠物、服务标签、已售数量(可刷单展示) |
|
||||
| 付费券配置 | 企业版:可退、有效期≤90 天、到期自动退 |
|
||||
| 预约押金 | 开则小程序预约需微信支付押金 |
|
||||
| 媒体 | 最多 9 图 + 1 短视频 |
|
||||
| 服务详情 | 富文本 |
|
||||
|
||||
#### 与宠小它现状对比
|
||||
|
||||
| 维度 | 宠老板 | 宠小它现在 |
|
||||
|------|--------|------------|
|
||||
| 模型 | 服务目录 + 多规格 SKU | `entity:service_type`:**仅 name + storeId**(系统默认不可改删) |
|
||||
| 价格 | 零售价 / 会员价 | **无**(预约不收银) |
|
||||
| 时长 | 规格级时长(15 分钟档) | 号源是门店半小时档,**不绑服务时长** |
|
||||
| 小程序 | 商城上架 + 预约开关分离 | 有名称即可出现在预约选项 |
|
||||
| 复购 | 规格级「复购提醒」 | 有 `service_interval` 建议周期,但是**按服务名匹配**,非规格级 |
|
||||
| 押金/券 | 微信支付押金、付费券 | **无** |
|
||||
|
||||
#### 借鉴分级(重要:别一次做成对方那样)
|
||||
|
||||
| 优先级 | 能力 | 说明 |
|
||||
|--------|------|------|
|
||||
| **A 期设置中心** | 名称、排序、启用/停用、适用宠物(猫/犬/不限)、备注、是否出现在预约 | 在现有 ServiceType 上**轻量加字段**即可;后台表单远短于对方 |
|
||||
| **A/B 可选** | 服务说明(预约页展示)、封面图 1 张、建议时长(仅展示,不改号源算法) | 提升预约体验,仍不做价 |
|
||||
| **B 期** | 与 `service_interval` 打通的「复购提醒」开关 | 对方规格级提醒 → 我们门店级/服务名级即可 |
|
||||
| **C 期(若做会员/收银)** | 零售价/会员价、多规格、条码、含商品、押金、付费券、商城上架 | 一旦做价,就进入交易账本,与会员引擎同级复杂度 |
|
||||
|
||||
#### Phase A「服务类型」后台表单草案(瘦身)
|
||||
|
||||
```text
|
||||
服务名称 *
|
||||
适用宠物:不限 | 猫 | 犬
|
||||
排序(数字,越小越靠前)
|
||||
预约可选:开/关
|
||||
备注(仅店内可见)
|
||||
[保存]
|
||||
```
|
||||
|
||||
**明确不做(A 期)**:多规格、条码、零售/会员价、包含商品、商城上架、押金、付费券、刷已售数量、富文本详情。
|
||||
|
||||
#### 产品含义
|
||||
|
||||
对方「新增服务」证明:完整宠物店后台会把服务当成 **商品**。
|
||||
宠小它若坚持「服务经营 + 报告转化」,A 期服务配置应保持 **预约目录**,不要提前长成 POS 商品卡——否则设置中心会拖垮 MVP,并倒逼收银。
|
||||
|
||||
### 11.9 PC 端升级公告 PDF 研读(2026-07-10)
|
||||
|
||||
> 目录:`docs/refs/chonglaoban-upgrade/*.pdf`(6 份,用户下载的升级说明)
|
||||
|
||||
| PDF | 主题 | 对宠小它 |
|
||||
|-----|------|----------|
|
||||
| 4月1日更新公告 | 服务规格批量改分类、盘点、入库权限、移动端再购进收银、小程序营销、会员卡/次卡分享、订单批量退货 | 库存/收银/营销深水;**A 期不跟** |
|
||||
| 6月16日更新公告 | 会员卡充值入口、改有效期、模板默认值 | 会员资产;C 期参考 |
|
||||
| 6.24 扫码点餐 | 桌码/小程序点餐、备餐状态机、语音提醒、核销回传收银 | **餐饮业态**;明确不做 |
|
||||
| 6.26 通用押金 | 通用 vs 锁定押金;取消预约/寄养须先处理锁定押金;结账默认可扣押金;有押金禁删会员 | 预约与资金账户强耦合;我们无支付则**不做押金态机** |
|
||||
| 7.2 支付宝安心生活 | 服务发布到支付宝、次卡→预约→核销→结账多单;可约库存(排班/时长/人数);跨店券 | 渠道扩张;预约含确认/拒绝/到店/履约,且可接外部单 |
|
||||
| 7.9 会员卡自动升降级 | 与已截图一致:规则引擎、审计、权限、连锁同步 | C 期整包;A 不做 |
|
||||
|
||||
#### PDF 补上的「预约模型」(弥补截图门禁)
|
||||
|
||||
支付宝对接说明暴露了对方预约能力(体验版看不到 UI 也能推断):
|
||||
|
||||
- 可配置 **是否自动接受预约**
|
||||
- 状态:确认 / 拒绝 / 到店(履约中) / 取消 / 完成;**核销并结账 → 收银台**
|
||||
- 可约:关联排班 / 服务时长 / 星期时段+人数库存
|
||||
- 按手机号自动匹配或创建会员 +「未知」宠物
|
||||
|
||||
与宠小它:我们是 **下单即占号**,完成导向 **出报告/成片**,号源是半小时档+占用块——**A 期按自有模型设计即可,不必再等预约列表截图**。
|
||||
|
||||
#### 对方近期迭代方向(从 PDF 汇总)
|
||||
|
||||
```text
|
||||
交易加深:押金账户、退货批量、再购进收银
|
||||
渠道加深:支付宝安心生活、扫码点餐
|
||||
会员加深:升降级引擎、卡模板、分享开卡
|
||||
库存/餐饮:盘点、点餐备餐
|
||||
```
|
||||
|
||||
几乎没有「洗护报告体验 / 内容分享转化」方向——**差异化窗口仍在宠小它**。
|
||||
|
||||
---
|
||||
|
||||
## 参考链接
|
||||
|
||||
- https://www.chonglaoban.cn/about/chonglaoban
|
||||
- https://www.chonglaoban.cn/
|
||||
- `docs/refs/chonglaoban-upgrade/`(截图 + PDF 素材)
|
||||
- `docs/架构与产品评估报告-2026-07-05.md`
|
||||
- `docs/coverage/ontology-coverage-audit.md`(P1/P2 gap)
|
||||