26 Commits

Author SHA1 Message Date
d7b3403524 Merge develop into test - sync latest code 2026-04-10 12:31:19 +08:00
Ranyunqiao
e9d4f57815 bug重新发布 2026-04-07 17:49:26 +08:00
e573d9f68b 新增校验,防止删除存在有效患者预约的医生排班。
更新 SurgeryDto,为计划手术时间添加 JSON 格式配置。

改进接诊确认逻辑,使医师确认流程更加健壮。

在 OrderMapper 中新增方法,用于统计患者在指定时间段内的有效预约订单数量。

增强 TicketServiceImpl,防止同一患者在相同科室与时间段内重复预约。
2026-04-07 17:37:53 +08:00
2584c8f076 340 预约管理-门诊预约挂号:选择患者弹窗列表数据字段显示错位 2026-04-07 16:37:09 +08:00
7b6c972a12 340 预约管理-门诊预约挂号:选择患者弹窗列表数据字段显示错位 2026-04-07 16:16:14 +08:00
Ranyunqiao
c3f1b105e9 301
预约管理-》门诊预约挂号:号源信息的序号未进行取值
316门诊医生站-》医嘱TAB页面:会诊医嘱状态从“已签发”变成“草稿”
317【门诊医生站】已签发会诊医嘱未同步至门诊收费系统生成待收费项目
344
门诊预约挂号:未过滤过期号源,允许预约已过时的时间段
347 医生门诊工作已就诊的病人提示未就诊
2026-04-07 15:36:27 +08:00
616c2d21a6 Merge remote-tracking branch 'origin/develop' into develop 2026-04-07 14:01:10 +08:00
63a9e26abf style(mapper): 统一SQL映射文件中的字段别名格式
- 在OutpatientRegistrationAppMapper.xml中将register_time别名添加引号
- 在DoctorStationMainAppMapper.xml中将register_time别名添加引号
- 在TencentAppMapper.xml中为两个register_time别名添加引号
- 确保所有字段别名使用一致的引号格式以避免解析错误
2026-04-07 14:01:00 +08:00
Ranyunqiao
d2dfc714ec 333 门诊医生站开立耗材医嘱时,类型误转为“中成药”且保存报错
341 门诊挂号报错
2026-04-07 10:33:04 +08:00
关羽
5c8bfbc98b Fix: #338 就诊状态校验缺失,#339 药房locationId筛选失效
1. Bug #338: 门诊划价时校验患者就诊状态,仅允许已接诊(1002/1003/1004)患者保存医嘱 2. Bug #339: 添加药房locationId过滤条件 3. 补充practitionerId和founderOrgId自动填充逻辑
2026-04-06 23:18:43 +08:00
关羽
885a147420 test: 关羽 Git 配置测试提交 2026-04-06 07:03:56 +08:00
赵云
afbf3f9075 chore: 添加bug修复进度文档 2026-04-06 07:00:46 +08:00
赵云
720cac8a8f fix: Bug#334 门诊医生站检验申请界面按钮布局优化
- 顶部操作区高度 60px -> 48px
- 按钮尺寸 large -> default
- padding/gap 优化提升垂直空间利用率

Co-Authored-By: 赵云 <zhaoyun@his.local>
2026-04-06 06:55:06 +08:00
5497c99f0c fix: BugFix#338 修复编译错误 - 更正字段名为 getStatusEnum()
- Encounter 类中字段名为 statusEnum 而非 encounterStatusEnum
- 修复 5 处编译错误
- 重新提交
2026-04-05 13:58:10 +08:00
HIS Dev
d8b4aed16c fix: BugFix#339 药房筛选条件失效 - 添加 locationId 过滤条件
- 在 getAdviceBaseInfo 方法中添加 locationId 过滤条件
- 修复药房筛选时返回所有药房数据的问题
- 添加日志记录便于调试
2026-04-05 13:53:03 +08:00
efc97c855c fix: BugFix#338 门诊划价新增时校验就诊状态(患者安全)
- 在保存/签发医嘱前校验就诊状态
- 未接诊患者禁止划价/保存医嘱
- 防止医疗差错和数据不一致

修复范围:
- DoctorStationAdviceAppServiceImpl.saveAdvice()
- 添加就诊状态校验逻辑
- 状态 1001(挂号) 禁止划价
- 状态 1002/1003/1004(已接诊/已收费/已完成) 允许划价
2026-04-05 13:15:28 +08:00
HuangXinQuan
0c5353cf8b 300,301,302预约挂号展示问题 2026-04-03 16:47:03 +08:00
Ranyunqiao
8a84b40ee5 333 门诊医生站开立耗材医嘱时,类型误转为“中成药”且保存报错 2026-04-03 16:42:10 +08:00
f6b39a4815 fix: 更新门诊定价服务以仅返回划价标记为“是”的项目,并修正日志路径和VitalSigns表名
- 修改 OutpatientPricingAppServiceImpl.java,确保仅返回划价标记为“是”的项目
- 修正 VitalSigns.java 中的表名为 "doc_vital_signs"
2026-04-03 16:35:21 +08:00
HuangXinQuan
1b3d4e3dc0 77 门诊挂号-》预约签到 2026-04-03 14:42:13 +08:00
his-dev
cb46461ede fix(#303): 将取消预约限制从取消操作移至预约挂号操作
问题:取消预约时检查次数限制,导致用户无法取消预约
修复:将取消次数限制检查移到预约挂号时进行

变更:
- bookTicket(): 添加取消次数限制检查,达到上限禁止预约
- cancelTicket(): 移除取消限制检查,允许正常取消

提示信息:"由于您在月度内累计取消预约已达X次,触发系统限制,暂时无法在线预约,请联系分诊台或咨询客服。"
2026-04-03 14:08:23 +08:00
3b0a359412 fix: 修复日期格式化函数,支持不带前导零的 M/D 格式
- 修改 formatDateStr 函数,添加对 M/ 和 /D 格式的支持
- 确保生成的日期格式与后端期望的 yyyy/M/d HH:mm:ss 格式匹配
2026-04-03 11:02:58 +08:00
6fa26e895d Merge remote-tracking branch 'origin/develop' into develop 2026-04-03 10:59:04 +08:00
8ab8691c17 fix: 修复禅道Bug #330 门诊医生站诊断保存失败问题
- 修改前端日期格式,从ISO格式改为 yyyy/M/d HH:mm:ss 格式
- 添加后端参数校验,防止NPE异常
- 优化前端错误提示,显示后端返回的具体错误信息
2026-04-03 10:58:23 +08:00
700e353b79 测试合并v4 2026-01-15 16:42:56 +08:00
0b2c19d2c5 测试合并v3 2026-01-15 15:54:22 +08:00
125 changed files with 12044 additions and 253 deletions

View File

@@ -0,0 +1,23 @@
- generic [ref=e5]:
- generic [ref=e6]:
- img [ref=e7]
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]:
- heading "经创贺联项目管理系统" [level=2] [ref=e11]
- generic [ref=e12]: 简体
- generic [ref=e13]:
- generic [ref=e14]:
- generic [ref=e16]: 用户名
- textbox [active] [ref=e17]
- generic [ref=e18]:
- generic [ref=e20]: 密码
- textbox [ref=e21]
- generic [ref=e22]:
- generic [ref=e24]:
- checkbox "保持登录" [ref=e25]
- generic [ref=e26] [cursor=pointer]: 保持登录
- link "忘记密码" [ref=e27] [cursor=pointer]:
- /url: /index.php?m=user&f=reset
- button "登录" [ref=e29] [cursor=pointer]:
- generic [ref=e30]: 登录

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,314 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- iframe [ref=e66]:
- generic [active] [ref=f11e1]:
- banner [ref=f11e2]:
- generic [ref=f11e3]:
- generic [ref=f11e4]:
- link " 测试" [ref=f11e6] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f11e7]:
- generic [ref=f11e8]: 测试
- button " 开源HIS改造落地" [ref=f11e10] [cursor=pointer]:
- generic [ref=f11e11]:
- generic "开源HIS改造落地" [ref=f11e12]
- navigation [ref=f11e15]:
- list [ref=f11e16]:
- listitem [ref=f11e17]:
- link "仪表盘" [ref=f11e18] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f11e19]: 仪表盘
- listitem [ref=f11e20]
- listitem [ref=f11e21]:
- link "Bug" [ref=f11e22] [cursor=pointer]:
- /url: /index.php?m=bug&f=browse&productID=4
- generic [ref=f11e23]: Bug
- listitem [ref=f11e24]:
- link "用例" [ref=f11e25] [cursor=pointer]:
- /url: /index.php?m=testcase&f=browse&productID=4
- generic [ref=f11e26]: 用例
- listitem [ref=f11e27]:
- link "套件" [ref=f11e28] [cursor=pointer]:
- /url: /index.php?m=testsuite&f=browse&productID=4
- generic [ref=f11e29]: 套件
- listitem [ref=f11e30]
- listitem [ref=f11e31]:
- link "测试单" [ref=f11e32] [cursor=pointer]:
- /url: /index.php?m=testtask&f=browse&productID=4
- generic [ref=f11e33]: 测试单
- listitem [ref=f11e34]:
- link "测试报告" [ref=f11e35] [cursor=pointer]:
- /url: /index.php?m=testreport&f=browse&productID=4
- generic [ref=f11e36]: 测试报告
- listitem [ref=f11e37]
- listitem [ref=f11e38]:
- link "用例库" [ref=f11e39] [cursor=pointer]:
- /url: /index.php?m=caselib&f=browse&libID=0
- generic [ref=f11e40]: 用例库
- listitem [ref=f11e41]
- listitem [ref=f11e42]:
- link "自动化" [ref=f11e43] [cursor=pointer]:
- /url: /index.php?m=zanode&f=instruction
- generic [ref=f11e44]: 自动化
- generic [ref=f11e46]:
- button "" [ref=f11e47] [cursor=pointer]:
- generic [ref=f11e48]:
- button " 9" [ref=f11e49] [cursor=pointer]:
- generic [ref=f11e50]:
- generic [ref=f11e51]: "9"
- generic [ref=f11e54] [cursor=pointer]: A
- generic [ref=f11e57]:
- generic [ref=f11e58]:
- generic [ref=f11e59]:
- button " 返回" [ref=f11e60] [cursor=pointer]:
- generic [ref=f11e61]:
- generic [ref=f11e62]: 返回
- generic [ref=f11e63]:
- generic [ref=f11e64]: "306"
- generic [ref=f11e65]: 手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单
- link " 提Bug" [ref=f11e68] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extras=projectID=11,executionID=0,moduleID=126
- generic [ref=f11e69]:
- generic [ref=f11e70]: 提Bug
- generic [ref=f11e71]:
- generic [ref=f11e72]:
- generic [ref=f11e74]:
- generic [ref=f11e76]: 重现步骤
- generic [ref=f11e78]:
- paragraph [ref=f11e79]: "[步骤]"
- paragraph [ref=f11e80]:
- link "index.php?m=file&f=read&t=png&fileID=1415" [ref=f11e81] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1415
- img "index.php?m=file&f=read&t=png&fileID=1415" [ref=f11e82]
- paragraph [ref=f11e83]: 图1
- paragraph [ref=f11e84]: 1、如上图1所示手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f11e85]: "[结果]"
- paragraph [ref=f11e86]: 1、手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f11e87]: "[期望]"
- paragraph [ref=f11e88]: 1、手术管理-》门诊手术安排:手术申请查询过滤掉已安排的手术申请单。
- generic [ref=f11e90]:
- generic [ref=f11e94]:
- generic [ref=f11e95]: 历史记录
- navigation [ref=f11e96]:
- button "" [ref=f11e97] [cursor=pointer]:
- generic [ref=f11e98]:
- button " 添加备注" [ref=f11e99] [cursor=pointer]:
- generic [ref=f11e100]:
- generic [ref=f11e101]: 添加备注
- list [ref=f11e103]:
- listitem [ref=f11e104]:
- generic [ref=f11e105]:
- generic [ref=f11e107]: "1"
- generic [ref=f11e110]:
- text: 2026-03-30 17:01:33,
- strong [ref=f11e111]: 陈显精
- text: 创建。
- listitem [ref=f11e112]:
- generic [ref=f11e113]:
- generic [ref=f11e115]: "2"
- generic [ref=f11e118]:
- text: 2026-03-30 17:01:45,
- strong [ref=f11e119]: 陈显精
- text: 指派给
- strong [ref=f11e120]: 王怡哲
- text:
- generic [ref=f11e123]:
- button " 返回" [ref=f11e124] [cursor=pointer]:
- generic [ref=f11e125]:
- generic [ref=f11e126]: 返回
- link " 确认" [ref=f11e128] [cursor=pointer]:
- /url: /index.php?m=bug&f=confirm&bugID=306
- generic [ref=f11e129]:
- generic [ref=f11e130]: 确认
- link " 指派" [ref=f11e131] [cursor=pointer]:
- /url: /index.php?m=bug&f=assignTo&bugID=306
- generic [ref=f11e132]:
- generic [ref=f11e133]: 指派
- link " 解决" [ref=f11e134] [cursor=pointer]:
- /url: /index.php?m=bug&f=resolve&bugID=306
- generic [ref=f11e135]:
- generic [ref=f11e136]: 解决
- button " 转研发需求" [ref=f11e137] [cursor=pointer]:
- generic [ref=f11e138]:
- generic [ref=f11e139]: 转研发需求
- button " 转任务" [ref=f11e140] [cursor=pointer]:
- generic [ref=f11e141]:
- generic [ref=f11e142]: 转任务
- link " 创建用例" [ref=f11e143] [cursor=pointer]:
- /url: /index.php?m=testcase&f=create&productID=4&branch=0&moduleID=0&from=bug&bugID=306
- generic [ref=f11e144]:
- generic [ref=f11e145]: 创建用例
- link "" [ref=f11e147] [cursor=pointer]:
- /url: /index.php?m=bug&f=edit&bugID=306
- generic [ref=f11e148]:
- link "" [ref=f11e149] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extra=bugID=306,projectID=11,executionID=0
- generic [ref=f11e150]:
- link "" [ref=f11e151] [cursor=pointer]:
- /url: /index.php?m=bug&f=delete&bugID=306
- generic [ref=f11e152]:
- generic [ref=f11e153]:
- generic [ref=f11e154]:
- generic [ref=f11e155]:
- list [ref=f11e156]:
- listitem [ref=f11e157]:
- link "基本信息" [ref=f11e158] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane"
- generic [ref=f11e159]: 基本信息
- listitem [ref=f11e160]:
- link "Bug的一生" [ref=f11e161] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_1"
- generic [ref=f11e162]: Bug的一生
- button "" [ref=f11e163] [cursor=pointer]:
- generic [ref=f11e164]:
- generic [ref=f11e167]:
- generic [ref=f11e168]:
- generic "所属模块" [ref=f11e169]
- list [ref=f11e171]:
- listitem [ref=f11e172]: 手术麻醉管理
- generic "所属计划" [ref=f11e174]
- generic "来源用例" [ref=f11e177]
- generic [ref=f11e179]:
- generic "Bug类型" [ref=f11e180]
- generic [ref=f11e181]: 代码错误
- generic [ref=f11e182]:
- generic "严重程度" [ref=f11e183]
- generic [ref=f11e185]: 3 3
- generic [ref=f11e186]:
- generic "优先级" [ref=f11e187]
- generic [ref=f11e189]: "3"
- generic [ref=f11e190]:
- generic "Bug状态" [ref=f11e191]
- generic [ref=f11e193]: 激活
- generic "激活次数" [ref=f11e195]
- generic "激活时间" [ref=f11e198]
- generic [ref=f11e200]:
- generic "是否确认" [ref=f11e201]
- generic [ref=f11e202]: 未确认
- generic [ref=f11e203]:
- generic "指派给" [ref=f11e204]
- generic [ref=f11e205]: 王怡哲 于 2026-03-30 17:01:31
- generic "截止日期" [ref=f11e207]
- generic "反馈者" [ref=f11e210]
- generic "通知邮箱" [ref=f11e213]
- generic "操作系统" [ref=f11e216]
- generic "浏览器" [ref=f11e219]
- generic "关键词" [ref=f11e222]
- generic "抄送给" [ref=f11e225]
- generic [ref=f11e227]:
- generic [ref=f11e228]:
- list [ref=f11e229]:
- listitem [ref=f11e230]:
- link "项目/迭代/研发需求/任务" [ref=f11e231] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_2"
- generic [ref=f11e232]: 项目/迭代/研发需求/任务
- listitem [ref=f11e233]:
- link "其他相关" [ref=f11e234] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_3"
- generic [ref=f11e235]: 其他相关
- button "" [ref=f11e236] [cursor=pointer]:
- generic [ref=f11e237]:
- generic [ref=f11e240]:
- generic [ref=f11e241]:
- generic "所属项目" [ref=f11e242]
- link "开源HIS改造落地" [ref=f11e244] [cursor=pointer]:
- /url: /index.php?m=project&f=view&projectID=11
- generic "所属执行" [ref=f11e246]
- generic "相关需求" [ref=f11e249]
- generic "相关任务" [ref=f11e252]
- button "" [ref=f11e255] [cursor=pointer]:
- generic [ref=f11e256]:
- text: "* *"
- generic [ref=e67]:
- button " 研发综合界面" [ref=e69] [cursor=pointer]:
- generic [ref=e70]:
- generic [ref=e71]: 研发综合界面
- list [ref=e73]:
- listitem [ref=e74]:
- generic [ref=e76] [cursor=pointer]: 测试
- generic [ref=e77]:
- textbox [ref=e83]:
- /placeholder: 搜索
- button [ref=e85] [cursor=pointer]:
- img [ref=e87]
- link " 开源版21.7" [ref=e88] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e89]:
- generic [ref=e90]: 开源版21.7
- button "升级 " [ref=e91] [cursor=pointer]:
- generic [ref=e92]: 升级
- generic [ref=e93]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,322 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- iframe [ref=e89]:
- generic [active] [ref=f19e1]:
- banner [ref=f19e2]:
- generic [ref=f19e3]:
- generic [ref=f19e4]:
- link " 测试" [ref=f19e6] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f19e7]:
- generic [ref=f19e8]: 测试
- button " 开源HIS改造落地" [ref=f19e10] [cursor=pointer]:
- generic [ref=f19e11]:
- generic "开源HIS改造落地" [ref=f19e12]
- navigation [ref=f19e15]:
- list [ref=f19e16]:
- listitem [ref=f19e17]:
- link "仪表盘" [ref=f19e18] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f19e19]: 仪表盘
- listitem [ref=f19e20]
- listitem [ref=f19e21]:
- link "Bug" [ref=f19e22] [cursor=pointer]:
- /url: /index.php?m=bug&f=browse&productID=4
- generic [ref=f19e23]: Bug
- listitem [ref=f19e24]:
- link "用例" [ref=f19e25] [cursor=pointer]:
- /url: /index.php?m=testcase&f=browse&productID=4
- generic [ref=f19e26]: 用例
- listitem [ref=f19e27]:
- link "套件" [ref=f19e28] [cursor=pointer]:
- /url: /index.php?m=testsuite&f=browse&productID=4
- generic [ref=f19e29]: 套件
- listitem [ref=f19e30]
- listitem [ref=f19e31]:
- link "测试单" [ref=f19e32] [cursor=pointer]:
- /url: /index.php?m=testtask&f=browse&productID=4
- generic [ref=f19e33]: 测试单
- listitem [ref=f19e34]:
- link "测试报告" [ref=f19e35] [cursor=pointer]:
- /url: /index.php?m=testreport&f=browse&productID=4
- generic [ref=f19e36]: 测试报告
- listitem [ref=f19e37]
- listitem [ref=f19e38]:
- link "用例库" [ref=f19e39] [cursor=pointer]:
- /url: /index.php?m=caselib&f=browse&libID=0
- generic [ref=f19e40]: 用例库
- listitem [ref=f19e41]
- listitem [ref=f19e42]:
- link "自动化" [ref=f19e43] [cursor=pointer]:
- /url: /index.php?m=zanode&f=instruction
- generic [ref=f19e44]: 自动化
- generic [ref=f19e46]:
- button "" [ref=f19e47] [cursor=pointer]:
- generic [ref=f19e48]:
- button " 9" [ref=f19e49] [cursor=pointer]:
- generic [ref=f19e50]:
- generic [ref=f19e51]: "9"
- generic [ref=f19e54] [cursor=pointer]: A
- generic [ref=f19e57]:
- generic [ref=f19e58]:
- generic [ref=f19e59]:
- button " 返回" [ref=f19e60] [cursor=pointer]:
- generic [ref=f19e61]:
- generic [ref=f19e62]: 返回
- generic [ref=f19e63]:
- generic [ref=f19e64]: "306"
- generic [ref=f19e65]: 手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单
- link " 提Bug" [ref=f19e68] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extras=projectID=11,executionID=0,moduleID=126
- generic [ref=f19e69]:
- generic [ref=f19e70]: 提Bug
- generic [ref=f19e71]:
- generic [ref=f19e72]:
- generic [ref=f19e74]:
- generic [ref=f19e76]: 重现步骤
- generic [ref=f19e78]:
- paragraph [ref=f19e79]: "[步骤]"
- paragraph [ref=f19e80]:
- link "index.php?m=file&f=read&t=png&fileID=1415" [ref=f19e81] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1415
- img "index.php?m=file&f=read&t=png&fileID=1415" [ref=f19e82]
- paragraph [ref=f19e83]: 图1
- paragraph [ref=f19e84]: 1、如上图1所示手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f19e85]: "[结果]"
- paragraph [ref=f19e86]: 1、手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f19e87]: "[期望]"
- paragraph [ref=f19e88]: 1、手术管理-》门诊手术安排:手术申请查询过滤掉已安排的手术申请单。
- generic [ref=f19e90]:
- generic [ref=f19e94]:
- generic [ref=f19e95]: 历史记录
- navigation [ref=f19e96]:
- button "" [ref=f19e97] [cursor=pointer]:
- generic [ref=f19e98]:
- button " 添加备注" [ref=f19e99] [cursor=pointer]:
- generic [ref=f19e100]:
- generic [ref=f19e101]: 添加备注
- list [ref=f19e103]:
- listitem [ref=f19e104]:
- generic [ref=f19e105]:
- generic [ref=f19e107]: "1"
- generic [ref=f19e110]:
- text: 2026-03-30 17:01:33,
- strong [ref=f19e111]: 陈显精
- text: 创建。
- listitem [ref=f19e112]:
- generic [ref=f19e113]:
- generic [ref=f19e115]: "2"
- generic [ref=f19e118]:
- text: 2026-03-30 17:01:45,
- strong [ref=f19e119]: 陈显精
- text: 指派给
- strong [ref=f19e120]: 王怡哲
- text:
- generic [ref=f19e123]:
- button " 返回" [ref=f19e124] [cursor=pointer]:
- generic [ref=f19e125]:
- generic [ref=f19e126]: 返回
- link " 确认" [ref=f19e128] [cursor=pointer]:
- /url: /index.php?m=bug&f=confirm&bugID=306
- generic [ref=f19e129]:
- generic [ref=f19e130]: 确认
- link " 指派" [ref=f19e131] [cursor=pointer]:
- /url: /index.php?m=bug&f=assignTo&bugID=306
- generic [ref=f19e132]:
- generic [ref=f19e133]: 指派
- link " 解决" [ref=f19e134] [cursor=pointer]:
- /url: /index.php?m=bug&f=resolve&bugID=306
- generic [ref=f19e135]:
- generic [ref=f19e136]: 解决
- button " 转研发需求" [ref=f19e137] [cursor=pointer]:
- generic [ref=f19e138]:
- generic [ref=f19e139]: 转研发需求
- button " 转任务" [ref=f19e140] [cursor=pointer]:
- generic [ref=f19e141]:
- generic [ref=f19e142]: 转任务
- link " 创建用例" [ref=f19e143] [cursor=pointer]:
- /url: /index.php?m=testcase&f=create&productID=4&branch=0&moduleID=0&from=bug&bugID=306
- generic [ref=f19e144]:
- generic [ref=f19e145]: 创建用例
- link "" [ref=f19e147] [cursor=pointer]:
- /url: /index.php?m=bug&f=edit&bugID=306
- generic [ref=f19e148]:
- link "" [ref=f19e149] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extra=bugID=306,projectID=11,executionID=0
- generic [ref=f19e150]:
- link "" [ref=f19e151] [cursor=pointer]:
- /url: /index.php?m=bug&f=delete&bugID=306
- generic [ref=f19e152]:
- generic [ref=f19e153]:
- generic [ref=f19e154]:
- generic [ref=f19e155]:
- list [ref=f19e156]:
- listitem [ref=f19e157]:
- link "基本信息" [ref=f19e158] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane"
- generic [ref=f19e159]: 基本信息
- listitem [ref=f19e160]:
- link "Bug的一生" [ref=f19e161] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_1"
- generic [ref=f19e162]: Bug的一生
- button "" [ref=f19e163] [cursor=pointer]:
- generic [ref=f19e164]:
- generic [ref=f19e167]:
- generic [ref=f19e168]:
- generic "所属模块" [ref=f19e169]
- list [ref=f19e171]:
- listitem [ref=f19e172]: 手术麻醉管理
- generic "所属计划" [ref=f19e174]
- generic "来源用例" [ref=f19e177]
- generic [ref=f19e179]:
- generic "Bug类型" [ref=f19e180]
- generic [ref=f19e181]: 代码错误
- generic [ref=f19e182]:
- generic "严重程度" [ref=f19e183]
- generic [ref=f19e185]: 3 3
- generic [ref=f19e186]:
- generic "优先级" [ref=f19e187]
- generic [ref=f19e189]: "3"
- generic [ref=f19e190]:
- generic "Bug状态" [ref=f19e191]
- generic [ref=f19e193]: 激活
- generic "激活次数" [ref=f19e195]
- generic "激活时间" [ref=f19e198]
- generic [ref=f19e200]:
- generic "是否确认" [ref=f19e201]
- generic [ref=f19e202]: 未确认
- generic [ref=f19e203]:
- generic "指派给" [ref=f19e204]
- generic [ref=f19e205]: 王怡哲 于 2026-03-30 17:01:31
- generic "截止日期" [ref=f19e207]
- generic "反馈者" [ref=f19e210]
- generic "通知邮箱" [ref=f19e213]
- generic "操作系统" [ref=f19e216]
- generic "浏览器" [ref=f19e219]
- generic "关键词" [ref=f19e222]
- generic "抄送给" [ref=f19e225]
- generic [ref=f19e227]:
- generic [ref=f19e228]:
- list [ref=f19e229]:
- listitem [ref=f19e230]:
- link "项目/迭代/研发需求/任务" [ref=f19e231] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_2"
- generic [ref=f19e232]: 项目/迭代/研发需求/任务
- listitem [ref=f19e233]:
- link "其他相关" [ref=f19e234] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_3"
- generic [ref=f19e235]: 其他相关
- button "" [ref=f19e236] [cursor=pointer]:
- generic [ref=f19e237]:
- generic [ref=f19e240]:
- generic [ref=f19e241]:
- generic "所属项目" [ref=f19e242]
- link "开源HIS改造落地" [ref=f19e244] [cursor=pointer]:
- /url: /index.php?m=project&f=view&projectID=11
- generic "所属执行" [ref=f19e246]
- generic "相关需求" [ref=f19e249]
- generic "相关任务" [ref=f19e252]
- button "" [ref=f19e255] [cursor=pointer]:
- generic [ref=f19e256]:
- generic:
- generic:
- link "" [ref=f19e257] [cursor=pointer]:
- /url: /index.php?m=bug&f=view&bugID=307
- generic [ref=f19e258]:
- link "" [ref=f19e259] [cursor=pointer]:
- /url: /index.php?m=bug&f=view&bugID=305
- generic [ref=f19e260]:
- text: "* *"
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list [ref=e90]:
- listitem [ref=e91]:
- generic [ref=e93] [cursor=pointer]: 测试
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,91 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,322 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- iframe [ref=e83]:
- generic [active] [ref=f20e1]:
- banner [ref=f20e2]:
- generic [ref=f20e3]:
- generic [ref=f20e4]:
- link " 测试" [ref=f20e6] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f20e7]:
- generic [ref=f20e8]: 测试
- button " 开源HIS改造落地" [ref=f20e10] [cursor=pointer]:
- generic [ref=f20e11]:
- generic "开源HIS改造落地" [ref=f20e12]
- navigation [ref=f20e15]:
- list [ref=f20e16]:
- listitem [ref=f20e17]:
- link "仪表盘" [ref=f20e18] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f20e19]: 仪表盘
- listitem [ref=f20e20]
- listitem [ref=f20e21]:
- link "Bug" [ref=f20e22] [cursor=pointer]:
- /url: /index.php?m=bug&f=browse&productID=4
- generic [ref=f20e23]: Bug
- listitem [ref=f20e24]:
- link "用例" [ref=f20e25] [cursor=pointer]:
- /url: /index.php?m=testcase&f=browse&productID=4
- generic [ref=f20e26]: 用例
- listitem [ref=f20e27]:
- link "套件" [ref=f20e28] [cursor=pointer]:
- /url: /index.php?m=testsuite&f=browse&productID=4
- generic [ref=f20e29]: 套件
- listitem [ref=f20e30]
- listitem [ref=f20e31]:
- link "测试单" [ref=f20e32] [cursor=pointer]:
- /url: /index.php?m=testtask&f=browse&productID=4
- generic [ref=f20e33]: 测试单
- listitem [ref=f20e34]:
- link "测试报告" [ref=f20e35] [cursor=pointer]:
- /url: /index.php?m=testreport&f=browse&productID=4
- generic [ref=f20e36]: 测试报告
- listitem [ref=f20e37]
- listitem [ref=f20e38]:
- link "用例库" [ref=f20e39] [cursor=pointer]:
- /url: /index.php?m=caselib&f=browse&libID=0
- generic [ref=f20e40]: 用例库
- listitem [ref=f20e41]
- listitem [ref=f20e42]:
- link "自动化" [ref=f20e43] [cursor=pointer]:
- /url: /index.php?m=zanode&f=instruction
- generic [ref=f20e44]: 自动化
- generic [ref=f20e46]:
- button "" [ref=f20e47] [cursor=pointer]:
- generic [ref=f20e48]:
- button " 9" [ref=f20e49] [cursor=pointer]:
- generic [ref=f20e50]:
- generic [ref=f20e51]: "9"
- generic [ref=f20e54] [cursor=pointer]: A
- generic [ref=f20e57]:
- generic [ref=f20e58]:
- generic [ref=f20e59]:
- button " 返回" [ref=f20e60] [cursor=pointer]:
- generic [ref=f20e61]:
- generic [ref=f20e62]: 返回
- generic [ref=f20e63]:
- generic [ref=f20e64]: "306"
- generic [ref=f20e65]: 手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单
- link " 提Bug" [ref=f20e68] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extras=projectID=11,executionID=0,moduleID=126
- generic [ref=f20e69]:
- generic [ref=f20e70]: 提Bug
- generic [ref=f20e71]:
- generic [ref=f20e72]:
- generic [ref=f20e74]:
- generic [ref=f20e76]: 重现步骤
- generic [ref=f20e78]:
- paragraph [ref=f20e79]: "[步骤]"
- paragraph [ref=f20e80]:
- link "index.php?m=file&f=read&t=png&fileID=1415" [ref=f20e81] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1415
- img "index.php?m=file&f=read&t=png&fileID=1415" [ref=f20e82]
- paragraph [ref=f20e83]: 图1
- paragraph [ref=f20e84]: 1、如上图1所示手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f20e85]: "[结果]"
- paragraph [ref=f20e86]: 1、手术管理-》门诊手术安排:手术申请查询未过滤掉已安排的手术申请单。
- paragraph [ref=f20e87]: "[期望]"
- paragraph [ref=f20e88]: 1、手术管理-》门诊手术安排:手术申请查询过滤掉已安排的手术申请单。
- generic [ref=f20e90]:
- generic [ref=f20e94]:
- generic [ref=f20e95]: 历史记录
- navigation [ref=f20e96]:
- button "" [ref=f20e97] [cursor=pointer]:
- generic [ref=f20e98]:
- button " 添加备注" [ref=f20e99] [cursor=pointer]:
- generic [ref=f20e100]:
- generic [ref=f20e101]: 添加备注
- list [ref=f20e103]:
- listitem [ref=f20e104]:
- generic [ref=f20e105]:
- generic [ref=f20e107]: "1"
- generic [ref=f20e110]:
- text: 2026-03-30 17:01:33,
- strong [ref=f20e111]: 陈显精
- text: 创建。
- listitem [ref=f20e112]:
- generic [ref=f20e113]:
- generic [ref=f20e115]: "2"
- generic [ref=f20e118]:
- text: 2026-03-30 17:01:45,
- strong [ref=f20e119]: 陈显精
- text: 指派给
- strong [ref=f20e120]: 王怡哲
- text:
- generic [ref=f20e123]:
- button " 返回" [ref=f20e124] [cursor=pointer]:
- generic [ref=f20e125]:
- generic [ref=f20e126]: 返回
- link " 确认" [ref=f20e128] [cursor=pointer]:
- /url: /index.php?m=bug&f=confirm&bugID=306
- generic [ref=f20e129]:
- generic [ref=f20e130]: 确认
- link " 指派" [ref=f20e131] [cursor=pointer]:
- /url: /index.php?m=bug&f=assignTo&bugID=306
- generic [ref=f20e132]:
- generic [ref=f20e133]: 指派
- link " 解决" [ref=f20e134] [cursor=pointer]:
- /url: /index.php?m=bug&f=resolve&bugID=306
- generic [ref=f20e135]:
- generic [ref=f20e136]: 解决
- button " 转研发需求" [ref=f20e137] [cursor=pointer]:
- generic [ref=f20e138]:
- generic [ref=f20e139]: 转研发需求
- button " 转任务" [ref=f20e140] [cursor=pointer]:
- generic [ref=f20e141]:
- generic [ref=f20e142]: 转任务
- link " 创建用例" [ref=f20e143] [cursor=pointer]:
- /url: /index.php?m=testcase&f=create&productID=4&branch=0&moduleID=0&from=bug&bugID=306
- generic [ref=f20e144]:
- generic [ref=f20e145]: 创建用例
- link "" [ref=f20e147] [cursor=pointer]:
- /url: /index.php?m=bug&f=edit&bugID=306
- generic [ref=f20e148]:
- link "" [ref=f20e149] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extra=bugID=306,projectID=11,executionID=0
- generic [ref=f20e150]:
- link "" [ref=f20e151] [cursor=pointer]:
- /url: /index.php?m=bug&f=delete&bugID=306
- generic [ref=f20e152]:
- generic [ref=f20e153]:
- generic [ref=f20e154]:
- generic [ref=f20e155]:
- list [ref=f20e156]:
- listitem [ref=f20e157]:
- link "基本信息" [ref=f20e158] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane"
- generic [ref=f20e159]: 基本信息
- listitem [ref=f20e160]:
- link "Bug的一生" [ref=f20e161] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_1"
- generic [ref=f20e162]: Bug的一生
- button "" [ref=f20e163] [cursor=pointer]:
- generic [ref=f20e164]:
- generic [ref=f20e167]:
- generic [ref=f20e168]:
- generic "所属模块" [ref=f20e169]
- list [ref=f20e171]:
- listitem [ref=f20e172]: 手术麻醉管理
- generic "所属计划" [ref=f20e174]
- generic "来源用例" [ref=f20e177]
- generic [ref=f20e179]:
- generic "Bug类型" [ref=f20e180]
- generic [ref=f20e181]: 代码错误
- generic [ref=f20e182]:
- generic "严重程度" [ref=f20e183]
- generic [ref=f20e185]: 3 3
- generic [ref=f20e186]:
- generic "优先级" [ref=f20e187]
- generic [ref=f20e189]: "3"
- generic [ref=f20e190]:
- generic "Bug状态" [ref=f20e191]
- generic [ref=f20e193]: 激活
- generic "激活次数" [ref=f20e195]
- generic "激活时间" [ref=f20e198]
- generic [ref=f20e200]:
- generic "是否确认" [ref=f20e201]
- generic [ref=f20e202]: 未确认
- generic [ref=f20e203]:
- generic "指派给" [ref=f20e204]
- generic [ref=f20e205]: 王怡哲 于 2026-03-30 17:01:31
- generic "截止日期" [ref=f20e207]
- generic "反馈者" [ref=f20e210]
- generic "通知邮箱" [ref=f20e213]
- generic "操作系统" [ref=f20e216]
- generic "浏览器" [ref=f20e219]
- generic "关键词" [ref=f20e222]
- generic "抄送给" [ref=f20e225]
- generic [ref=f20e227]:
- generic [ref=f20e228]:
- list [ref=f20e229]:
- listitem [ref=f20e230]:
- link "项目/迭代/研发需求/任务" [ref=f20e231] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_2"
- generic [ref=f20e232]: 项目/迭代/研发需求/任务
- listitem [ref=f20e233]:
- link "其他相关" [ref=f20e234] [cursor=pointer]:
- /url: "#zin_bug_view_306_tabPane_3"
- generic [ref=f20e235]: 其他相关
- button "" [ref=f20e236] [cursor=pointer]:
- generic [ref=f20e237]:
- generic [ref=f20e240]:
- generic [ref=f20e241]:
- generic "所属项目" [ref=f20e242]
- link "开源HIS改造落地" [ref=f20e244] [cursor=pointer]:
- /url: /index.php?m=project&f=view&projectID=11
- generic "所属执行" [ref=f20e246]
- generic "相关需求" [ref=f20e249]
- generic "相关任务" [ref=f20e252]
- button "" [ref=f20e255] [cursor=pointer]:
- generic [ref=f20e256]:
- generic:
- generic:
- link "" [ref=f20e257] [cursor=pointer]:
- /url: /index.php?m=bug&f=view&bugID=307
- generic [ref=f20e258]:
- link "" [ref=f20e259] [cursor=pointer]:
- /url: /index.php?m=bug&f=view&bugID=305
- generic [ref=f20e260]:
- text: "* *"
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list [ref=e84]:
- listitem [ref=e85]:
- generic [ref=e87] [cursor=pointer]: 测试
- generic [ref=e71]:
- textbox [ref=e93]:
- /placeholder: 搜索
- button [ref=e73] [cursor=pointer]:
- img [ref=e75]
- link " 开源版21.7" [ref=e76] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e77]:
- generic [ref=e78]: 开源版21.7
- button "升级 " [ref=e79] [cursor=pointer]:
- generic [ref=e80]: 升级
- generic [ref=e81]:

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,337 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- iframe [ref=e89]:
- generic [active] [ref=f1e1]:
- banner [ref=f1e2]:
- generic [ref=f1e3]:
- generic [ref=f1e4]:
- link " 测试" [ref=f1e6] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f1e7]:
- generic [ref=f1e8]: 测试
- button " 开源HIS改造落地" [ref=f1e10] [cursor=pointer]:
- generic [ref=f1e11]:
- generic "开源HIS改造落地" [ref=f1e12]
- navigation [ref=f1e15]:
- list [ref=f1e16]:
- listitem [ref=f1e17]:
- link "仪表盘" [ref=f1e18] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f1e19]: 仪表盘
- listitem [ref=f1e20]
- listitem [ref=f1e21]:
- link "Bug" [ref=f1e22] [cursor=pointer]:
- /url: /index.php?m=bug&f=browse&productID=4
- generic [ref=f1e23]: Bug
- listitem [ref=f1e24]:
- link "用例" [ref=f1e25] [cursor=pointer]:
- /url: /index.php?m=testcase&f=browse&productID=4
- generic [ref=f1e26]: 用例
- listitem [ref=f1e27]:
- link "套件" [ref=f1e28] [cursor=pointer]:
- /url: /index.php?m=testsuite&f=browse&productID=4
- generic [ref=f1e29]: 套件
- listitem [ref=f1e30]
- listitem [ref=f1e31]:
- link "测试单" [ref=f1e32] [cursor=pointer]:
- /url: /index.php?m=testtask&f=browse&productID=4
- generic [ref=f1e33]: 测试单
- listitem [ref=f1e34]:
- link "测试报告" [ref=f1e35] [cursor=pointer]:
- /url: /index.php?m=testreport&f=browse&productID=4
- generic [ref=f1e36]: 测试报告
- listitem [ref=f1e37]
- listitem [ref=f1e38]:
- link "用例库" [ref=f1e39] [cursor=pointer]:
- /url: /index.php?m=caselib&f=browse&libID=0
- generic [ref=f1e40]: 用例库
- listitem [ref=f1e41]
- listitem [ref=f1e42]:
- link "自动化" [ref=f1e43] [cursor=pointer]:
- /url: /index.php?m=zanode&f=instruction
- generic [ref=f1e44]: 自动化
- generic [ref=f1e46]:
- button "" [ref=f1e47] [cursor=pointer]:
- generic [ref=f1e48]:
- button " 9" [ref=f1e49] [cursor=pointer]:
- generic [ref=f1e50]:
- generic [ref=f1e51]: "9"
- generic [ref=f1e54] [cursor=pointer]: A
- generic [ref=f1e57]:
- generic [ref=f1e58]:
- generic [ref=f1e59]:
- button " 返回" [ref=f1e60] [cursor=pointer]:
- generic [ref=f1e61]:
- generic [ref=f1e62]: 返回
- generic [ref=f1e63]:
- generic [ref=f1e64]: "307"
- generic [ref=f1e65]: 门诊医生站:开立的手术申请后未关联生成预手术收费明细记录
- link " 提Bug" [ref=f1e68] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extras=projectID=11,executionID=0,moduleID=126
- generic [ref=f1e69]:
- generic [ref=f1e70]: 提Bug
- generic [ref=f1e71]:
- generic [ref=f1e72]:
- generic [ref=f1e74]:
- generic [ref=f1e76]: 重现步骤
- generic [ref=f1e78]:
- paragraph [ref=f1e79]: "[步骤]"
- paragraph [ref=f1e80]:
- link "index.php?m=file&f=read&t=png&fileID=1416" [ref=f1e81] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1416
- img "index.php?m=file&f=read&t=png&fileID=1416" [ref=f1e82]
- paragraph [ref=f1e83]: 图1门诊医生站手术申请
- paragraph [ref=f1e84]
- paragraph [ref=f1e85]:
- link "index.php?m=file&f=read&t=png&fileID=1417" [ref=f1e86] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1417
- img "index.php?m=file&f=read&t=png&fileID=1417" [ref=f1e87]
- paragraph [ref=f1e88]: 图2手术申请开立成功
- paragraph [ref=f1e89]
- paragraph [ref=f1e90]:
- link "index.php?m=file&f=read&t=png&fileID=1418" [ref=f1e91] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1418
- img "index.php?m=file&f=read&t=png&fileID=1418" [ref=f1e92]
- paragraph [ref=f1e93]: 图3《门诊收费》检索不到待缴费手术费用。
- paragraph [ref=f1e94]: 1、如上图1、2、3所示门诊医生站开立的手术申请后未关联生成预手术收费明细记录。
- paragraph [ref=f1e95]: "[结果]"
- paragraph [ref=f1e96]: 1、门诊医生站:开立的手术申请后未关联生成预手术收费明细记录。
- paragraph [ref=f1e97]: "[期望]"
- paragraph [ref=f1e98]:
- text: 1、门诊医生站:开立的手术申请成功后,
- generic [ref=f1e99]:
- text: 系统应
- strong [ref=f1e100]: 自动将手术收项目明细插入预收费明细表
- text: 确保《门诊收费》处能够实时看见并进行结算如下图4文档所示
- paragraph [ref=f1e101]:
- link "index.php?m=file&f=read&t=png&fileID=1419" [ref=f1e102] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1419
- img "index.php?m=file&f=read&t=png&fileID=1419" [ref=f1e103]
- paragraph [ref=f1e104]: 图4
- paragraph [ref=f1e105]
- generic [ref=f1e107]:
- generic [ref=f1e111]:
- generic [ref=f1e112]: 历史记录
- navigation [ref=f1e113]:
- button "" [ref=f1e114] [cursor=pointer]:
- generic [ref=f1e115]:
- button " 添加备注" [ref=f1e116] [cursor=pointer]:
- generic [ref=f1e117]:
- generic [ref=f1e118]: 添加备注
- list [ref=f1e120]:
- listitem [ref=f1e121]:
- generic [ref=f1e122]:
- generic [ref=f1e124]: "1"
- generic [ref=f1e127]:
- text: 2026-03-30 17:44:22,
- strong [ref=f1e128]: 陈显精
- text: 创建。
- listitem [ref=f1e129]:
- generic [ref=f1e130]:
- generic [ref=f1e132]: "2"
- generic [ref=f1e135]:
- text: 2026-03-30 17:44:26,
- strong [ref=f1e136]: 陈显精
- text: 指派给
- strong [ref=f1e137]: 王怡哲
- text:
- generic [ref=f1e140]:
- button " 返回" [ref=f1e141] [cursor=pointer]:
- generic [ref=f1e142]:
- generic [ref=f1e143]: 返回
- link " 确认" [ref=f1e145] [cursor=pointer]:
- /url: /index.php?m=bug&f=confirm&bugID=307
- generic [ref=f1e146]:
- generic [ref=f1e147]: 确认
- link " 指派" [ref=f1e148] [cursor=pointer]:
- /url: /index.php?m=bug&f=assignTo&bugID=307
- generic [ref=f1e149]:
- generic [ref=f1e150]: 指派
- link " 解决" [ref=f1e151] [cursor=pointer]:
- /url: /index.php?m=bug&f=resolve&bugID=307
- generic [ref=f1e152]:
- generic [ref=f1e153]: 解决
- button " 转研发需求" [ref=f1e154] [cursor=pointer]:
- generic [ref=f1e155]:
- generic [ref=f1e156]: 转研发需求
- button " 转任务" [ref=f1e157] [cursor=pointer]:
- generic [ref=f1e158]:
- generic [ref=f1e159]: 转任务
- link " 创建用例" [ref=f1e160] [cursor=pointer]:
- /url: /index.php?m=testcase&f=create&productID=4&branch=0&moduleID=0&from=bug&bugID=307
- generic [ref=f1e161]:
- generic [ref=f1e162]: 创建用例
- link "" [ref=f1e164] [cursor=pointer]:
- /url: /index.php?m=bug&f=edit&bugID=307
- generic [ref=f1e165]:
- link "" [ref=f1e166] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extra=bugID=307,projectID=11,executionID=0
- generic [ref=f1e167]:
- link "" [ref=f1e168] [cursor=pointer]:
- /url: /index.php?m=bug&f=delete&bugID=307
- generic [ref=f1e169]:
- generic [ref=f1e170]:
- generic [ref=f1e171]:
- generic [ref=f1e172]:
- list [ref=f1e173]:
- listitem [ref=f1e174]:
- link "基本信息" [ref=f1e175] [cursor=pointer]:
- /url: "#zin_bug_view_307_tabPane"
- generic [ref=f1e176]: 基本信息
- listitem [ref=f1e177]:
- link "Bug的一生" [ref=f1e178] [cursor=pointer]:
- /url: "#zin_bug_view_307_tabPane_1"
- generic [ref=f1e179]: Bug的一生
- button "" [ref=f1e180] [cursor=pointer]:
- generic [ref=f1e181]:
- generic [ref=f1e184]:
- generic [ref=f1e185]:
- generic "所属模块" [ref=f1e186]
- list [ref=f1e188]:
- listitem [ref=f1e189]: 手术麻醉管理
- generic "所属计划" [ref=f1e191]
- generic "来源用例" [ref=f1e194]
- generic [ref=f1e196]:
- generic "Bug类型" [ref=f1e197]
- generic [ref=f1e198]: 设计缺陷
- generic [ref=f1e199]:
- generic "严重程度" [ref=f1e200]
- generic [ref=f1e202]: 3 3
- generic [ref=f1e203]:
- generic "优先级" [ref=f1e204]
- generic [ref=f1e206]: "3"
- generic [ref=f1e207]:
- generic "Bug状态" [ref=f1e208]
- generic [ref=f1e210]: 激活
- generic "激活次数" [ref=f1e212]
- generic "激活时间" [ref=f1e215]
- generic [ref=f1e217]:
- generic "是否确认" [ref=f1e218]
- generic [ref=f1e219]: 未确认
- generic [ref=f1e220]:
- generic "指派给" [ref=f1e221]
- generic [ref=f1e222]: 王怡哲 于 2026-03-30 17:44:22
- generic "截止日期" [ref=f1e224]
- generic "反馈者" [ref=f1e227]
- generic "通知邮箱" [ref=f1e230]
- generic "操作系统" [ref=f1e233]
- generic "浏览器" [ref=f1e236]
- generic "关键词" [ref=f1e239]
- generic "抄送给" [ref=f1e242]
- generic [ref=f1e244]:
- generic [ref=f1e245]:
- list [ref=f1e246]:
- listitem [ref=f1e247]:
- link "项目/迭代/研发需求/任务" [ref=f1e248] [cursor=pointer]:
- /url: "#zin_bug_view_307_tabPane_2"
- generic [ref=f1e249]: 项目/迭代/研发需求/任务
- listitem [ref=f1e250]:
- link "其他相关" [ref=f1e251] [cursor=pointer]:
- /url: "#zin_bug_view_307_tabPane_3"
- generic [ref=f1e252]: 其他相关
- button "" [ref=f1e253] [cursor=pointer]:
- generic [ref=f1e254]:
- generic [ref=f1e257]:
- generic [ref=f1e258]:
- generic "所属项目" [ref=f1e259]
- link "开源HIS改造落地" [ref=f1e261] [cursor=pointer]:
- /url: /index.php?m=project&f=view&projectID=11
- generic "所属执行" [ref=f1e263]
- generic "相关需求" [ref=f1e266]
- generic "相关任务" [ref=f1e269]
- button "" [ref=f1e272] [cursor=pointer]:
- generic [ref=f1e273]:
- text: "* *"
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list [ref=e90]:
- listitem [ref=e91]:
- generic [ref=e93] [cursor=pointer]: 测试
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,23 @@
- generic [ref=e5]:
- generic [ref=e6]:
- img [ref=e7]
- img [ref=e8]
- generic [ref=e9]:
- generic [ref=e10]:
- heading "经创贺联项目管理系统" [level=2] [ref=e11]
- generic [ref=e12]: 简体
- generic [ref=e13]:
- generic [ref=e14]:
- generic [ref=e16]: 用户名
- textbox [active] [ref=e17]: admin
- generic [ref=e18]:
- generic [ref=e20]: 密码
- textbox [ref=e21]: Jchl1528
- generic [ref=e22]:
- generic [ref=e24]:
- checkbox "保持登录" [checked] [ref=e25]
- generic [ref=e26] [cursor=pointer]: 保持登录
- link "忘记密码" [ref=e27] [cursor=pointer]:
- /url: /index.php?m=user&f=reset
- button "登录" [ref=e29] [cursor=pointer]:
- generic [ref=e30]: 登录

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

