Files
zyt/app/research/parity_patients_permissions.md
2026-08-10 17:29:05 +08:00

230 lines
23 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.
# 患者列表、动态菜单与权限一致性审计
审计日期:2026-08-10
管理端唯一事实来源:`D:\web\zyt\admin\src\views\**`
Python 对照范围:`D:\web\zyt\app\src\doctor_workstation\**`
## 1. 结论摘要
| 审计项 | 结论 | 摘要 |
|---|---|---|
| “患者列表”真实业务页面识别 | **EXACT** | 医生工作站语义下的真实页面是 `first_visit/my_patients/index.vue`,不是同样含“患者列表”字样的 `doctor/progress.vue`。 |
| Python 患者列表基础查询 | **PARTIAL** | 使用同一“我的患者”业务语义、相同五种状态和服务端数据范围,但只实现了主列表的子集。 |
| 患者详情与历史关联 | **MISSING** | Python 没有进入诊单编辑/只读详情链,也没有详情请求;右侧内容直接取列表行及其 `raw`。 |
| 患者订单管理、面诊进度 | **MISSING** | 管理端同一路由内的两个完整工作区在 Python 中均不存在。 |
| 五模块主导航 | **PARTIAL** | Python 解析并保存 `mySelf.menu`,登录时只判断菜单非空;Shell 随后忽略菜单树,使用固定五项加扁平权限码。 |
| 按钮权限 | **PARTIAL** | 多数已有写操作有权限门禁,但存在 canonical 权限码偏差、额外别名放行、患者动作大量缺失;处方库所有者判断存在 fail-open。 |
| 医生角色/部门数据范围 | **PARTIAL** | 患者主列表的请求未注入任意医生 ID,保持服务端权威范围;但 Python 丢弃 `extend.scope`、汇总和 ownership 模式,且缺少订单/进度工作区。 |
判定口径:
- **EXACT**:当前证据范围内,业务入口、条件、权限或数据语义一致。
- **PARTIAL**:主链存在,但字段、筛选、动作、菜单约束或范围提示不完整/不一致。
- **MISSING**:管理端已存在的业务能力,在 Python 中未找到入口或调用链。
## 2. 证据边界
1. 本报告没有读取 `D:\web\zyt\admin\src\views` 之外的管理端文件。因此可以确认 Vue 页面实际引用的 API **函数名、参数和交互**,但不能读取 `@/api/**` 的实现来反推真实 HTTP URL。
2. `views/**` 内没有 `auth.admin/mySelf` 调用或该接口返回的真实菜单 JSON。五个主页面的 `paths/component/perms/sort/is_show/is_disable` 精确菜单记录无法从本次允许范围内恢复。
3. 管理端的菜单管理页证明动态菜单记录至少分别保存“路由路径”“组件路径”“权限字符”“是否显示”“菜单状态”:`D:\web\zyt\admin\src\views\permission\menu\edit.vue:41-94,125-150`;其中隐藏菜单仍可访问的语义写在 `D:\web\zyt\admin\src\views\permission\menu\edit.vue:135-137`。因此,不能把单一扁平权限码等同于完整路由记录。
4. 对服务端是否再次执行权限/所有权校验不作推断;本报告的 P0 是**客户端授权门禁一致性**问题。
## 3. “患者列表”真实页面识别
### 3.1 主页面:`first_visit/my_patients/index.vue`
确认依据:
- 页面直接声明“一诊 / 我的患者”,并说明“患者、挂号与诊单信息按当前角色和部门数据范围展示”:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:5-10`
- 同一路由内明确包含“患者列表 / 订单管理 / 面诊进度”三个工作区:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:15-20`
- 主表由 `myPatientLists` 驱动,并异步装载诊单编辑、预约、订单和进度组件:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:330-344,404-409`
结论:Python `PatientsPage` 应对标这一页面及其子工作区,而不应只对标某张通用患者/挂号表。
### 3.2 排除项:`doctor/progress.vue`
该文件虽在表头使用“患者列表”,但它是医生面诊进度看板:
- 页面要求先选择医生,再展示该医生挂号:`D:\web\zyt\admin\src\views\doctor\progress.vue:57-88`
- 组件名为 `doctorProgress`API 为 `doctorLists``appointmentLists``D:\web\zyt\admin\src\views\doctor\progress.vue:176-182`
- 它显式传 `role_id: 1` 拉医生,并用 `doctor_id + progress_board=1` 拉挂号;注释说明不按医生/医助角色收窄:`D:\web\zyt\admin\src\views\doctor\progress.vue:408-448,460`
因此该页面是跨医生进度看板,不是“我的患者”主数据页。把它的数据范围套到患者模块会错误扩大语义范围。
## 4. 管理端患者模块真实合同
### 4.1 主列表、筛选和服务端范围
| 能力 | 管理端证据 |
|---|---|
| 关键词 | 患者姓名、手机号、助理、医生:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:23-43` |
| 状态 | 未预约、待面诊、已完成、已过号:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:346-393` |
| 挂号日期 | 今日、明日、后天、近 7 天、近 30 天、自定义区间:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:46-69,395-401` |
| 汇总 | 今日/明日/后天预约人数来自 `pager.extend.summary/dates``D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:72-88,411-416` |
| 范围提示 | 顶部和表格显示 `pager.extend.scope.label``D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:90-96,417` |
| 列表字段 | 患者性别年龄/脱敏手机、归属助理、预约医生及状态、预约时间、复诊次数、确认信息、诊单日期:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:105-148` |
主列表 API 符号为 `myPatientLists`,调用参数为 `keyword/status_filter/start_date/end_date/page``D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:330-336,380-408`
### 4.2 行级动作与权限码
| 动作 | 管理端权限/条件 | 证据 |
|---|---|---|
| 编辑诊单 | `tcm.diagnosis/edit` | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:152,418,492-498` |
| 只读查看 | `tcm.diagnosis/readonlyDetail` | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:153,419,500-510` |
| 预约/取消挂号 | `tcm.diagnosis/guahao`;取消仅状态 1/4 | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:154,179-186,420,681-696` |
| 指派/重新指派医助 | `tcm.diagnosis/assign` | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:155-162,421,527-575` |
| 补全身份证 | 复用 `tcm.diagnosis/edit` | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:163-170,422,578-628` |
| 诊单二维码 | 复用挂号权限,且要求有效挂号、状态 1、医生 ID | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:171-178,630-673` |
只读诊单不是列表内的摘要面板。页面先从动态路由中查找 `meta.perms === 'tcm.diagnosis/readonlyDetail'`,再携带诊单 ID 跳转:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:500-510`
### 4.3 订单管理子工作区
- 数据口径明确为“当前患者范围内”的处方业务订单,且“订单创建人不参与数据归属判断”:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:3-10`
- 支持处方审核、支付单审核、履约状态、关键词与日期筛选:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:14-79`
- 汇总包含订单数、有效金额、待审核、完成/签收、拒收数和拒收率:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:82-123,289-297`
- 列表显示订单/患者/处方与诊单/金额/双审/履约/支付单/助理/医生/创建人:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:125-188`
- 数据由 `myPatientOrderLists` 分页加载:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:234-244,282-286`
- 订单按钮以 canonical `tcm.prescriptionOrder/*` 权限和业务状态共同判定;完整矩阵在 `D:\web\zyt\admin\src\views\first_visit\my_patients\components\order-actions.ts:32-124`,包括详情、编辑、双审及撤回、快递、补支付、完成、退款、撤回、上传药房。
### 4.4 面诊进度子工作区
- 服务端通过 `extend.schedule_mode` 决定“按本人归属”或“与排班合并”,而不是客户端按角色 ID 猜测:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:7-26,206-214`
- “本人患者”语义、近七日安排、医生聚合、候诊队列均在同一工作区:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:31-117,120-178`
- API 符号为 `myPatientProgressLists`,数据范围标签来自 `extend.scope.label`,本人患者由 `row.is_self_patient` 标识:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:185-203,263,308-309`
- 每 15 秒自动刷新:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:318-325`
### 4.5 详情与历史关联链
管理端患者行会进入诊单编辑或只读详情,而后继续关联:
- 只读页真实标题为“患者信息详情”,无权限/不存在时明确空态:`D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:4-36`
- 基本信息、完整病历、日常记录、医生备注/舌苔/报告:`D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:38-72`
- 业务订单、视频回放、聊天、医助指派历史、挂号历史分别按权限展示:`D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:74-146`
- 详情由 `diagnosisReadonlyDetail({id})` 拉取,备注另用 `getDoctorNotes({diagnosis_id})` 补齐:`D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:151-188,209-234`
- 编辑页还提供处方病历、订单、视频、聊天、指派与挂号记录分页签:`D:\web\zyt\admin\src\views\tcm\diagnosis\edit.vue:655-759`
- 挂号历史不是列表行快照,而是按诊单 ID 单独调用 `appointmentLists`,携带 `diag_scope_relax: 1`、最多 500 条:`D:\web\zyt\admin\src\views\tcm\diagnosis\components\AppointmentRecordPanel.vue:79-89,139-152`
- 指派历史同样按诊单 ID 调用 `tcmDiagnosisAssignLogList``D:\web\zyt\admin\src\views\tcm\diagnosis\components\AssignLogPanel.vue:47-57,102-125`
- 业务订单按 `context_diagnosis_id + patient_id + scene=diagnosis_edit` 独立分页:`D:\web\zyt\admin\src\views\tcm\diagnosis\components\PatientOrderList.vue:118-148,170-179`
## 5. Python 患者模块逐项对照
| 项目 | Python 当前实现 | 判定 | 说明 |
|---|---|---|---|
| 页面身份 | 标题“我的患者”,描述服务端授权范围:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:84-92` | **EXACT** | 对标对象正确。 |
| 状态筛选 | 五种状态一致:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:94-119` | **EXACT** | 状态值与管理端一致。 |
| 关键词 | Python 提示“姓名、手机号或诊单号”:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:99-103` | **PARTIAL** | 管理端还明确支持助理/医生。 |
| 日期与汇总 | 无日期快捷项、区间和三日汇总 | **MISSING** | 管理端证据见 4.1。 |
| 表格 | 患者、性别年龄、电话、进度、医助、最近预约、复诊:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:132-196` | **PARTIAL** | 缺预约医生独立列、确认信息、诊单日期和全部操作。 |
| 列表请求 | `patients(keyword,status,page,page_size)``D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:307-326` | **PARTIAL** | 无 `start_date/end_date`;服务层正确映射 `status -> status_filter`。 |
| 服务端范围 | `list_patients` 使用 `firstvisit.myPatient/lists`,不注入医生 ID`D:\web\zyt\app\src\doctor_workstation\services\repository.py:398-412` | **EXACT** | 保持服务端权威数据范围,没有客户端越权扩大证据。 |
| `extend` | `PageResult` 完整保留 `extend``D:\web\zyt\app\src\doctor_workstation\core\models.py:590-598,618-672`;页面只读 items/total`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:328-335` | **PARTIAL** | scope、summary、dates 已被解析却未消费。 |
| 患者模型 | 覆盖主列表核心字段并保留 `raw``D:\web\zyt\app\src\doctor_workstation\core\models.py:241-318` | **EXACT** | 解析层可承载主表数据。 |
| 详情 | 选择行后直接 `_render_detail(patient)``D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:349-415` | **MISSING** | 没有诊单详情请求,不能等价于管理端只读/编辑页。 |
| 历史预约 | 从列表行 `appointments` 取最多 6 条:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:417-446` | **MISSING** | 管理端按诊单单独请求最多 500 条并显示更完整字段。 |
| 患者行动作 | 无编辑/只读/预约/指派/补身份证/二维码/取消挂号按钮 | **MISSING** | `D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1-454` 没有动作链。 |
| 订单/进度工作区 | 无 | **MISSING** | Python 只有列表与右侧摘要。 |
补充:`get_value` 会在 dataclass 属性缺失时回退到 `.raw``D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:48-66`。因此右侧摘要“可能”显示列表响应附带的额外字段,但它仍是列表快照,不是独立详情/历史合同。
## 6. 五模块动态路由、API 与按钮权限复核
### 6.1 主路由组件与 Python 导航
| 模块 | 管理端实际组件(views 证据) | Python 固定导航权限 | 判定 |
|---|---|---|---|
| 接诊台 | `D:\web\zyt\admin\src\views\patient\reception\index.vue:1-18` | `doctor.appointment/lists``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:40-47` | **PARTIAL** |
| 处方库 | `D:\web\zyt\admin\src\views\consumer\prescription\list.vue:1-4,234-245` | `tcm.prescriptionLibrary/lists``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:48-54` | **PARTIAL** |
| 已开处方 | `D:\web\zyt\admin\src\views\consumer\prescription\index.vue:1-4,1697-1711` | `tcm.prescription/lists``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:55-61` | **PARTIAL** |
| 患者 | `D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:5-20` | `firstvisit.myPatient/lists``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:62-68` | **PARTIAL** |
| 问诊/诊单 | `D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:726-738` | `tcm.diagnosis/lists``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:69-75` | **PARTIAL** |
这里的 **PARTIAL** 不是说固定权限码必然错误,而是:在 `views/**` 证据范围内无法恢复五条真实 `mySelf.menu` 记录,Python 又没有使用已经解析到的菜单树来做组件/顺序/显示/停用约束。
### 6.2 `mySelf` 菜单链
1. Python 正确请求 `auth.admin/mySelf`,解析用户、权限与 menu`D:\web\zyt\app\src\doctor_workstation\services\repository.py:164-195` —— **EXACT**
2. `Session` 正确保留 `menu``D:\web\zyt\app\src\doctor_workstation\core\session.py:12-22` —— **EXACT**
3. 正式登录只检查 `session.menu` 非空,然后把权限交给 Shell`D:\web\zyt\app\src\doctor_workstation\app.py:413-443` —— **PARTIAL**
4. Shell 注册页面时遍历固定 `NAVIGATION`,只调用扁平 `has_permission`,没有读取 `session.menu``D:\web\zyt\app\src\doctor_workstation\ui\shell.py:248-280` —— **MISSING(菜单绑定)**
实际影响:菜单中任意一项存在即可通过登录守卫;随后五模块的显示、顺序、名称及停用状态均由本地固定表决定。动态路由中的隐藏/停用/组件路径/排序语义没有落地。
### 6.3 按钮权限与功能矩阵
#### 接诊台 — **PARTIAL**
- 管理端备注、编辑病历、完成接诊分别使用 `doctor.appointment/addDoctorNote``tcm.diagnosis/edit``doctor.appointment/complete``D:\web\zyt\admin\src\views\patient\reception\index.vue:144-171`
- 管理端队列内“通知医助/发起通话”按钮本身没有 `v-perms``D:\web\zyt\admin\src\views\patient\reception\index.vue:75-90`
- Python 备注与完成权限一致,但通知增加了 `doctor.appointment/notifyAssistant`,视频增加了 `tcm.diagnosis/videoQr`;缺“编辑病历”:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:322-331`
- Python 的通知、备注、完成调用链存在:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:558-641`;管理端对应 API 调用见 `D:\web\zyt\admin\src\views\patient\reception\index.vue:460-527`
#### 处方库 — **PARTIAL,含 P0 权限门禁问题**
- 管理端 canonical 按钮码是 `wcf.prescription/add|read|edit|delete``D:\web\zyt\admin\src\views\consumer\prescription\list.vue:35-41,83-105`
- 管理端编辑/删除还要求创建人本人,或 root/角色 0、3:`D:\web\zyt\admin\src\views\consumer\prescription\list.vue:278-301`
- Python 新增/编辑/删除同时接受 canonical `wcf.*` 和额外 `tcm.prescriptionLibrary/*``D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:271-279,330-348`
- Python root/角色 0、3 的例外与管理端一致,但当 `user_id``creator_id` 缺失时直接返回可管理:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:460-472`。管理端的 `Number(row.creator_id) === Number(user.id)` 不会对缺失创建人默认放行。
#### 已开处方 — **PARTIAL/MISSING actions**
- 管理端支持业务订单跳转、新增、查看、修正患者、创建订单、编辑、审核、删除,对应权限码见 `D:\web\zyt\admin\src\views\consumer\prescription\index.vue:102-117,214-265`
- 业务订单导航依赖动态路由中的 `meta.perms === 'tcm.prescriptionOrder/lists'`,菜单不存在会拒绝跳转并提示:`D:\web\zyt\admin\src\views\consumer\prescription\index.vue:1862-1871`
- Python 只实现关键词/审核状态的列表和详情请求:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:91-188,284-340`;上述写操作与订单导航均不存在。
#### 患者 — **MISSING actions**
完整动作矩阵见第 4.2 节;Python 患者页没有对应按钮或权限检查。
#### 问诊/诊单 — **PARTIAL/MISSING actions**
- 管理端新增、批量指派、查看、诊单、开方、预约、补身份证、指派/取消指派、视频二维码、确认二维码/取消挂号、挂号日志、订单、删除的权限矩阵见 `D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:154-175,342-405`
- Python 仅保留列表筛选与 `tcm.diagnosis/videoQr` 视频入口:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:101-107,110-236,264-323`
### 6.4 权限匹配语义
- `PermissionSet` 支持精确码、全局 `*``prefix/*``D:\web\zyt\app\src\doctor_workstation\core\permissions.py:24-84`OR 语义见 `D:\web\zyt\app\src\doctor_workstation\core\permissions.py:96-104`
- UI `has_permission` 额外把 `/``.` 互换后尝试匹配:`D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:135-180`
- 管理端视图只声明 canonical 形态,例如 `tcm.diagnosis/edit``wcf.prescription/edit`。由于本次不能读取管理端权限工具实现,不能证明管理端也接受点/斜杠变体;Python 的变体放行应判为 **PARTIAL**,不应当作已证明的兼容要求。
## 7. 医生角色与数据范围
### 7.1 已证明一致的部分
- 患者列表:管理端要求按当前角色/部门展示并把最终范围作为 `extend.scope.label` 返回;Python 调用相同业务域列表,不传任意 `doctor_id`,所以没有客户端扩大数据集的证据。判定 **EXACT(请求边界)**
- 处方库:普通医生只能管理自己创建的模板,root/角色 0、3 才可管理全部。Python 的正常 ID 分支与此一致。判定 **EXACT(正常分支)**
### 7.2 部分或缺失
- Python 不显示 `extend.scope.label`,用户无法验证当前是本人、部门还是其他服务端范围:**PARTIAL**。
- Python 缺失患者订单工作区,因而没有落实“当前患者范围内、订单创建人不参与归属”的口径:**MISSING**。
- Python 缺失面诊进度工作区,因而没有落实服务端 `schedule_mode=ownership``is_self_patient` 和近七日聚合:**MISSING**。
- 除患者模块视图明确写出的范围文字外,接诊、处方、诊单视图没有在允许证据范围内定义服务端角色/部门过滤算法。Python 使用同域列表接口且未见额外越权 `doctor_id` 注入,但无法仅凭 views 宣称五模块的后端数据范围 **EXACT**;应保留 **PARTIAL / 服务端待证**
## 8. P0 / P1 缺口
### P0
1. **处方库所有权校验 fail-open。**
Python 在当前用户 ID 或模板 `creator_id` 任一缺失时允许编辑/删除:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:460-466`;管理端仅允许严格所有者匹配或 root/角色 0、3:`D:\web\zyt\admin\src\views\consumer\prescription\list.vue:286-301`。应改成缺失即拒绝,并只在明确 root/角色例外时放行。若服务端另有强制校验,实际数据写入风险会降低,但客户端授权偏差仍已成立。
### P1
1. **患者模块只有主表子集。** 缺日期筛选、三日汇总、范围标签、确认/诊单日期列和全部行级动作。
2. **患者详情不是管理端真实详情。** Python 不请求诊单只读/编辑详情,病历摘要与最多 6 条历史预约依赖列表行快照,可能为空或陈旧。
3. **患者订单管理与面诊进度两个工作区完全缺失。** 同时丢失订单数据范围、订单操作权限矩阵、ownership 模式和 15 秒候诊刷新。
4. **Shell 忽略 `mySelf.menu`。** 已解析的动态菜单只用于“非空”登录守卫;五模块由固定表和扁平权限决定,未落实组件路径、显示/停用、排序和真实菜单成员关系。
5. **处方库权限码存在额外别名放行。** Python 接受 `tcm.prescriptionLibrary/add|edit|delete`,而视图仅证明 canonical `wcf.prescription/*`;应在拿到真实 `mySelf.permissions/menu` 样本后收敛。
6. **通用权限帮助器接受 `/`/`.` 互换。** 该兼容未被 views-only 证据证明,可能让非 canonical grant 意外点亮按钮。
7. **接诊台按钮矩阵不一致。** Python 比管理端额外门禁通知/视频,同时缺少 `tcm.diagnosis/edit` 的编辑病历动作。
8. **已开处方和问诊页均被压缩为只读子集。** 管理端已有的新增/编辑/审核/删除/订单/预约/指派等受权操作未落地。
## 9. 建议的验收优先级
1. 先修复处方库所有权 fail-open,并用“缺 `creator_id`、缺当前用户 ID、本人、他人、root、角色 0/3”六组合同测试覆盖。
2. 让 Shell 以 `Session.menu` 为主约束,再叠加权限码;不要仅以硬编码五项替代动态菜单。
3. 将患者页拆成与管理端一致的三个工作区,优先补真实只读详情/历史请求和主表行级动作。
4. 消费 `PageResult.extend.scope/summary/dates/schedule_mode`,把服务端权威范围直接呈现给医生。
5. 获取一份真实医生角色 `mySelf` 响应样本后,补做菜单 `paths/component/perms/is_show/is_disable/sort` 的精确合同测试;这一步无法由 `views/**` 单独完成。