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

35 KiB
Raw Blame History

医生工作站最终 parity 审计

审计日期:2026-08-10
审计方式:只读源码复核 + 纯本地测试;未修改业务源码。
结论基准:D:\web\zyt\admin\src\views 的实际实现优先于既有 research/parity_*.md

1. 结论

当前医生桌面整体判定为 PARTIAL,不建议在修复 P0 前作为后台同型版本发布。

  • P03 项
    1. 接诊附件没有上传步骤,本机绝对路径被作为附件 URL 发给服务端。
    2. 问诊开方在当前 appointment 无处方或查询失败时按 diagnosis 回退,可显示、编辑或作废另一挂号的处方。
    3. “我的患者”预约把 source_patient_id 优先写入 patient_id,而管理端该端点明确要求诊单 ID,可能预约到错误上下文或被后端拒绝。
  • Repository 方法存在性:EXACT。当前五页及相关对话框实际调用的 canonical 方法均同时存在于 Protocol、Remote、Demo,未发现不存在或拼错的方法名;兼容别名均能解析到有效方法。
  • 动态菜单:EXACT(限定五个受支持页面)。非 Demo 会话使用服务端菜单,遵守显示、禁用、排序、路由及 canonical permission;未把未实现的后台运营页面伪装成本地入口。
  • 医生活跃视频 eligibilityEXACT;权限语义:PARTIAL。状态与三个业务 ID 已对齐,但问诊页错误复用了小程序二维码权限 tcm.diagnosis/videoQr
  • 既有三份 parity 文档明显早于本轮实现,不能直接当作当前验收结果;本报告已重新逐项分类。

本次执行:

.venv\Scripts\python.exe -m pytest
110 passed in 1.33s

全绿不能覆盖本报告的 P0D:\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.pyui/dialogs/prescription.pyui/widgets.pyui/shell.pycore/models.pyservices/*
  • 管理端唯一事实基准:D:\web\zyt\admin\src\views,必要时追到其直接使用的 src/componentssrc/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 调用全部存在且签名兼容 上传素材方法 MISSINGDemo 问诊筛选语义不完整
动态菜单 EXACT 使用服务端 menu;显示/禁用/排序/路由/权限;只注册受支持页面 无发布阻断偏差
视频端点与 eligibility PARTIAL ticket/start/bind/end 方法存在;问诊 has_appointment && status==1;接诊今日状态域;ID 分离 原生直呼错误复用 videoQr 权限;服务端仍须最终复核当前状态/归属

4. P0 发布阻断

P0-1 接诊附件不是上传,而是泄漏并保存本机路径 — MISSING

管理端合同是严格的两阶段流程:

  1. POST /upload/imagePOST /upload/filemultipart 字段为 filecid=0,返回服务器 uri/urlD:\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/addDoctorNotepayload 为 {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_filesD:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:1358-1393Remote repository 直接 JSON 透传:D:\web\zyt\app\src\doctor_workstation\services\repository.py:709-730ApiClient 只有 JSON 请求并固定 Content-Type: application/json,没有 multipartD:\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.idappointment_id=row.appointment_idD:\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_idappointment_idcase_recordD:\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 返回首张,忽略 appointmentD:\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_recordD:\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_recorddiagnosis_id + appointment_id 一起提交。
  4. Prescription 增加并完整序列化 appointment_idcase_recordDemo 按 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 || idD:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:513-518。预约组件把该值设为 patientInfo.idD:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:392-402,并提交到 POST /firstvisit.myPatient/createAppointmentD:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:701-719

Python payload 虽计算了 diagnosis_id,却让 patient_id 优先取 source_patient_idD:\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_idsource_patient_idPython 就与实际端点 DTO 不同,可能绑定错误患者上下文或被后端拒绝。

可执行修复:

  1. firstvisit.myPatient/createAppointmentpatient_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 构造函数不接 permissionsD:\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_typechannel_sourcechannel_source_detailD:\web\zyt\admin\src\views\tcm\diagnosis\appointment.vue:683-719。Python 只提供自由输入医生 ID、任意日期/时间/period/remarkpayload 缺上述字段: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-88D:\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_remarkcompletion_request=0pay_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-1827paidPayOrders 只在初始化时异步加载,没有 generation/diagnosis ID 回验:D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:1928-1979diagnosis 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 DTOworker 只执行 repository.method(**query)。这样既满足 Qt 线程约束,也保证 generation 对应的条件不会在执行中变化。

P1-7 通用 permission helper 会把非 canonical 点号别名当成授权 — PARTIAL

canonical 权限常量采用 slash 形式,例如 wcf.prescription/addcf.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-161D:\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-142D:\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-1780Python 同样分离并复核 appointment/patient/diagnosis IDD:\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-1564D:\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=50D:\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/listsstatus,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
接诊详情/完成 detaildoctorNotifycompleteAppointment D:\web\zyt\app\src\doctor_workstation\services\repository.py:694-765 EXACT
医生备注 doctorNotes/addDoctorNote/deleteDoctorNoteImage;素材 URL 先上传 读/增/删端点存在,但缺 `/upload/image file`
我的患者列表 GET firstvisit.myPatient/listskeyword/status/date/page D:\web\zyt\app\src\doctor_workstation\services\repository.py:1088-1102 EXACT
患者订单/进度 ordersfaceToFaceProgress + 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/createAppointmentpatient_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/endCallIDs 分离 D:\web\zyt\app\src\doctor_workstation\services\repository.py:1575-1615 endpoint/eligibility EXACTpermission 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。PythonD:\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。
  • PARTIALPython 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) 行条件。PythonD:\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
  • EXACTdisable_edit 是导入后药材行的保存锁,不是“处方库模板禁止编辑”的行权限。Python:D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:554-585
  • PARTIALcanonical permission alias 与异步控件读取。

7.5 已开处方

  • EXACT:SN、患者、审核状态、来源、日期、医生筛选;15 条分页;详情、CRUD、患者修正、审核通过/驳回备注、作废、订单创建/列表、A4 打印、PDF。PythonD:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:297-847D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:955-1715
  • EXACT:编辑/删除/审核/患者修正/建单的行状态门槛与管理端 action matrix 等价。PythonD:\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_receptionnotes、tracking、complete 均存在。
  • 患者列表、订单、进度、详情、所有订单动作、预约/取消、指派/身份证均在 Protocol/Remote/Demo 存在。
  • 问诊列表、字典、医助、诊单 CRUD、appointment/diagnosis 处方查询、开方/作废均存在。
  • 处方库 CRUD、药材检索均存在。
  • 已开处方 CRUD、患者修正、审核、作废、诊单详情、订单 CRUD/paid orders 均存在。

定义集中于 D:\web\zyt\app\src\doctor_workstation\services\repository.py:56-425Remote 实现在 D:\web\zyt\app\src\doctor_workstation\services\repository.py:640-1615Demo 实现在 D:\web\zyt\app\src\doctor_workstation\services\mock_repository.py:97-1515D:\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-443D:\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-238D:\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-664D:\web\zyt\app\src\doctor_workstation\ui\dialogs\prescription.py:2178-2196 _detail_generation + target ID;加载期间禁重复点击

11. 低优先级与 Demo 差异

  • P2 / Demo filtersUI 发 diagnosis_confirmed 等筛选:D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:875-904Demo 只处理少数键且读成 confirmedD:\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 患者预约 IDexact 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 提升到可发布候选。