View File

@@ -0,0 +1,328 @@
- generic [active]:
- generic [ref=e1]:
- generic [ref=e2]:
- list [ref=e3]:
- listitem [ref=e4]:
- link " 地盘" [ref=e5] [cursor=pointer]:
- /url: /index.php?m=my&f=index
- generic [ref=e6]:
- generic [ref=e7]: 地盘
- listitem [ref=e8]:
- link " 项目集" [ref=e9] [cursor=pointer]:
- /url: /index.php?m=program&f=browse
- generic [ref=e10]:
- generic [ref=e11]: 项目集
- listitem [ref=e12]:
- link " 产品" [ref=e13] [cursor=pointer]:
- /url: /index.php?m=product&f=all
- generic [ref=e14]:
- generic [ref=e15]: 产品
- listitem [ref=e16]:
- link " 项目" [ref=e17] [cursor=pointer]:
- /url: /index.php?m=project&f=browse
- generic [ref=e18]:
- generic [ref=e19]: 项目
- listitem [ref=e20]:
- link "  执行" [ref=e21] [cursor=pointer]:
- /url: /index.php?m=execution&f=task
- generic [ref=e22]:  
- generic [ref=e23]: 执行
- listitem [ref=e24]:
- link " 测试" [ref=e25] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=e26]:
- generic [ref=e27]: 测试
- listitem [ref=e28]:
- link " DevOps" [ref=e29] [cursor=pointer]:
- /url: /index.php?m=repo&f=maintain
- generic [ref=e30]:
- generic [ref=e31]: DevOps
- listitem [ref=e32]
- listitem [ref=e33]:
- link " AI" [ref=e34] [cursor=pointer]:
- /url: /index.php?m=aiapp&f=square
- generic [ref=e35]:
- generic [ref=e36]: AI
- listitem [ref=e37]:
- link " BI" [ref=e38] [cursor=pointer]:
- /url: /index.php?m=screen&f=browse
- generic [ref=e39]:
- generic [ref=e40]: BI
- listitem [ref=e41]
- listitem [ref=e42]:
- link " 看板" [ref=e43] [cursor=pointer]:
- /url: /index.php?m=kanban&f=space
- generic [ref=e44]:
- generic [ref=e45]: 看板
- listitem [ref=e46]:
- link " 文档" [ref=e47] [cursor=pointer]:
- /url: /index.php?m=doc&f=lastViewedSpace
- generic [ref=e48]:
- generic [ref=e49]: 文档
- listitem [ref=e50]
- listitem [ref=e51]:
- link " 组织" [ref=e52] [cursor=pointer]:
- /url: /index.php?m=my&f=team
- generic [ref=e53]:
- generic [ref=e54]: 组织
- listitem [ref=e55]:
- link " 后台" [ref=e56] [cursor=pointer]:
- /url: /index.php?m=admin&f=index
- generic [ref=e57]:
- generic [ref=e58]: 后台
- text:
- list [ref=e60]:
- listitem [ref=e61]:
- generic [ref=e63] [cursor=pointer]:
- iframe [ref=e89]:
- generic [active] [ref=f3e1]:
- banner [ref=f3e2]:
- generic [ref=f3e3]:
- generic [ref=f3e4]:
- link " 测试" [ref=f3e6] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f3e7]:
- generic [ref=f3e8]: 测试
- button " 开源HIS改造落地" [ref=f3e10] [cursor=pointer]:
- generic [ref=f3e11]:
- generic "开源HIS改造落地" [ref=f3e12]
- navigation [ref=f3e15]:
- list [ref=f3e16]:
- listitem [ref=f3e17]:
- link "仪表盘" [ref=f3e18] [cursor=pointer]:
- /url: /index.php?m=qa&f=index
- generic [ref=f3e19]: 仪表盘
- listitem [ref=f3e20]
- listitem [ref=f3e21]:
- link "Bug" [ref=f3e22] [cursor=pointer]:
- /url: /index.php?m=bug&f=browse&productID=4
- generic [ref=f3e23]: Bug
- listitem [ref=f3e24]:
- link "用例" [ref=f3e25] [cursor=pointer]:
- /url: /index.php?m=testcase&f=browse&productID=4
- generic [ref=f3e26]: 用例
- listitem [ref=f3e27]:
- link "套件" [ref=f3e28] [cursor=pointer]:
- /url: /index.php?m=testsuite&f=browse&productID=4
- generic [ref=f3e29]: 套件
- listitem [ref=f3e30]
- listitem [ref=f3e31]:
- link "测试单" [ref=f3e32] [cursor=pointer]:
- /url: /index.php?m=testtask&f=browse&productID=4
- generic [ref=f3e33]: 测试单
- listitem [ref=f3e34]:
- link "测试报告" [ref=f3e35] [cursor=pointer]:
- /url: /index.php?m=testreport&f=browse&productID=4
- generic [ref=f3e36]: 测试报告
- listitem [ref=f3e37]
- listitem [ref=f3e38]:
- link "用例库" [ref=f3e39] [cursor=pointer]:
- /url: /index.php?m=caselib&f=browse&libID=0
- generic [ref=f3e40]: 用例库
- listitem [ref=f3e41]
- listitem [ref=f3e42]:
- link "自动化" [ref=f3e43] [cursor=pointer]:
- /url: /index.php?m=zanode&f=instruction
- generic [ref=f3e44]: 自动化
- generic [ref=f3e46]:
- button "" [ref=f3e47] [cursor=pointer]:
- generic [ref=f3e48]:
- button " 9" [ref=f3e49] [cursor=pointer]:
- generic [ref=f3e50]:
- generic [ref=f3e51]: "9"
- generic [ref=f3e54] [cursor=pointer]: A
- generic [ref=f3e57]:
- generic [ref=f3e58]:
- generic [ref=f3e59]:
- button " 返回" [ref=f3e60] [cursor=pointer]:
- generic [ref=f3e61]:
- generic [ref=f3e62]: 返回
- generic [ref=f3e63]:
- generic [ref=f3e64]: "320"
- generic [ref=f3e65]: 手术管理-》门诊手术安排:新增手术安排界面的就诊卡号取值错误
- link " 提Bug" [ref=f3e68] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extras=projectID=11,executionID=0,moduleID=126
- generic [ref=f3e69]:
- generic [ref=f3e70]: 提Bug
- generic [ref=f3e71]:
- generic [ref=f3e72]:
- generic [ref=f3e74]:
- generic [ref=f3e76]: 重现步骤
- generic [ref=f3e78]:
- paragraph [ref=f3e79]: "[步骤]"
- paragraph [ref=f3e80]:
- link "index.php?m=file&f=read&t=png&fileID=1450" [ref=f3e81] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1450
- img "index.php?m=file&f=read&t=png&fileID=1450" [ref=f3e82]
- paragraph [ref=f3e83]: 图1门诊手术安排手术申请查询选中手术申请记录点击【确认】
- paragraph [ref=f3e84]:
- link "index.php?m=file&f=read&t=png&fileID=1451" [ref=f3e85] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1451
- img "index.php?m=file&f=read&t=png&fileID=1451" [ref=f3e86]
- paragraph [ref=f3e87]: 图2新增手术安排界面的就诊卡号取值错误
- paragraph [ref=f3e88]: 1、如上图1、2所示手术管理-》门诊手术安排:新增手术安排界面的就诊卡号取值错误。
- paragraph [ref=f3e89]: "[结果]"
- paragraph [ref=f3e90]: 1、手术管理-》门诊手术安排:新增手术安排界面的就诊卡号取值错误。
- paragraph [ref=f3e91]: "[期望]"
- paragraph [ref=f3e92]:
- text: 1、如上图1、2所示手术管理-》门诊手术安排新增手术安排界面的就诊卡号取值于患者档案的就诊卡号字段的值adm_
- link "patient.id" [ref=f3e93] [cursor=pointer]:
- /url: http://patient.id
- text: = adm_patient_identifier.patient_id;adm_patient_identifier.identifier_no 字段就是就诊卡号如下图3所示
- paragraph [ref=f3e94]:
- link "index.php?m=file&f=read&t=png&fileID=1452" [ref=f3e95] [cursor=pointer]:
- /url: /index.php?m=file&f=read&t=png&fileID=1452
- img "index.php?m=file&f=read&t=png&fileID=1452" [ref=f3e96]
- paragraph [ref=f3e97]: 图3
- generic [ref=f3e99]:
- generic [ref=f3e103]:
- generic [ref=f3e104]: 历史记录
- navigation [ref=f3e105]:
- button "" [ref=f3e106] [cursor=pointer]:
- generic [ref=f3e107]:
- button " 添加备注" [ref=f3e108] [cursor=pointer]:
- generic [ref=f3e109]:
- generic [ref=f3e110]: 添加备注
- list [ref=f3e112]:
- listitem [ref=f3e113]:
- generic [ref=f3e114]:
- generic [ref=f3e116]: "1"
- generic [ref=f3e119]:
- text: 2026-03-31 22:53:45,
- strong [ref=f3e120]: 陈显精
- text: 创建。
- listitem [ref=f3e121]:
- generic [ref=f3e122]:
- generic [ref=f3e124]: "2"
- generic [ref=f3e127]:
- text: 2026-03-31 22:53:49,
- strong [ref=f3e128]: 陈显精
- text: 指派给
- strong [ref=f3e129]: 王怡哲
- text:
- generic [ref=f3e132]:
- button " 返回" [ref=f3e133] [cursor=pointer]:
- generic [ref=f3e134]:
- generic [ref=f3e135]: 返回
- link " 确认" [ref=f3e137] [cursor=pointer]:
- /url: /index.php?m=bug&f=confirm&bugID=320
- generic [ref=f3e138]:
- generic [ref=f3e139]: 确认
- link " 指派" [ref=f3e140] [cursor=pointer]:
- /url: /index.php?m=bug&f=assignTo&bugID=320
- generic [ref=f3e141]:
- generic [ref=f3e142]: 指派
- link " 解决" [ref=f3e143] [cursor=pointer]:
- /url: /index.php?m=bug&f=resolve&bugID=320
- generic [ref=f3e144]:
- generic [ref=f3e145]: 解决
- button " 转研发需求" [ref=f3e146] [cursor=pointer]:
- generic [ref=f3e147]:
- generic [ref=f3e148]: 转研发需求
- button " 转任务" [ref=f3e149] [cursor=pointer]:
- generic [ref=f3e150]:
- generic [ref=f3e151]: 转任务
- link " 创建用例" [ref=f3e152] [cursor=pointer]:
- /url: /index.php?m=testcase&f=create&productID=4&branch=0&moduleID=0&from=bug&bugID=320
- generic [ref=f3e153]:
- generic [ref=f3e154]: 创建用例
- link "" [ref=f3e156] [cursor=pointer]:
- /url: /index.php?m=bug&f=edit&bugID=320
- generic [ref=f3e157]:
- link "" [ref=f3e158] [cursor=pointer]:
- /url: /index.php?m=bug&f=create&productID=4&branch=0&extra=bugID=320,projectID=11,executionID=0
- generic [ref=f3e159]:
- link "" [ref=f3e160] [cursor=pointer]:
- /url: /index.php?m=bug&f=delete&bugID=320
- generic [ref=f3e161]:
- generic [ref=f3e162]:
- generic [ref=f3e163]:
- generic [ref=f3e164]:
- list [ref=f3e165]:
- listitem [ref=f3e166]:
- link "基本信息" [ref=f3e167] [cursor=pointer]:
- /url: "#zin_bug_view_320_tabPane"
- generic [ref=f3e168]: 基本信息
- listitem [ref=f3e169]:
- link "Bug的一生" [ref=f3e170] [cursor=pointer]:
- /url: "#zin_bug_view_320_tabPane_1"
- generic [ref=f3e171]: Bug的一生
- button "" [ref=f3e172] [cursor=pointer]:
- generic [ref=f3e173]:
- generic [ref=f3e176]:
- generic [ref=f3e177]:
- generic "所属模块" [ref=f3e178]
- list [ref=f3e180]:
- listitem [ref=f3e181]: 手术麻醉管理
- generic "所属计划" [ref=f3e183]
- generic "来源用例" [ref=f3e186]
- generic [ref=f3e188]:
- generic "Bug类型" [ref=f3e189]
- generic [ref=f3e190]: 代码错误
- generic [ref=f3e191]:
- generic "严重程度" [ref=f3e192]
- generic [ref=f3e194]: 3 3
- generic [ref=f3e195]:
- generic "优先级" [ref=f3e196]
- generic [ref=f3e198]: "3"
- generic [ref=f3e199]:
- generic "Bug状态" [ref=f3e200]
- generic [ref=f3e202]: 激活
- generic "激活次数" [ref=f3e204]
- generic "激活时间" [ref=f3e207]
- generic [ref=f3e209]:
- generic "是否确认" [ref=f3e210]
- generic [ref=f3e211]: 未确认
- generic [ref=f3e212]:
- generic "指派给" [ref=f3e213]
- generic [ref=f3e214]: 王怡哲 于 2026-03-31 22:53:45
- generic "截止日期" [ref=f3e216]
- generic "反馈者" [ref=f3e219]
- generic "通知邮箱" [ref=f3e222]
- generic "操作系统" [ref=f3e225]
- generic "浏览器" [ref=f3e228]
- generic "关键词" [ref=f3e231]
- generic "抄送给" [ref=f3e234]
- generic [ref=f3e236]:
- generic [ref=f3e237]:
- list [ref=f3e238]:
- listitem [ref=f3e239]:
- link "项目/迭代/研发需求/任务" [ref=f3e240] [cursor=pointer]:
- /url: "#zin_bug_view_320_tabPane_2"
- generic [ref=f3e241]: 项目/迭代/研发需求/任务
- listitem [ref=f3e242]:
- link "其他相关" [ref=f3e243] [cursor=pointer]:
- /url: "#zin_bug_view_320_tabPane_3"
- generic [ref=f3e244]: 其他相关
- button "" [ref=f3e245] [cursor=pointer]:
- generic [ref=f3e246]:
- generic [ref=f3e249]:
- generic [ref=f3e250]:
- generic "所属项目" [ref=f3e251]
- link "开源HIS改造落地" [ref=f3e253] [cursor=pointer]:
- /url: /index.php?m=project&f=view&projectID=11
- generic "所属执行" [ref=f3e255]
- generic "相关需求" [ref=f3e258]
- generic "相关任务" [ref=f3e261]
- button "" [ref=f3e264] [cursor=pointer]:
- generic [ref=f3e265]:
- text: "* *"
- generic [ref=e65]:
- button " 研发综合界面" [ref=e67] [cursor=pointer]:
- generic [ref=e68]:
- generic [ref=e69]: 研发综合界面
- list [ref=e90]:
- listitem [ref=e91]:
- generic [ref=e93] [cursor=pointer]: 测试
- generic [ref=e71]:
- textbox [ref=e77]:
- /placeholder: 搜索
- button [ref=e79] [cursor=pointer]:
- img [ref=e81]
- link " 开源版21.7" [ref=e82] [cursor=pointer]:
- /url: https://www.zentao.net
- generic [ref=e83]:
- generic [ref=e84]: 开源版21.7
- button "升级 " [ref=e85] [cursor=pointer]:
- generic [ref=e86]: 升级
- generic [ref=e87]:

91
BUGFIX_ANALYSIS.md Normal file
View File

@@ -0,0 +1,91 @@
# Bug 根因分析与修复方案
## Bug 335 - 门诊医生站开立药品医嘱保存报错
### 问题分析
根据代码分析,`DoctorStationAdviceAppServiceImpl.saveAdvice()` 方法处理药品医嘱保存时可能报错的原因:
1. **patientId/encounterId 为 null** - 删除操作时前端可能未传
2. **accountId 为 null** - 患者账户信息未正确获取
3. **definitionId/definitionDetailId 为 null** - 定价信息缺失
4. **库存校验失败** - 药品库存不足
### 修复方案
✅ 已部分修复(见代码中的 BugFix 注释)
- 已添加 patientId/encounterId 自动补全逻辑
- 已添加 accountId 自动创建逻辑
- 需要进一步验证 definitionId 的处理
---
## Bug 336 - 门诊医生站开立诊疗项目保存报错
### 问题分析
诊疗项目保存与药品类似,但有以下特殊点:
1. **必须选择执行科室** - 代码中有校验 `throw new ServiceException("诊疗项目必须选择执行科室")`
2. **活动绑定设备处理** - 需要处理 `handService()` 中的设备绑定逻辑
3. **库存校验** - 诊疗项目可能关联耗材
### 修复方案
- 确保前端传递 executeDeptId执行科室
- 检查 handService() 方法中的异常处理
- 添加更详细的错误日志
---
## Bug 338 - 门诊划价新增时未校验就诊记录及诊断记录
### 问题分析
**这是患者安全问题!** 未接诊患者也可新增划价项目可能导致:
- 收费错误
- 医疗纠纷
- 数据不一致
当前代码问题:
- `OutpatientPricingAppServiceImpl.getAdviceBaseInfo()` 仅查询医嘱,未校验就诊状态
- 前端划价保存接口未找到(可能在其他地方)
### 修复方案
1. 在划价查询时增加就诊状态校验
2. 在划价保存时增加诊断记录校验
3. 未接诊患者禁止划价
---
## Bug 339 - 药房筛选条件失效
### 问题分析
查询结果中包含非选中药房的数据,可能原因:
- SQL WHERE 条件未正确应用 locationId
- 多表关联时过滤条件丢失
### 修复方案
- 检查 `DoctorStationAdviceAppMapper.getAdviceBaseInfo()` 的 SQL
- 确保 locationId 条件正确应用
---
## 修复优先级
1. **Bug 338** - 患者安全问题,最高优先级
2. **Bug 335/336** - 核心功能阻断,高优先级
3. **Bug 339** - 数据准确性问题,中优先级
---
## 测试用例
### Bug 338 测试
1. 选择未接诊患者,尝试划价 → 应禁止
2. 选择已接诊但无诊断的患者,尝试划价 → 应提示补充诊断
3. 选择正常接诊患者,划价 → 应成功
### Bug 335/336 测试
1. 门诊医生站开立药品医嘱 → 应成功保存
2. 门诊医生站开立诊疗项目 → 应成功保存
3. 签发医嘱 → 应成功
### Bug 339 测试
1. 选择"西药房"筛选 → 结果应仅包含西药房数据
2. 选择"中药房"筛选 → 结果应仅包含中药房数据

84
BUGFIX_PLAN.md Normal file
View File

@@ -0,0 +1,84 @@
# HIS 系统 Bug 修复计划
## 修复负责人
华佗 (AI 团队)
## 修复时间
2026-04-05 开始
---
## Bug 清单与修复优先级
### 🔴 高优先级(核心业务阻断)
#### Bug 335 - 门诊医生站开立药品医嘱保存报错
- **模块**: 医生工作站
- **文件**: `DoctorStationAdviceAppServiceImpl.java`
- **根因分析**: 待分析
- **修复状态**: 🔄 分析中
#### Bug 336 - 门诊医生站开立诊疗项目保存报错
- **模块**: 医生工作站
- **文件**: `DoctorStationAdviceAppServiceImpl.java`
- **根因分析**: 待分析
- **修复状态**: ⏳ 等待 335 修复后验证
#### Bug 338 - 门诊划价新增时未校验就诊记录及诊断记录
- **模块**: 门诊收费
- **问题**: 未接诊患者也可新增划价项目(患者安全问题)
- **修复方案**: 在划价保存前增加就诊状态和诊断记录校验
- **修复状态**: ⏳ 待修复
### 🟡 中优先级(数据准确性/用户体验)
#### Bug 339 - 药房筛选条件失效
- **模块**: 药房药库报表管理
- **问题**: 查询结果中包含非选中药房的数据
- **修复状态**: ⏳ 待分析
#### Bug 333 - 耗材医嘱类型错误
- **模块**: 医生工作站
- **问题**: 类型误转为"中成药"且保存报错
- **修复状态**: ⏳ 待分析
#### Bug 337 - 挂号时间显示异常
- **模块**: 建档挂号管理
- **问题**: 未显示当前实际挂号时间
- **修复状态**: ⏳ 待分析
#### Bug 334 - 检验申请界面布局优化
- **模块**: 门诊医生工作站
- **问题**: 按钮布局需要调整
- **修复状态**: ⏳ 待修复(前端)
### 🟢 低优先级(历史遗留问题)
#### Bug 249/253/280/300 - 3 月份遗留 bug
- **修复状态**: ⏳ 后续处理
---
## 修复流程
1. **分析根因** - 查看代码和日志,定位问题
2. **编写修复** - 修改代码并添加必要校验
3. **本地测试** - 确保修复有效且不引入新问题
4. **提交代码** - commit 并推送到 gitea
5. **验证关闭** - 在禅道更新 Bug 状态
---
## 测试要求
- 修复后必须测试
- 测试不通过继续修
- 确保不影响其他功能
---
## 备注
- 所有修复基于 develop 分支
- 修复完成后统一提交
- 重要修复添加详细注释

61
BUG_FIX_PROGRESS.md Normal file
View File

@@ -0,0 +1,61 @@
# HIS项目 Bug修复与需求开发进度表
## 项目信息
- **项目名称**: 开源HIS改造落地
- **当前分支**: develop
- **代码路径**:
- 前端: openhis-ui-vue3
- 后端: openhis-server-new
- ** Git仓库**: https://gitea.gentronhealth.com/wangyizhe/his
- **禅道地址**: https://zentao.gentronhealth.com
## 当前状态
- ✅ 代码已克隆完成
- ✅ Bug 已重新分配(管理员操作)
- ⏳ 等待修复人员开始工作
- 📋 张飞负责测试验证
## Bug修复任务列表重新分配后
| Bug ID | 严重程度 | 状态 | 模块 | 标题 | 原指派给 | **新指派给** | 进度 |
|--------|----------|------|------|------|----------|--------------|------|
| 339 | 3 | 激活 | 药房药库报表管理 | 药房筛选条件失效 | 王怡哲 | **关羽** | 待处理 |
| 338 | 3 | 激活 | 门诊收费管理 | 未校验就诊记录 | 王怡哲 | **关羽** | 待处理 |
| 337 | 3 | 激活 | 建档挂号管理 | 挂号时间显示异常 | 王怡哲 | **关羽** | 待处理 |
| 336 | 3 | 激活 | 门诊医生工作站 | 开立诊疗项目保存报错 | 王怡哲 | **关羽** | 待处理 |
| 335 | 3 | 激活 | 门诊医生工作站 | 开立药品医嘱保存报错 | 王怡哲 | **关羽** | 待处理 |
| 334 | 3 | 激活 | 门诊医生工作站 | 检验申请界面布局优化 | 王建 | **子龙** | 待处理 |
| 333 | 3 | 激活 | 门诊医生工作站 | 耗材医嘱类型误转 | 陈显精 | **关羽** | 待处理 |
## P0 级别 Bug紧急优先修复
| Bug ID | 标题 | 严重程度 | 负责人 |
|--------|------|----------|--------|
| 335 | 开立药品医嘱保存报错 | 严重 | 关羽 |
| 336 | 开立诊疗项目保存报错 | 严重 | 关羽 |
| 338 | 未校验就诊记录 | 严重 | 关羽 |
## 需求开发任务列表10个全部未关闭
待进一步确认分配情况...
## 工作流程
1. **认领任务** - 在禅道将 Bug 分配给自己
2. **修改代码** - 从 develop 分支创建新分支:`bug/bug-id`
3. **本地测试** - 确保本地 JDK 17 环境编译通过
4. **提交PR** - 提交 Pull Request 到 develop 分支
5. **测试验证** - 张飞进行测试
6. **合并分支** - 测试通过后合并到 develop
## 注意事项
- 所有代码修改必须先创建新分支
- 分支命名:`bug/bug-id``feature/feedback-id`
- 提交信息必须包含禅道Bug/需求ID
- 修改前请先阅读 `AGENTS.md` 了解项目规范
- **JDK 17 配置** - 确保本地开发环境使用 JDK 17
## 今日会议纪要
- 2026-04-05 15:09: 管理员重新分配 Bug 给群内武将
- 2026-04-05 14:58: 确认将王怡哲的 Bug 分配给关羽、张飞、陈琳
- 2026-04-05 13:47: 统一调度分配人员任务
- 2026-04-05 12:45: 初始任务分配完成

89
Bug318_修复总结.md Normal file
View File

