更新
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
# 医生工作站最终 parity 审计
|
||||
|
||||
审计日期:2026-08-10
|
||||
审计方式:只读源码复核 + 纯本地测试;未修改业务源码。
|
||||
结论基准:`D:\web\zyt\admin\src\views` 的实际实现优先于既有 `research/parity_*.md`。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
当前医生桌面整体判定为 **PARTIAL**,不建议在修复 P0 前作为后台同型版本发布。
|
||||
|
||||
- **P0:3 项**
|
||||
1. 接诊附件没有上传步骤,本机绝对路径被作为附件 URL 发给服务端。
|
||||
2. 问诊开方在当前 appointment 无处方或查询失败时按 diagnosis 回退,可显示、编辑或作废另一挂号的处方。
|
||||
3. “我的患者”预约把 `source_patient_id` 优先写入 `patient_id`,而管理端该端点明确要求诊单 ID,可能预约到错误上下文或被后端拒绝。
|
||||
- **Repository 方法存在性:EXACT**。当前五页及相关对话框实际调用的 canonical 方法均同时存在于 Protocol、Remote、Demo,未发现不存在或拼错的方法名;兼容别名均能解析到有效方法。
|
||||
- **动态菜单:EXACT(限定五个受支持页面)**。非 Demo 会话使用服务端菜单,遵守显示、禁用、排序、路由及 canonical permission;未把未实现的后台运营页面伪装成本地入口。
|
||||
- **医生活跃视频 eligibility:EXACT;权限语义:PARTIAL**。状态与三个业务 ID 已对齐,但问诊页错误复用了小程序二维码权限 `tcm.diagnosis/videoQr`。
|
||||
- 既有三份 parity 文档明显早于本轮实现,不能直接当作当前验收结果;本报告已重新逐项分类。
|
||||
|
||||
本次执行:
|
||||
|
||||
```text
|
||||
.venv\Scripts\python.exe -m pytest
|
||||
110 passed in 1.33s
|
||||
```
|
||||
|
||||
全绿不能覆盖本报告的 P0:`D:\web\zyt\app\tests\test_reception_parity_ui.py:263-306` 当前把本地绝对路径进入备注 payload 当成正确行为;`D:\web\zyt\app\tests\test_consultations_parity_ui.py:241-264` 当前把 appointment miss 后回退 diagnosis 旧处方当成正确行为。这两组测试需要随 P0 修复反向改写。
|
||||
|
||||
## 2. 范围与判定规则
|
||||
|
||||
审计范围:
|
||||
|
||||
- Python 五页:
|
||||
- `D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py`
|
||||
- `D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py`
|
||||
- `D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py`
|
||||
- `D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py`
|
||||
- `D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py`
|
||||
- 与五页直接相连的 `ui/dialogs/diagnosis.py`、`ui/dialogs/prescription.py`、`ui/widgets.py`、`ui/shell.py`、`core/models.py`、`services/*`。
|
||||
- 管理端唯一事实基准:`D:\web\zyt\admin\src\views`,必要时追到其直接使用的 `src/components` 与 `src/api`。
|
||||
|
||||
明确排除:企业微信后台运营、转化/统计后台、医助专属旁观、小程序二维码本身、其他角色专属批量运营能力。医助旁观因此判为 **N/A / intentionally excluded**,不计 MISSING。
|
||||
|
||||
判定含义:
|
||||
|
||||
- **EXACT**:端点、DTO、可见字段/筛选/动作、权限及状态门槛在医生桌面范围内等价。
|
||||
- **PARTIAL**:主链存在,但字段、上下文、权限、状态门槛、分页或异步一致性不完整。
|
||||
- **MISSING**:管理端医生主链存在,而 Python 没有可用实现。
|
||||
|
||||
## 3. 页面与横切能力总表
|
||||
|
||||
| 范围 | 判定 | 已对齐 | 主要偏差 |
|
||||
|---|---|---|---|
|
||||
| 接诊台 | **PARTIAL** | 今日范围;等待/过号状态 1/4;详情、通知医助、备注读取/删除、完成接诊;直呼 ID | 附件真实上传 **MISSING/P0**;固定只取前 50 条;附件无预览/打开 |
|
||||
| 我的患者 | **PARTIAL** | 患者/订单/面诊进度三工作区;订单筛选、summary/scope、字段、状态动作矩阵与 canonical permissions | 预约上下文 **P0**;预约表单、诊单详情、上下文订单不完整;写操作竞态 |
|
||||
| 我的问诊 | **PARTIAL** | 列表、详情/编辑/删除端点;字典/医助;开方/作废入口;视频状态与 ID | appointment 处方回退 **P0**;诊单编辑只覆盖小字段子集;部分医生行操作/筛选缺失;视频权限码错误 |
|
||||
| 我的处方库 | 功能 **EXACT** / 运行时 **PARTIAL** | 筛选、15 条分页、字段、查看/新增/编辑/删除、远程药材、校验、owner/root/role 行条件;`disable_edit` 语义正确 | 通用权限 helper 接受非 canonical 别名;worker 读 Qt 控件;加载中 refresh 被丢弃 |
|
||||
| 已开处方 | **PARTIAL** | 完整筛选与主要字段;详情、CRUD、患者修正、审核、作废、订单创建/查看、A4 打印、PDF;状态动作矩阵 | 诊单详情授权边界;建单支付单竞态;诊单上下文订单与部分业务字段;重复药材校验 |
|
||||
| Repository Protocol/Remote/Demo 方法 | **EXACT** | 五页实际 canonical 调用全部存在且签名兼容 | 上传素材方法 **MISSING**;Demo 问诊筛选语义不完整 |
|
||||
| 动态菜单 | **EXACT** | 使用服务端 menu;显示/禁用/排序/路由/权限;只注册受支持页面 | 无发布阻断偏差 |
|
||||
| 视频端点与 eligibility | **PARTIAL** | ticket/start/bind/end 方法存在;问诊 `has_appointment && status==1`;接诊今日状态域;ID 分离 | 原生直呼错误复用 `videoQr` 权限;服务端仍须最终复核当前状态/归属 |
|
||||
|
||||
## 4. P0 发布阻断
|
||||
|
||||
### P0-1 接诊附件不是上传,而是泄漏并保存本机路径 — MISSING
|
||||
|
||||
管理端合同是严格的两阶段流程:
|
||||
|
||||
1. `POST /upload/image` 或 `POST /upload/file`,multipart 字段为 `file`、`cid=0`,返回服务器 `uri/url`:`D:\web\zyt\admin\src\api\file.ts:7-33`。
|
||||
2. 素材选择器只把上传成功的服务器地址交给业务组件:`D:\web\zyt\admin\src\components\material\picker.vue:260-283`。
|
||||
3. 再调用 `POST /doctor.appointment/addDoctorNote`,payload 为 `{diagnosis_id, content?, tongue_images?: string[], report_files?: string[]}`:`D:\web\zyt\admin\src\views\patient\reception\components\NoteTimeline.vue:195-227`。
|
||||
|
||||
Python 只是用文件选择器保存 `Path(raw_path)`,界面虽写“待上传”,但没有任何上传请求:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:1291-1346`。保存时本机路径被原样放进 `tongue_images/report_files`:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:1358-1393`;Remote repository 直接 JSON 透传:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:709-730`。`ApiClient` 只有 JSON 请求并固定 `Content-Type: application/json`,没有 multipart:`D:\web\zyt\app\src\doctor_workstation\services\api_client.py:108-180,211-220`。
|
||||
|
||||
影响:服务端会收到 `C:\Users\...\report.pdf` 一类不可共享路径,其他终端无法访问,同时泄漏本机目录。已有 notes 读取与删除端点正确,但不能补救创建时的坏数据:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:732-759`。
|
||||
|
||||
可执行修复:
|
||||
|
||||
1. 在 `ApiClient` 增加独立 multipart 方法,不沿用 JSON `Content-Type`,由 httpx 生成 boundary。
|
||||
2. 在 repository 增加 `upload_material(path, material_type, cid=0)`;图片走 `/upload/image`,报告走 `/upload/file`。
|
||||
3. 所有素材上传成功后才调用 `addDoctorNote`;最终 DTO 必须拒绝盘符路径、UNC、`file://`。
|
||||
4. 部分失败不提交本地路径,逐文件提示;必要时清理已上传但未关联素材。
|
||||
5. 将 `D:\web\zyt\app\tests\test_reception_parity_ui.py:263-306` 改成 multipart + 最终 JSON 双阶段测试,并断言最终 JSON 只含服务器地址。
|
||||
|
||||
### P0-2 当前 appointment 处方 miss/异常后回退 diagnosis 旧处方 — PARTIAL
|
||||
|
||||
管理端从问诊行传 `diagnosis_id=row.id`、`appointment_id=row.appointment_id`:`D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:1685-1694`。处方组件只以 `GET /tcm.prescription/getByAppointment {appointment_id}` 判断当前挂号是否已有处方;空结果进入当前挂号的新建流程,再用 `GET /tcm.diagnosis/detail {id}` 生成病历快照:`D:\web\zyt\admin\src\components\tcm-prescription\index.vue:1829-1907`。保存明确发送 `diagnosis_id`、`appointment_id`、`case_record`:`D:\web\zyt\admin\src\components\tcm-prescription\index.vue:2219-2268`。
|
||||
|
||||
Python 已有正确的两个 repository 端点:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:994-1016`。但当前 appointment 查询为空,甚至 401/403/网络异常时,都会继续按 diagnosis 查询并选择“最新一张”:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:1179-1216`。选中的 fallback 随后可被展示或作废:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:1250-1283,1350-1383`。Demo 的 `get_prescription_by_appointment` 也仅按 diagnosis 返回首张,忽略 appointment:`D:\web\zyt\app\src\doctor_workstation\services\mock_repository.py:752-760`。
|
||||
|
||||
同时,新建只从列表行拼少量患者字段:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:1300-1347`;编辑器 payload 没有完整 round-trip `appointment_id/case_record`:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1304-1370`;模型也没有正式保留这两个字段:`D:\web\zyt\app\src\doctor_workstation\core\models.py:580-646,728-771`。
|
||||
|
||||
影响:同一 diagnosis 的 appointment A 有处方、B 无处方时,B 可误看、误编辑或误作废 A;网络/权限错误也被错误解释为“允许回退”。
|
||||
|
||||
可执行修复:
|
||||
|
||||
1. `appointment_id > 0` 时只允许 `getByAppointment` 决定当前处方;空结果新建 B,异常 fail-closed 并提示,绝不按 diagnosis 自动回退。
|
||||
2. diagnosis 级历史只能做独立只读历史列表,不能成为查看/编辑/作废目标选择器。
|
||||
3. 新建前调用 `get_diagnosis_detail`,把不可变 `case_record` 与 `diagnosis_id + appointment_id` 一起提交。
|
||||
4. 给 `Prescription` 增加并完整序列化 `appointment_id`、`case_record`;Demo 按 appointment 精确匹配。
|
||||
5. 反向改写 `D:\web\zyt\app\tests\test_consultations_parity_ui.py:241-264`:A 有处方、B 无处方时 B 必须新建 B;查询异常不得回退或作废 A。
|
||||
|
||||
### P0-3 “我的患者”预约使用了错误的 `patient_id` 语义 — PARTIAL
|
||||
|
||||
管理端在打开预约框时刻意把 `patient_id` 覆盖成 `diagnosis_id || id`:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:513-518`。预约组件把该值设为 `patientInfo.id`:`D:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:392-402`,并提交到 `POST /firstvisit.myPatient/createAppointment`:`D:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:701-719`。
|
||||
|
||||
Python payload 虽计算了 `diagnosis_id`,却让 `patient_id` 优先取 `source_patient_id`:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:266-279`。Remote 不作转换,直接把整个 body 发给同一端点:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:1318-1325`。管理端仅在视频 ticket 中使用 `source_patient_id`,同时保留 diagnosis ID,证明两者不是同一语义:`D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:642-661`。
|
||||
|
||||
影响:只要列表同时返回 `diagnosis_id` 与 `source_patient_id`,Python 就与实际端点 DTO 不同,可能绑定错误患者上下文或被后端拒绝。
|
||||
|
||||
可执行修复:
|
||||
|
||||
1. `firstvisit.myPatient/createAppointment` 的 `patient_id` 固定使用当前 `diagnosis_id`,不要用 `source_patient_id`。
|
||||
2. repository 为该端点定义显式 DTO,避免“完整字典透传”隐藏字段语义错误。
|
||||
3. 增加 `diagnosis_id != source_patient_id` 的合同测试,断言 body 的 `patient_id == diagnosis_id`。
|
||||
4. 视频 ticket 继续使用独立的真实 patient ID,不把本修复扩散到视频 DTO。
|
||||
|
||||
## 5. P1 高优先级缺口
|
||||
|
||||
### P1-1 诊单详情/编辑只是字段子集,且未落实隐私权限 — PARTIAL
|
||||
|
||||
管理端根据 `tcm.diagnosis/phonePlain` 决定明文手机号,并在无权时先对手机号、身份证脱敏:`D:\web\zyt\admin\src\views\tcm\diagnosis\edit.vue:822-856,1263-1274`。Python `DiagnosisDialog` 构造函数不接 permissions:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\diagnosis.py:53-65`,直接渲染电话等字段:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\diagnosis.py:317-352`。编辑仅有 9 个文本字段:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\diagnosis.py:150-174`,保存也只回传这组子集:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\diagnosis.py:446-464`。
|
||||
|
||||
修复:把 permission set 传入对话框;明文手机严格要求 `tcm.diagnosis/phonePlain`;身份证同样 fail-closed;按管理端 DTO 补齐患者基本信息、生命体征、病史/四诊/诊断字段及电话/身份证唯一性检查;对后端声明不可编辑的基础字段锁定。
|
||||
|
||||
### P1-2 患者预约表单缺排班、号源和关键 DTO 字段 — PARTIAL
|
||||
|
||||
管理端加载医生列表、未来 7 天排班、可用时间段,并检查当天重复预约:`D:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:423-447,495-625`;提交要求 `appointment_type`、`channel_source`、`channel_source_detail`:`D:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:683-719`。Python 只提供自由输入医生 ID、任意日期/时间/period/remark,payload 缺上述字段:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:220-279`。
|
||||
|
||||
修复:用后端医生/排班/可用时间段数据驱动选择器;限制日期和可预约 slot;增加 appointment type、channel source/detail;提交前与服务端均做重复预约校验。与 P0-3 一起建立 exact DTO 测试。
|
||||
|
||||
### P1-3 诊单上下文订单缺失,支付/退款字段缩水 — PARTIAL
|
||||
|
||||
管理端诊单只读页按 `tcm.diagnosis/patientOrders` 显示订单,并请求 `GET /tcm.prescriptionOrder/lists {context_diagnosis_id, patient_id, scene:'diagnosis_edit'}`:`D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:74-88`、`D:\web\zyt\admin\src\views\tcm\diagnosis\components\PatientOrderList.vue:118-179`。Python `DiagnosisDialog` 只有病历、备注、挂号、指派四个 tab:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\diagnosis.py:53-99,381-386`,虽然 repository 已有订单列表端点:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:1018-1029`。
|
||||
|
||||
此外,Python 补支付单把 `pay_remark`、`completion_request=0`、`pay_create_type=fubei` 写死:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:2048-2076`,而管理端由用户明确选择:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderActionHost.vue:573-587`。Python 强制填写退款金额:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:2108-2131`;管理端允许省略:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderActionHost.vue:610-621`。
|
||||
|
||||
修复:给 `DiagnosisDialog` 传 permissions,按 canonical `tcm.diagnosis/patientOrders` 增加只读订单 tab并使用 exact context DTO;补支付单暴露三项业务字段;退款增加“不指定金额”。
|
||||
|
||||
### P1-4 处方建单可在门槛未加载时提交,并可混入旧诊单支付单 — PARTIAL
|
||||
|
||||
Python 把 `deposit_min_amount` 初始化为 0,创建按钮立即可用:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1793-1827`。`paidPayOrders` 只在初始化时异步加载,没有 generation/diagnosis ID 回验:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1928-1979`;diagnosis ID 又可编辑。最终 payload 可把新 diagnosis ID 与旧 `pay_order_ids/deposit_min_amount` 组合:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1989-2054`。管理端在诊单变化时清空并重载支付单:`D:\web\zyt\admin\src\views\consumer\prescription\index.vue:2307-2409`。
|
||||
|
||||
修复:来自处方的 diagnosis ID 设为只读,或用 `/tcm.diagnosis/searchPatient` 受控选择器;变化时立即清空支付单并禁提交;捕获 `(generation, diagnosis_id)`,仅应用同上下文响应;加载成功后才启用保存;服务端再校验处方、diagnosis、每个支付单的归属和定金门槛。
|
||||
|
||||
### P1-5 患者页写操作 latest-wins 会吞掉已执行 mutation 的回调 — PARTIAL
|
||||
|
||||
`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1696-1728` 为所有写操作共用 `_action_generation`,但未串行化或禁用其他动作。动作 B 启动后,动作 A 即使已在服务端成功或失败,其回调也会被丢弃,可能不刷新、不提示,界面与服务端不一致。
|
||||
|
||||
修复:按 operation/entity 维护 pending token,或串行化并禁用动作;任何成功 mutation 都必须触发最终一致性 refresh;generation 只能决定消息落点,不能取消写后 reconcile。补“两个不同订单动作乱序完成”的测试。
|
||||
|
||||
### P1-6 QRunnable 工作线程读取 Qt 控件 — PARTIAL
|
||||
|
||||
`run_async` 的函数实际在工作线程执行:`D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:257-319`。以下 worker lambda 仍调用 `.text()`、`.currentData()` 或 QWidget 属性:
|
||||
|
||||
- 接诊状态:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:502-525`
|
||||
- 患者列表:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:825-845`
|
||||
- 患者订单:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1114-1129`
|
||||
- 处方库:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:220-238`
|
||||
- 模板导入:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:736-755`
|
||||
|
||||
修复:所有控件值在 GUI 线程先快照成不可变 query/page DTO,worker 只执行 `repository.method(**query)`。这样既满足 Qt 线程约束,也保证 generation 对应的条件不会在执行中变化。
|
||||
|
||||
### P1-7 通用 permission helper 会把非 canonical 点号别名当成授权 — PARTIAL
|
||||
|
||||
canonical 权限常量采用 slash 形式,例如 `wcf.prescription/add` 与 `cf.prescription/edit` 对应代码定义:`D:\web\zyt\app\src\doctor_workstation\services\repository.py:33-52`。但通用 `has_permission` 会生成 slash/dot 互换别名:`D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:135-180`,因此仅持有 `cf.prescription.edit` 也可能通过 `cf.prescription/edit` 门槛。处方库和已开处方使用了该 helper:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:101-161`、`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:282-391,809-815`。shell、患者、问诊已采用 exact/wildcard 语义:`D:\web\zyt\app\src\doctor_workstation\ui\shell.py:109-142`、`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:110-143`。
|
||||
|
||||
修复:全项目统一 exact + `*`/`prefix/*` helper;删除点/斜杠互换;增加“只有 alias grant 时按钮必须隐藏且 handler 必须拒绝”的测试。服务端权限仍是最终防线。
|
||||
|
||||
### P1-8 原生医生直呼错误复用小程序二维码权限 — PARTIAL
|
||||
|
||||
医生直呼状态已经与管理端一致:管理端仅在 `has_appointment && appointment_status===1` 时启用:`D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:1778-1780`;Python 同样分离并复核 appointment/patient/diagnosis ID:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:142-183,1094-1122,1413-1427`。接诊台的今日状态 1/4 和 ID 也正确:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:1542-1564`、`D:\web\zyt\app\src\doctor_workstation\services\repository.py:640-692`。
|
||||
|
||||
偏差是问诊原生直呼受 `tcm.diagnosis/videoQr` 控制:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:625-630,1413-1415`;管理端该权限只保护“小程序视频二维码”:`D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:390-391`。这还造成接诊台和问诊页对同一原生直呼的授权不一致。
|
||||
|
||||
修复:与后端确认独立原生直呼 permission(例如 `tcm.diagnosis/startCall`)并让两入口统一;若没有独立 grant,则不复用 `videoQr`,由 call endpoint 的后端授权兜底。ticket/start/bind/end 的服务端必须再次验证 appointment 当前状态及 diagnosis/patient 归属。医助 `watchCall` 继续排除。
|
||||
|
||||
### P1-9 接诊队列固定前 50 条,无继续加载 — PARTIAL
|
||||
|
||||
管理端每页 15 条并持续加载,同时维护其他队列计数:`D:\web\zyt\admin\src\views\patient\reception\index.vue:213,296-379`。Python 每次只请求 `page_no=1,page_size=50`:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:502-525`,没有分页或 infinite scroll。忙时第 51 位及以后患者不可达。
|
||||
|
||||
修复:按管理端实现 `page_no/page_size=15` 累加加载,并以 total 判定是否继续;切 tab/搜索重置页码和 items;用 generation + pending refresh 保证旧页不污染新筛选。
|
||||
|
||||
### P1-10 已开处方可打开诊单详情,但没有诊单权限门槛 — PARTIAL
|
||||
|
||||
处方页无条件给详情对话框启用诊单入口:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:633-664`,处方详情对话框据此显示按钮:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1641-1669`。这条数据入口未检查 `tcm.diagnosis/readonlyDetail` 或产品定义的等价授权。
|
||||
|
||||
修复:显式传入 permission set;按钮可见与 handler 双重校验 canonical 诊单只读权限;无权限时不发 `get_diagnosis_detail`。补只有 `cf.prescription/read` 而无诊单权限的拒绝测试。
|
||||
|
||||
## 6. Endpoint、DTO 与动作复核
|
||||
|
||||
| 业务 | 管理端/服务端合同 | Python | 判定 |
|
||||
|---|---|---|---|
|
||||
| 接诊队列 | `GET doctor.appointment/lists`;`status,start_date,end_date,page_no,page_size,patient_name` | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:640-692`;今日和 1/4 强约束 | DTO **EXACT**;分页 **PARTIAL** |
|
||||
| 接诊详情/完成 | `detail`、`doctorNotify`、`completeAppointment` | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:694-765` | **EXACT** |
|
||||
| 医生备注 | `doctorNotes/addDoctorNote/deleteDoctorNoteImage`;素材 URL 先上传 | 读/增/删端点存在,但缺 `/upload/image|file` | **MISSING/P0** |
|
||||
| 我的患者列表 | `GET firstvisit.myPatient/lists`,keyword/status/date/page | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1088-1102` | **EXACT** |
|
||||
| 患者订单/进度 | `orders`、`faceToFaceProgress` + scope/summary | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1104-1128` | **EXACT** |
|
||||
| 患者订单动作 | detail/edit、两类审核/撤销、支付、物流、完成、退款、撤回、上传药房 | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1130-1287`;状态矩阵 `D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1211-1289` | 主链 **EXACT**;支付/退款表单 **PARTIAL** |
|
||||
| 患者预约 | `POST firstvisit.myPatient/createAppointment`;`patient_id` 实为当前 diagnosis ID,另含医生/日期/时间/type/channel | Remote `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1318-1325` 透传缩水且错误 DTO | **PARTIAL/P0** |
|
||||
| 问诊列表/诊单 CRUD | `tcm.diagnosis/lists|detail|add|edit|delete` | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1339-1418` | endpoint **EXACT**;UI 字段 **PARTIAL** |
|
||||
| appointment 处方上下文 | `getByAppointment`;空则当前 appointment 新建;保存 diagnosis/appointment/case_record | 端点存在,但错误 diagnosis fallback | **PARTIAL/P0** |
|
||||
| 处方库 | `tcm.prescriptionLibrary/lists|detail|add|edit|delete`;`doctor.medicine/lists` | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:766-864` | **EXACT** |
|
||||
| 已开处方 | `tcm.prescription/lists|detail|add|edit|delete|patchPatient|audit|void` | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:866-1016` | endpoint/状态动作 **EXACT**;上下文/权限 **PARTIAL** |
|
||||
| 处方订单 | lists/detail/create/paidPayOrders | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1018-1080` | endpoint **EXACT**;建单异步 **PARTIAL** |
|
||||
| 视频 | `getCallSignature/startCall/bindCallRoom/endCall`,IDs 分离 | `D:\web\zyt\app\src\doctor_workstation\services\repository.py:1575-1615` | endpoint/eligibility **EXACT**;permission **PARTIAL** |
|
||||
|
||||
## 7. 筛选、字段、分页和行状态门槛
|
||||
|
||||
### 7.1 接诊台
|
||||
|
||||
- **EXACT**:今日 `start_date=end_date`、等待/过号状态 1/4、姓名筛选、选中患者详情与完成前重取详情。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:488-558,591-750,1474-1529`。
|
||||
- **PARTIAL**:页面固定前 50 条;备注附件只能显示名称/删除,无管理端的图片预览/报告打开。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:1091-1142`;管理端:`D:\web\zyt\admin\src\views\patient\reception\components\NoteTimeline.vue:52-101`。
|
||||
|
||||
### 7.2 我的患者
|
||||
|
||||
- **EXACT**:患者列表 filters、page size 15、summary/scope;订单 keyword、处方审核、支付审核、履约状态、日期与分页;订单列和 action matrix。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:547-688,825-874,913-1289,1292-1595`。管理端动作基准:`D:\web\zyt\admin\src\views\first_visit\my_patients\components\order-actions.ts:32-124`。
|
||||
- **PARTIAL**:预约和诊单详情/编辑字段;诊单上下文订单;支付/退款输入;写操作回调竞态。
|
||||
|
||||
### 7.3 我的问诊
|
||||
|
||||
- **EXACT**:核心列表/详情 CRUD endpoints、canonical `tcm.diagnosis/add|edit|delete|readonlyDetail|kaifang`、视频 eligibility 与 ID。
|
||||
- **PARTIAL**:Python filters/columns 位于 `D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:649-660,875-904`;管理端基准在 `D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:191-341,775-798,878-887`。Python 发出管理端不存在的 `consultation_type`,缺 doctor-relevant 的未接诊天数排序;预约、补身份证等管理端行入口没有在本页呈现。指派/医助专属动作不因本页缺失计发布阻断,因为它们不是医生桌面主链或已在患者页提供。
|
||||
- **P0**:处方上下文不得按 diagnosis 自动选最近处方。
|
||||
|
||||
### 7.4 我的处方库
|
||||
|
||||
- **EXACT**:处方名/剂型/公开范围筛选、15 条分页、处方名/剂型/功效/归属/创建人/时间字段、只读/新增/编辑/删除、远程药材选择、剂量校验、owner/root/role(0/3) 行条件。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:106-193,267-376`;管理端:`D:\web\zyt\admin\src\views\consumer\prescription\list.vue:1-111,260-301,333-390`。
|
||||
- **EXACT**:`disable_edit` 是导入后药材行的保存锁,不是“处方库模板禁止编辑”的行权限。Python:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:554-585`。
|
||||
- **PARTIAL**:canonical permission alias 与异步控件读取。
|
||||
|
||||
### 7.5 已开处方
|
||||
|
||||
- **EXACT**:SN、患者、审核状态、来源、日期、医生筛选;15 条分页;详情、CRUD、患者修正、审核通过/驳回备注、作废、订单创建/列表、A4 打印、PDF。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:297-847`、`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:955-1715`。
|
||||
- **EXACT**:编辑/删除/审核/患者修正/建单的行状态门槛与管理端 action matrix 等价。Python:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:569-577,799-841`;管理端:`D:\web\zyt\admin\src\views\consumer\prescription\index.vue:214-263,3154-3157`。
|
||||
- **PARTIAL**:编辑器没有管理端保存前的重复药材名拒绝。Python校验:`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1397-1417`;管理端:`D:\web\zyt\admin\src\components\tcm-prescription\index.vue:2228-2231`。处方建单及诊单详情权限另见 P1。
|
||||
|
||||
## 8. Repository 调用存在性审计
|
||||
|
||||
结论:**EXACT;没有发现 UI 调用不存在/错误命名的 canonical repository 方法。**
|
||||
|
||||
- 接诊别名最终映射到 `list_appointments/get_reception`;notes、tracking、complete 均存在。
|
||||
- 患者列表、订单、进度、详情、所有订单动作、预约/取消、指派/身份证均在 Protocol/Remote/Demo 存在。
|
||||
- 问诊列表、字典、医助、诊单 CRUD、appointment/diagnosis 处方查询、开方/作废均存在。
|
||||
- 处方库 CRUD、药材检索均存在。
|
||||
- 已开处方 CRUD、患者修正、审核、作废、诊单详情、订单 CRUD/paid orders 均存在。
|
||||
|
||||
定义集中于 `D:\web\zyt\app\src\doctor_workstation\services\repository.py:56-425`,Remote 实现在 `D:\web\zyt\app\src\doctor_workstation\services\repository.py:640-1615`,Demo 实现在 `D:\web\zyt\app\src\doctor_workstation\services\mock_repository.py:97-1515`。`D:\web\zyt\app\src\doctor_workstation\services\demo_repository.py:1-5` 只是重导出 Demo 类。
|
||||
|
||||
兼容调用器 `D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:183-248` 会静默删除未知 kwargs。当前没有因此丢掉必需字段,但这会掩盖未来拼写错误,判 **P2**:把允许删除的冗余键改为显式 adapter/allowlist,测试环境对其他未知键报错。
|
||||
|
||||
## 9. 动态菜单与 canonical permissions
|
||||
|
||||
### 动态菜单 — EXACT
|
||||
|
||||
- 支持页面注册表:`D:\web\zyt\app\src\doctor_workstation\ui\shell.py:41-106`。
|
||||
- 服务端节点 flatten、显示/禁用、排序:`D:\web\zyt\app\src\doctor_workstation\ui\shell.py:145-197`。
|
||||
- component/path 匹配和受支持页面解析:`D:\web\zyt\app\src\doctor_workstation\ui\shell.py:200-249`。
|
||||
- 非 Demo 会话取 menu 并解析:`D:\web\zyt\app\src\doctor_workstation\app.py:413-443`、`D:\web\zyt\app\src\doctor_workstation\ui\shell.py:264-301,436-460`。
|
||||
|
||||
仅渲染本地已经实现的五个页面是本轮明确范围,不把其余 admin 路由判 MISSING。不存在“拿静态菜单覆盖后端菜单”的旧问题。
|
||||
|
||||
### Permissions — PARTIAL
|
||||
|
||||
患者、问诊、shell 使用 exact/wildcard;处方库和已开处方仍经通用 alias helper。所有按钮可见性还必须在 action handler 再检查同一 canonical 权限,不能只靠隐藏按钮。优先修复 P1-7 与 P1-10。
|
||||
|
||||
## 10. 异步与竞态审计
|
||||
|
||||
已正确做 generation/目标校验的主链包括:接诊队列与详情、患者列表/助手/订单详情、问诊列表/计数/字典选项/处方上下文、诊单对话框加载保存、药材搜索、模板列表、订单列表。
|
||||
|
||||
仍需处理:
|
||||
|
||||
| 优先级 | 问题 | 证据 | 修复 |
|
||||
|---|---|---|---|
|
||||
| P1 | 患者 mutations 共用 latest-wins generation | `D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1696-1728` | 串行化或 per-operation token;所有成功写入都 reconcile |
|
||||
| P1 | 处方建单支付单/定金无上下文 generation | `D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1928-2054` | `(generation,diagnosis_id)` 回验;加载期间禁提交 |
|
||||
| P1 | worker 直接读 QWidget | `D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:257-319` 及 P1-6 列表 | GUI 线程快照不可变 query |
|
||||
| P1 | 切换问诊行会 invalidate generation 但可能保留 busy | `D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:1094-1105,1234-1292` | invalidation 同时结束旧 busy,或 active token/cancel;确保新行按钮可恢复 |
|
||||
| P2 | 处方库/处方列表 loading 时直接丢 refresh | `D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:220-238`;`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:499-529` | pending refresh 或并发请求 + generation;快照 filters |
|
||||
| P2 | 处方诊单详情、订单详情缺目标 ID generation | `D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:653-664`;`D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:2178-2196` | `_detail_generation` + target ID;加载期间禁重复点击 |
|
||||
|
||||
## 11. 低优先级与 Demo 差异
|
||||
|
||||
- **P2 / Demo filters**:UI 发 `diagnosis_confirmed` 等筛选:`D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:875-904`;Demo 只处理少数键且读成 `confirmed`:`D:\web\zyt\app\src\doctor_workstation\services\mock_repository.py:1243-1268`。Demo 字典除 `server_order` 外也为空:`D:\web\zyt\app\src\doctor_workstation\services\mock_repository.py:851-860`。补齐 UI 暴露的筛选和四组演示字典。
|
||||
- **P2 / raw detail**:已开处方的订单列表详情是通用字段展示,管理端跳向完整订单路由。若桌面产品要求在本应用闭环,需实现 typed detail;否则应明确“只读摘要”范围。
|
||||
- **P2 / refresh**:处方库与已开处方在请求过程中改筛选/翻页可能显示旧条件结果,见异步表。
|
||||
|
||||
## 12. 既有 parity 文档复核
|
||||
|
||||
### `parity_reception_consultations.md`
|
||||
|
||||
`D:\web\zyt\app\research\parity_reception_consultations.md:14-16,81,148-154` 中“未限定今日/队列详情竞态/视频状态错误/无附件 UI/无开方”等描述大多已经关闭。当前真实结论是:今日和状态 **EXACT**、详情竞态主要链路 **EXACT**、视频 eligibility **EXACT**、开方入口已存在;附件 UI 已存在但上传协议 **MISSING/P0**。
|
||||
|
||||
### `parity_patients_permissions.md`
|
||||
|
||||
`D:\web\zyt\app\research\parity_patients_permissions.md:13-14,124-127,200-216` 中“订单工作区和动态菜单完全缺失”已关闭。当前三工作区、订单状态矩阵、summary/scope 和动态菜单均存在;仍开放的是预约 DTO/P0、诊单详情隐私、诊单上下文订单和写操作竞态。
|
||||
|
||||
### `parity_prescriptions.md`
|
||||
|
||||
`D:\web\zyt\app\research\parity_prescriptions.md:255-285,337-343` 把已开处方描述成只读,已过期。当前 `D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:253-393,588-847` 已有 CRUD、审核、患者修正、订单、打印/PDF。处方库旧文档的所有权 fail-open 也已修成缺 ID 默认拒绝:`D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:267-279`。仍开放的是 P0 处方上下文、P1 order async/权限/重复药材等。
|
||||
|
||||
## 13. 建议修复顺序与验收门槛
|
||||
|
||||
1. **先修 P0-1 附件上传**:没有 multipart 和服务器 URL 的实现不得开放附件提交。
|
||||
2. **再修 P0-2 处方上下文**:appointment authoritative、异常 fail-closed、完整 `case_record`;删除 diagnosis 自动 fallback。
|
||||
3. **修 P0-3 患者预约 ID**:exact DTO + diagnosis/source patient 分离测试。
|
||||
4. **随后修权限和订单上下文**:P1-1、P1-3、P1-4、P1-7、P1-8、P1-10。
|
||||
5. **最后收口异步/分页**:per-operation mutation、Qt query snapshot、接诊分页、pending refresh。
|
||||
|
||||
发布验收至少应新增以下反例:
|
||||
|
||||
- 本机盘符/UNC/`file://` 永远不能进入 `addDoctorNote` JSON。
|
||||
- 同 diagnosis 两个 appointments 时,B 的 miss/异常永远不能展示或作废 A 的处方。
|
||||
- `diagnosis_id != source_patient_id` 时,预约 body 的 `patient_id` 必须等于 diagnosis ID;视频 body 保持真实 patient ID。
|
||||
- 只有点号 alias permission 时,slash canonical action 必须拒绝。
|
||||
- 两个患者订单 mutation 乱序完成后,UI 必须最终与服务端一致。
|
||||
- 诊单切换前返回的 paid-order 响应不能进入新诊单 payload,门槛未加载时不能提交。
|
||||
- 超过 50 位的今日接诊队列仍可继续加载。
|
||||
|
||||
完成以上 P0 并替换两条错误预期测试后,才可把整体结论从 **PARTIAL** 提升到可发布候选。
|
||||
Reference in New Issue
Block a user