# 宠小它 — P0 研发落地清单

> **本体对齐**：任务条目引用 [`ontology/`](./ontology/) 中的实体/规则 ID（如 `entity:report`、`rule:BR-RPT-002`）。冲突以本体为准。
>
> 用途：发给研发拆任务与验收；**仅含 P0**（本期质量与主线闭环必需，不含 P1/P2 增强）。  
> 依据：《产品全方位优化说明 v1》《本期主线》《宠主端预约体验优化》《产品设计文档》及当前仓库实现粗对齐。  
> 更新：2026-07-05（P0 稳定化轮次：冻结本期范围，重新标记状态并绑定 Owner 与验收命令）

**关联**： [产品全方位优化说明-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 证据。

---

## 优先级说明

- **P0**：本迭代必须交付；阻塞对外口碑、数据脏读、合规提示缺失、或主线（传播/成片/留资）不可用。  
- 已部分实现的项：**仍保留**，以下方「验收标准」为准做 gap 关闭。

---

## A. 后端（Spring Boot）

| 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`、万能验证码和宽 CORS；生产启动不写默认业务数据 | 仓库不含真实 DB/微信密钥；最终配置不安全时启动失败；有 liveness/readiness、只读 schema/数据预检和上线 smoke | Backend Ops / System Architect | ✅ 代码与脚本已实现；待真实环境留证 | `mvn test` + `deploy/release-preflight.sh` + `deploy/production-smoke.sh` | 对应 P0-8；生产域名/凭据/迁移/线上 smoke 归 H3；已暴露凭据需轮换 |
| **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；持续回归数量以 Surefire 当次统计为准 |
| **A13** | **预约/报告身份语义拆分**：客户、创建人、服务技师、报告作者不得复用同一语义字段 | customer 自约身份从 session 派生；门店代约必须提供真实宠主；宠主报告按预约客户归属查询；legacy 字段只做兼容 | System Architect / Backend Core / Report Media Backend | ✅ 已实现（Phase 0 RC1 后） | `mvn -f backend/pom.xml test` + H5/小程序构建 | 迁移见 `backend/db/migrations/20260801_split_service_identity.sql` |
| **A14** | **StoreCustomer 门店客户主档**：客户列表不再临时拼成无 ID 投影 | 预约/留资自动建档；同店账号与手机号唯一；API 返回 storeCustomerId 且手机脱敏；历史数据可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现（待生产迁移） | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` | 迁移见 `backend/db/migrations/20260801_create_store_customer.sql` |
| **A15** | **BusinessEvent 业务事件落库**：经营漏斗不再依赖日志或前端猜测 | 首批 7 类事实统一落库；稳定事件幂等；metadata 不含 token/手机号/IP；工作台已打开可统计；历史可靠事实可回填 | Backend Core / Report Media Backend / Data Model | ✅ 已实现（待生产迁移） | `mvn -f backend/pom.xml test` + `npm --prefix admin run build` + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_business_event.sql` |
| **A16** | **真实预约容量与连续排班占用**：服务时长、门店并发容量和预约时长快照共同决定号源 | 长服务跨半小时桶占号；`walk_in` 消耗一份容量，`blocked` 关闭全部；并发创建不超卖 | Backend Core / Store Admin FE / Data Model | ✅ 已实现（待生产迁移） | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_booking_capacity.sql`；不等同排队/准点承诺 |
| **A17** | **报告显式确认发送状态**：传播动作与发送事实分离 | 历史为 `unknown`；新报告为 `unsent`；首次确认写不可覆盖回执与幂等 `report_sent`；工作台仅提醒新报告待发送 | Report Media Backend / Store Miniapp FE / Store Admin FE / Data Model | ✅ 已实现（待生产迁移） | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260801_create_report_send_status.sql`；不宣称微信投递成功 |
| **A18** | **门店开通 + 一次性员工邀请 + AuditLog** | 老板入驻须微信核验；历史开通状态 unknown；完成清单需资料/容量/服务；邀请 token 只显一次且库内仅 hash，手机号微信核验、限时/一次/可撤销；删员工立即失效旧会话 | Backend Core / Store Miniapp FE / Store Admin FE / Data Model | ✅ 代码完成（待生产迁移与微信真机验收） | backend 全测 + admin/H5/小程序构建 + 本体漂移审计 | 迁移见 `backend/db/migrations/20260802_create_store_onboarding.sql`；单角色账号冲突明确拒绝 |
| **A19** | **StoreCustomer 事实时间线**：客户详情不再只显示当前聚合快照 | 以 canonical `storeCustomerId` 同店校验；BusinessEvent 按发生时间稳定分页；合并前别名事件不断档；预约/报告/回访提供低敏摘要和跳转；不返回手机号原文、备注、媒体、token 或原始 metadata | Backend Core / Store Admin FE | ✅ 代码完成（待生产迁移） | backend 全测 + admin build + 本体漂移审计 | 迁移见 `backend/db/migrations/20260802_create_store_customer_timeline.sql`；既有事件不改写 |
| **A20** | **FollowUpTask 回访状态机与真实再次预约归因** | 留资与操作任务拆分；领取人独占处理；未接通/稍后联系可改期；终态不可回退；退订取消开放任务；只有真实预约保存成功才能记 `rebooked/rebook_created`；跨店 404，事件/审计低敏 | Backend Core / Store Admin FE / Data Model | ✅ 代码完成（待生产迁移与三角色验收） | backend 全测 + admin build + 本体漂移审计 | 迁移见 `backend/db/migrations/20260802_create_follow_up_task.sql`；`ReportLead.remind_status` 仅兼容投影 |

---

## B. 小程序 — 门店端（老板/员工）

| 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 | 对齐 A17；不得把剪贴板动作冒充发送 |
| **B3** | **报告提交锁死提示** | 提交前弹窗固定文案：「提交后不可修改，请确认照片与备注无误」 | Store Miniapp FE | 🟡 部分实现：核对文案与位置 | 手工 smoke | 与产品 v1 锁死策略一致 |
| **B4** | **「我的」老板端分组标题** | 至少两组：`店铺与经营` / `团队与账号`；高频在上；使用 `.module-title` | Store Miniapp FE | ✅ 已实现 | `npm --prefix frontend run build:h5` + 手工 smoke | 见《产品全方位优化说明》§2.2 |
| **B5** | **报告前后照片必填前端校验** | 无 before 提示「请至少上传1张服务前照片」；无 after 提示「请至少上传1张服务后照片」 | Store Miniapp FE / Customer Experience FE | ✅ 已实现（Batch A） | `npm --prefix frontend run build:h5` + `npm --prefix frontend run build:mp-weixin` | 对应 A7、P0-4 |
| **B6** | **小程序开通向导与员工邀请卡片** | 老板可查看/完成开通清单；创建邀请后原始口令只显示一次，可直接 `open-type=share` 转发；员工预览脱敏信息并经微信手机号核验加入 | Store Miniapp FE / Backend Core | ✅ 代码完成，待微信真机 smoke | H5 + mp-weixin 构建；老板/员工双微信真机验收 | H5 只预览，不绕过微信核验 |

---

## C. 小程序 — 宠主端（customer）

| 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 | 需求 | 验收标准 | 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 | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|----|------|----------|-------|------|----------|------|
| **E1** | **分享卡片元信息规则** | 小程序 `onShareAppMessage`：标题「宠物名 + 服务 + 门店」；封面优先级 成片封面 > 服务后首图 > Logo | Customer Experience FE | ✅ 已实现（`reportShare.js`） | 手工 smoke + 代码审查 | 见 `frontend/src/utils/reportShare.js` |
| **E2** | **埋点：报告打开** | 前端提交 token 定位报告；事件表仅存 report ID、首次/再次和时间，不存 token/hash；H5/小程序均上报 | Report Share QA / Backend Ops | ✅ 已落 BusinessEvent 并进入工作台去重统计（待生产迁移） | `BusinessEventServiceTest` + admin build | 对应 P0-10、P1-4 |
| **E3** | **埋点事件 schema**（最小集） | 定义 `report_opened`、`report_reopened`、`highlight_succeeded`、`highlight_failed`、`video_play`、`video_save`、`lead_submitted` 字段、来源、统计窗口 | Report Share QA / Product Design | ✅ 已冻结；核心履约/打开/留资已落库，成片与客户端播放事件待接入 | 见《本期主线-下一步产品优化点》埋点 schema 表 | 对应 P1-4 |

---

## F. 视觉与规范（工程债）

| 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 | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|----|------|----------|-------|------|----------|------|
| **G1** | **无排队/叫号能力不承诺「不排队」** | 全局检索「排队」「不排队」；宠主副标题等改为「在线选时段，到店更省心」 | Product Design / Customer Experience FE | ✅ 已实现（Batch A 文档冻结，容量模型上线后仍成立） | `rg "到店不排队|不排队" docs frontend/src` | 与《产品全方位优化说明》D2 一致 |
| **G2** | **对内录单表述（可选）** | 门店端新建预约入口文案是否改为「代客预约」由产品拍板；若改则全局统一 | Product Design | 🟡 待产品拍板 | — | 降低「预约」歧义 |

---

## H. 发布 RC 门禁（Batch D 收口）

| ID | 需求 | 验收标准 | Owner | 状态 | 验收命令 | 备注 |
|----|------|----------|-------|------|----------|------|
| **H1** | **后端可重复发布门禁** | README + production profile + 配置校验 + schema/31 项只读预检 + liveness/readiness + 只读 smoke + systemd/Nginx 模板 | Backend Ops | ✅ 仓库能力已实现；待真实环境执行 | backend 全测/打包 + `bash -n deploy/*.sh` + Runbook | 不把模板/本地通过写成已上线 |
| **H2** | **前端可重复发布门禁** | admin/H5/小程序真实域名配置先门禁再构建；仓库占位值不能进入正式包；微信合法域名、体验版、静态回滚路径明确 | Frontend Release Ops | ✅ 仓库能力已实现；待真实环境执行 | admin/frontend preflight + 三端构建 + Runbook | 真实配置不得提交 |
| **H3** | **P0 通过条件** | 见下方「P0 通过条件」块 | System Architect / Core Flow QA | 🟡 待人工 smoke 复核 | 见下方 | 对应 P0-10 |

---

## 建议排期顺序（供研发参考）

1. **A1～A3**（后端状态机）— 防脏数据，优先合。  
2. **B1**（已完成 Tab）— 前端小改、用户感知强。
3. **B2、D1、D2、E1**（传播与报告体验）— 主线门面。
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/微信密钥，最终 profile 为 `ddl-auto=validate`，CORS 仅允许显式生产 HTTPS 源；只读生产预检通过。
- 已按固定顺序完成身份、StoreCustomer、BusinessEvent、预约容量、报告发送、门店开通、客户时间线与 FollowUpTask 八个生产迁移，并归档备份与验证证据。
- liveness/readiness、只读 smoke 与 boss/staff/customer 三角色真实环境 smoke 通过；Gitea/GitLab 未受 Petstore 配置影响。
- 文档口径扫描：`rg "到店不排队|会员储值 \| 路线图 P0|宠主通过微信查看服务报告（v2）|无限制，随便选" docs` 无命中。

---

## 不在 P0（勿与本清单混排）

- 会员卡 / 储值 / 次卡 / 套餐（P1/P2）
- 宠主账号内历史报告汇总（P2）
- 报告 token 作废重发（中期，见 D3 决策）
- 多技师分成、排班深度联动  
- 复杂数据看板、漏斗大屏  
- 分包优化（可 P1）

---

*评审后可将本页 ID 勾进看板；与专文冲突以迭代评审为准。*