@@ -0,0 +1,89 @@
# Bug #318 修复总结 - 手术医嘱类型冲突解决
## 问题描述
手术医嘱的 `adviceType=4` 与耗材类型冲突,导致保存手术医嘱时被错误归类为耗材,进而引发 `device_def_id` 为 null 的数据库错误。
## 解决方案
引入新的手术类型值 **6**避免与耗材类型4冲突。
## 类型映射表
| 类型 | 前端 adviceType | 后端 ItemType | 数据库 category_enum |
|------|----------------|---------------|---------------------|
| 药品 | 1 | MEDICINE(1) | 1 |
| 耗材 | 4 | DEVICE(2) | 2 |
| 诊疗 | 3 | ACTIVITY(3) | 3 |
| 会诊 | 5 | - | 31 |
| **手术** | **6** | **SURGERY(6)** | **6** |
## 修改的文件
### 1. 后端 - 枚举定义
**文件**: `openhis-server-new/openhis-common/src/main/java/com/openhis/common/enums/ItemType.java`
- 添加 `SURGERY(6, "6", "手术")` 枚举值
### 2. 后端 - SQL Mapper医生站
**文件**: `openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml`
- 修改 SQL`category_enum=4` 映射为 `advice_type=6`
- 修改前: `COALESCE(T1.category_enum, 3) AS advice_type`
- 修改后: `CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type`
### 3. 后端 - SQL Mapper住院医生站
**文件**: `openhis-server-new/openhis-application/src/main/resources/mapper/regdoctorstation/AdviceManageAppMapper.xml`
- 同上修改
### 4. 后端 - 分类逻辑
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
- 更新 `deviceList` 筛选条件:处理 `adviceType=4`(耗材)
- 更新 `activityList` 筛选条件:处理 `adviceType=6`(手术)
### 5. 前端 - 医嘱类型选项
**文件**: `openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/index.vue`
- 修改手术类型值从 4 改为 6
## 数据迁移
### 历史数据更新脚本
**文件**: `sql/迁移记录-DB变更记录/20260401_update_surgery_advice_type.sql`
```sql
-- 更新历史手术医嘱的 category_enum 从 4 改为 6
UPDATE wor_service_request
SET category_enum = 6
WHERE category_enum = 4
AND delete_flag = '0';
```
### 执行步骤
1. **备份数据库**(重要!)
2. 执行上述 UPDATE 语句
3. 重启后端服务
4. 清除前端缓存
## 验证方法
### 1. 后端日志验证
保存手术申请单后,检查日志:
```
BugFix#219: 医嘱分类完成 - 药品:{}, 耗材:{}, 诊疗:{}
```
手术医嘱应该被归类到"诊疗"中。
### 2. 数据库验证
```sql
-- 检查手术医嘱的 category_enum 是否为 6
SELECT id, bus_no, category_enum, content_json::jsonb->>'surgeryName'
FROM wor_service_request
WHERE category_enum = 6;
```
### 3. 前端验证
- 医嘱列表中手术医嘱显示正常
- 手术医嘱的 adviceType 为 6
- 耗材医嘱的 adviceType 为 4
## 注意事项
1. **部署前必须执行数据迁移脚本**,否则历史手术医嘱无法正确显示
2. 修改后需要重新编译部署后端和前端
3. 建议先在测试环境验证后再部署到生产环境

2
GIT_TEST_GUANYU.md Normal file
View File

@@ -0,0 +1,2 @@
# 关羽 Git 配置测试
测试时间: Mon Apr 6 07:03:56 AM CST 2026

105
analyze_empty_data.js Normal file
View File

@@ -0,0 +1,105 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function analyze() {
try {
// 1. 检查申请科室ID在adm_organization中是否存在
console.log('=== 分析申请科室ID是否有效 ===');
const deptResult = await pool.query(`
SELECT DISTINCT s.apply_dept_id
FROM hisdev.cli_surgery s
WHERE s.delete_flag = '0'
AND (s.apply_dept_name IS NULL OR s.apply_dept_name = '')
AND s.apply_dept_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM hisdev.adm_organization o WHERE o.id = s.apply_dept_id
)
`);
console.log(`申请科室ID无效的记录数: ${deptResult.rows.length}`);
if (deptResult.rows.length > 0) {
console.log('无效的apply_dept_id:', deptResult.rows.map(r => r.apply_dept_id));
}
// 2. 检查主刀医生ID在sys_user中是否存在
console.log('\n=== 分析主刀医生ID是否有效 ===');
const surgeonResult = await pool.query(`
SELECT DISTINCT s.main_surgeon_id
FROM hisdev.cli_surgery s
WHERE s.delete_flag = '0'
AND (s.main_surgeon_name IS NULL OR s.main_surgeon_name = '')
AND s.main_surgeon_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM hisdev.sys_user u WHERE u.user_id = s.main_surgeon_id
)
`);
console.log(`主刀医生ID无效的记录数: ${surgeonResult.rows.length}`);
if (surgeonResult.rows.length > 0) {
console.log('无效的main_surgeon_id:', surgeonResult.rows.map(r => r.main_surgeon_id));
}
// 3. 查看有ID但没有name的记录详情
console.log('\n=== 有ID但Name为空的记录详情 ===');
const detailResult = await pool.query(`
SELECT
s.id,
s.surgery_no,
s.apply_dept_id,
s.main_surgeon_id,
s.create_time,
EXISTS (SELECT 1 FROM hisdev.adm_organization o WHERE o.id = s.apply_dept_id) as dept_exists,
EXISTS (SELECT 1 FROM hisdev.sys_user u WHERE u.user_id = s.main_surgeon_id) as surgeon_exists
FROM hisdev.cli_surgery s
WHERE s.delete_flag = '0'
AND (
((s.apply_dept_name IS NULL OR s.apply_dept_name = '') AND s.apply_dept_id IS NOT NULL)
OR
((s.main_surgeon_name IS NULL OR s.main_surgeon_name = '') AND s.main_surgeon_id IS NOT NULL)
)
ORDER BY s.create_time DESC
LIMIT 10
`);
console.log(JSON.stringify(detailResult.rows, null, 2));
// 4. 检查最近创建的记录为什么name为空
console.log('\n=== 最近10条记录的创建情况 ===');
const recentResult = await pool.query(`
SELECT
s.id,
s.surgery_no,
s.apply_dept_id,
s.apply_dept_name,
s.main_surgeon_id,
s.main_surgeon_name,
s.create_time,
s.create_by,
CASE
WHEN s.apply_dept_id IS NULL THEN 'apply_dept_id为空'
WHEN NOT EXISTS (SELECT 1 FROM hisdev.adm_organization o WHERE o.id = s.apply_dept_id) THEN 'apply_dept_id无效'
ELSE 'apply_dept_id有效'
END as dept_status,
CASE
WHEN s.main_surgeon_id IS NULL THEN 'main_surgeon_id为空'
WHEN NOT EXISTS (SELECT 1 FROM hisdev.sys_user u WHERE u.user_id = s.main_surgeon_id) THEN 'main_surgeon_id无效'
ELSE 'main_surgeon_id有效'
END as surgeon_status
FROM hisdev.cli_surgery s
WHERE s.delete_flag = '0'
ORDER BY s.create_time DESC
LIMIT 10
`);
console.log(JSON.stringify(recentResult.rows, null, 2));
} catch (err) {
console.error('分析失败:', err.message);
} finally {
pool.end();
}
}
analyze();

View File

@@ -0,0 +1,73 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function analyze() {
try {
// 1. 检查最近的手术安排及其关联的手术申请
console.log('=== 分析手术安排与手术申请的关联 ===\n');
const result = await pool.query(`
SELECT
os.schedule_id,
os.oper_code,
os.apply_id,
os.create_time,
os.creator_id,
cs.id as surgery_id,
cs.surgery_no,
cs.apply_dept_id,
cs.apply_dept_name,
cs.main_surgeon_id,
cs.main_surgeon_name,
cs.status_enum,
CASE
WHEN cs.id IS NULL THEN '手术申请记录不存在'
WHEN cs.apply_dept_name IS NULL THEN '申请科室名称为空'
WHEN cs.main_surgeon_name IS NULL THEN '主刀医生名称为空'
ELSE '正常'
END as status
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
WHERE os.delete_flag = '0'
ORDER BY os.create_time DESC
LIMIT 10
`);
console.table(result.rows);
// 2. 检查手术申请本身的字段情况
console.log('\n=== 最近创建的手术申请 ===');
const surgeryResult = await pool.query(`
SELECT
id,
surgery_no,
patient_name,
apply_dept_id,
apply_dept_name,
main_surgeon_id,
main_surgeon_name,
status_enum,
create_time,
create_by
FROM hisdev.cli_surgery
WHERE delete_flag = '0'
ORDER BY create_time DESC
LIMIT 5
`);
console.table(surgeryResult.rows);
} catch (err) {
console.error('查询失败:', err.message);
} finally {
pool.end();
}
}
analyze();

1
auto-confirm-skill Submodule

Submodule auto-confirm-skill added at 569e5191a5

72
check_id_match.js Normal file
View File

@@ -0,0 +1,72 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function checkIdMatch() {
try {
// 1. 检查 op_schedule 中的 apply_id
console.log('=== 手术安排中的 apply_id ===');
const scheduleResult = await pool.query(`
SELECT DISTINCT apply_id
FROM hisdev.op_schedule
WHERE delete_flag = '0'
ORDER BY apply_id DESC
LIMIT 10
`);
console.log('apply_id 列表:', scheduleResult.rows.map(r => r.apply_id));
// 2. 检查 cli_surgery 中存在的 id
console.log('\n=== 手术申请中的 id ===');
const surgeryResult = await pool.query(`
SELECT id, surgery_no
FROM hisdev.cli_surgery
WHERE delete_flag = '0'
ORDER BY create_time DESC
LIMIT 10
`);
console.table(surgeryResult.rows);
// 3. 检查 ID 类型
console.log('\n=== 检查 ID 字段类型 ===');
const typeResult = await pool.query(`
SELECT
column_name,
data_type,
character_maximum_length
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name IN ('op_schedule', 'cli_surgery')
AND column_name IN ('id', 'apply_id')
ORDER BY table_name, column_name
`);
console.table(typeResult.rows);
// 4. 尝试匹配
console.log('\n=== 尝试匹配 ===');
const matchResult = await pool.query(`
SELECT
os.schedule_id,
os.apply_id,
cs.id as surgery_id,
CASE WHEN cs.id IS NULL THEN '未找到' ELSE '已找到' END as match_status
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
WHERE os.delete_flag = '0'
ORDER BY os.create_time DESC
LIMIT 5
`);
console.table(matchResult.rows);
} catch (err) {
console.error('查询失败:', err.message);
} finally {
pool.end();
}
}
checkIdMatch();

31
check_nulls.js Normal file
View File

@@ -0,0 +1,31 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
const query = `
SELECT
COUNT(*) as total_count,
COUNT(apply_dept_id) as has_apply_dept_id_count,
COUNT(apply_dept_name) as has_apply_dept_name_count,
COUNT(*) - COUNT(apply_dept_name) as apply_dept_name_null_count,
COUNT(main_surgeon_id) as has_main_surgeon_id_count,
COUNT(main_surgeon_name) as has_main_surgeon_name_count,
COUNT(*) - COUNT(main_surgeon_name) as main_surgeon_name_null_count
FROM hisdev.cli_surgery
WHERE delete_flag = '0'
`;
pool.query(query, (err, res) => {
if (err) {
console.error('Error:', err.message);
} else {
console.log('=== 当前 cli_surgery 表空值统计 ===');
console.log(JSON.stringify(res.rows[0], null, 2));
}
pool.end();
});

67
check_surgery_save.js Normal file
View File

@@ -0,0 +1,67 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function checkLatestSurgery() {
try {
console.log('=== 检查最近创建的手术安排 ===\n');
// 1. 查询最近创建的手术安排
const latestSchedule = await pool.query(`
SELECT
os.schedule_id,
os.oper_code,
os.oper_name,
os.apply_id,
os.create_time,
cs.apply_dept_id,
cs.apply_dept_name,
cs.main_surgeon_id,
cs.main_surgeon_name,
cs.surgery_no
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
WHERE os.delete_flag = '0'
ORDER BY os.create_time DESC
LIMIT 5
`);
console.log('最近5条手术安排');
console.table(latestSchedule.rows);
// 2. 检查是否有空值
const nullCheck = await pool.query(`
SELECT
COUNT(*) as total,
COUNT(CASE WHEN cs.apply_dept_name IS NULL OR cs.apply_dept_name = '' THEN 1 END) as null_apply_dept,
COUNT(CASE WHEN cs.main_surgeon_name IS NULL OR cs.main_surgeon_name = '' THEN 1 END) as null_surgeon
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
WHERE os.delete_flag = '0'
AND os.create_time > NOW() - INTERVAL '1 hour'
`);
console.log('\n=== 最近1小时内创建的手术安排 ===');
console.log(`总计: ${nullCheck.rows[0].total}`);
console.log(`申请科室为空的: ${nullCheck.rows[0].null_apply_dept}`);
console.log(`主刀医生为空的: ${nullCheck.rows[0].null_surgeon}`);
if (parseInt(nullCheck.rows[0].null_apply_dept) === 0 && parseInt(nullCheck.rows[0].null_surgeon) === 0) {
console.log('\n✅ 所有字段都已正确保存!');
} else {
console.log('\n⚠ 仍有部分字段为空,请检查后端代码是否已部署');
}
} catch (err) {
console.error('查询失败:', err.message);
} finally {
pool.end();
}
}
checkLatestSurgery();

76
find_missing_ids.js Normal file
View File

@@ -0,0 +1,76 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function findMissingIds() {
try {
// 1. 查找 op_schedule 中存在的 apply_id但在 cli_surgery 中不存在的
console.log('=== 查找不匹配的 apply_id ===\n');
const result = await pool.query(`
SELECT DISTINCT os.apply_id
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
WHERE os.delete_flag = '0'
AND cs.id IS NULL
ORDER BY os.apply_id DESC
LIMIT 10
`);
console.log('在 op_schedule 中存在,但在 cli_surgery 中不存在的 apply_id:');
console.table(result.rows);
// 2. 尝试用这些 ID 直接查询 cli_surgery
if (result.rows.length > 0) {
console.log('\n=== 尝试直接查询这些 ID ===');
for (const row of result.rows) {
const id = row.apply_id;
const checkResult = await pool.query(`
SELECT id, surgery_no, patient_name, status_enum, delete_flag
FROM hisdev.cli_surgery
WHERE id = $1
`, [id]);
if (checkResult.rows.length === 0) {
console.log(`ID ${id}: 在 cli_surgery 表中不存在`);
} else {
console.log(`ID ${id}: 找到记录`, checkResult.rows[0]);
}
}
}
// 3. 检查是否有可能是手术单号匹配而不是 ID 匹配
console.log('\n=== 检查手术单号关联 ===');
const operCodeResult = await pool.query(`
SELECT
os.schedule_id,
os.oper_code,
os.apply_id,
cs.id as surgery_id_by_apply_id,
cs2.id as surgery_id_by_oper_code,
cs2.surgery_no,
cs2.apply_dept_name,
cs2.main_surgeon_name
FROM hisdev.op_schedule os
LEFT JOIN hisdev.cli_surgery cs ON os.apply_id = cs.id
LEFT JOIN hisdev.cli_surgery cs2 ON os.oper_code = cs2.surgery_no
WHERE os.delete_flag = '0'
ORDER BY os.create_time DESC
LIMIT 5
`);
console.table(operCodeResult.rows);
} catch (err) {
console.error('查询失败:', err.message);
} finally {
pool.end();
}
}
findMissingIds();

105
fix_surgery_data.js Normal file
View File

@@ -0,0 +1,105 @@
const { Pool } = require('pg');
const pool = new Pool({
host: '47.116.196.11',
port: 15432,
database: 'postgresql',
user: 'postgresql',
password: 'Jchl1528'
});
async function fixData() {
const client = await pool.connect();
try {
await client.query('BEGIN');
// 1. 修复申请科室名称
console.log('步骤1: 修复申请科室名称...');
const fixDeptResult = await client.query(`
UPDATE hisdev.cli_surgery s
SET apply_dept_name = o.name
FROM hisdev.adm_organization o
WHERE s.apply_dept_id = o.id
AND (s.apply_dept_name IS NULL OR s.apply_dept_name = '')
AND s.delete_flag = '0'
`);
console.log(` 修复了 ${fixDeptResult.rowCount} 条申请科室记录`);
// 2. 修复主刀医生姓名
console.log('步骤2: 修复主刀医生姓名...');
const fixSurgeonResult = await client.query(`
UPDATE hisdev.cli_surgery s
SET main_surgeon_name = u.nick_name
FROM hisdev.sys_user u
WHERE s.main_surgeon_id = u.user_id
AND (s.main_surgeon_name IS NULL OR s.main_surgeon_name = '')
AND s.delete_flag = '0'
`);
console.log(` 修复了 ${fixSurgeonResult.rowCount} 条主刀医生记录`);
// 3. 对于 apply_dept_id 为空但有 org_id 的记录,使用 org_name
console.log('步骤3: 使用 org_name 填充剩余空申请科室...');
const fixOrgResult = await client.query(`
UPDATE hisdev.cli_surgery s
SET apply_dept_name = o.name,
apply_dept_id = s.org_id
FROM hisdev.adm_organization o
WHERE s.org_id = o.id
AND (s.apply_dept_name IS NULL OR s.apply_dept_name = '')
AND s.delete_flag = '0'
`);
console.log(` 修复了 ${fixOrgResult.rowCount} 条使用org_name的记录`);
// 4. 验证修复结果
console.log('步骤4: 验证修复结果...');
const checkResult = await client.query(`
SELECT
COUNT(*) as total_count,
COUNT(apply_dept_name) as has_apply_dept_name_count,
COUNT(*) - COUNT(apply_dept_name) as apply_dept_name_null_count,
COUNT(main_surgeon_name) as has_main_surgeon_name_count,
COUNT(*) - COUNT(main_surgeon_name) as main_surgeon_name_null_count
FROM hisdev.cli_surgery
WHERE delete_flag = '0'
`);
console.log('\n=== 修复后统计 ===');
console.log(JSON.stringify(checkResult.rows[0], null, 2));
// 5. 查看仍有空值的记录
const nullRecords = await client.query(`
SELECT
id,
surgery_no,
patient_name,
apply_dept_id,
apply_dept_name,
main_surgeon_id,
main_surgeon_name
FROM hisdev.cli_surgery
WHERE delete_flag = '0'
AND (apply_dept_name IS NULL OR apply_dept_name = '' OR main_surgeon_name IS NULL OR main_surgeon_name = '')
ORDER BY create_time DESC
LIMIT 10
`);
if (nullRecords.rows.length > 0) {
console.log('\n=== 仍有空值的记录 ===');
console.log(JSON.stringify(nullRecords.rows, null, 2));
} else {
console.log('\n✅ 所有记录的申请科室和主刀医生姓名已修复完毕!');
}
await client.query('COMMIT');
console.log('\n修复完成');
} catch (err) {
await client.query('ROLLBACK');
console.error('修复失败:', err.message);
} finally {
client.release();
pool.end();
}
}
fixData();

70
md/BUG_ANALYSIS.md Normal file
View File

@@ -0,0 +1,70 @@
# HIS项目 Bug 分析与修复日志
## 2026-04-05 23:55 - 子龙开始工作
### Bug 334 分析:门诊医生站-检验申请界面按钮布局优化
**文件位置**
- `/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue`
**当前布局问题**
1. 顶部操作按钮区高度 60px可能有优化空间
2. 表单区域 padding 较大
3. 需要优化垂直空间利用率
**修复方案**
- 减少不必要的 padding 和 margin
- 优化表单字段布局
- 调整按钮区域高度
---
### Bug 335 分析:门诊医生站开立药品医嘱点击【保存】时报错
**文件位置**
- `/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
**问题定位**
- 方法:`saveAdvice()` -> `handMedication()`
- 可能原因:
1. encounterId 或 patientId 为 null
2. 库存校验失败
3. 账户ID缺失
**代码已修复**
- 行 488-588已添加 encounterId 和 patientId 校验
- 行 497-588自动补全逻辑
---
### Bug 336 分析:门诊医生站开立诊疗项目后点击【保存】报错
**文件位置**
- 同上文件
**问题定位**
- 方法:`saveAdvice()` -> `handService()`
- 可能原因:
1. effectiveOrgId执行科室为 null
2. accountId 为 null
**代码已修复**
- 行 1290-1390已添加 accountId 自动补全
- 行 1338-1343诊疗项目执行科室非空校验
---
## 工作分工
| Bug ID | 负责人 | 状态 |
|--------|--------|------|
| 334 | 子龙 | 分析中 |
| 335 | 关羽 | 待修复 |
| 336 | 关羽 | 待修复 |
| 338 | 关羽 | 待修复 |
## 下一步行动
1. 子龙修复 Bug 334检验申请界面布局优化
2. 关羽修复 Bug 335、336、338
3. 张飞测试验证

10
md/test.html Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试合并11111</title>
</head>
<body>
</body>
</html>

193
null_key_error_analysis.md Normal file
View File

@@ -0,0 +1,193 @@
# "element cannot be mapped to a null key" 错误分析报告
## 错误根本原因
该错误发生在 Java Stream 使用 `Collectors.groupingBy``Collectors.toMap` 时,**key 为 null** 导致的。
Java 的 `HashMap` 不允许 null key`HashMap` 的实现有关),当试图将元素映射到 null key 时,就会抛出此异常。
## 问题定位
### 1. 数据层问题 - SQL 查询返回 null
#### 位置1: ChargeItemMapper.xml (第71行)
**文件**: `openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ChargeItemMapper.xml`
```sql
SELECT
...
contract.bus_no as contract_no, -- 第71行
...
FROM adm_charge_item a
...
LEFT JOIN adm_account acc ON a.account_id = acc.id
LEFT JOIN fin_contract contract ON acc.contract_no = contract.bus_no -- LEFT JOIN
```
**问题**: 使用 LEFT JOIN 关联 `fin_contract` 表,当 `acc.contract_no` 为 null 或没有匹配的合同记录时,`contract.bus_no` 会返回 null。
#### 位置2: PaymentRecDetailMapper.xml (第37行)
**文件**: `openhis-server-new/openhis-domain/src/main/resources/mapper/financial/PaymentRecDetailMapper.xml`
```sql
SELECT
...
T2.contract_no, -- 第37行
...
FROM fin_payment_rec_detail T1
LEFT JOIN adm_account T2 ON T1.account_id = T2."id" -- LEFT JOIN
```
**问题**: 使用 LEFT JOIN 关联 `adm_account` 表,当 `T1.account_id` 为 null 或没有匹配的账户记录时,`T2.contract_no` 会返回 null。
---
### 2. 业务层问题 - 使用 groupingBy 时 key 可能为 null
#### 位置1: PaymentRecServiceImpl.java (第2334行)
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java`
```java
// 2333-2334行
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
```
**风险**: 如果 `ChargeItemBaseInfoDto.contractNo` 为 null来自上述 SQL 查询),将抛出异常。
#### 位置2: PaymentRecServiceImpl.java (第2462行)
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java`
```java
// 2461-2462行
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
```
**风险**: 同上,如果 `contractNo` 为 null将抛出异常。
#### 位置3: PaymentRecServiceImpl.java (第2478行)
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/PaymentRecServiceImpl.java`
```java
// 2477-2478行
Map<Long, List<PaymentRecDetail>> payTransNoMap
= paymentRecDetails.stream().collect(Collectors.groupingBy(PaymentRecDetail::getAccountId));
```
**风险**: 如果 `PaymentRecDetail.accountId` 为 null将抛出异常。
#### 位置4: IChargeBillServiceImpl.java (第936行)
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java`
```java
// 935-936行
Map<String, List<PaymentRecDetailAccountResult>> paymentDetailsMapByContract = PaymentRecDetailAccountResultList
.stream().collect(Collectors.groupingBy(PaymentRecDetailAccountResult::getContractNo));
```
**风险**: 如果 `PaymentRecDetailAccountResult.contractNo` 为 null来自上述 SQL 查询),将抛出异常。
#### 位置5: IChargeBillServiceImpl.java (第1485行)
**文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/paymentmanage/appservice/impl/IChargeBillServiceImpl.java`
```java
// 1484-1485行
Map<String, List<PaymentRecDetailAccountResult>> paymentDetailsMapByContract = PaymentRecDetailAccountResultList
.stream().collect(Collectors.groupingBy(PaymentRecDetailAccountResult::getContractNo));
```
**风险**: 同上,如果 `contractNo` 为 null将抛出异常。
---
## 修复建议
### 方案1: 修改 SQL 查询,使用 COALESCE 处理 null (推荐)
修改 `ChargeItemMapper.xml` 第71行
```sql
-- 修改前
contract.bus_no as contract_no,
-- 修改后
COALESCE(contract.bus_no, 'DEFAULT') as contract_no,
```
修改 `PaymentRecDetailMapper.xml` 第37行
```sql
-- 修改前
T2.contract_no,
-- 修改后
COALESCE(T2.contract_no, 'DEFAULT') as contract_no,
```
### 方案2: 修改 Java 代码,过滤 null key
在使用 `groupingBy` 之前过滤掉 key 为 null 的数据:
```java
// 修改前
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
// 修改后
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream()
.filter(dto -> dto.getContractNo() != null)
.collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
```
### 方案3: 使用 null-safe 的收集器
自定义一个处理 null key 的收集器:
```java
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingByNullSafe(
Function<? super T, ? extends K> classifier) {
return Collectors.groupingBy(
classifier,
HashMap::new,
Collectors.toList()
);
}
```
---
## 建议修复优先级
1. **高优先级**: 修改 `ChargeItemMapper.xml``PaymentRecDetailMapper.xml` 的 SQL 查询
- 这是根本原因,修复后可以防止 null 值传播到 Java 层
2. **中优先级**: 修改 `PaymentRecServiceImpl.java` 第2334行和第2462行
- 这是门诊收费和住院结算的关键路径
3. **低优先级**: 修改 `IChargeBillServiceImpl.java` 第936行和第1485行
- 这是收费账单相关功能
---
## 与最近修改的关系
用户提到最近修改了 `OutpatientChargeAppMapper.xml`,增加了 `cli_surgery` 表关联。
虽然这个修改本身不会直接导致 `contract_no` 为 null但可能触发了某些收费流程使得原本不会执行的代码路径被执行从而暴露了这个潜在问题。
根本原因还是 `ChargeItemMapper.xml` 中的 SQL 查询使用了 LEFT JOIN 导致 `contract_no` 可能为 null。
---
## 验证方法
1. 检查数据库中是否存在 `account_id` 为 null 的 `adm_charge_item` 记录
2. 检查数据库中是否存在 `contract_no` 为 null 的 `adm_account` 记录
3. 在出现错误的收费操作中,打印相关 DTO 对象的 `contractNo` 字段值
```java
// 调试代码示例
chargeItemBaseInfoByIds.forEach(dto -> {
System.out.println("ChargeItem ID: " + dto.getId() + ", contractNo: " + dto.getContractNo());
});
```

View File

@@ -1,11 +1,15 @@
package com.core.framework.config; package com.core.framework.config;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone; import java.util.TimeZone;
/** /**
@@ -24,6 +28,14 @@ public class ApplicationConfig {
*/ */
@Bean @Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); return builder -> {
// 设置默认时区
builder.timeZone(TimeZone.getDefault());
// 设置日期格式为 yyyy/M/d HH:mm:ss支持多种格式反序列化
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
// 添加JavaTimeModule支持用于LocalDateTime
builder.modules(new JavaTimeModule());
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
};
} }
} }

View File

@@ -1,8 +1,10 @@
package com.openhis.web.appointmentmanage.appservice.impl; package com.openhis.web.appointmentmanage.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.openhis.common.constant.CommonConstants;
import com.openhis.appointmentmanage.domain.DoctorSchedule; import com.openhis.appointmentmanage.domain.DoctorSchedule;
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto; import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
import com.openhis.appointmentmanage.domain.SchedulePool; import com.openhis.appointmentmanage.domain.SchedulePool;
@@ -497,6 +499,15 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
if (ObjectUtil.isNotEmpty(pools)) { if (ObjectUtil.isNotEmpty(pools)) {
List<Long> poolIds = pools.stream().map(SchedulePool::getId).collect(java.util.stream.Collectors.toList()); List<Long> poolIds = pools.stream().map(SchedulePool::getId).collect(java.util.stream.Collectors.toList());
// 该排班下存在有效患者预约(号源槽:已预约/已锁定/已取号)则禁止删除;已退号、仅可用/已取消槽位不计入
long appointmentCount = scheduleSlotService.count(new QueryWrapper<ScheduleSlot>()
.in("pool_id", poolIds)
.in("status", CommonConstants.SlotStatus.BOOKED, CommonConstants.SlotStatus.LOCKED,
CommonConstants.SlotStatus.CHECKED_IN));
if (appointmentCount > 0) {
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。");
}
// 2. 根据号源池ID找到所有关联的号源槽 // 2. 根据号源池ID找到所有关联的号源槽
List<ScheduleSlot> slots = scheduleSlotService.list( List<ScheduleSlot> slots = scheduleSlotService.list(
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<ScheduleSlot>() new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<ScheduleSlot>()

View File

@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -123,6 +124,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (query == null) { if (query == null) {
query = new com.openhis.appointmentmanage.dto.TicketQueryDTO(); query = new com.openhis.appointmentmanage.dto.TicketQueryDTO();
} }
normalizeQueryStatus(query);
// 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数) // 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数)
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>( com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
@@ -140,42 +142,67 @@ public class TicketAppServiceImpl implements ITicketAppService {
// 基础字段映射 // 基础字段映射
dto.setSlot_id(raw.getSlotId()); dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); dto.setBusNo(String.valueOf(raw.getSlotId()));
dto.setDoctor(raw.getDoctor()); dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名 dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名
dto.setFee(raw.getFee()); dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName()); dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null); dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone()); dto.setPhone(raw.getPhone());
dto.setIdCard(raw.getIdCard());
dto.setDoctorId(raw.getDoctorId());
dto.setDepartmentId(raw.getDepartmentId());
dto.setRealPatientId(raw.getPatientId());
// 性别处理:直接读取优先级最高的订单性别字段 (SQL 已处理优先级)
if (raw.getPatientGender() != null) {
String pg = raw.getPatientGender().trim();
dto.setGender("1".equals(pg) ? "" : ("2".equals(pg) ? "" : "未知"));
} else {
dto.setGender("未知");
}
// 号源类型处理 (底层是1前端要的是expert)
if (raw.getRegType() != null && raw.getRegType() == 1) { if (raw.getRegType() != null && raw.getRegType() == 1) {
dto.setTicketType("expert"); dto.setTicketType("expert");
} else { } else {
dto.setTicketType("general"); dto.setTicketType("general");
} }
// 拼接就诊时间
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) { if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString()); dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try { try {
dto.setAppointmentDate( String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString())); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(timeStr.length() > 10 ? "yyyy-MM-dd HH:mm" : "yyyy-MM-dd");
java.util.Date date = sdf.parse(timeStr);
dto.setAppointmentDate(date);
dto.setAppointmentTime(date);
} catch (Exception e) { } catch (Exception e) {
dto.setAppointmentDate(new java.util.Date()); dto.setAppointmentDate(new java.util.Date());
} }
} }
// 精准状态翻译把底层的1和2翻译回前端能懂的中文
if (Boolean.TRUE.equals(raw.getIsStopped())) { if (Boolean.TRUE.equals(raw.getIsStopped())) {
dto.setStatus("已停诊"); dto.setStatus("已停诊");
} else { } else {
Integer slotStatus = raw.getSlotStatus(); Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) { if (slotStatus != null) {
if (SlotStatus.BOOKED.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约"); dto.setStatus("已取号");
} else if (SlotStatus.STOPPED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else {
dto.setStatus("已预约");
}
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
dto.setStatus("已退号");
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
dto.setStatus("已停诊"); dto.setStatus("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else { } else {
dto.setStatus("未预约"); dto.setStatus("未预约");
} }
@@ -198,6 +225,62 @@ public class TicketAppServiceImpl implements ITicketAppService {
return R.ok(result); return R.ok(result);
} }
/**
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
*/
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
String rawStatus = query.getStatus();
if (rawStatus == null) {
return;
}
String normalized = rawStatus.trim();
if (normalized.isEmpty()) {
query.setStatus(null);
return;
}
String lower = normalized.toLowerCase(Locale.ROOT);
switch (lower) {
case "all":
case "全部":
query.setStatus("all");
break;
case "unbooked":
case "0":
case "未预约":
query.setStatus("unbooked");
break;
case "booked":
case "1":
case "已预约":
query.setStatus("booked");
break;
case "checked":
case "checkin":
case "checkedin":
case "2":
case "已取号":
query.setStatus("checked");
break;
case "cancelled":
case "canceled":
case "3":
case "已停诊":
case "已取消":
query.setStatus("cancelled");
break;
case "returned":
case "4":
case "5":
case "已退号":
query.setStatus("returned");
break;
default:
// 设置为 impossible 值,配合 mapper 的 otherwise 分支直接返回空
query.setStatus("__invalid__");
break;
}
}
@Override @Override
public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) { public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
if (query == null) { if (query == null) {
@@ -237,12 +320,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
// --- 基础字段处理 --- // --- 基础字段处理 ---
// 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串! // 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串!
dto.setSlot_id(raw.getSlotId()); dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号 dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号
dto.setDoctor(raw.getDoctor()); dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); dto.setDepartment(raw.getDepartmentName());
dto.setFee(raw.getFee()); dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName()); dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getPatientId() != null ? String.valueOf(raw.getPatientId()) : null); dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone()); dto.setPhone(raw.getPhone());
// --- 号源类型处理 (普通/专家) --- // --- 号源类型处理 (普通/专家) ---
@@ -258,9 +342,13 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) { if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString()); dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try { try {
dto.setAppointmentDate( String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
new java.text.SimpleDateFormat("yyyy-MM-dd").parse(raw.getScheduleDate().toString())); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(timeStr.length() > 10 ? "yyyy-MM-dd HH:mm" : "yyyy-MM-dd");
java.util.Date date = sdf.parse(timeStr);
dto.setAppointmentDate(date);
dto.setAppointmentTime(date);
} catch (Exception e) { } catch (Exception e) {
log.error("时间解析失败", e);
dto.setAppointmentDate(new java.util.Date()); dto.setAppointmentDate(new java.util.Date());
} }
} }
@@ -273,10 +361,22 @@ public class TicketAppServiceImpl implements ITicketAppService {
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...) // 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
Integer slotStatus = raw.getSlotStatus(); Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) { if (slotStatus != null) {
if (SlotStatus.BOOKED.equals(slotStatus)) { if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus(AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus()) ? "已取号" : "已预约"); dto.setStatus("已取号");
} else if (SlotStatus.STOPPED.equals(slotStatus)) { } else if (SlotStatus.BOOKED.equals(slotStatus)) {
dto.setStatus("已停诊"); // 视业务可改回已取消 if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else {
dto.setStatus("已预约");
}
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
dto.setStatus("已退号");
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
dto.setStatus("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else { } else {
dto.setStatus("未预约"); dto.setStatus("未预约");
} }
@@ -355,15 +455,12 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (patient != null) { if (patient != null) {
Integer genderEnum = patient.getGenderEnum(); Integer genderEnum = patient.getGenderEnum();
if (genderEnum != null) { if (genderEnum != null) {
switch (genderEnum) { if (Integer.valueOf(1).equals(genderEnum)) {
case 1: dto.setGender("");
dto.setGender(""); } else if (Integer.valueOf(2).equals(genderEnum)) {
break; dto.setGender("");
case 2: } else {
dto.setGender(""); dto.setGender("未知");
break;
default:
dto.setGender("未知");
} }
} }
} }

View File

@@ -23,6 +23,11 @@ public class TicketDto {
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long slot_id; private Long slot_id;
/**
* 号源序号(对应 adm_schedule_slot.seq_no
*/
private Integer seqNo;
/** /**
* 号源编码 * 号源编码
*/ */
@@ -99,4 +104,15 @@ public class TicketDto {
*/ */
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long doctorId; private Long doctorId;
/**
* 真实患者ID数据库主键区别于 patientId 存的就诊卡号)
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long realPatientId;
/**
* 身份证号
*/
private String idCard;
} }

View File

@@ -73,11 +73,10 @@ public class OutpatientPricingAppServiceImpl implements IOutpatientPricingAppSer
} else { } else {
adviceTypes = List.of(1, 2, 3); adviceTypes = List.of(1, 2, 3);
} }
// 门诊划价:不要强制 pricingFlag=1 参与过滤wor_activity_definition.pricing_flag 可能为 0
// 否则会导致诊疗项目(adviceType=3)查询结果为空 records=[]
String categoryCode = adviceBaseDto != null ? adviceBaseDto.getCategoryCode() : null; String categoryCode = adviceBaseDto != null ? adviceBaseDto.getCategoryCode() : null;
// 门诊划价:仅返回划价标记为“是”的项目
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null, return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
organizationId, pageNo, pageSize, null, adviceTypes, null, categoryCode); organizationId, pageNo, pageSize, Whether.YES.getValue(), adviceTypes, null, categoryCode);
} }
} }

View File

