# 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 创建预约:按服务时长跨半小时容量桶占号,并保存时长快照;取消不占号;客户与创建人分开记录。可选携带 `followUpTaskId`,在真实保存成功后闭环再次预约归因。 - **入口**:`POST /api/appointment/create` → `AppointmentController#create` → `AppointmentService#createBooking` - **输入**:`petName`、`petType`、`serviceType`、`appointmentTime`、`storeId`、`remark?`、`petId?`、`followUpTaskId?`;门店代客预约另需 `customerUserId` 或 `customerPhone` - **输出**:`entity:appointment`;携带任务且成功时更新 `entity:follow_up_task` - **身份口径**:customer 自约时客户=创建人=current.userId;boss/staff 代约时创建人=current.userId,客户按请求解析,legacy userId 镜像客户 - **适用规则**:`rule:BR-APPT-001`(状态机)、`rule:BR-APPT-003`(客户归属)、`rule:BR-APPT-004`(服务时长 + 门店容量 + 并发锁)、`rule:BR-SC-001`(门店客户建档)、`rule:BR-FU-002`(真实再次预约) - **代码**:`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/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 旧“直接创建员工”入口已停用,固定返回 `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;按姓名、本人手机号、1~30 天有效期创建。返回原始 `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` ## 客户端埋点 / 会话生命周期 ### 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 查看今日行动工作台(登录落点)。服务端按完整本店数据聚合逾期预约、服务中、今日待开始、成片异常、新报告待发送和到期 FollowUpTask,返回准确总数与低敏预览;报告提交、显式确认发送、打开、留资与回访后真实再次预约均通过 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 后台回访任务池。导航路径保留 `/leads`,操作对象已由原始留资切换为 `FollowUpTask`。 - **入口**:Admin UI `/leads`;`GET /api/admin/follow-up-tasks` - **输入**:`status`、`dueDateTo`、`page`、`pageSize` - **输出**:`entity:follow_up_task` - **适用规则**:`rule:BR-ADMIN-001`、`rule:BR-ADMIN-002`、`rule:BR-FU-001` - **代码/测试**:`AdminFollowUpTaskController#list`、`FollowUpTaskService#list`、`admin/src/views/LeadsView.vue`;`AdminFollowUpTaskControllerTest`、`FollowUpTaskServiceTest` - **证据**:`anchored` ### action:start_follow_up_task 领取待处理回访任务;同一任务只能由一名门店员工持有。 - **入口**:`POST /api/admin/follow-up-tasks/{taskId}/start` - **输出**:`entity:follow_up_task` - **适用规则**:`rule:BR-FU-001` - **代码/测试**:`AdminFollowUpTaskController#start`、`FollowUpTaskService#start`;`FollowUpTaskServiceTest` - **证据**:`anchored` ### action:reschedule_follow_up_task 领取人登记 `no_answer/follow_later` 并设置下次日期,联系次数加一,任务回到 `pending`。 - **入口**:`POST /api/admin/follow-up-tasks/{taskId}/reschedule` - **输入**:`reason`、`dueDate` - **输出**:`entity:follow_up_task` - **适用规则**:`rule:BR-FU-001` - **证据**:`anchored` ### action:close_follow_up_task 领取人以 `not_interested/invalid_contact` 关闭本轮任务;`rebooked` 不允许手工选择。 - **入口**:`POST /api/admin/follow-up-tasks/{taskId}/close` - **输入**:`outcome` - **输出**:`entity:follow_up_task` - **适用规则**:`rule:BR-FU-001`、`rule:BR-FU-002` - **证据**:`anchored` ### 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:view_store_customer_timeline 查看本店某个 StoreCustomer 的事实时间线。 - **入口**:`GET /api/admin/service-customers/timeline?storeCustomerId=&page=&pageSize=` → `AdminServiceCustomerController#timeline` → Admin UI `/customers` 详情抽屉 - **输入**:`storeCustomerId`、`page`、`pageSize`;`storeId` 强制从当前会话派生 - **输出**:`entity:customer_timeline` - **适用规则**:`rule:BR-AUTH-001`、`rule:BR-ADMIN-001`~`003`、`rule:BR-SC-001`、`rule:BR-SC-002`、`rule:BR-BE-001` - **行为**:解析 canonical 主档及其同店合并别名,按 `occurredAt DESC, id DESC` 分页读取全部关联 BusinessEvent;批量补齐预约/报告/留资/FollowUpTask 的白名单摘要并提供详情跳转 ID,既有事件不改写 - **代码/测试**:`StoreCustomerTimelineService`、`CustomersView.vue`;`StoreCustomerTimelineServiceTest`、`AdminServiceCustomerControllerTest` - **证据**:`anchored` ### 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`