35 KiB
医生工作站最终 parity 审计
审计日期:2026-08-10
审计方式:只读源码复核 + 纯本地测试;未修改业务源码。
结论基准:D:\web\zyt\admin\src\views 的实际实现优先于既有 research/parity_*.md。
1. 结论
当前医生桌面整体判定为 PARTIAL,不建议在修复 P0 前作为后台同型版本发布。
- P0:3 项
- 接诊附件没有上传步骤,本机绝对路径被作为附件 URL 发给服务端。
- 问诊开方在当前 appointment 无处方或查询失败时按 diagnosis 回退,可显示、编辑或作废另一挂号的处方。
- “我的患者”预约把
source_patient_id优先写入patient_id,而管理端该端点明确要求诊单 ID,可能预约到错误上下文或被后端拒绝。
- Repository 方法存在性:EXACT。当前五页及相关对话框实际调用的 canonical 方法均同时存在于 Protocol、Remote、Demo,未发现不存在或拼错的方法名;兼容别名均能解析到有效方法。
- 动态菜单:EXACT(限定五个受支持页面)。非 Demo 会话使用服务端菜单,遵守显示、禁用、排序、路由及 canonical permission;未把未实现的后台运营页面伪装成本地入口。
- 医生活跃视频 eligibility:EXACT;权限语义:PARTIAL。状态与三个业务 ID 已对齐,但问诊页错误复用了小程序二维码权限
tcm.diagnosis/videoQr。 - 既有三份 parity 文档明显早于本轮实现,不能直接当作当前验收结果;本报告已重新逐项分类。
本次执行:
.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.pyD:\web\zyt\app\src\doctor_workstation\ui\pages\patients.pyD:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.pyD:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.pyD:\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
管理端合同是严格的两阶段流程:
POST /upload/image或POST /upload/file,multipart 字段为file、cid=0,返回服务器uri/url:D:\web\zyt\admin\src\api\file.ts:7-33。- 素材选择器只把上传成功的服务器地址交给业务组件:
D:\web\zyt\admin\src\components\material\picker.vue:260-283。 - 再调用
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。
可执行修复:
- 在
ApiClient增加独立 multipart 方法,不沿用 JSONContent-Type,由 httpx 生成 boundary。 - 在 repository 增加
upload_material(path, material_type, cid=0);图片走/upload/image,报告走/upload/file。 - 所有素材上传成功后才调用
addDoctorNote;最终 DTO 必须拒绝盘符路径、UNC、file://。 - 部分失败不提交本地路径,逐文件提示;必要时清理已上传但未关联素材。
- 将
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;网络/权限错误也被错误解释为“允许回退”。
可执行修复:
appointment_id > 0时只允许getByAppointment决定当前处方;空结果新建 B,异常 fail-closed 并提示,绝不按 diagnosis 自动回退。- diagnosis 级历史只能做独立只读历史列表,不能成为查看/编辑/作废目标选择器。
- 新建前调用
get_diagnosis_detail,把不可变case_record与diagnosis_id + appointment_id一起提交。 - 给
Prescription增加并完整序列化appointment_id、case_record;Demo 按 appointment 精确匹配。 - 反向改写
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 不同,可能绑定错误患者上下文或被后端拒绝。
可执行修复:
firstvisit.myPatient/createAppointment的patient_id固定使用当前diagnosis_id,不要用source_patient_id。- repository 为该端点定义显式 DTO,避免“完整字典透传”隐藏字段语义错误。
- 增加
diagnosis_id != source_patient_id的合同测试,断言 body 的patient_id == diagnosis_id。 - 视频 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` |
| 我的患者列表 | 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 |
| appointment 处方上下文 | getByAppointment;空则当前 appointment 新建;保存 diagnosis/appointment/case_record |
端点存在,但错误 diagnosis fallback | PARTIAL/P0 |
| 处方库 | `tcm.prescriptionLibrary/lists | detail | add |
| 已开处方 | `tcm.prescription/lists | detail | add |
| 处方订单 | 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. 建议修复顺序与验收门槛
- 先修 P0-1 附件上传:没有 multipart 和服务器 URL 的实现不得开放附件提交。
- 再修 P0-2 处方上下文:appointment authoritative、异常 fail-closed、完整
case_record;删除 diagnosis 自动 fallback。 - 修 P0-3 患者预约 ID:exact DTO + diagnosis/source patient 分离测试。
- 随后修权限和订单上下文:P1-1、P1-3、P1-4、P1-7、P1-8、P1-10。
- 最后收口异步/分页:per-operation mutation、Qt query snapshot、接诊分页、pending refresh。
发布验收至少应新增以下反例:
- 本机盘符/UNC/
file://永远不能进入addDoctorNoteJSON。 - 同 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 提升到可发布候选。