@@ -22,6 +22,10 @@ import com.openhis.common.enums.ybenums.YbPayment;
import com.openhis.common.utils.EnumUtils; import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisPageUtils; import com.openhis.common.utils.HisPageUtils;
import com.openhis.common.utils.HisQueryUtils; import com.openhis.common.utils.HisQueryUtils;
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
import com.openhis.clinical.domain.Order;
import com.openhis.clinical.service.IOrderService;
import com.openhis.financial.domain.PaymentReconciliation; import com.openhis.financial.domain.PaymentReconciliation;
import com.openhis.financial.domain.RefundLog; import com.openhis.financial.domain.RefundLog;
import com.openhis.financial.service.IRefundLogService; import com.openhis.financial.service.IRefundLogService;
@@ -48,6 +52,7 @@ import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -97,6 +102,15 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Resource @Resource
IRefundLogService iRefundLogService; IRefundLogService iRefundLogService;
@Resource
IOrderService orderService;
@Resource
ScheduleSlotMapper scheduleSlotMapper;
@Resource
SchedulePoolMapper schedulePoolMapper;
/** /**
* 门诊挂号 - 查询患者信息 * 门诊挂号 - 查询患者信息
* *
@@ -291,6 +305,11 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
} }
} }
// 如果本次门诊挂号来自预约签到,同步把预约订单与号源槽位状态改为已退号
if (result != null && result.getCode() == 200) {
syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
}
// 记录退号日志 // 记录退号日志
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon); recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
@@ -399,6 +418,74 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
return R.ok("已取消挂号"); return R.ok("已取消挂号");
} }
/**
* 同步预约号源状态为已退号。
* 说明:
* 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。
* 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。
*/
private void syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) {
return;
}
try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
if (encounter.getOrganizationId() != null) {
queryWrapper.eq(Order::getDepartmentId, encounter.getOrganizationId());
}
if (encounter.getTenantId() != null) {
queryWrapper.eq(Order::getTenantId, encounter.getTenantId());
}
if (encounter.getCreateTime() != null) {
LocalDate encounterDate = encounter.getCreateTime().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
Date startOfDay = Date.from(encounterDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
Date nextDayStart = Date.from(encounterDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
queryWrapper.ge(Order::getAppointmentDate, startOfDay)
.lt(Order::getAppointmentDate, nextDayStart);
}
Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) {
return;
}
Date now = new Date();
if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) {
Order updateOrder = new Order();
updateOrder.setId(appointmentOrder.getId());
updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED);
updateOrder.setCancelTime(now);
updateOrder.setCancelReason(
StringUtils.isNotEmpty(reason) ? reason : "门诊退号");
updateOrder.setUpdateTime(now);
orderService.updateById(updateOrder);
}
Long slotId = appointmentOrder.getSlotId();
if (slotId == null) {
return;
}
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.RETURNED);
if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId);
}
}
} catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e);
}
}
/** /**
* 补打挂号 * 补打挂号
* 补打挂号不需要修改数据库,只需要返回成功即可,前端已有所有需要的数据用于打印 * 补打挂号不需要修改数据库,只需要返回成功即可,前端已有所有需要的数据用于打印

View File

@@ -2,6 +2,7 @@ package com.openhis.web.clinicalmanage.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.openhis.common.annotation.Dict; import com.openhis.common.annotation.Dict;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@@ -87,6 +88,7 @@ public class SurgeryDto {
private String statusEnum_dictText; private String statusEnum_dictText;
/** 计划手术时间 */ /** 计划手术时间 */
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "GMT+8")
private Date plannedTime; private Date plannedTime;
/** 实际开始时间 */ /** 实际开始时间 */

View File

@@ -421,6 +421,20 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 新增:更新门诊医嘱表状态为已提交 // 新增:更新门诊医嘱表状态为已提交
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.ACTIVE.getValue()); updateServiceRequestStatus(entity.getOrderId(), RequestStatus.ACTIVE.getValue());
// 🎯 更新会诊关联费用项状态为"待收费",提交后即可在收费界面看到
if (entity.getOrderId() != null) {
LambdaQueryWrapper<ChargeItem> chargeItemWrapper = new LambdaQueryWrapper<>();
chargeItemWrapper.eq(ChargeItem::getServiceId, entity.getOrderId())
.eq(ChargeItem::getServiceTable, "wor_service_request");
List<ChargeItem> chargeItems = iChargeItemService.list(chargeItemWrapper);
for (ChargeItem chargeItem : chargeItems) {
chargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
iChargeItemService.updateById(chargeItem);
}
log.info("会诊提交,更新关联费用项状态为待收费,更新数量: {}", chargeItems.size());
}
return true; return true;
} catch (Exception e) { } catch (Exception e) {
log.error("提交会诊申请失败", e); log.error("提交会诊申请失败", e);
@@ -464,6 +478,18 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 更新门诊医嘱表状态为新开 // 更新门诊医嘱表状态为新开
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue()); updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue());
// 更新关联费用项状态为草稿
if (entity.getOrderId() != null) {
LambdaQueryWrapper<ChargeItem> chargeItemWrapper = new LambdaQueryWrapper<>();
chargeItemWrapper.eq(ChargeItem::getServiceId, entity.getOrderId())
.eq(ChargeItem::getServiceTable, "wor_service_request");
List<ChargeItem> chargeItems = iChargeItemService.list(chargeItemWrapper);
for (ChargeItem chargeItem : chargeItems) {
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
iChargeItemService.updateById(chargeItem);
}
}
} else { } else {
// 作废:状态校验 - 已确认(20)、已签名(30)、已完成(40) 状态禁止作废 // 作废:状态校验 - 已确认(20)、已签名(30)、已完成(40) 状态禁止作废
ConsultationStatusEnum currentStatus = ConsultationStatusEnum.getByCode(entity.getConsultationStatus()); ConsultationStatusEnum currentStatus = ConsultationStatusEnum.getByCode(entity.getConsultationStatus());
@@ -480,6 +506,18 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 更新门诊医嘱表状态为已作废 // 更新门诊医嘱表状态为已作废
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.CANCELLED.getValue()); updateServiceRequestStatus(entity.getOrderId(), RequestStatus.CANCELLED.getValue());
// 更新关联费用项状态为终止
if (entity.getOrderId() != null) {
LambdaQueryWrapper<ChargeItem> chargeItemWrapper = new LambdaQueryWrapper<>();
chargeItemWrapper.eq(ChargeItem::getServiceId, entity.getOrderId())
.eq(ChargeItem::getServiceTable, "wor_service_request");
List<ChargeItem> chargeItems = iChargeItemService.list(chargeItemWrapper);
for (ChargeItem chargeItem : chargeItems) {
chargeItem.setStatusEnum(ChargeItemStatus.ABORTED.getValue());
iChargeItemService.updateById(chargeItem);
}
}
} }
return true; return true;
@@ -668,12 +706,14 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
@Override @Override
public List<ConsultationRequestDto> getMyInvitations() { public List<ConsultationRequestDto> getMyInvitations() {
try { try {
// 获取当前登录医生ID // 获取当前登录医生ID和租户ID
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId(); Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
Long tenantId = SecurityUtils.getLoginUser().getTenantId().longValue();
// 查询邀请我的会诊申请 // 查询邀请我的会诊申请
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
invitedWrapper.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId) invitedWrapper.eq(ConsultationInvited::getTenantId, tenantId)
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId)
.orderByDesc(ConsultationInvited::getCreateTime); .orderByDesc(ConsultationInvited::getCreateTime);
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper); List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper);
@@ -1201,14 +1241,16 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
@Override @Override
public List<ConsultationConfirmationDto> getPendingConfirmationList() { public List<ConsultationConfirmationDto> getPendingConfirmationList() {
try { try {
// 获取当前登录医生ID // 获取当前登录医生ID和租户ID
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId(); Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
Long tenantId = SecurityUtils.getLoginUser().getTenantId().longValue();
log.info("获取待确认会诊列表当前医生ID: {}", currentPhysicianId); log.info("获取待确认会诊列表当前医生ID: {}", currentPhysicianId);
// 🎯 关键修改:查询当前医生个人状态为"待确认"、"已确认"或"已签名"的邀请记录 // 🎯 关键修改:查询当前医生个人状态为"待确认"、"已确认"或"已签名"的邀请记录
// 10=已提交待确认、20=已确认待签名、30=已签名排除40=已完成 // 10=已提交待确认、20=已确认待签名、30=已签名排除40=已完成
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
invitedWrapper.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId) invitedWrapper.eq(ConsultationInvited::getTenantId, tenantId)
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId)
.in(ConsultationInvited::getInvitedStatus, .in(ConsultationInvited::getInvitedStatus,
ConsultationStatusEnum.SUBMITTED.getCode(), // 10-待确认 ConsultationStatusEnum.SUBMITTED.getCode(), // 10-待确认
ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认(待签名) ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认(待签名)
@@ -1233,7 +1275,8 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 🎯 查询会诊申请详情(白名单:只查询正在进行中的会诊,明确业务范围) // 🎯 查询会诊申请详情(白名单:只查询正在进行中的会诊,明确业务范围)
// 查询已提交、已确认、已签名状态的会诊排除已完成40 // 查询已提交、已确认、已签名状态的会诊排除已完成40
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
requestWrapper.in(ConsultationRequest::getId, requestIds) requestWrapper.eq(ConsultationRequest::getTenantId, tenantId)
.in(ConsultationRequest::getId, requestIds)
.in(ConsultationRequest::getConsultationStatus, .in(ConsultationRequest::getConsultationStatus,
ConsultationStatusEnum.SUBMITTED.getCode(), // 10-已提交 ConsultationStatusEnum.SUBMITTED.getCode(), // 10-已提交
ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认 ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认
@@ -1322,10 +1365,13 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
} }
// 4. 更新邀请记录(存储会诊意见) // 4. 更新邀请记录(存储会诊意见)
// 格式:科室-医生:意见内容 // 格式:科室-会诊确认参加医师:意见内容
// 兼容:若前端未填写“会诊确认参加医师”,则回退为当前医生姓名
String confirmingPhysicianText =
StringUtils.hasText(dto.getConfirmingPhysician()) ? dto.getConfirmingPhysician().trim() : currentPhysicianName;
String formattedOpinion = String.format("%s-%s%s", String formattedOpinion = String.format("%s-%s%s",
currentDeptName, currentDeptName,
currentPhysicianName, confirmingPhysicianText,
dto.getConsultationOpinion()); dto.getConsultationOpinion());
invited.setInvitedStatus(ConsultationStatusEnum.CONFIRMED.getCode()); // 已确认 invited.setInvitedStatus(ConsultationStatusEnum.CONFIRMED.getCode()); // 已确认
@@ -1634,6 +1680,19 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 更新医嘱状态为"已完成" // 更新医嘱状态为"已完成"
updateServiceRequestStatus(request.getOrderId(), RequestStatus.COMPLETED.getValue()); updateServiceRequestStatus(request.getOrderId(), RequestStatus.COMPLETED.getValue());
// 🎯 更新会诊关联费用项状态为"待收费",这样收费界面就能看到了
if (request.getOrderId() != null) {
LambdaQueryWrapper<ChargeItem> chargeItemWrapper = new LambdaQueryWrapper<>();
chargeItemWrapper.eq(ChargeItem::getServiceId, request.getOrderId())
.eq(ChargeItem::getServiceTable, "wor_service_request");
List<ChargeItem> chargeItems = iChargeItemService.list(chargeItemWrapper);
for (ChargeItem chargeItem : chargeItems) {
chargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
iChargeItemService.updateById(chargeItem);
}
log.info("会诊完成,更新关联费用项状态为待收费,更新数量: {}", chargeItems.size());
}
log.info("所有医生都已签名,会诊申请状态更新为:已签名(30)"); log.info("所有医生都已签名,会诊申请状态更新为:已签名(30)");
} else { } else {
// 🎯 关键修改部分医生签名整体状态不变保持为10或20 // 🎯 关键修改部分医生签名整体状态不变保持为10或20

View File

@@ -205,6 +205,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 构建查询条件 // 构建查询条件
QueryWrapper<AdviceBaseDto> queryWrapper = HisQueryUtils.buildQueryWrapper(adviceBaseDto, searchKey, QueryWrapper<AdviceBaseDto> queryWrapper = HisQueryUtils.buildQueryWrapper(adviceBaseDto, searchKey,
new HashSet<>(Arrays.asList("advice_name", "py_str", "wb_str")), null); new HashSet<>(Arrays.asList("advice_name", "py_str", "wb_str")), null);
// 🔧 BugFix#339: 药房筛选条件失效 - 添加 locationId 过滤条件
if (locationId != null) {
queryWrapper.eq("location_id", locationId);
log.info("BugFix#339: 添加药房筛选条件 locationId={}", locationId);
}
IPage<AdviceBaseDto> adviceBaseInfo = doctorStationAdviceAppMapper.getAdviceBaseInfo( IPage<AdviceBaseDto> adviceBaseInfo = doctorStationAdviceAppMapper.getAdviceBaseInfo(
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId, new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION, CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
@@ -561,6 +566,25 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
return R.fail(null, "无法获取患者信息,请重新选择患者"); return R.fail(null, "无法获取患者信息,请重新选择患者");
} }
} }
// 🔧 BugFix#338: 门诊划价新增时校验就诊状态和诊断记录(患者安全)
// 仅对新增/修改操作进行校验,删除操作不需要
if (!DbOpType.DELETE.getCode().equals(adviceSaveDto.getDbOpType())) {
// 1. 校验就诊状态:必须是已接诊状态
Encounter encounterCheck = iEncounterService.getById(adviceSaveDto.getEncounterId());
if (encounterCheck != null) {
// 就诊状态1=待诊(PLANNED),允许保存的状态 = 2(IN_PROGRESS在诊)、3(ON_HOLD暂离)、4(DISCHARGED诊毕)、5(COMPLETED完成)
if (encounterCheck.getStatusEnum() != null &&
encounterCheck.getStatusEnum() != EncounterStatus.IN_PROGRESS.getValue() &&
encounterCheck.getStatusEnum() != EncounterStatus.ON_HOLD.getValue() &&
encounterCheck.getStatusEnum() != EncounterStatus.DISCHARGED.getValue() &&
encounterCheck.getStatusEnum() != EncounterStatus.COMPLETED.getValue()) {
log.error("BugFix#338: 患者未接诊,禁止划价/保存医嘱encounterId={}, status={}",
adviceSaveDto.getEncounterId(), encounterCheck.getStatusEnum());
return R.fail(null, "患者尚未接诊,无法保存医嘱。请先完成接诊操作!");
}
}
}
} }
// 药品前端adviceType=1 // 药品前端adviceType=1
@@ -770,6 +794,18 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
} }
// 🔧 Bug Fix: 确保practitionerId不为null
if (adviceSaveDto.getPractitionerId() == null) {
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
log.info("handMedication - 自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
}
// 🔧 Bug Fix: 确保founderOrgId不为null
if (adviceSaveDto.getFounderOrgId() == null) {
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
log.info("handMedication - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
}
boolean firstTimeSave = false;// 第一次保存 boolean firstTimeSave = false;// 第一次保存
medicationRequest = new MedicationRequest(); medicationRequest = new MedicationRequest();
medicationRequest.setId(adviceSaveDto.getRequestId()); // 主键id medicationRequest.setId(adviceSaveDto.getRequestId()); // 主键id
@@ -932,13 +968,20 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 处理耗材发放 // 处理耗材发放
Long dispenseId = iDeviceDispenseService.handleDeviceDispense(deviceRequest, DbOpType.INSERT.getCode()); Long dispenseId = iDeviceDispenseService.handleDeviceDispense(deviceRequest, DbOpType.INSERT.getCode());
// 查询耗材定价信息 // 查询耗材定价信息 - 直接使用mapper查询避免递归调用getAdviceBaseInfo导致栈溢出
AdviceBaseDto deviceAdviceDto = new AdviceBaseDto(); IPage<AdviceBaseDto> devicePage = doctorStationAdviceAppMapper.getAdviceBaseInfo(
deviceAdviceDto.setAdviceDefinitionId(boundDevice.getDevActId()); new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(1, 1),
deviceAdviceDto.setAdviceTableName(CommonConstants.TableName.ADM_DEVICE_DEFINITION); PublicationStatus.ACTIVE.getValue(),
IPage<AdviceBaseDto> devicePage = getAdviceBaseInfo(deviceAdviceDto, null, null, null, adviceSaveDto.getFounderOrgId(),
adviceSaveDto.getFounderOrgId(), 1, 1, Whether.NO.getValue(), null,
List.of(ItemType.DEVICE.getValue()), null, null); CommonConstants.TableName.ADM_DEVICE_DEFINITION,
null,
null,
List.of(boundDevice.getDevActId()),
null,
null,
null,
null);
if (devicePage == null || devicePage.getRecords().isEmpty()) { if (devicePage == null || devicePage.getRecords().isEmpty()) {
log.warn("无法找到耗材定价信息: deviceDefId={}", boundDevice.getDevActId()); log.warn("无法找到耗材定价信息: deviceDefId={}", boundDevice.getDevActId());
@@ -946,12 +989,19 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
AdviceBaseDto deviceBaseInfo = devicePage.getRecords().get(0); AdviceBaseDto deviceBaseInfo = devicePage.getRecords().get(0);
if (deviceBaseInfo.getPriceList() == null || deviceBaseInfo.getPriceList().isEmpty()) {
// 查询价格信息 - 直接查询定价主表
List<AdvicePriceDto> mainCharge = doctorStationAdviceAppMapper.getMainCharge(
List.of(deviceBaseInfo.getChargeItemDefinitionId()), PublicationStatus.ACTIVE.getValue());
if (mainCharge == null || mainCharge.isEmpty()) {
log.warn("耗材没有定价信息: deviceDefId={}", boundDevice.getDevActId()); log.warn("耗材没有定价信息: deviceDefId={}", boundDevice.getDevActId());
continue; continue;
} }
AdvicePriceDto devicePrice = deviceBaseInfo.getPriceList().get(0); AdvicePriceDto devicePrice = mainCharge.get(0);
devicePrice.setDefinitionId(deviceBaseInfo.getChargeItemDefinitionId());
// 如果需要定价子表ID可以从mainCharge中获取
// 创建耗材费用项 // 创建耗材费用项
ChargeItem deviceChargeItem = new ChargeItem(); ChargeItem deviceChargeItem = new ChargeItem();
@@ -1137,6 +1187,18 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
} }
// 🔧 Bug Fix: 确保practitionerId不为null
if (adviceSaveDto.getPractitionerId() == null) {
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
log.info("自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
}
// 🔧 Bug Fix: 确保founderOrgId不为null
if (adviceSaveDto.getFounderOrgId() == null) {
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
log.info("自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
}
deviceRequest = new DeviceRequest(); deviceRequest = new DeviceRequest();
deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态 deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
@@ -1201,6 +1263,47 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表 chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表
chargeItem.setProductId(adviceSaveDto.getAdviceDefinitionId());// 收费项id chargeItem.setProductId(adviceSaveDto.getAdviceDefinitionId());// 收费项id
// 🔧 Bug Fix: 如果 definitionId 或 definitionDetailId 为 null从定价信息中获取
if (chargeItem.getDefinitionId() == null || chargeItem.getDefDetailId() == null) {
log.warn("耗材的 definitionId 或 definitionDetailId 为 null尝试从定价信息中获取: deviceDefId={}",
adviceSaveDto.getAdviceDefinitionId());
// 查询耗材定价信息
IPage<AdviceBaseDto> devicePage = doctorStationAdviceAppMapper.getAdviceBaseInfo(
new Page<>(1, 1),
PublicationStatus.ACTIVE.getValue(),
orgId,
CommonConstants.TableName.ADM_DEVICE_DEFINITION,
null,
null,
null,
Arrays.asList(adviceSaveDto.getAdviceDefinitionId()),
null,
null,
null,
null);
if (devicePage != null && !devicePage.getRecords().isEmpty()) {
AdviceBaseDto deviceBaseInfo = devicePage.getRecords().get(0);
if (deviceBaseInfo.getPriceList() != null && !deviceBaseInfo.getPriceList().isEmpty()) {
AdvicePriceDto devicePrice = deviceBaseInfo.getPriceList().get(0);
if (chargeItem.getDefinitionId() == null) {
chargeItem.setDefinitionId(devicePrice.getDefinitionId());
log.info("从定价信息中获取 definitionId: {}", devicePrice.getDefinitionId());
}
if (chargeItem.getDefDetailId() == null) {
chargeItem.setDefDetailId(devicePrice.getDefinitionDetailId());
log.info("从定价信息中获取 definitionDetailId: {}", devicePrice.getDefinitionDetailId());
}
}
}
}
// 🔧 Bug Fix: 确保定义ID不为null
if (chargeItem.getDefinitionId() == null) {
log.error("无法获取耗材的 definitionId: deviceDefId={}", adviceSaveDto.getAdviceDefinitionId());
throw new ServiceException("无法获取耗材的定价信息,请联系管理员");
}
// 🔧 Bug Fix: 如果accountId为null从就诊中获取账户ID如果没有则自动创建 // 🔧 Bug Fix: 如果accountId为null从就诊中获取账户ID如果没有则自动创建
Long accountId = adviceSaveDto.getAccountId(); Long accountId = adviceSaveDto.getAccountId();
if (accountId == null) { if (accountId == null) {
@@ -1323,6 +1426,18 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} }
} }
// 🔧 Bug Fix: 确保practitionerId不为null
if (adviceSaveDto.getPractitionerId() == null) {
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
log.info("handService - 自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
}
// 🔧 Bug Fix: 确保founderOrgId不为null
if (adviceSaveDto.getFounderOrgId() == null) {
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
log.info("handService - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
}
// 🔧 Bug Fix #238: 诊疗项目执行科室非空校验 // 🔧 Bug Fix #238: 诊疗项目执行科室非空校验
if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3) { if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3) {
Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId(); Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId();
@@ -1454,6 +1569,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// } // }
// log.error(e.getMessage(), e); // log.error(e.getMessage(), e);
// } // }
// 签发时将收费项目状态从草稿改为待收费
Long chargeItemId = adviceSaveDto.getChargeItemId();
if (chargeItemId != null) {
ChargeItem existingChargeItem = iChargeItemService.getById(chargeItemId);
if (existingChargeItem != null) {
existingChargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
iChargeItemService.updateById(existingChargeItem);
}
}
} }
} }
} }

View File

@@ -273,12 +273,28 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
*/ */
@Override @Override
public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) { public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) {
// 参数校验:确保诊断列表不为空
if (saveDiagnosisParam == null) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "保存诊断参数" }));
}
// 患者id // 患者id
Long patientId = saveDiagnosisParam.getPatientId(); Long patientId = saveDiagnosisParam.getPatientId();
// 就诊ID // 就诊ID
Long encounterId = saveDiagnosisParam.getEncounterId(); Long encounterId = saveDiagnosisParam.getEncounterId();
// 诊断定义集合 // 诊断定义集合
List<SaveDiagnosisChildParam> diagnosisChildList = saveDiagnosisParam.getDiagnosisChildList(); List<SaveDiagnosisChildParam> diagnosisChildList = saveDiagnosisParam.getDiagnosisChildList();
// 校验患者ID和就诊ID
if (patientId == null || encounterId == null) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "患者ID或就诊ID" }));
}
// 校验诊断列表不为空
if (diagnosisChildList == null || diagnosisChildList.isEmpty()) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "诊断列表" }));
}
// 先删除再保存 // 先删除再保存
// iEncounterDiagnosisService.deleteEncounterDiagnosisInfos(encounterId); // iEncounterDiagnosisService.deleteEncounterDiagnosisInfos(encounterId);

View File

@@ -115,6 +115,15 @@ public class AdviceUtils {
matched = true; matched = true;
// 检查库存是否充足 // 检查库存是否充足
BigDecimal minUnitQuantity = saveDto.getMinUnitQuantity(); BigDecimal minUnitQuantity = saveDto.getMinUnitQuantity();
// 🔧 Bug Fix: 对于耗材类型如果没有设置minUnitQuantity则使用quantity作为默认值
if (minUnitQuantity == null) {
if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(inventoryDto.getItemTable())) {
// 耗材只有一个单位minUnitQuantity等于quantity
minUnitQuantity = saveDto.getQuantity();
} else {
return saveDto.getAdviceName() + "的小单位数量不能为空";
}
}
BigDecimal chineseHerbsDoseQuantity = saveDto.getChineseHerbsDoseQuantity(); // 中药付数 BigDecimal chineseHerbsDoseQuantity = saveDto.getChineseHerbsDoseQuantity(); // 中药付数
// 中草药医嘱的情况 // 中草药医嘱的情况
if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) { if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) {

View File

@@ -59,7 +59,7 @@
T9.gender_enum AS genderEnum, T9.gender_enum AS genderEnum,
T9.id_card AS idCard, T9.id_card AS idCard,
T9.status_enum AS statusEnum, T9.status_enum AS statusEnum,
T9.register_time AS registerTime, T9.register_time AS register_time,
T9.total_price AS totalPrice, T9.total_price AS totalPrice,
T9.account_name AS accountName, T9.account_name AS accountName,
T9.enterer_name AS entererName, T9.enterer_name AS entererName,
@@ -84,7 +84,7 @@
T8.gender_enum AS gender_enum, T8.gender_enum AS gender_enum,
T8.id_card AS id_card, T8.id_card AS id_card,
T1.status_enum AS status_enum, T1.status_enum AS status_enum,
T1.create_time AS register_time, T1.create_time AS "register_time",
T10.total_price, T10.total_price,
T11."name" AS account_name, T11."name" AS account_name,
T12."name" AS enterer_name, T12."name" AS enterer_name,

View File

@@ -49,7 +49,7 @@
T8.phone AS phone, T8.phone AS phone,
T8.birth_date AS birth_date, T8.birth_date AS birth_date,
T1.status_enum AS status_enum, T1.status_enum AS status_enum,
T1.create_time AS register_time, T1.create_time AS "register_time",
T1.reception_time AS reception_time, T1.reception_time AS reception_time,
T1.organization_id AS org_id, T1.organization_id AS org_id,
T8.bus_no AS bus_no, T8.bus_no AS bus_no,

View File

@@ -43,7 +43,7 @@
T8.gender_enum AS gender_enum, T8.gender_enum AS gender_enum,
T8.id_card AS id_card, T8.id_card AS id_card,
T1.status_enum AS status_enum, T1.status_enum AS status_enum,
T1.create_time AS register_time, T1.create_time AS "register_time",
T10.total_price, T10.total_price,
T11."name" AS account_name, T11."name" AS account_name,
T12."name" AS enterer_name, T12."name" AS enterer_name,
@@ -140,7 +140,7 @@
T8.phone AS phone, T8.phone AS phone,
T8.birth_date AS birth_date, T8.birth_date AS birth_date,
T1.status_enum AS status_enum, T1.status_enum AS status_enum,
T1.create_time AS register_time, T1.create_time AS "register_time",
T1.reception_time AS reception_time, T1.reception_time AS reception_time,
T1.organization_id AS org_id, T1.organization_id AS org_id,
T8.bus_no AS bus_no, T8.bus_no AS bus_no,

View File

@@ -769,15 +769,21 @@ public class CommonConstants {
} }
/** /**
* 号源槽位状态 (adm_schedule_slot.slot_status) * 号源槽位状态 (adm_schedule_slot.status)
*/ */
public interface SlotStatus { public interface SlotStatus {
/** 可用 / 待预约 */ /** 可用 / 待预约 */
Integer AVAILABLE = 0; Integer AVAILABLE = 0;
/** 已预约 */ /** 已预约 */
Integer BOOKED = 1; Integer BOOKED = 1;
/** 已停诊 / 已失效 */ /** 已取消 / 已停诊 */
Integer STOPPED = 2; Integer CANCELLED = 2;
/** 已锁定 */
Integer LOCKED = 3;
/** 已签到 / 已取号 */
Integer CHECKED_IN = 4;
/** 已退号 */
Integer RETURNED = 5;
} }
/** /**
@@ -790,6 +796,8 @@ public class CommonConstants {
Integer CHECKED_IN = 2; Integer CHECKED_IN = 2;
/** 已取消 */ /** 已取消 */
Integer CANCELLED = 3; Integer CANCELLED = 3;
/** 已退号 */
Integer RETURNED = 4;
} }
} }

View File

@@ -9,6 +9,8 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import java.time.LocalTime; import java.time.LocalTime;
import java.util.Date;
/** /**
* 号源池明细Entity * 号源池明细Entity
* *
@@ -29,7 +31,7 @@ public class ScheduleSlot extends HisBaseEntity {
/** 序号 */ /** 序号 */
private Integer seqNo; private Integer seqNo;
/** 序号状态: 0-可用,1-已预约,2-已取消,3-已过期等 */ /** 序号状态: 0-可用,1-已预约,2-已取消/已停诊,3-已锁定,4-已签到,5-已退号 */
private Integer status; private Integer status;
/** 预约订单ID */ /** 预约订单ID */
@@ -37,4 +39,7 @@ public class ScheduleSlot extends HisBaseEntity {
/** 预计叫号时间 */ /** 预计叫号时间 */
private LocalTime expectTime; private LocalTime expectTime;
/** 签到时间 */
private Date checkInTime;
} }

View File

@@ -11,6 +11,7 @@ import java.time.LocalTime;
public class TicketSlotDTO { public class TicketSlotDTO {
// 基础信息 // 基础信息
private Long slotId; private Long slotId;
private Integer seqNo;
private Long scheduleId; private Long scheduleId;
private String doctor; private String doctor;
private Long doctorId; private Long doctorId;
@@ -22,6 +23,13 @@ public class TicketSlotDTO {
private Long patientId; private Long patientId;
private String phone; private String phone;
private Integer orderStatus; private Integer orderStatus;
private Long orderId;
private String orderNo;
private String patientGender;
private Integer genderEnum;
private String idCard;
private String encounterId;
private String appointmentTime;
// 底层逻辑判断专属字段 // 底层逻辑判断专属字段
private Integer slotStatus; private Integer slotStatus;

View File

@@ -37,4 +37,21 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
AND p.delete_flag = '0' AND p.delete_flag = '0'
""") """)
int refreshPoolStats(@Param("poolId") Long poolId); int refreshPoolStats(@Param("poolId") Long poolId);
/**
* 签到时更新号源池统计:锁定数-1已预约数+1
*
* @param poolId 号源池ID
* @return 结果
*/
@Update("""
UPDATE adm_schedule_pool
SET locked_num = locked_num - 1,
booked_num = booked_num + 1,
update_time = NOW()
WHERE id = #{poolId}
AND locked_num > 0
AND delete_flag = '0'
""")
int updatePoolStatsOnCheckIn(@Param("poolId") Integer poolId);
} }

View File

@@ -6,6 +6,7 @@ import com.openhis.appointmentmanage.domain.ScheduleSlot;
import com.openhis.appointmentmanage.domain.TicketSlotDTO; import com.openhis.appointmentmanage.domain.TicketSlotDTO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.openhis.appointmentmanage.dto.TicketQueryDTO; import com.openhis.appointmentmanage.dto.TicketQueryDTO;
import java.util.Date;
import java.util.List; import java.util.List;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -30,6 +31,16 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
*/ */
int updateSlotStatus(@Param("slotId") Long slotId, @Param("status") Integer status); int updateSlotStatus(@Param("slotId") Long slotId, @Param("status") Integer status);
/**
* 更新槽位状态并记录签到时间
*
* @param slotId 槽位ID
* @param status 状态
* @param checkInTime 签到时间
* @return 结果
*/
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
/** /**
* 根据槽位ID查询所属号源池ID。 * 根据槽位ID查询所属号源池ID。
*/ */

View File

@@ -20,7 +20,7 @@ import lombok.experimental.Accessors;
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class Order extends HisBaseEntity { public class Order extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.AUTO)
@JsonSerialize(using = ToStringSerializer.class) @JsonSerialize(using = ToStringSerializer.class)
private Long id; private Long id;

View File

@@ -32,4 +32,30 @@ public interface OrderMapper extends BaseMapper<Order> {
int updateOrderStatusById(Long id, Integer status); int updateOrderStatusById(Long id, Integer status);
int updateOrderCancelInfoById(Long id, Date cancelTime, String cancelReason); int updateOrderCancelInfoById(Long id, Date cancelTime, String cancelReason);
/**
* 更新订单支付状态
*
* @param orderId 订单ID
* @param payStatus 支付状态0-未支付1-已支付
* @param payTime 支付时间
* @return 结果
*/
int updatePayStatus(@Param("orderId") Long orderId, @Param("payStatus") Integer payStatus, @Param("payTime") Date payTime);
/**
* 统计同一患者在同一科室、同一时段(上午/下午)内的有效预约订单数量
*
* @param patientId 患者ID
* @param departmentId 科室ID
* @param startTime 时段起始时间(含)
* @param endTime 时段结束时间(不含)
* @param statuses 订单状态集合(如 1=已预约,2=已取号)
* @return 数量
*/
int countPatientDeptOrdersInPeriod(@Param("patientId") Long patientId,
@Param("departmentId") Long departmentId,
@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("statuses") List<Integer> statuses);
} }

View File

