petstore-docs/ontology/actions.md

693 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Actions动作
> 读写实体的命令/用例。每个动作标注入口controller/service 方法)、输入输出实体、适用规则。
## Store 相关
### action:register_boss
老板入驻:微信核验手机号后,在同一事务创建 `Store` + `boss` `User`,签发 `session_token`,写 `store_registered` BusinessEvent 与 AuditLog。
- **入口**`POST /api/onboarding/register-boss` → `MerchantOnboardingController#registerBoss``MerchantOnboardingService#registerVerifiedBoss`(公开)
- **输入**`storeName`、`bossName`、微信 `phoneCode`、可选 `loginCode`;不接受手填手机号或明文密码
- **输出**`entity:store`、`entity:user`、`session_token`、`entity:business_event`、`entity:audit_log`
- **适用规则**`rule:BR-ONBOARD-001`、`rule:BR-AUDIT-001`
- **代码**`backend/src/main/java/com/petstore/service/MerchantOnboardingService.java`
- **证据**`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
legacy 永久邀请码查询入口已停用,固定返回 `410 / LEGACY_INVITE_DISABLED`,不再查询或返回门店。
- **入口**`GET /api/store/invite-code` → `StoreController#getByInviteCode`
- **适用规则**`rule:BR-STORE-002`legacy 禁用)
- **代码**`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
创建预约:按服务时长跨半小时容量桶占号,并保存时长快照;取消不占号;客户与创建人分开记录。
- **入口**`POST /api/appointment/create` → `AppointmentController#create``AppointmentService#createBooking`
- **输入**`petName`、`petType`、`serviceType`、`appointmentTime`、`storeId`、`remark?`、`petId?`;门店代客预约另需 `customerUserId``customerPhone`
- **输出**`entity:appointment`
- **身份口径**customer 自约时客户=创建人=current.userIdboss/staff 代约时创建人=current.userId客户按请求解析legacy userId 镜像客户
- **适用规则**`rule:BR-APPT-001`(状态机)、`rule:BR-APPT-003`(客户归属)、`rule:BR-APPT-004`(服务时长 + 门店容量 + 并发锁)、`rule:BR-SC-001`(门店客户建档)
- **代码**`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`authorStaffId` 从上下文派生,`customerUserId` 从预约派生;报告所属门店必须等于当前用户门店。
- **入口**`POST /api/report/create` → `ReportController#create``ReportService#create`
- **输入**`appointmentId`、`remark?`、`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:confirm_report_sent
门店员工在宠主实际收到报告后显式确认发送。复制链接、生成/展示二维码、打开 H5 或预览报告都不自动计为发送;首次确认写入不可覆盖的回执,重复确认幂等成功。
- **入口**`POST /api/report/confirm-sent` → `ReportController#confirmSent``ReportService#confirmSent`
- **输入**`reportId`、`channel=wechat|qr|other`;门店、员工与角色均从会话派生
- **输出**`sendStatus`、`sentAt`、`sendChannel`、`alreadyConfirmed`
- **适用规则**`rule:BR-RPT-005`、`rule:BR-RPT-008`、`rule:BR-BE-001`
- **触发事件**`event:report_sent`(仅首次确认)
- **代码**`ReportController#confirmSent`、`ReportService#confirmSent`、`BusinessEventService#recordReportSent`
- **测试**`ReportControllerTest`、`ReportServiceTest`、`BusinessEventServiceTest`
- **证据**`anchored`
### action:track_report_open
报告打开埋点:服务端用 token 定位 Report 后写入低敏 BusinessEvent事件表不保存 token/hash日志只记 SHA-256 前 8 位 hash。
- **入口**`POST /api/report/open-track` → `ReportController#trackReportOpen`(公开)
- **适用规则**`rule:BR-RPT-007`token hash 日志)、`rule:BR-BE-001`(不可变与低敏事件)
- **代码**`ReportController#trackReportOpen`、`BusinessEventService#recordReportOpenByToken`
- **测试**`ReportControllerTest`、`BusinessEventServiceTest`
- **证据**`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`(去重)、`rule:BR-SC-001`(门店客户建档/绑定)、`rule:BR-BE-001`(低敏事件)
- **代码**`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder`
- **测试**`ReportLeadControllerTest`、`ReportLeadServiceStoreCustomerTest`
- **证据**`anchored`
### 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`;必须填写 30 分钟粒度的预计时长。
- **入口**`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``walk_in` 消耗一个并发名额,`blocked` 关闭全部名额。
- **入口**`POST /api/schedule/block` → `ScheduleController#createBlock``ScheduleService#createBlock`
- **适用规则**`rule:BR-SCH-001`、`rule:BR-SCH-002`、`rule:BR-APPT-004`
- **代码**`backend/src/main/java/com/petstore/controller/ScheduleController.java#createBlock`
- **测试**`ScheduleControllerTest`
- **证据**`anchored`
### action:delete_schedule_block
取消手动占用:仅 boss/staffservice 层做同店校验(`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
旧“直接创建员工”入口已停用,固定返回 `DIRECT_STAFF_CREATION_DISABLED`;新员工必须由老板创建限时邀请后本人接受。
- **入口**`POST /api/user/create-staff` → `UserController#createStaff`deprecated
- **适用规则**`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目标必须是本店 `staff`,不能删除 boss。删除后活跃账号校验会立即让旧 session token 失效,并写低敏 AuditLog。
- **入口**`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
门店某日可预约开始时段:公开接口,接收可选 `serviceType`,按服务时长检查覆盖的所有容量桶,返回开始/结束时间、容量与不可约原因。
- **入口**`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
永久邀请码员工注册已停用,固定返回 `410 / LEGACY_INVITE_DISABLED`
- **入口**`POST /api/user/register-staff` → `UserController#registerStaff`公开、deprecated
- **适用规则**`rule:BR-STORE-002`
- **代码**`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
店铺注册旧入口固定返回 `LEGACY_STORE_REGISTRATION_DISABLED`,不创建 Store。
- **入口**`POST /api/store/register` → `StoreController#register`
- **状态**`deprecated`
- **代码**`backend/src/main/java/com/petstore/controller/StoreController.java#register`
- **证据**`anchored`
## 门店开通与员工邀请Phase 0
### action:get_store_onboarding / action:complete_store_onboarding
- `GET /api/admin/onboarding`boss/staff 查看 4 步清单;必填为门店资料、预约容量、至少一个服务项目,邀请员工为选填。
- `POST /api/admin/onboarding/complete`:仅 boss必填项全部满足才写 `completed_at/by` 与 AuditLog重复确认幂等。
- 历史门店迁移为 `unknown`,不推测已经完成。
### action:create_staff_invitation / action:list_staff_invitations / action:revoke_staff_invitation
- `POST /api/admin/staff-invitations`:仅 boss按姓名、本人手机号、130 天有效期创建。返回原始 `inviteToken` 和小程序 `invitePath` **一次**
- `GET /api/admin/staff-invitations`:仅 boss只返回脱敏手机号和状态不返回原始 token/hash。
- `DELETE /api/admin/staff-invitations?invitationId=`:仅 boss + 同店;已接受不能撤销,重复撤销幂等。
### action:preview_staff_invitation / action:accept_staff_invitation
- `GET /api/staff-invitations/preview?token=`:公开预览门店、受邀姓名、脱敏手机号、状态和有效期。
- `POST /api/staff-invitations/accept`:公开但必须提交微信 `phoneCode`;服务端换得的手机号必须与邀请绑定手机号一致。成功后创建 staff、消费邀请、写 AuditLog 并签发新 session。
- 已存在 User、openid 或 unionid 冲突时拒绝;本期不把 customer 静默升级为 staff避免单角色模型混淆。
## 删除/列表类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`
## 微信 OAuthTODO demodeprecated
### 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
> 产品:`docs/门店管理后台升级方案-对标宠老板.md`。优先复用现有 `/api/**`,不另造状态机。
### action:view_workbench
查看今日行动工作台(登录落点)。服务端按完整本店数据聚合逾期预约、服务中、今日待开始、成片异常、新报告待发送和到期回访,返回准确总数与低敏预览;报告提交、显式确认发送、打开与留资通过 BusinessEvent 汇总,仅未建模的转预约保持未知。
- **入口**Admin UI `/workbench``GET /api/admin/workbench/actions`;兼容 `GET /api/admin/workbench/report-funnel?date=yyyy-MM-dd`
- **输出**`entity:workbench`、`entity:workbench_todo_item`
- **适用规则**`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-004`、`rule:BR-BE-001`
- **触发事件**`event:workbench_viewed`可选gap
- **代码**`AdminBusinessEventController`、`AdminWorkbenchService`、`AdminBusinessEventService`、`admin/src/views/WorkbenchView.vue`
- **测试**`AdminBusinessEventControllerTest`、`AdminWorkbenchServiceTest`、`AdminBusinessEventServiceTest`
- **证据**`anchored`
### action:drilldown_workbench_todo
工作台行动组或具体预览下钻到对应列表并带筛选;预约预览可按 ID 直接打开受保护详情。
- **入口**Admin UI workbench action click
- **适用规则**`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`
- **代码**`admin/src/views/WorkbenchView.vue`、`AppointmentsView.vue`
- **证据**`anchored`
### 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`;复用 `GET /api/report/list` + `action:confirm_report_sent` + `action:generate_highlight`
- **适用规则**`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-ADMIN-003`、`rule:BR-RPT-005`、`rule:BR-RPT-008`
- **证据**`anchored`
### action:admin_list_leads
后台回访线索列表。
- **入口**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:store_customer`
- **适用规则**`rule:BR-ADMIN-001` … `BR-ADMIN-004`、`rule:BR-SC-001`
- **证据**`anchored`
- **代码**`backend/src/main/java/com/petstore/controller/AdminServiceCustomerController.java`、`AdminServiceCustomerService`
- **产品决策**:以 `t_store_customer` 稳定主档为入口聚合,返回 `storeCustomerId`;不做余额/次卡列、持卡会员导入
- **文档**`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`