diff --git a/.agents/common-context.md b/.agents/common-context.md index d0ceab6..6d766f4 100644 --- a/.agents/common-context.md +++ b/.agents/common-context.md @@ -45,7 +45,7 @@ 项目领域知识以本体论框架建模为 SSOT,位于 `docs/ontology/`: -- `objects.md` — 实体(含 `admin_console`/`workbench`/`service_customer` Phase A 预录入;P1/P2 gap 占位见 membership 等) +- `objects.md` — 实体(`store_customer` 已落为 JPA `StoreCustomer`;`admin_console`/`workbench` 为后台读模型;P1/P2 gap 占位见 membership 等) - `actions.md` — 动作(controller 接口 + 客户端埋点 + **admin_* 后台动作 documented**) - `events.md` — 事件(含 `workbench_viewed` gap) - `rules.md` — 规则(含 `rule:BR-ADMIN-001`…`004` 后台门禁与范围闸门) diff --git a/P0-研发落地清单.md b/P0-研发落地清单.md index da163d8..ee3fc0d 100644 --- a/P0-研发落地清单.md +++ b/P0-研发落地清单.md @@ -58,6 +58,7 @@ | **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` | --- diff --git a/coverage/ontology-coverage-audit.md b/coverage/ontology-coverage-audit.md index ea3def3..5f65ead 100644 --- a/coverage/ontology-coverage-audit.md +++ b/coverage/ontology-coverage-audit.md @@ -122,7 +122,7 @@ python3 docs/graph/audit_drift.py |----|------|------| | `entity:admin_console` | Entity | Web 管理端壳 | | `entity:workbench` / `workbench_todo_item` | Entity | 今日工作台与待办 | -| `entity:service_customer` | Entity | 瘦身客户主档(非会员卡) | +| `entity:store_customer` | Entity | 门店客户稳定主档(非会员卡) | | `action:view_workbench` 等 8 个 admin_* | Action | 工作台/列表/设置/排班 | | `rule:BR-ADMIN-001`…`004` | Rule | 角色、店范围、内部字段、范围闸门 | | `event:workbench_viewed` | Event | 可选埋点(gap) | diff --git a/graph/ontology.jsonl b/graph/ontology.jsonl index 79a082f..4fe9139 100644 --- a/graph/ontology.jsonl +++ b/graph/ontology.jsonl @@ -10,7 +10,7 @@ {"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","customerUserId","customerPhone","customerName"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/create","AppointmentController#create","AppointmentService#createBooking"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-003","rule:BR-APPT-004"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#create","test":"AppointmentControllerIdentityTest; AppointmentServiceTest","evidence":"anchored","domain":"appointment"} +{"id":"action:create_appointment","type":"Action","name":"createAppointment","nameZh":"创建预约","inputs":["petName","petType","serviceType","appointmentTime","storeId","remark","petId","customerUserId","customerPhone","customerName"],"outputs":["entity:appointment"],"entrypoints":["POST /api/appointment/create","AppointmentController#create","AppointmentService#createBooking"],"appliesRules":["rule:BR-APPT-001","rule:BR-APPT-003","rule:BR-APPT-004","rule:BR-SC-001"],"code":"backend/src/main/java/com/petstore/controller/AppointmentController.java#create; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchBooking","test":"AppointmentControllerIdentityTest; AppointmentServiceTest; StoreCustomerServiceTest","evidence":"anchored","domain":"appointment"} {"id":"action: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"} @@ -19,7 +19,7 @@ {"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:submit_lead","type":"Action","name":"submitLead","nameZh":"宠主留资","inputs":["token","phone","consent","loginCode"],"outputs":["leadId","remindDate","unsubscribeToken","wechatBound"],"entrypoints":["POST /api/report/{token}/reminder","ReportLeadController#submitReminder","ReportLeadService#submit"],"appliesRules":["rule:BR-LEAD-001","rule:BR-SC-001"],"code":"backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder; backend/src/main/java/com/petstore/service/StoreCustomerService.java#touchLead","evidence":"anchored","test":"ReportLeadControllerTest; ReportLeadServiceStoreCustomerTest; StoreCustomerServiceTest","domain":"lead"} {"id":"action: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"} @@ -47,6 +47,7 @@ {"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-SC-001","type":"Rule","name":"门店客户主档唯一性与合并","domain":"admin","appliesTo":["action:create_appointment","action:submit_lead","action:admin_list_service_customers"],"code":"backend/src/main/java/com/petstore/service/StoreCustomerService.java","doc":"docs/ontology/rules.md#rule:BR-SC-001; docs/架构决策-门店客户主档-2026-08-01.md","test":"StoreCustomerServiceTest; ReportLeadServiceStoreCustomerTest; IdentityOwnershipQueryTest","evidence":"anchored"} {"id":"rule:BR-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"} @@ -193,7 +194,7 @@ {"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":"entity:store_customer","type":"Entity","name":"StoreCustomer","nameZh":"门店客户主档(瘦身)","domain":"admin","jpa":true,"fields":["id","store_id","customer_user_id","phone","display_name","source","first_contact_at","last_contact_at","create_time","update_time","deleted"],"systems":["Store Admin FE","Backend Core","Report Media Backend"],"code":"backend/src/main/java/com/petstore/entity/StoreCustomer.java; backend/src/main/java/com/petstore/service/StoreCustomerService.java; backend/src/main/java/com/petstore/service/AdminServiceCustomerService.java","doc":"docs/ontology/objects.md#entity:store_customer; docs/架构决策-门店客户主档-2026-08-01.md","test":"StoreCustomerServiceTest; AdminServiceCustomerServiceTest; IdentityOwnershipQueryTest","evidence":"anchored","note":"门店与客户的稳定关系实体;API 动态聚合宠物/到店/报告/留资,不含余额/次卡/积分。","status":"implemented"} {"id":"rule:BR-ADMIN-001","type":"Rule","name":"adminRoleGate","nameZh":"门店后台角色门禁(boss/staff 可读写)","domain":"admin","appliesTo":["action:view_workbench","action:admin_list_appointments","action:admin_list_reports","action:admin_list_leads","action:admin_list_service_customers","action:admin_update_settings"],"code":"","doc":"docs/ontology/rules.md#rule:BR-ADMIN-001; docs/门店管理后台-PhaseA-页面清单PRD.md","test":"","evidence":"documented","note":"boss 与 staff 均可进入 admin_console 并编辑本店数据;customer 禁止。删店/部分设置仍仅 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 约束。手机号默认仍脱敏,明文需更高权限(预留)。"} @@ -203,7 +204,7 @@ {"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_list_service_customers","type":"Action","name":"adminListServiceCustomers","nameZh":"后台服务客户列表","domain":"admin","inputs":["source","hasPendingLead","lastVisitFrom","lastVisitTo","q","page","pageSize"],"outputs":["entity:store_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","rule:BR-SC-001"],"code":"backend/src/main/java/com/petstore/controller/AdminServiceCustomerController.java#listServiceCustomers; backend/src/main/java/com/petstore/service/AdminServiceCustomerService.java","doc":"docs/ontology/actions.md#action:admin_list_service_customers; docs/门店管理后台-PhaseA-页面清单PRD.md §7.6","test":"AdminServiceCustomerControllerTest; AdminServiceCustomerServiceTest","evidence":"anchored","note":"以 t_store_customer 稳定主档为入口,动态聚合宠物/到店/报告/留资;返回 storeCustomerId 和脱敏手机号。"} {"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 期可不实现上报。"} @@ -212,8 +213,9 @@ {"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:store_has_service_customers","type":"Relation","subject":"entity:store","predicate":"HAS","object":"entity:store_customer","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"StoreCustomer.store_id"} +{"id":"rel:service_customer_is_user","type":"Relation","subject":"entity:store_customer","predicate":"LINKS","object":"entity:user","domain":"admin","evidence":"anchored","cardinality":"N—0..1","doc":"StoreCustomer.customer_user_id;留资客户可先为空"} +{"id":"rel:service_customer_has_pets","type":"Relation","subject":"entity:store_customer","predicate":"HAS","object":"entity:pet","domain":"admin","evidence":"anchored","cardinality":"1—N","doc":"customer_user_id -> Pet.owner_user_id(动态读模型)"} {"id":"rel: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"} @@ -221,5 +223,5 @@ {"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:admin_list_customers_reads_sc","type":"Relation","subject":"action:admin_list_service_customers","predicate":"READS","object":"entity:store_customer","domain":"admin","evidence":"anchored"} {"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 等"} diff --git a/ontology/README.md b/ontology/README.md index 3c0c9f2..bdfd6d5 100644 --- a/ontology/README.md +++ b/ontology/README.md @@ -61,7 +61,7 @@ rel:subject_predicate_object # rel:report_belongs_to_appointment 已覆盖 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。 +**门店 Web 后台 Phase A**:`AdminConsole`、`Workbench`、`WorkbenchTodoItem` 仍是非 JPA 读模型;`StoreCustomer` 已落为 JPA 稳定主档。`BR-ADMIN-001`…`004` 与 admin actions 继续约束后台;收银/会员资产仍为 gap。 P1/P2 方向(会员/储值/套餐/历史报告/看板)以 `gap` 证据条目录入,不展开字段;受 `rule:BR-ADMIN-004` 约束不得偷渡进 Phase A 设计。 diff --git a/ontology/actions.md b/ontology/actions.md index 74ff60c..bc3bab8 100644 --- a/ontology/actions.md +++ b/ontology/actions.md @@ -90,7 +90,7 @@ - **输入**:`petName`、`petType`、`serviceType`、`appointmentTime`、`storeId`、`remark?`、`petId?`;门店代客预约另需 `customerUserId` 或 `customerPhone` - **输出**:`entity:appointment` - **身份口径**:customer 自约时客户=创建人=current.userId;boss/staff 代约时创建人=current.userId,客户按请求解析,legacy userId 镜像客户 -- **适用规则**:`rule:BR-APPT-001`(状态机)、`rule:BR-APPT-003`(客户归属)、`rule:BR-APPT-004`(半小时档 + 营业时间 + 占用校验) +- **适用规则**:`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` @@ -184,9 +184,10 @@ - **入口**:`POST /api/report/{token}/reminder` → `ReportLeadController#submitReminder` → `ReportLeadService#submit`(公开) - **输入**:`phone`、`consent`、`loginCode?` - **输出**:`leadId`、`remindDate`、`unsubscribeToken`、`wechatBound` -- **适用规则**:`rule:BR-LEAD-001`(去重) +- **适用规则**:`rule:BR-LEAD-001`(去重)、`rule:BR-SC-001`(门店客户建档/绑定) - **代码**:`backend/src/main/java/com/petstore/controller/ReportLeadController.java#submitReminder` -- **证据**:`documented`(去重逻辑未补测试用例) +- **测试**:`ReportLeadControllerTest`、`ReportLeadServiceStoreCustomerTest` +- **证据**:`anchored` ### action:unsubscribe @@ -631,11 +632,11 @@ 后台服务客户列表(瘦身主档;姓名/手机/宠物名搜索)。 - **入口**:`GET /api/admin/service-customers` → `AdminServiceCustomerController#listServiceCustomers` → Admin UI `/customers` -- **输出**:`entity:service_customer` -- **适用规则**:`rule:BR-ADMIN-001` … `BR-ADMIN-004` +- **输出**:`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` -- **产品决策**:新建聚合 API,不以前端拼装为正式方案;不做余额/次卡列、持卡会员导入 +- **产品决策**:以 `t_store_customer` 稳定主档为入口聚合,返回 `storeCustomerId`;不做余额/次卡列、持卡会员导入 - **文档**:`docs/门店管理后台-PhaseA-页面清单PRD.md §7.6` ### action:admin_update_settings diff --git a/ontology/objects.md b/ontology/objects.md index 18fcf08..72383bd 100644 --- a/ontology/objects.md +++ b/ontology/objects.md @@ -385,20 +385,24 @@ - **证据**:`documented` -## entity:service_customer +## entity:store_customer -**服务客户主档(瘦身)**。服务经营视角的客户投影(User + Pet + 预约/报告/留资),**不是** `entity:membership`。 +**StoreCustomer 门店客户主档(瘦身)**。门店与客户的稳定关系实体,**不是** `entity:membership`。后台读模型以它为入口,再聚合 Pet + Appointment + Report + ReportLead。 | 字段 | 说明 | |------|------| -| userId / displayName / phoneMasked | 身份(手机默认脱敏) | -| pets | 关联宠物摘要 | -| lastVisitAt / lastReportId | 最近到店 / 最近报告 | -| leadStatus / source | 留资状态;来源:预约/报告留资 | -| storeId | 本店范围 | +| id | 本店客户稳定 ID(API: storeCustomerId) | +| store_id | 本店范围 | +| customer_user_id | 可选全局 customer 账号;留资客户可先为空 | +| phone / display_name | 本店联系手机号与称呼;API 手机号默认脱敏 | +| source | 首次来源:customer_booking / assisted_booking / report_lead | +| first_contact_at / last_contact_at | 首次/最近接触时间 | +| create_time / update_time / deleted | 审计与软删除 | + +API 动态聚合字段:`pets`、`lastVisitAt`、`lastReportId`、`leadStatus`、`source`。`lastVisitAt` 只统计已开始/已完成的历史服务。 - **不做**:余额、押金、积分、次卡、会员等级、批量导入持卡会员 -- **证据**:`documented`(可先无独立表,查询聚合) +- **证据**:`anchored`(`t_store_customer` + 动态读模型) - **动作**:`action:admin_list_service_customers` --- diff --git a/ontology/relations.md b/ontology/relations.md index c48c0b9..28bf146 100644 --- a/ontology/relations.md +++ b/ontology/relations.md @@ -134,7 +134,7 @@ | `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 | +| `entity:admin_console` / `workbench` / `workbench_todo_item` / `store_customer` | Store Admin FE + Backend Core | | P1/P2 gap 占位 | 未实现(membership / stored_value / service_package / report_history / analytics_dashboard);Phase A 后台禁止偷渡 | ## 其他关系(Batch 补全) @@ -151,8 +151,9 @@ | `admin_console` HAS `workbench` | 登录默认工作台 | | `workbench` HAS `workbench_todo_item` | 今日待办 | | `workbench` AGGREGATES `appointment` / `report` / `report_lead` | 指标与异常来源 | -| `service_customer` PROJECTS `user` | 客户投影 | -| `service_customer` HAS `pet` | 一人多宠 | +| `store` HAS `store_customer` | 一店多个稳定客户关系 | +| `store_customer` LINKS `user` | 可选关联全局 customer 账号;留资客户可先不关联 | +| `store_customer` HAS `pet` | 通过 customer user 关联一人多宠 | | `view_workbench` EMITS `workbench_viewed` | 可选埋点 | | `BR-ADMIN-001/002` APPLIES_TO `view_workbench` 等 | 角色与店范围 | | `BR-ADMIN-004` CONSTRAINS `admin_console` | Phase A 范围闸门 | diff --git a/ontology/rules.md b/ontology/rules.md index 5868ed2..ec5249b 100644 --- a/ontology/rules.md +++ b/ontology/rules.md @@ -332,7 +332,7 @@ - **测试**:`ReportLeadControllerTest` - **证据**:`anchored` -## 门店 Web 后台(Phase A · documented) +## 门店 Web 后台(Phase A) ### rule:BR-ADMIN-001 @@ -364,3 +364,12 @@ - **适用**:`entity:admin_console`、`entity:workbench` 及一切 admin Phase A 动作 - **文档**:`docs/门店管理后台升级方案-对标宠老板.md` - **证据**:`documented` + +### rule:BR-SC-001 + +**门店客户主档唯一性与合并**:同一门店内,`customer_user_id` 和手机号分别只能对应一条有效 StoreCustomer。留资可先按手机号建档;后续预约/登录识别到 customer 后绑定或合并。不得将 boss/staff 绑定为客户主档。 + +- **适用动作**:`action:create_appointment`、`action:submit_lead`、`action:admin_list_service_customers` +- **代码**:`backend/src/main/java/com/petstore/service/StoreCustomerService.java` +- **测试**:`StoreCustomerServiceTest`、`ReportLeadServiceStoreCustomerTest`、`IdentityOwnershipQueryTest` +- **证据**:`anchored` diff --git a/产品设计文档.md b/产品设计文档.md index d2d3359..acddf94 100644 --- a/产品设计文档.md +++ b/产品设计文档.md @@ -84,10 +84,23 @@ | phone | VARCHAR | 手机号 | | avatar | VARCHAR | 头像 | | store_id | BIGINT | 所属店铺 | -| role | VARCHAR | boss / staff | +| role | VARCHAR | boss / staff / customer | | create_time | DATETIME | 创建时间 | | update_time | DATETIME | 更新时间 | +### t_store_customer(门店客户主档) +| 字段 | 类型 | 说明 | +|------|------|------| +| id | BIGINT | 本店客户稳定主键 | +| store_id | BIGINT | 门店 ID | +| customer_user_id | BIGINT | 全局 customer 账号,留资客户可空 | +| phone | VARCHAR(20) | 本店最后确认手机号,API 默认脱敏 | +| display_name | VARCHAR(64) | 门店视角客户称呼 | +| source | VARCHAR(32) | customer_booking / assisted_booking / report_lead | +| first_contact_at / last_contact_at | DATETIME | 首次/最近接触时间 | +| create_time / update_time | DATETIME | 创建/更新时间 | +| deleted | TINYINT | 软删除标记 | + ### t_appointment(预约表) | 字段 | 类型 | 说明 | |------|------|------| diff --git a/架构决策-门店客户主档-2026-08-01.md b/架构决策-门店客户主档-2026-08-01.md new file mode 100644 index 0000000..259d0d9 --- /dev/null +++ b/架构决策-门店客户主档-2026-08-01.md @@ -0,0 +1,55 @@ +# 架构决策:StoreCustomer 门店客户主档 + +> 日期:2026-08-01 +> +> 状态:已采纳,待生产数据迁移 +> +> 范围:Backend Core、Report Media Backend、Store Admin、Data Model + +## 1. 决策背景 + +原「服务客户」列表每次请求时才从 User、Pet、Appointment、Report 和 ReportLead 拼装。同一个客户在不同门店没有稳定关系 ID,留资手机号与后续 customer 账号也可能生成两条投影,无法可靠承载客户时间线、回访任务和再次预约。 + +## 2. 冻结模型 + +`StoreCustomer` 是「门店与客户的关系主档」,不是全局账号,也不是会员资产。 + +| 字段 | 语义 | +|---|---| +| `id` | 本店客户稳定 ID,后续时间线/回访任务的关联键 | +| `store_id` | 门店数据范围 | +| `customer_user_id` | 全局 customer 账号;可空,便于先承接留资 | +| `phone` | 本店最后确认联系号;后台 API 默认脱敏 | +| `display_name` | 门店视角称呼 | +| `source` | 首次来源:`customer_booking` / `assisted_booking` / `report_lead` | +| `first_contact_at` / `last_contact_at` | 本店关系首次/最近触达时间 | + +同店分别对 `(store_id, customer_user_id)` 和 `(store_id, phone)` 加唯一约束。`customer_user_id` 为空时可先以手机号建档;后续识别到 customer 账号时绑定或合并,禁止同手机号指向两个 customer。 + +## 3. 写入入口 + +- 宠主自助预约:建档来源 `customer_booking`。 +- boss/staff 代客预约:建档来源 `assisted_booking`。 +- 报告留资:建档来源 `report_lead`;若手机号已对应 customer,直接绑定。 +- 后续触达只更新 `last_contact_at`,不覆盖可靠的首次来源。 + +## 4. 读模型与口径 + +`GET /api/admin/service-customers` 以 `StoreCustomer` 为列表入口,再聚合宠物、预约、报告和留资,返回稳定 `storeCustomerId`。 + +- `lastVisitAt` 只计算已开始/已完成的历史服务;未来预约不算到店。 +- `lastReportId`、`leadStatus`、`pets` 仍从事实表实时读取,避免将易变经营指标重复写入主档。 +- 返回 `phoneMasked`,不返回明文 `phone`。 + +## 5. 历史迁移 + +按顺序执行: + +1. `backend/db/migrations/20260801_split_service_identity.sql` +2. `backend/db/migrations/20260801_create_store_customer.sql` + +第二个脚本先从已确定客户归属的预约建档,再合并报告留资。末尾三个验证计数必须全部为 0 才可发布新后端。 + +## 6. 非目标 + +本阶段不引入会员等级、积分、储值、次卡、标签群发、跨店共享或客户合并后台。这些需要独立的权限、资产和审计模型。 diff --git a/门店管理后台-PhaseA-编码任务brief.md b/门店管理后台-PhaseA-编码任务brief.md index 937a8fb..2a20a74 100644 --- a/门店管理后台-PhaseA-编码任务brief.md +++ b/门店管理后台-PhaseA-编码任务brief.md @@ -62,22 +62,22 @@ cd /Users/apple/_src/petstore/admin && npm run build | 字段 | 内容 | |------|------| -| Task Name | 新增服务客户聚合列表接口 | +| Task Name | 建立 StoreCustomer 主档与服务客户聚合列表 | | 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 客户域) | +| Status | Done(2026-08-01) | +| Repo/Path | `backend/.../entity|mapper|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]` | +| ontologyRefs | `[entity:store_customer, action:admin_list_service_customers, rule:BR-SC-001, 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(投影聚合,无新表优先) | +| Data Model Review Required | Yes(`t_store_customer` 唯一约束与历史迁移) | | Access / Privacy Review Required | Yes(手机号脱敏、本店) | -**验收**:boss/staff 本店 200;customer 403;跨店不可见;无余额/次卡字段;搜索含宠物名;本体 entrypoint 升 anchored 且 `audit_drift` 无漂移。 +**验收**:boss/staff 本店 200;customer 403;跨店不可见;返回稳定 `storeCustomerId` 且手机脱敏;无余额/次卡字段;搜索含宠物名;本体与代码无漂移。 **验证**: ```bash @@ -146,7 +146,7 @@ python3 /Users/apple/_src/petstore/docs/graph/audit_drift.py | 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]` | +| ontologyRefs | `[entity:store_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。 diff --git a/门店管理后台-PhaseA-页面清单PRD.md b/门店管理后台-PhaseA-页面清单PRD.md index efd6b3f..f407330 100644 --- a/门店管理后台-PhaseA-页面清单PRD.md +++ b/门店管理后台-PhaseA-页面清单PRD.md @@ -300,7 +300,7 @@ entity:admin_console **页面 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]` +**ontologyRefs**:`[entity:store_customer, entity:pet, entity:user, action:admin_list_service_customers, rule:BR-SC-001, rule:BR-ADMIN-001, rule:BR-ADMIN-002, rule:BR-ADMIN-003, rule:BR-ADMIN-004]` ### 7.1 定位 @@ -337,14 +337,16 @@ entity:admin_console 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[] } + Response: { total, summary: { customerCount, pendingLeadCount, monthVisitCount }, list: StoreCustomerView[] } ``` +`StoreCustomerView` 必含稳定 `storeCustomerId`,并包含 `userId?`、`displayName`、`phoneMasked`、`originSource`、`firstContactAt`、`lastContactAt`、`pets`、`lastVisitAt`、`lastReportId`、`leadStatus`、`source`。明文手机号不得返回。 + - 对应动作:`action:admin_list_service_customers` -- 对应实体:`entity:service_customer` +- 对应实体:`entity:store_customer` - **不做**前端拼装多接口作为正式方案(可临时 mock,上线以新 API 为准) -可选后续:`GET /api/admin/service-customers/{userId}` 详情。 +可选后续:`GET /api/admin/service-customers/{storeCustomerId}` 详情。 ### 7.7 验收 @@ -408,7 +410,7 @@ API:`user/staff-list`、`create-staff`、删除接口 | 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` | +| A5 客户 | `admin_list_service_customers` | `store_customer` | **已实现** `GET /api/admin/service-customers` | | A7 设置 | `admin_update_settings` | `store` / `service_type` / `user` | store/service-type/user | --- diff --git a/门店管理后台升级方案-对标宠老板.md b/门店管理后台升级方案-对标宠老板.md index 4a0dd6e..8cbc5a3 100644 --- a/门店管理后台升级方案-对标宠老板.md +++ b/门店管理后台升级方案-对标宠老板.md @@ -184,7 +184,7 @@ Web 后台主攻:**经营、检索、批量、报表、设置**;**允许「 ## 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` 闸门约束。 +**本体状态**:Phase A 后台概念已写入 ontology(`entity:admin_console` / `workbench` / `store_customer`,`rule:BR-ADMIN-001`…`004`,`action:view_workbench` 等);其中 `store_customer` 已落为稳定 JPA 主档,其余读模型按实现状态为准。 下一文档步骤:~~用上述 ontologyRefs 写 Phase A 页面清单 PRD~~ → **已完成**:`docs/门店管理后台-PhaseA-页面清单PRD.md`