@@ -88,6 +88,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
} }
Order order = new Order(); Order order = new Order();
order.setId(null); // 显式置空,确保触发数据库自增,避免 MP 预分配雪花 ID 的干扰
String orderNo = assignSeqUtil.getSeq(AssignSeqEnum.ORDER_NUM.getPrefix(), 18); String orderNo = assignSeqUtil.getSeq(AssignSeqEnum.ORDER_NUM.getPrefix(), 18);
order.setOrderNo(orderNo); order.setOrderNo(orderNo);

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.openhis.appointmentmanage.domain.AppointmentConfig; import com.openhis.appointmentmanage.domain.AppointmentConfig;
import com.openhis.appointmentmanage.service.IAppointmentConfigService; import com.openhis.appointmentmanage.service.IAppointmentConfigService;
import com.openhis.appointmentmanage.domain.TicketSlotDTO; import com.openhis.appointmentmanage.domain.TicketSlotDTO;
import com.openhis.appointmentmanage.domain.ScheduleSlot;
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper; import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper; import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
import com.openhis.clinical.domain.Order; import com.openhis.clinical.domain.Order;
@@ -27,6 +28,7 @@ import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters; import java.time.temporal.TemporalAdjusters;
import java.util.Date; import java.util.Date;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -52,6 +54,9 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
@Resource @Resource
private SchedulePoolMapper schedulePoolMapper; private SchedulePoolMapper schedulePoolMapper;
@Resource
private com.openhis.clinical.mapper.OrderMapper orderMapper;
@Resource @Resource
private IAppointmentConfigService appointmentConfigService; private IAppointmentConfigService appointmentConfigService;
@@ -146,7 +151,25 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
logger.debug("开始执行纯净打单路线slotId: {}, patientName: {}", slotId, dto.getPatientName()); logger.debug("开始执行纯净打单路线slotId: {}, patientName: {}", slotId, dto.getPatientName());
// 1. 直查物理大底座! // 1. 检查患者取消预约次数限制(应在预约挂号时限制,而非取消预约时)
Integer tenantId = dto.getTenant_id();
Long patientId = dto.getPatientId();
if (tenantId != null && patientId != null) {
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
if (config != null && config.getCancelAppointmentCount() != null
&& config.getCancelAppointmentCount() > 0) {
// 计算当前周期的起始时间
LocalDateTime startTime = calculatePeriodStartTime(config.getCancelAppointmentType());
// 统计已取消次数
long cancelledCount = orderService.countPatientCancellations(patientId, tenantId, startTime);
if (cancelledCount >= config.getCancelAppointmentCount()) {
String periodName = getPeriodName(config.getCancelAppointmentType());
throw new RuntimeException("由于您在" + periodName + "内累计取消预约已达" + cancelledCount + "次,触发系统限制,暂时无法在线预约,请联系分诊台或咨询客服。");
}
}
}
// 2. 直查物理大底座!
TicketSlotDTO slot = scheduleSlotMapper.selectTicketSlotById(slotId); TicketSlotDTO slot = scheduleSlotMapper.selectTicketSlotById(slotId);
if (slot == null) { if (slot == null) {
@@ -160,6 +183,28 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
throw new RuntimeException("该排班医生已停诊"); throw new RuntimeException("该排班医生已停诊");
} }
// 2.1 同一患者同一天/同一科室/同一时段(上午/下午)不可重复预约
if (dto.getPatientId() != null && slot.getDepartmentId() != null && slot.getScheduleDate() != null && slot.getExpectTime() != null) {
boolean isMorning = slot.getExpectTime().isBefore(LocalTime.NOON);
LocalDate scheduleDateForCheck = slot.getScheduleDate();
LocalDateTime periodStart = isMorning
? LocalDateTime.of(scheduleDateForCheck, LocalTime.MIN)
: LocalDateTime.of(scheduleDateForCheck, LocalTime.NOON);
LocalDateTime periodEnd = isMorning
? LocalDateTime.of(scheduleDateForCheck, LocalTime.NOON)
: LocalDateTime.of(scheduleDateForCheck.plusDays(1), LocalTime.MIN);
Date startTime = Date.from(periodStart.atZone(ZoneId.systemDefault()).toInstant());
Date endTime = Date.from(periodEnd.atZone(ZoneId.systemDefault()).toInstant());
// 预约去重以订单为准order_main因为预约成功会先落订单clinical_ticket 不一定在此链路写入
List<Integer> effectiveOrderStatuses = Arrays.asList(AppointmentOrderStatus.BOOKED, AppointmentOrderStatus.CHECKED_IN);
int exists = orderMapper.countPatientDeptOrdersInPeriod(dto.getPatientId(), slot.getDepartmentId(), startTime, endTime, effectiveOrderStatuses);
if (exists > 0) {
throw new RuntimeException("该患者已在当前科室该时段存在预约记录,不可重复预约");
}
}
// 原子抢占:避免并发下同一槽位被重复预约 // 原子抢占:避免并发下同一槽位被重复预约
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId); int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId);
if (lockRows <= 0) { if (lockRows <= 0) {
@@ -242,26 +287,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
throw new RuntimeException("当前号源没有可取消的预约订单"); throw new RuntimeException("当前号源没有可取消的预约订单");
} }
// 核心逻辑:获取订单信息并检查机构取消限制 // 直接执行取消,不再检查取消限制
Order latestOrder = orders.get(0); // 根据需求,取消限制应在预约挂号时检查,而非取消预约时
Integer tenantId = latestOrder.getTenantId();
Long patientId = latestOrder.getPatientId();
if (tenantId != null && patientId != null) {
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
if (config != null && config.getCancelAppointmentCount() != null
&& config.getCancelAppointmentCount() > 0) {
// 计算当前周期的起始时间
LocalDateTime startTime = calculatePeriodStartTime(config.getCancelAppointmentType());
// 统计已取消次数
long cancelledCount = orderService.countPatientCancellations(patientId, tenantId, startTime);
if (cancelledCount >= config.getCancelAppointmentCount()) {
String periodName = getPeriodName(config.getCancelAppointmentType());
throw new RuntimeException("您在" + periodName + "内已达到该机构取消预约次数上限(" + config.getCancelAppointmentCount() + "次),禁止取消");
}
}
}
for (Order order : orders) { for (Order order : orders) {
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约"); orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
} }
@@ -274,7 +301,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
} }
/** /**
* 取号 * 取号(签到)
* *
* @param slotId 槽位ID * @param slotId 槽位ID
* @return 结果 * @return 结果
@@ -287,7 +314,24 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
throw new RuntimeException("当前号源没有可取号的预约订单"); throw new RuntimeException("当前号源没有可取号的预约订单");
} }
Order latestOrder = orders.get(0); Order latestOrder = orders.get(0);
return orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN);
// 1. 更新订单状态为已取号,并更新支付状态和支付时间
orderService.updateOrderStatusById(latestOrder.getId(), AppointmentOrderStatus.CHECKED_IN);
// 更新支付状态为已支付,记录支付时间
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
// 2. 查询号源槽位信息
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
// 3. 更新号源槽位状态为已签到,记录签到时间
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN, new Date());
// 4. 更新号源池统计:锁定数-1已预约数+1
if (slot != null && slot.getPoolId() != null) {
schedulePoolMapper.updatePoolStatsOnCheckIn(slot.getPoolId());
}
return 1;
} }
/** /**
@@ -309,7 +353,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
orderService.cancelAppointmentOrder(order.getId(), "医生停诊"); orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
} }
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.STOPPED); int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
if (updated > 0) { if (updated > 0) {
refreshPoolStatsBySlotId(slotId); refreshPoolStatsBySlotId(slotId);
} }

View File

@@ -17,7 +17,7 @@ import java.util.Date;
* @date 2025-06-03 * @date 2025-06-03
*/ */
@Data @Data
@TableName("doc_ital_signs") @TableName("doc_vital_signs")
@Accessors(chain = true) @Accessors(chain = true)
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class VitalSigns extends HisBaseEntity { public class VitalSigns extends HisBaseEntity {

View File

@@ -4,10 +4,46 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper"> <mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
<!-- 统一状态值(兼容数字/英文字符串存储),输出 Integer避免 resultType 映射 NumberFormatException -->
<sql id="slotStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', s.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
WHEN LOWER(CONCAT('', s.status)) IN ('3', 'locked') THEN 3
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'checked', 'checked_in', 'checkin') THEN 4
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<sql id="orderStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', o.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', o.status)) IN ('2', 'checked', 'checked_in', 'checkin') THEN 2
WHEN LOWER(CONCAT('', o.status)) IN ('3', 'cancelled', 'canceled') THEN 3
WHEN LOWER(CONCAT('', o.status)) IN ('4', 'returned') THEN 4
ELSE NULL
END
</sql>
<sql id="poolStatusNormExpr">
CASE
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
WHEN LOWER(CONCAT('', p.status)) IN ('1', 'booked') THEN 1
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
WHEN LOWER(CONCAT('', p.status)) IN ('3', 'locked') THEN 3
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'checked', 'checked_in', 'checkin') THEN 4
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
ELSE NULL
END
</sql>
<!-- 注意这里的 resultType 指向了您刚建好的 DTO 实体类 --> <!-- 注意这里的 resultType 指向了您刚建好的 DTO 实体类 -->
<select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectAllTicketSlots" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.dept_id AS departmentId, p.dept_id AS departmentId,
@@ -16,12 +52,12 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, <include refid="orderStatusNormExpr" /> AS orderStatus,
s.status AS slotStatus, <include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -39,7 +75,7 @@
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
@@ -56,6 +92,7 @@
<select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectTicketSlotById" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
@@ -66,12 +103,18 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, o.id AS orderId,
s.status AS slotStatus, o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -87,15 +130,20 @@
patient_name, patient_name,
medical_card, medical_card,
phone, phone,
id,
order_no,
gender,
appointment_time,
status status
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
) o ON o.slot_id = s.id ) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
WHERE WHERE
s.id = #{id} s.id = #{id}
</select> </select>
@@ -115,7 +163,7 @@
UPDATE adm_schedule_slot UPDATE adm_schedule_slot
SET SET
status = #{status}, status = #{status},
<if test="status == 0"> <if test="status != null and '0'.equals(status.toString())">
order_id = NULL, order_id = NULL,
</if> </if>
update_time = now() update_time = now()
@@ -124,11 +172,25 @@
AND delete_flag = '0' AND delete_flag = '0'
</update> </update>
<update id="updateSlotStatusAndCheckInTime">
UPDATE adm_schedule_slot
SET
status = #{status},
check_in_time = #{checkInTime},
update_time = NOW()
WHERE
id = #{slotId}
AND delete_flag = '0'
</update>
<select id="selectPoolIdBySlotId" resultType="java.lang.Long"> <select id="selectPoolIdBySlotId" resultType="java.lang.Long">
SELECT pool_id SELECT
FROM adm_schedule_slot pool_id
WHERE id = #{slotId} FROM
AND delete_flag = '0' adm_schedule_slot
WHERE
id = #{slotId}
AND delete_flag = '0'
</select> </select>
<update id="bindOrderToSlot"> <update id="bindOrderToSlot">
@@ -145,6 +207,7 @@
<select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO"> <select id="selectTicketSlotsPage" resultType="com.openhis.appointmentmanage.domain.TicketSlotDTO">
SELECT SELECT
s.id AS slotId, s.id AS slotId,
s.seq_no AS seqNo,
p.schedule_id AS scheduleId, p.schedule_id AS scheduleId,
p.doctor_name AS doctor, p.doctor_name AS doctor,
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
@@ -155,12 +218,18 @@
o.patient_name AS patientName, o.patient_name AS patientName,
o.medical_card AS medicalCard, o.medical_card AS medicalCard,
o.phone AS phone, o.phone AS phone,
o.status AS orderStatus, o.id AS orderId,
s.status AS slotStatus, o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
<include refid="slotStatusNormExpr" /> AS slotStatus,
s.expect_time AS expectTime, s.expect_time AS expectTime,
p.schedule_date AS scheduleDate, p.schedule_date AS scheduleDate,
d.reg_type AS regType, d.reg_type AS regType,
p.status AS poolStatus, <include refid="poolStatusNormExpr" /> AS poolStatus,
p.stop_reason AS stopReason, p.stop_reason AS stopReason,
d.is_stopped AS isStopped d.is_stopped AS isStopped
FROM FROM
@@ -176,15 +245,20 @@
patient_name, patient_name,
medical_card, medical_card,
phone, phone,
id,
order_no,
gender,
appointment_time,
status status
FROM FROM
order_main order_main
WHERE WHERE
status IN (1, 2) LOWER(CONCAT('', status)) IN ('1', '2', '4', 'booked', 'checked', 'checked_in', 'checkin', 'returned')
ORDER BY ORDER BY
slot_id, slot_id,
create_time DESC create_time DESC
) o ON o.slot_id = s.id ) o ON o.slot_id = s.id
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
<where> <where>
p.delete_flag = '0' p.delete_flag = '0'
AND s.delete_flag = '0' <!-- 1. 按日期查 --> AND s.delete_flag = '0' <!-- 1. 按日期查 -->
@@ -225,35 +299,49 @@
<!-- 5. 核心:解答您疑问的 4 种业务状态的复合查询! --> <!-- 5. 核心:解答您疑问的 4 种业务状态的复合查询! -->
<if test="query.status != null and query.status != '' and query.status != 'all'"> <if test="query.status != null and query.status != '' and query.status != 'all'">
<choose> <choose>
<when test="query.status == 'unbooked'"> <when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
AND s.status = 0 AND <include refid="slotStatusNormExpr" /> = 0
AND ( AND (
d.is_stopped IS NULL d.is_stopped IS NULL
OR d.is_stopped = FALSE OR d.is_stopped = FALSE
) )
</when> </when>
<when test="query.status == 'booked'"> <when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
AND s.status = 1 AND <include refid="slotStatusNormExpr" /> = 1
AND o.status = 1 AND <include refid="orderStatusNormExpr" /> = 1
AND ( AND (
d.is_stopped IS NULL d.is_stopped IS NULL
OR d.is_stopped = FALSE OR d.is_stopped = FALSE
) )
</when> </when>
<when test="query.status == 'checked'"> <when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
AND s.status = 1 AND (
AND o.status = 2 <include refid="slotStatusNormExpr" /> = 4
OR (
<include refid="slotStatusNormExpr" /> = 1
AND <include refid="orderStatusNormExpr" /> = 2
)
)
AND ( AND (
d.is_stopped IS NULL d.is_stopped IS NULL
OR d.is_stopped = FALSE OR d.is_stopped = FALSE
) )
</when> </when>
<when test="query.status == 'cancelled'"> <when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
AND ( AND (
s.status = 2 <include refid="slotStatusNormExpr" /> = 2
OR d.is_stopped = TRUE OR d.is_stopped = TRUE
) )
</when> </when>
<when test="'returned'.equals(query.status) or '已退号'.equals(query.status)">
AND (
<include refid="slotStatusNormExpr" /> = 5
OR <include refid="orderStatusNormExpr" /> = 4
)
</when>
<otherwise>
AND 1 = 2
</otherwise>
</choose> </choose>
</if> </if>
</where> </where>
@@ -266,9 +354,22 @@
SELECT SELECT
p.doctor_id AS doctorId, p.doctor_id AS doctorId,
p.doctor_name AS doctorName, p.doctor_name AS doctorName,
COALESCE(SUM(GREATEST(COALESCE(p.total_quota, 0) - COALESCE(p.booked_num, 0) - COALESCE(p.locked_num, 0), 0)), 0) AS available, COALESCE(
SUM(
GREATEST(
COALESCE(p.total_quota, 0) - COALESCE(p.booked_num, 0) - COALESCE(p.locked_num, 0),
0
)
),
0
) AS available,
CASE CASE
WHEN MAX(CASE WHEN d.reg_type = 1 THEN 1 ELSE 0 END) = 1 THEN 'expert' WHEN MAX(
CASE
WHEN d.reg_type = 1 THEN 1
ELSE 0
END
) = 1 THEN 'expert'
ELSE 'general' ELSE 'general'
END AS ticketType END AS ticketType
FROM FROM
@@ -290,7 +391,10 @@
AND d.reg_type = 1 AND d.reg_type = 1
</when> </when>
<when test="query.type == 'general'"> <when test="query.type == 'general'">
AND (d.reg_type != 1 OR d.reg_type IS NULL) AND (
d.reg_type != 1
OR d.reg_type IS NULL
)
</when> </when>
</choose> </choose>
</if> </if>

View File

@@ -127,7 +127,7 @@
select * from order_main where slot_id = #{slotId} and status = 1 select * from order_main where slot_id = #{slotId} and status = 1
</select> </select>
<insert id="insertOrder" parameterType="com.openhis.clinical.domain.Order"> <insert id="insertOrder" parameterType="com.openhis.clinical.domain.Order" useGeneratedKeys="true" keyProperty="id">
insert into order_main insert into order_main
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderNo != null and orderNo != ''">order_no,</if> <if test="orderNo != null and orderNo != ''">order_no,</if>
@@ -217,6 +217,23 @@
where id = #{id} where id = #{id}
</update> </update>
<select id="countPatientDeptOrdersInPeriod" resultType="int">
select count(*)
from order_main
<where>
and patient_id = #{patientId}
and department_id = #{departmentId}
and appointment_time &gt;= #{startTime}
and appointment_time &lt; #{endTime}
<if test="statuses != null and statuses.size() &gt; 0">
and status in
<foreach item="s" collection="statuses" open="(" separator="," close=")">
#{s}
</foreach>
</if>
</where>
</select>
<update id="updateOrderStatusById"> <update id="updateOrderStatusById">
update order_main set status = #{status} where id = #{id} update order_main set status = #{status} where id = #{id}
</update> </update>
@@ -225,6 +242,12 @@
update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id} update order_main set status = 3, cancel_time = #{cancelTime}, cancel_reason = #{cancelReason} where id = #{id}
</update> </update>
<update id="updatePayStatus">
update order_main
set pay_status = #{payStatus}, pay_time = #{payTime}, update_time = NOW()
where id = #{orderId}
</update>
<delete id="deleteOrderById"> <delete id="deleteOrderById">
delete from order_main where id = #{id} delete from order_main where id = #{id}
</delete> </delete>

View File

@@ -29,10 +29,17 @@ export function formatDateStr(cellValue, format = 'YYYY-MM-DD HH:mm:ss') {
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0');
// 支持不带前导零的格式
const monthNoPad = String(date.getMonth() + 1);
const dayNoPad = String(date.getDate());
return format return format
.replace('YYYY', year) .replace('YYYY', year)
.replace('MM', month) .replace('MM', month)
.replace('DD', day) .replace('DD', day)
.replace('M/', monthNoPad + '/') // 支持 M/ 格式
.replace('D ', dayNoPad + ' ') // 支持 D 格式(后跟空格)
.replace('/D', '/' + dayNoPad) // 支持 /D 格式
.replace('HH', hours) .replace('HH', hours)
.replace('mm', minutes) .replace('mm', minutes)
.replace('ss', seconds); .replace('ss', seconds);

View File

@@ -162,3 +162,61 @@ export const STATUS = {
NORMAL: '0', // 正常/启用 NORMAL: '0', // 正常/启用
DISABLE: '1' // 停用 DISABLE: '1' // 停用
}; };
/**
* 号源槽位状态(与后端 CommonConstants.SlotStatus 保持一致)
* adm_schedule_slot.status 字段
*/
export const SlotStatus = {
/** 可用 / 待预约 */
AVAILABLE: 0,
/** 已预约 */
BOOKED: 1,
/** 已取消 / 已停诊 */
CANCELLED: 2,
/** 已锁定 */
LOCKED: 3,
/** 已签到 / 已取号 */
CHECKED_IN: 4,
};
/**
* 号源槽位状态说明信息
*/
export const SlotStatusDescriptions = {
0: '未预约',
1: '已预约',
2: '已停诊',
3: '已锁定',
4: '已取号',
};
/**
* 号源槽位状态对应的CSS类名
*/
export const SlotStatusClassMap = {
'未预约': 'status-unbooked',
'已预约': 'status-booked',
'已取号': 'status-checked',
'已停诊': 'status-cancelled',
'已取消': 'status-cancelled',
'已锁定': 'status-locked',
};
/**
* 获取号源槽位状态的说明
* @param {number} value - 状态值
* @returns {string} - 说明信息
*/
export function getSlotStatusDescription(value) {
return SlotStatusDescriptions[value] || '未知状态';
}
/**
* 获取号源槽位状态对应的CSS类名
* @param {string} status - 状态说明
* @returns {string} - CSS类名
*/
export function getSlotStatusClass(status) {
return SlotStatusClassMap[status] || 'status-unbooked';
}

View File

