10 KiB
业务订单辅方袋数:后端只读调查
调查日期:2026-10-17。范围为业务订单详情的服用参数修改链路;未修改业务代码、未读取业务数据库或配置中的敏感信息。仓库 .trellis/ 不存在,已按现有代码约定调查。
结论
辅方「5g · 1袋」中的袋数已经持久化在 zyt_tcm_prescription.aux_usage.dosage_bag_count,无需新增数据库字段。业务订单的修改入口是 POST /tcm.prescriptionOrder/patchPrescriptionUsage,当前只更新辅方每天次数和开立天数,没有接受或保存辅方袋数。建议沿用此入口增加可选参数 aux_dosage_bag_count,限制为 1~5 的整数;未传时保留旧袋数,兼容现有调用方。
字段与读写链路
server/sql/1.9.20260520/add_prescription_aux_usage.sql:处方表的aux_usage是已有 JSON 列。server/app/common/model/tcm/Prescription.php:21:aux_usage配置为 JSON,关联数组读写。主方袋数为处方独立字段dosage_bag_count;辅方袋数位于 JSON 中。bags_per_dose是另一个字段,不应用来保存本次截图的袋数。server/app/adminapi/logic/tcm/PrescriptionLogic.php:183:normalizeAuxUsage()维护dosage_amount、dosage_bag_count、need_decoction、bags_per_dose、times_per_day、usage_days、prescription_name。normalizeDosageBagCount()将袋数约束到 1~5,默认 1。server/app/adminapi/validate/tcm/PrescriptionValidate.php:15:通用处方主方袋数采用integer|between:1,5,可复用该业务口径。server/app/adminapi/controller/tcm/PrescriptionOrderController.php:240:调用post()->goCheck('patchPrescriptionUsage'),再传入管理员身份调用逻辑层。server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php:5770:公开方法进入受保护事务,内部patchPrescriptionUsageLocked()加载订单与关联处方。当前合并旧aux_usage后仅改times_per_day、usage_days,其余键保持不变。- 同文件
detail():1212复用处方详情,导出函数formatUsageSegmentForExport():3756和normalizeAuxUsageForExport():3807已读取辅方dosage_bag_count。保存正确 JSON 键后,无需另建展示或导出字段。 - 辅方存在性以
herbs[].formula_type === '辅方'判定;残留aux_usage不代表仍有辅方。
校验现状与必须注意的问题
当前业务逻辑强制主方次数 1~6、主方开立天数 1~999、订单服用天数 1~999;存在辅方时要求辅方次数及开立天数,并分别检查 1~6、1~999。
发现既有验证器场景命名问题:PrescriptionOrderValidate.php:136 定义的是 patchPrescriptionUsage(),而非 scenePatchPrescriptionUsage()。ThinkPHP vendor/topthink/framework/src/think/Validate.php:1673 的 getScene() 只调用 scene 前缀方法。因此控制器当前场景校验不会执行该方法中的 append();静态 $scene 虽然列了用量字段,但这些字段没有基础 $rule,实际主要只校验已有的 id 规则。逻辑层先 (int) 转换再判断范围,也会把部分非整数输入截断为整数。
已用只加载 Composer 和内存验证器的 PHP 探针复现:同一组非法次数/天数参数,scene('patchPrescriptionUsage')->check(...) 返回 true,手动先调用旧 patchPrescriptionUsage()->check(...) 返回 false。探针不初始化业务应用、不连接数据库。
最小修复应正确使用 scenePatchPrescriptionUsage(),把新参数加进场景并追加 integer|between:1,5。业务逻辑还应在保存前验证袋数确实为整数并在范围内,避免直接调用逻辑方法时发生小数截断。无需改变其他接口的验证场景。
权限、状态与事务
- 现有接口权限为
tcm.prescriptionOrder/patchPrescriptionUsage,菜单迁移在server/sql/1.9.20260708/add_prescription_order_patch_usage_menu.sql;新增袋数应沿用此权限。 AuthMiddleware根据菜单 URI 校验管理员权限;现有框架对未注册在菜单表的普通 URI 存在历史放行逻辑,因此部署应确保已有菜单迁移已执行。本次无需新增权限或迁移。- 逻辑层先检查订单存在、
canAccessOrder()、药房快照可变、订单未取消、已关联有效处方,以及PrescriptionLogic::canViewPrescription()。 canAccessOrder()沿用当前全量角色、处方订单菜单访问、订单创建人、开方人授权或数据域可见创建人等口径;处方可见性另检查超管、创建人、医助、共享、可见角色及现有相关权限等。LockedPharmacySnapshotMutation::execute()在Db::transaction()中锁订单行及药房提交声明行;内部返回false会导致回滚。新增字段必须留在此现有事务内部。PharmacyRemoteSnapshotPolicy在已有药房远端单号、提交时间或声明状态为PENDING、UNKNOWN、PENDING_RECONCILE、SUCCESS时锁定修改。新增袋数不可绕开该限制。
日志
当前写入 zyt_tcm_prescription_order_log,action = patch_rx_usage,摘要为主方次数/天数、辅方次数/天数及订单天数的前后值。建议在原辅方段落加上「袋数 旧值 → 新值」,沿用当前日志动作,确保能回溯这次更改。
writeLog():5723 默认是非严格日志,日志保存失败会吞掉异常;本接口沿用该默认行为。是否改成严格日志属于额外行为变更,不是实现辅方袋数所必需。
最小修改建议
- 修改
PrescriptionOrderValidate的正确场景方法,仅对新增可选aux_dosage_bag_count检查整数及 1~5 范围。 - 在
patchPrescriptionUsageLocked()中,存在辅方且新参数已提供时才更新aux_usage['dosage_bag_count'];继续复制旧 JSON,保留用量、名称、代煎等所有其他键。不修改主方dosage_bag_count。 - 缺少新参数时保留旧袋数;无辅方时沿用现有忽略辅方修改的逻辑。不因编辑次数或天数而把旧袋数重置为默认值。
- 扩展
patch_rx_usage摘要记录袋数前后值。 - 验证有效 1、5,非法 0、6、小数、非数字,省略字段兼容,JSON 其他键保留,无辅方不产生新辅方用法,权限/取消/药房锁定失败,以及保存后回读与日志。参数校验至少要通过真实
scene('patchPrescriptionUsage')路径测试,不能仅直接调用场景方法。
现有测试与本次验证
- 未发现针对辅方袋数修改或
patch_rx_usage摘要的既有功能测试。 server/tests/pharmacy/run.php:809对本接口的受保护事务、错误清理及回滚信息有源码契约检查;它不验证真实辅方袋数持久化。server/tests/pharmacy/prescription_order_export_name_regression.php覆盖辅方存在性及名称回退,本次运行 7 项通过。php tests/pharmacy/run.php本次基线运行在run.php:870失败,提示tracking correction must append a strict operation log containing old and new logistics values。这是快递修改日志的源码文本断言,调查时未修改任何后端业务代码,应与本次新增功能分开判断。- 真实 ORM/事务测试可参考
PrescriptionOrderReleaseAndTimeTest.php或PrescriptionOrderTrackingAuditTest.php的临时数据库夹具;它们要求显式指定隔离 MySQL 端口,不能直接用业务数据库。目前未运行这些数据库测试。
推荐从仓库根目录执行:
php -l server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php
php -l server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
php server/tests/pharmacy/run.php
php server/tests/pharmacy/prescription_order_export_name_regression.php
若补充独立辅方袋数行为测试,应使用可控临时数据库或现有内存替身,覆盖上述范围、合并保存及权限/状态拒绝;测试命令随新测试实际路径确定。已有真实 ORM 测试仅在已准备隔离 MySQL 时运行,并按对应文件头说明设置环境变量。
实施结果(2026-10-17)
调查完成后,按授权实现了最小后端扩展:
PrescriptionOrderValidate已改为正确的scenePatchPrescriptionUsage(),新增可选aux_dosage_bag_count。使用场景闭包严格验证 1~5 整数/整数字符串;缺少字段可通过,但显式空值、布尔值、小数或其他类型会拒绝,避免框架普通可选规则跳过空值。PrescriptionOrderLogic仅在处方含辅方且prescription_type为「浓缩水丸」时处理新袋数,逻辑层独立重复严格校验;合并原 JSON 保存该键,省略字段保留原值,其他处方类型和无辅方处方忽略袋数变更。原有主方、次数/天数、权限、取消限制、药房锁及事务边界继续沿用。- 在
patch_rx_usage原日志中增加「辅方袋数 X袋 → Y袋」;只在实际提供并处理袋数字段时记录,不改变原日志失败容错行为。 - 控制器仅更新注释。未新增数据库迁移,未修改本分工之外的前端代码。
新增 server/tests/PrescriptionOrderAuxBagCountTest.php,直接运行真实场景验证器和公开业务逻辑入口,使用独立本地 MySQL 随机临时库,结束后自动删除该库;不加载业务数据库配置。覆盖整数边界、非法输入、JSON/主方字段保留、保存后数据库及模型回读、旧请求省略字段、旧 JSON 无袋数字段、非浓缩水丸、无辅方及残留 JSON、日志、权限/取消/药房快照拒绝,以及处方已保存后订单更新失败的真实事务回滚。
验证结果:
- 新增真实验证器/ORM/事务测试:83 项断言通过。
- 相关 4 个 PHP 文件语法检查通过。
- 辅方导出名称回归:7 项通过。
- 3 个修改过的现有 PHP 文件
git -c core.whitespace=cr-at-eol diff --check通过。 - 上文记录的药房套件快递日志文本断言基线失败未扩大修改。
在已准备隔离 MySQL 实例后,可从仓库根目录重跑:
$env:ZYT_AUX_BAG_TEST_MYSQL_PORT='13483'
php server/tests/PrescriptionOrderAuxBagCountTest.php
php server/tests/pharmacy/prescription_order_export_name_regression.php
本次测试使用 .codex-tasks/aux-bag-count-mysql/data 的专用实例,绑定本机回环地址与 13483 端口。测试会拒绝未指定端口或默认 3306 端口。