@@ -37,6 +37,7 @@
<option value="booked">已预约</option> <option value="booked">已预约</option>
<option value="checked">已取号</option> <option value="checked">已取号</option>
<option value="cancelled">已停诊</option> <option value="cancelled">已停诊</option>
<option value="returned">已退号</option>
</select> </select>
</div> </div>
<div id="patientSearch" class="patient-search"> <div id="patientSearch" class="patient-search">
@@ -105,7 +106,7 @@
<div class="ticket-grid" v-if="filteredTickets.length > 0"> <div class="ticket-grid" v-if="filteredTickets.length > 0">
<div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)"> <div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)">
<!-- 序号放在最右侧 --> <!-- 序号放在最右侧 -->
<div class="ticket-index">{{ index + 1 }}</div> <div class="ticket-index">{{ item.seqNo != null ? item.seqNo : index + 1 }}</div>
<!-- 1.时间 --> <!-- 1.时间 -->
<div class="ticket-id-time">{{ item.dateTime }}</div> <div class="ticket-id-time">{{ item.dateTime }}</div>
<!-- 2. 状态标签 --> <!-- 2. 状态标签 -->
@@ -125,7 +126,7 @@
<div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div> <div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div>
<!-- 7. 已预约患者信息 --> <!-- 7. 已预约患者信息 -->
<div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient"> <div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient">
{{ item.patientName }}({{ item.patientId }}) {{ item.patientName }}({{ item.patientId }},{{ getGenderText(item.gender || item.patientGender) }})
</div> </div>
<div class="ticket-actions"> <div class="ticket-actions">
<button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }"> <button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }">
@@ -194,7 +195,8 @@
> >
<td>{{ index + 1 }}</td> <td>{{ index + 1 }}</td>
<td>{{ patient.name }}</td> <td>{{ patient.name }}</td>
<td>{{ patient.id || patient.medicalCard }}</td> <td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td>
<td>{{ patient.identifierNo }}</td>
<td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td> <td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td>
<td>{{ patient.idCard }}</td> <td>{{ patient.idCard }}</td>
<td>{{ patient.phone }}</td> <td>{{ patient.phone }}</td>
@@ -254,6 +256,7 @@ const STATUS_CLASS_MAP = {
'未预约': 'status-unbooked', '未预约': 'status-unbooked',
'已预约': 'status-booked', '已预约': 'status-booked',
'已取号': 'status-checked', '已取号': 'status-checked',
'已退号': 'status-returned',
'已停诊': 'status-cancelled', '已停诊': 'status-cancelled',
'已取消': 'status-cancelled' '已取消': 'status-cancelled'
}; };
@@ -310,6 +313,37 @@ export default {
} }
}, },
computed: { computed: {
// 全部号源经过日期过期过滤后的数据(不按医生过滤,不按患者搜索过滤),用于统计医生余号
allTicketsForDoctorCount() {
let filtered = [...this.tickets];
// 🎯 只过滤过期号源,不按医生过滤,不按患者搜索过滤
// 这样余号统计总是基于该日期下所有号源,得到正确的每个医生余号
const now = new Date();
filtered = filtered.filter(ticket => {
// dateTime 格式示例:"2024-01-01 08:00-09:00"
const parts = (ticket.dateTime || '').split(' ');
if (parts.length < 2) return true; // 如果格式不正确,保留显示
const dateStr = parts[0];
const timeRangeStr = parts[1];
if (!dateStr || !timeRangeStr) return true;
// 提取开始时间
const startTimeStr = timeRangeStr.split('-')[0]; // "08:00"
if (!startTimeStr) return true;
// 构建号源开始时间的完整 Date 对象
const ticketStartStr = `${dateStr} ${startTimeStr}`;
const ticketStart = new Date(ticketStartStr);
// 只显示开始时间晚于当前时间的号源
return ticketStart > now;
});
return filtered;
},
filteredDoctors() { filteredDoctors() {
let filtered = [...this.doctors]; let filtered = [...this.doctors];
@@ -327,10 +361,96 @@ export default {
); );
} }
// 🎯 实时更新余号数量:统计该医生当前筛选条件下剩余可预约(未预约 + 未过期)号源数量
// 使用全部未过期号源统计(不按选中医生过滤),这样所有医生余号都正确显示
const availableCountMap = {};
this.allTicketsForDoctorCount.forEach(ticket => {
const doctorId = String(ticket.doctorId || ticket.doctor_id);
if (!availableCountMap[doctorId]) {
availableCountMap[doctorId] = 0;
}
// 只有未预约的号源才算作可预约余号
if (ticket.status === '未预约') {
availableCountMap[doctorId]++;
}
});
// 更新每个医生的余号数量
filtered = filtered.map(doctor => {
const actualAvailable = availableCountMap[String(doctor.id)] || 0;
return {
...doctor,
available: actualAvailable
};
});
return filtered; return filtered;
}, },
// 过滤并排序后的完整号源列表(用于右侧显示)
filteredAndSortedTickets() {
// 从已经过滤掉过期的全部数据开始
let filtered = [...this.allTicketsForDoctorCount];
// 🎯 根据选中的医生过滤(右侧只显示选中医生的号源)
if (this.selectedDoctorId) {
const doctorIdStr = String(this.selectedDoctorId);
filtered = filtered.filter(ticket => {
const ticketDoctorId = String(ticket.doctorId || ticket.doctor_id || '');
return ticketDoctorId === doctorIdStr;
});
}
// 🎯 根据患者搜索条件过滤
if (this.patientName?.trim()) {
const keyword = this.patientName.trim().toLowerCase();
filtered = filtered.filter(ticket =>
(ticket.patientName || '').toLowerCase().includes(keyword)
);
}
if (this.patientCard?.trim()) {
const keyword = this.patientCard.trim().toLowerCase();
filtered = filtered.filter(ticket =>
(ticket.patientId || '').toLowerCase().includes(keyword) ||
(ticket.medicalCard || '').toLowerCase().includes(keyword)
);
}
if (this.patientPhone?.trim()) {
const keyword = this.patientPhone.trim().toLowerCase();
filtered = filtered.filter(ticket =>
(ticket.phone || '').toLowerCase().includes(keyword)
);
}
// 🎯 按开始时间升序排序 → 较早的号源排在前面
filtered.sort((a, b) => {
const getStartTime = (ticket) => {
const parts = (ticket.dateTime || '').split(' ');
if (parts.length < 2) return new Date(0).getTime();
const dateStr = parts[0];
const timeRangeStr = parts[1];
const startTimeStr = (timeRangeStr || '').split('-')[0];
if (!startTimeStr) return new Date(0).getTime();
const ticketStartStr = `${dateStr} ${startTimeStr}`;
return new Date(ticketStartStr).getTime();
};
const timeA = getStartTime(a);
const timeB = getStartTime(b);
return timeA - timeB;
});
return filtered;
},
// 🎯 分页:按照用户选择的每页条数分页,返回当前页的数据
filteredTickets() { filteredTickets() {
return [...this.tickets]; const filtered = this.filteredAndSortedTickets;
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
return filtered.slice(startIndex, endIndex);
},
// 更新总条数为过滤后的实际条数,分页自动处理
totalTickets() {
return this.filteredAndSortedTickets.length;
}, },
hasSearchCriteria() { hasSearchCriteria() {
return !!this.patientKeyword?.trim(); return !!this.patientKeyword?.trim();
@@ -340,6 +460,8 @@ export default {
selectDoctor(doctorId) { selectDoctor(doctorId) {
this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId; this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId;
this.currentPage = 1; this.currentPage = 1;
// 🔧 BugFix: 选择医生后不改变医生列表,余号计算基于 filteredAndSortedTickets 已经正确过滤
// 只需要重新获取号源,医生列表保持不变,余号计算会自动正确
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {}); this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
}, },
onTypeChange() { onTypeChange() {
@@ -364,14 +486,23 @@ export default {
this.searchPatients(); this.searchPatients();
}, },
getPatientUniqueId(patient, index = null) { getPatientUniqueId(patient, index = null) {
return patient.idCard || patient.medicalCard || patient.id || (index !== null ? `temp_${index}` : null); return patient.id || patient.patientId || patient.identifierNo || patient.medicalCard || patient.idCard ||
(index !== null ? `temp_${index}` : null);
}, },
matchPatientKeyword(patient, keyword) { matchPatientKeyword(patient, keyword) {
const normalizedKeyword = String(keyword || '').trim().toLowerCase(); const normalizedKeyword = String(keyword || '').trim().toLowerCase();
if (!normalizedKeyword) { if (!normalizedKeyword) {
return true; return true;
} }
const fields = [patient.name, patient.id, patient.medicalCard, patient.idCard, patient.phone]; const fields = [
patient.name,
patient.id,
patient.patientId,
patient.identifierNo,
patient.medicalCard,
patient.idCard,
patient.phone
];
return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword)); return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword));
}, },
searchPatients() { searchPatients() {
@@ -394,6 +525,8 @@ export default {
const rowKey = this.getPatientUniqueId(patient, index); const rowKey = this.getPatientUniqueId(patient, index);
return { return {
...patient, ...patient,
// 统一口径:预约场景的就诊卡号使用患者标识表中的 identifierNo
medicalCard: patient.identifierNo || patient.medicalCard || '',
_rowKey: rowKey _rowKey: rowKey
}; };
}); });
@@ -494,6 +627,7 @@ export default {
this.tickets[ticketIndex].patientName = null; this.tickets[ticketIndex].patientName = null;
this.tickets[ticketIndex].patientId = null; this.tickets[ticketIndex].patientId = null;
this.tickets[ticketIndex].patientGender = null; this.tickets[ticketIndex].patientGender = null;
this.tickets[ticketIndex].gender = null;
this.tickets[ticketIndex].medicalCard = null; this.tickets[ticketIndex].medicalCard = null;
this.tickets[ticketIndex].phone = null; this.tickets[ticketIndex].phone = null;
} }
@@ -554,12 +688,22 @@ export default {
try { try {
const userStore = useUserStore(); const userStore = useUserStore();
const patientPrimaryId = this.selectedPatient.id || this.selectedPatient.patientId;
const medicalCard = this.selectedPatient.identifierNo || this.selectedPatient.medicalCard;
if (!patientPrimaryId) {
ElMessage.error('患者ID缺失无法预约请重新选择患者');
return;
}
if (!medicalCard) {
ElMessage.error('就诊卡号缺失,无法预约,请先维护患者就诊卡号');
return;
}
const appointmentData = { const appointmentData = {
ticketId: this.currentTicket.slot_id, ticketId: this.currentTicket.slot_id,
slotId: this.currentTicket.slot_id, slotId: this.currentTicket.slot_id,
patientId: this.selectedPatient.id || this.selectedPatient.idCard || this.selectedPatient.medicalCard, patientId: patientPrimaryId,
patientName: this.selectedPatient.name, patientName: this.selectedPatient.name,
medicalCard: this.selectedPatient.medicalCard || this.selectedPatient.id, medicalCard,
phone: this.selectedPatient.phone, phone: this.selectedPatient.phone,
gender: this.getGenderValueForBackend(this.selectedPatient), gender: this.getGenderValueForBackend(this.selectedPatient),
fee: Number(this.currentTicket.fee) || 0, fee: Number(this.currentTicket.fee) || 0,
@@ -578,8 +722,9 @@ export default {
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
this.tickets[ticketIndex].status = '已预约'; this.tickets[ticketIndex].status = '已预约';
this.tickets[ticketIndex].patientName = this.selectedPatient.name; this.tickets[ticketIndex].patientName = this.selectedPatient.name;
this.tickets[ticketIndex].patientId = this.selectedPatient.medicalCard || this.selectedPatient.id; this.tickets[ticketIndex].patientId = medicalCard;
this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex; this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex;
this.tickets[ticketIndex].gender = this.getGenderText(this.tickets[ticketIndex].patientGender);
} }
this.closePatientSelectModal(); this.closePatientSelectModal();
@@ -601,9 +746,9 @@ export default {
}, },
// 获取性别文本 // 获取性别文本
getGenderText(genderValue) { getGenderText(genderValue) {
// 如果值为null或undefined返回'-' // 如果值为null或undefined返回"未知"
if (genderValue === null || genderValue === undefined) { if (genderValue === null || genderValue === undefined) {
return '-'; return '未知';
} }
// 将值转换为字符串进行比较 // 将值转换为字符串进行比较
@@ -623,8 +768,8 @@ export default {
return '女'; return '女';
} }
// 如果都不是,返回原值或'-' // 如果都不是,返回"未知"
return '-'; return '未知';
}, },
// 获取用于后端的性别值 // 获取用于后端的性别值
getGenderValueForBackend(patient) { getGenderValueForBackend(patient) {
@@ -657,21 +802,20 @@ export default {
return STATUS_CLASS_MAP[status] || 'status-unbooked'; return STATUS_CLASS_MAP[status] || 'status-unbooked';
}, },
buildQueryParams(page = this.currentPage) { buildQueryParams(page = this.currentPage) {
const doctorId =
this.selectedDoctorId === null || this.selectedDoctorId === undefined || this.selectedDoctorId === ''
? null
: String(this.selectedDoctorId);
return { return {
date: this.selectedDate, date: this.selectedDate,
status: this.selectedStatus === 'all' ? null : this.selectedStatus, status: null, // 状态过滤在前端做
type: this.selectedType === 'all' ? null : this.selectedType, type: this.selectedType === 'all' ? null : this.selectedType,
department: this.selectedDepartment === 'all' ? null : this.selectedDepartment, department: this.selectedDepartment === 'all' ? null : this.selectedDepartment,
doctorId, doctorId: null, // 🎯 关键:永远不传 doctorId 给后端,后端返回全量数据
name: this.patientName?.trim() || null, // 医生过滤、状态过滤、患者搜索都在前端做,才能保证所有医生余号统计正确
card: this.patientCard?.trim() || null, name: null,
phone: this.patientPhone?.trim() || null, card: null,
page, phone: null,
limit: this.pageSize // 🎯 获取全量数据到前端,由前端做过滤和分页,保证余号统计总是正确
// 号源数量每个日期每个科室不会太多,全量获取可行
page: 1,
limit: 10000
}; };
}, },
buildDoctorQueryParams() { buildDoctorQueryParams() {
@@ -699,14 +843,34 @@ export default {
if (!payload) { if (!payload) {
this.tickets = []; this.tickets = [];
this.allTickets = []; this.allTickets = [];
this.totalTickets = 0;
return; return;
} }
const records = payload.list || payload.records || []; let records = payload.list || payload.records || [];
const total = Number(payload.total); // 获取全量数据,应用状态过滤后保存所有数据到 tickets
// 过滤、余号统计、分页都由前端完成,保证余号计算正确
records = this.applyStatusFilter(records);
this.tickets = [...records]; this.tickets = [...records];
this.allTickets = [...records]; this.allTickets = [...records];
this.totalTickets = Number.isFinite(total) ? total : this.tickets.length; },
applyStatusFilter(records = []) {
if (!Array.isArray(records) || records.length === 0) {
return [];
}
if (!this.selectedStatus || this.selectedStatus === 'all') {
return records;
}
const statusMap = {
unbooked: ['未预约'],
booked: ['已预约'],
checked: ['已取号'],
cancelled: ['已停诊', '已取消'],
returned: ['已退号']
};
const matchedStatusList = statusMap[this.selectedStatus] || [];
if (matchedStatusList.length === 0) {
return records;
}
return records.filter(item => matchedStatusList.includes(item?.status));
}, },
updateDoctorsListFromApi(doctorResponse) { updateDoctorsListFromApi(doctorResponse) {
let doctorList = []; let doctorList = [];
@@ -1376,6 +1540,11 @@ export default {
color: #52c41a; color: #52c41a;
} }
.status-returned {
background-color: #fff7e6;
color: #d46b08;
}
.status-cancelled { .status-cancelled {
background-color: #fff1f0; background-color: #fff1f0;
color: #ff4d4f; color: #ff4d4f;

View File

@@ -7,6 +7,7 @@
<div style="display: flex; align-items: center; width: 100%"> <div style="display: flex; align-items: center; width: 100%">
<span style="font-size: 16px; font-weight: bold; margin-right: 20px;">门诊挂号</span> <span style="font-size: 16px; font-weight: bold; margin-right: 20px;">门诊挂号</span>
<div style="flex: 1; display: flex; justify-content: center; align-items: center;"> <div style="flex: 1; display: flex; justify-content: center; align-items: center;">
<el-button type="success" icon="Check" @click="handleCheckIn" size="small">预约签到</el-button>
<el-button type="primary" icon="Document" @click="goToPatientRecord" size="small">档案</el-button> <el-button type="primary" icon="Document" @click="goToPatientRecord" size="small">档案</el-button>
<el-button type="primary" icon="Plus" @click="handleAddPatient" size="small">新建</el-button> <el-button type="primary" icon="Plus" @click="handleAddPatient" size="small">新建</el-button>
<el-button type="primary" plain icon="Search" @click="handleSearch" size="small">查询</el-button> <el-button type="primary" plain icon="Search" @click="handleSearch" size="small">查询</el-button>
@@ -15,7 +16,7 @@
<el-button type="primary" plain @click="handleReadCard('03')" size="small">医保卡</el-button> <el-button type="primary" plain @click="handleReadCard('03')" size="small">医保卡</el-button>
<el-button type="warning" plain icon="CircleClose" @click="handleClear" size="small">清空</el-button> <el-button type="warning" plain icon="CircleClose" @click="handleClear" size="small">清空</el-button>
<el-button type="primary" icon="Plus" @click="handleAdd" size="small">保存挂号</el-button> <el-button type="primary" icon="Plus" @click="handleAdd" size="small">保存挂号</el-button>
<el-button type="success" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button> <el-button type="info" icon="Printer" @click="handleReprint" size="small">补打挂号</el-button>
</div> </div>
</div> </div>
</template> </template>
@@ -615,6 +616,74 @@
} }
" "
/> />
<!-- 预约签到患者选择弹窗 -->
<el-dialog
v-model="showCheckInPatientModal"
title="请选择预约的患者"
width="1200px"
:close-on-click-modal="false"
>
<div style="margin-bottom: 20px; display: flex; gap: 10px;">
<el-input
v-model="checkInSearchKey"
placeholder="输入患者姓名回车查询"
style="width: 400px"
@keyup.enter="loadCheckInPatientList"
/>
<el-button type="primary" @click="loadCheckInPatientList">查询</el-button>
</div>
<el-table
v-loading="checkInLoading"
:data="checkInPatientList"
border
style="width: 100%"
@row-click="selectRow"
highlight-current-row
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientId" label="就诊卡号" width="120" align="center" />
<el-table-column prop="patientName" label="姓名" width="120" align="center">
<template #default="scope">
<span style="color: #ff4d4f">{{ scope.row.patientName }}</span>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="80" align="center" />
<el-table-column label="证件类型" width="150" align="center">
<template #default>居民身份证</template>
</el-table-column>
<el-table-column prop="idCard" label="证件号码" width="200" align="center" />
<el-table-column prop="phone" label="手机号码" width="150" align="center" />
<el-table-column label="号源类型" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.ticketType === 'expert' ? 'danger' : 'success'">
{{ scope.row.ticketType === 'expert' ? '专家号' : '普通号' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fee" label="预约金额" width="100" align="center">
<template #default="scope">
<span style="font-weight: bold; color: #f5222d">{{ scope.row.fee }}</span>
</template>
</el-table-column>
<el-table-column prop="dateTime" label="就诊时间" width="180" align="center" />
</el-table>
<div style="margin-top: 20px; display: flex; justify-content: space-between; align-items: center;">
<el-pagination
v-model:current-page="checkInPage"
v-model:page-size="checkInLimit"
:total="checkInTotal"
layout="prev, pager, next"
@current-change="loadCheckInPatientList"
/>
<div class="dialog-footer">
<el-button @click="showCheckInPatientModal = false">取消</el-button>
<el-button type="primary" @click="confirmCheckIn" :disabled="!selectedCheckInPatient">确定</el-button>
</div>
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -632,7 +701,8 @@ import {
returnRegister, returnRegister,
updatePatientPhone, updatePatientPhone,
} from './components/outpatientregistration'; } from './components/outpatientregistration';
import {invokeYbPlugin5000, invokeYbPlugin5001} from '@/api/public'; import { listTicket, checkInTicket } from '@/api/appoinmentmanage/ticket';
import { invokeYbPlugin5000, invokeYbPlugin5001 } from '@/api/public';
import patientInfoDialog from './components/patientInfoDialog'; import patientInfoDialog from './components/patientInfoDialog';
import PatientAddDialog from './components/patientAddDialog'; import PatientAddDialog from './components/patientAddDialog';
import patientList from './components/patientList'; import patientList from './components/patientList';
@@ -644,7 +714,7 @@ import {handleColor} from '@/utils/his';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import {formatDateStr} from '@/utils/index'; import {formatDateStr} from '@/utils/index';
import {isValidCNPhoneNumber} from '../../../utils/validate'; import {isValidCNPhoneNumber} from '../../../utils/validate';
import {ElMessage} from 'element-plus'; import {ElMessage, ElMessageBox} from 'element-plus';
import {hiprint} from 'vue-plugin-hiprint'; import {hiprint} from 'vue-plugin-hiprint';
import outpatientRegistrationTemplate from '@/components/Print/OutpatientRegistration.json'; import outpatientRegistrationTemplate from '@/components/Print/OutpatientRegistration.json';
@@ -687,14 +757,25 @@ const ybTypeRef = ref(null);
const openDialog = ref(false); const openDialog = ref(false);
const openRefundDialog = ref(false); const openRefundDialog = ref(false);
const openReprintDialog = ref(false); const openReprintDialog = ref(false);
// 预约签到相关变量
const showCheckInPatientModal = ref(false);
const checkInPatientList = ref([]);
const selectedCheckInPatient = ref(null);
const totalAmount = ref(0); const totalAmount = ref(0);
const chargeItemIdList = ref([]); const chargeItemIdList = ref([]);
const chrgBchnoList = ref([]); const chrgBchnoList = ref([]);
const paymentId = ref(''); const paymentId = ref('');
const loadingText = ref(''); const loadingText = ref('');
const checkInSearchKey = ref('');
const checkInPage = ref(1);
const checkInLimit = ref(10);
const checkInTotal = ref(0);
const checkInLoading = ref(false);
const registerInfo = ref({}); // 原挂号记录信息 const registerInfo = ref({}); // 原挂号记录信息
const queryType = ref('all'); // 查询类型all-全部, normal-正常挂号, returned-退号记录 const queryType = ref('all'); // 查询类型all-全部, normal-正常挂号, returned-退号记录
const guardianAgeConfig = ref(''); // 监护人规定年龄配置 const guardianAgeConfig = ref(''); // 监护人规定年龄配置
const currentSlotId = ref(null); // 当前预约签到的号源ID
// 使用 ref 定义查询所得用户信息数据 // 使用 ref 定义查询所得用户信息数据
const patientInfoList = ref(undefined); const patientInfoList = ref(undefined);
@@ -1584,6 +1665,189 @@ function handleReprint() {
openReprintDialog.value = true; openReprintDialog.value = true;
} }
/** 预约签到 - 打开患者选择弹窗 */
function handleCheckIn() {
// 打开患者选择弹窗,显示已预约但未签到的患者列表
showCheckInPatientModal.value = true;
// 加载已预约未签到的患者列表
loadCheckInPatientList();
}
/** 加载预约签到患者列表 */
function loadCheckInPatientList() {
checkInLoading.value = true;
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
listTicket({
date: today,
status: 'booked',
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
page: checkInPage.value,
limit: checkInLimit.value
}).then(res => {
const data = res.data?.list || res.list || res.data || [];
const total = res.data?.total || res.total || data.length;
checkInPatientList.value = data.map(item => ({
...item,
appointmentDate: item.scheduleDate + ' ' + (item.expectTime || '')
}));
checkInTotal.value = total;
}).catch(err => {
console.error('加载预约导出失败:', err);
ElMessage.error('获取预约列表失败');
}).finally(() => {
checkInLoading.value = false;
});
}
/** 弹窗行点击处理 */
function selectRow(row) {
selectedCheckInPatient.value = row;
}
/** 确认签到(一键签到:直接构建挂号参数 → 预结算 → 弹收费窗口) */
async function confirmCheckIn() {
if (!selectedCheckInPatient.value) {
ElMessage.warning('请先选择患者');
return;
}
const patient = selectedCheckInPatient.value;
// 每次开始新的签到流程先清理残留 slotId避免历史脏值串单
currentSlotId.value = null;
// 弹出确认提示
try {
await ElMessageBox.confirm(
`确认为患者【${patient.patientName}】办理签到挂号?\n` +
`科室:${patient.department || '-'}\n` +
`医生:${patient.doctor || '-'}\n` +
`费用:¥${patient.fee || '0.00'}`,
'签到确认',
{
confirmButtonText: '确认签到',
cancelButtonText: '取消',
type: 'info',
}
);
} catch {
// 用户点了取消
return;
}
showCheckInPatientModal.value = false;
readCardLoading.value = true;
loadingText.value = '正在处理签到挂号...';
try {
// 1. 用科室ID加载该科室的挂号类型列表获取 serviceTypeId 和 definitionId
const healthcareRes = await getHealthcareMetadata({ organizationId: patient.departmentId });
const healthcareRecords = healthcareRes.data?.records || [];
if (healthcareRecords.length === 0) {
ElMessage.error('该科室未配置挂号类型,无法自动签到');
readCardLoading.value = false;
return;
}
// 2. 按号源类型(专家/普通)模糊匹配挂号类型
const matchTypeName = (patient.ticketType === 'expert') ? '专家' : '普通';
const matchedService = healthcareRecords.find(h => h.name && h.name.includes(matchTypeName));
if (!matchedService) {
// 匹配不到就取第一个作为兜底
ElMessage.warning('未精确匹配到挂号类型,已使用默认类型');
}
const service = matchedService || healthcareRecords[0];
const realPatientId = patient.realPatientId; // 后端新增的真实患者数据库ID
if (!realPatientId) {
ElMessage.error('患者ID缺失请联系管理员检查预约数据');
readCardLoading.value = false;
return;
}
// 3. 构建挂号参数(与 transformFormData 结构一致)
const registrationParam = {
encounterFormData: {
patientId: realPatientId,
priorityEnum: 3, // 默认优先级
serviceTypeId: service.id,
organizationId: patient.departmentId,
},
encounterLocationFormData: {
locationId: null,
},
encounterParticipantFormData: {
practitionerId: patient.doctorId,
},
accountFormData: {
patientId: realPatientId,
typeCode: 1, // 个人现金账户
contractNo: '0000', // 默认自费
},
chargeItemFormData: {
patientId: realPatientId,
definitionId: service.definitionId,
serviceId: service.id,
totalPrice: parseFloat(patient.fee) || ((service.price || 0) + (service.activityPrice || 0)),
},
};
// 4. 设置 patientInfoChargeDialog 需要展示)
patientInfo.value = {
patientId: realPatientId,
patientName: patient.patientName,
genderEnum_enumText: patient.gender || '-',
age: '',
contractName: '自费',
idCard: patient.idCard,
phone: patient.phone,
categoryEnum: '门诊',
organizationName: patient.department || '',
practitionerName: patient.doctor || '',
healthcareName: service.name || '',
};
// 同步设置 form 的 contractNoChargeDialog 的 feeType 会读取它
form.value.contractNo = '0000';
// 5. 调用预结算接口reg-pre-pay
const res = await addOutpatientRegistration(registrationParam);
if (res.code == 200) {
// 仅在预结算成功后记录待签到的号源,避免失败路径残留脏数据
currentSlotId.value = patient.slot_id;
// 6. 设置收费弹窗所需的数据
chrgBchno.value = res.data.chrgBchno;
registerBusNo.value = res.data.busNo;
totalAmount.value = res.data.psnCashPay;
patientInfo.value.encounterId = res.data.encounterId || '';
patientInfo.value.busNo = res.data.busNo || '';
transformedData.value = registrationParam;
chargeItemIdList.value = [];
// 7. 打开收费弹窗
openDialog.value = true;
// 打印挂号单
printRegistrationByHiprint(res.data);
} else {
currentSlotId.value = null;
ElMessage.error(res.msg || '预结算失败');
}
} catch (err) {
currentSlotId.value = null;
console.error('预约签到失败:', err);
ElMessage.error('签到处理失败: ' + (err.message || '未知错误'));
} finally {
readCardLoading.value = false;
}
}
/** /**
* 点击患者列表给表单赋值 * 点击患者列表给表单赋值
*/ */
@@ -1656,20 +1920,29 @@ function handleClose(value) {
proxy.$modal.msgSuccess('操作成功'); proxy.$modal.msgSuccess('操作成功');
// 更新患者手机号 // 更新患者手机号
updatePhone(); updatePhone();
// getList();
// reset(); // 先取出并清空,避免接口失败/取消等路径导致 slotId 残留污染下一单
// addOutpatientRegistration(transformedData.value).then((response) => { const pendingSlotId = currentSlotId.value;
// reset(); currentSlotId.value = null;
// proxy.$modal.msgSuccess('新增成功');
// getList(); // 如果是预约签到的挂号,执行签到状态更新
// }); if (pendingSlotId) {
checkInTicket(pendingSlotId).then(() => {
console.log('预约状态已更新为已取号');
}).catch(err => {
console.error('更新预约状态失败:', err);
ElMessage.error('预约状态更新失败,请手动签到');
});
}
} else if (value == 'cancel') { } else if (value == 'cancel') {
currentSlotId.value = null;
// cancelRegister(patientInfo.value.encounterId).then((res) => { // cancelRegister(patientInfo.value.encounterId).then((res) => {
// if (res.code == 200) { // if (res.code == 200) {
// getList(); // getList();
// } // }
// }); // });
} else { } else {
currentSlotId.value = null;
openRefundDialog.value = false; openRefundDialog.value = false;
} }
} }

View File

@@ -352,7 +352,20 @@ const applyRowToForm = (row) => {
if (myOpinion) { if (myOpinion) {
// 如果当前医生已确认,回显其信息 // 如果当前医生已确认,回显其信息
formData.value.confirmingPhysician = myOpinion.physicianName || '' // 回显“会诊确认参加医师”:优先从 opinion 前缀解析(格式:科室-参加医师:意见)
// 兼容旧数据(格式:科室-医生:意见)以及异常格式
if (myOpinion.opinion) {
const opinionText = myOpinion.opinion
const colonIndex = opinionText.indexOf('')
const dashIndex = opinionText.indexOf('-')
if (dashIndex >= 0 && colonIndex > dashIndex) {
formData.value.confirmingPhysician = opinionText.substring(dashIndex + 1, colonIndex).trim()
} else {
formData.value.confirmingPhysician = myOpinion.physicianName || ''
}
} else {
formData.value.confirmingPhysician = myOpinion.physicianName || ''
}
formData.value.confirmingPhysicianName = myOpinion.physicianName formData.value.confirmingPhysicianName = myOpinion.physicianName
formData.value.confirmingDeptName = myOpinion.deptName formData.value.confirmingDeptName = myOpinion.deptName

View File

@@ -257,15 +257,16 @@ function fetchFromApi(searchKey) {
searchKey: searchKey || '', searchKey: searchKey || '',
statusEnum: 2, statusEnum: 2,
}).then((res) => { }).then((res) => {
console.log('[Debug] 耗材列表返回数据:', res.data); console.log('[BugFix] 耗材列表返回数据:', res.data);
if (res.data && res.data.records) { if (res.data && res.data.records) {
adviceBaseList.value = res.data.records.map((item) => { adviceBaseList.value = res.data.records.map((item) => {
console.log('[Debug] 耗材项:', item.name, 'price:', item.price, 'retailPrice:', item.retailPrice); console.log('[BugFix] 耗材项:', item.name, 'price:', item.price, 'retailPrice:', item.retailPrice);
return { const mappedItem = {
...item, ...item,
// 🔧 Bug Fix: 强制覆盖后端返回的字段,确保数据正确 // 🔧 Bug Fix: 强制覆盖后端返回的字段,确保数据正确
adviceName: item.name || item.busNo, adviceName: item.name || item.busNo,
adviceType: 4, // 强制设置为前端耗材类型 adviceType: 4, // 强制设置为前端耗材类型
adviceType_dictText: '耗材', // 🔧 Bug Fix: 设置医嘱类型显示文本
adviceTableName: 'adm_device_definition', adviceTableName: 'adm_device_definition',
unitCode: item.unitCode || '', unitCode: item.unitCode || '',
unitCode_dictText: item.unitCode_dictText || '', unitCode_dictText: item.unitCode_dictText || '',
@@ -273,7 +274,10 @@ function fetchFromApi(searchKey) {
minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '', minUnitCode_dictText: item.minUnitCode_dictText || item.unitCode_dictText || '',
volume: item.size || item.totalVolume || '', volume: item.size || item.totalVolume || '',
partPercent: item.partPercent || 1, partPercent: item.partPercent || 1,
inventoryList: [], // 🔧 Bug Fix: 如果后端提供了inventoryList则使用否则为空数组
inventoryList: item.inventoryList || [],
// 🔧 Bug Fix: 构造stockList用于库存显示
stockList: item.inventoryList || [],
adviceDefinitionId: item.id, adviceDefinitionId: item.id,
chargeItemDefinitionId: item.id, chargeItemDefinitionId: item.id,
positionId: item.locationId, positionId: item.locationId,
@@ -296,6 +300,8 @@ function fetchFromApi(searchKey) {
? [{ price: item.price }] ? [{ price: item.price }]
: []), : []),
}; };
console.log('[BugFix] 映射后的耗材项:', mappedItem.adviceName, 'adviceType:', mappedItem.adviceType, 'adviceType_dictText:', mappedItem.adviceType_dictText);
return mappedItem;
}); });
nextTick(() => { nextTick(() => {
currentIndex.value = 0; currentIndex.value = 0;
@@ -354,6 +360,11 @@ function handleQuantity(row) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0); const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText; return totalQuantity.toString() + row.minUnitCode_dictText;
} }
// 🔧 Bug Fix: 耗材类型可能没有inventoryList但可能有stockList
if (row.stockList && row.stockList.length > 0) {
const totalQuantity = row.stockList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + row.minUnitCode_dictText;
}
return 0; return 0;
} }

View File

@@ -280,6 +280,7 @@ import {
saveDiagnosis, saveDiagnosis,
} from '../api'; } from '../api';
import { DIAG_TYPE } from '@/utils/medicalConstants'; import { DIAG_TYPE } from '@/utils/medicalConstants';
import { formatDateStr } from '@/utils';
import diagnosisdialog from '../diagnosis/diagnosisdialog.vue'; import diagnosisdialog from '../diagnosis/diagnosisdialog.vue';
import AddDiagnosisDialog from './addDiagnosisDialog.vue'; import AddDiagnosisDialog from './addDiagnosisDialog.vue';
import diagnosislist from '../diagnosis/diagnosislist.vue'; import diagnosislist from '../diagnosis/diagnosislist.vue';
@@ -628,11 +629,11 @@ async function handleSaveDiagnosis() {
// 开始加载状态,防止重复提交 // 开始加载状态,防止重复提交
saveLoading.value = true; saveLoading.value = true;
// 保存前按排序号排序,并转换日期格式为ISO字符串 // 保存前按排序号排序,并转换日期格式为后端期望的格式 yyyy/M/d HH:mm:ss
const diagnosisChildList = form.value.diagnosisList.map(item => ({ const diagnosisChildList = form.value.diagnosisList.map(item => ({
...item, ...item,
onsetDate: item.onsetDate ? new Date(item.onsetDate).toISOString() : null, onsetDate: item.onsetDate ? formatDateStr(item.onsetDate, 'YYYY/M/D HH:mm:ss') : null,
diagnosisTime: item.diagnosisTime ? new Date(item.diagnosisTime).toISOString() : null diagnosisTime: item.diagnosisTime ? formatDateStr(item.diagnosisTime, 'YYYY/M/D HH:mm:ss') : null
})); }));
// 调用保存诊断接口 // 调用保存诊断接口
@@ -657,7 +658,9 @@ async function handleSaveDiagnosis() {
} }
} catch (error) { } catch (error) {
console.error('保存诊断失败:', error); console.error('保存诊断失败:', error);
proxy.$modal.msgError('保存诊断失败,请稍后重试'); // 显示后端返回的具体错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '保存诊断失败,请稍后重试';
proxy.$modal.msgError(errorMsg);
} finally { } finally {
// 结束加载状态 // 结束加载状态
saveLoading.value = false; saveLoading.value = false;

View File

@@ -164,7 +164,14 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="检查方法"> <el-form-item label="检查方法">
<el-input v-model="form.inspectionMethod" readonly /> <el-select v-model="form.inspectionMethod" placeholder="请选择" clearable filterable style="width: 100%;">
<el-option
v-for="method in availableMethods"
:key="method.id"
:label="method.name"
:value="method.name"
/>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -308,6 +315,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Printer, Delete } from '@element-plus/icons-vue'; import { Printer, Delete } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { listCheckMethod } from '@/api/system/checkType';
const props = defineProps({ const props = defineProps({
patientInfo: { type: Object, default: () => ({}) }, patientInfo: { type: Object, default: () => ({}) },
@@ -373,10 +381,69 @@ const categoryList = ref([]); // 原始分类+项目数据
const dictSearchKey = ref(''); const dictSearchKey = ref('');
const activeNames = ref([]); // 当前展开的折叠项 const activeNames = ref([]); // 当前展开的折叠项
const allMethods = ref([]);
// 加载所有检查方法
async function loadAllMethods() {
try {
const res = await listCheckMethod(); // 使用已导入的或者直接利用 request 请求
let methods = [];
if (res && res.data) {
if (Array.isArray(res.data)) {
methods = res.data;
} else if (res.data.records) {
methods = res.data.records;
} else if (res.data.data && Array.isArray(res.data.data)) {
methods = res.data.data;
}
} else if (Array.isArray(res)) {
methods = res;
} else if (res && res.rows) {
methods = res.rows;
}
allMethods.value = methods;
} catch (err) {
console.error('加载检查方法失败', err);
}
}
onMounted(async () => { onMounted(async () => {
await loadAllMethods();
await loadCategoryList(); await loadCategoryList();
}); });
// 动态可用的检查方法(根据已选部位所属的检查类型进行过滤)
const normalizeTypeValue = value => String(value ?? '').trim().toLowerCase();
const availableMethods = computed(() => {
// 获取当前已选部位的检查类型(可取第一个选中的部位的 checkType
const currentType = form.examTypeCode || (selectedItems.value.length > 0 ? selectedItems.value[0].checkType : '');
const normalizedCurrentType = normalizeTypeValue(currentType);
if (normalizedCurrentType) {
// 兼容脏数据method 的类型可能落在 checkType/type/typeCode/code/typeName/categoryName 中
const filtered = allMethods.value.filter(m => {
const typeCandidates = [
m.checkType,
m.type,
m.typeCode,
m.code,
m.typeName,
m.categoryName
].map(normalizeTypeValue).filter(Boolean);
return typeCandidates.includes(normalizedCurrentType);
});
return filtered.length > 0 ? filtered : allMethods.value;
}
return allMethods.value;
});
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
watch(availableMethods, (newMethods) => {
if (form.inspectionMethod && !newMethods.find(m => m.name === form.inspectionMethod)) {
form.inspectionMethod = '';
}
});
/** /**
* 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示 * 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示
*/ */
@@ -390,25 +457,45 @@ async function loadCategoryList() {
params: { pageNo: 1, pageSize: 500 } // 取全量分类数据 params: { pageNo: 1, pageSize: 500 } // 取全量分类数据
}); });
let types = []; let types = [];
if (typeRes.data?.records) types = typeRes.data.records; if (typeRes && typeRes.data) {
else if (Array.isArray(typeRes.data)) types = typeRes.data; if (Array.isArray(typeRes.data)) {
else if (Array.isArray(typeRes.rows)) types = typeRes.rows; types = typeRes.data;
} else if (typeRes.data.records) {
types = typeRes.data.records;
} else if (typeRes.data.data && Array.isArray(typeRes.data.data)) {
types = typeRes.data.data;
}
} else if (Array.isArray(typeRes)) {
types = typeRes;
} else if (typeRes && typeRes.rows) {
types = typeRes.rows;
}
// 2. 加载检查项目(检查部位项目) // 2. 加载检查项目(检查部位项目)
const partRes = await request({ url: '/check/part/list', method: 'get' }); const partRes = await request({ url: '/check/part/list', method: 'get' });
let parts = []; let parts = [];
if (Array.isArray(partRes)) parts = partRes; if (partRes && partRes.data) {
else if (Array.isArray(partRes.data?.data)) parts = partRes.data.data; // 双层嵌套:{ data: { data: [...] } } if (Array.isArray(partRes.data)) {
else if (Array.isArray(partRes.data)) parts = partRes.data; parts = partRes.data;
else if (Array.isArray(partRes.rows)) parts = partRes.rows; } else if (partRes.data.records) {
else if (partRes.data?.records) parts = partRes.data.records; parts = partRes.data.records;
} else if (partRes.data.data && Array.isArray(partRes.data.data)) {
parts = partRes.data.data;
}
} else if (Array.isArray(partRes)) {
parts = partRes;
} else if (partRes && partRes.rows) {
parts = partRes.rows;
}
// 3. 按 checkType 归类 // 3. 按 checkType 归类
const dict = []; const dict = [];
for (const t of types) { for (const t of types) {
dict.push({ dict.push({
typeId: t.id, typeId: t.id,
typeCode: t.type, typeCode: t.code, // 保存 code 用于后备匹配
orgType: t.type, // 保存 type 用于后备匹配
typeName: t.name, // 保存 name
categoryName: t.name, categoryName: t.name,
items: [] items: []
}); });
@@ -425,7 +512,15 @@ async function loadCategoryList() {
nationalCode: p.nationalCode || '', nationalCode: p.nationalCode || '',
checked: false checked: false
}; };
const target = dict.find(d => d.typeCode === p.checkType);
// 增强匹配逻辑:部位的 checkType (如 'ECG', 'CT') 优先去匹配大类的 orgType
// 如果大类的 type 字段脏了(比如填了中文),则尝试匹配 code甚至是分类名称
const target = dict.find(d =>
d.orgType === p.checkType ||
d.typeCode === p.checkType ||
d.typeName === p.checkType
);
if (target) target.items.push(mapped); if (target) target.items.push(mapped);
else unclassified.push(mapped); else unclassified.push(mapped);
} }

View File

@@ -1,14 +1,14 @@
<template> <template>
<el-container class="inspection-application-container"> <el-container class="inspection-application-container">
<!-- 顶部操作按钮区 --> <!-- 顶部操作按钮区 - Bug#334: 优化垂直空间利用率 -->
<el-header class="top-action-bar" height="60px"> <el-header class="top-action-bar" height="48px">
<el-row class="action-buttons" type="flex" justify="end" :gutter="10"> <el-row class="action-buttons" type="flex" justify="end" :gutter="8">
<el-button type="primary" size="large" @click="handleSave" class="save-btn" :loading="saving"> <el-button type="primary" size="default" @click="handleSave" class="save-btn" :loading="saving">
<el-icon><Document /></el-icon> <el-icon><Document /></el-icon>
保存 保存
</el-button> </el-button>
<el-button type="primary" size="large" @click="handleNewApplication" class="new-btn"> <el-button type="primary" size="default" @click="handleNewApplication" class="new-btn">
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
新增 新增
</el-button> </el-button>
@@ -1647,19 +1647,20 @@ defineExpose({
padding: 0; padding: 0;
} }
/* 顶部操作按钮区 */ /* Bug#334: 顶部操作按钮区 - 优化垂直空间利用率 */
.top-action-bar { .top-action-bar {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
border-bottom: 1px solid var(--el-border-color-light); border-bottom: 1px solid var(--el-border-color-light);
background: var(--el-bg-color); background: var(--el-bg-color);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
padding: 0 12px;
} }
.action-buttons { .action-buttons {
display: flex; display: flex;
gap: 10px; gap: 8px;
} }
/* 新增按钮样式 - PRD要求蓝色背景 #4a89dc */ /* 新增按钮样式 - PRD要求蓝色背景 #4a89dc */
@@ -1686,9 +1687,9 @@ defineExpose({
border-color: #58dfbd !important; border-color: #58dfbd !important;
} }
/* 检验信息表格区 - 紧凑高度 */ /* Bug#334: 检验信息表格区 - 优化垂直空间利用率 */
.inspection-section { .inspection-section {
padding: 4px 10px 0 10px; padding: 2px 10px 0 10px;
} }
.table-card { .table-card {
@@ -1696,7 +1697,7 @@ defineExpose({
} }
.table-card :deep(.el-card__body) { .table-card :deep(.el-card__body) {
padding-bottom: 8px; padding-bottom: 6px;
} }
.card-header { .card-header {
@@ -1707,9 +1708,9 @@ defineExpose({
color: var(--el-text-color-primary); color: var(--el-text-color-primary);
} }
/* 底部内容区域 */ /* Bug#334: 底部内容区域 - 优化垂直空间利用率 */
.bottom-content-area { .bottom-content-area {
padding: 4px 10px; padding: 2px 10px;
} }
/* 表单区域 */ /* 表单区域 */
@@ -1732,7 +1733,7 @@ defineExpose({
.application-form { .application-form {
overflow: visible; overflow: visible;
padding: 6px 8px; padding: 4px 8px;
border: 1px solid #e4e7ed; border: 1px solid #e4e7ed;
border-radius: 4px; border-radius: 4px;
margin: 2px; margin: 2px;

View File

@@ -223,7 +223,12 @@
style="width: 70px; margin-right: 20px" /> style="width: 70px; margin-right: 20px" />
</el-form-item> </el-form-item>
<span class="medicine-info"> 诊断{{ diagnosisName }} </span> <span class="medicine-info"> 诊断{{ diagnosisName }} </span>
<span class="medicine-info"> 皮试{{ scope.row.skinTestFlag_enumText }} </span> <span class="medicine-info" style="display: flex; align-items: center; gap: 5px;">
皮试<el-checkbox v-model="scope.row.skinTestFlag" :true-value="1" :false-value="0"
@change="handleSkinTestChange(scope.row, scope.$index)">
</el-checkbox>
</span>
<span class="medicine-info"> 注射药品{{ scope.row.injectFlag_enumText }} </span> <span class="medicine-info"> 注射药品{{ scope.row.injectFlag_enumText }} </span>
<span class="total-amount"> <span class="total-amount">
总金额{{ 总金额{{
@@ -470,6 +475,12 @@
</template> </template>
</el-select> </el-select>
</el-form-item> </el-form-item>
<span class="medicine-info" style="display: flex; align-items: center; gap: 5px;">
皮试:<el-checkbox v-model="scope.row.skinTestFlag" :true-value="1" :false-value="0"
@change="handleSkinTestChange(scope.row, scope.$index)">
</el-checkbox>
</span>
<span class="total-amount"> <span class="total-amount">
总金额:{{ 总金额:{{
(scope.row.totalPrice !== undefined && scope.row.totalPrice !== null && !isNaN(scope.row.totalPrice) (scope.row.totalPrice !== undefined && scope.row.totalPrice !== null && !isNaN(scope.row.totalPrice)
@@ -546,6 +557,11 @@
expandOrder = []; expandOrder = [];
// 当医嘱类型改变时,清空当前选择的项目名称,因为不同类型项目的数据结构可能不兼容 // 当医嘱类型改变时,清空当前选择的项目名称,因为不同类型项目的数据结构可能不兼容
prescriptionList[scope.$index].adviceName = undefined; prescriptionList[scope.$index].adviceName = undefined;
prescriptionList[scope.$index].adviceType_dictText = '';
// 🔧 Bug Fix: 医嘱类型改变时重置minUnitQuantity和minUnitCode避免null值
prescriptionList[scope.$index].minUnitQuantity = prescriptionList[scope.$index].quantity || 1;
prescriptionList[scope.$index].minUnitCode = prescriptionList[scope.$index].unitCode;
prescriptionList[scope.$index].minUnitCode_dictText = prescriptionList[scope.$index].unitCode_dictText;
adviceQueryParams.adviceTypes = value; // 🎯 修复:改为 adviceTypes复数 adviceQueryParams.adviceTypes = value; // 🎯 修复:改为 adviceTypes复数
// 根据选择的类型设置categoryCode用于药品分类筛选 // 根据选择的类型设置categoryCode用于药品分类筛选
@@ -756,7 +772,13 @@
</el-table-column> </el-table-column>
<el-table-column label="皮试" align="center" prop="" width="80"> <el-table-column label="皮试" align="center" prop="" width="80">
<template #default="scope"> <template #default="scope">
<span v-if="!scope.row.isEdit"> <template v-if="scope.row.isEdit">
<el-checkbox v-model="scope.row.skinTestFlag" :true-value="1" :false-value="0"
@change="handleSkinTestChange(scope.row, scope.$index)">
</el-checkbox>
</template>
<span v-else>
{{ scope.row.skinTestFlag_enumText || '-' }} {{ scope.row.skinTestFlag_enumText || '-' }}
</span> </span>
</template> </template>
@@ -956,25 +978,38 @@ const { method_code, unit_code, rate_code, distribution_category_code, drord_doc
// drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术 // drord_doctor_type: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
const adviceTypeList = computed(() => { const adviceTypeList = computed(() => {
// 如果字典已加载,使用字典数据;否则使用默认值 // 如果字典已加载,使用字典数据;否则使用默认值
let list = [];
if (drord_doctor_type.value && drord_doctor_type.value.length > 0) { if (drord_doctor_type.value && drord_doctor_type.value.length > 0) {
return drord_doctor_type.value.map(item => ({ // 过滤掉字典中已有的"全部"选项,避免重复
label: item.label, list = drord_doctor_type.value
value: parseInt(item.value) || item.value .filter(item => item.label !== '全部')
})); .map(item => ({
label: item.label,
value: parseInt(item.value) || item.value
}));
} else {
// 默认返回值,确保页面正常显示
list = [
{ label: '西药', value: 1 },
{ label: '中成药', value: 2 },
{ label: '诊疗', value: 3 },
{ label: '耗材', value: 4 },
{ label: '会诊', value: 5 },
{ label: '手术', value: 6 },
];
} }
// 默认返回值,确保页面正常显示 // 在最后添加"全部"选项
return [ list.push({ label: '全部', value: 0 });
{ label: '西药', value: 1 }, return list;
{ label: '中成药', value: 2 },
{ label: '诊疗', value: 3 },
{ label: '耗材', value: 4 },
{ label: '会诊', value: 5 },
{ label: '手术', value: 6 },
];
}); });
// 根据类型值获取显示标签,避免非编辑态出现空标签 // 根据类型值获取显示标签,避免非编辑态出现空标签
const mapAdviceTypeLabel = (type) => { const mapAdviceTypeLabel = (type, adviceTableName) => {
// 🔧 Bug Fix: 根据adviceTableName判断耗材类型
// 后端adviceType=2既表示中成药又表示耗材需要通过表名区分
if (type === 2 && adviceTableName === 'adm_device_definition') {
return '耗材';
}
const found = adviceTypeList.value.find((item) => item.value === type); const found = adviceTypeList.value.find((item) => item.value === type);
return found ? found.label : ''; return found ? found.label : '';
}; };
@@ -1602,11 +1637,8 @@ function getListInfo(addNewRow) {
// 🔧 Bug Fix: 后端保存时将耗材(4)转换为中成药(2),显示时需要转换回来 // 🔧 Bug Fix: 后端保存时将耗材(4)转换为中成药(2),显示时需要转换回来
// 检查 adviceTableName如果是耗材表则应该是耗材类型 // 检查 adviceTableName如果是耗材表则应该是耗材类型
const adviceTableName = contentJson?.adviceTableName || item.adviceTableName; const adviceTableName = contentJson?.adviceTableName || item.adviceTableName;
if (adviceType === 2 && adviceTableName === 'adm_device_definition') {
adviceType = 4; // 后端2(中成药) -> 前端4(耗材)
}
let adviceType_dictText = item.adviceType_dictText || mapAdviceTypeLabel(adviceType); let adviceType_dictText = item.adviceType_dictText || mapAdviceTypeLabel(adviceType, adviceTableName);
// 如果是会诊类型,设置为会诊类型 // 如果是会诊类型,设置为会诊类型
if (isConsultation) { if (isConsultation) {
@@ -1893,6 +1925,34 @@ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
* 选择药品回调 * 选择药品回调
*/ */
function selectAdviceBase(key, row) { function selectAdviceBase(key, row) {
// 🔧 Bug Fix: 检查药品是否需要皮试,如果需要则弹出确认框
if (row.skinTestFlag == 1) {
ElMessageBox.confirm(`药品:${row.adviceName}需要做皮试,是否做皮试?`, '提示', {
confirmButtonText: '是',
cancelButtonText: '否',
type: 'warning',
center: true,
customClass: 'skin-test-confirm-dialog',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
// 用户点击右边的按钮confirm保持皮试标记为1
setNewRow(key, row);
done();
} else if (action === 'cancel') {
// 用户点击左边的按钮cancel将皮试标记改为0
row.skinTestFlag = 0;
row.skinTestFlag_enumText = '否';
setNewRow(key, row);
done();
} else {
done();
}
}
});
return;
}
// 检查检查检验项目是否有历史记录30天内
if (row.categoryCode == 22 || row.categoryCode == 23) { if (row.categoryCode == 22 || row.categoryCode == 23) {
checkServicesHistory({ checkServicesHistory({
patientId: props.patientInfo.patientId, patientId: props.patientInfo.patientId,
@@ -1915,6 +1975,7 @@ function selectAdviceBase(key, row) {
} }
async function setNewRow(key, row) { async function setNewRow(key, row) {
console.log('[BugFix] setNewRow - row.adviceType:', row.adviceType, 'row.adviceType_dictText:', row.adviceType_dictText, 'row.adviceTableName:', row.adviceTableName);
// 每次选择药品时,将当前行数据完全重置,清空所有旧数据 // 每次选择药品时,将当前行数据完全重置,清空所有旧数据
const preservedData = { const preservedData = {
uniqueKey: prescriptionList.value[rowIndex.value].uniqueKey, uniqueKey: prescriptionList.value[rowIndex.value].uniqueKey,
@@ -1928,6 +1989,8 @@ function selectAdviceBase(key, row) {
setValue(row); setValue(row);
console.log('[BugFix] setNewRow after setValue - prescriptionList[rowIndex].adviceType:', prescriptionList.value[rowIndex.value].adviceType, 'adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 Bug #220 修复确保在setValue之后重新计算耗材类型的总金额 // 🔧 Bug #220 修复确保在setValue之后重新计算耗材类型的总金额
// 耗材(adviceType=4)和诊疗(adviceType=3)需要重新计算以确保显示正确 // 耗材(adviceType=4)和诊疗(adviceType=3)需要重新计算以确保显示正确
const currentRow = prescriptionList.value[rowIndex.value]; const currentRow = prescriptionList.value[rowIndex.value];
@@ -2343,6 +2406,13 @@ function handleSave(prescriptionId) {
item.accountId = finalAccountId; item.accountId = finalAccountId;
} }
item.dbOpType = '1'; item.dbOpType = '1';
// 🔧 Bug Fix: 确保耗材的minUnitQuantity被正确设置
if (item.adviceType == 4) {
item.minUnitQuantity = item.quantity;
item.minUnitCode = item.unitCode;
item.minUnitCode_dictText = item.unitCode_dictText;
}
}); });
loading.value = true; loading.value = true;
@@ -2366,20 +2436,25 @@ function handleSave(prescriptionId) {
finalUnitCode = item.minUnitCode; finalUnitCode = item.minUnitCode;
} }
item.minUnitQuantity = finalQuantity; item.minUnitQuantity = finalQuantity;
} else if (item.adviceType == 4) {
// 🔧 Bug Fix: 耗材类型只有一个单位minUnitQuantity等于quantity
item.minUnitQuantity = item.quantity;
// 🔧 Bug Fix: 确保minUnitCode等于unitCode
item.minUnitCode = item.unitCode;
item.minUnitCode_dictText = item.unitCode_dictText;
finalUnitCode = item.unitCode;
} else { } else {
item.minUnitQuantity = item.quantity; item.minUnitQuantity = item.quantity;
} }
// --- 【修改点3Bug 1 修复 (类型转换)】 --- // --- 【修改点3Bug 1 修复 (类型转换)】 ---
let saveAdviceType = item.adviceType; let saveAdviceType = item.adviceType;
if (item.adviceType == 4) { // 🔧 Bug Fix: 保持原类型,不进行转换
saveAdviceType = 2; // 耗材前端4 -> 后端2 // 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
} else if (item.adviceType == 2) { // 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
saveAdviceType = 1; // 中成药前端2 -> 后端1 // 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
} else if (item.adviceType == 5) { if (item.adviceType == 5) {
saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类 saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类
} else if (item.adviceType == 6) {
saveAdviceType = 6; // 🔧 BugFix#318: 手术类型保持为6
} }
// 🔧 Bug Fix: Validate and fix NaN values before sending to backend // 🔧 Bug Fix: Validate and fix NaN values before sending to backend
@@ -2439,6 +2514,8 @@ function handleSave(prescriptionId) {
quantity: finalQuantity, quantity: finalQuantity,
unitCode: finalUnitCode, unitCode: finalUnitCode,
totalPrice: item.totalPrice, // Ensure totalPrice is valid totalPrice: item.totalPrice, // Ensure totalPrice is valid
// 🔧 Bug Fix: 确保 categoryEnum 被传递(耗材必填字段)
categoryEnum: item.categoryEnum || parsedContent.categoryEnum,
// 🔧 Bug Fix: 确保库存匹配成功的关键字段 // 🔧 Bug Fix: 确保库存匹配成功的关键字段
adviceTableName: adviceTableNameVal, adviceTableName: adviceTableNameVal,
locationId: locationIdVal, locationId: locationIdVal,
@@ -2446,6 +2523,13 @@ function handleSave(prescriptionId) {
methodCode: item.methodCode || parsedContent.methodCode, methodCode: item.methodCode || parsedContent.methodCode,
// 🔧 确保 accountId 被传递(用于预结算) // 🔧 确保 accountId 被传递(用于预结算)
accountId: item.accountId || parsedContent.accountId, accountId: item.accountId || parsedContent.accountId,
// 🔧 Bug Fix: 确保minUnitQuantity被传递耗材必填字段
minUnitQuantity: item.minUnitQuantity,
minUnitCode: item.minUnitCode,
minUnitCode_dictText: item.minUnitCode_dictText,
// 🔧 Bug Fix: 确保 definitionId 和 definitionDetailId 被传递(费用项必填字段)
definitionId: item.definitionId || parsedContent.definitionId,
definitionDetailId: item.definitionDetailId || parsedContent.definitionDetailId,
// 🔧 更新 contentJson 中的 adviceType确保后端分类正确 // 🔧 更新 contentJson 中的 adviceType确保后端分类正确
contentJson: JSON.stringify({ contentJson: JSON.stringify({
...parsedContent, ...parsedContent,
@@ -2635,12 +2719,17 @@ function handleOrderBindInfo(bindIdInfo, currentMethodCode) {
encounterDiagnosisId: encounterDiagnosisId.value, encounterDiagnosisId: encounterDiagnosisId.value,
// 🔧 确保 adviceType 和显示文本正确设置 // 🔧 确保 adviceType 和显示文本正确设置
adviceType: item.adviceType, adviceType: item.adviceType,
adviceType_dictText: mapAdviceTypeLabel(item.adviceType), adviceType_dictText: mapAdviceTypeLabel(item.adviceType, item.adviceTableName),
}; };
// 计算价格和总量 // 计算价格和总量
const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode); const unitInfo = unitCodeList.value.find((k) => k.value == item.unitCode);
if (unitInfo && unitInfo.type == 'minUnit') { // 🔧 Bug Fix: 耗材类型只有一个单位minUnitQuantity等于quantity
if (item.adviceType == 4) {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity;
} else if (unitInfo && unitInfo.type == 'minUnit') {
newRow.price = newRow.minUnitPrice; newRow.price = newRow.minUnitPrice;
newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6); newRow.totalPrice = (item.quantity * newRow.minUnitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity; newRow.minUnitQuantity = item.quantity;
@@ -2705,6 +2794,12 @@ function handleSaveSign(row, index, prescriptionId) {
return; return;
} }
// 🔧 Bug Fix: 验证医嘱类型不能为"全部"
if (row.adviceType === 0) {
proxy.$modal.msgWarning('请选择医嘱类型');
return;
}
// 重新查找索引,确保使用当前处方列表中的正确索引 // 重新查找索引,确保使用当前处方列表中的正确索引
const actualIndex = prescriptionList.value.findIndex(item => item.uniqueKey === row.uniqueKey); const actualIndex = prescriptionList.value.findIndex(item => item.uniqueKey === row.uniqueKey);
if (actualIndex === -1) { if (actualIndex === -1) {
@@ -2774,14 +2869,18 @@ function handleSaveSign(row, index, prescriptionId) {
row.encounterId = props.patientInfo.encounterId; row.encounterId = props.patientInfo.encounterId;
row.accountId = accountId.value; row.accountId = accountId.value;
// 确保非编辑态显示正确的医嘱类型标签 // 确保非编辑态显示正确的医嘱类型标签
row.adviceType_dictText = mapAdviceTypeLabel(row.adviceType); row.adviceType_dictText = mapAdviceTypeLabel(row.adviceType, row.adviceTableName);
// 更新本地显示的最小单位数量(仅用于前端逻辑,不影响显示单位) // 更新本地显示的最小单位数量(仅用于前端逻辑,不影响显示单位)
if (row.adviceType == 1 || row.adviceType == 2) { if (row.adviceType == 1 || row.adviceType == 2) {
row.minUnitQuantity = row.minUnitQuantity =
row.minUnitCode == row.unitCode ? row.quantity : row.quantity * row.partPercent; row.minUnitCode == row.unitCode ? row.quantity : row.quantity * row.partPercent;
} else { } else {
// 🔧 Bug Fix: 耗材和其他类型minUnitQuantity等于quantity
row.minUnitQuantity = row.quantity; row.minUnitQuantity = row.quantity;
// 🔧 Bug Fix: 确保minUnitCode等于unitCode耗材只有一个单位
row.minUnitCode = row.unitCode;
row.minUnitCode_dictText = row.unitCode_dictText;
} }
row.conditionId = conditionId.value; row.conditionId = conditionId.value;
@@ -2976,14 +3075,12 @@ function handleSaveBatch(prescriptionId) {
// --- Bug 1 修复:类型转换 --- // --- Bug 1 修复:类型转换 ---
let saveAdviceType = item.adviceType; let saveAdviceType = item.adviceType;
if (item.adviceType == 4) { // 🔧 Bug Fix: 保持原类型,不进行转换
saveAdviceType = 2; // 耗材前端4 -> 后端2 // 前端类型: 1=西药, 2=中成药, 3=诊疗, 4=耗材, 5=会诊, 6=手术
} else if (item.adviceType == 2) { // 后端类型: 1=药品, 2=耗材, 3=医疗活动, 6=手术
saveAdviceType = 1; // 中成药前端2 -> 后端1 // 后端会通过 ItemType.DEVICE.getValue() || adviceType == 4 来识别耗材
} else if (item.adviceType == 5) { if (item.adviceType == 5) {
saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类 saveAdviceType = 3; // 会诊前端5 -> 后端3诊疗类
} else if (item.adviceType == 6) {
saveAdviceType = 6; // 🔧 BugFix#318: 手术类型保持为6
} }
// 🔧 BugFix#318: 过滤掉手术特有字段,只保留标准医嘱字段 // 🔧 BugFix#318: 过滤掉手术特有字段,只保留标准医嘱字段
@@ -2994,7 +3091,11 @@ function handleSaveBatch(prescriptionId) {
'encounterId', 'groupId', 'injectFlag', 'lotNumber', 'methodCode', 'partPercent', 'encounterId', 'groupId', 'injectFlag', 'lotNumber', 'methodCode', 'partPercent',
'patientId', 'positionId', 'positionName', 'prescriptionNo', 'quantity', 'rateCode', 'patientId', 'positionId', 'positionName', 'prescriptionNo', 'quantity', 'rateCode',
'requestId', 'skinTestFlag', 'sortNumber', 'statusEnum', 'totalPrice', 'requestId', 'skinTestFlag', 'sortNumber', 'statusEnum', 'totalPrice',
'unitCode', 'unitPrice', 'volume', 'ybClassEnum' 'unitCode', 'unitPrice', 'volume', 'ybClassEnum',
// 🔧 Bug Fix: 添加 definitionId 和 definitionDetailId 字段
'definitionId', 'definitionDetailId',
// 🔧 Bug Fix: 添加 categoryEnum 字段(耗材必填)
'categoryEnum'
]; ];
let filteredItem = {}; let filteredItem = {};
standardItemFields.forEach(field => { standardItemFields.forEach(field => {
@@ -3014,10 +3115,8 @@ function handleSaveBatch(prescriptionId) {
// 🔧 Bug Fix: 处理accountId如果是'ZIFEI'或0则转为null让后端查询默认账户 // 🔧 Bug Fix: 处理accountId如果是'ZIFEI'或0则转为null让后端查询默认账户
let itemAccountId = finalAccountId; let itemAccountId = finalAccountId;
if (itemAccountId === 'ZIFEI' || itemAccountId === 0) { if (itemAccountId === 'ZIFEI' || itemAccountId === 0 || itemAccountId === '0') {
itemAccountId = null; itemAccountId = null;
} else if (itemAccountId && !isNaN(Number(itemAccountId))) {
itemAccountId = Number(itemAccountId);
} }
// 🔧 Bug Fix: 确保库存匹配成功的关键字段 // 🔧 Bug Fix: 确保库存匹配成功的关键字段
@@ -3064,7 +3163,11 @@ function handleSaveBatch(prescriptionId) {
accountId: itemAccountId, accountId: itemAccountId,
// 🔧 Bug Fix: 确保库存匹配成功 // 🔧 Bug Fix: 确保库存匹配成功
adviceTableName: adviceTableNameVal, adviceTableName: adviceTableNameVal,
locationId: locationIdVal locationId: locationIdVal,
// 🔧 Bug Fix: 确保 minUnitQuantity、minUnitCode 等字段被传递(药品必填字段)
minUnitQuantity: item.minUnitQuantity,
minUnitCode: item.minUnitCode,
minUnitCode_dictText: item.minUnitCode_dictText
}; };
}); });
// --- 【修改结束】 --- // --- 【修改结束】 ---
@@ -3193,17 +3296,26 @@ function syncGroupFields(row) {
function setValue(row) { function setValue(row) {
unitCodeList.value = []; unitCodeList.value = [];
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' }); unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
unitCodeList.value.push({
value: row.doseUnitCode,
label: row.doseUnitCode_dictText,
type: 'dose',
});
unitCodeList.value.push({ // 🔧 Bug Fix: 耗材类型只有一个单位不需要dose和minUnit选项
value: row.minUnitCode, if (row.adviceType == 4) {
label: row.minUnitCode_dictText, // 耗材只添加一个单位选项
type: 'minUnit', row.minUnitCode = row.unitCode;
}); row.minUnitCode_dictText = row.unitCode_dictText;
} else {
// 药品类型添加dose和minUnit选项
unitCodeList.value.push({
value: row.doseUnitCode,
label: row.doseUnitCode_dictText,
type: 'dose',
});
unitCodeList.value.push({
value: row.minUnitCode,
label: row.minUnitCode_dictText,
type: 'minUnit',
});
}
if (row.adviceType == 2 && row.minUnitCode != row.unitCode) { if (row.adviceType == 2 && row.minUnitCode != row.unitCode) {
unitCodeList.value.push({ unitCodeList.value.push({
value: row.minUnitCode, value: row.minUnitCode,
@@ -3217,10 +3329,13 @@ function setValue(row) {
: 0; : 0;
// 创建一个新的对象,而不是合并旧数据,以避免残留数据问题 // 创建一个新的对象,而不是合并旧数据,以避免残留数据问题
console.log('[BugFix] setValue - row.adviceType:', row.adviceType, 'row.adviceType_dictText:', row.adviceType_dictText, 'row.adviceTableName:', row.adviceTableName);
prescriptionList.value[rowIndex.value] = { prescriptionList.value[rowIndex.value] = {
...JSON.parse(JSON.stringify(row)), ...JSON.parse(JSON.stringify(row)),
// 确保adviceType为数字类型避免类型不匹配导致的显示问题 // 确保adviceType为数字类型避免类型不匹配导致的显示问题
adviceType: Number(row.adviceType), adviceType: Number(row.adviceType),
// 🔧 Bug Fix: 确保adviceType_dictText被正确设置避免展开行时显示错误
adviceType_dictText: row.adviceType_dictText || mapAdviceTypeLabel(row.adviceType, row.adviceTableName),
skinTestFlag: skinTestFlag, // 确保皮试字段是数字类型 skinTestFlag: skinTestFlag, // 确保皮试字段是数字类型
skinTestFlag_enumText: skinTestFlag == 1 ? '是' : '否', // 更新显示文本 skinTestFlag_enumText: skinTestFlag == 1 ? '是' : '否', // 更新显示文本
// 保留原来设置的初始状态值 // 保留原来设置的初始状态值
@@ -3229,6 +3344,7 @@ function setValue(row) {
statusEnum: prescriptionList.value[rowIndex.value].statusEnum, statusEnum: prescriptionList.value[rowIndex.value].statusEnum,
showPopover: false, // 确保查询框关闭 showPopover: false, // 确保查询框关闭
}; };
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 Bug #218 修复保留组套中的值不要强制设为undefined // 🔧 Bug #218 修复保留组套中的值不要强制设为undefined
// 只有当值未定义时才使用默认值 // 只有当值未定义时才使用默认值
prescriptionList.value[rowIndex.value].orgId = row.positionId || row.orgId; prescriptionList.value[rowIndex.value].orgId = row.positionId || row.orgId;
@@ -3237,8 +3353,15 @@ function setValue(row) {
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value; prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode; prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode; prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode;
prescriptionList.value[rowIndex.value].unitCode = // 🔧 Bug Fix: 耗材类型只有一个单位minUnitCode应该等于unitCode
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode; if (Number(row.adviceType) == 4) {
prescriptionList.value[rowIndex.value].minUnitCode = row.unitCode;
prescriptionList.value[rowIndex.value].minUnitCode_dictText = row.unitCode_dictText;
prescriptionList.value[rowIndex.value].unitCode = row.unitCode;
} else {
prescriptionList.value[rowIndex.value].unitCode =
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
}
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode; prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
prescriptionList.value[rowIndex.value].skinTestFlag = row.skinTestFlag; prescriptionList.value[rowIndex.value].skinTestFlag = row.skinTestFlag;
prescriptionList.value[rowIndex.value].definitionId = row.chargeItemDefinitionId; prescriptionList.value[rowIndex.value].definitionId = row.chargeItemDefinitionId;
@@ -3307,6 +3430,10 @@ function setValue(row) {
// 🔧 Bug #218 修复保留组套中的quantity如果没有则默认1 // 🔧 Bug #218 修复保留组套中的quantity如果没有则默认1
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1; prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].totalPrice = validPrice * (row.quantity || 1); prescriptionList.value[rowIndex.value].totalPrice = validPrice * (row.quantity || 1);
// 🔧 Bug Fix: 设置耗材的minUnitQuantity避免保存时null错误
prescriptionList.value[rowIndex.value].minUnitQuantity = row.quantity || 1;
// 🔧 Bug Fix: 设置耗材的categoryEnum避免数据库约束错误
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode || 3; // 默认为3
prescriptionList.value[rowIndex.value].positionName = row.positionName || ''; prescriptionList.value[rowIndex.value].positionName = row.positionName || '';
// 🔧 Bug Fix: 使用 positionId如果为空则使用患者信息中的 orgId // 🔧 Bug Fix: 使用 positionId如果为空则使用患者信息中的 orgId
console.log('设置耗材locationId:', { console.log('设置耗材locationId:', {
@@ -3325,6 +3452,10 @@ function setValue(row) {
prescriptionList.value[rowIndex.value].minUnitPrice = 0; prescriptionList.value[rowIndex.value].minUnitPrice = 0;
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1; prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].totalPrice = 0; prescriptionList.value[rowIndex.value].totalPrice = 0;
// 🔧 Bug Fix: 设置耗材的minUnitQuantity避免保存时null错误
prescriptionList.value[rowIndex.value].minUnitQuantity = row.quantity || 1;
// 🔧 Bug Fix: 设置耗材的categoryEnum避免数据库约束错误
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode || 3; // 默认为3
prescriptionList.value[rowIndex.value].positionName = row.positionName || ''; prescriptionList.value[rowIndex.value].positionName = row.positionName || '';
const finalLocationId = row.positionId || props.patientInfo.orgId; const finalLocationId = row.positionId || props.patientInfo.orgId;
prescriptionList.value[rowIndex.value].locationId = finalLocationId; prescriptionList.value[rowIndex.value].locationId = finalLocationId;
@@ -3967,6 +4098,16 @@ function convertDoseValues(row, index) {
// 总量计算,仅适用只有两种单位的情况 // 总量计算,仅适用只有两种单位的情况
function calculateTotalAmount(row, index) { function calculateTotalAmount(row, index) {
nextTick(() => { nextTick(() => {
// 🔧 Bug Fix: 处理耗材类型的总金额计算
if (row.adviceType == 4) {
row.totalPrice = (row.quantity * row.unitPrice).toFixed(6);
// 🔧 Bug Fix: 确保耗材的minUnitQuantity和minUnitCode正确设置
row.minUnitQuantity = row.quantity;
row.minUnitCode = row.unitCode;
row.minUnitCode_dictText = row.unitCode_dictText;
return;
}
if (row.adviceType == 2) { if (row.adviceType == 2) {
calculateTotalPrice(row, index); calculateTotalPrice(row, index);
return; return;
@@ -4424,6 +4565,36 @@ function handleOrderSetSaved() {
defineExpose({ getListInfo, getDiagnosisInfo }); defineExpose({ getListInfo, getDiagnosisInfo });
</script> </script>
<style lang="scss">
/* 皮试确认弹窗全局样式 - 反转按钮顺序,左边是是,右边是否 */
.skin-test-confirm-dialog.el-message-box {
.el-message-box__btns {
display: flex !important;
flex-direction: row-reverse !important;
justify-content: center !important;
.el-button {
margin-left: 10px !important;
margin-right: 10px !important;
}
}
}
/* 如果自定义类名不生效,使用更强的选择器 */
.el-message-box.skin-test-confirm-dialog {
.el-message-box__btns {
display: flex !important;
flex-direction: row-reverse !important;
justify-content: center !important;
.el-button {
margin-left: 10px !important;
margin-right: 10px !important;
}
}
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-table__expand-icon) { :deep(.el-table__expand-icon) {
display: none !important; display: none !important;

View File

@@ -0,0 +1,20 @@
/*
* @Description: Vue MCP Plugin - Enable MCP server for Vue application debugging
*/
import { VueMcp } from 'vite-plugin-vue-mcp'
export default function createVueMcpPlugin() {
return VueMcp({
// The host to listen on, default is `localhost`
host: 'localhost',
// Print the MCP server URL in the console
printUrl: true,
// Update the address of the MCP server in the cursor config file `.cursor/mcp.json`
updateCursorMcpJson: false, // Disable for OpenCode environment
// The path to the MCP server, default is `/__mcp`
mcpPath: '/__mcp',
})
}

1
openhis-v1.3 Submodule

Submodule openhis-v1.3 added at 582c2c8ac6

26
scripts/api_check.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# 诊断脚本 - 检查API返回
echo "=========================================="
echo "API 诊断工具"
echo "=========================================="
echo ""
# 请替换为实际的token和服务器地址
SERVER_URL="http://192.168.110.252:18080"
ENCOUNTER_ID="2038823905749327873"
echo "1. 检查 doctor-station API:"
echo "URL: ${SERVER_URL}/openhis/doctor-station/advice/request-base-info?encounterId=${ENCOUNTER_ID}"
echo ""
echo "2. 检查 reg-doctorstation API:"
echo "URL: ${SERVER_URL}/openhis/reg-doctorstation/advice-manage/reg-request-base-info?encounterId=${ENCOUNTER_ID}"
echo ""
echo "请在浏览器中访问上述URL查看返回的JSON数据"
echo ""
echo "需要确认:"
echo " - 是否有 adviceType=4 的记录?"
echo " - adviceName 是否有值?"
echo " - 手术医嘱是否包含在返回数据中?"

120
scripts/api_test.py Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""
直接调用 API 检查返回数据
"""
import requests
import json
import sys
# 配置
BASE_URL = "http://192.168.110.252:18080/openhis"
ENCOUNTER_ID = "2038823905749327873"
# 尝试不登录直接访问(如果允许)
# 或者需要添加 token
def check_api():
"""检查 API 返回"""
print("=" * 80)
print("直接调用 API 检查")
print("=" * 80)
print()
# 如果有 token请在这里设置
headers = {
# "Authorization": "Bearer YOUR_TOKEN_HERE",
"Content-Type": "application/json"
}
# 检查 doctor-station API
print("1. 检查 doctor-station API:")
url = f"{BASE_URL}/doctor-station/advice/request-base-info"
params = {"encounterId": ENCOUNTER_ID}
try:
print(f" URL: {url}")
print(f" 参数: {params}")
print()
response = requests.get(url, params=params, headers=headers, timeout=10)
print(f" 状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f" 返回 code: {data.get('code')}")
print(f" 返回 msg: {data.get('msg')}")
print()
if data.get("code") == 200:
records = data.get("data", [])
print(f" 记录总数: {len(records)}")
print()
# 查找手术医嘱 (adviceType=4)
surgery_records = []
for record in records:
if record.get("adviceType") == 4 or record.get("categoryEnum") == 4:
surgery_records.append(record)
print(f" 手术医嘱数量: {len(surgery_records)}")
print()
if surgery_records:
print(" 手术医嘱详情:")
for idx, record in enumerate(surgery_records, 1):
print(f"\n [{idx}]:")
print(f" requestId: {record.get('requestId')}")
print(f" adviceType: {record.get('adviceType')}")
print(f" adviceName: {record.get('adviceName')}")
print(f" categoryEnum: {record.get('categoryEnum')}")
content = record.get("contentJson", "{}")
if content:
try:
content_obj = (
json.loads(content)
if isinstance(content, str)
else content
)
print(
f" contentJson.surgeryName: {content_obj.get('surgeryName', 'N/A')}"
)
except:
print(f" contentJson: {content[:100]}...")
else:
print(" ✗ 未找到手术医嘱 (adviceType=4)")
print()
print(" 可能原因:")
print(" 1. 后端代码未正确部署")
print(" 2. 浏览器缓存了旧数据")
print(" 3. SQL 未生效")
# 打印所有记录查看 adviceType 分布
print()
print(" 所有记录的 adviceType 分布:")
type_count = {}
for record in records:
atype = record.get("adviceType")
type_count[atype] = type_count.get(atype, 0) + 1
for atype, count in sorted(type_count.items()):
print(f" adviceType={atype}: {count}")
else:
print(f" ✗ 返回错误: {data}")
else:
print(f" ✗ 请求失败: {response.status_code}")
print(f" 返回: {response.text[:500]}")
print()
print(" 可能需要登录 token请修改脚本添加 Authorization header")
except Exception as e:
print(f" ✗ 请求异常: {e}")
print()
print(" 请确认:")
print(" 1. 服务器地址是否正确")
print(" 2. 网络是否连通")
print(" 3. 是否需要登录 token")
if __name__ == "__main__":
check_api()

4
scripts/build.bat Normal file
View File

@@ -0,0 +1,4 @@
cd /d D:\his\openhis-server-new
call mvn clean package -DskipTests
echo Build complete!
pause

View File

@@ -0,0 +1,60 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("检查手术医嘱的 activity_id 和 advice_name")
print("=" * 80)
print()
# 查询手术医嘱的 activity_id
cursor.execute("""
SELECT
sr.id,
sr.prescription_no,
sr.activity_id,
ad.name as activity_name,
sr.content_json
FROM wor_service_request sr
LEFT JOIN wor_activity_definition ad ON ad.id = sr.activity_id AND ad.delete_flag = '0'
WHERE sr.category_enum = 4
AND sr.delete_flag = '0'
AND sr.encounter_id = 2038823905749327873
""")
rows = cursor.fetchall()
for row in rows:
print(f"医嘱ID: {row[0]}")
print(f"单号: {row[1]}")
print(f"activity_id: {row[2]}")
print(f"activity_name: {row[3]}")
if row[4]:
import json
try:
content = json.loads(row[4]) if isinstance(row[4], str) else row[4]
print(f"手术名称: {content.get('surgeryName', 'N/A')}")
except:
print(f"content_json: {row[4][:100]}")
print("-" * 80)
print()
print("问题SQL查询使用 activity_id 关联 wor_activity_definition 获取 advice_name")
print("但手术医嘱的 activity_id 可能为 null 或指向不存在的记录!")
print()
print("解决方案:应该从 content_json 中解析 surgeryName 作为 advice_name")
cursor.close()
conn.close()

View File

@@ -0,0 +1,71 @@
import requests
import json
# API配置
BASE_URL = "http://192.168.110.252:18080/openhis"
ENCOUNTER_ID = "2038823905749327873"
print("=" * 80)
print("API 返回数据检查")
print("=" * 80)
print()
# 检查 doctor-station API
print("1. 检查 doctor-station API:")
print(
f" URL: {BASE_URL}/doctor-station/advice/request-base-info?encounterId={ENCOUNTER_ID}"
)
print()
try:
response = requests.get(
f"{BASE_URL}/doctor-station/advice/request-base-info",
params={"encounterId": ENCOUNTER_ID},
timeout=10,
)
print(f" 状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
if data.get("code") == 200:
records = data.get("data", [])
print(f" 返回记录数: {len(records)}")
print()
# 查找手术医嘱 (adviceType=4)
surgery_records = [
r
for r in records
if r.get("adviceType") == 4 or r.get("categoryEnum") == 4
]
print(f" 手术医嘱数量: {len(surgery_records)}")
print()
for record in surgery_records:
print(f" 手术医嘱详情:")
print(f" - requestId: {record.get('requestId')}")
print(f" - adviceType: {record.get('adviceType')}")
print(f" - adviceName: {record.get('adviceName')}")
print(f" - categoryEnum: {record.get('categoryEnum')}")
print(
f" - contentJson: {record.get('contentJson', '')[:100] if record.get('contentJson') else 'None'}..."
)
print()
else:
print(f" 返回错误: {data.get('msg')}")
else:
print(f" 请求失败: {response.status_code}")
except Exception as e:
print(f" 请求异常: {e}")
print()
print("=" * 80)
print("诊断建议:")
print("=" * 80)
print()
print("如果手术医嘱未显示,可能原因:")
print(" 1. adviceType 仍为 3 (不是 4)")
print(" 2. adviceName 为空")
print(" 3. 前端过滤逻辑排除了该记录")
print(" 4. API返回了数据但前端未正确渲染")
print()

View File

@@ -0,0 +1,115 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("检查SQL查询的列顺序...")
print()
cursor.execute("""
SELECT
3 AS advice_type, -- 0
'test-id' AS request_id, -- 1
'test-key' AS unique_key, -- 2
123 AS requester_id, -- 3
NOW() AS request_time, -- 4
'1' AS biz_request_flag, -- 5
'{}' AS content_json, -- 6
null AS skin_test_flag, -- 7
null AS inject_flag, -- 8
null AS group_id, -- 9
'test-name' AS advice_name, -- 10
'' AS volume, -- 11
'' AS lot_number, -- 12
1 AS quantity, -- 13
'' AS unit_code, -- 14
1 AS status_enum, -- 15
'' AS method_code, -- 16
'' AS rate_code, -- 17
NULL AS dose, -- 18
'' AS dose_unit_code, -- 19
'ci-id' AS charge_item_id, -- 20
100 AS total_price, -- 21
1 AS charge_status, -- 22
'pos-id' AS position_id, -- 23
'pos-name' AS position_name, -- 24
null AS dispense_per_duration, -- 25
1 AS part_percent, -- 26
'' AS condition_definition_name, -- 27
COALESCE(null, 2) AS therapyEnum, -- 28 <- 这里
99 AS sort_number, -- 29
null AS based_on_id -- 30
""")
row = cursor.fetchone()
print(f"therapyEnum 列索引: 28, 值: {row[28]}")
print()
# 现在用实际的SQL查询
cursor.execute("""
SELECT
3 AS advice_type,
T1.id AS request_id,
T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = 1980296166230962178 THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName') AS advice_name,
'' AS volume,
'' AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
'' AS method_code,
'' AS rate_code,
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
ao.id AS position_id,
ao.name AS position_name,
null AS dispense_per_duration,
1 AS part_percent,
'' AS condition_definition_name,
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
99 AS sort_number,
T1.based_on_id AS based_on_id
FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3 ON T3.service_id = T1.ID AND T3.delete_flag = '0' AND
T3.service_table = 'wor_service_request'
LEFT JOIN adm_organization AS ao ON ao.ID = T1.org_id AND ao.delete_flag = '0'
WHERE T1.delete_flag = '0' AND T1.generate_source_enum = 1
AND T1.parent_id IS NULL
AND T1.encounter_id = 2038823905749327873
AND T1.category_enum = 4
""")
row = cursor.fetchone()
if row:
print("实际查询结果:")
print(f" advice_name (索引10): {row[10]}")
print(f" therapyEnum (索引28): {row[28]}")
print(f" status_enum (索引15): {row[15]}")
else:
print("未找到记录")
cursor.close()
conn.close()

40
scripts/check_columns.py Normal file
View File

@@ -0,0 +1,40 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("doc_request_form 表字段:")
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name = 'doc_request_form'
ORDER BY ordinal_position
""")
for row in cursor.fetchall():
print(f" {row[0]}")
print("\nwor_service_request 表字段:")
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name = 'wor_service_request'
ORDER BY ordinal_position
""")
for row in cursor.fetchall():
print(f" {row[0]}")
cursor.close()
conn.close()

63
scripts/check_db.py Normal file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import psycopg2
import json
from datetime import datetime
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def check_surgery():
conn = None
try:
print("Connecting to database...")
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("Connected!\n")
# Query recent surgery advice
cursor.execute("""
SELECT
id,
content_json::jsonb->>'surgeryName' as surgery_name,
content_json::jsonb->>'surgeryCode' as surgery_code,
create_time
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
ORDER BY create_time DESC
LIMIT 3
""")
rows = cursor.fetchall()
print("=" * 60)
print("Surgery Advice Check")
print("=" * 60)
if not rows:
print("No surgery advice found!")
else:
for row in rows:
print(f"\nID: {row[0]}")
print(f"surgeryName: {row[1] if row[1] else 'EMPTY'}")
print(f"surgeryCode: {row[2] if row[2] else 'EMPTY'}")
print(f"create_time: {row[3]}")
cursor.close()
except Exception as e:
print(f"Error: {e}")
finally:
if conn:
conn.close()
if __name__ == "__main__":
check_surgery()

59
scripts/check_empty.py Normal file
View File

@@ -0,0 +1,59 @@
import psycopg2
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def check():
conn = None
try:
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("=" * 80)
print("Records WITHOUT surgeryName (need fix)")
print("=" * 80)
cursor.execute("""
SELECT
wsr.id,
wsr.activity_id,
wsr.create_time,
cs.surgery_name,
cs.surgery_code
FROM wor_service_request wsr
LEFT JOIN cli_surgery cs ON cs.id = wsr.activity_id
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
AND (wsr.content_json::jsonb->>'surgeryName' IS NULL OR wsr.content_json::jsonb->>'surgeryName' = '')
ORDER BY wsr.create_time DESC
""")
rows = cursor.fetchall()
print(f"\nTotal: {len(rows)} records\n")
for row in rows:
print(f"ID: {row[0]}")
print(f" activity_id: {row[1]}")
print(f" create_time: {row[2]}")
print(f" cli_surgery.surgery_name: {'[HAS]' if row[3] else '[NULL]'}")
print(f" cli_surgery.surgery_code: {'[HAS]' if row[4] else '[NULL]'}")
print()
cursor.close()
except Exception as e:
print(f"Error: {e}")
finally:
if conn:
conn.close()
if __name__ == "__main__":
check()

246
scripts/check_filter.py Normal file
View File

@@ -0,0 +1,246 @@
import psycopg2
import json
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("检查前端过滤条件")
print("=" * 80)
print()
encounter_id = 2038823905749327873
practitioner_id = 1980296166230962178
# 查询所有数据
cursor.execute(
"""
(SELECT 1 AS advice_type,
T1.id AS request_id,
T1.id || '-1' AS unique_key,
T1.practitioner_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.practitioner_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
T1.skin_test_flag AS skin_test_flag,
T1.infusion_flag AS inject_flag,
T1.group_id AS group_id,
T2.NAME AS advice_name,
T3.total_volume AS volume,
T1.lot_number AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
T1.method_code AS method_code,
T1.rate_code AS rate_code,
T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id,
T4.total_price AS total_price,
T4.status_enum AS charge_status,
al.id AS position_id,
al.name AS position_name,
T1.dispense_per_duration AS dispense_per_duration,
T2.part_percent AS part_percent,
ccd.name AS condition_definition_name,
T1.therapy_enum AS therapyEnum,
T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id
FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
AND T2.delete_flag = '0'
LEFT JOIN med_medication AS T3 ON T3.medication_def_id = T2.ID
AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item AS T4 ON T4.service_id = T1.ID AND T4.delete_flag = '0' AND
T4.service_table = 'med_medication_request'
LEFT JOIN adm_location AS al ON al.ID = T1.perform_location AND al.delete_flag = '0'
LEFT JOIN cli_condition AS cc ON cc.id = T1.condition_id AND cc.delete_flag = '0'
LEFT JOIN cli_condition_definition AS ccd ON ccd.id = cc.definition_id
WHERE T1.delete_flag = '0' AND T1.tcm_flag = 0 AND T1.generate_source_enum = 1
AND T1.encounter_id = %s
AND T1.refund_medicine_id IS NULL)
UNION ALL
(SELECT 2 AS advice_type,
T1.id AS request_id,
T1.id || '-2' AS unique_key,
T1.requester_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
T2.NAME AS advice_name,
T2.SIZE AS volume,
T1.lot_number AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
'' AS method_code,
T1.rate_code AS rate_code,
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
al.id AS position_id,
al.name AS position_name,
null AS dispense_per_duration,
T2.part_percent AS part_percent,
'' AS condition_definition_name,
2 AS therapyEnum,
99 AS sort_number,
T1.based_on_id AS based_on_id
FROM wor_device_request AS T1
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3
ON T3.service_id = T1.ID AND T3.delete_flag = '0' AND
T3.service_table = 'wor_device_request'
LEFT JOIN adm_location AS al ON al.ID = T1.perform_location AND al.delete_flag = '0'
WHERE T1.delete_flag = '0' AND T1.generate_source_enum = 1
AND T1.encounter_id = %s
AND T1.refund_device_id IS NULL)
UNION ALL
(SELECT 3 AS advice_type,
T1.id AS request_id,
T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName') AS advice_name,
'' AS volume,
'' AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
'' AS method_code,
'' AS rate_code,
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
ao.id AS position_id,
ao.name AS position_name,
null AS dispense_per_duration,
1 AS part_percent,
'' AS condition_definition_name,
T1.therapy_enum AS therapyEnum,
99 AS sort_number,
T1.based_on_id AS based_on_id
FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3 ON T3.service_id = T1.ID AND T3.delete_flag = '0' AND
T3.service_table = 'wor_service_request'
LEFT JOIN adm_organization AS ao ON ao.ID = T1.org_id AND ao.delete_flag = '0'
WHERE T1.delete_flag = '0' AND T1.generate_source_enum = 1
AND T1.parent_id IS NULL
AND T1.encounter_id = %s)
ORDER BY status_enum
""",
(
practitioner_id,
encounter_id,
practitioner_id,
encounter_id,
practitioner_id,
encounter_id,
),
)
rows = cursor.fetchall()
print(f"查询到 {len(rows)} 条记录")
print()
# 模拟前端过滤
print("模拟前端过滤逻辑:")
print()
# 前端过滤条件
therapy_enum_filter = "" # 不过滤
order_class_code_filter = "" # 不过滤(全部)
order_status_filter = "" # 不过滤
filtered_count = 0
for i, row in enumerate(rows):
advice_type = row[0] # SQL中的 advice_type
request_id = row[1]
unique_key = row[2]
advice_name = row[10]
therapy_enum = row[25]
status_enum = row[14]
# 模拟前端的 adviceType 赋值
# 从前端代码看adviceType 应该等于 SQL 的 advice_type
item_advice_type = advice_type # 1=药品, 2=耗材, 3=项目
# 模拟过滤
therapy_match = not therapy_enum_filter or str(therapy_enum_filter) == str(
therapy_enum
)
class_match = not order_class_code_filter or str(order_class_code_filter) == str(
item_advice_type
)
status_match = not order_status_filter or (
str(order_status_filter) == str(status_enum) and request_id
)
if therapy_match and class_match and status_match:
filtered_count += 1
type_names = {1: "药品", 2: "耗材", 3: "诊疗/手术"}
type_name = type_names.get(advice_type, f"类型{advice_type}")
print(
f"✓ 第{i + 1}条通过过滤: advice_type={advice_type}({type_name}), advice_name={advice_name}, therapyEnum={therapy_enum}, status={status_enum}"
)
print()
print(f"过滤后剩余: {filtered_count}")
print()
# 检查手术医嘱
cursor.execute(
"""
SELECT
id,
category_enum,
COALESCE(
(SELECT name FROM wor_activity_definition WHERE id = sr.activity_id AND delete_flag = '0'),
sr.content_json::jsonb->>'surgeryName'
) as advice_name,
therapy_enum,
status_enum
FROM wor_service_request sr
WHERE category_enum = 4
AND delete_flag = '0'
AND encounter_id = %s
""",
(encounter_id,),
)
print("手术医嘱详情:")
for row in cursor.fetchall():
print(
f" ID: {row[0]}, category_enum: {row[1]}(手术), advice_name: {row[2]}, therapy_enum: {row[3]}, status: {row[4]}"
)
cursor.close()
conn.close()

View File

@@ -0,0 +1,113 @@
import psycopg2
import json
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def check_new_records():
conn = None
try:
print("Connecting to database...")
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("Connected!\n")
print("=" * 80)
print("New Surgery Records Check")
print("=" * 80)
# Check specific IDs
ids = ["2039583488323280897", "2039583488231006210"]
for id in ids:
cursor.execute(
"""
SELECT
wsr.id,
wsr.category_enum,
wsr.activity_id,
wsr.content_json::jsonb->>'surgeryName' as surgery_name,
wsr.content_json::jsonb->>'surgeryCode' as surgery_code,
wsr.content_json as full_json,
wsr.create_time,
cs.surgery_name as cli_surgery_name,
cs.surgery_code as cli_surgery_code
FROM wor_service_request wsr
LEFT JOIN cli_surgery cs ON cs.id = wsr.activity_id
WHERE wsr.id = %s
""",
(id,),
)
row = cursor.fetchone()
if row:
print(f"\nRecord ID: {row[0]}")
print(f" category_enum: {row[1]}")
print(f" activity_id: {row[2]}")
print(
f" surgeryName from content_json: {row[3] if row[3] else 'EMPTY'}"
)
print(
f" surgeryCode from content_json: {row[4] if row[4] else 'EMPTY'}"
)
print(f" cli_surgery_name: {row[7] if row[7] else 'N/A'}")
print(f" create_time: {row[6]}")
if row[5]:
try:
content = json.loads(row[5])
print(f" Full content_json keys: {list(content.keys())}")
except:
print(f" Raw content_json: {row[5][:100]}")
else:
print(f"\nRecord {id} not found!")
# Check most recent 3 surgery records
print("\n" + "=" * 80)
print("Most Recent 3 Surgery Records")
print("=" * 80)
cursor.execute("""
SELECT
wsr.id,
wsr.category_enum,
wsr.activity_id,
wsr.content_json::jsonb->>'surgeryName' as surgery_name,
wsr.content_json::jsonb->>'surgeryCode' as surgery_code,
wsr.create_time,
cs.surgery_name as cli_surgery_name
FROM wor_service_request wsr
LEFT JOIN cli_surgery cs ON cs.id = wsr.activity_id
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
ORDER BY wsr.create_time DESC
LIMIT 3
""")
rows = cursor.fetchall()
for row in rows:
print(f"\nID: {row[0]}")
print(f" surgeryName: {row[3] if row[3] else 'EMPTY'}")
print(f" surgeryCode: {row[4] if row[4] else 'EMPTY'}")
print(f" cli_surgery_name: {row[6] if row[6] else 'N/A'}")
print(f" create_time: {row[5]}")
cursor.close()
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
finally:
if conn:
conn.close()
if __name__ == "__main__":
check_new_records()

View File

@@ -0,0 +1,105 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("直接检查 ServiceRequest 数据")
print("=" * 80)
print()
# 直接查询表数据
cursor.execute("""
SELECT
id,
category_enum,
content_json,
activity_id
FROM wor_service_request
WHERE encounter_id = 2038823905749327873
AND delete_flag = '0'
AND parent_id IS NULL
""")
print("原始数据:")
for row in cursor.fetchall():
print(f" ID: {row[0]}")
print(f" category_enum: {row[1]}")
print(f" activity_id: {row[3]}")
if row[2]:
import json
try:
content = json.loads(row[2]) if isinstance(row[2], str) else row[2]
print(f" content_json.surgeryName: {content.get('surgeryName', 'N/A')}")
except:
print(f" content_json: {row[2][:100]}")
print()
print()
print("=" * 80)
print("测试 COALESCE 语法")
print("=" * 80)
print()
# 测试 COALESCE 语法
cursor.execute("""
SELECT
id,
COALESCE(category_enum, 3) as advice_type,
COALESCE(content_json::jsonb->>'surgeryName', '默认值') as surgery_name
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
LIMIT 1
""")
row = cursor.fetchone()
if row:
print(f"COALESCE 测试:")
print(f" ID: {row[0]}")
print(f" advice_type: {row[1]}")
print(f" surgery_name: {row[2]}")
else:
print("未找到记录")
print()
print("=" * 80)
print("检查 activity_definition 关联")
print("=" * 80)
print()
# 检查 activity_definition
cursor.execute("""
SELECT
sr.id,
sr.activity_id,
ad.name as activity_name,
COALESCE(ad.name, sr.content_json::jsonb->>'surgeryName') as coalesce_name
FROM wor_service_request sr
LEFT JOIN wor_activity_definition ad ON ad.id = sr.activity_id AND ad.delete_flag = '0'
WHERE sr.category_enum = 4
AND sr.delete_flag = '0'
AND sr.encounter_id = 2038823905749327873
""")
for row in cursor.fetchall():
print(f" ServiceRequest ID: {row[0]}")
print(f" activity_id: {row[1]}")
print(f" activity_name: {row[2]}")
print(f" coalesce_name: {row[3]}")
print()
cursor.close()
conn.close()

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
诊断脚本:检查手术医嘱是否正确保存和显示
"""
import psycopg2
import json
from datetime import datetime
# 数据库配置
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"database": "his",
"user": "postgres",
"password": "postgres",
}
def check_surgery_advice():
"""检查最近保存的手术医嘱"""
try:
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("=" * 80)
print("手术医嘱诊断报告")
print("=" * 80)
print(f"查询时间: {datetime.now()}")
print()
# 1. 查询最近的手术医嘱category_enum = 4
print("1. 查询最近保存的手术医嘱:")
print("-" * 80)
cursor.execute("""
SELECT
id,
category_enum,
activity_id,
content_json,
patient_id,
encounter_id,
create_time
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
ORDER BY create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
if not rows:
print("⚠️ 没有找到手术医嘱记录category_enum = 4")
else:
for row in rows:
print(f"\n手术医嘱 ID: {row[0]}")
print(f" category_enum: {row[1]}")
print(f" activity_id: {row[2]}")
print(f" patient_id: {row[4]}")
print(f" encounter_id: {row[5]}")
print(f" create_time: {row[6]}")
# 解析 content_json
try:
content = json.loads(row[3]) if row[3] else {}
surgery_name = content.get("surgeryName", "NOT FOUND")
surgery_code = content.get("surgeryCode", "NOT FOUND")
print(f" content_json.surgeryName: {surgery_name}")
print(f" content_json.surgeryCode: {surgery_code}")
if surgery_name == "NOT FOUND" or not surgery_name:
print(" ❌ 警告: content_json 中没有 surgeryName")
else:
print(" ✅ surgeryName 已正确保存")
except Exception as e:
print(f" ❌ 解析 content_json 失败: {e}")
print(f" 原始内容: {row[3]}")
print("\n" + "=" * 80)
# 2. 测试 SQL 查询是否能正确获取 advice_name
print("\n2. 测试 SQL 查询是否能正确获取手术名称:")
print("-" * 80)
cursor.execute("""
SELECT
wsr.id,
COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName', wsr.content_json::jsonb->>'adviceName') AS advice_name,
wsr.content_json::jsonb->>'surgeryName' AS surgery_name_from_json,
wad.NAME AS activity_name,
wsr.category_enum
FROM wor_service_request wsr
LEFT JOIN wor_activity_definition wad ON wad.ID = wsr.activity_id AND wad.delete_flag = '0'
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
ORDER BY wsr.create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
if not rows:
print("⚠️ 没有找到手术医嘱记录")
else:
for row in rows:
print(f"\n手术医嘱 ID: {row[0]}")
print(f" COALESCE advice_name: {row[1]}")
print(f" surgeryName from JSON: {row[2]}")
print(f" activity_definition name: {row[3]}")
print(f" category_enum: {row[4]}")
if not row[1]:
print(" ❌ 警告: advice_name 为空!")
else:
print(" ✅ advice_name 可以正确获取")
print("\n" + "=" * 80)
# 3. 检查是否存在 activity_id 对应的 wor_activity_definition
print("\n3. 检查手术医嘱的 activity_id 关联:")
print("-" * 80)
cursor.execute("""
SELECT
wsr.id,
wsr.activity_id,
wad.id AS def_id,
wad.name AS def_name,
CASE WHEN wad.id IS NULL THEN 'MISSING' ELSE 'OK' END AS status
FROM wor_service_request wsr
LEFT JOIN wor_activity_definition wad ON wad.ID = wsr.activity_id
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
ORDER BY wsr.create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
for row in rows:
status_icon = "" if row[4] == "OK" else ""
print(
f"{status_icon} service_request.id={row[0]}, activity_id={row[1]}, definition={row[3] or 'NULL'}"
)
print("\n" + "=" * 80)
print("诊断完成")
print("=" * 80)
cursor.close()
conn.close()
except Exception as e:
print(f"❌ 诊断失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
check_surgery_advice()

117
scripts/check_surgery_db.py Normal file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import psycopg2
import json
from datetime import datetime
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def check_surgery_advice():
conn = None
try:
print("Connecting to database...")
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("Connected!\n")
print("=" * 80)
print("Surgery Advice Diagnostic Report")
print("=" * 80)
print(f"Time: {datetime.now()}")
print()
# 1. Query recent surgery advice
print("1. Recent surgery advice records:")
print("-" * 80)
cursor.execute("""
SELECT
id,
category_enum,
activity_id,
content_json::jsonb->>'surgeryName' as surgery_name,
content_json::jsonb->>'surgeryCode' as surgery_code,
create_time
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
ORDER BY create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
if not rows:
print("No surgery advice found (category_enum = 4)")
else:
for row in rows:
print(f"\nID: {row[0]}")
print(f" category_enum: {row[1]}")
print(f" activity_id: {row[2]}")
print(f" surgeryName from JSON: {row[3]}")
print(f" surgeryCode from JSON: {row[4]}")
print(f" create_time: {row[5]}")
if not row[3]:
print(" WARNING: surgeryName is EMPTY!")
else:
print(" OK: surgeryName exists")
print("\n" + "=" * 80)
# 2. Test SQL COALESCE query
print("\n2. Testing SQL COALESCE query:")
print("-" * 80)
cursor.execute("""
SELECT
wsr.id,
COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName', wsr.content_json::jsonb->>'adviceName') AS advice_name,
wsr.content_json::jsonb->>'surgeryName' AS surgery_name_from_json,
wad.NAME AS activity_name,
wsr.activity_id
FROM wor_service_request wsr
LEFT JOIN wor_activity_definition wad ON wad.ID = wsr.activity_id AND wad.delete_flag = '0'
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
ORDER BY wsr.create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
for row in rows:
print(f"\nID: {row[0]}")
print(f" COALESCE advice_name: {row[1]}")
print(f" surgeryName from JSON: {row[2]}")
print(f" activity_definition name: {row[3]}")
print(f" activity_id: {row[4]}")
if not row[1]:
print(" WARNING: advice_name is EMPTY!")
else:
print(" OK: advice_name exists")
print("\n" + "=" * 80)
print("Diagnostic complete")
print("=" * 80)
cursor.close()
except Exception as e:
print(f"ERROR: {e}")
import traceback
traceback.print_exc()
finally:
if conn:
conn.close()
if __name__ == "__main__":
check_surgery_advice()

View File

@@ -0,0 +1,83 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("检查手术医嘱的详细信息")
print("=" * 80)
print()
# 查询手术医嘱的详细信息
cursor.execute("""
SELECT
id,
prescription_no,
category_enum,
status_enum,
patient_id,
encounter_id,
activity_id,
generate_source_enum,
content_json,
create_time
FROM wor_service_request
WHERE prescription_no = 'OP202603311433'
AND delete_flag = '0'
""")
row = cursor.fetchone()
if row:
print("手术医嘱详细信息:")
print(f" ID: {row[0]}")
print(f" 单号: {row[1]}")
print(f" category_enum: {row[2]} (4=手术)")
print(f" status_enum: {row[3]} (1=待签发)")
print(f" patient_id: {row[4]}")
print(f" encounter_id: {row[5]}")
print(f" activity_id: {row[6]}")
print(f" generate_source_enum: {row[7]} (1=医生处方)")
print(f" content_json: {row[8]}")
print(f" create_time: {row[9]}")
else:
print("未找到手术医嘱")
print()
print("=" * 80)
# 查询该就诊的所有医嘱
cursor.execute("""
SELECT
id,
prescription_no,
category_enum,
status_enum,
activity_id
FROM wor_service_request
WHERE encounter_id = 2038823905749327873
AND delete_flag = '0'
ORDER BY category_enum, create_time DESC
""")
rows = cursor.fetchall()
print(f"就诊ID 2038823905749327873 的所有医嘱(共{len(rows)}条):")
for row in rows:
cat_names = {1: "西药", 2: "耗材", 3: "诊疗", 4: "手术"}
cat_name = cat_names.get(row[2], f"类型{row[2]}")
print(
f" ID: {row[0]}, 单号: {row[1]}, 类型: {cat_name}({row[2]}), 状态: {row[3]}, activity_id: {row[4]}"
)
cursor.close()
conn.close()

View File

@@ -0,0 +1,53 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("wor_service_request therapy相关列:")
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name = 'wor_service_request'
AND column_name LIKE '%therapy%'
""")
for row in cursor.fetchall():
print(f" {row[0]}")
print()
print("med_medication_request therapy相关列:")
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name = 'med_medication_request'
AND column_name LIKE '%therapy%'
""")
for row in cursor.fetchall():
print(f" {row[0]}")
print()
print("wor_device_request therapy相关列:")
cursor.execute("""
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'hisdev'
AND table_name = 'wor_device_request'
AND column_name LIKE '%therapy%'
""")
for row in cursor.fetchall():
print(f" {row[0]}")
cursor.close()
conn.close()

View File

@@ -0,0 +1,111 @@
import psycopg2
import json
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def diagnose():
conn = None
try:
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("=" * 80)
print("Diagnosis: Surgery Data Flow")
print("=" * 80)
# 1. Find latest cli_surgery records
print("\n1. Latest cli_surgery records:")
print("-" * 80)
cursor.execute("""
SELECT id, surgery_name, surgery_code, surgery_no, create_time
FROM cli_surgery
WHERE delete_flag = '0'
ORDER BY create_time DESC
LIMIT 3
""")
rows = cursor.fetchall()
for row in rows:
print(f"ID: {row[0]}")
print(f" surgery_name: {'[HAS VALUE]' if row[1] else '[NULL/EMPTY]'}")
print(f" surgery_code: {'[HAS VALUE]' if row[2] else '[NULL/EMPTY]'}")
# 2. Check corresponding wor_service_request
print("\n2. Check wor_service_request:")
print("-" * 80)
for cli_id, cli_name, cli_code, cli_no, cli_time in rows:
cursor.execute(
"""
SELECT
wsr.id,
wsr.content_json::jsonb->>'surgeryName' as surgery_name_json,
wsr.content_json::jsonb->>'surgeryCode' as surgery_code_json,
wsr.create_time
FROM wor_service_request wsr
WHERE wsr.activity_id = %s
AND wsr.category_enum = 4
AND wsr.delete_flag = '0'
""",
(cli_id,),
)
wsr_row = cursor.fetchone()
if wsr_row:
print(f"cli_surgery ID: {cli_id}")
print(
f" cli_surgery surgery_name: {'[HAS VALUE]' if cli_name else '[NULL]'}"
)
print(
f" cli_surgery surgery_code: {'[HAS VALUE]' if cli_code else '[NULL]'}"
)
print(f" wor_service_request ID: {wsr_row[0]}")
print(
f" wor_service_request surgeryName: {'[HAS VALUE]' if wsr_row[1] else '[NULL/EMPTY]'}"
)
print(
f" wor_service_request surgeryCode: {'[HAS VALUE]' if wsr_row[2] else '[NULL/EMPTY]'}"
)
if not wsr_row[1] and cli_name:
print(
f" *** PROBLEM: cli_surgery has name but wor_service_request does NOT! ***"
)
else:
print(
f"cli_surgery ID: {cli_id} - No matching wor_service_request found!"
)
# 3. Statistics
print("\n3. Statistics:")
print("-" * 80)
cursor.execute("""
SELECT COUNT(*)
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
AND (content_json::jsonb->>'surgeryName' IS NULL OR content_json::jsonb->>'surgeryName' = '')
""")
count = cursor.fetchone()[0]
print(f" Surgery advice without surgeryName: {count}")
cursor.close()
except Exception as e:
print(f"Error: {e}")
finally:
if conn:
conn.close()
if __name__ == "__main__":
diagnose()

182
scripts/execute_repair.py Normal file
View File

@@ -0,0 +1,182 @@
import psycopg2
import json
import sys
# 设置UTF-8编码
sys.stdout.reconfigure(encoding="utf-8")
# 数据库连接
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
# 设置schema
cursor.execute("SET search_path TO hisdev, public")
conn.commit()
# 1. 查询需要修复的记录数
print("=" * 60)
print("Bug #318 历史数据修复")
print("=" * 60)
print()
print("步骤1: 查询需要修复的手术申请单...")
cursor.execute("""
SELECT COUNT(*)
FROM doc_request_form rf
LEFT JOIN wor_service_request sr ON sr.prescription_no = rf.prescription_no
AND sr.delete_flag = '0'
WHERE rf.type_code = 'PROCEDURE'
AND rf.delete_flag = '0'
AND sr.id IS NULL
""")
count = cursor.fetchone()[0]
print(f"✓ 发现 {count} 条需要修复的手术申请单")
print()
if count == 0:
print("没有需要修复的数据")
else:
# 2. 查看部分记录详情
print("步骤2: 查看部分记录详情...")
cursor.execute("""
SELECT rf.id, rf.prescription_no, rf.encounter_id, rf.patient_id, rf.create_time
FROM doc_request_form rf
LEFT JOIN wor_service_request sr ON sr.prescription_no = rf.prescription_no
AND sr.delete_flag = '0'
WHERE rf.type_code = 'PROCEDURE'
AND rf.delete_flag = '0'
AND sr.id IS NULL
ORDER BY rf.create_time DESC
LIMIT 5
""")
rows = cursor.fetchall()
for row in rows:
print(
f" ID: {row[0]}, 单号: {row[1]}, 就诊: {row[2]}, 患者: {row[3]}, 时间: {row[4]}"
)
print()
# 3. 执行修复 - 生成ServiceRequest
print("步骤3: 生成手术医嘱ServiceRequest...")
cursor.execute("""
INSERT INTO wor_service_request (
bus_no, prescription_no, status_enum, generate_source_enum,
therapy_enum, quantity, unit_code, category_enum,
patient_id, requester_id, encounter_id, authored_time,
org_id, content_json, delete_flag, create_time, create_by, tenant_id
)
SELECT
LPAD(FLOOR(RANDOM() * 10000)::TEXT, 4, '0'),
rf.prescription_no,
1, 1, 2, 1, '', 4,
rf.patient_id, rf.requester_id, rf.encounter_id, rf.create_time,
rf.org_id, rf.desc_json, '0', rf.create_time, rf.requester_id, 1
FROM doc_request_form rf
LEFT JOIN wor_service_request sr ON sr.prescription_no = rf.prescription_no
AND sr.delete_flag = '0'
WHERE rf.type_code = 'PROCEDURE'
AND rf.delete_flag = '0'
AND sr.id IS NULL
RETURNING id, prescription_no
""")
new_requests = cursor.fetchall()
print(f"✓ 成功生成 {len(new_requests)} 条手术医嘱")
print()
# 4. 生成ChargeItem
print("步骤4: 生成收费项目ChargeItem...")
# 获取刚插入的ServiceRequest
prescription_nos = [r[1] for r in new_requests]
if prescription_nos:
cursor.execute(
"""
INSERT INTO adm_charge_item (
bus_no, status_enum, generate_source_enum, patient_id,
context_enum, encounter_id, enterer_id, entered_date,
service_table, service_id, product_table, requesting_org_id,
quantity_value, quantity_unit, unit_price, total_price,
delete_flag, create_time, create_by, tenant_id
)
SELECT
'CI' || sr.bus_no, 1, 1, sr.patient_id, 3, sr.encounter_id,
sr.requester_id, sr.create_time, 'wor_service_request', sr.id,
'wor_activity_definition', sr.org_id, 1, '',
COALESCE((sr.content_json::jsonb->>'surgeryFee')::numeric, 0),
COALESCE((sr.content_json::jsonb->>'surgeryFee')::numeric, 0),
'0', sr.create_time, sr.requester_id, 1
FROM wor_service_request sr
WHERE sr.prescription_no = ANY(%s)
AND sr.category_enum = 4
AND sr.delete_flag = '0'
""",
(prescription_nos,),
)
# 麻醉费用
cursor.execute(
"""
INSERT INTO adm_charge_item (
bus_no, status_enum, generate_source_enum, patient_id,
context_enum, encounter_id, enterer_id, entered_date,
service_table, service_id, product_table, requesting_org_id,
quantity_value, quantity_unit, unit_price, total_price,
delete_flag, create_time, create_by, tenant_id
)
SELECT
'CI' || sr.bus_no || '_A', 1, 1, sr.patient_id, 3, sr.encounter_id,
sr.requester_id, sr.create_time, 'wor_service_request', sr.id,
'wor_activity_definition', sr.org_id, 1, '',
(sr.content_json::jsonb->>'anesthesiaFee')::numeric,
(sr.content_json::jsonb->>'anesthesiaFee')::numeric,
'0', sr.create_time, sr.requester_id, 1
FROM wor_service_request sr
WHERE sr.prescription_no = ANY(%s)
AND sr.category_enum = 4
AND sr.delete_flag = '0'
AND sr.content_json::jsonb->>'anesthesiaFee' IS NOT NULL
AND (sr.content_json::jsonb->>'anesthesiaFee')::numeric > 0
""",
(prescription_nos,),
)
print(f"✓ 成功生成收费项目")
print()
# 5. 验证结果
print("步骤5: 验证修复结果...")
cursor.execute("""
SELECT COUNT(*) FROM wor_service_request
WHERE category_enum = 4 AND delete_flag = '0'
""")
service_count = cursor.fetchone()[0]
cursor.execute("""
SELECT COUNT(*) FROM adm_charge_item ci
WHERE ci.service_table = 'wor_service_request'
AND EXISTS (
SELECT 1 FROM wor_service_request sr
WHERE sr.id = ci.service_id AND sr.category_enum = 4
)
""")
charge_count = cursor.fetchone()[0]
print(f"✓ 手术医嘱总数: {service_count}")
print(f"✓ 手术收费项目总数: {charge_count}")
print()
# 提交事务
conn.commit()
print("=" * 60)
print("✓ 修复完成!")
print("=" * 60)
cursor.close()
conn.close()

271
scripts/final_check.py Normal file
View File

@@ -0,0 +1,271 @@
import psycopg2
import json
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("检查手术医嘱的完整数据")
print("=" * 80)
print()
# 检查手术医嘱的所有字段
cursor.execute("""
SELECT
sr.id,
sr.prescription_no,
sr.category_enum,
sr.status_enum,
sr.therapy_enum,
sr.patient_id,
sr.encounter_id,
sr.requester_id,
sr.activity_id,
sr.quantity,
sr.unit_code,
sr.content_json,
sr.generate_source_enum,
sr.delete_flag,
sr.parent_id,
sr.create_time
FROM wor_service_request sr
WHERE sr.category_enum = 4
AND sr.delete_flag = '0'
AND sr.encounter_id = 2038823905749327873
""")
rows = cursor.fetchall()
print(f"手术医嘱数量: {len(rows)}")
print()
for row in rows:
print(f"ID: {row[0]}")
print(f"单号: {row[1]}")
print(f"category_enum: {row[2]} (4=手术)")
print(f"status_enum: {row[3]} (1=待签发)")
print(f"therapy_enum: {row[4]}")
print(f"patient_id: {row[5]}")
print(f"encounter_id: {row[6]}")
print(f"requester_id: {row[7]}")
print(f"activity_id: {row[8]}")
print(f"quantity: {row[9]}")
print(f"unit_code: {row[10]}")
print(f"content_json: {row[11][:100] if row[11] else 'None'}...")
print(f"generate_source_enum: {row[12]}")
print(f"delete_flag: {row[13]}")
print(f"parent_id: {row[14]}")
print(f"create_time: {row[15]}")
print("-" * 80)
print()
# 检查是否有parent_id不为null的记录子医嘱
print("检查是否有子医嘱parent_id不为null")
cursor.execute("""
SELECT COUNT(*)
FROM wor_service_request sr
WHERE sr.encounter_id = 2038823905749327873
AND sr.parent_id IS NOT NULL
AND sr.delete_flag = '0'
""")
parent_count = cursor.fetchone()[0]
print(f"子医嘱数量: {parent_count}")
print()
# 检查SQL查询返回的完整数据模拟getRegRequestBaseInfo
print("=" * 80)
print("模拟完整的API查询包含所有UNION")
print("=" * 80)
print()
practitioner_id = 1980296166230962178
encounter_id = 2038823905749327873
# 使用完整的SQL查询包含COALESCE修复
cursor.execute(
"""
(SELECT 1 AS advice_type,
T1.id AS request_id,
T1.id || '-1' AS unique_key,
T1.practitioner_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.practitioner_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
T1.skin_test_flag AS skin_test_flag,
T1.infusion_flag AS inject_flag,
T1.group_id AS group_id,
T2.NAME AS advice_name,
T3.total_volume AS volume,
T1.lot_number AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
T1.method_code AS method_code,
T1.rate_code AS rate_code,
T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id,
T4.total_price AS total_price,
T4.status_enum AS charge_status,
al.id AS position_id,
al.name AS position_name,
T1.dispense_per_duration AS dispense_per_duration,
T2.part_percent AS part_percent,
ccd.name AS condition_definition_name,
COALESCE(T1.therapy_enum, 1) AS therapyEnum,
T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id
FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
AND T2.delete_flag = '0'
LEFT JOIN med_medication AS T3 ON T3.medication_def_id = T2.ID
AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item AS T4 ON T4.service_id = T1.ID AND T4.delete_flag = '0' AND
T4.service_table = 'med_medication_request'
LEFT JOIN adm_location AS al ON al.ID = T1.perform_location AND al.delete_flag = '0'
LEFT JOIN cli_condition AS cc ON cc.id = T1.condition_id AND cc.delete_flag = '0'
LEFT JOIN cli_condition_definition AS ccd ON ccd.id = cc.definition_id
WHERE T1.delete_flag = '0' AND T1.tcm_flag = 0 AND T1.generate_source_enum = 1
AND T1.encounter_id = %s
AND T1.refund_medicine_id IS NULL)
UNION ALL
(SELECT 2 AS advice_type,
T1.id AS request_id,
T1.id || '-2' AS unique_key,
T1.requester_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
T2.NAME AS advice_name,
T2.SIZE AS volume,
T1.lot_number AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
'' AS method_code,
T1.rate_code AS rate_code,
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
al.id AS position_id,
al.name AS position_name,
null AS dispense_per_duration,
T2.part_percent AS part_percent,
'' AS condition_definition_name,
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
99 AS sort_number,
T1.based_on_id AS based_on_id
FROM wor_device_request AS T1
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3
ON T3.service_id = T1.ID AND T3.delete_flag = '0' AND
T3.service_table = 'wor_device_request'
LEFT JOIN adm_location AS al ON al.ID = T1.perform_location AND al.delete_flag = '0'
WHERE T1.delete_flag = '0' AND T1.generate_source_enum = 1
AND T1.encounter_id = %s
AND T1.refund_device_id IS NULL)
UNION ALL
(SELECT 3 AS advice_type,
T1.id AS request_id,
T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id,
T1.create_time AS request_time,
CASE WHEN T1.requester_id = %s THEN '1' ELSE '0' END AS biz_request_flag,
T1.content_json AS content_json,
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName') AS advice_name,
'' AS volume,
'' AS lot_number,
T1.quantity AS quantity,
T1.unit_code AS unit_code,
T1.status_enum AS status_enum,
'' AS method_code,
'' AS rate_code,
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
ao.id AS position_id,
ao.name AS position_name,
null AS dispense_per_duration,
1 AS part_percent,
'' AS condition_definition_name,
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
99 AS sort_number,
T1.based_on_id AS based_on_id
FROM wor_service_request AS T1
LEFT JOIN wor_activity_definition AS T2
ON T2.ID = T1.activity_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item AS T3 ON T3.service_id = T1.ID AND T3.delete_flag = '0' AND
T3.service_table = 'wor_service_request'
LEFT JOIN adm_organization AS ao ON ao.ID = T1.org_id AND ao.delete_flag = '0'
WHERE T1.delete_flag = '0' AND T1.generate_source_enum = 1
AND T1.parent_id IS NULL
AND T1.encounter_id = %s)
ORDER BY status_enum
""",
(
practitioner_id,
encounter_id,
practitioner_id,
encounter_id,
practitioner_id,
encounter_id,
),
)
api_rows = cursor.fetchall()
print(f"API返回数据总数: {len(api_rows)}")
print()
# 检查手术医嘱advice_type=3
surgery_rows = [r for r in api_rows if r[0] == 3]
print(f"手术医嘱advice_type=3数量: {len(surgery_rows)}")
print()
for row in surgery_rows:
advice_name = row[10]
therapy_enum = row[25]
status = row[14]
print(f" advice_name: {advice_name}")
print(f" therapyEnum: {therapy_enum} (2=临时)")
print(f" status_enum: {status}")
print()
cursor.close()
conn.close()
print("=" * 80)
print("结论:")
if len(surgery_rows) > 0:
print(f"✓ API查询返回了 {len(surgery_rows)} 条手术医嘱")
print("✓ advice_name 已正确返回")
print("✓ therapyEnum 默认为 2 (临时)")
print()
print("如果前端仍不显示,问题可能:")
print(" 1. 前端缓存 - 请清除浏览器缓存")
print(" 2. 代码未正确部署 - 请检查实际运行的代码版本")
print(" 3. 前端过滤逻辑 - 请检查浏览器控制台网络请求返回的数据")
else:
print("✗ API查询未返回手术医嘱")
print(" 请检查数据库数据或SQL条件")

126
scripts/fix_content_json.py Normal file
View File

@@ -0,0 +1,126 @@
import psycopg2
import json
import sys
sys.stdout.reconfigure(encoding="utf-8")
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
print("=" * 80)
print("修复手术医嘱 content_json 为 null 的问题")
print("=" * 80)
print()
# 1. 查询所有 content_json 为 null 的手术医嘱
cursor.execute("""
SELECT
id,
prescription_no,
content_json
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
AND content_json IS NULL
""")
rows = cursor.fetchall()
print(f"发现 {len(rows)} 条 content_json 为 null 的手术医嘱")
print()
# 2. 从对应的手术申请单获取 desc_json 并更新
cursor.execute("""
SELECT
sr.id,
sr.prescription_no,
rf.desc_json
FROM wor_service_request sr
JOIN doc_request_form rf ON rf.prescription_no = sr.prescription_no
WHERE sr.category_enum = 4
AND sr.delete_flag = '0'
AND sr.content_json IS NULL
AND rf.delete_flag = '0'
""")
fix_rows = cursor.fetchall()
print(f"找到 {len(fix_rows)} 条可以修复的记录")
print()
# 3. 更新 content_json
update_count = 0
for row in fix_rows:
service_id = row[0]
prescription_no = row[1]
desc_json = row[2]
if desc_json:
try:
# 解析 desc_json
if isinstance(desc_json, str):
desc_data = json.loads(desc_json)
else:
desc_data = desc_json
# 构建 content_json
content_data = {
"surgeryName": desc_data.get("surgeryName", ""),
"surgeryCode": desc_data.get("surgeryCode", ""),
"surgeryFee": desc_data.get("surgeryFee", "0"),
"anesthesiaFee": desc_data.get("anesthesiaFee", "0"),
"plannedTime": desc_data.get("plannedTime", ""),
"surgeryIndication": desc_data.get("surgeryIndication", ""),
"preoperativeDiagnosis": desc_data.get("preoperativeDiagnosis", ""),
}
content_json = json.dumps(content_data, ensure_ascii=False)
# 更新数据库
cursor.execute(
"""
UPDATE wor_service_request
SET content_json = %s
WHERE id = %s
""",
(content_json, service_id),
)
update_count += 1
print(f"✓ 更新 ID={service_id}, 单号={prescription_no}")
except Exception as e:
print(f"✗ 更新失败 ID={service_id}: {e}")
conn.commit()
print()
print(f"✓ 成功修复 {update_count} 条记录")
print()
# 4. 验证修复结果
cursor.execute("""
SELECT
id,
prescription_no,
content_json
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
AND content_json IS NULL
""")
remaining = cursor.fetchall()
print(f"剩余 {len(remaining)} 条 content_json 为 null 的记录")
cursor.close()
conn.close()
print()
print("=" * 80)
print("修复完成!请刷新医嘱列表页面查看效果。")
print("=" * 80)

90
scripts/fix_remaining.py Normal file
View File

@@ -0,0 +1,90 @@
import psycopg2
DB_CONFIG = {
"host": "192.168.110.252",
"port": 15432,
"database": "postgresql",
"user": "postgresql",
"password": "Jchl1528",
"options": "-c search_path=hisdev",
}
def fix():
conn = None
try:
conn = psycopg2.connect(**DB_CONFIG)
cursor = conn.cursor()
print("=" * 80)
print("Fixing remaining surgery records")
print("=" * 80)
# Count before
cursor.execute("""
SELECT COUNT(*)
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
AND (content_json::jsonb->>'surgeryName' IS NULL OR content_json::jsonb->>'surgeryName' = '')
""")
before = cursor.fetchone()[0]
print(f"\nBefore fix: {before} records without surgeryName")
# Execute fix
cursor.execute("""
UPDATE wor_service_request wsr
SET content_json =
COALESCE(
(wsr.content_json::jsonb || jsonb_build_object(
'surgeryName', cs.surgery_name,
'surgeryCode', COALESCE(cs.surgery_code, '')
))::text,
jsonb_build_object(
'surgeryName', cs.surgery_name,
'surgeryCode', COALESCE(cs.surgery_code, '')
)::text
)
FROM cli_surgery cs
WHERE wsr.category_enum = 4
AND wsr.delete_flag = '0'
AND (wsr.content_json::jsonb->>'surgeryName' IS NULL OR wsr.content_json::jsonb->>'surgeryName' = '')
AND cs.id = wsr.activity_id
AND cs.surgery_name IS NOT NULL
AND cs.delete_flag = '0'
""")
fixed = cursor.rowcount
conn.commit()
print(f"Fixed: {fixed} records")
# Count after
cursor.execute("""
SELECT COUNT(*)
FROM wor_service_request
WHERE category_enum = 4
AND delete_flag = '0'
AND (content_json::jsonb->>'surgeryName' IS NULL OR content_json::jsonb->>'surgeryName' = '')
""")
after = cursor.fetchone()[0]
print(f"After fix: {after} records without surgeryName")
if after == 0:
print("\n*** ALL RECORDS FIXED! ***")
else:
print(f"\n*** {after} records still need attention ***")
cursor.close()
except Exception as e:
print(f"Error: {e}")
if conn:
conn.rollback()
finally:
if conn:
conn.close()
if __name__ == "__main__":
fix()

View File

@@ -0,0 +1,206 @@
import psycopg2
import sys
sys.stdout.reconfigure(encoding="utf-8")
# 数据库连接
conn = psycopg2.connect(
host="192.168.110.252",
port=15432,
database="postgresql",
user="postgresql",
password="Jchl1528",
)
cursor = conn.cursor()
cursor.execute("SET search_path TO hisdev, public")
conn.commit()
print("=" * 80)
print("查询患者:王俊彭 (病历号: PN0000000038)")
print("=" * 80)
print()
# 1. 查询患者基本信息和就诊ID
print("步骤1: 查询患者基本信息...")
cursor.execute("""
SELECT
p.id as patient_id,
p.name as patient_name,
e.id as encounter_id,
e.create_time as encounter_time
FROM adm_patient p
LEFT JOIN adm_encounter e ON e.patient_id = p.id
WHERE p.name = '王俊彭'
ORDER BY e.create_time DESC
LIMIT 5
""")
patient_rows = cursor.fetchall()
if not patient_rows:
print("✗ 未找到患者信息")
else:
for row in patient_rows:
print(
f" 患者ID: {row[0]}, 姓名: {row[1]}, 就诊ID: {row[2]}, 就诊时间: {row[3]}"
)
# 获取最新的就诊ID
encounter_id = patient_rows[0][2]
patient_id = patient_rows[0][0]
print(f"\n使用就诊ID: {encounter_id}")
print()
# 2. 查询手术申请单
print("步骤2: 查询手术申请单...")
cursor.execute(
"""
SELECT
id,
prescription_no,
type_code,
name,
create_time,
desc_json
FROM doc_request_form
WHERE encounter_id = %s
AND type_code = 'PROCEDURE'
AND delete_flag = '0'
ORDER BY create_time DESC
""",
(encounter_id,),
)
request_forms = cursor.fetchall()
if not request_forms:
print("✗ 未找到手术申请单")
else:
print(f"✓ 找到 {len(request_forms)} 条手术申请单:")
for rf in request_forms:
print(
f" 申请单ID: {rf[0]}, 单号: {rf[1]}, 类型: {rf[2]}, 名称: {rf[3]}, 时间: {rf[4]}"
)
if rf[5]:
try:
import json
desc = json.loads(rf[5])
surgery_name = desc.get("surgeryName", "N/A")
surgery_fee = desc.get("surgeryFee", "N/A")
print(f" -> 手术名称: {surgery_name}, 手术费用: {surgery_fee}")
except:
print(f" -> 手术详情: {rf[5][:100]}")
print()
# 3. 查询手术医嘱ServiceRequest
print("步骤3: 查询手术医嘱ServiceRequest...")
cursor.execute(
"""
SELECT
id,
prescription_no,
status_enum,
category_enum,
patient_id,
encounter_id,
create_time,
content_json
FROM wor_service_request
WHERE patient_id = %s
AND category_enum = 4
AND delete_flag = '0'
ORDER BY create_time DESC
""",
(patient_id,),
)
service_requests = cursor.fetchall()
if not service_requests:
print("✗ 未找到手术医嘱")
print("\n⚠️ 问题确认:有手术申请单但没有对应的手术医嘱!")
else:
print(f"✓ 找到 {len(service_requests)} 条手术医嘱:")
for sr in service_requests:
status_text = {1: "待签发", 2: "已签发", 3: "已执行"}.get(
sr[2], f"状态{sr[2]}"
)
print(
f" 医嘱ID: {sr[0]}, 单号: {sr[1]}, 状态: {status_text}, 就诊ID: {sr[5]}, 时间: {sr[6]}"
)
print()
# 4. 查询收费项目
print("步骤4: 查询收费项目ChargeItem...")
cursor.execute(
"""
SELECT
id,
bus_no,
status_enum,
total_price,
service_id
FROM adm_charge_item
WHERE patient_id = %s
AND service_table = 'wor_service_request'
AND delete_flag = '0'
ORDER BY create_time DESC
""",
(patient_id,),
)
charge_items = cursor.fetchall()
if not charge_items:
print("✗ 未找到收费项目")
else:
print(f"✓ 找到 {len(charge_items)} 条收费项目:")
for ci in charge_items:
print(f" 收费ID: {ci[0]}, 单号: {ci[1]}, 金额: {ci[3]}, 关联医嘱: {ci[4]}")
print()
# 5. 查询医嘱列表(用于对比)
print("步骤5: 查询该患者的所有医嘱...")
cursor.execute(
"""
SELECT
category_enum,
status_enum,
count(*) as count
FROM wor_service_request
WHERE patient_id = %s
AND delete_flag = '0'
GROUP BY category_enum, status_enum
ORDER BY category_enum
""",
(patient_id,),
)
categories = cursor.fetchall()
if categories:
print("✓ 医嘱统计:")
cat_names = {1: "西药", 2: "耗材", 3: "诊疗", 4: "手术"}
stat_names = {1: "待签发", 2: "已签发", 3: "已执行"}
for cat in categories:
cat_name = cat_names.get(cat[0], f"类型{cat[0]}")
stat_name = stat_names.get(cat[1], f"状态{cat[1]}")
print(f" {cat_name} - {stat_name}: {cat[2]}")
print()
print("=" * 80)
# 问题诊断
if request_forms and not service_requests:
print("\n🔴 问题确认:")
print(" ✓ 手术申请单存在doc_request_form")
print(" ✗ 手术医嘱不存在wor_service_request")
print("\n 结论:保存手术申请单时未生成手术医嘱")
print(" 原因:代码修复可能未生效或未部署到生产环境")
elif service_requests:
print("\n✅ 数据正常:")
print(" ✓ 手术申请单存在")
print(" ✓ 手术医嘱存在")
print("\n 请在医嘱TAB页面查看是否能显示")
print()
cursor.close()
conn.close()

Some files were not shown because too many files have changed in this diff Show More