168 Commits

Author SHA1 Message Date
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
Ranyunqiao
35b8a7d10a 320 手术管理-》门诊手术安排:新增手术安排界面的就诊卡号取值错误 2026-04-03 10:45:19 +08:00
22de02f132 fix: 恢复 IChargeBillServiceImpl.java 中被意外删除的方法
- 恢复 getTotalCcu、getDaySumByTime、getTotalOut 等方法

- 修复编译错误
2026-04-03 09:37:06 +08:00
11244aa48f fix: 修复收费失败错误 'element cannot be mapped to a null key' - 根本原因
- 修复 PaymentRecStaticServiceImpl.java 第 49、52、55、58 行

- 添加对 ChargeItemDefInfo::getTypeCode 和 ChargeItemDefInfo::getYbType 的 null 过滤

- 修复 IChargeBillServiceImpl.java 第 657 行 Invoice::getReconciliationId
2026-04-03 08:32:04 +08:00
0a5f26e9c0 fix: 修复收费失败错误 'element cannot be mapped to a null key' - 补充修复
- 修复 PaymentRecServiceImpl.java 第 2472 行 groupingBy(Account::getId)

- 修复 PaymentRecServiceImpl.java 第 264 行 groupingBy(ChargeItem::getAccountId)

- 修复 IChargeBillServiceImpl.java 多处 groupingBy 可能遇到的 null key 问题
2026-04-02 18:44:06 +08:00
4a8e9b5a22 Merge branch 'develop' of https://gitea.gentronhealth.com/wangyizhe/his into develop 2026-04-02 18:22:35 +08:00
bfb2491842 fix: 修复收费失败错误 'element cannot be mapped to a null key'
- 在 PaymentRecServiceImpl.java 中添加过滤,排除 contractNo 为 null 的数据

- 在 IChargeBillServiceImpl.java 中添加过滤,排除 contractNo 为 null 的数据

- 防止 Java Stream groupingBy 操作时出现 null key 异常
2026-04-02 18:22:18 +08:00
wangjian963
b747f80507 feat(doctorstation): 检验申请单列表添加申请ID字段
- DTO添加applicationId(自增主键)字段
- Mapper返回类型从实体类改为DTO
- 前端表格显示申请ID替代行号
- 调整UI布局和分页器样式
2026-04-02 17:59:21 +08:00
ced931a280 Merge remote-tracking branch 'origin/develop' into develop 2026-04-02 17:54:31 +08:00
b497eb853c fix(surgery): 解决手术申请中的数据绑定和字段映射问题
- 修复了手术申请组件中 userStore 初始化问题,确保 applyDoctorName 和 applyDeptName 正确赋值
- 添加了 surgeryApplication 组件的 saved 事件发射,用于通知父组件刷新医嘱列表
- 修复了手术项目选择变更时 surgeryName 的正确设置和空值处理
- 添加了手术名称和编码的验证逻辑,防止提交时出现空值错误
- 修复了手术排班页面中就诊卡号字段的属性映射(visitId 改为 patientCardNo)
- 在后端 DTO 中添加了 patientCardNo 字段支持
- 修复了数据库查询中就诊卡号的关联查询逻辑,通过患者标识表获取正确的就诊卡号
- 优化了手术医嘱的 contentJson 设置,确保手术名称和编码正确存储
2026-04-02 17:54:07 +08:00
7a2342ea2e 311 检验项目设置-》检验项目:【新增】一条检验项目系统自动在《诊疗目录》增加一条检验收费项目
312检验项目设置-套餐设置:折扣%字段换算公式错误
319 住院管理》-住院医生站》-住院医生站保存患者诊断时报错
2026-04-02 17:25:28 +08:00
Ranyunqiao
09fdfa294a 重新发布 2026-04-02 15:31:56 +08:00
Ranyunqiao
4ef9aa07d2 91 分诊排队管理-》门诊医生站:【完诊】患者队列状态的变化 2026-04-02 15:23:42 +08:00
08085403b3 Merge remote-tracking branch 'origin/develop' into develop 2026-04-02 08:46:09 +08:00
2d7dcb4aeb fix(inpatient): 解决手术申请单数据同步和命名问题
- 在应用表单底部按钮中添加延迟刷新机制,确保后端数据提交完成后再触发刷新事件
- 在手术组件中添加诊疗定义名称字段,完善手术项目信息传递
- 优化手术医嘱生成功能,添加详细的调试日志以便追踪问题
- 修复手术项目名称获取逻辑,优先使用activityList中的手术项目名称
- 完善手术收费项目生成流程,添加异常处理和日志记录
- 在控制器中添加手术申请单保存的日志输出,便于问题排查
2026-04-02 08:15:11 +08:00
ad29502488 Fix: 帮助文档打包失败v2 2026-04-01 18:56:51 +08:00
5b0acede89 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-ui-vue3/src/views/clinicmanagement/bargain/component/prescriptionlist.vue
2026-04-01 18:27:31 +08:00
ac1cd3afc8 fix(prescription): 解决处方列表中手术类型和其他医嘱类型的问题
- 更新 lodash.template 修复脚本以处理 assignWith 函数的自定义器参数
- 在多个处方组件中引入 drord_doctor_type 字典用于动态生成医嘱类型列表
- 修复手术类型(adviceType=6)的特殊处理逻辑,包括类型映射和字段过滤
- 调整后端医嘱保存服务中的类型分类逻辑,正确处理手术类型
- 更新数据库查询映射以支持手术类型的正确显示和数据传输
- 修复费用对话框和订单表单中的相关类型显示问题
2026-04-01 18:24:24 +08:00
Ranyunqiao
8a863b4ecb 106 入科选床界面的住院医生、主治医生、主任医生字段需按照医生维护的职称进行过滤 2026-04-01 16:47:27 +08:00
wangjian963
882d63249c refactor(检验申请): 重构检验申请单生成逻辑,由后端统一处理
- 移除前端生成申请单号的逻辑,改为后端在保存时自动生成
- 申请日期由后端统一处理,前端实时显示当前时间
- 优化金额计算逻辑,确保后端重新计算防止篡改
- 增加废号处理机制,记录生成但保存失败的申请单号
- 简化前端代码,移除不必要的检查逻辑
2026-04-01 16:37:32 +08:00
Ranyunqiao
6315ca5658 220 门诊医生站:新增耗材收费项目医嘱单价/总金额未显示正确的值 2026-04-01 15:25:08 +08:00
Ranyunqiao
9f802b67f0 313
检查项目设置-》套餐设置:折扣字段换算错误
2026-04-01 15:23:46 +08:00
6694ae52ba feat: 手术申请列表-手术单号移到申请日期之前(第一栏) 2026-04-01 14:00:58 +08:00
9491ceaa5d feat: 手术申请列表-手术单号支持点击查看详情 2026-04-01 13:36:44 +08:00
db9a70a99d feat: 手术申请列表-手术单号放第二栏并支持点击查看详情 2026-04-01 13:28:24 +08:00
Ranyunqiao
9105e687d6 98 门诊管理-》门诊划价:选项增加‘西药’和‘中成药’ 2026-04-01 13:14:46 +08:00
b1d6c6008e fix: doctorstation手术医嘱advice_type使用category_enum,advice_name支持surgeryName 2026-04-01 12:57:52 +08:00
6b9f9a107e fix: 手术医嘱类型显示修复 - SQL返回category_enum作为advice_type,前端添加手术类型选项 2026-04-01 12:45:15 +08:00
11a7f49162 fix: 手术医嘱therapy_enum默认为2(临时医嘱),避免被前端过滤 2026-04-01 12:16:36 +08:00
b4e5061b73 Merge remote-tracking branch 'origin/develop' into develop 2026-04-01 11:56:27 +08:00
f5a1ad7f3f fix: 手术医嘱advice_name从content_json解析surgeryName 2026-04-01 11:36:50 +08:00
eeac88b1d1 fix: Bug #318 历史数据修复脚本(Python+SQL) 2026-04-01 10:48:44 +08:00
1ab9b020c1 Merge branch 'develop' of https://gitea.gentronhealth.com/py/his into develop 2026-04-01 10:46:14 +08:00
3055518d2b Fix: 帮助文档打包失败 2026-04-01 10:46:05 +08:00
9f619ccdd4 fix: Bug #318 历史数据修复SQL脚本 2026-04-01 10:28:45 +08:00
df78ff29bd fix(build): 解决 lodash.template 中 assignWith 函数缺失问题
- 添加 JavaScript 脚本修复 lodash.template 模块中的 assignWith 问题
- 提供 Shell 脚本支持 Linux/Mac 系统的自动修复功能
- 实现 assignWith 函数的简单 polyfill 版本以确保兼容性
- 添加补丁检测机制防止重复修补同一文件
- 在构建前自动运行修复脚本确保依赖完整性
2026-04-01 10:03:38 +08:00
4d13acacc2 chore(deps): 添加 lodash 依赖包
- 在 package.json 中新增 lodash 依赖,版本为 ^4.17.21
- 更新依赖配置以支持工具函数库的引入
2026-04-01 09:35:42 +08:00
67573c1d9d fix: 添加诊断日志排查手术医嘱生成问题 2026-04-01 09:27:23 +08:00
b27d8a6703 fix: 修复门诊手术申请后未生成预收费明细记录的问题 (Bug #307)
- 修改 OutpatientChargeAppMapper.xml

- 在门诊收费查询SQL中增加对 cli_surgery 表的关联

- 支持手术申请生成的收费项目正确显示在门诊收费系统中
2026-04-01 09:17:41 +08:00
6f3d4272e6 fix: 添加手术医嘱生成日志,用于排查问题 2026-04-01 08:59:44 +08:00
6e5315fdd6 fix: 修复Bug #318 - 使用contentJson替代note字段存储手术信息 2026-03-31 17:52:17 +08:00
544d7ee95c Merge remote-tracking branch 'origin/develop' into develop 2026-03-31 17:38:20 +08:00
7f7f7d69f7 fix: 修复Bug #318 - 门诊医生站手术申请单自动生成手术医嘱(从descJson解析) 2026-03-31 17:37:15 +08:00
wangjian963
ae9a96822e fix(consultation): 限制会诊申请作废状态条件
修改会诊申请作废逻辑,仅允许新开和已提交状态可作废
前端界面同步调整作废按钮的禁用状态
后端增加状态校验防止非法操作
2026-03-31 17:30:16 +08:00
bbef0322a3 feat(surgicalschedule): 添加手术单号查询功能并优化收费状态 BUG#306
- 在手术申请查询界面添加手术单号输入框
- 将收费项目状态从草稿改为待收费状态
- 在请求表单DTO中添加手术单号字段
- 在数据库查询中关联手术安排表并添加手术单号过滤条件
- 添加筛选条件确保只查询未安排手术的申请记录
2026-03-31 17:18:09 +08:00
a8a205aa48 fix: 修复门诊医生站手术申请单保存后未生成手术医嘱 (#318)
- 在 RequestFormManageAppServiceImpl.saveRequestForm 方法中添加手术医嘱生成逻辑
- 当 typeCode 为 PROCEDURE(24) 时,额外生成 ServiceRequest 手术医嘱
- 同时生成对应的 ChargeItem 收费项目
- 医嘱状态设置为 DRAFT(待签发)
- 关联申请单的 prescriptionNo 处方号
2026-03-31 16:35:34 +08:00
c052ea7c39 Merge remote-tracking branch 'origin/develop' into develop 2026-03-31 16:10:42 +08:00
6accaa35c9 feat(surgicalschedule): 将手术安排日期查询改为日期范围选择 BUG#305
- 将前端日期选择器从单日期改为日期范围选择器
- 修改查询参数从 scheduleDate 改为 scheduleDateRange 数组
- 新增 scheduleDateStart 和 scheduleDateEnd 参数用于后端查询
- 在后端 DTO 中添加日期范围查询字段并配置格式化注解
- 更新 MyBatis XML 映射文件中的日期查询条件逻辑
- 实现前端日期范围到查询参数的转换处理逻辑
2026-03-31 16:10:34 +08:00
466e7296fa 309检验项目设置-》套餐管理:查询条件的用户字段下拉选项无内容
310检验项目设置-》套餐管理:点击【编辑】/【查看】套餐设置界面的lis分组字段显示数字
311 检验项目设置-》检验项目:【新增】一条检验项目系统自动在《诊疗目录》增加一条检验收费项目
312检验项目设置-套餐设置:折扣%字段换算公式错误
2026-03-31 15:59:39 +08:00
wangjian963
5678535d88 feat(检验申请): 新增检验申请单号生成功能并优化执行科室选择
refactor(检验申请): 重构申请单详情加载逻辑,使用后端接口获取完整数据
fix(检验申请): 修复执行科室默认值设置问题
fix(会诊): 修复就诊卡号取值错误和表格选中状态问题
perf(检验申请): 使用Redis实现并发安全的申请单号生成
docs(检验申请): 补充相关接口和方法注释
2026-03-31 15:47:56 +08:00
b7993885bb Merge remote-tracking branch 'origin/develop' into develop 2026-03-31 14:00:36 +08:00
3b8ef380ae feat(surgicalschedule): 添加手术单号筛选和详情显示功能 BUG#278
- 在筛选区域添加手术单号输入框支持按手术单号搜索
- 在表格中添加手术单号列并支持点击查看详情
- 修复权限指令使用正确的 v-hasPermi 指令
- 更新查询参数结构体添加 operCode 字段
- 移除冗余的分页重置逻辑
- 优化后端服务层手术安排名称填充逻辑
- 添加系统用户表名称查询作为备选方案
- 修复数据库查询关联条件使用手术单号匹配
- 添加手术单号模糊查询的SQL条件支持
2026-03-31 14:00:20 +08:00
wangjian963
2334a27467 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-server-new/openhis-application/src/main/java/com/openhis/web/consultation/appservice/impl/ConsultationAppServiceImpl.java
2026-03-30 15:53:24 +08:00
wangjian963
92511c2777 fix(consultation): 修复会诊取消提交逻辑并优化医生列表显示
新增检查医生确认/签名状态的逻辑,防止已确认/签名的会诊被取消提交
优化前端参与医生列表的显示,只显示已确认或已签名的医生
2026-03-30 15:47:56 +08:00
64b02466b1 feat(consultation): 添加会诊ID查询功能
- 在前端表单中新增会诊ID输入框用于查询过滤
- 更新查询参数对象以包含consultationId字段
- 在后端服务中实现会诊ID的模糊匹配查询逻辑
- 将会诊ID查询条件集成到现有的查询构建器中
- 保持与其他查询条件的兼容性以支持组合筛选
2026-03-30 15:46:06 +08:00
2ffbe73305 feat(consultation): 添加根据ID查询会诊申请详情功能
- 在前端API文件中新增getConsultationById函数用于查询会诊详情
- 在后端服务接口中定义getConsultationById方法
- 在后端服务实现类中实现详细的会诊申请查询逻辑
- 在控制器中添加/detailed{id}接口支持会诊详情查询
- 添加参数验证和异常处理确保查询安全性
- 移除多余的日志输出信息优化代码整洁性
2026-03-30 15:29:56 +08:00
48d3941701 fix(consultation): 修复会诊确认参加医师字段取值逻辑 - Bug #266
- 从consultation_confirmation表的confirming_physicians字段取值
- 添加备用逻辑:如果确认表无数据则从invitedList拼接
- 会诊意见同样优先从确认表取值
2026-03-30 15:08:01 +08:00
0ad1889029 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-server-new/openhis-application/src/main/java/com/openhis/web/consultation/appservice/impl/ConsultationAppServiceImpl.java
2026-03-30 15:02:30 +08:00
7dc98dcf84 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-server-new/openhis-application/src/main/java/com/openhis/web/consultation/appservice/impl/ConsultationAppServiceImpl.java
2026-03-30 15:01:08 +08:00
681fb695bd Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	openhis-server-new/openhis-application/src/main/java/com/openhis/web/consultation/appservice/impl/ConsultationAppServiceImpl.java
2026-03-30 14:51:48 +08:00
518d8385e6 292 检验项目设置-》套餐设置:检验套餐明细选择项目后,服务费字段=金额字段的值/10
293
检验项目设置-》套餐设置:lis分组字段下拉选项未进行取值
296 检验项目设置-》套餐管理:点击行【编辑】套餐设置界面点击【更新】报错
297 检验项目设置-》套餐管理:点击行【删除】按钮报错提示“删除失败”
2026-03-30 14:01:43 +08:00
wangjian963
7073ef0be0 feat(会诊管理): 优化会诊申请功能并新增会诊意见列表
- 新增获取会诊意见列表的API接口
- 重构会诊记录信息填充逻辑,支持已确认和已签名状态
- 优化前端会诊申请页面,调整时间类型选项和状态筛选
- 添加紧急程度复选框和会诊确认参加医师显示
- 实现会诊意见列表加载和自动填充功能
2026-03-30 13:36:11 +08:00
2288162ad7 fix(consultation): 修复会诊确认参加医师字段取值逻辑 - Bug #266
**问题修复:**
- 字段标签:将'会诊邀请参加医师'改为'会诊确认参加医师'
- 后端取值:从consultation_confirmation表的confirming_physicians字段取值
- 前端显示:解析JSON格式并格式化为'科室-姓名'的友好显示

**技术变更:**
- ConsultationAppServiceImpl.java: 修改convertToDto(),查询确认表获取字段值
- consultation.vue: 添加JSON解析逻辑,格式化显示医师列表
2026-03-30 11:32:07 +08:00
6f701d7fa6 Merge remote-tracking branch 'origin/develop' into develop 2026-03-30 11:25:08 +08:00
34253f88b2 fix(consultation): 修复会诊记录字段标签错误 - Bug #266
- 将'会诊邀请参加医师'字段标签改为'会诊确认参加医师'
- 与后端取值逻辑保持一致
2026-03-30 11:25:03 +08:00
Ranyunqiao
488c311788 288 门诊医生站-》诊断TAB页面:新增诊断点【保存诊断】报错“保存诊断失败,请稍后重试”
289 手术管理-》门诊手术安排:新增手术安排点击【保存】报错提示“新增手术安排失败,请检查表单信息”
298 检查项目设置-》套餐设置:新增个人套餐【保存】报错。
2026-03-30 10:34:48 +08:00
Ranyunqiao
b5527cc07f 294 检查项目设置-》套餐设置:基本信息服务费字段的值系统没有自动合计套餐明细服务费字段所有行的值
295 检查项目设置-》套餐设置:套餐明细数量字段后面需要增加单位字段
2026-03-30 09:03:49 +08:00
6d23d36a9c 211
检验项目设置-》套餐管理:点击【新增】跳转至套餐设置界面系统未进行初始化新增模式界面数据
212
检验项目设置-》套餐管理:点击行【编辑】跳转至套餐设置编辑模式该行的套餐数据未正确引入
213
检验项目设置-》套餐管理:点击行【查看】跳转至套餐设置界面套餐内容显示错误并且未进入只读模式
2026-03-27 16:39:41 +08:00
HuangXinQuan
e2e5999276 258 预约管理-》医生排班管理:点【预约设置】界面编辑内容【确定】提示”保存成功“但是刷新重新进入未显示最后一次更新的数据 2026-03-27 15:42:26 +08:00
Ranyunqiao
112ec2e4a3 275 276 284 285 286 287 检查项目设置-》套餐管理:卫生机构下拉选项取值错误
检查项目设置-》检查部位:下拉医技类型未做成下拉选项
检查项目设置-》检查方法:费用套餐筛选字段不可以模糊查找选项内容。
检查项目设置-》检查方法:点【导出表格】报错。
检查项目设置-》检查部位:点击【导出表格】报错
检查项目设置-》套餐设置:【套餐设置】改成【套餐管理】好区分
2026-03-27 13:23:44 +08:00
4b92be10b4 Merge remote-tracking branch 'origin/develop' into develop 2026-03-27 11:56:48 +08:00
0b361df0a4 fix(doctorstation): 统一儿童患者家长姓名输入框提示文本
- 将诊断组件中家长姓名输入框占位符从"≤14 岁必填"改为"≤14岁必填"
- 将传染病报告组件中家长姓名输入框占位符统一为"≤14岁必填"
- 移除多余的条件判断逻辑,简化占位符显示逻辑
2026-03-27 11:56:39 +08:00
3a242074ff 209 检验项目设置-》套餐设置:填写套餐基本信息/明细未实现检验套餐的保存功能
290 检验项目设置-》套餐设置:项目名称不能快速定位到所有的项目
291 维护系统 - 》检验项目设置 - 》套餐设置(套餐明细表单)项目名称检索不够人性化
2026-03-27 11:48:47 +08:00
HuangXinQuan
353f267488 261 预约管理-》科室预约工作时间维护:所属机构下拉选项未过滤掉状态为未启动的机构名称(包括【新增】/【编辑】界面) 2026-03-27 11:41:53 +08:00
Ranyunqiao
2d705d2f81 251
手术管理-》门诊手术安排:【新增手术安排】界面安排时间字段的时分秒无法选值和未显示
252 手术管理-》门诊手术安排:【新增手术安排】界面的麻醉方法字段未默认取值于手术申请的麻醉方式字段的值
254 手术管理-》门诊手术管理:【新增手术安排】界面的切口类型字段下拉选项未取值
277 门诊医生站-》手术申请TAB页面:【新增】/【编辑】界面点击【提交申请】提示成功也提示失败
2026-03-27 10:44:11 +08:00
184871e84f refactor(surgicalschedule): 移除重复的手术申请信息填充逻辑
- 删除了重复的 selectedRow 变量声明和赋值操作
- 移除了冗余的表单字段填充代码
- 清理了重复的手术申请信息处理流程
- 简化了手术安排页面的数据处理逻辑
2026-03-26 19:07:33 +08:00
ffcdaed087 refactor(surgical): 优化手术安排服务实现
- 添加动态数据源上下文日志支持
- 导入静态日志工具类简化日志记录
2026-03-26 18:26:49 +08:00
91a0b48662 fix(consultation): 解决会诊流程中的多个功能问题
- 在 deptappthoursManage.js 中添加 status 参数以仅获取已启动的机构
- 为 consultationapplication 组件添加已确认和已签名状态选项
- 扩展操作列宽度并添加打印功能按钮
- 优化 handlePrint 方法以支持行参数和性别枚举转换
- 为 consultationconfirmation 组件添加必填验证和编辑权限控制
- 修复会诊确认医师信息回显逻辑
- 在 inspectionApplication 组件中修复表格行点击事件和检验项目加载
- 禁用非紧急标记的编辑权限以解决Bug #268
- 为 surgeryApplication 组件添加响应码验证和错误处理
- 在 consultation 组件中添加表单验证清除功能
- 为 PackageManagement 组件实现动态机构选项加载
- 重构 PackageSettings 组件的套餐金额显示和只读模式
- 为检查项目设置组件添加套餐筛选和下级类型选择功能
- 实现检验套餐的编辑和查看模式切换功能
2026-03-26 18:22:21 +08:00
c509a804ec fix: 修复会诊申请单已确认/签名还能取消提交的 Bug #256
- 在 cancelConsultation 方法中添加状态校验
- 禁止已确认 (20)、已签名 (30)、已完成 (40) 状态的会诊申请取消提交
- 只有已提交 (10) 状态的会诊申请才允许取消提交
2026-03-26 17:59:45 +08:00
1a7b6c0cd4 fix: 修复门诊医生站诊断页面家长姓名字段缺少提示语 #270 2026-03-26 17:53:13 +08:00
HuangXinQuan
11cf88fd49 232 预约管理-》门诊预约挂号:打开界面报错且无医生排班预约号源数据 2026-03-26 17:09:08 +08:00
3f0fa3bbb3 Merge remote-tracking branch 'origin/develop' into develop 2026-03-26 17:00:41 +08:00
d7c15848f0 208 检验项目设置-》套餐设置:项目名称字段未实现取值于《诊疗目录》做字典库 2026-03-26 16:58:21 +08:00
Ranyunqiao
188b907907 217 收费工作站-》门诊收费:【确认收费】报错“打印失败”
220 门诊医生站:新增耗材收费项目医嘱单价/总金额未显示正确的值
2026-03-26 16:55:06 +08:00
71e3601d51 feat(prescription): 更新处方列表数据结构并优化药品管理界面功能
- 在处方列表中新增总价、剂量和剂量数量字段
- 修复药品审批页面跳转时仓库信息丢失问题
- 扩展药品列表列宽度并启用溢出提示功能
- 为采购单界面添加多种视图状态下的字段禁用逻辑
- 优化采购单仓库位置字段的初始化流程,防止数据丢失
2026-03-26 16:54:20 +08:00
f04c3d112c fix(core): 解决ID字段精度丢失和账户ID为空问题
- 在前端请求处理中添加convertIdsToString函数,将超过安全范围的数字转换为字符串
- 使用json-bigint库处理大数字序列化,防止精度丢失
- 在医嘱保存逻辑中确保accountId不为null,自动创建自费账户
- 添加IAccountService依赖注入支持账户操作
- 在产品转移详情DTO中添加@TableField注解标识非数据库字段
2026-03-26 15:36:17 +08:00
8739959be0 fix(doctorstation): 解决处方列表中账户ID为空导致的保存问题 BUG#282
- 在处方保存流程中添加账户ID空值检查和自动补全逻辑
- 当账户ID为空时自动获取或创建患者自费账户
- 修复给药途径下拉框宽度显示问题
- 在药品单位后添加单位文本显示
- 统一设备费用项目的账户ID处理逻辑
- 确保新创建账户的名称字段不为空以避免数据库约束错误
2026-03-26 14:42:42 +08:00
24bc049fa0 feat(surgicalschedule): 添加费用类别字段支持
- 在手术安排界面中添加费用类别字段映射
- 在申请单页面DTO中新增费用类别属性
- 在数据映射文件中添加费用类别结果映射
- 通过关联账户和合同表查询费用类别信息
- 实现手术安排中费用类别的完整数据流处理
2026-03-25 19:17:05 +08:00
b42cffdd8a Merge remote-tracking branch 'origin/develop' into develop 2026-03-25 18:17:16 +08:00
927691a27b fix(prescription): 解决处方列表中药品拆零比计算问题
- 修复药品名称显示格式,添加拆零比信息显示
- 在用药天数输入框添加失焦和输入事件触发总量计算
- 调整单位选择下拉框样式间距
- 添加拆零比提示信息显示功能
- 重构总量计算逻辑,使用字典数据获取频次对应次数
- 修复拆零比计算算法,统一使用partPercent参数
- 添加调试日志便于问题排查
- 优化计算精度,将toFixed从6位改为2位
- 添加CSS样式支持拆零比提示显示
2026-03-25 18:17:06 +08:00
6c36ae5340 删除 openhis-ui-vue3/public/help-center/vuepress-theme-vdoing-doc/docs/01.HIS操作手册/02.门诊挂号/01.门诊挂号操作.md
系统版本更新快,该门诊挂号操作已不适用。
2026-03-25 18:01:08 +08:00
5473a21418 优化: 帮助页去除与“测试”相关的页面 2026-03-25 17:42:46 +08:00
b14c19a887 fix(prescription): 解决处方列表中剂量变化后未重新计算总量的问题
- 在费率代码选择器上添加 change 事件监听器以触发总量计算
- 在持续时间输入框上添加 change 事件监听器以触发总量计算
- 移除注释的计算调用并添加正确的剂量变化后总量计算逻辑
- 修复 Bug #273 中单次剂量变化后总量未更新的问题

feat(surgery): 为手术管理界面中的手术单号添加链接功能

- 将手术单号列转换为可点击的链接组件
- 为手术单号添加 handleView 点击事件处理
- 扩展手术单号列宽度以改善显示效果
- 在手术排程界面中为手术单号同样添加链接功能
2026-03-25 16:28:40 +08:00
979dc0a34c fix(surgical): 修复手术安排冲突检测逻辑
- 添加了对重复手术安排校验的注释说明,确保执行顺序正确
- 修复了手术室占用检测的时间范围判断条件
- 增加了对空值的安全检查避免潜在异常
- 在SQL查询中添加了删除标记过滤条件
- 统一了变量命名提高代码可读性
2026-03-25 16:27:58 +08:00
c2fa13de82 ```
feat(surgical): 添加手术安排重复校验功能 BUG #278

- 在手术安排创建流程中增加重复校验逻辑
- 实现同一患者同一手术单号同一手术名称的唯一性约束
- 新增 existsDuplicateSchedule 数据库查询方法
- 添加 XML 映射文件中的重复校验 SQL 查询
- 防止相同手术安排的重复提交问题
```
2026-03-25 15:59:13 +08:00
Ranyunqiao
77b054a86c 215 系统管理-》门诊划价:点击【新增】项目字段检索不出收费项目 2026-03-25 14:35:54 +08:00
Ranyunqiao
20eb020071 145 绑定耗材门诊收费未显示无法进行收费 2026-03-25 14:33:23 +08:00
Ranyunqiao
d3deb244c0 248 门诊医生站-》手术申请TAB页面:点击行【编辑】手术申请界面的手术类型、手术等级、切口类型、麻醉方式选项显示数字 2026-03-25 10:16:46 +08:00
d20a95c3c4 Merge remote-tracking branch 'origin/develop' into develop 2026-03-24 18:38:16 +08:00
1f84a641ea fix(prescription): 修正医嘱撤回条件验证逻辑
- 修复了撤回功能允许已作废医嘱撤回的错误
- 现在只有状态为草稿(1)或已签发(2)的医嘱可以撤回
- 已作废(5)状态的医嘱不再支持撤回操作,只能通过删除处理
- 更新了撤回条件判断逻辑以确保数据一致性
2026-03-24 18:38:05 +08:00
c542b057b5 fix(doctorstation): 解决医嘱管理中的状态控制和数据处理问题
- 修复了已收费医嘱仍可被勾选的问题,添加了选择条件限制
- 实现了过滤已作废会诊医嘱的功能,防止无效数据展示
- 完善了医嘱删除逻辑,支持草稿、待签发和已作废状态的医嘱删除
- 修复了医嘱撤回功能中的大整数精度丢失问题
- 优化了签退医嘱的服务端处理逻辑,统一处理各种类型的医嘱作废
- 添加了详细的操作日志记录便于问题排查
- 修复了前端医嘱列表加载和操作过程中的数据类型转换问题
2026-03-24 18:27:30 +08:00
Ranyunqiao
03d980e0cf 246 手术管理-》门诊手术安排:点击【编辑】填写手术过程的相关时间字段值点击【保存】报错
251 手术管理-》门诊手术安排:【新增手术安排】界面安排时间字段的时分秒无法选值和未显示
252 手术管理-》门诊手术安排:【新增手术安排】界面的麻醉方法字段未默认取值于手术申请的麻醉方式字段的值
254 手术管理-》门诊手术管理:【新增手术安排】界面的切口类型字段下拉选项未取值
2026-03-24 17:22:16 +08:00
b03f563df4 206
检验项目设置-》套餐设置:卫生机构字段取值当前登录账户的科室名称了
210 检验项目设置-》套餐管理:卫生机构筛选字段下拉选项取值错误
2026-03-24 16:42:24 +08:00
8fa0a239b5 Merge remote-tracking branch 'origin/develop' into develop 2026-03-24 16:09:41 +08:00
ee51ab2960 fix(doctorstation): 解决医嘱管理中不同类型医嘱的删除和撤回逻辑问题
- 分离不同状态的会诊医嘱,已作废医嘱直接从前端移除,其他状态医嘱调用后端API处理
- 修复普通医嘱删除逻辑,支持草稿和待签发状态的医嘱删除操作
- 宽松医嘱状态条件,支持statusEnum为1(草稿)或2(已签发)的医嘱进行撤回操作
- 修复前端adviceType与后端ItemType映射关系,药品类型值为1,耗材类型值为4,诊疗类型值为3
- 添加详细的调试日志用于追踪医嘱处理流程
- 优化医嘱分类逻辑,确保各类医嘱正确归类处理
2026-03-24 16:09:24 +08:00
Ranyunqiao
22d73e5b44 110 库房管理-》领用管理-》领用退库的升级(可参考开源代码移植) 2026-03-24 15:58:53 +08:00
b31cacd930 Merge remote-tracking branch 'origin/develop' into develop 2026-03-24 15:10:11 +08:00
1440cd45a0 fix(doctorstation): 解决会诊医嘱删除和撤回功能问题
- 引入cancelConsultation接口用于处理会诊医嘱作废
- 分离会诊医嘱和普通医嘱的删除逻辑
- 实现会诊医嘱的作废功能,支持从contentJson解析consultationId
- 添加会诊医嘱撤回功能,区分草稿状态和已提交状态
- 修复医嘱分类逻辑,将会诊类型值5归类到诊疗活动
- 添加调试日志用于跟踪医嘱处理流程
- 优化耗材医嘱删除逻辑,完善费用项清理
- 修复列表更新机制,确保作废医嘱及时从界面移除
2026-03-24 15:10:00 +08:00
Ranyunqiao
07829b93c7 bug241 264 265 fixed 2026-03-24 14:44:58 +08:00
c2b1d7d9d9 fix(doctorstation): 解决医嘱删除时的空指针异常和费用项处理问题
- 添加了对EMR详情中的contextJson字段进行空值检查,避免解析空值导致异常
- 优化了医嘱删除时的patientId和encounterId补全逻辑,支持从药品、耗材、诊疗医嘱记录中获取缺失信息
- 修复了删除不同类型医嘱时费用项过滤问题,确保只处理对应类型的费用项目
- 简化了费用项删除逻辑,移除冗余的查询验证步骤,直接执行删除操作
- 增强了日志记录,便于追踪医嘱删除过程中的关键操作和状态变化
2026-03-24 13:27:31 +08:00
9f6e94da4b fix(prescription): 解决处方列表中科室选择和数据删除问题
- 为科室选择下拉框添加最小宽度样式,确保内容完整显示
- 添加orgTreeLoading状态管理,避免重复加载组织机构树
- 在selectAdviceBase方法中添加异步处理和边界检查逻辑
- 实现诊疗项目默认使用患者就诊科室的逻辑验证
- 修复ensureOrgTreeLoaded方法中的加载状态管理
- 在处方删除操作中添加encounterId和patientId参数传递
- 优化组织机构树查找算法,提升性能表现
2026-03-24 12:48:07 +08:00
e0b9081649 Merge remote-tracking branch 'origin/develop' into develop 2026-03-24 11:53:21 +08:00
e1dc5c895f fix(prescription): 解决处方列表患者信息不完整导致保存失败的问题 BUG#220
- 在前端处方组件中添加患者信息完整性校验
- 当患者信息缺失时显示错误提示并阻止保存操作
- 确保处方项目正确携带patientId和encounterId信息
- 在后端服务中验证并自动补全缺失的patientId信息
- 当encounterId为空时返回相应错误提示
- 添加详细的日志记录以便问题追踪
2026-03-24 11:53:04 +08:00
HuangXinQuan
4e58601b2c 260 预约管理-》医生排班管理:系统未正确限制同一天同一时段(上午/下午)的重复排班,导致同一医生在同一时间段可被多次排班,产生数据冲突和资源调度混乱 2026-03-24 11:51:17 +08:00
4060be4de7 207 检验项目设置-》套餐设置:点击【+】按钮后进入新增行的只读模式 2026-03-24 11:14:02 +08:00
Ranyunqiao
059078c264 108 库房管理-》领用管理-》领用出库的升级 2026-03-24 10:39:56 +08:00
cc51d0b345 fix(doctorstation): 修复耗材无库存时价格设置问题
- 前端组件中确保覆盖后端可能冲突的字段并强制设置耗材类型
- 后端服务中修复库存为空时无法获取价格的问题,直接从定价主表获取统一零售价
- 数据库查询中使用COALESCE函数优先从多个来源获取零售价,提高价格获取准确性
- 优化价格获取逻辑,支持从adm_charge_item_definition和adm_charge_item_def_detail表中按优先级获取价格
- 添加按批次售价的价格匹配机制,确保不同定价策略的正确应用
2026-03-23 19:31:04 +08:00
bedad38ca3 fix(doctorstation): 修复耗材价格获取逻辑
- 优先从priceList获取药品/诊疗价格
- 添加耗材类型直接从retailPrice或price字段获取价格
- 支持耗材价格字段为空时返回默认值
- 修复价格显示格式化问题
2026-03-23 18:38:16 +08:00
c15c091718 Merge remote-tracking branch 'origin/develop' into develop 2026-03-23 17:58:37 +08:00
e90e541af3 fix(doctorstation): 解决诊疗项目执行科室验证及耗材价格显示问题 bug#220
- 修复诊疗项目执行科室非空校验逻辑,使用effectiveOrgId替代positionId
- 添加getEffectiveOrgId方法统一获取执行科室ID的兼容处理
- 修复耗材价格处理逻辑,正确区分price和retailPrice字段避免零值判断错误
- 更新数据库查询映射,优化设备定义表与收费项目定义表关联条件
- 添加调试日志输出便于问题排查
2026-03-23 17:58:27 +08:00
Ranyunqiao
88088c01ac 109 住院医生工作站-》住院病历的【存为模板】界面升级 2026-03-23 17:24:01 +08:00
251cf263ff fix(doctorstation): 解决诊疗项目执行科室缺失问题
- 在批量签发前验证诊疗项目的执行科室是否已设置
- 对于未选择执行科室的诊疗项目显示警告信息并阻止签发
- 当诊疗项目没有设置执行科室时默认使用患者的就诊科室
- 在后端服务中增加对诊疗项目执行科室的非空校验
- 确保诊疗项目签发流程中执行科室信息完整有效
2026-03-23 17:18:12 +08:00
f1a4fc87c8 🔧 Bug Fix #238: 修复诊疗项目执行科室缺失问题
修复内容:
1. 选择诊疗项目时,如果positionId为空则使用orgId作为默认值
2. 保存时添加非空校验,诊疗项目必须选择执行科室
3. 保存成功后检测脏数据并提示用户修正

涉及文件:
- prescriptionlist.vue
2026-03-23 17:01:46 +08:00
d28ac34ae0 fix(doctorstation): 解决删除诊疗医嘱时费用项不存在导致的异常
- 添加费用项存在性检查逻辑
- 在删除前先查询费用项是否存在于数据库中
- 添加详细的操作日志记录便于问题追踪
- 避免因费用项不存在导致的删除操作失败
2026-03-23 16:32:42 +08:00
4d2a321999 fix(doctorstation): 修复处方列表和医嘱处理中的多个问题
- 修复耗材和诊疗类型在setValue后总金额未正确计算的问题
- 修复耗材类型没有价格列表时的默认值处理逻辑
- 修复组套中positionId被医嘱库信息覆盖的问题
- 修复删除耗材医嘱时费用项不存在导致的异常
- 修复处方位置ID查询中组织ID为空时的回退逻辑
2026-03-23 16:29:17 +08:00
b5cf685b13 fix(medical-order): 解决医嘱编辑界面显示和验证问题 BUG#250,145
- 修复医嘱集合对话框中的条件判断逻辑,统一新增行的显示规则
- 更新医嘱集合对话框中剂量、给药途径、频次、天数等字段的显示逻辑
- 为西药类医嘱添加必填字段验证功能,包括剂量、给药途径、频次等
- 优化处方列表组件中编辑状态下的输入控件显示
- 修复处方详情数据合并逻辑,确保正确的字段优先级处理
- 解决多个库存管理模块中API请求数据格式错误的问题
- 修复医嘱库信息保存和数据回填的相关问题
2026-03-23 15:46:30 +08:00
wangjian963
316c1478fc Merge remote-tracking branch 'origin/develop' into develop 2026-03-20 17:47:22 +08:00
wangjian963
24e8a3cfdf fix(consultation): 修复会诊确认页面医师显示问题
- 将confirmingPhysicianText字段替换为confirmingPhysician以正确显示参与医师
- 新增displayApplicationTime计算属性以区分新增和编辑时的申请时间显示
- 清理冗余代码并修复格式问题
2026-03-20 17:46:47 +08:00
888ac1fee3 fix(medicalOrderSet): 修复医嘱剂量转换计算错误
- 修正了剂量数量字段的单位转换公式,从除法改为乘法
- 修正了剂量字段的单位转换公式,从乘法改为除法
- 确保剂量和剂量数量之间的转换逻辑正确性
- 解决了因单位转换错误导致的数据显示问题
2026-03-20 17:46:28 +08:00
427d567337 205 检验项目设置-》套餐设置:点开【套餐设置】TAB页面的内容套餐金额、服务费及检验套餐明细带入脏数据,
206 检验项目设置-》套餐设置:卫生机构字段取值当前登录账户的科室名称了
2026-03-20 17:26:02 +08:00
5d73de3072 203 检验项目设置-》检验项目:小类项目名称重名也能保存,没有做限制 2026-03-20 11:48:29 +08:00
1fdaafb1e8 202 检验项目设置-》检验项目:点击【编辑】按钮检验类型字段的内容显示数字 2026-03-20 10:22:53 +08:00
dbdaba5a6a 202 检验项目设置-》检验项目:点击【编辑】按钮检验类型字段的内容显示数字 2026-03-20 09:53:49 +08:00
4210f32a05 fix(charge): 解决门诊收费中耗材请求数据查询问题 BUG#145
- 在处方查询方法中添加耗材请求表名参数支持
- 修复数据库查询中缺少耗材请求表关联的问题
- 将设备费用项状态从草稿改为计划状态以确保正确显示
- 为设备请求设置处方号以保证门诊收费能正确关联
- 优化数据库表连接逻辑支持耗材请求数据查询
2026-03-19 19:36:11 +08:00
wangjian963
dc2e4098ae 这个编辑按钮功能展示的数据为什么会不全,已确认状态下的数据展示不全。 2026-03-19 17:50:06 +08:00
wangjian963
83747d8626 - 将"已取消"状态显示修改为"已取消/作废"
- 修复申请时间显示问题,使用动态时间并统一格式
- 过滤查询中的已作废会诊数据
- 优化参与医生列表显示字段
- 修复时间格式化显示为YYYY-MM-DD格式
2026-03-19 16:25:59 +08:00
68c0c098c8 242 检验项目设置-》检验项目:费用套餐/下级医技类型字段两列无数据 2026-03-19 15:54:53 +08:00
dc1366890f Merge remote-tracking branch 'origin/develop' into develop 2026-03-19 15:53:06 +08:00
f1087b04f0 fix(surgery): 解决手术管理中的空指针异常和数据库字段映射问题 BUG#233
- 移除了前端手术对话框中的错误注释代码
- 在手术排班服务中添加了对多个可为空字符串字段的默认值处理
- 为手术实体类中的关键字段添加了MyBatis-Plus字段策略注解
- 确保术前诊断、术后诊断、麻醉方式等字段在数据库操作时正确处理空值情况
- 统一了手术相关字段的插入策略为忽略空值,避免数据库约束错误
2026-03-19 15:52:55 +08:00
Ranyunqiao
0b4b4f7283 107 库房管理-》采购管理-》采购入库的升级(可参考开源代码移植) 2026-03-19 15:47:43 +08:00
d41d3066b3 Merge remote-tracking branch 'origin/develop' into develop 2026-03-19 10:25:59 +08:00
3bfe25c2f2 fix(medical): 修复医嘱剂量计算和数据同步问题
- 修正剂量转换公式,将乘法改为除法以正确计算
- 修复剂量数量字段的计算逻辑错误
- 添加字典文本字段回显支持
- 实现医嘱组套字段同步功能
- 保留组套中的原始值而不是强制重置
- 修复设备名称映射字段错误
2026-03-19 10:25:34 +08:00
Ranyunqiao
266b06114a 重新提交 2026-03-19 10:21:24 +08:00
Ranyunqiao
6ed41eb513 240 检查项目设置-》检查部位:服务范围字段下拉选项取值错误 2026-03-19 10:11:02 +08:00
Ranyunqiao
0a8428a4ca 241 检查项目设置-》套餐设置:项目名称字段检索不出“径直肠B超检查”诊疗收费项目 2026-03-19 09:54:38 +08:00
HuangXinQuan
d058b30872 221,222,223,224,227,228,229,230,231 2026-03-19 09:19:03 +08:00
0c06b05764 fix(print): 优化打印功能调试日志和修复位置ID获取问题 BUG#217
- 在printUtils.js中添加详细的打印诊断日志,包含hiprint对象检查、模板数据检查、医院名称处理等步骤
- 修复orderGroupDrawer.vue中的positionId获取逻辑,优先使用item.positionId
- 修复prescriptionlist.vue中quantity和totalPrice的计算问题
- 修复服务器端DiagTreatMAppServiceImpl中pricingFlag过滤条件处理问题
- 修复EleInvoiceMapper.xml中orgClassEnum类型的CAST转换问题
- 优化打印错误处理和异常捕获机制
- 添加完整的打印流程日志跟踪功能
2026-03-18 18:17:54 +08:00
wangjian963
557f626c05 会诊管理-》门诊会诊申请确认:补上字段“会诊确认参加医师 2026-03-18 18:04:29 +08:00
32bfef6686 200 检验项目设置-》检验项目:下级医技类型字段未做成下拉选项并取值于检验类型字段选中项关联的子项 2026-03-18 17:28:19 +08:00
wangjian963
5795d9eb74 feat(传染病报卡): 新增传染病报卡管理功能模块
实现传染病报卡的基础功能,包括:
1. 新增报卡查询参数DTO、报卡详情DTO和状态枚举
2. 添加报卡Mapper接口及XML实现分页查询和详情查询
3. 实现报卡AppService接口及Controller提供REST API
4. 新增前端API接口定义
5. 添加审核记录实体类
2026-03-18 17:24:30 +08:00
7c29c6359f feat(print): 更新门诊收费结算单打印配置和诊断治疗页面功能 BUG#217
- 修改门诊收费结算单名称字段为数字类型
- 添加面板页面规则、页码续传、扩展CSS等打印配置选项
- 集成水印、重印、布局等高级打印功能
- 为收费项目列表添加列选择、固定列等表格功能
- 将线元素类型从line更改为hline并调整标题显示
- 在诊断治疗页面添加划价标记筛选条件
- 更新查询参数结构以支持新的划价标记过滤功能
2026-03-18 16:55:29 +08:00
Ranyunqiao
40c5d26dfd 235
检查项目设置-》检查管理界面 费用套餐字段取值未更新
2026-03-18 16:53:21 +08:00
Ranyunqiao
81c97de170 190 检查项目设置-》套餐设置:项目名称字段模糊查询出预览界面增加项目单价字段显示
191 检查项目设置-》套餐设置:单价字段的值未自动获取项目单价的值并可编辑
192 检查项目设置-》套餐设置:数量和服务费字段的值被加减按钮挡住了看不见数值
193 检查项目设置-》套餐设置:套餐级别选中个人套餐时系统未自动获取当前登录账号名称赋值给用户选择字段
2026-03-18 16:51:38 +08:00
0cdf332ee7 fix(charge): 修正收费小票打印功能中的参数引用错误 BUG#217
- 将打印日志中的 chargeItem 参数更正为 chargedItems
- 修复数据处理逻辑中使用错误的参数名 chargeItem
- 添加注册号 regNo 字段到打印数据对象
- 修正机构名称拼接时可能出现的空值问题
- 将患者姓名字段从 name 更正为 patientName 以保持一致性
2026-03-18 16:28:31 +08:00
b4e13e1305 fix: 修复门诊划价新增收费项目总金额未自动计算的问题 (BUG #216)
问题原因:
- prescriptionlist.vue 文件中引用了 calculateTotalPrice 函数但未定义
- 选择收费项目后未设置默认数量,也未触发总金额计算

修复内容:
1. 添加 calculateTotalPrice 函数,根据单价×数量自动计算总金额
2. 选择诊疗项目后自动设置默认执行次数为1并计算总金额
3. 选择耗材项目后自动设置默认数量为1并计算总金额

验证:
- 构建成功,无编译错误
2026-03-18 15:01:10 +08:00
dd1cd17801 fix: 修复门诊划价无法检索收费项目问题 (Bug #215)
问题原因:
- 门诊划价调用 getAdviceBaseInfo 时 pricingFlag 为 null
- SQL 固定过滤 pricing_flag=1 OR IS NULL,导致 pricing_flag=0 的诊疗项目被错误过滤

修复方案:
- 将固定过滤条件改为动态条件
- 当 pricingFlag 为 null 时不添加过滤,查询所有启用状态项目
- 当 pricingFlag 有值时按传入值过滤

影响范围:
- 修复门诊划价检索功能
- 不影响医生站等其他场景的 pricing_flag 过滤逻辑
2026-03-18 14:52:53 +08:00
6d87b7c445 feat(role): 添加角色用户关系管理功能并优化处方列表组件 bug#182
- 在SysUserRoleMapper中新增selectUserIdsByRoleId方法查询角色下的用户ID列表
- 在SysRoleServiceImpl中注入ISysMenuService并实现菜单缓存清理逻辑
- 修改updateRole方法在角色权限变更后清除相关用户的菜单缓存
- 更新处方列表组件确保showPopover初始值为false避免自动弹出问题
- 将采购入库页面的按钮文本从"添加记录"改为"采购入库"
- 添加删除验证检查已审批记录不允许删除的功能
2026-03-18 14:26:12 +08:00
0d1710f201 fix(ATDManage): 解决入院体征数据映射兼容性问题 BUG#179
- 添加心率字段兼容性处理,支持heartRate和hertRate历史数据
- 增强血压数据解析安全性,防止数组越界异常
- 实现DocStatisticsDefinition查找fallback机制
- 添加缺失定义的日志记录和错误处理
- 确保只设置非空血压数值到DTO对象
2026-03-18 13:47:56 +08:00
a3650aa386 fix(doctorstation): 解决医嘱签发时关键字段缺失问题 bug#181
- 处理前端体征字段中的 null 值转换为空字符串
- 移除后端保存和签发逻辑中的条件判断,确保关键字段始终被设置
- 修复 BUG #181 中医嘱签发时缺少必要业务字段的问题
- 统一保存和签发流程中的字段赋值逻辑
- 保持业务编号在签发时的一致性处理
2026-03-18 12:33:37 +08:00
0f9ef726bf Merge remote-tracking branch 'origin/develop' into develop 2026-03-18 11:26:21 +08:00
8f2405ee34 fix(BUG-179): 修复入科选床入院体征数据未保存问题
问题描述:
- 入科选床弹窗中填写的入院体征数据(身高、体重、体温等)未保存到数据库
- 重新打开弹窗时数据未显示

根因分析:
1. 前端提交时过滤空字符串,导致空值的体征字段未被提交
2. 后端保存时只保存非空值,且未清除已有数据,导致旧数据残留

修复方案:
1. 前端: transferInDialog.vue - 体征字段(height, weight等)即使为空字符串也要提交
2. 后端: ATDManageAppServiceImpl.java - 保存前先删除已有体征记录,再保存新数据

测试建议:
- 测试填写完整体征数据后保存并重新打开
- 测试清空部分体征字段后保存并重新打开
- 测试修改已有体征数据后保存

Closes BUG-179
2026-03-18 11:26:04 +08:00
246 changed files with 20604 additions and 13408 deletions

View File

@@ -2,5 +2,5 @@
"tools": {
"approvalMode": "yolo"
},
"$version": 2
"$version": 3
}

6
.qwen/settings.json.orig Normal file
View File

@@ -0,0 +1,6 @@
{
"tools": {
"approvalMode": "yolo"
},
"$version": 2
}

View File

@@ -1,11 +1,15 @@
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.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
@@ -24,6 +28,14 @@ public class ApplicationConfig {
*/
@Bean
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

@@ -54,9 +54,17 @@ public interface SysUserRoleMapper {
/**
* 批量取消授权用户角色
*
* @param roleId 角色ID
* @param userIds 需要删除的用户数据ID
* @param roleId 角色 ID
* @param userIds 需要删除的用户数据 ID
* @return 结果
*/
public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds);
/**
* 通过角色 ID 查询用户 ID 列表
*
* @param roleId 角色 ID
* @return 用户 ID 列表
*/
public List<Long> selectUserIdsByRoleId(Long roleId);
}

View File

@@ -15,6 +15,7 @@ import com.core.system.mapper.SysRoleDeptMapper;
import com.core.system.mapper.SysRoleMapper;
import com.core.system.mapper.SysRoleMenuMapper;
import com.core.system.mapper.SysUserRoleMapper;
import com.core.system.service.ISysMenuService;
import com.core.system.service.ISysRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -41,6 +42,9 @@ public class SysRoleServiceImpl implements ISysRoleService {
@Autowired
private SysRoleDeptMapper roleDeptMapper;
@Autowired
private ISysMenuService menuService;
/**
* 根据条件分页查询角色数据
*
@@ -221,11 +225,23 @@ public class SysRoleServiceImpl implements ISysRoleService {
@Override
@Transactional
public int updateRole(SysRole role) {
// 修改角色信息
// 1. 获取该角色下的所有用户 ID在修改权限前查询
List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(role.getRoleId());
// 2. 修改角色信息
roleMapper.updateRole(role);
// 删除角色与菜单关联
// 3. 删除角色与菜单关联
roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());
return insertRoleMenu(role);
int result = insertRoleMenu(role);
// 4. 清除该角色下所有用户的菜单树缓存
if (userIds != null && !userIds.isEmpty()) {
for (Long userId : userIds) {
menuService.clearMenuCacheByUserId(userId);
}
}
return result;
}
/**

View File

@@ -48,4 +48,10 @@
#{userId}
</foreach>
</delete>
<select id="selectUserIdsByRoleId" resultType="Long">
select user_id
from sys_user_role
where role_id = #{roleId}
</select>
</mapper>

View File

@@ -0,0 +1,28 @@
package com.openhis.web.appointmentmanage.appservice;
import com.core.common.core.domain.R;
import com.openhis.appointmentmanage.domain.AppointmentConfig;
/**
* 预约配置AppService接口
*
* @author openhis
* @date 2026-03-23
*/
public interface IAppointmentConfigAppService {
/**
* 获取当前机构的预约配置
*
* @return 预约配置
*/
R<?> getAppointmentConfig();
/**
* 保存预约配置
*
* @param appointmentConfig 预约配置
* @return 结果
*/
R<?> saveAppointmentConfig(AppointmentConfig appointmentConfig);
}

View File

@@ -2,7 +2,9 @@ package com.openhis.web.appointmentmanage.appservice;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.openhis.appointmentmanage.dto.TicketQueryDTO;
import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.appointmentmanage.dto.TicketQueryDTO;
import java.util.Map;
@@ -14,37 +16,53 @@ import java.util.Map;
public interface ITicketAppService {
/**
* 预约号源
* 分页查询门诊号源列表(真分页)
*
* @param params 预约参数
* @param query 查询参数
* @return 结果
*/
R<?> bookTicket(Map<String, Object> params);
R<?> listTicket(TicketQueryDTO query);
/**
* 查询医生余号汇总(基于号源池,不受分页影响)
*
* @param query 查询参数
* @return 结果
*/
R<?> listDoctorAvailability(TicketQueryDTO query);
/**
* 预约号源
*
* @param dto 预约参数
* @return 结果
*/
R<?> bookTicket(com.openhis.appointmentmanage.domain.AppointmentBookDTO dto);
/**
* 取消预约
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
R<?> cancelTicket(Long ticketId);
R<?> cancelTicket(Long slotId);
/**
* 取号
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
R<?> checkInTicket(Long ticketId);
R<?> checkInTicket(Long slotId);
/**
* 停诊
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
R<?> cancelConsultation(Long ticketId);
R<?> cancelConsultation(Long slotId);
/**
* 查询所有号源(用于测试)
*

View File

@@ -0,0 +1,62 @@
package com.openhis.web.appointmentmanage.appservice.impl;
import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils;
import com.openhis.appointmentmanage.domain.AppointmentConfig;
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
import com.openhis.web.appointmentmanage.appservice.IAppointmentConfigAppService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 预约配置AppService实现类
*
* @author openhis
* @date 2026-03-23
*/
@Service
public class AppointmentConfigAppServiceImpl implements IAppointmentConfigAppService {
@Resource
private IAppointmentConfigService appointmentConfigService;
@Override
public R<?> getAppointmentConfig() {
// 获取当前登录用户的机构ID
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
if (tenantId == null) {
return R.fail("获取机构信息失败");
}
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
return R.ok(config);
}
@Override
public R<?> saveAppointmentConfig(AppointmentConfig appointmentConfig) {
// 获取当前登录用户的机构ID
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
if (tenantId == null) {
return R.fail("获取机构信息失败");
}
// 查询是否已存在配置
AppointmentConfig existingConfig = appointmentConfigService.getConfigByTenantId(tenantId);
if (existingConfig != null) {
// 更新现有配置
existingConfig.setCancelAppointmentType(appointmentConfig.getCancelAppointmentType());
existingConfig.setCancelAppointmentCount(appointmentConfig.getCancelAppointmentCount());
existingConfig.setValidFlag(appointmentConfig.getValidFlag());
appointmentConfigService.saveOrUpdate(existingConfig);
return R.ok(existingConfig);
} else {
// 新增配置
appointmentConfig.setTenantId(tenantId);
appointmentConfig.setValidFlag(1);
appointmentConfigService.saveOrUpdateConfig(appointmentConfig);
return R.ok(appointmentConfig);
}
}
}

View File

@@ -118,6 +118,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
newSchedule.setStopReason(doctorSchedule.getStopReason() != null ? doctorSchedule.getStopReason() : "");
newSchedule.setDeptId(doctorSchedule.getDeptId());
newSchedule.setRegType(doctorSchedule.getRegType() != null ? doctorSchedule.getRegType() : 0);
newSchedule.setDoctorId(doctorSchedule.getDoctorId());
// 不设置id字段让数据库自动生成
@@ -162,6 +163,30 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
return R.fail("限号数量必须大于0");
}
// 检查结束时间必须大于开始时间
if (doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) {
if (!doctorSchedule.getStartTime().isBefore(doctorSchedule.getEndTime())) {
return R.fail("结束时间必须大于开始时间");
}
}
// 检查时间重叠(同一医生、同一天时间段有重叠)
if (doctorSchedule.getDoctorId() != null && scheduledDate != null
&& doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) {
LocalDate scheduleDate = LocalDate.parse(scheduledDate);
boolean hasOverlap = checkTimeOverlap(
doctorSchedule.getDoctorId(),
scheduleDate,
doctorSchedule.getStartTime(),
doctorSchedule.getEndTime()
);
if (hasOverlap) {
return R.fail("该医生在 " + scheduledDate + ""
+ doctorSchedule.getStartTime() + "-" + doctorSchedule.getEndTime()
+ " 时间段与已有排班重叠,不能重复添加");
}
}
// 创建新对象排除id字段数据库id列是GENERATED ALWAYS由数据库自动生成
DoctorSchedule newSchedule = new DoctorSchedule();
newSchedule.setWeekday(doctorSchedule.getWeekday());
@@ -183,6 +208,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
newSchedule.setStopReason(doctorSchedule.getStopReason() != null ? doctorSchedule.getStopReason() : "");
newSchedule.setDeptId(doctorSchedule.getDeptId());
newSchedule.setRegType(doctorSchedule.getRegType() != null ? doctorSchedule.getRegType() : 0);
newSchedule.setDoctorId(doctorSchedule.getDoctorId());
// 不设置id字段让数据库自动生成
@@ -213,14 +239,51 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
}
}
@Transactional
@Override
public R<?> updateDoctorSchedule(DoctorSchedule doctorSchedule) {
if (ObjectUtil.isEmpty(doctorSchedule) || ObjectUtil.isEmpty(doctorSchedule.getId())) {
return R.fail("医生排班ID不能为空");
}
// 注意:此为核心更新,暂未处理号源池和号源槽的同步更新
int result = doctorScheduleMapper.updateDoctorSchedule(doctorSchedule);
return result > 0 ? R.ok(result) : R.fail("更新排班信息失败");
if (result <= 0) {
return R.fail("更新排班信息失败");
}
// 同步更新号源池,避免查询联表时医生/诊室等字段看起来“未更新”
boolean needSyncPool = doctorSchedule.getDoctorId() != null
|| doctorSchedule.getDoctor() != null
|| doctorSchedule.getClinic() != null
|| doctorSchedule.getStartTime() != null
|| doctorSchedule.getEndTime() != null
|| doctorSchedule.getLimitNumber() != null
|| doctorSchedule.getStopReason() != null
|| doctorSchedule.getRegType() != null
|| doctorSchedule.getRegisterFee() != null
|| doctorSchedule.getRegisterItem() != null
|| doctorSchedule.getDiagnosisItem() != null
|| doctorSchedule.getDiagnosisFee() != null;
if (needSyncPool) {
schedulePoolService.lambdaUpdate()
.eq(SchedulePool::getScheduleId, doctorSchedule.getId())
.set(doctorSchedule.getDoctorId() != null, SchedulePool::getDoctorId, doctorSchedule.getDoctorId())
.set(doctorSchedule.getDoctor() != null, SchedulePool::getDoctorName, doctorSchedule.getDoctor())
.set(doctorSchedule.getClinic() != null, SchedulePool::getClinicRoom, doctorSchedule.getClinic())
.set(doctorSchedule.getStartTime() != null, SchedulePool::getStartTime, doctorSchedule.getStartTime())
.set(doctorSchedule.getEndTime() != null, SchedulePool::getEndTime, doctorSchedule.getEndTime())
.set(doctorSchedule.getLimitNumber() != null, SchedulePool::getTotalQuota,
doctorSchedule.getLimitNumber())
.set(doctorSchedule.getStopReason() != null, SchedulePool::getStopReason, doctorSchedule.getStopReason())
.set(doctorSchedule.getRegType() != null, SchedulePool::getRegType, String.valueOf(doctorSchedule.getRegType()))
.set(doctorSchedule.getRegisterFee() != null, SchedulePool::getFee, Double.valueOf(doctorSchedule.getRegisterFee().toString()))
.set(doctorSchedule.getRegisterFee() != null, SchedulePool::getInsurancePrice,
Double.valueOf(doctorSchedule.getRegisterFee().toString()))
.update();
}
return R.ok(result);
}
/**
@@ -246,7 +309,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
// 不设置available_num因为它是数据库生成列
// pool.setAvailableNum(0); // 初始为0稍后更新
pool.setRegType(schedule.getRegisterItem() != null ? schedule.getRegisterItem() : "普通");
pool.setFee(schedule.getRegisterFee() != null ? schedule.getRegisterFee() / 100.0 : 0.0); // 假设数据库中以分为单位存储
pool.setFee(schedule.getRegisterFee() != null ? Double.valueOf(schedule.getRegisterFee().toString()) : 0.0); // 直接使用原始价格
pool.setInsurancePrice(pool.getFee()); // 医保价格暂时与原价相同
// 暂时设置support_channel为空字符串避免JSON类型问题
pool.setSupportChannel("");
@@ -299,7 +362,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
// 不设置available_num因为它是数据库生成列
// pool.setAvailableNum(0); // 初始为0稍后更新
pool.setRegType(schedule.getRegisterItem() != null ? schedule.getRegisterItem() : "普通");
pool.setFee(schedule.getRegisterFee() != null ? schedule.getRegisterFee() / 100.0 : 0.0); // 假设数据库中以分为单位存储
pool.setFee(schedule.getRegisterFee() != null ? Double.valueOf(schedule.getRegisterFee().toString()) : 0.0); // 直接使用原始价格
pool.setInsurancePrice(pool.getFee()); // 医保价格暂时与原价相同
// 暂时设置support_channel为空字符串避免JSON类型问题
pool.setSupportChannel("");
@@ -345,6 +408,42 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
return slots;
}
/**
* 检查同一医生、同一天、同时段是否已存在排班
*
* @param doctorId 医生ID
* @param scheduleDate 出诊日期
* @param timePeriod 时段(上午/下午)
* @return true表示存在重复排班false表示不存在
*/
// private boolean checkDuplicateSchedule(Long doctorId, LocalDate scheduleDate, String timePeriod) {
// return schedulePoolService.lambdaQuery()
// .eq(SchedulePool::getDoctorId, doctorId)
// .eq(SchedulePool::getScheduleDate, scheduleDate)
// .eq(SchedulePool::getShift, timePeriod)
// .exists();
// }
/**
* 检查同一医生、同一天是否有时间重叠的排班
* 重叠判断startA < endB AND startB < endA即时间段有交集
*
* @param doctorId 医生ID
* @param scheduleDate 出诊日期
* @param startTime 开始时间
* @param endTime 结束时间
* @return true表示存在时间重叠false表示不重叠
*/
private boolean checkTimeOverlap(Long doctorId, LocalDate scheduleDate,
LocalTime startTime, LocalTime endTime) {
return schedulePoolService.lambdaQuery()
.eq(SchedulePool::getDoctorId, doctorId)
.eq(SchedulePool::getScheduleDate, scheduleDate)
.lt(SchedulePool::getStartTime, endTime) // 已有开始时间 < 新结束时间
.gt(SchedulePool::getEndTime, startTime) // 已有结束时间 > 新开始时间
.exists();
}
/**
* 根据星期几计算具体日期(下周的对应星期)
*/

View File

@@ -4,28 +4,22 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.openhis.administration.domain.Patient;
import com.openhis.administration.service.IPatientService;
import com.openhis.appointmentmanage.domain.DoctorSchedule;
import com.openhis.appointmentmanage.mapper.DoctorScheduleMapper;
import com.openhis.appointmentmanage.service.IDoctorScheduleService;
import com.openhis.clinical.domain.Order;
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
import com.openhis.clinical.domain.Ticket;
import com.openhis.clinical.mapper.OrderMapper;
import com.openhis.clinical.service.ITicketService;
import com.openhis.web.appointmentmanage.appservice.IDoctorScheduleAppService;
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 号源管理应用服务实现类
*
@@ -36,73 +30,41 @@ public class TicketAppServiceImpl implements ITicketAppService {
@Resource
private ITicketService ticketService;
@Resource
private ScheduleSlotMapper scheduleSlotMapper;
@Resource
private IPatientService patientService;
@Resource
private IDoctorScheduleAppService doctorScheduleAppService;
@Resource
private DoctorScheduleMapper doctorScheduleMapper;
@Resource
private OrderMapper orderMapper;
private static final Logger log = LoggerFactory.getLogger(TicketAppServiceImpl.class);
/**
* 预约号源
* 预约号源 (重构版:精准锁定单一槽位)
*
* @param params 预约参数
* @param dto 预约参数
* @return 结果
*/
@Override
public R<?> bookTicket(Map<String, Object> params) {
// 1. 获取 ticketId 和 slotId
Long ticketId = null;
Long slotId = null;
if (params.get("ticketId") != null) {
ticketId = Long.valueOf(params.get("ticketId").toString());
public R<?> bookTicket(com.openhis.appointmentmanage.domain.AppointmentBookDTO dto) {
Long slotId = dto.getSlotId();
if (slotId == null) {
return R.fail("参数校验失败:缺少排班槽位唯一标识");
}
if (params.get("slotId") != null) {
slotId = Long.valueOf(params.get("slotId").toString());
}
// 2. 参数校验
if (ticketId == null || slotId == null) {
return R.fail("参数错误ticketId 或 slotId 不能为空");
}
try {
// 3. 执行原有的预约逻辑
int result = ticketService.bookTicket(params);
int result = ticketService.bookTicket(dto);
if (result > 0) {
// 4. 预约成功后,更新排班表状态
DoctorSchedule schedule = new DoctorSchedule();
schedule.setId(slotId); // 对应 XML 中的 WHERE id = #{id}
schedule.setIsStopped(true); // 设置为已预约
schedule.setStopReason("booked"); // 设置停用原因
// 执行更新
int updateCount = doctorScheduleMapper.updateDoctorSchedule(schedule);
if (updateCount > 0) {
return R.ok("预约成功并已更新排班状态");
} else {
// 如果更新失败,可能需要根据业务逻辑决定是否回滚预约
return R.ok("预约成功,但排班状态更新失败");
}
} else {
return R.fail("预约失败");
return R.ok("预约成功!号源已安全锁定。");
}
return R.fail("预约挂单核发失败");
} catch (Exception e) {
// e.printStackTrace();
log.error(e.getMessage());
log.error("大厅挂号捕获系统异常", e);
return R.fail("系统异常:" + e.getMessage());
}
}
/**
* 取消预约
* 取消预约 (重构版:精准释放单一槽位)
*
* @param slotId 医生排班ID
* @param slotId 医生槽位排班ID
* @return 结果
*/
@Override
@@ -111,18 +73,8 @@ public class TicketAppServiceImpl implements ITicketAppService {
return R.fail("参数错误");
}
try {
ticketService.cancelTicket(slotId);
DoctorSchedule schedule = new DoctorSchedule();
schedule.setId(slotId); // 对应 WHERE id = #{id}
schedule.setIsStopped(false); // 设置为 false
schedule.setStopReason(""); // 将原因清空 (设为空字符串)
// 3. 调用自定义更新方法
int updateCount = doctorScheduleMapper.updateDoctorSchedule(schedule);
if (updateCount > 0) {
return R.ok("取消成功");
} else {
return R.ok("取消成功");
}
int result = ticketService.cancelTicket(slotId);
return R.ok(result > 0 ? "取消成功,号源已重新释放回市场" : "取消失败");
} catch (Exception e) {
return R.fail(e.getMessage());
}
@@ -131,16 +83,16 @@ public class TicketAppServiceImpl implements ITicketAppService {
/**
* 取号
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
@Override
public R<?> checkInTicket(Long ticketId) {
if (ticketId == null) {
public R<?> checkInTicket(Long slotId) {
if (slotId == null) {
return R.fail("参数错误");
}
try {
int result = ticketService.checkInTicket(ticketId);
int result = ticketService.checkInTicket(slotId);
return R.ok(result > 0 ? "取号成功" : "取号失败");
} catch (Exception e) {
return R.fail(e.getMessage());
@@ -150,109 +102,300 @@ public class TicketAppServiceImpl implements ITicketAppService {
/**
* 停诊
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
@Override
public R<?> cancelConsultation(Long ticketId) {
if (ticketId == null) {
public R<?> cancelConsultation(Long slotId) {
if (slotId == null) {
return R.fail("参数错误");
}
try {
int result = ticketService.cancelConsultation(ticketId);
int result = ticketService.cancelConsultation(slotId);
return R.ok(result > 0 ? "停诊成功" : "停诊失败");
} catch (Exception e) {
return R.fail(e.getMessage());
}
}
@Override
public R<?> listAllTickets() {
// 1. 从 AppService 获取排班数据
R<?> response = doctorScheduleAppService.getDoctorScheduleList();
// 获取返回的 List 数据 (假设 R.ok 里的数据是 List<DoctorSchedule>)
List<DoctorSchedule> scheduleList = (List<DoctorSchedule>) response.getData();
public R<?> listTicket(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
// 1. 防空指针处理
if (query == null) {
query = new com.openhis.appointmentmanage.dto.TicketQueryDTO();
}
normalizeQueryStatus(query);
// 2. 转换数据为 TicketDto
List<TicketDto> tickets = new ArrayList<>();
// 2. 构造 MyBatis 的分页对象 (传入前端给的当前页和每页条数)
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> pageParam = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
query.getPage(), query.getLimit());
if (scheduleList != null) {
for (DoctorSchedule schedule : scheduleList) {
// 3. 调用刚才写的底层动态 SQL 查询!
com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.openhis.appointmentmanage.domain.TicketSlotDTO> rawPage = scheduleSlotMapper
.selectTicketSlotsPage(pageParam, query);
// 4. 将查出来的数据翻译为前端可以直接渲染的结构
java.util.List<TicketDto> tickets = new java.util.ArrayList<>();
if (rawPage.getRecords() != null) {
for (com.openhis.appointmentmanage.domain.TicketSlotDTO raw : rawPage.getRecords()) {
TicketDto dto = new TicketDto();
// 基础信息映射
dto.setSlot_id(Long.valueOf(schedule.getId())); // Integer 转 Long
dto.setBusNo(String.valueOf(schedule.getId())); // 生成一个业务编号
dto.setDepartment(String.valueOf(schedule.getDeptId())); // 如果有科室名建议关联查询这里暂填ID
dto.setDoctor(schedule.getDoctor());
// 基础字段映射
dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId()));
dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName()); // 注意以前这里传成了ID导致前端出Bug现在修复成了真正的科室名
dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone());
dto.setIdCard(raw.getIdCard());
dto.setDoctorId(raw.getDoctorId());
dto.setDepartmentId(raw.getDepartmentId());
dto.setRealPatientId(raw.getPatientId());
// 号源类型处理:根据挂号项目判断是普通号还是专家号
String registerItem = schedule.getRegisterItem();
if (registerItem != null && registerItem.contains("专家")) {
// 性别处理:直接读取优先级最高的订单性别字段 (SQL 已处理优先级)
if (raw.getPatientGender() != null) {
String pg = raw.getPatientGender().trim();
dto.setGender("1".equals(pg) ? "" : ("2".equals(pg) ? "" : "未知"));
} else {
dto.setGender("未知");
}
if (raw.getRegType() != null && raw.getRegType() == 1) {
dto.setTicketType("expert");
} else {
dto.setTicketType("general");
}
// 时间处理:格式化为日期+时间范围,如 "2025-12-01 08:00-12:00"
String currentDate = LocalDate.now().toString(); // 或者从schedule中获取具体日期
String timeRange = schedule.getStartTime() + "-" + schedule.getEndTime();
dto.setDateTime(currentDate + " " + timeRange);
LocalTime nowTime = LocalTime.now();
LocalTime endTime = schedule.getEndTime();
String stopReason1 = schedule.getStopReason();
if ("cancelled".equals(stopReason1)||(endTime != null && nowTime.isAfter(endTime))) {
dto.setStatus("已停诊");
}else if (Boolean.TRUE.equals(schedule.getIsStopped())) {
// 获取原因并处理可能的空值
String stopReason = schedule.getStopReason();
// 使用 .equals() 比较内容,并将常量放在前面防止空指针
if ("booked".equals(stopReason)) {
dto.setStatus("已预约");
// --- 新增:获取患者信息 ---
List<Order> Order = orderMapper.selectOrderBySlotId(Long.valueOf(schedule.getId()));
Order latestOrder=Order.get(0);
if (latestOrder != null) {
dto.setPatientName(latestOrder.getPatientName());
dto.setPatientId(String.valueOf(latestOrder.getPatientId()));
dto.setPhone(latestOrder.getPhone());
}
// -----------------------
} else if ("checked".equals(stopReason)) {
dto.setStatus("已取号");
} else {
// 兜底逻辑:如果 is_stopped 为 true 但没有匹配到原因
dto.setStatus("不可预约");
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try {
String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().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) {
dto.setAppointmentDate(new java.util.Date());
}
} else {
// is_stopped 为 false 或 null 时
dto.setStatus("未预约");
}
// 费用处理 (挂号费 + 诊疗费)
int totalFee = schedule.getRegisterFee() + schedule.getDiagnosisFee();
dto.setFee(String.valueOf(totalFee));
// 日期处理LocalDateTime 转 Date
if (schedule.getCreateTime() != null) {
// 1. 先转成 Instant
Instant instant = schedule.getCreateTime().toInstant();
// 2. 结合时区转成 ZonedDateTime
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
// 3. 再转回 Date (如果 DTO 需要的是 Date)
dto.setAppointmentDate(Date.from(zdt.toInstant()));
if (Boolean.TRUE.equals(raw.getIsStopped())) {
dto.setStatus("已停诊");
} else {
Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} 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("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else {
dto.setStatus("未预约");
}
} else {
dto.setStatus("未预约");
}
}
tickets.add(dto);
}
}
// 3. 封装分页响应结构
Map<String, Object> result = new HashMap<>();
// 5. 按照前端组件需要的【真分页】格式进行包装,并返回
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("list", tickets);
result.put("total", rawPage.getTotal()); // 这个 total 就是底层用 COUNT(*) 算出来的真实总条数!
result.put("page", query.getPage());
result.put("limit", query.getLimit());
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
public R<?> listDoctorAvailability(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
if (query == null) {
query = new com.openhis.appointmentmanage.dto.TicketQueryDTO();
}
java.util.List<com.openhis.appointmentmanage.domain.DoctorAvailabilityDTO> rawList = scheduleSlotMapper
.selectDoctorAvailabilitySummary(query);
java.util.List<java.util.Map<String, Object>> doctors = new java.util.ArrayList<>();
if (rawList != null) {
for (com.openhis.appointmentmanage.domain.DoctorAvailabilityDTO item : rawList) {
java.util.Map<String, Object> row = new java.util.HashMap<>();
String doctorName = item.getDoctorName();
Long doctorId = item.getDoctorId();
row.put("id", doctorId != null ? String.valueOf(doctorId) : doctorName);
row.put("name", doctorName);
row.put("available", item.getAvailable() == null ? 0 : item.getAvailable());
row.put("type", item.getTicketType() == null ? "general" : item.getTicketType());
doctors.add(row);
}
}
return R.ok(doctors);
}
@Override
public R<?> listAllTickets() {
// 1. 调用最新的 Mapper直接从数据库抽出我们半成品的 DTO强类型
List<com.openhis.appointmentmanage.domain.TicketSlotDTO> rawDtos = scheduleSlotMapper.selectAllTicketSlots();
// 这是真正要发给前端展示的包裹外卖盒
List<TicketDto> tickets = new ArrayList<>();
if (rawDtos != null) {
for (com.openhis.appointmentmanage.domain.TicketSlotDTO raw : rawDtos) {
TicketDto dto = new TicketDto();
// --- 基础字段处理 ---
// 注意:这里已经变成了极其舒服的 .getSlotId() 方法调用,告别魔鬼字符串!
dto.setSlot_id(raw.getSlotId());
dto.setSeqNo(raw.getSeqNo());
dto.setBusNo(String.valueOf(raw.getSlotId())); // 暂时借用真实槽位ID做唯一流水号
dto.setDoctor(raw.getDoctor());
dto.setDepartment(raw.getDepartmentName());
dto.setFee(raw.getFee());
dto.setPatientName(raw.getPatientName());
dto.setPatientId(raw.getMedicalCard());
dto.setPhone(raw.getPhone());
// --- 号源类型处理 (普通/专家) ---
// 改用底层 adm_doctor_schedule 传来的标准数字字典0=普通1=专家
if (raw.getRegType() != null && raw.getRegType() == 1) {
dto.setTicketType("expert");
} else {
dto.setTicketType("general");
}
// --- 就诊时间严谨拼接 ---
// 拼接出来给前端展示的,如 "2026-03-20 08:30"
if (raw.getScheduleDate() != null && raw.getExpectTime() != null) {
dto.setDateTime(raw.getScheduleDate().toString() + " " + raw.getExpectTime().toString());
try {
String timeStr = raw.getAppointmentTime() != null ? raw.getAppointmentTime() : (raw.getScheduleDate().toString() + " " + raw.getExpectTime().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) {
log.error("时间解析失败", e);
dto.setAppointmentDate(new java.util.Date());
}
}
// --- 核心逻辑:精准状态分类 ---
// 第一关:底层硬性停诊拦截
if (Boolean.TRUE.equals(raw.getIsStopped())) {
dto.setStatus("已停诊");
} else {
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
Integer slotStatus = raw.getSlotStatus();
if (slotStatus != null) {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} 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("已停诊");
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
dto.setStatus("已锁定");
} else {
dto.setStatus("未预约");
}
} else {
dto.setStatus("未预约");
}
}
tickets.add(dto);
}
}
// 3. 封装分页响应结构并吐给前端
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("list", tickets);
result.put("total", tickets.size());
result.put("page", 1);
result.put("limit", 20);
return R.ok(result);
}
@@ -268,7 +411,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
dto.setBusNo(ticket.getBusNo());
dto.setDepartment(ticket.getDepartment());
dto.setDoctor(ticket.getDoctor());
// 处理号源类型转换为英文前端期望的是general或expert
String ticketType = ticket.getTicketType();
if ("普通".equals(ticketType)) {
@@ -278,10 +421,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
} else {
dto.setTicketType(ticketType);
}
// 处理号源时间dateTime
dto.setDateTime(ticket.getTime());
// 处理号源状态(转换为中文)
String status = ticket.getStatus();
switch (status) {
@@ -300,32 +443,29 @@ public class TicketAppServiceImpl implements ITicketAppService {
default:
dto.setStatus(status);
}
dto.setFee(ticket.getFee());
dto.setPatientName(ticket.getPatientName());
dto.setPatientId(ticket.getMedicalCard()); // 就诊卡号
dto.setPhone(ticket.getPhone());
// 获取患者性别
if (ticket.getPatientId() != null) {
Patient patient = patientService.getById(ticket.getPatientId());
if (patient != null) {
Integer genderEnum = patient.getGenderEnum();
if (genderEnum != null) {
switch (genderEnum) {
case 1:
dto.setGender("");
break;
case 2:
dto.setGender("");
break;
default:
dto.setGender("未知");
if (Integer.valueOf(1).equals(genderEnum)) {
dto.setGender("");
} else if (Integer.valueOf(2).equals(genderEnum)) {
dto.setGender("");
} else {
dto.setGender("未知");
}
}
}
}
dto.setAppointmentDate(ticket.getAppointmentDate());
dto.setAppointmentTime(ticket.getAppointmentTime());
dto.setDepartmentId(ticket.getDepartmentId());

View File

@@ -1,11 +1,10 @@
package com.openhis.web.appointmentmanage.controller;
import com.core.common.core.domain.R;
import com.openhis.appointmentmanage.domain.AppointmentConfig;
import com.openhis.web.appointmentmanage.appservice.IAppointmentConfigAppService;
import com.openhis.web.appointmentmanage.appservice.IDeptAppService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -16,6 +15,9 @@ public class DeptController {
@Resource
private IDeptAppService deptAppService;
@Resource
private IAppointmentConfigAppService appointmentConfigAppService;
/*
* 获取科室列表
*
@@ -38,4 +40,22 @@ public class DeptController {
){
return R.ok(deptAppService.searchDept(pageNo,pageSize,orgName,deptName));
}
/*
* 获取预约配置
*
* */
@GetMapping("/config")
public R<?> getAppointmentConfig(){
return appointmentConfigAppService.getAppointmentConfig();
}
/*
* 保存预约配置
*
* */
@PostMapping("/config")
public R<?> saveAppointmentConfig(@RequestBody AppointmentConfig appointmentConfig){
return appointmentConfigAppService.saveAppointmentConfig(appointmentConfig);
}
}

View File

@@ -53,7 +53,6 @@ public class DoctorScheduleController {
/*
* 新增医生排班(带具体日期)
*
* */
@PostMapping("/add-with-date")
public R<?> addDoctorScheduleWithDate(@RequestBody DoctorSchedule doctorSchedule) {
@@ -77,7 +76,7 @@ public class DoctorScheduleController {
* */
@DeleteMapping("/delete/{doctorScheduleId}")
public R<?> removeDoctorSchedule(@PathVariable Integer doctorScheduleId){
return R.ok(doctorScheduleAppService.removeDoctorSchedule(doctorScheduleId));
return doctorScheduleAppService.removeDoctorSchedule(doctorScheduleId);
}
/*

View File

@@ -3,8 +3,12 @@ package com.openhis.web.appointmentmanage.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.annotation.Anonymous;
import com.core.common.core.domain.R;
import com.openhis.appointmentmanage.domain.AppointmentBookDTO;
import com.openhis.appointmentmanage.dto.TicketQueryDTO;
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -19,11 +23,35 @@ import java.util.Map;
@RequestMapping("/appointment/ticket")
public class TicketController {
/**
* 分页查询门诊号源列表 (带多条件过滤)
*
* @param query 查询条件
* @return 分页号源列表
*/
@Anonymous
@PostMapping("/list")
public R<?> listTicket(@RequestBody @Validated TicketQueryDTO query) {
return ticketAppService.listTicket(query);
}
/**
* 查询医生余号汇总(基于号源池,不受分页影响)
*
* @param query 查询条件
* @return 医生余号列表
*/
@Anonymous
@PostMapping("/doctorSummary")
public R<?> listDoctorAvailability(@RequestBody @Validated TicketQueryDTO query) {
return ticketAppService.listDoctorAvailability(query);
}
@Resource
private ITicketAppService ticketAppService;
/**
* 查询所有号源(用于测试)
* 查询所有号源
*
* @return 所有号源列表
*/
@@ -36,44 +64,44 @@ public class TicketController {
/**
* 预约号源
*
* @param params 预约参数
* @param dto 预约参数
* @return 结果
*/
@PostMapping("/book")
public R<?> bookTicket(@RequestBody Map<String, Object> params) {
return ticketAppService.bookTicket(params);
public R<?> bookTicket(@RequestBody @Validated AppointmentBookDTO dto) {
return ticketAppService.bookTicket(dto);
}
/**
* 取消预约
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
@PostMapping("/cancel")
public R<?> cancelTicket(@RequestParam Long ticketId) {
return ticketAppService.cancelTicket(ticketId);
public R<?> cancelTicket(@RequestParam Long slotId) {
return ticketAppService.cancelTicket(slotId);
}
/**
* 取号
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
@PostMapping("/checkin")
public R<?> checkInTicket(@RequestParam Long ticketId) {
return ticketAppService.checkInTicket(ticketId);
public R<?> checkInTicket(@RequestParam Long slotId) {
return ticketAppService.checkInTicket(slotId);
}
/**
* 停诊
*
* @param ticketId 号源ID
* @param slotId 槽位ID
* @return 结果
*/
@PostMapping("/cancelConsultation")
public R<?> cancelConsultation(@RequestParam Long ticketId) {
return ticketAppService.cancelConsultation(ticketId);
public R<?> cancelConsultation(@RequestParam Long slotId) {
return ticketAppService.cancelConsultation(slotId);
}
}

View File

@@ -23,6 +23,11 @@ public class TicketDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long slot_id;
/**
* 号源序号(对应 adm_schedule_slot.seq_no
*/
private Integer seqNo;
/**
* 号源编码
*/
@@ -49,7 +54,7 @@ public class TicketDto {
private String dateTime;
/**
* 状态 (unbooked:未预约, booked:已预约, checked:已取号, cancelled:已取消, locked:已锁定)
* 状态
*/
private String status;
@@ -99,4 +104,15 @@ public class TicketDto {
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long doctorId;
/**
* 真实患者ID数据库主键区别于 patientId 存的就诊卡号)
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long realPatientId;
/**
* 身份证号
*/
private String idCard;
}

View File

@@ -1,12 +1,14 @@
package com.openhis.web.basedatamanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.StringUtils;
import com.openhis.administration.domain.Organization;
import com.openhis.administration.mapper.OrganizationMapper;
import com.openhis.administration.service.IOrganizationService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.constant.PromptMsgConstant;
@@ -35,12 +37,15 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
@Resource
private AssignSeqUtil assignSeqUtil;
@Resource
private OrganizationMapper organizationMapper;
@Override
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum,
List<String> classEnumList,
String sortField, String sortOrder, HttpServletRequest request) {
// 使用Page对象进行分页查询
// 使用 Page 对象进行分页查询
Page<Organization> page = new Page<>(pageNo, pageSize);
// 创建查询条件
@@ -54,7 +59,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
queryWrapper.eq(Organization::getTypeEnum, typeEnum);
}
if (classEnumList != null && !classEnumList.isEmpty()) {
// 使用OR条件来匹配class_enum字段中包含任一值的记录
// 使用 OR 条件来匹配 class_enum 字段中包含任一值的记录
queryWrapper.and(wrapper -> {
for (int i = 0; i < classEnumList.size(); i++) {
String classEnum = classEnumList.get(i);
@@ -63,18 +68,18 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
wrapper.and(subWrapper -> {
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
.or() // 或者
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值"开头
.or() // 或者
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
.or() // 或者
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
});
} else {
// 后续条件使用OR连接
// 后续条件使用 OR 连接
wrapper.or(subWrapper -> {
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
.or() // 或者
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值"开头
.or() // 或者
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
.or() // 或者
@@ -88,7 +93,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
// 执行分页查询
Page<Organization> resultPage = organizationService.page(page, queryWrapper);
// 将查询结果转为DTO并构建树结构
// 将查询结果转为 DTO 并构建树结构
List<Organization> organizationList = resultPage.getRecords();
List<OrganizationDto> orgTree = buildTree(organizationList);
@@ -109,7 +114,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
* @return tree
*/
private List<OrganizationDto> buildTree(List<Organization> records) {
// 按b_no的层级排序确保父节点先处理
// 按 b_no 的层级排序,确保父节点先处理
List<Organization> sortedRecords = records.stream()
.sorted(Comparator.comparingInt(r -> r.getBusNo().split("\\.").length)).collect(Collectors.toList());
@@ -131,7 +136,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
// 根节点
tree.add(node);
} else {
// 获取父节点的b_no去掉最后一部分
// 获取父节点的 b_no去掉最后一部分
String parentBNo = String.join(".", Arrays.copyOf(parts, parts.length - 1));
OrganizationDto parent = nodeMap.get(parentBNo);
@@ -149,7 +154,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
/**
* 机构信息详情
*
* @param orgId 机构信息id
* @param orgId 机构信息 id
* @return 机构信息详情
*/
@Override
@@ -159,7 +164,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, new Object[] { "机构信息" }));
}
// 转换为DTO对象确保数据格式一致
// 转换为 DTO 对象,确保数据格式一致
OrganizationDto organizationDto = new OrganizationDto();
BeanUtils.copyProperties(organization, organizationDto);
organizationDto
@@ -181,7 +186,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
@Override
public R<?> addOrEditOrganization(OrganizationDto organizationDto) {
// 新增organization信息
// 新增 organization 信息
Organization organization = new Organization();
BeanUtils.copyProperties(organizationDto, organization);
@@ -191,9 +196,9 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
} else {
// 活动标识:有效
organization.setActiveFlag(AccountStatus.ACTIVE.getValue());
// 采番bus_no三位
// 采番 bus_no 三位
String code = assignSeqUtil.getSeq(AssignSeqEnum.ORGANIZATION_BUS_NO.getPrefix(), 3);
// 如果传了上级科室 把当前的code拼到后边
// 如果传了上级科室 把当前的 code 拼到后边
if (StringUtils.isNotEmpty(organization.getBusNo())) {
organization.setBusNo(String.format(CommonConstants.Common.MONTAGE_FORMAT, organization.getBusNo(),
CommonConstants.Common.POINT, code));
@@ -203,7 +208,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
// 生成待发送的机构信息
organizationService.save(organization);
}
// 返回机构id
// 返回机构 id
return R.ok(organization.getId(),
MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] { "机构信息更新添加" }));
}
@@ -211,7 +216,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
/**
* 删除机构
*
* @param orgIds 机构信息id
* @param orgIds 机构信息 id
* @return 操作结果
*/
@Override
@@ -232,7 +237,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
/**
* 机构启用
*
* @param orgId 机构信息id
* @param orgId 机构信息 id
* @return 操作结果
*/
@Override
@@ -247,7 +252,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
/**
* 机构停用
*
* @param orgId 机构信息id
* @param orgId 机构信息 id
* @return 操作结果
*/
@Override
@@ -299,38 +304,27 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
*/
@Override
public R<?> getRegisterOrganizations(Integer pageNo, Integer pageSize, String name, String orgName) {
// 使用Page对象进行分页查询
// 使用 Page 对象进行分页查询
Page<Organization> page = new Page<>(pageNo != null ? pageNo : 1, pageSize != null ? pageSize : 10);
// 创建查询条件只查询register_flag为1的组织机构
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Organization::getRegisterFlag, 1); // 只获取挂号科室
queryWrapper.eq(Organization::getDeleteFlag, "0"); // 确保未删除
// 使用 Mapper 方法关联查询 sys_tenant 表获取租户名称
IPage<Organization> resultPage = organizationMapper.selectRegisterOrganizationsWithTenant(
page,
1, // register_flag = 1
"0", // delete_flag = '0'
name,
orgName
);
// 添加名称过滤条件
if (StringUtils.isNotEmpty(name)) {
queryWrapper.like(Organization::getName, name);
}
// 如果有机构名称筛选
if (StringUtils.isNotEmpty(orgName)) {
// 这里假设 orgName 是父机构名称,如果需要更复杂的关联查询可在此扩展
// 当前逻辑暂保持与原逻辑一致的过滤方式或根据需求调整
}
// 按编码排序
queryWrapper.orderByAsc(Organization::getBusNo);
// 执行分页查询
Page<Organization> resultPage = organizationService.page(page, queryWrapper);
// 转换为DTO对象并设置字典文本
// 转换为 DTO 对象并设置字典文本
List<OrganizationDto> organizationDtoList = resultPage.getRecords().stream().map(org -> {
OrganizationDto dto = new OrganizationDto();
BeanUtils.copyProperties(org, dto);
dto.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, dto.getTypeEnum()));
dto.setClassEnum_dictText(formatClassEnumDictText(dto.getClassEnum()));
dto.setActiveFlag_dictText(EnumUtils.getInfoByValue(AccountStatus.class, dto.getActiveFlag()));
// 设置租户名称
dto.setOrgName(org.getTenantName());
return dto;
}).collect(Collectors.toList());

View File

@@ -8,7 +8,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
@@ -60,18 +59,20 @@ public class OrganizationDto {
private Integer displayOrder;
/** 子集合 */
@ToString.Exclude
private List<OrganizationDto> children = new ArrayList<>();
/** 挂号科室标记 */
private Integer registerFlag;
/** 科室位置 */
private String location;
/** 科室简介 */
private String intro;
/** 备注 */
private String remark;
/** 租户名称 */
private String orgName;
}

View File

@@ -119,9 +119,11 @@ public class OutpatientChargeAppServiceImpl implements IOutpatientChargeAppServi
= outpatientChargeAppMapper.selectEncounterPatientPrescription(encounterId,
ChargeItemContext.ACTIVITY.getValue(), ChargeItemContext.MEDICATION.getValue(),
ChargeItemContext.DEVICE.getValue(), ChargeItemContext.REGISTER.getValue(),
ChargeItemContext.WESTERN_MEDICINE.getValue(), ChargeItemContext.CHINESE_PATENT_MEDICINE.getValue(),
ChargeItemStatus.PLANNED.getValue(), ChargeItemStatus.BILLABLE.getValue(),
ChargeItemStatus.BILLED.getValue(), ChargeItemStatus.REFUNDING.getValue(),
ChargeItemStatus.REFUNDED.getValue(), ChargeItemStatus.PART_REFUND.getValue());
ChargeItemStatus.REFUNDED.getValue(), ChargeItemStatus.PART_REFUND.getValue(),
CommonConstants.TableName.WOR_DEVICE_REQUEST);
prescriptionDtoList.forEach(e -> {
// 收费状态枚举
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(ChargeItemStatus.class, e.getStatusEnum()));
@@ -229,7 +231,8 @@ public class OutpatientChargeAppServiceImpl implements IOutpatientChargeAppServi
ChargeItemStatus.REFUNDING.getValue(), ChargeItemStatus.REFUNDED.getValue(),
ChargeItemStatus.PART_REFUND.getValue(), YbPayment.DISCOUNT_PAY.getValue(),
YbPayment.SELF_CASH_VALUE.getValue(), YbPayment.SELF_CASH_VX_VALUE.getValue(),
YbPayment.SELF_CASH_ALI_VALUE.getValue(), YbPayment.SELF_CASH_UNION_VALUE.getValue());
YbPayment.SELF_CASH_ALI_VALUE.getValue(), YbPayment.SELF_CASH_UNION_VALUE.getValue(),
CommonConstants.TableName.WOR_DEVICE_REQUEST);
prescriptionDtoList.forEach(e -> {
// 应收金额
BigDecimal receivableAmount = e.getReceivableAmount();

View File

@@ -73,10 +73,10 @@ public class OutpatientPricingAppServiceImpl implements IOutpatientPricingAppSer
} else {
adviceTypes = List.of(1, 2, 3);
}
// 门诊划价:不要强制 pricingFlag=1 参与过滤wor_activity_definition.pricing_flag 可能为 0
// 否则会导致诊疗项目(adviceType=3)查询结果为空 records=[]
String categoryCode = adviceBaseDto != null ? adviceBaseDto.getCategoryCode() : null;
// 门诊划价:仅返回划价标记为“是”的项目
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
organizationId, pageNo, pageSize, null, adviceTypes, null);
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.HisPageUtils;
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.RefundLog;
import com.openhis.financial.service.IRefundLogService;
@@ -48,6 +52,7 @@ import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@@ -97,6 +102,15 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Resource
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);
@@ -399,6 +418,74 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
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

@@ -61,7 +61,12 @@ public class OutpatientPricingController {
@RequestParam(value = "locationId", required = false) Long locationId,
@RequestParam(value = "organizationId") Long organizationId,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "categoryCode", required = false) String categoryCode) {
// 将 categoryCode 设置到 adviceBaseDto 中
if (categoryCode != null && !categoryCode.isEmpty()) {
adviceBaseDto.setCategoryCode(categoryCode);
}
return R.ok(iOutpatientPricingAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, organizationId,
pageNo, pageSize));
}

View File

@@ -42,19 +42,24 @@ public interface OutpatientChargeAppMapper {
* @param medication 药品
* @param device 耗材
* @param register 挂号费
* @param westernMedicine 西药
* @param chinesePatentMedicine 中成药
* @param planned 收费状态:待收费
* @param billable 收费状态:待结算
* @param billed 收费状态:已结算
* @param refunding 收费状态:退费中
* @param refunded 收费状态:全部退费
* @param partRefund 收费状态:部分退费
* @param worDeviceRequest 耗材请求表名常量
* @return 患者处方列表
*/
List<EncounterPatientPrescriptionDto> selectEncounterPatientPrescription(@Param("encounterId") Long encounterId,
@Param("activity") Integer activity, @Param("medication") Integer medication, @Param("device") Integer device,
@Param("register") Integer register, @Param("planned") Integer planned, @Param("billable") Integer billable,
@Param("register") Integer register, @Param("westernMedicine") Integer westernMedicine,
@Param("chinesePatentMedicine") Integer chinesePatentMedicine,
@Param("planned") Integer planned, @Param("billable") Integer billable,
@Param("billed") Integer billed, @Param("refunding") Integer refunding, @Param("refunded") Integer refunded,
@Param("partRefund") Integer partRefund);
@Param("partRefund") Integer partRefund, @Param("worDeviceRequest") String worDeviceRequest);
/**
* 根据就诊id查询患者处方列表并新增字段应收金额实收金额优惠金额折扣率
@@ -75,6 +80,7 @@ public interface OutpatientChargeAppMapper {
* @param selfVxCode 微信枚举码
* @param selfAliCode 支付宝枚举码
* @param selfUnionCode 银联枚举码
* @param worDeviceRequest 耗材请求表名常量
* @return 患者处方列表
*/
List<EncounterPatientPrescriptionDto> selectEncounterPatientPrescriptionWithPrice(
@@ -84,5 +90,5 @@ public interface OutpatientChargeAppMapper {
@Param("refunding") Integer refunding, @Param("refunded") Integer refunded,
@Param("partRefund") Integer partRefund, @Param("discountCode") Integer discountCode,
@Param("self") Integer selfCode, @Param("selfVx") Integer selfVxCode, @Param("selfAli") Integer selfAliCode,
@Param("selfUnion") Integer selfUnionCode);
@Param("selfUnion") Integer selfUnionCode, @Param("worDeviceRequest") String worDeviceRequest);
}

View File

@@ -24,5 +24,5 @@ public interface ICheckMethodAppService{
R<?> searchCheckMethodList(Integer pageNo, Integer pageSize, String checkType, String name, String packageName);
R<?> exportCheckMethod(String checkType, String name, String packageName, HttpServletResponse response);
void exportCheckMethod(String checkType, String name, String packageName, HttpServletResponse response);
}

View File

@@ -16,5 +16,5 @@ public interface ICheckPartAppService {
R<?> searchCheckPartList(Integer pageNo, Integer pageSize, String checkType, String name, String packageName);
R<?> exportCheckPart(String checkType, String name, String packageName, HttpServletResponse response);
void exportCheckPart(String checkType, String name, String packageName, HttpServletResponse response);
}

View File

@@ -89,7 +89,7 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
}
@Override
public R<?> exportCheckMethod(String checkType, String name, String packageName, HttpServletResponse response) {
public void exportCheckMethod(String checkType, String name, String packageName, HttpServletResponse response) {
LambdaQueryWrapper<CheckMethod> wrapper = new LambdaQueryWrapper<>();
if (checkType != null && ObjectUtil.isNotEmpty(checkType)) {
wrapper.eq(CheckMethod::getCheckType, checkType);
@@ -103,7 +103,13 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
List<CheckMethod> list = checkMethodService.list(wrapper);
if (list.isEmpty()) {
return R.fail("导出Excel失败,无数据。");
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"导出Excel失败,无数据。\"}");
} catch (IOException e) {
log.error("写入响应失败", e);
}
return;
}
try {
@@ -123,9 +129,12 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
} catch (IOException | IllegalAccessException e) {
log.error("导出Excel失败", e);
return R.fail("导出Excel失败" + e.getMessage());
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"导出Excel失败" + e.getMessage() + "\"}");
} catch (IOException ex) {
log.error("写入响应失败", ex);
}
}
return R.ok(null, "导出Excel成功");
}
}

View File

@@ -22,7 +22,7 @@ import java.util.List;
import java.util.stream.Collectors;
/**
* 检查套餐AppService实现
* 检查套餐 AppService 实现
*
* @author system
* @date 2025-11-26
@@ -35,6 +35,32 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
private final ICheckPackageService checkPackageService;
private final ICheckPackageDetailService checkPackageDetailService;
/**
* 转换明细 DTO 列表为实体列表
* @param detailDtos 明细 DTO 列表
* @param packageId 套餐 ID
* @param orderNumStart 起始序号
* @return 明细实体列表
*/
private List<CheckPackageDetail> convertToDetails(List<CheckPackageDetailDto> detailDtos, Long packageId, int orderNumStart) {
if (detailDtos == null || detailDtos.isEmpty()) {
return new ArrayList<>();
}
List<CheckPackageDetail> details = new ArrayList<>();
int orderNum = orderNumStart;
for (CheckPackageDetailDto detailDto : detailDtos) {
CheckPackageDetail detail = new CheckPackageDetail();
BeanUtils.copyProperties(detailDto, detail);
detail.setPackageId(packageId);
detail.setOrderNum(orderNum++);
detail.setCreateTime(LocalDateTime.now());
detail.setUpdateTime(LocalDateTime.now());
details.add(detail);
}
return details;
}
@Override
public R<?> getCheckPackageList() {
try {
@@ -61,7 +87,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
.orderByAsc(CheckPackageDetail::getOrderNum)
);
// 转换为DTO
// 转换为 DTO
CheckPackageDto dto = new CheckPackageDto();
BeanUtils.copyProperties(checkPackage, dto);
@@ -101,28 +127,21 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 保存套餐明细
if (checkPackageDto.getItems() != null && !checkPackageDto.getItems().isEmpty()) {
List<CheckPackageDetail> details = new ArrayList<>();
int orderNum = 1;
for (CheckPackageDetailDto detailDto : checkPackageDto.getItems()) {
CheckPackageDetail detail = new CheckPackageDetail();
BeanUtils.copyProperties(detailDto, detail);
detail.setPackageId(checkPackage.getId());
detail.setOrderNum(orderNum++);
detail.setCreateTime(LocalDateTime.now());
detail.setUpdateTime(LocalDateTime.now());
details.add(detail);
List<CheckPackageDetail> details = convertToDetails(checkPackageDto.getItems(), checkPackage.getId(), 1);
boolean detailSaveResult = checkPackageDetailService.saveBatch(details);
if (!detailSaveResult) {
throw new RuntimeException("保存套餐明细失败");
}
checkPackageDetailService.saveBatch(details);
}
return R.ok(checkPackage.getId(), "保存成功");
} catch (Exception e) {
log.error("新增检查套餐失败", e);
// 捕获PostgreSQL唯一约束冲突异常
// 捕获 PostgreSQL 唯一约束冲突异常
String errorMessage = e.getMessage();
if (errorMessage != null) {
// PostgreSQL唯一约束错误通常包含 "duplicate key value" 或约束名称
// PostgreSQL 唯一约束错误通常包含 "duplicate key value" 或约束名称
if (errorMessage.contains("duplicate key value") ||
errorMessage.contains("违反唯一约束") ||
errorMessage.contains("unique constraint")) {
@@ -135,7 +154,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
}
}
return R.fail("新增检查套餐失败: " + errorMessage);
return R.fail("新增检查套餐失败" + errorMessage);
}
}
@@ -170,24 +189,14 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 保存新的套餐明细
if (checkPackageDto.getItems() != null && !checkPackageDto.getItems().isEmpty()) {
List<CheckPackageDetail> details = new ArrayList<>();
int orderNum = 1;
for (CheckPackageDetailDto detailDto : checkPackageDto.getItems()) {
CheckPackageDetail detail = new CheckPackageDetail();
BeanUtils.copyProperties(detailDto, detail);
detail.setPackageId(checkPackage.getId());
detail.setOrderNum(orderNum++);
detail.setCreateTime(LocalDateTime.now());
detail.setUpdateTime(LocalDateTime.now());
details.add(detail);
}
List<CheckPackageDetail> details = convertToDetails(checkPackageDto.getItems(), checkPackage.getId(), 1);
checkPackageDetailService.saveBatch(details);
}
return R.ok("更新成功");
} catch (Exception e) {
log.error("更新检查套餐失败", e);
return R.fail("更新检查套餐失败: " + e.getMessage());
return R.fail("更新检查套餐失败" + e.getMessage());
}
}
@@ -201,11 +210,14 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
return R.fail("套餐不存在");
}
// 删除套餐明细
checkPackageDetailService.remove(
// 删除套餐明细 - 先删除子表数据
boolean removeDetailsResult = checkPackageDetailService.remove(
new LambdaQueryWrapper<CheckPackageDetail>()
.eq(CheckPackageDetail::getPackageId, id)
);
if (!removeDetailsResult) {
log.warn("删除套餐明细失败,套餐 ID: {}", id);
}
// 删除套餐主表
boolean deleteResult = checkPackageService.removeById(id);
@@ -213,11 +225,11 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
return R.fail("删除套餐失败");
}
log.info("删除检查套餐成功,套餐 ID: {}", id);
return R.ok("删除成功");
} catch (Exception e) {
log.error("删除检查套餐失败", e);
return R.fail("删除检查套餐失败: " + e.getMessage());
return R.fail("删除检查套餐失败" + e.getMessage());
}
}
}

View File

@@ -65,7 +65,7 @@ public class CheckPartAppServiceImpl implements ICheckPartAppService {
}
@Override
public R<?> exportCheckPart(String checkType, String name, String packageName, HttpServletResponse response) {
public void exportCheckPart(String checkType, String name, String packageName, HttpServletResponse response) {
LambdaQueryWrapper<CheckPart> wrapper = new LambdaQueryWrapper<>();
if (checkType != null && ObjectUtil.isNotEmpty(checkType)) {
wrapper.eq(CheckPart::getCheckType, checkType);
@@ -79,7 +79,13 @@ public class CheckPartAppServiceImpl implements ICheckPartAppService {
List<CheckPart> list = checkPartService.list(wrapper);
if (list.isEmpty()) {
return R.fail("导出Excel失败,无数据。");
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"导出Excel失败,无数据。\"}");
} catch (IOException e) {
log.error("写入响应失败", e);
}
return;
}
try {
@@ -102,8 +108,12 @@ public class CheckPartAppServiceImpl implements ICheckPartAppService {
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
} catch (IOException | IllegalAccessException e) {
log.error("导出Excel失败", e);
return R.fail("导出Excel失败" + e.getMessage());
try {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"导出Excel失败" + e.getMessage() + "\"}");
} catch (IOException ex) {
log.error("写入响应失败", ex);
}
}
return R.ok(null, "导出Excel成功");
}
}

View File

@@ -49,6 +49,9 @@ public class CheckPackageDetailDto {
@NotNull(message = "数量不能为空")
private Integer quantity;
/** 单位 */
private String unit;
/** 单价 */
@NotNull(message = "单价不能为空")
private BigDecimal unitPrice;

View File

@@ -343,11 +343,28 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
serviceRequest.setEncounterId(surgeryDto.getEncounterId()); // 就诊id
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
serviceRequest.setOrgId(orgId); // 执行科室
// 🔧 BugFix#318: 设置 contentJson包含手术名称
Map<String, String> serviceContentMap = new HashMap<>();
String surgeryNameFromDto = surgeryDto.getSurgeryName();
String surgeryCodeFromDto = surgeryDto.getSurgeryCode();
log.info("【DEBUG】surgeryName from DTO: {}", surgeryNameFromDto);
log.info("【DEBUG】surgeryCode from DTO: {}", surgeryCodeFromDto);
serviceContentMap.put("surgeryName", surgeryNameFromDto != null ? surgeryNameFromDto : "");
serviceContentMap.put("surgeryCode", surgeryCodeFromDto != null ? surgeryCodeFromDto : "");
try {
String contentJson = new ObjectMapper().writeValueAsString(serviceContentMap);
log.info("【DEBUG】Setting contentJson: {}", contentJson);
serviceRequest.setContentJson(contentJson);
} catch (JsonProcessingException e) {
log.error("【DEBUG】设置手术医嘱 contentJson 失败", e);
}
serviceRequestService.save(serviceRequest);
log.info("【DEBUG】Saved serviceRequest with ID: {}, contentJson: {}",
serviceRequest.getId(), serviceRequest.getContentJson());
// 生成收费项目
ChargeItem chargeItem = new ChargeItem();
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
chargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue()); // 收费状态:待收费
chargeItem.setBusNo("CI" + serviceRequest.getBusNo());
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
chargeItem.setPatientId(surgeryDto.getPatientId()); // 患者
@@ -541,15 +558,33 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 收集所有需要查询的ID
Set<Long> practitionerIds = new HashSet<>();
Set<Long> orgIds = new HashSet<>();
Set<Long> otherIds = new HashSet<>();
Set<Long> userIds = new HashSet<>(); // 用于查询sys_user表
// 收集Practitioner IDs
if (surgery.getMainSurgeonId() != null) practitionerIds.add(surgery.getMainSurgeonId());
if (surgery.getAnesthetistId() != null) practitionerIds.add(surgery.getAnesthetistId());
if (surgery.getAssistant1Id() != null) practitionerIds.add(surgery.getAssistant1Id());
if (surgery.getAssistant2Id() != null) practitionerIds.add(surgery.getAssistant2Id());
if (surgery.getScrubNurseId() != null) practitionerIds.add(surgery.getScrubNurseId());
if (surgery.getApplyDoctorId() != null) practitionerIds.add(surgery.getApplyDoctorId());
// 收集Practitioner IDs (医生相关)
if (surgery.getMainSurgeonId() != null) {
practitionerIds.add(surgery.getMainSurgeonId());
userIds.add(surgery.getMainSurgeonId());
}
if (surgery.getAnesthetistId() != null) {
practitionerIds.add(surgery.getAnesthetistId());
userIds.add(surgery.getAnesthetistId());
}
if (surgery.getAssistant1Id() != null) {
practitionerIds.add(surgery.getAssistant1Id());
userIds.add(surgery.getAssistant1Id());
}
if (surgery.getAssistant2Id() != null) {
practitionerIds.add(surgery.getAssistant2Id());
userIds.add(surgery.getAssistant2Id());
}
if (surgery.getScrubNurseId() != null) {
practitionerIds.add(surgery.getScrubNurseId());
userIds.add(surgery.getScrubNurseId());
}
if (surgery.getApplyDoctorId() != null) {
practitionerIds.add(surgery.getApplyDoctorId());
userIds.add(surgery.getApplyDoctorId());
}
// 收集Organization IDs
if (surgery.getOrgId() != null) orgIds.add(surgery.getOrgId());
@@ -558,69 +593,151 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 批量查询并缓存结果
Map<Long, String> practitionerNameMap = new HashMap<>();
Map<Long, String> orgNameMap = new HashMap<>();
Map<Long, String> userNameMap = new HashMap<>(); // 从sys_user查询的名称
// 批量查询Practitioner
if (!practitionerIds.isEmpty()) {
List<com.openhis.administration.domain.Practitioner> practitioners = practitionerService.listByIds(practitionerIds);
for (com.openhis.administration.domain.Practitioner p : practitioners) {
practitionerNameMap.put(p.getId(), p.getName());
try {
List<com.openhis.administration.domain.Practitioner> practitioners = practitionerService.listByIds(practitionerIds);
for (com.openhis.administration.domain.Practitioner p : practitioners) {
if (p.getName() != null && !p.getName().isEmpty()) {
practitionerNameMap.put(p.getId(), p.getName());
}
}
} catch (Exception e) {
log.warn("查询Practitioner名称失败: {}", e.getMessage());
}
}
// 批量查询SysUser (作为备选) - 使用逐个查询
if (!userIds.isEmpty()) {
try {
for (Long userId : userIds) {
SysUser u = sysUserService.selectUserById(userId);
if (u != null) {
String userName = u.getNickName() != null && !u.getNickName().isEmpty()
? u.getNickName()
: u.getUserName();
if (userName != null && !userName.isEmpty()) {
userNameMap.put(u.getUserId(), userName);
}
}
}
} catch (Exception e) {
log.warn("查询SysUser名称失败: {}", e.getMessage());
}
}
// 批量查询Organization
if (!orgIds.isEmpty()) {
List<Organization> orgs = organizationService.listByIds(orgIds);
for (Organization o : orgs) {
orgNameMap.put(o.getId(), o.getName());
try {
List<Organization> orgs = organizationService.listByIds(orgIds);
for (Organization o : orgs) {
if (o.getName() != null && !o.getName().isEmpty()) {
orgNameMap.put(o.getId(), o.getName());
}
}
} catch (Exception e) {
log.warn("查询Organization名称失败: {}", e.getMessage());
}
}
// 填充患者姓名
if (surgery.getPatientId() != null && surgery.getPatientName() == null) {
Patient patient = patientService.getById(surgery.getPatientId());
if (patient != null) {
surgery.setPatientName(patient.getName());
try {
Patient patient = patientService.getById(surgery.getPatientId());
if (patient != null) {
surgery.setPatientName(patient.getName());
}
} catch (Exception e) {
log.warn("查询患者名称失败: {}", e.getMessage());
}
}
// 使用缓存填充名称
// 填充医生名称 - 优先使用practitioner如果不存在则使用sys_user
if (surgery.getMainSurgeonId() != null && surgery.getMainSurgeonName() == null) {
surgery.setMainSurgeonName(practitionerNameMap.get(surgery.getMainSurgeonId()));
String name = practitionerNameMap.get(surgery.getMainSurgeonId());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getMainSurgeonId());
}
if (name != null && !name.isEmpty()) {
surgery.setMainSurgeonName(name);
}
}
if (surgery.getAnesthetistId() != null && surgery.getAnesthetistName() == null) {
surgery.setAnesthetistName(practitionerNameMap.get(surgery.getAnesthetistId()));
String name = practitionerNameMap.get(surgery.getAnesthetistId());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getAnesthetistId());
}
if (name != null && !name.isEmpty()) {
surgery.setAnesthetistName(name);
}
}
if (surgery.getAssistant1Id() != null && surgery.getAssistant1Name() == null) {
surgery.setAssistant1Name(practitionerNameMap.get(surgery.getAssistant1Id()));
String name = practitionerNameMap.get(surgery.getAssistant1Id());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getAssistant1Id());
}
if (name != null && !name.isEmpty()) {
surgery.setAssistant1Name(name);
}
}
if (surgery.getAssistant2Id() != null && surgery.getAssistant2Name() == null) {
surgery.setAssistant2Name(practitionerNameMap.get(surgery.getAssistant2Id()));
String name = practitionerNameMap.get(surgery.getAssistant2Id());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getAssistant2Id());
}
if (name != null && !name.isEmpty()) {
surgery.setAssistant2Name(name);
}
}
if (surgery.getScrubNurseId() != null && surgery.getScrubNurseName() == null) {
surgery.setScrubNurseName(practitionerNameMap.get(surgery.getScrubNurseId()));
String name = practitionerNameMap.get(surgery.getScrubNurseId());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getScrubNurseId());
}
if (name != null && !name.isEmpty()) {
surgery.setScrubNurseName(name);
}
}
if (surgery.getApplyDoctorId() != null && surgery.getApplyDoctorName() == null) {
surgery.setApplyDoctorName(practitionerNameMap.get(surgery.getApplyDoctorId()));
String name = practitionerNameMap.get(surgery.getApplyDoctorId());
if (name == null || name.isEmpty()) {
name = userNameMap.get(surgery.getApplyDoctorId());
}
if (name != null && !name.isEmpty()) {
surgery.setApplyDoctorName(name);
}
}
// 填充手术室名称
if (surgery.getOperatingRoomId() != null && surgery.getOperatingRoomName() == null) {
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
if (operatingRoom != null) {
surgery.setOperatingRoomName(operatingRoom.getName());
try {
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
if (operatingRoom != null) {
surgery.setOperatingRoomName(operatingRoom.getName());
}
} catch (Exception e) {
log.warn("查询手术室名称失败: {}", e.getMessage());
}
}
// 使用缓存填充组织名称
if (surgery.getOrgId() != null && surgery.getOrgName() == null) {
surgery.setOrgName(orgNameMap.get(surgery.getOrgId()));
String name = orgNameMap.get(surgery.getOrgId());
if (name != null && !name.isEmpty()) {
surgery.setOrgName(name);
}
}
if (surgery.getApplyDeptId() != null && surgery.getApplyDeptName() == null) {
surgery.setApplyDeptName(orgNameMap.get(surgery.getApplyDeptId()));
String name = orgNameMap.get(surgery.getApplyDeptId());
if (name != null && !name.isEmpty()) {
surgery.setApplyDeptName(name);
}
}
log.debug("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, orgName: {}",
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getOrgName());
log.debug("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, applyDeptName: {}",
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getApplyDeptName());
}
/**

View File

@@ -1,12 +1,16 @@
package com.openhis.web.clinicalmanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.SecurityUtils;
import com.openhis.administration.domain.Patient;
import com.openhis.administration.service.IOrganizationService;
import com.openhis.administration.service.IPatientService;
import com.openhis.clinical.domain.Surgery;
import com.openhis.clinical.service.ISurgeryService;
import com.openhis.surgicalschedule.domain.OpSchedule;
import com.openhis.surgicalschedule.service.IOpScheduleService;
import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
@@ -29,6 +33,8 @@ import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import static com.core.framework.datasource.DynamicDataSourceContextHolder.log;
/**
* 手术安排业务层实现类
*
@@ -47,6 +53,15 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
@Resource
private SurgicalScheduleAppMapper surgicalScheduleAppMapper;
@Resource
private ISurgeryService surgeryService;
@Resource
private com.openhis.administration.service.IOrganizationService organizationService;
@Resource
private com.core.system.service.ISysUserService sysUserService;
@Resource
private RequestFormManageAppMapper requestFormManageAppMapper;
@@ -94,13 +109,31 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
return R.fail("患者信息不存在");
}
}
//校验该时段内手术间是否被占用
LocalDateTime scheduleDate = opCreateScheduleDto.getEntryTime();//入室时间
String roomCode = opCreateScheduleDto.getRoomCode();//手术室编号
// 校验是否重复手术安排(必须在校验手术间占用之前执行,确保能正确返回重复错误)
// 同一患者 + 同一手术单号 + 同一手术名称 只能有一条有效安排记录
if (opCreateScheduleDto.getPatientId() != null
&& opCreateScheduleDto.getOperCode() != null && !opCreateScheduleDto.getOperCode().isEmpty()
&& opCreateScheduleDto.getOperName() != null && !opCreateScheduleDto.getOperName().isEmpty()) {
Boolean existsDuplicate = surgicalScheduleAppMapper.existsDuplicateSchedule(
opCreateScheduleDto.getPatientId(),
opCreateScheduleDto.getOperCode(),
opCreateScheduleDto.getOperName()
);
if (existsDuplicate != null && existsDuplicate) {
return R.fail("该患者此手术单号已存在手术安排,请勿重复提交");
}
}
// 校验该时段内手术间是否被占用
LocalDateTime startTime = opCreateScheduleDto.getEntryTime();//入室时间
LocalDateTime endTime = opCreateScheduleDto.getEndTime();//手术结束时间
Boolean scheduleConflict = surgicalScheduleAppMapper.isScheduleConflict(scheduleDate, endTime, roomCode);
if (scheduleConflict) {
return R.fail("该时段内手术间被占用");
String roomCode = opCreateScheduleDto.getRoomCode();//手术室编号
if (startTime != null && endTime != null && roomCode != null && !roomCode.isEmpty()) {
Boolean scheduleConflict = surgicalScheduleAppMapper.isScheduleConflict(startTime, endTime, roomCode);
if (scheduleConflict != null && scheduleConflict) {
return R.fail("该时段内手术间被占用");
}
}
LoginUser loginUser = new LoginUser();
@@ -113,6 +146,44 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
OpSchedule opSchedule = new OpSchedule();
BeanUtils.copyProperties(opCreateScheduleDto, opSchedule);
// 处理可能为null的字符串字段设置默认值为空字符串
if (opSchedule.getPreoperativeDiagnosis() == null) {
opSchedule.setPreoperativeDiagnosis("");
}
if (opSchedule.getPostoperativeDiagnosis() == null) {
opSchedule.setPostoperativeDiagnosis("");
}
if (opSchedule.getAnesMethod() == null) {
opSchedule.setAnesMethod("");
}
if (opSchedule.getAnesDoctor1Code() == null) {
opSchedule.setAnesDoctor1Code("");
}
if (opSchedule.getAnesDoctor2Code() == null) {
opSchedule.setAnesDoctor2Code("");
}
if (opSchedule.getAnesDoctor3Code() == null) {
opSchedule.setAnesDoctor3Code("");
}
if (opSchedule.getScrubNurseCode() == null) {
opSchedule.setScrubNurseCode("");
}
if (opSchedule.getCircuNurse1Code() == null) {
opSchedule.setCircuNurse1Code("");
}
if (opSchedule.getCircuNurse2Code() == null) {
opSchedule.setCircuNurse2Code("");
}
if (opSchedule.getScrubNurse1Code() == null) {
opSchedule.setScrubNurse1Code("");
}
if (opSchedule.getScrubNurse2Code() == null) {
opSchedule.setScrubNurse2Code("");
}
if (opSchedule.getSurgeonCode() == null) {
opSchedule.setSurgeonCode("");
}
// 设置创建者ID
opSchedule.setCreatorId(userId);
//设置创建人名称
@@ -127,12 +198,34 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
// 保存手术安排
boolean saved = opScheduleService.save(opSchedule);
//修改申请单状态为已排期
if (!saved) {
return R.fail("新增手术安排失败");
}
// Bug #247 修复:更新手术申请单状态为已排期 (1)
if (opCreateScheduleDto.getApplyId() != null) {
try {
// 通过手术单号查找手术申请记录并更新状态
LambdaQueryWrapper<com.openhis.clinical.domain.Surgery> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(com.openhis.clinical.domain.Surgery::getSurgeryNo, opSchedule.getOperCode())
.eq(com.openhis.clinical.domain.Surgery::getDeleteFlag, "0");
com.openhis.clinical.domain.Surgery surgery = surgeryService.getOne(queryWrapper);
if (surgery != null) {
surgery.setStatusEnum(1); // 1 = 已排期
surgery.setUpdateTime(new Date());
// 填充缺失的申请科室和主刀医生名称
fillSurgeryMissingNames(surgery);
surgeryService.updateById(surgery);
log.info("更新手术申请单状态为已排期 - surgeryNo: {}, surgeryId: {}", opSchedule.getOperCode(), surgery.getId());
}
} catch (Exception e) {
log.error("更新手术申请单状态失败 - operCode: {}", opSchedule.getOperCode(), e);
// 状态更新失败不影响主流程,只记录日志
}
}
return R.ok("新增手术安排成功");
}
@@ -158,6 +251,44 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
OpSchedule opSchedule = new OpSchedule();
BeanUtils.copyProperties(opScheduleDto, opSchedule);
// 处理可能为null的字符串字段设置默认值为空字符串
if (opSchedule.getPreoperativeDiagnosis() == null) {
opSchedule.setPreoperativeDiagnosis("");
}
if (opSchedule.getPostoperativeDiagnosis() == null) {
opSchedule.setPostoperativeDiagnosis("");
}
if (opSchedule.getAnesMethod() == null) {
opSchedule.setAnesMethod("");
}
if (opSchedule.getAnesDoctor1Code() == null) {
opSchedule.setAnesDoctor1Code("");
}
if (opSchedule.getAnesDoctor2Code() == null) {
opSchedule.setAnesDoctor2Code("");
}
if (opSchedule.getAnesDoctor3Code() == null) {
opSchedule.setAnesDoctor3Code("");
}
if (opSchedule.getScrubNurseCode() == null) {
opSchedule.setScrubNurseCode("");
}
if (opSchedule.getCircuNurse1Code() == null) {
opSchedule.setCircuNurse1Code("");
}
if (opSchedule.getCircuNurse2Code() == null) {
opSchedule.setCircuNurse2Code("");
}
if (opSchedule.getScrubNurse1Code() == null) {
opSchedule.setScrubNurse1Code("");
}
if (opSchedule.getScrubNurse2Code() == null) {
opSchedule.setScrubNurse2Code("");
}
if (opSchedule.getSurgeonCode() == null) {
opSchedule.setSurgeonCode("");
}
// 更新时间
opSchedule.setUpdateTime(new Date());
@@ -226,21 +357,21 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
int index = 0;
for (OpScheduleDto schedule : scheduleList) {
index++;
// 转换手术类型
String surgeryType = convertSurgeryNature(schedule.getSurgeryNature());
// 转换麻醉方法
String anesthesiaMethod = convertAnesMethod(schedule.getAnesMethod());
// 格式化安排时间
String formattedDate = formatScheduleDate(schedule.getScheduleDate());
writer.printf("%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
index, // 序号从1开始
schedule.getOrgName() != null ? schedule.getOrgName() : "",
schedule.getPatientName() != null ? schedule.getPatientName() : "",
schedule.getVisitId() != null ? schedule.getVisitId().toString() : "",
schedule.getIdentifierNo() != null ? schedule.getIdentifierNo() : "",
schedule.getOperCode() != null ? schedule.getOperCode() : "",
schedule.getOperName() != null ? schedule.getOperName() : "",
schedule.getApplyDeptName() != null ? schedule.getApplyDeptName() : "",
@@ -293,10 +424,84 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
/**
* 格式化安排时间
*/
private String formatScheduleDate(LocalDate scheduleDate) {
private String formatScheduleDate(LocalDateTime scheduleDate) {
if (scheduleDate == null) return "";
// 格式化为 yyyy-MM-dd
return scheduleDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
// 格式化为 yyyy-MM-dd HH:mm:ss
return scheduleDate.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
/**
* 填充手术申请中缺失的名称字段
* 在创建手术安排时调用确保关联的cli_surgery表中的名称字段有值
*
* @param surgery 手术申请对象
*/
private void fillSurgeryMissingNames(com.openhis.clinical.domain.Surgery surgery) {
// 填充申请科室名称
if ((surgery.getApplyDeptName() == null || surgery.getApplyDeptName().isEmpty())
&& surgery.getApplyDeptId() != null) {
try {
com.openhis.administration.domain.Organization org = organizationService.getById(surgery.getApplyDeptId());
if (org != null && org.getName() != null) {
surgery.setApplyDeptName(org.getName());
log.info("填充申请科室名称 - surgeryId: {}, deptId: {}, deptName: {}",
surgery.getId(), surgery.getApplyDeptId(), org.getName());
}
} catch (Exception e) {
log.warn("查询申请科室名称失败 - deptId: {}, error: {}", surgery.getApplyDeptId(), e.getMessage());
}
}
// 填充主刀医生名称
if ((surgery.getMainSurgeonName() == null || surgery.getMainSurgeonName().isEmpty())
&& surgery.getMainSurgeonId() != null) {
try {
com.core.common.core.domain.entity.SysUser user = sysUserService.selectUserById(surgery.getMainSurgeonId());
if (user != null) {
String surgeonName = user.getNickName() != null && !user.getNickName().isEmpty()
? user.getNickName()
: user.getUserName();
if (surgeonName != null) {
surgery.setMainSurgeonName(surgeonName);
log.info("填充主刀医生名称 - surgeryId: {}, surgeonId: {}, surgeonName: {}",
surgery.getId(), surgery.getMainSurgeonId(), surgeonName);
}
}
} catch (Exception e) {
log.warn("查询主刀医生名称失败 - surgeonId: {}, error: {}", surgery.getMainSurgeonId(), e.getMessage());
}
}
// 填充麻醉医生名称
if ((surgery.getAnesthetistName() == null || surgery.getAnesthetistName().isEmpty())
&& surgery.getAnesthetistId() != null) {
try {
com.core.common.core.domain.entity.SysUser user = sysUserService.selectUserById(surgery.getAnesthetistId());
if (user != null) {
String anesthetistName = user.getNickName() != null && !user.getNickName().isEmpty()
? user.getNickName()
: user.getUserName();
if (anesthetistName != null) {
surgery.setAnesthetistName(anesthetistName);
}
}
} catch (Exception e) {
log.warn("查询麻醉医生名称失败 - anesthetistId: {}, error: {}", surgery.getAnesthetistId(), e.getMessage());
}
}
// 填充执行科室名称
if ((surgery.getOrgName() == null || surgery.getOrgName().isEmpty())
&& surgery.getOrgId() != null) {
try {
com.openhis.administration.domain.Organization org = organizationService.getById(surgery.getOrgId());
if (org != null && org.getName() != null) {
surgery.setOrgName(org.getName());
}
} catch (Exception e) {
log.warn("查询执行科室名称失败 - orgId: {}, error: {}", surgery.getOrgId(), e.getMessage());
}
}
}
}

View File

@@ -24,6 +24,11 @@ public class OpCreateScheduleDto {
*/
private Long visitId;
/**
* 就诊卡号
*/
private String identifierNo;
/**
* 手术编码
*/
@@ -45,9 +50,10 @@ public class OpCreateScheduleDto {
private String postoperativeDiagnosis;
/**
* 手术安排日期
* 手术安排日期时间
*/
private LocalDate scheduleDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime scheduleDate;
/**
* 手术台次序号
@@ -82,11 +88,13 @@ public class OpCreateScheduleDto {
/**
* 入院时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime admissionTime;
/**
* 入手术室时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime entryTime;
/**
@@ -167,21 +175,25 @@ public class OpCreateScheduleDto {
/**
* 手术开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
/**
* 手术结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
/**
* 麻醉开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime anesStart;
/**
* 麻醉结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime anesEnd;
/**

View File

@@ -1,8 +1,10 @@
package com.openhis.web.clinicalmanage.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.openhis.surgicalschedule.domain.OpSchedule;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@@ -17,6 +19,20 @@ import java.time.LocalDate;
@EqualsAndHashCode(callSuper = true)
public class OpScheduleDto extends OpSchedule {
/**
* 手术安排日期开始(查询用)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate scheduleDateStart;
/**
* 手术安排日期结束(查询用)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate scheduleDateEnd;
/**
* 患者姓名
*/
@@ -27,6 +43,11 @@ public class OpScheduleDto extends OpSchedule {
*/
private Long encounterId;
/**
* 就诊卡号
*/
private String patientCardNo;
/**
* 性别
*/
@@ -55,6 +76,7 @@ public class OpScheduleDto extends OpSchedule {
/**
* 申请时间开始
*/
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private String applyTime;
/**

View File

@@ -45,6 +45,9 @@ public class SurgeryDto {
/** 就诊流水号 */
private String encounterNo;
/** 就诊卡号 */
private String patientCardNo;
/** 申请医生ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long applyDoctorId;

View File

@@ -58,4 +58,14 @@ public interface SurgicalScheduleAppMapper {
* @return 是否存在冲突的手术安排
*/
Boolean isScheduleConflict(LocalDateTime startTime, LocalDateTime endTime, String surgeryRoomId);
/**
* 检查是否存在重复的手术安排
*
* @param patientId 患者ID
* @param operCode 手术单号
* @param operName 手术名称
* @return 是否存在重复记录
*/
Boolean existsDuplicateSchedule(@Param("patientId") Long patientId, @Param("operCode") String operCode, @Param("operName") String operName);
}

View File

@@ -149,6 +149,14 @@ public interface IConsultationAppService {
* @return 会诊意见列表
*/
List<ConsultationOpinionDto> getConsultationOpinions(String consultationId);
/**
* 根据ID查询会诊申请详情
*
* @param id 会诊申请ID
* @return 会诊申请详情
*/
ConsultationRequestDto getConsultationById(Long id);
}

View File

@@ -61,6 +61,8 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.openhis.web.consultation.enums.ConsultationStatusEnum.CANCELLED;
/**
* 会诊管理AppService实现类
*
@@ -134,6 +136,8 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 根据就诊ID查询该患者的会诊申请
wrapper.eq(ConsultationRequest::getEncounterId, encounterId);
// 过滤已作废的数据
wrapper.ne(ConsultationRequest::getConsultationStatus, CANCELLED.getCode());
wrapper.orderByDesc(ConsultationRequest::getCreateTime);
List<ConsultationRequest> list = consultationRequestMapper.selectList(wrapper);
@@ -182,6 +186,11 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
wrapper.like(ConsultationRequest::getPatientName, dto.getPatientName());
}
// 会诊ID查询支持模糊匹配
if (StringUtils.hasText(dto.getConsultationId())) {
wrapper.like(ConsultationRequest::getConsultationId, dto.getConsultationId());
}
// 按创建时间倒序排列
wrapper.orderByDesc(ConsultationRequest::getConsultationRequestDate);
@@ -236,6 +245,11 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
wrapper.like(ConsultationRequest::getPatientName, dto.getPatientName());
}
// 会诊ID查询支持模糊匹配
if (StringUtils.hasText(dto.getConsultationId())) {
wrapper.like(ConsultationRequest::getConsultationId, dto.getConsultationId());
}
// 按创建时间倒序排列
wrapper.orderByDesc(ConsultationRequest::getConsultationRequestDate);
@@ -282,11 +296,15 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
entity = new ConsultationRequest();
entity.setConsultationId(generateConsultationId());
entity.setTenantId(SecurityUtils.getLoginUser().getTenantId().longValue());
entity.setConsultationRequestDate(new Date());
}
// 复制基本属性(现在字段名已统一,可以直接复制)
BeanUtils.copyProperties(dto, entity, "id", "consultationId", "invitedList", "submitFlag", "provisionalDiagnosis", "consultationRequestDate");
BeanUtils.copyProperties(dto, entity, "id", "consultationId", "invitedList", "submitFlag", "provisionalDiagnosis");
// 新增时:如果前端没有传递申请时间,使用服务器时间
if (!isUpdate && entity.getConsultationRequestDate() == null) {
entity.setConsultationRequestDate(new Date());
}
// 如果前端没有传递申请医生ID使用当前登录用户
if (entity.getRequestingPhysicianId() == null) {
@@ -427,7 +445,15 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
}
// 判断是"取消提交"还是"作废"
if ("取消提交".equals(cancelReason) && ConsultationStatusEnum.SUBMITTED.getCode().equals(entity.getConsultationStatus())) {
if ("取消提交".equals(cancelReason)) {
// 状态校验:禁止已确认 (20)、已签名 (30)、已完成 (40) 的会诊申请取消提交
if (entity.getConsultationStatus() >= ConsultationStatusEnum.CONFIRMED.getCode()) {
throw new IllegalArgumentException("当前状态不允许取消提交,只有已提交状态的会诊申请才能取消提交");
}
// 只有状态为 10(已提交) 才允许取消提交
if (!ConsultationStatusEnum.SUBMITTED.getCode().equals(entity.getConsultationStatus())) {
throw new IllegalArgumentException("只有已提交状态的会诊申请才能取消提交");
}
// 取消提交:将状态从"已提交"改回"新开"
entity.setConsultationStatus(ConsultationStatusEnum.NEW.getCode());
entity.setConfirmingPhysician(null);
@@ -439,8 +465,14 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
updateServiceRequestStatus(entity.getOrderId(), RequestStatus.DRAFT.getValue());
} else {
// 作废:状态改为"已取消"
entity.setConsultationStatus(ConsultationStatusEnum.CANCELLED.getCode());
// 作废:状态校验 - 已确认(20)、已签名(30)、已完成(40) 状态禁止作废
ConsultationStatusEnum currentStatus = ConsultationStatusEnum.getByCode(entity.getConsultationStatus());
if (currentStatus != null && !currentStatus.canCancel()) {
throw new IllegalArgumentException("当前状态【" + currentStatus.getDescription() + "】不允许作废,只有新开或已提交状态的会诊申请才能作废");
}
// 将状态改为"已取消"
entity.setConsultationStatus(CANCELLED.getCode());
entity.setCancelReason(cancelReason);
entity.setCancelNatureDate(new Date());
consultationRequestMapper.updateById(entity);
@@ -709,38 +741,64 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
dto.setInvitedList(invitedDtoList);
// 🎯 如果会诊已完成或已签名,填充会诊记录信息(从已签名的医生中获取)
if (entity.getConsultationStatus() != null &&
(entity.getConsultationStatus() == ConsultationStatusEnum.SIGNED.getCode() ||
entity.getConsultationStatus() == ConsultationStatusEnum.COMPLETED.getCode())) {
// 🎯 如果会诊已确认、已签名或已完成,填充会诊记录信息(从会诊确认表中获取)
// 会诊状态20=已确认30=已签名40=已完成
if (entity.getConsultationStatus() != null &&
entity.getConsultationStatus() >= ConsultationStatusEnum.CONFIRMED.getCode()) {
// 查询会诊确认记录
LambdaQueryWrapper<ConsultationConfirmation> confirmWrapper = new LambdaQueryWrapper<>();
confirmWrapper.eq(ConsultationConfirmation::getConsultationRequestId, entity.getId());
ConsultationConfirmation confirmation = consultationConfirmationMapper.selectOne(confirmWrapper);
// 查询所有已确认和已签名的医生invited_status >= 2
List<ConsultationInvited> confirmedAndSignedPhysicians = invitedList.stream()
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= 2)
.collect(Collectors.toList());
// 查询所有已签名的医生invited_status >= 3
List<ConsultationInvited> signedPhysicians = invitedList.stream()
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= 3)
.collect(Collectors.toList());
if (!signedPhysicians.isEmpty()) {
// 1. 会诊邀请参加医师:拼接所有已签名医生的"科室-姓名"
String invitedPhysiciansText = signedPhysicians.stream()
.map(inv -> inv.getInvitedDepartmentName() + "-" + inv.getInvitedPhysicianName())
.collect(Collectors.joining(""));
dto.setInvitedPhysiciansText(invitedPhysiciansText);
// 2. 会诊意见:汇总所有已签名医生的意见
String consultationOpinion = signedPhysicians.stream()
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
.map(ConsultationInvited::getConfirmOpinion)
.collect(Collectors.joining("\n"));
dto.setConsultationOpinion(consultationOpinion);
// 3. 所属医生、代表科室、签名医生、签名时间:使用第一个签名的医生
ConsultationInvited firstSigned = signedPhysicians.get(0);
dto.setAttendingPhysician(firstSigned.getInvitedPhysicianName());
dto.setRepresentDepartment(firstSigned.getInvitedDepartmentName());
dto.setSignPhysician(firstSigned.getInvitedPhysicianName());
dto.setSignTime(firstSigned.getSignatureTime());
log.info("填充会诊记录信息,已签名医生数:{}", signedPhysicians.size());
if (confirmation != null) {
// 1. 会诊确认参加医师:优先从确认表的confirming_physicians字段取值
if (StringUtils.hasText(confirmation.getConfirmingPhysicians())) {
dto.setInvitedPhysiciansText(confirmation.getConfirmingPhysicians());
} else if (!confirmedAndSignedPhysicians.isEmpty()) {
// 备用从invitedList拼接
String invitedPhysiciansText = confirmedAndSignedPhysicians.stream()
.map(inv -> inv.getInvitedDepartmentName() + "-" + inv.getInvitedPhysicianName())
.collect(Collectors.joining(""));
dto.setInvitedPhysiciansText(invitedPhysiciansText);
}
// 2. 会诊意见:优先从确认表取值
if (StringUtils.hasText(confirmation.getConsultationOpinion())) {
dto.setConsultationOpinion(confirmation.getConsultationOpinion());
} else if (!confirmedAndSignedPhysicians.isEmpty()) {
// 备用从invitedList汇总
String consultationOpinion = confirmedAndSignedPhysicians.stream()
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
.map(ConsultationInvited::getConfirmOpinion)
.collect(Collectors.joining("\n"));
dto.setConsultationOpinion(consultationOpinion);
}
// 3. 签名医生、签名时间:从确认表取值
dto.setSignPhysician(confirmation.getSignature());
dto.setSignTime(confirmation.getSignatureDate());
}
// 4. 所属医生、代表科室:使用第一个确认的医生(向后兼容)
if (!confirmedAndSignedPhysicians.isEmpty()) {
ConsultationInvited firstConfirmed = confirmedAndSignedPhysicians.get(0);
dto.setAttendingPhysician(firstConfirmed.getInvitedPhysicianName());
dto.setRepresentDepartment(firstConfirmed.getInvitedDepartmentName());
log.info("填充会诊记录信息,已确认和已签名医生数:{},已签名医生数:{}",
confirmedAndSignedPhysicians.size(), signedPhysicians.size());
}
}
}
@@ -1265,9 +1323,9 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
// 4. 更新邀请记录(存储会诊意见)
// 格式:科室-医生:意见内容
String formattedOpinion = String.format("%s-%s%s",
currentDeptName,
currentPhysicianName,
String formattedOpinion = String.format("%s-%s%s",
currentDeptName,
currentPhysicianName,
dto.getConsultationOpinion());
invited.setInvitedStatus(ConsultationStatusEnum.CONFIRMED.getCode()); // 已确认
@@ -1775,5 +1833,26 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
return new ArrayList<>();
}
}
@Override
public ConsultationRequestDto getConsultationById(Long id) {
try {
if (id == null) {
throw new IllegalArgumentException("会诊申请ID不能为空");
}
// 1. 查询会诊申请
ConsultationRequest request = consultationRequestMapper.selectById(id);
if (request == null) {
throw new IllegalArgumentException("会诊申请不存在ID: " + id);
}
// 2. 转换为DTO并返回
return convertToDto(request);
} catch (Exception e) {
log.error("查询会诊申请详情失败", e);
throw new RuntimeException("查询会诊申请详情失败: " + e.getMessage());
}
}
}

View File

@@ -302,5 +302,21 @@ public class ConsultationController {
return R.fail("获取会诊意见列表失败: " + e.getMessage());
}
}
/**
* 根据ID查询会诊申请详情
*/
@ApiOperation("根据ID查询会诊申请详情")
@GetMapping("/detail/{id}")
public R<ConsultationRequestDto> getConsultationById(
@ApiParam("会诊申请ID") @PathVariable Long id) {
try {
ConsultationRequestDto detail = consultationAppService.getConsultationById(id);
return R.ok(detail);
} catch (Exception e) {
log.error("查询会诊申请详情失败", e);
return R.fail("查询会诊申请详情失败: " + e.getMessage());
}
}
}

View File

@@ -41,7 +41,7 @@ public enum ConsultationStatusEnum {
/**
* 已取消
*/
CANCELLED(50, "已取消");
CANCELLED(50, "已取消/作废");
/**
* 状态码
@@ -76,10 +76,12 @@ public enum ConsultationStatusEnum {
}
/**
* 判断是否可以取消
* 判断是否可以取消/作废
* 只有新开(0)和已提交(10)状态可以作废
* 已确认(20)、已签名(30)、已完成(40)状态禁止作废
*/
public boolean canCancel() {
return this == NEW || this == SUBMITTED || this == CONFIRMED;
return this == NEW || this == SUBMITTED;
}
}

View File

@@ -130,4 +130,13 @@ public interface IDiagTreatMAppService {
* @return 结果
*/
R<?> updatePricingFlag(List<Long> ids, Integer pricingFlag);
/**
* 诊疗目录下拉列表(轻量级,用于套餐设置)
*
* @param statusEnum 状态2=启用)
* @param searchKey 搜索关键词(可选)
* @return 只包含 id, name, busNo, retailPrice
*/
R<?> getDiagnosisTreatmentSimpleList(Integer statusEnum, String searchKey);
}

View File

@@ -78,7 +78,6 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
private IOperationRecordService operationRecordService;
@Resource
private IServiceRequestService serviceRequestService;
/**
* 诊疗目录初期查询
*
@@ -186,6 +185,14 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
public R<?> getDiseaseTreatmentPage(DiagnosisTreatmentSelParam DiagnosisTreatmentSelParam, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request) {
// 如果没有指定状态默认只查询启用状态status_enum=2避免显示未启用的项目导致保存失败
if (DiagnosisTreatmentSelParam == null) {
DiagnosisTreatmentSelParam = new DiagnosisTreatmentSelParam();
}
if (DiagnosisTreatmentSelParam.getStatusEnum() == null) {
DiagnosisTreatmentSelParam.setStatusEnum(PublicationStatus.ACTIVE.getValue());
}
// 临时保存ybType值并从参数对象中移除避免HisQueryUtils构建yb_type条件
String ybTypeValue = null;
if (DiagnosisTreatmentSelParam != null && StringUtils.isNotEmpty(DiagnosisTreatmentSelParam.getYbType())) {
@@ -200,6 +207,13 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
DiagnosisTreatmentSelParam.setInspectionTypeId(null); // 临时移除防止HisQueryUtils处理
}
// 临时保存pricingFlag值手动添加带表别名的条件
Integer pricingFlagValue = null;
if (DiagnosisTreatmentSelParam != null && DiagnosisTreatmentSelParam.getPricingFlag() != null) {
pricingFlagValue = DiagnosisTreatmentSelParam.getPricingFlag();
DiagnosisTreatmentSelParam.setPricingFlag(null); // 临时移除防止HisQueryUtils处理
}
// 构建查询条件
QueryWrapper<DiagnosisTreatmentDto> queryWrapper = HisQueryUtils.buildQueryWrapper(DiagnosisTreatmentSelParam,
searchKey, new HashSet<>(Arrays.asList("T1.bus_no", "T1.name", "T1.py_str", "T1.wb_str")), request);
@@ -218,9 +232,15 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
DiagnosisTreatmentSelParam.setInspectionTypeId(inspectionTypeIdValue);
}
// 分页查询
// 如果需要按划价标记过滤,添加带表别名的条件
if (pricingFlagValue != null) {
queryWrapper.eq("T1.pricing_flag", pricingFlagValue);
// 恢复参数对象中的值
DiagnosisTreatmentSelParam.setPricingFlag(pricingFlagValue);
}
IPage<DiagnosisTreatmentDto> diseaseTreatmentPage
= activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<DiagnosisTreatmentDto>(pageNo, pageSize), queryWrapper);
= activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<>(pageNo, pageSize), queryWrapper);
diseaseTreatmentPage.getRecords().forEach(e -> {
// 医保标记枚举类回显赋值
@@ -280,24 +300,19 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
}
/**
* 获取诊查项目列表医保类型为02
* 获取诊查项目列表医保类型为02,返回全量数据
*
* @param orgId 科室ID
* @param orgId 科室ID(兼容保留,不参与过滤)
* @return 诊查项目列表
*/
@Override
public R<?> getClinicItems(Long orgId) {
// 构建查询条件只查询医保类型为02费)的项目
// 构建查询条件只查询医保类型为02费)的项目,不按科室过滤
QueryWrapper<DiagnosisTreatmentDto> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("T2.yb_type", "02"); // 使用T2表的yb_type字段避免歧义
queryWrapper.eq("T1.delete_flag", "0"); // 只查询未删除的记录
queryWrapper.eq("T2.instance_table", "wor_activity_definition"); // 确保关联正确
// 如果提供了科室ID则过滤该科室的项目
if (orgId != null) {
queryWrapper.eq("T1.org_id", orgId); // 使用机构ID进行过滤
}
// 分页查询,设置一个较大的页大小以获取所有诊查项目
IPage<DiagnosisTreatmentDto> diseaseTreatmentPage
= activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<DiagnosisTreatmentDto>(1, 100), queryWrapper);
@@ -430,24 +445,17 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
*/
@Override
public R<?> editDiseaseTreatmentStop(List<Long> ids) {
List<ActivityDefinition> ActivityDefinitionList = new CopyOnWriteArrayList<>();
// 取得更新值
for (Long detail : ids) {
ActivityDefinition ActivityDefinition = new ActivityDefinition();
ActivityDefinition.setId(detail);
ActivityDefinition.setStatusEnum(PublicationStatus.RETIRED.getValue());
ActivityDefinitionList.add(ActivityDefinition);
List<ActivityDefinition> actList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
ActivityDefinition act = new ActivityDefinition();
act.setId(id);
act.setStatusEnum(PublicationStatus.RETIRED.getValue());
actList.add(act);
}
// 插入操作记录
operationRecordService.addIdsOperationRecord(DbOpType.STOP.getCode(),
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids);
// 更新诊疗信息
return activityDefinitionService.updateBatchById(ActivityDefinitionList)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
activityDefinitionService.updateBatchById(actList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"\u8bca\u7597\u76ee\u5f55"}));
}
/**
@@ -458,24 +466,17 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
*/
@Override
public R<?> editDiseaseTreatmentStart(List<Long> ids) {
List<ActivityDefinition> ActivityDefinitionList = new CopyOnWriteArrayList<>();
// 取得更新值
for (Long detail : ids) {
ActivityDefinition ActivityDefinition = new ActivityDefinition();
ActivityDefinition.setId(detail);
ActivityDefinition.setStatusEnum(PublicationStatus.ACTIVE.getValue());
ActivityDefinitionList.add(ActivityDefinition);
List<ActivityDefinition> actList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
ActivityDefinition act = new ActivityDefinition();
act.setId(id);
act.setStatusEnum(PublicationStatus.ACTIVE.getValue());
actList.add(act);
}
// 插入操作记录
operationRecordService.addIdsOperationRecord(DbOpType.START.getCode(),
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, ids);
// 更新诊疗信息
return activityDefinitionService.updateBatchById(ActivityDefinitionList)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
activityDefinitionService.updateBatchById(actList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"诊疗目录"}));
}
/**
@@ -813,4 +814,20 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
}
return activityDefinition;
}
/**
* 诊疗目录下拉列表(轻量级,用于套餐设置)
* 只查询必要字段减少JOIN提高查询速度
* 支持搜索关键词过滤
*
* @param statusEnum 状态2=启用)
* @param searchKey 搜索关键词
* @return 只包含 id, name, busNo, retailPrice
*/
@Override
public R<?> getDiagnosisTreatmentSimpleList(Integer statusEnum, String searchKey) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
List<DiagnosisTreatmentDto> list = activityDefinitionManageMapper.getDiagnosisTreatmentSimpleList(statusEnum, tenantId, searchKey);
return R.ok(list);
}
}

View File

@@ -207,4 +207,21 @@ public class DiagnosisTreatmentController {
.orderByAsc(InspectionType::getSortOrder);
return R.ok(inspectionTypeService.list(queryWrapper));
}
/**
* 诊疗目录下拉列表(轻量级,用于套餐设置)
* 只查询必要字段减少JOIN提高查询速度
* 支持搜索关键词过滤
*
* @param statusEnum 状态2=启用)
* @param searchKey 搜索关键词
* @return 只包含 id, name, busNo, retailPrice
*/
@GetMapping("/simple-list")
public R<?> getDiagnosisTreatmentSimpleList(@RequestParam(required = false) Integer statusEnum, @RequestParam(required = false) String searchKey) {
if (statusEnum == null) {
statusEnum = 2;
}
return diagTreatMAppService.getDiagnosisTreatmentSimpleList(statusEnum, searchKey);
}
}

View File

@@ -139,4 +139,18 @@ public class DiagnosisTreatmentDto {
/** 检验类型名称(用于前端 testType 字段) */
private String testType;
/** 费用套餐ID关联 inspection_basic_information */
@JsonSerialize(using = ToStringSerializer.class)
private Long feePackageId;
/** 费用套餐名称JOIN inspection_basic_information.package_name */
private String packageName;
/** 下级医技类型ID关联 inspection_type 子类) */
@JsonSerialize(using = ToStringSerializer.class)
private Long subItemId;
/** 下级医技类型名称JOIN inspection_type.name */
private String subItemName;
}

View File

@@ -33,4 +33,7 @@ public class DiagnosisTreatmentSelParam {
/** 检验类型ID */
private Long inspectionTypeId;
/** 划价标记 */
private Integer pricingFlag;
}

View File

@@ -129,7 +129,15 @@ public class DiagnosisTreatmentUpDto {
/** 服务范围 */
private String serviceRange;
/** 检验类型ID */
/** 检验类型ID(关联 inspection_type 大类parent_id 为空) */
@JsonSerialize(using = ToStringSerializer.class)
private Long inspectionTypeId;
/** 费用套餐ID关联 inspection_basic_information */
@JsonSerialize(using = ToStringSerializer.class)
private Long feePackageId;
/** 下级医技类型ID关联 inspection_type 子类parent_id 不为空) */
@JsonSerialize(using = ToStringSerializer.class)
private Long subItemId;
}

View File

@@ -8,6 +8,8 @@ import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 诊疗定义管理
*
@@ -36,4 +38,14 @@ public interface ActivityDefinitionManageMapper {
*/
DiagnosisTreatmentDto getDiseaseTreatmentOne(@Param("id") Long id, @Param("tenantId") Integer tenantId);
/**
* 诊疗目录下拉列表轻量级只查4个字段
*
* @param statusEnum 状态
* @param tenantId 租户ID
* @param searchKey 搜索关键词(可选)
* @return id, name, busNo, retailPrice
*/
List<DiagnosisTreatmentDto> getDiagnosisTreatmentSimpleList(@Param("statusEnum") Integer statusEnum, @Param("tenantId") Integer tenantId, @Param("searchKey") String searchKey);
}

View File

@@ -0,0 +1,51 @@
package com.openhis.web.datadictionary.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 检验项目定义管理 Mapper操作 lab_activity_definition 表)
*/
@Repository
public interface LabActivityDefinitionManageMapper {
/**
* 检验项目分页查询
*
* @param page 分页参数
* @param queryWrapper 查询条件
* @return 分页结果
*/
IPage<DiagnosisTreatmentDto> getLabActivityDefinitionPage(
@Param("page") Page<DiagnosisTreatmentDto> page,
@Param(Constants.WRAPPER) QueryWrapper<DiagnosisTreatmentDto> queryWrapper);
/**
* 检验项目详情
*
* @param id 项目ID
* @param tenantId 租户ID
* @return 详情
*/
DiagnosisTreatmentDto getLabActivityDefinitionOne(@Param("id") Long id, @Param("tenantId") Integer tenantId);
/**
* 检验项目下拉列表(轻量级)
*
* @param statusEnum 状态
* @param tenantId 租户ID
* @param searchKey 搜索关键词(可选)
* @return 列表
*/
List<DiagnosisTreatmentDto> getLabActivityDefinitionSimpleList(
@Param("statusEnum") Integer statusEnum,
@Param("tenantId") Integer tenantId,
@Param("searchKey") String searchKey);
}

View File

@@ -31,7 +31,7 @@ public interface IDoctorStationAdviceAppService {
*/
IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing);
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing, String categoryCode);
/**
* 查询医嘱绑定信息

View File

@@ -16,14 +16,17 @@ import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils;
import com.core.web.util.TenantOptionUtil;
import com.openhis.administration.domain.Account;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.domain.Encounter;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.administration.service.IEncounterService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.openhis.medication.domain.MedicationDispense;
import com.openhis.medication.domain.MedicationRequest;
import com.openhis.medication.service.IMedicationDispenseService;
import com.openhis.medication.service.IMedicationRequestService;
@@ -42,6 +45,8 @@ import com.openhis.workflow.service.IActivityDefinitionService;
import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService;
import com.openhis.workflow.domain.InventoryItem;
import com.openhis.workflow.service.IInventoryItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -106,6 +111,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource
RedisCache redisCache;
@Resource
IEncounterService iEncounterService;
@Resource
IInventoryItemService inventoryItemService;
// 缓存 key 前缀
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
// 缓存过期时间(小时)
@@ -130,7 +141,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Override
public IPage<AdviceBaseDto> getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId,
List<Long> adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize,
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing) {
Integer pricingFlag, List<Integer> adviceTypes, String orderPricing, String categoryCode) {
// 生成缓存键处理可能的null值
String safeSearchKey = searchKey != null ? searchKey : "";
@@ -198,7 +209,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList,
adviceTypes, searchKey,
adviceTypes, searchKey, categoryCode,
queryWrapper);
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
@@ -389,28 +400,41 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
unitCode = baseDto.getUnitCode();
chargeItemDefinitionId = baseDto.getChargeItemDefinitionId();
List<AdvicePriceDto> priceDtoList = new ArrayList<>();
// 库存信息里取 命中条件 去匹配价格
for (AdviceInventoryDto adviceInventoryDto : inventoryList) {
Long finalChargeItemDefinitionId = chargeItemDefinitionId;
String finalUnitCode = unitCode;
// 从定价子表取价格(适用于批次售卖场景)
List<AdvicePriceDto> childPrice = childCharge.stream()
.filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null
&& e.getDefinitionId().equals(finalChargeItemDefinitionId)
&& e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null
&& e.getConditionValue().equals(adviceInventoryDto.getLotNumber()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 从定价主表取价格(适用于统一零售价场景)
// 🔧 Bug #220 修复:耗材无库存时也需要设置价格
if (inventoryList.isEmpty()) {
// 库存为空时,直接从定价主表获取统一零售价
String finalUnitCode = unitCode; // 创建final变量用于lambda
List<AdvicePriceDto> mainPrice = mainCharge.stream()
.filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 按批次售价
if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) {
priceDtoList.addAll(childPrice);
} else {
priceDtoList.addAll(mainPrice);
priceDtoList.addAll(mainPrice);
} else {
// 库存信息里取 命中条件 去匹配价格
for (AdviceInventoryDto adviceInventoryDto : inventoryList) {
Long finalChargeItemDefinitionId = chargeItemDefinitionId;
String finalUnitCode = unitCode;
// 从定价子表取价格(适用于批次售卖场景)
List<AdvicePriceDto> childPrice = childCharge.stream()
.filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null
&& e.getDefinitionId().equals(finalChargeItemDefinitionId)
&& e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null
&& e.getConditionValue().equals(adviceInventoryDto.getLotNumber()))
.peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode
.collect(Collectors.toList());
// 从定价主表取价格(适用于统一零售价场景)
List<AdvicePriceDto> mainPrice = mainCharge.stream()
.filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
.collect(Collectors.toList());
// 按批次售价
if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) {
priceDtoList.addAll(childPrice);
} else {
priceDtoList.addAll(mainPrice);
}
}
}
// 价格信息
@@ -467,15 +491,105 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
Long organizationId = adviceSaveParam.getOrganizationId();
// 医嘱分类信息
List<AdviceSaveDto> adviceSaveList = adviceSaveParam.getAdviceSaveList();
// 药品
// 🔍 Debug日志: 记录请求入口
log.info("========== BugFix#219 DEBUG START ==========");
log.info("saveAdvice called, adviceOpType={}, organizationId={}, adviceSaveList.size={}",
adviceOpType, organizationId, adviceSaveList != null ? adviceSaveList.size() : 0);
if (adviceSaveList != null && !adviceSaveList.isEmpty()) {
for (int i = 0; i < adviceSaveList.size(); i++) {
AdviceSaveDto dto = adviceSaveList.get(i);
log.info("Request[{}]: requestId={}, dbOpType={}, adviceType={}, encounterId={}, patientId={}",
i, dto.getRequestId(), dto.getDbOpType(), dto.getAdviceType(),
dto.getEncounterId(), dto.getPatientId());
}
}
// 🔧 Bug Fix: 校验并补全patientId和encounterId如果为null尝试从医嘱记录获取
for (AdviceSaveDto adviceSaveDto : adviceSaveList) {
// 对于删除操作如果encounterId为null尝试从医嘱记录获取
if (adviceSaveDto.getEncounterId() == null && DbOpType.DELETE.getCode().equals(adviceSaveDto.getDbOpType())) {
// 尝试从各类医嘱记录中获取encounterId
Long requestId = adviceSaveDto.getRequestId();
if (requestId != null) {
// 尝试从药品医嘱获取
MedicationRequest medRequest = iMedicationRequestService.getById(requestId);
if (medRequest != null && medRequest.getEncounterId() != null) {
adviceSaveDto.setEncounterId(medRequest.getEncounterId());
adviceSaveDto.setPatientId(medRequest.getPatientId());
log.info("BugFix: 删除药品医嘱时自动补全encounterId和patientId: requestId={}, encounterId={}, patientId={}",
requestId, medRequest.getEncounterId(), medRequest.getPatientId());
} else {
// 尝试从耗材医嘱获取
DeviceRequest devRequest = iDeviceRequestService.getById(requestId);
if (devRequest != null && devRequest.getEncounterId() != null) {
adviceSaveDto.setEncounterId(devRequest.getEncounterId());
adviceSaveDto.setPatientId(devRequest.getPatientId());
log.info("BugFix: 删除耗材医嘱时自动补全encounterId和patientId: requestId={}, encounterId={}, patientId={}",
requestId, devRequest.getEncounterId(), devRequest.getPatientId());
} else {
// 尝试从诊疗医嘱获取
ServiceRequest srvRequest = iServiceRequestService.getById(requestId);
if (srvRequest != null && srvRequest.getEncounterId() != null) {
adviceSaveDto.setEncounterId(srvRequest.getEncounterId());
adviceSaveDto.setPatientId(srvRequest.getPatientId());
log.info("BugFix: 删除诊疗医嘱时自动补全encounterId和patientId: requestId={}, encounterId={}, patientId={}",
requestId, srvRequest.getEncounterId(), srvRequest.getPatientId());
}
}
}
}
}
// 首先检查encounterId是否为null
if (adviceSaveDto.getEncounterId() == null) {
log.error("encounterId为null无法保存医嘱, dbOpType={}, requestId={}, adviceType={}",
adviceSaveDto.getDbOpType(), adviceSaveDto.getRequestId(), adviceSaveDto.getAdviceType());
return R.fail(null, "就诊信息不完整,请重新选择患者后再试");
}
// 如果patientId为null尝试从encounter获取
if (adviceSaveDto.getPatientId() == null) {
// 从就诊记录中获取patientId
Encounter encounter = iEncounterService.getById(adviceSaveDto.getEncounterId());
if (encounter != null && encounter.getPatientId() != null) {
adviceSaveDto.setPatientId(encounter.getPatientId());
log.info("自动补全patientId: encounterId={}, patientId={}",
adviceSaveDto.getEncounterId(), encounter.getPatientId());
} else {
log.error("无法获取patientId: encounterId={}", adviceSaveDto.getEncounterId());
return R.fail(null, "无法获取患者信息,请重新选择患者");
}
}
}
// 药品前端adviceType=1
List<AdviceSaveDto> medicineList = adviceSaveList.stream()
.filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 耗材
.filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())
|| e.getAdviceType() == 1).collect(Collectors.toList());
// 耗材前端adviceType=4后端ItemType.DEVICE=2
List<AdviceSaveDto> deviceList = adviceSaveList.stream()
.filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 诊疗活动
.filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())
|| e.getAdviceType() == 4) // 前端耗材类型值为4
.collect(Collectors.toList());
// 诊疗活动前端adviceType=3诊疗、adviceType=5会诊、adviceType=6手术
List<AdviceSaveDto> activityList = adviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())
|| e.getAdviceType() == 3 // 前端诊疗类型值为3
|| e.getAdviceType() == 5 // 前端会诊类型值为5
|| ItemType.SURGERY.getValue().equals(e.getAdviceType())) // 🔧 BugFix#318: 手术类型值为6
.collect(Collectors.toList());
// 🔍 Debug日志: 记录分类结果
log.info("BugFix#219: 医嘱分类完成 - 药品:{}, 耗材:{}, 诊疗:{}",
medicineList.size(), deviceList.size(), activityList.size());
// 统计各类删除操作
long medDeleteCount = medicineList.stream().filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).count();
long devDeleteCount = deviceList.stream().filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).count();
long actDeleteCount = activityList.stream().filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).count();
log.info("BugFix#219: 待删除数量 - 药品:{}, 耗材:{}, 诊疗:{}", medDeleteCount, devDeleteCount, actDeleteCount);
/**
* 保存时,校验库存
@@ -493,11 +607,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
}
// 🔧 Bug Fix: 跳过耗材的库存校验(耗材的库存校验逻辑不同)
// 🔧 Bug Fix: 跳过耗材、诊疗、手术的库存校验
List<AdviceSaveDto> needCheckList = adviceSaveList.stream()
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())
&& !ItemType.DEVICE.getValue().equals(e.getAdviceType())) // 排除耗材
&& !ItemType.DEVICE.getValue().equals(e.getAdviceType())
&& !ItemType.SURGERY.getValue().equals(e.getAdviceType())) // 🔧 BugFix#318: 排除手术类型
.collect(Collectors.toList());
// 校验库存
String tipRes = adviceUtils.checkInventory(needCheckList);
@@ -535,11 +650,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
.collect(Collectors.toList());
// 就诊id
Long encounterId = adviceSaveList.get(0).getEncounterId();
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
.set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue())
.eq(ChargeItem::getEncounterId, encounterId)
.eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue())
.in(ChargeItem::getServiceId, requestIds));
// 使用安全的更新方法,避免并发冲突
iChargeItemService.updateChargeStatusByConditionSafe(
encounterId,
ChargeItemStatus.DRAFT.getValue(),
ChargeItemStatus.PLANNED.getValue(),
requestIds);
}
// 数据变更后清理相关缓存
@@ -592,6 +709,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
List<Long> delRequestIdList = deleteList.stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
if (!delRequestIdList.isEmpty()) {
List<ChargeItem> chargeItemList = iChargeItemService.getChargeItemInfoByReqId(delRequestIdList);
// 🔧 BugFix#219: 过滤只保留药品类型的费用项
if (chargeItemList != null && !chargeItemList.isEmpty()) {
chargeItemList = chargeItemList.stream()
.filter(ci -> CommonConstants.TableName.MED_MEDICATION_REQUEST.equals(ci.getServiceTable()))
.collect(Collectors.toList());
}
if (chargeItemList != null && !chargeItemList.isEmpty()) {
for (ChargeItem ci : chargeItemList) {
if (ChargeItemStatus.BILLED.getValue().equals(ci.getStatusEnum())) {
@@ -604,9 +727,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iMedicationRequestService.removeById(adviceSaveDto.getRequestId());
// 删除已经产生的药品发放信息
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
// 删除费用项
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.MED_MEDICATION_REQUEST,
adviceSaveDto.getRequestId());
// 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.MED_MEDICATION_REQUEST;
// 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
log.info("BugFix#219: 药品医嘱删除完成, requestId={}, serviceTable={}", requestId, serviceTable);
// 删除基于这个药品生成的需要执行的诊疗请求
iServiceRequestService.remove(
new LambdaQueryWrapper<ServiceRequest>()
@@ -622,6 +748,28 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
List<String> medRequestIdList = new ArrayList<>();
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null与handleBoundDevices保持一致
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
boolean firstTimeSave = false;// 第一次保存
medicationRequest = new MedicationRequest();
medicationRequest.setId(adviceSaveDto.getRequestId()); // 主键id
@@ -634,38 +782,40 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
if (is_sign) {
medicationRequest.setSignCode(signCode);
}
// 保存时,处理数据(请求,发放,账单)
// 🔧 Bug Fix: 签发时也需要设置关键字段修复BUG #181
// 保存时生成业务编号,签发时沿用已有编号
if (is_save) {
medicationRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.MEDICATION_RES_NO.getPrefix(), 4));
medicationRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
medicationRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
medicationRequest.setExecuteNum(adviceSaveDto.getExecuteNum()); // 执行次数
medicationRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
medicationRequest.setLotNumber(adviceSaveDto.getLotNumber()); // 产品批号
medicationRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
medicationRequest.setMedicationId(adviceSaveDto.getAdviceDefinitionId());// 医嘱定义id
medicationRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
medicationRequest.setPractitionerId(adviceSaveDto.getPractitionerId()); // 开方医生
medicationRequest.setOrgId(adviceSaveDto.getFounderOrgId()); // 开方人科室
medicationRequest.setReqAuthoredTime(curDate); // 请求开始时间
// 发放药房
medicationRequest.setPerformLocation(adviceSaveDto.getLocationId());
medicationRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
medicationRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
medicationRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
medicationRequest.setTherapyEnum(adviceSaveDto.getTherapyEnum()); // 治疗类型,默认-临时
medicationRequest.setMethodCode(adviceSaveDto.getMethodCode()); // 用法
medicationRequest.setRateCode(adviceSaveDto.getRateCode()); // 用药频次
medicationRequest.setDose(adviceSaveDto.getDose()); // 单次剂量
medicationRequest.setDoseUnitCode(adviceSaveDto.getDoseUnitCode()); // 剂量单位
medicationRequest.setDispensePerDuration(adviceSaveDto.getDispensePerDuration()); // 每次发药供应天数
medicationRequest.setPackageId(adviceSaveDto.getPackageId()); // 组套id
medicationRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
medicationRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
medicationRequest.setSkinTestFlag(adviceSaveDto.getSkinTestFlag()); // 皮试标志
medicationRequest.setInfusionFlag(adviceSaveDto.getInjectFlag()); // 输液标志
medicationRequest.setSortNumber(adviceSaveDto.getSortNumber()); // 排序号
}
medicationRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
medicationRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
medicationRequest.setExecuteNum(adviceSaveDto.getExecuteNum()); // 执行次数
medicationRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
medicationRequest.setLotNumber(adviceSaveDto.getLotNumber()); // 产品批号
medicationRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
medicationRequest.setMedicationId(adviceSaveDto.getAdviceDefinitionId());// 医嘱定义id
medicationRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
medicationRequest.setPractitionerId(adviceSaveDto.getPractitionerId()); // 开方医生
medicationRequest.setOrgId(adviceSaveDto.getFounderOrgId()); // 开方人科室
medicationRequest.setReqAuthoredTime(curDate); // 请求开始时间
// 发放药房
medicationRequest.setPerformLocation(adviceSaveDto.getLocationId());
medicationRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
medicationRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
medicationRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
medicationRequest.setTherapyEnum(adviceSaveDto.getTherapyEnum()); // 治疗类型,默认-临时
medicationRequest.setMethodCode(adviceSaveDto.getMethodCode()); // 用法
medicationRequest.setRateCode(adviceSaveDto.getRateCode()); // 用药频次
medicationRequest.setDose(adviceSaveDto.getDose()); // 单次剂量
medicationRequest.setDoseUnitCode(adviceSaveDto.getDoseUnitCode()); // 剂量单位
medicationRequest.setDispensePerDuration(adviceSaveDto.getDispensePerDuration()); // 每次发药供应天数
medicationRequest.setPackageId(adviceSaveDto.getPackageId()); // 组套id
medicationRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
medicationRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
medicationRequest.setSkinTestFlag(adviceSaveDto.getSkinTestFlag()); // 皮试标志
medicationRequest.setInfusionFlag(adviceSaveDto.getInjectFlag()); // 输液标志
medicationRequest.setSortNumber(adviceSaveDto.getSortNumber()); // 排序号
if (medicationRequest.getId() == null) {
firstTimeSave = true;
}
@@ -774,6 +924,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 关联到药品请求
deviceRequest.setBasedOnId(medicationRequest.getId());
deviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
// 🔧 Bug Fix #145: 设置处方号,确保门诊收费能正确显示
deviceRequest.setPrescriptionNo(adviceSaveDto.getPrescriptionNo());
iDeviceRequestService.save(deviceRequest);
@@ -786,7 +938,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceAdviceDto.setAdviceTableName(CommonConstants.TableName.ADM_DEVICE_DEFINITION);
IPage<AdviceBaseDto> devicePage = getAdviceBaseInfo(deviceAdviceDto, null, null, null,
adviceSaveDto.getFounderOrgId(), 1, 1, Whether.NO.getValue(),
List.of(ItemType.DEVICE.getValue()), null);
List.of(ItemType.DEVICE.getValue()), null, null);
if (devicePage == null || devicePage.getRecords().isEmpty()) {
log.warn("无法找到耗材定价信息: deviceDefId={}", boundDevice.getDevActId());
@@ -806,7 +958,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceChargeItem.setTenantId(tenantId);
deviceChargeItem.setCreateBy(currentUsername);
deviceChargeItem.setCreateTime(curDate);
deviceChargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
deviceChargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue());
deviceChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
deviceChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
deviceChargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号,与药品一致
@@ -822,7 +974,29 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceChargeItem.setServiceId(deviceRequest.getId());
deviceChargeItem.setProductTable(CommonConstants.TableName.ADM_DEVICE_DEFINITION);
deviceChargeItem.setProductId(boundDevice.getDevActId());
deviceChargeItem.setAccountId(adviceSaveDto.getAccountId());
// 🔧 Bug Fix #281: 如果accountId为null从就诊中获取账户ID如果没有则自动创建
Long deviceAccountId = adviceSaveDto.getAccountId();
if (deviceAccountId == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
deviceAccountId = selfAccount.getId();
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
// 🔧 Bug Fix: 设置账户名称避免数据库NOT NULL约束错误
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
deviceAccountId = iAccountService.saveAccountByRegister(newAccount);
}
}
deviceChargeItem.setAccountId(deviceAccountId);
deviceChargeItem.setConditionId(adviceSaveDto.getConditionId());
deviceChargeItem.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId());
deviceChargeItem.setDispenseId(dispenseId);
@@ -842,6 +1016,18 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
* 处理耗材
*/
private void handDevice(List<AdviceSaveDto> deviceList, Date curDate, String adviceOpType) {
// 🔍 Debug日志: handDevice方法入口
log.info("BugFix#219: ========== handDevice START ==========");
log.info("BugFix#219: handDevice called, deviceList.size={}, adviceOpType={}",
deviceList != null ? deviceList.size() : 0, adviceOpType);
if (deviceList != null && !deviceList.isEmpty()) {
for (int i = 0; i < deviceList.size(); i++) {
AdviceSaveDto dto = deviceList.get(i);
log.info("BugFix#219: Device[{}]: requestId={}, dbOpType={}",
i, dto.getRequestId(), dto.getDbOpType());
}
}
// 当前登录账号的科室id
Long orgId = SecurityUtils.getLoginUser().getOrgId();
// 获取当前登录用户的tenantId
@@ -863,10 +1049,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 删除
List<AdviceSaveDto> deleteList = deviceList.stream()
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
// 🔍 Debug日志: 记录删除列表
log.info("BugFix#219: handDevice - insertOrUpdateList.size={}, deleteList.size={}",
insertOrUpdateList.size(), deleteList.size());
if (!deleteList.isEmpty()) {
for (AdviceSaveDto dto : deleteList) {
log.info("BugFix#219: handDevice - 待删除: requestId={}", dto.getRequestId());
}
}
// 校验删除的医嘱是否已经收费
List<Long> delRequestIdList = deleteList.stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
if (!delRequestIdList.isEmpty()) {
List<ChargeItem> chargeItemList = iChargeItemService.getChargeItemInfoByReqId(delRequestIdList);
// 🔧 BugFix#219: 过滤只保留耗材类型的费用项
if (chargeItemList != null && !chargeItemList.isEmpty()) {
chargeItemList = chargeItemList.stream()
.filter(ci -> CommonConstants.TableName.WOR_DEVICE_REQUEST.equals(ci.getServiceTable()))
.collect(Collectors.toList());
}
if (chargeItemList != null && !chargeItemList.isEmpty()) {
for (ChargeItem ci : chargeItemList) {
if (ChargeItemStatus.BILLED.getValue().equals(ci.getStatusEnum())) {
@@ -875,16 +1077,66 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
}
}
}
// 🔍 Debug日志: 开始删除循环
log.info("BugFix#219: handDevice - 开始删除循环, deleteList.size={}", deleteList.size());
for (AdviceSaveDto adviceSaveDto : deleteList) {
iDeviceRequestService.removeById(adviceSaveDto.getRequestId());
// 删除已经产生的耗材发放信息
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
// 删除费用项
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_DEVICE_REQUEST,
adviceSaveDto.getRequestId());
Long requestId = adviceSaveDto.getRequestId();
log.info("BugFix#219: handDevice - 删除开始: requestId={}", requestId);
// 1. 删除耗材请求
boolean deviceRemoved = iDeviceRequestService.removeById(requestId);
log.info("BugFix#219: handDevice - 删除DeviceRequest: requestId={}, result={}", requestId, deviceRemoved);
// 2. 删除已经产生的耗材发放信息
iDeviceDispenseService.deleteDeviceDispense(requestId);
log.info("BugFix#219: handDevice - 删除DeviceDispense: requestId={}", requestId);
// 3. 删除费用项
String serviceTable = CommonConstants.TableName.WOR_DEVICE_REQUEST;
// 先查询费用项是否存在
try {
List<ChargeItem> existingChargeItems = iChargeItemService.getChargeItemInfoByReqId(Arrays.asList(requestId));
log.info("BugFix#219: handDevice - 查询到费用项数量: requestId={}, count={}", requestId,
existingChargeItems != null ? existingChargeItems.size() : 0);
if (existingChargeItems != null) {
for (ChargeItem ci : existingChargeItems) {
log.info("BugFix#219: handDevice - 费用项详情: id={}, serviceTable={}, serviceId={}, status={}",
ci.getId(), ci.getServiceTable(), ci.getServiceId(), ci.getStatusEnum());
}
}
} catch (Exception e) {
log.error("BugFix#219: handDevice - 查询费用项异常: requestId={}", requestId, e);
}
// 直接删除费用项使用serviceTable和serviceId作为条件
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
log.info("BugFix#219: handDevice - 删除ChargeItem: requestId={}, serviceTable={}", requestId, serviceTable);
log.info("BugFix#219: handDevice - 删除完成: requestId={}", requestId);
}
log.info("BugFix#219: ========== handDevice END ==========");
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
deviceRequest = new DeviceRequest();
deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
@@ -896,31 +1148,32 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
deviceRequest.setCreateBy(currentUsername); // 设置创建人
deviceRequest.setCreateTime(curDate); // 设置创建时间
// 保存时,处理数据(请求,发放,账单)
// 🔧 Bug Fix: 签发时也需要设置关键字段修复BUG #181
// 保存时生成业务编号,签发时沿用已有编号
if (is_save) {
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
deviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间
// 发放耗材房
deviceRequest.setPerformLocation(adviceSaveDto.getLocationId());
deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(adviceSaveDto.getPackageId()); // 组套id
// deviceRequest.setActivityId(adviceSaveDto.getActivityId());
deviceRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
deviceRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
}
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
deviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
deviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
deviceRequest.setLotNumber(adviceSaveDto.getLotNumber());// 产品批号
deviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum()); // 请求类型
deviceRequest.setDeviceDefId(adviceSaveDto.getAdviceDefinitionId());// 耗材定义id
deviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(adviceSaveDto.getFounderOrgId());// 开方人科室
deviceRequest.setReqAuthoredTime(curDate); // 请求开始时间
// 发放耗材房
deviceRequest.setPerformLocation(adviceSaveDto.getLocationId());
deviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(adviceSaveDto.getPackageId()); // 组套id
// deviceRequest.setActivityId(adviceSaveDto.getActivityId());
deviceRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
deviceRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
iDeviceRequestService.saveOrUpdate(deviceRequest);
if (is_save) {
// 处理耗材发放
@@ -929,11 +1182,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 保存耗材费用项
chargeItem = new ChargeItem();
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setTenantId(tenantId); // 补全租户ID
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项 id
chargeItem.setTenantId(tenantId); // 补全租户 ID
chargeItem.setCreateBy(currentUsername); // 补全创建人
chargeItem.setCreateTime(curDate); // 补全创建时间
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
chargeItem.setStatusEnum(ChargeItemStatus.PLANNED.getValue()); // 收费状态
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
@@ -948,6 +1201,47 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表
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如果没有则自动创建
Long accountId = adviceSaveDto.getAccountId();
if (accountId == null) {
@@ -965,6 +1259,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
// 🔧 Bug Fix: 设置账户名称避免数据库NOT NULL约束错误
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
accountId = iAccountService.saveAccountByRegister(newAccount);
}
}
@@ -1018,6 +1314,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
List<Long> delRequestIdList = deleteList.stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
if (!delRequestIdList.isEmpty()) {
List<ChargeItem> chargeItemList = iChargeItemService.getChargeItemInfoByReqId(delRequestIdList);
// 🔧 BugFix#219: 过滤只保留诊疗类型的费用项
if (chargeItemList != null && !chargeItemList.isEmpty()) {
chargeItemList = chargeItemList.stream()
.filter(ci -> CommonConstants.TableName.WOR_SERVICE_REQUEST.equals(ci.getServiceTable()))
.collect(Collectors.toList());
}
if (chargeItemList != null && !chargeItemList.isEmpty()) {
for (ChargeItem ci : chargeItemList) {
if (ChargeItemStatus.BILLED.getValue().equals(ci.getStatusEnum())) {
@@ -1031,12 +1333,44 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iServiceRequestService.remove(
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
adviceSaveDto.getRequestId()));// 删除诊疗套餐对应的子项
// 删除费用项
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_SERVICE_REQUEST,
adviceSaveDto.getRequestId());
// 🔧 Bug Fix #219: 删除费用项
Long requestId = adviceSaveDto.getRequestId();
String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST;
// 直接删除费用项
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
log.info("BugFix#219: 诊疗医嘱删除完成, requestId={}, serviceTable={}", requestId, serviceTable);
}
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
// 🔧 Bug Fix #238: 诊疗项目执行科室非空校验
if (adviceSaveDto.getAdviceType() != null && adviceSaveDto.getAdviceType() == 3) {
Long effectiveOrgId = adviceSaveDto.getEffectiveOrgId();
if (effectiveOrgId == null) {
throw new ServiceException("诊疗项目必须选择执行科室");
}
}
serviceRequest = new ServiceRequest();
serviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
serviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
@@ -1047,42 +1381,42 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
if (is_sign) {
serviceRequest.setSignCode(signCode);
}
// 只有保存时才处理的字段属性
// 🔧 Bug Fix: 签发时也需要设置关键字段修复BUG #181
// 保存时生成业务编号,签发时沿用已有编号
if (is_save) {
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
}
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
serviceRequest.setQuantity(adviceSaveDto.getQuantity()); // 请求数量
serviceRequest.setUnitCode(adviceSaveDto.getUnitCode()); // 请求单位编码
// 🎯 判断是否为会诊医嘱如果categoryEnum为31则为会诊类型
Integer categoryEnum = adviceSaveDto.getCategoryEnum();
if (categoryEnum != null && categoryEnum == 31) {
// 会诊医嘱category_enum设置为31
serviceRequest.setCategoryEnum(31);
log.info("保存会诊医嘱category_enum=31");
} else {
// 普通诊疗医嘱
serviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum());
}
serviceRequest.setActivityId(adviceSaveDto.getAdviceDefinitionId());// 诊疗定义id
serviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
serviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
serviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
// 执行科室
serviceRequest.setOrgId(adviceSaveDto.getPositionId());
serviceRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
serviceRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
serviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
serviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
// 基于皮试药品生成的皮试检查
if (adviceSaveDto.getBasedOnId() != null) {
serviceRequest.setBasedOnId(adviceSaveDto.getBasedOnId());
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
}
// 🎯 判断是否为会诊医嘱如果categoryEnum为31则为会诊类型
Integer categoryEnum = adviceSaveDto.getCategoryEnum();
if (categoryEnum != null && categoryEnum == 31) {
// 会诊医嘱category_enum设置为31
serviceRequest.setCategoryEnum(31);
log.info("保存会诊医嘱category_enum=31");
} else {
// 普通诊疗医嘱
serviceRequest.setCategoryEnum(adviceSaveDto.getCategoryEnum());
}
serviceRequest.setActivityId(adviceSaveDto.getAdviceDefinitionId());// 诊疗定义id
serviceRequest.setPatientId(adviceSaveDto.getPatientId()); // 患者
serviceRequest.setRequesterId(adviceSaveDto.getPractitionerId()); // 开方医生
serviceRequest.setEncounterId(adviceSaveDto.getEncounterId()); // 就诊id
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
// 执行科室 - 使用兼容方法获取执行科室ID
serviceRequest.setOrgId(adviceSaveDto.getEffectiveOrgId());
serviceRequest.setContentJson(adviceSaveDto.getContentJson()); // 请求内容json
serviceRequest.setYbClassEnum(adviceSaveDto.getYbClassEnum());// 类别医保编码
serviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
serviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
// 基于皮试药品生成的皮试检查
if (adviceSaveDto.getBasedOnId() != null) {
serviceRequest.setBasedOnId(adviceSaveDto.getBasedOnId());
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
}
iServiceRequestService.saveOrUpdate(serviceRequest);
@@ -1141,26 +1475,26 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 只有在签发时
if (is_sign) {
// 发送跨系统申请
adviceSaveDto.setRequestId(serviceRequest.getId());
try {
// 查询诊疗定义
ActivityDefinition activityDefinition
= iActivityDefinitionService.getById(adviceSaveDto.getAdviceDefinitionId());
if (activityDefinition != null) {
// 检验 或 检查
if (ActivityType.PROOF.getValue().equals(activityDefinition.getTypeEnum())
|| ActivityType.TEST.getValue().equals(activityDefinition.getTypeEnum())) {
doctorStationSendApplyUtil.sendCrossSystemApply(adviceSaveDto, organizationId, curDate);
}
}
} catch (Exception e) {
if (!Whether.YES.getCode()
.equals(TenantOptionUtil.getOptionContent(TenantOptionDict.LIS_PACS_ERROR_IGNORE))) {
throw e;
}
log.error(e.getMessage(), e);
}
// 发送跨系统申请 - 已注释项目未使用LIS/PACS系统
// adviceSaveDto.setRequestId(serviceRequest.getId());
// try {
// // 查询诊疗定义
// ActivityDefinition activityDefinition
// = iActivityDefinitionService.getById(adviceSaveDto.getAdviceDefinitionId());
// if (activityDefinition != null) {
// // 检验 或 检查
// if (ActivityType.PROOF.getValue().equals(activityDefinition.getTypeEnum())
// || ActivityType.TEST.getValue().equals(activityDefinition.getTypeEnum())) {
// doctorStationSendApplyUtil.sendCrossSystemApply(adviceSaveDto, organizationId, curDate);
// }
// }
// } catch (Exception e) {
// if (!Whether.YES.getCode()
// .equals(TenantOptionUtil.getOptionContent(TenantOptionDict.LIS_PACS_ERROR_IGNORE))) {
// throw e;
// }
// log.error(e.getMessage(), e);
// }
}
}
}
@@ -1205,48 +1539,83 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
*/
@Override
public R<?> signOffAdvice(List<Long> requestIdList) {
// 根据请求编号列表查询收费项目信息
log.info("BugFix#219: signOffAdvice - requestIdList={}", requestIdList);
// 🔧 BugFix: 直接对所有requestId进行作废操作不再先查询分类
// 药品、耗材、诊疗请求都尝试作废,只有存在的才会被更新
// 根据请求编号列表查询收费项目信息(用于检查是否已收费)
List<ChargeItem> chargeItemList = iChargeItemService.getChargeItemInfoByReqId(requestIdList);
if (chargeItemList != null && !chargeItemList.isEmpty()) {
for (ChargeItem chargeItem : chargeItemList) {
if (ChargeItemStatus.BILLED.getValue().equals(chargeItem.getStatusEnum())) {
throw new ServiceException("已收费的项目无法签退,请刷新页面后重试");
}
}
// 分别获取各个请求id列表
List<Long> medReqIdList = new ArrayList<>();
List<Long> devReqIdList = new ArrayList<>();
List<Long> serReqIdList = new ArrayList<>();
chargeItemList.forEach(item -> {
switch (item.getServiceTable()) {
case CommonConstants.TableName.MED_MEDICATION_REQUEST ->
medReqIdList.add(item.getServiceId());
case CommonConstants.TableName.WOR_DEVICE_REQUEST ->
devReqIdList.add(item.getServiceId());
case CommonConstants.TableName.WOR_SERVICE_REQUEST ->
serReqIdList.add(item.getServiceId());
}
});
List<Long> chargeItemIdList = chargeItemList.stream().map(ChargeItem::getId).collect(Collectors.toList());
// 根据id更新收费项目状态
iChargeItemService.updatePaymentStatus(chargeItemIdList, ChargeItemStatus.DRAFT.getValue());// 撤回后需要更新为草稿
if (!medReqIdList.isEmpty()) {
// 根据请求id更新请求状态
iMedicationRequestService.updateDraftStatusBatch(medReqIdList, null, null);
}
if (!devReqIdList.isEmpty()) {
// 根据请求id更新请求状态
iDeviceRequestService.updateDraftStatusBatch(devReqIdList);
}
if (!serReqIdList.isEmpty()) {
// 根据请求id更新请求状态
iServiceRequestService.updateDraftStatusBatch(serReqIdList);
}
} else {
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, null));
iChargeItemService.updatePaymentStatus(chargeItemIdList, ChargeItemStatus.DRAFT.getValue());
}
// 🔧 新增:签退时回滚库存
// 查询已发放的药品记录(用于回滚库存)
List<MedicationDispense> dispensedList = iMedicationDispenseService.list(
new LambdaQueryWrapper<MedicationDispense>()
.in(MedicationDispense::getMedReqId, requestIdList)
.eq(MedicationDispense::getStatusEnum, DispenseStatus.COMPLETED.getValue())
);
if (dispensedList != null && !dispensedList.isEmpty()) {
// 需要回滚的库存列表
List<InventoryItem> inventoryUpdateList = new ArrayList<>();
for (MedicationDispense dispense : dispensedList) {
// 查询对应的库存记录根据批号和药品ID
if (dispense.getMedicationId() != null && dispense.getLotNumber() != null) {
InventoryItem inventoryItem = inventoryItemService.getOne(
new LambdaQueryWrapper<InventoryItem>()
.eq(InventoryItem::getItemId, dispense.getMedicationId())
.eq(InventoryItem::getLotNumber, dispense.getLotNumber())
);
if (inventoryItem != null) {
// 计算回滚后的数量(加上已发放的数量)
BigDecimal currentQuantity = inventoryItem.getQuantity() != null ? inventoryItem.getQuantity() : BigDecimal.ZERO;
BigDecimal dispenseQuantity = dispense.getQuantity() != null ? dispense.getQuantity() : BigDecimal.ZERO;
inventoryUpdateList.add(new InventoryItem()
.setId(inventoryItem.getId())
.setQuantity(currentQuantity.add(dispenseQuantity))
);
}
}
// 更新发药记录状态为已退药
dispense.setStatusEnum(DispenseStatus.RETURNED.getValue());
}
// 批量更新库存(回滚数量)
if (!inventoryUpdateList.isEmpty()) {
inventoryItemService.updateBatchById(inventoryUpdateList);
}
// 更新发药记录状态
iMedicationDispenseService.updateBatchById(dispensedList);
}
// 🔧 BugFix: 直接对所有requestId进行签退操作将状态改为待签发
log.info("BugFix: signOffAdvice - 签退所有请求,状态改为待签发, requestIdList={}", requestIdList);
// 尝试签退药品请求(只有存在的才会更新)
iMedicationRequestService.updateDraftStatusBatch(requestIdList, null, null);
// 尝试签退耗材请求(只有存在的才会更新)
iDeviceRequestService.updateDraftStatusBatch(requestIdList);
// 尝试签退诊疗请求(只有存在的才会更新)
iServiceRequestService.updateDraftStatusBatch(requestIdList);
log.info("BugFix#219: signOffAdvice - 所有请求作废完成");
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null));
}

View File

@@ -10,8 +10,10 @@ import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils;
import com.openhis.administration.domain.Account;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.domain.EncounterDiagnosis;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.administration.service.IEncounterDiagnosisService;
import com.openhis.clinical.domain.Condition;
@@ -80,6 +82,9 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
@Resource
AdviceUtils adviceUtils;
@Resource
IAccountService iAccountService;
/**
* 查询中医诊断数据
*
@@ -364,7 +369,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
adviceBaseDto.setAdviceType(1); // 医嘱类型为药品
adviceBaseDto.setCategoryCode(MedCategoryCode.CHINESE_HERBAL_MEDICINE.getValue());// 中草药
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, pricingFlag, List.of(1, 2, 3), null);
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, pricingFlag, List.of(1, 2, 3), null, null);
}
/**
@@ -475,6 +480,28 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 医嘱签发编码
String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10);
for (AdviceSaveDto adviceSaveDto : insertOrUpdateList) {
// 🔧 Bug Fix: 确保accountId不为null
if (adviceSaveDto.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
adviceSaveDto.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
adviceSaveDto.setAccountId(newAccountId);
}
}
// 中药付数
BigDecimal chineseHerbsDoseQuantity = adviceSaveDto.getChineseHerbsDoseQuantity();
medicationRequest = new MedicationRequest();
@@ -586,7 +613,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 对应的诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null,
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords().get(0);
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords().get(0);
if (activityAdviceBaseDto != null) {
// 费用定价
AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0);

View File

@@ -36,10 +36,11 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.Date;
import java.util.*;
import static com.openhis.common.constant.CommonConstants.FieldName.DeleteFlag;
import static com.openhis.common.enums.ReportCardStatus.SUBMITTED;
import static java.time.LocalDateTime.now;
/**
@@ -272,12 +273,28 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
*/
@Override
public R<?> saveDoctorDiagnosisNew(SaveDiagnosisParam saveDiagnosisParam) {
// 参数校验:确保诊断列表不为空
if (saveDiagnosisParam == null) {
return R.fail(MessageUtils.message(PromptMsgConstant.Common.M00009, new Object[] { "保存诊断参数" }));
}
// 患者id
Long patientId = saveDiagnosisParam.getPatientId();
// 就诊ID
Long encounterId = saveDiagnosisParam.getEncounterId();
// 诊断定义集合
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);
@@ -603,15 +620,16 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
* 报告单位,报告医生,报告日期
*/
infectiousDiseaseReport.setReportOrg(null);
infectiousDiseaseReport.setReportDate(LocalDate.now());
infectiousDiseaseReport.setReportDate(new Date());
// 如果状态为空,设置为草稿状态
if (infectiousDiseaseReport.getStatus() == null) {
infectiousDiseaseReport.setStatus(0); // 0-草稿
infectiousDiseaseReport.setStatus(SUBMITTED.getValue()); //已提交。草稿状态
}
log.debug("保存传染病报告卡数据getReportOrg{}", infectiousDiseaseReport.getReportOrg());
log.debug("保存传染病报告卡数据:{}", infectiousDiseaseReport.getDeleteFlag());
log.debug("保存传染病报告卡数据,更新日期:{}",infectiousDiseaseReport.getUpdateTime());
log.debug("保存传染病报告卡数据getStatus:{}",infectiousDiseaseReport.getStatus());
// 保存到数据库
boolean success = iInfectiousDiseaseReportService.save(infectiousDiseaseReport);

View File

@@ -1,6 +1,7 @@
package com.openhis.web.doctorstation.appservice.impl;
import com.core.common.core.domain.R;
import com.core.common.core.redis.RedisCache;
import com.core.common.enums.DelFlag;
import com.core.common.utils.SecurityUtils;
import com.openhis.common.enums.DbOpType;
@@ -35,6 +36,8 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -76,6 +79,9 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
@Autowired
private IServiceRequestService serviceRequestService;
@Autowired
private RedisCache redisCache;
/**
* 保存检验申请单信息
* @param doctorStationLabApplyDto
@@ -88,8 +94,39 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
* 保存检验申请单信息逻辑
* 保存检验申请单信息同时根据检验申请单检验项目数据保存检验申请单明细信息
*/
log.debug("保存检验申请单信息:{}", doctorStationLabApplyDto);
log.debug("保存申请单明细信息:{}",doctorStationLabApplyDto.getLabApplyItemList());
// 申请单号为空或"待生成"时,由后端生成新单号
String applyNo = doctorStationLabApplyDto.getApplyNo();
boolean isNewApplyNo = false;
if (applyNo == null || applyNo.trim().isEmpty() || "待生成".equals(applyNo) || "自动生成".equals(applyNo)) {
applyNo = generateApplyNo();
isNewApplyNo = true;
}
// 将生成的单号设置回 DTO
doctorStationLabApplyDto.setApplyNo(applyNo);
try {
// 执行保存逻辑
doSaveInspectionLabApply(doctorStationLabApplyDto, applyNo);
} catch (Exception e) {
// 记录废号日志(申请单号已生成但保存失败)
if (isNewApplyNo) {
log.error("申请单号 {} 因保存失败成为废号,原因:{}", applyNo, e.getMessage());
}
throw e; // 重新抛出异常,让事务回滚
}
// 返回生成的申请单号
Map<String, Object> result = new HashMap<>();
result.put("applyNo", applyNo);
return R.ok(result);
}
/**
* 执行保存检验申请单的实际逻辑
*/
private void doSaveInspectionLabApply(DoctorStationLabApplyDto doctorStationLabApplyDto, String applyNo) {
//获取当前登陆用户 ID
String userId = String.valueOf(SecurityUtils.getLoginUser().getUserId());
InspectionLabApply inspectionLabApply = new InspectionLabApply();
@@ -102,22 +139,34 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
inspectionLabApply.setOperatorId(userId);
inspectionLabApply.setCreateTime(new Date());
inspectionLabApply.setDeleteFlag(DelFlag.NO.getCode());
// 申请日期使用服务器当前系统时间
inspectionLabApply.setApplyTime(new Date());
log.debug("保存检验申请单信息:{}", inspectionLabApply);
inspectionLabApplyService.saveOrUpdate(inspectionLabApply);
// 金额校验和重算:后端重新计算金额,防止前端篡改
java.math.BigDecimal totalAmount = java.math.BigDecimal.ZERO;
int index = 0;
//遍历 doctorStationLabApplyDto.getLabApplyItemList()
int index = 0;
for (DoctorStationLabApplyItemDto doctorStationLabApplyItemDto : doctorStationLabApplyDto.getLabApplyItemList()) {
//将 dto 数据复制到 InspectionLabApplyItem 对象中
InspectionLabApplyItem inspectionLabApplyItem = new InspectionLabApplyItem();
BeanUtils.copyProperties(doctorStationLabApplyItemDto, inspectionLabApplyItem);
// 后端重新计算金额:金额 = 单价 × 数量
java.math.BigDecimal itemPrice = doctorStationLabApplyItemDto.getItemPrice();
java.math.BigDecimal itemQty = doctorStationLabApplyItemDto.getItemQty();
if (itemPrice != null && itemQty != null) {
java.math.BigDecimal calculatedAmount = itemPrice.multiply(itemQty).setScale(2, java.math.RoundingMode.HALF_UP);
inspectionLabApplyItem.setItemAmount(calculatedAmount);
totalAmount = totalAmount.add(calculatedAmount);
}
//设置从表申请单明细的申请单号
inspectionLabApplyItem.setApplyNo(doctorStationLabApplyDto.getApplyNo());
//检验科代码,取值于检验申请单
inspectionLabApplyItem.setPerformDeptCode(doctorStationLabApplyDto.getApplyDeptCode());
//执行科室代码,取值于检验申请单明细(前端传递的字典值)
inspectionLabApplyItem.setPerformDeptCode(doctorStationLabApplyItemDto.getPerformDeptCode());
//同主表状态,可单独回写
inspectionLabApplyItem.setItemStatus(doctorStationLabApplyDto.getApplyStatus());
// 设置项目序号 (打印顺序),按照遍历序号进行排序
@@ -125,7 +174,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
index++;
inspectionLabApplyItem.setDeleteFlag(DelFlag.NO.getCode());
log.debug("保存申请单明细信息:{}", inspectionLabApplyItem);
inspectionLabApplyItemService.saveOrUpdate(inspectionLabApplyItem);
//创建条码对象
@@ -141,8 +189,6 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
barCode.setCreateTime(new Date());
barCode.setDeleteFlag(DelFlag.NO.getCode());
log.debug("插入条码数据前barCode:{}",barCode);
inspectionLabBarCodeService.saveOrUpdate(barCode);
}
@@ -189,11 +235,14 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
);
if (organization != null) {
positionId = organization.getId();
} else {
log.warn("未找到执行科室代码对应的科室:{}", performDeptCode);
}
}
// 如果没有指定执行科室,使用当前医生所在的科室作为默认执行科室
if (positionId == null) {
positionId = SecurityUtils.getDeptId();
}
// 4. 创建医嘱保存对象
AdviceSaveDto adviceSaveDto = new AdviceSaveDto();
// 设置医嘱操作类型
@@ -270,23 +319,45 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
adviceSaveParam.setAdviceSaveList(adviceSaveList);
// 调用门诊医嘱保存接口,创建关联的医嘱记录
try {
iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, "1"); // "1"表示保存操作
} catch (Exception e) {
throw new RuntimeException("创建关联医嘱记录失败", e);
}
return R.ok();
iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, "1"); // "1"表示保存操作
}
/**
* 根据申请单号查询检验申请单
* 根据申请单号查询检验申请单(包含检验项目明细)
*
* @param applyNo
* @return
*/
@Override
public Object getInspectionApplyByApplyNo(String applyNo) {
return doctorStationLabApplyMapper.getInspectionApplyByApplyNo(applyNo);
// 查询主表数据
DoctorStationLabApplyDto applyDto = (DoctorStationLabApplyDto) doctorStationLabApplyMapper.getInspectionApplyByApplyNo(applyNo);
if (applyDto == null) {
return null;
}
// 查询检验项目明细
List<InspectionLabApplyItem> itemList = inspectionLabApplyItemService.list(
new QueryWrapper<InspectionLabApplyItem>()
.eq("apply_no", applyNo)
.eq("delete_flag", "0")
.orderByAsc("item_seq")
);
// 转换为 DTO 列表
List<DoctorStationLabApplyItemDto> itemDtoList = new ArrayList<>();
if (itemList != null && !itemList.isEmpty()) {
for (InspectionLabApplyItem item : itemList) {
DoctorStationLabApplyItemDto itemDto = new DoctorStationLabApplyItemDto();
BeanUtils.copyProperties(item, itemDto);
itemDtoList.add(itemDto);
}
// 从第一个明细项获取执行科室代码
applyDto.setExecuteDepartment(itemList.get(0).getPerformDeptCode());
}
applyDto.setLabApplyItemList(itemDtoList);
return applyDto;
}
/**
@@ -303,11 +374,11 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 查询检验申请单列表
log.debug("查询申请单数据前");
List<InspectionLabApply> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId);
List<DoctorStationLabApplyDto> list = doctorStationLabApplyMapper.getInspectionApplyListPage(encounterId);
log.debug("查询申请单数据后");
// 使用 PageInfo 包装查询结果
PageInfo<InspectionLabApply> pageInfo = new PageInfo<>(list);
PageInfo<DoctorStationLabApplyDto> pageInfo = new PageInfo<>(list);
// 构建返回结果
Map<String, Object> result = new HashMap<>();
@@ -502,7 +573,7 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
);
if (deleteResult) {
log.debug("成功删除申请单号 [{}] 的条码数据,更新人:{},更新时间:{}",
log.debug("成功删除申请单号 [{}] 的条码数据,更新人:{},更新时间:{}",
applyNo, currentUsername, currentTime);
} else {
log.warn("删除申请单号 [{}] 的条码数据失败", applyNo);
@@ -514,4 +585,36 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
}
}
/**
* 生成检验申请单号
* 规则LS + YYYYMMDD + 5位流水号每日从1开始递增
* 支持并发安全:使用 Redis 原子递增保证唯一性
* @return 申请单号
*/
private String generateApplyNo() {
// 获取当前日期
LocalDate today = LocalDate.now();
String dateStr = today.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
// 生成前缀LS + 日期
String prefix = "LS" + dateStr;
// Redis key 用于存储当天的流水号
String redisKey = "lab_apply_no:" + dateStr;
// 使用 Redis 原子递增获取流水号(并发安全)
long sequence = redisCache.incr(redisKey, 1);
// 设置 Redis key 过期时间(每天的 key 按日期独立隔天不再使用25小时确保跨午夜场景安全
redisCache.expire(redisKey, 25 * 60 * 60);
// 格式化流水号为5位不足前补0
String sequenceStr = String.format("%05d", sequence);
// 生成完整的申请单号
String applyNo = prefix + sequenceStr;
return applyNo;
}
}

View File

@@ -1,6 +1,7 @@
package com.openhis.web.doctorstation.appservice.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -16,21 +17,23 @@ import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
import com.openhis.web.doctorstation.appservice.*;
import com.openhis.web.doctorstation.dto.PatientInfoDto;
import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto;
import com.openhis.web.doctorstation.dto.PrescriptionInfoDetailDto;
import com.openhis.web.doctorstation.dto.ReceptionStatisticsDto;
import com.openhis.web.doctorstation.mapper.DoctorStationMainAppMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
@@ -64,6 +67,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private TriageQueueItemService triageQueueItemService;
/**
* 查询就诊患者信息
*
@@ -124,14 +130,40 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> receiveEncounter(Long encounterId) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
String currentUsername = SecurityUtils.getUsername();
// 检查就诊记录是否存在
Encounter encounter = encounterMapper.selectById(encounterId);
if (encounter == null) {
return R.fail("就诊记录不存在");
}
// 检查患者状态,防止重复接诊
Integer currentStatus = encounter.getStatusEnum();
if (EncounterStatus.IN_PROGRESS.getValue().equals(currentStatus)) {
return R.fail("已接诊,请勿重复点击,已为您刷新");
}
int update = encounterMapper.update(null,
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
.eq(Encounter::getStatusEnum, EncounterStatus.PLANNED.getValue()) // 只更新待诊状态的患者
.set(Encounter::getReceptionTime, new Date())
.set(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.RECEIVING_CARE.getValue()));
// 如果更新失败,说明状态已被其他医生修改
if (update <= 0) {
// 重新查询当前状态
encounter = encounterMapper.selectById(encounterId);
if (EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
return R.fail("已接诊,请勿重复接诊");
}
return R.fail("接诊失败,请刷新后重试");
}
// 先把之前的接诊记录更新为已完成
iEncounterParticipantService.update(new LambdaUpdateWrapper<EncounterParticipant>()
.eq(EncounterParticipant::getTypeCode, ParticipantType.ADMITTER.getCode())
@@ -148,7 +180,28 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
encounterParticipant.setCreateBy(currentUsername);
encounterParticipant.setCreateTime(new Date());
iEncounterParticipantService.save(encounterParticipant);
return update > 0 ? R.ok() : R.fail();
// 更新 triage_queue_item 队列记录状态为 CALLING
try {
TriageQueueItem queueItem = triageQueueItemService.getOne(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getEncounterId, encounterId)
.eq(TriageQueueItem::getDeleteFlag, "0")
);
if (queueItem != null) {
queueItem.setStatus("CALLING");
queueItem.setUpdateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
triageQueueItemService.updateById(queueItem);
log.info("接诊时更新队列状态为CALLINGencounterId={}, queueItemId={}", encounterId, queueItem.getId());
} else {
log.warn("接诊时未找到队列记录encounterId={}", encounterId);
}
} catch (Exception e) {
log.error("接诊时更新队列状态失败encounterId={}", encounterId, e);
}
return R.ok();
}
/**
@@ -181,11 +234,54 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
return R.fail("就诊记录不存在");
}
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
return R.fail("当前患者不在就诊中状态");
// 检查患者状态,防止重复完诊
Integer currentStatus = encounter.getStatusEnum();
if (EncounterStatus.DISCHARGED.getValue().equals(currentStatus) ||
EncounterStatus.COMPLETED.getValue().equals(currentStatus)) {
// 患者已完成就诊,返回特定提示
return R.fail("患者已完成就诊,已为您自动刷新患者列表");
}
// 2. 更新状态、完成时间以及初复诊标识
if (!EncounterStatus.IN_PROGRESS.getValue().equals(currentStatus)) {
return R.fail("非就诊中患者不能完诊");
}
// 2. 查找队列项
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
TriageQueueItem queueItem = triageQueueItemService.getOne(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getEncounterId, encounterId)
.eq(TriageQueueItem::getDeleteFlag, "0")
);
// 如果队列项存在,检查状态并更新
if (queueItem != null && "CALLING".equals(queueItem.getStatus())) {
// 更新队列状态为已完成
java.time.LocalDateTime nowLocal = java.time.LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
queueItem.setStatus("COMPLETED");
queueItem.setUpdateTime(nowLocal);
triageQueueItemService.updateById(queueItem);
// 写入 div_log 审计日志
try {
Long userId = SecurityUtils.getLoginUser().getUserId();
String divLogSql = "INSERT INTO hisdev.div_log "
+ "(pool_id, slot_id, queue_no, op_user_id, action, create_time) "
+ "VALUES (?, ?, ?, ?, 'COMPLETE', NOW()::timestamp(0))";
jdbcTemplate.update(divLogSql,
queueItem.getOrganizationId(), // pool_id: 候选池ID科室
queueItem.getPractitionerId(), // slot_id: 槽位ID医生
queueItem.getQueueOrder(), // queue_no: 队列号
userId); // op_user_id: 操作用户ID
} catch (Exception e) {
log.error("写入div_log审计日志失败", e);
// 审计日志失败不影响主流程
}
}
// 3. 更新状态、完成时间以及初复诊标识
Date now = new Date();
int update = encounterMapper.update(null,
new LambdaUpdateWrapper<Encounter>()
@@ -198,7 +294,7 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
if (update <= 0) return R.fail("完诊失败");
// 3. 审计日志
// 4. 审计日志sys_oper_log
try {
String username = SecurityUtils.getUsernameSafe();
String sql = "INSERT INTO sys_oper_log "

View File

@@ -3,6 +3,7 @@
*/
package com.openhis.web.doctorstation.controller;
import com.core.common.annotation.RepeatSubmit;
import com.core.common.core.domain.R;
import com.openhis.common.enums.AdviceOpType;
import com.openhis.common.enums.Whether;
@@ -15,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* 医生站-医嘱/处方 controller
@@ -50,7 +52,7 @@ public class DoctorStationAdviceController {
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return R.ok(iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue(), adviceTypes, null));
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue(), adviceTypes, null, null));
}
/**
@@ -84,6 +86,7 @@ public class DoctorStationAdviceController {
* @return 结果
*/
@PostMapping(value = "/sign-advice")
@RepeatSubmit(interval = 5000, message = "请勿重复签发医嘱,请稍候再试")
public R<?> signAdvice(@RequestBody AdviceSaveParam adviceSaveParam) {
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, AdviceOpType.SIGN_ADVICE.getCode());
}
@@ -91,12 +94,16 @@ public class DoctorStationAdviceController {
/**
* 门诊签退医嘱
*
* @param requestIdList 请求id列表
* @param requestIdList 请求id列表(字符串类型,避免前端大整数精度丢失)
* @return 结果
*/
@PostMapping(value = "/sign-off")
public R<?> signOffAdvice(@RequestBody List<Long> requestIdList) {
return iDoctorStationAdviceAppService.signOffAdvice(requestIdList);
public R<?> signOffAdvice(@RequestBody List<String> requestIdList) {
// 🔧 BugFix: 将字符串转换为Long
List<Long> ids = requestIdList.stream()
.map(Long::parseLong)
.collect(Collectors.toList());
return iDoctorStationAdviceAppService.signOffAdvice(ids);
}
/**

View File

@@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import org.springframework.validation.annotation.Validated;
import java.util.HashMap;
import java.util.Map;
/**
* 门诊医生站-检验控制器

View File

@@ -50,10 +50,32 @@ public class AdviceSaveDto {
/**
* 物理位置id | 可能是 发药药房id,耗材房id,执行科室id
* 前端字段名orgId诊疗项目
* 对应数据库org_id / perform_location
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long positionId;
/**
* 执行科室ID | 诊疗项目使用
* 前端传来字段名orgId
* 对应数据库org_id
* 🔧 Bug Fix #238: 添加此字段以支持前端orgId传参
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long orgId;
/**
* 获取有效的执行科室ID兼容处理
* 优先使用orgId如果不存在则使用positionId
* 🔧 Bug Fix #238: 统一获取执行科室ID的方法
*
* @return 有效的科室ID如果都为null则返回null
*/
public Long getEffectiveOrgId() {
return orgId != null ? orgId : positionId;
}
/** 药品性质 | 分方使用 */
private String pharmacologyCategoryCode;

View File

@@ -17,6 +17,10 @@ import java.util.List;
@Data
@Accessors(chain = true)
public class DoctorStationLabApplyDto {
/**
* 申请单ID数据库自增主键
*/
private Long applicationId;
/**
* 申请单编号
*/
@@ -135,6 +139,10 @@ public class DoctorStationLabApplyDto {
* 就诊id
*/
private Long encounterId;
/**
* 执行科室代码(从明细项获取)
*/
private String executeDepartment;
/**
* 检验项目数据列表
*/

View File

@@ -79,6 +79,11 @@ public class RequestBaseDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long chargeItemId;
/**
* 医嘱定义对应表名
*/
private String adviceTableName;
/**
* 医嘱名称
*/
@@ -156,6 +161,11 @@ public class RequestBaseDto {
private String doseUnitCode;
private String doseUnitCode_dictText;
/**
* 单价
*/
private BigDecimal unitPrice;
/**
* 总价
*/
@@ -210,4 +220,16 @@ public class RequestBaseDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long basedOnId;
/**
* 就诊id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
/**
* 患者id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
}

View File

@@ -2,6 +2,7 @@ package com.openhis.web.doctorstation.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -73,12 +74,16 @@ public class SaveDiagnosisChildParam {
/**
* 诊断时间
* 添加 pattern 以支持前端传来的 "yyyy/M/d HH:mm:ss" 格式
*/
@JsonFormat(pattern = "yyyy/M/d HH:mm:ss", timezone = "GMT+8")
private Date diagnosisTime;
/**
* 发病时间
* 同样添加 pattern 以防前端传来相同格式的发病时间
*/
@JsonFormat(pattern = "yyyy/M/d HH:mm:ss", timezone = "GMT+8")
private Date onsetDate;
/** 患者疾病诊断类型代码 */

View File

@@ -38,6 +38,7 @@ public interface DoctorStationAdviceAppMapper {
@Param("adviceDefinitionIdParamList") List<Long> adviceDefinitionIdParamList,
@Param("adviceTypes") List<Integer> adviceTypes,
@Param("searchKey") String searchKey,
@Param("categoryCode") String categoryCode,
@Param(Constants.WRAPPER) QueryWrapper<AdviceBaseDto> queryWrapper);
/**

View File

@@ -1,6 +1,6 @@
package com.openhis.web.doctorstation.mapper;
import com.openhis.lab.domain.InspectionLabApply;
import com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@@ -11,6 +11,11 @@ import java.util.List;
*/
@Repository
public interface DoctorStationLabApplyMapper {
/**
* 根据申请单号查询检验申请单
* @param applyNo 申请单号
* @return 检验申请单DTO
*/
Object getInspectionApplyByApplyNo(String applyNo);
/**
@@ -18,5 +23,5 @@ public interface DoctorStationLabApplyMapper {
* @param encounterId 就诊 ID
* @return 检验申请单列表
*/
List<InspectionLabApply> getInspectionApplyListPage(@Param("encounterId") Long encounterId);
List<DoctorStationLabApplyDto> getInspectionApplyListPage(@Param("encounterId") Long encounterId);
}

View File

@@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.domain.Account;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*;
@@ -71,6 +73,9 @@ public class AdviceUtils {
@Resource
IDoctorStationAdviceAppService iDoctorStationAdviceAppService;
@Resource
IAccountService iAccountService;
/**
* 校验库存
*
@@ -110,6 +115,15 @@ public class AdviceUtils {
matched = true;
// 检查库存是否充足
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(); // 中药付数
// 中草药医嘱的情况
if (chineseHerbsDoseQuantity != null && chineseHerbsDoseQuantity.compareTo(BigDecimal.ZERO) > 0) {
@@ -305,6 +319,28 @@ public class AdviceUtils {
*/
public void handleActivityChild(String childrenJson, Long organizationId,
ActivityChildrenJsonParams activityChildrenJsonParams) {
// 🔧 Bug Fix: 确保accountId不为null
if (activityChildrenJsonParams.getAccountId() == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(activityChildrenJsonParams.getEncounterId());
if (selfAccount != null) {
activityChildrenJsonParams.setAccountId(selfAccount.getId());
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(activityChildrenJsonParams.getPatientId());
newAccount.setEncounterId(activityChildrenJsonParams.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
newAccount.setName(AccountType.PERSONAL_CASH_ACCOUNT.getInfo());
Long newAccountId = iAccountService.saveAccountByRegister(newAccount);
activityChildrenJsonParams.setAccountId(newAccountId);
}
}
// 治疗类型 (长期/临时)
Integer therapyEnum = activityChildrenJsonParams.getTherapyEnum();
// 当前登录账号的科室id
@@ -337,7 +373,7 @@ public class AdviceUtils {
// 对应的子项诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto
= iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null, null, organizationId, 1,
1, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords().get(0);
1, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords().get(0);
if (activityAdviceBaseDto != null) {
// 费用定价
AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0);

View File

@@ -32,9 +32,11 @@ public class PrescriptionUtils {
if (medicineList == null || medicineList.isEmpty()) {
return;
}
// 1. 按诊断ID分组不同诊断必须分开
// 1. 按诊断ID分组不同诊断必须分开null值归为一组
Map<Long, List<AdviceSaveDto>> diagnosisGroups =
medicineList.stream().collect(Collectors.groupingBy(AdviceSaveDto::getConditionDefinitionId));
medicineList.stream().collect(Collectors.groupingBy(dto ->
dto.getConditionDefinitionId() != null ? dto.getConditionDefinitionId() : 0L
));
// 2. 处理每个诊断组
diagnosisGroups.values().forEach(this::processDiagnosisGroup);
}
@@ -46,9 +48,11 @@ public class PrescriptionUtils {
if (diagnosisGroup.isEmpty()) {
return;
}
// 1. 按药品性质分组
// 1. 按药品性质分组null值归为普通药品
Map<String, List<AdviceSaveDto>> pharmacologyGroups =
diagnosisGroup.stream().collect(Collectors.groupingBy(AdviceSaveDto::getPharmacologyCategoryCode));
diagnosisGroup.stream().collect(Collectors.groupingBy(dto ->
dto.getPharmacologyCategoryCode() != null ? dto.getPharmacologyCategoryCode() : "0"
));
// 2. 处理每个药品性质组
pharmacologyGroups.values().forEach(pharmaGroup -> {
// 2.1 先处理有分组ID的药品确保它们不会被拆分

View File

@@ -59,7 +59,7 @@ public class DocTemplateAppServiceImpl implements IDocTemplateAppService {
docTemplateDto.setUserId(SecurityUtils.getLoginUser().getUserId());
}
docTemplate.setOrganizationId(docTemplateDto.getOrganizationId());
docTemplate.setId(docTemplateDto.getUserId());
docTemplate.setUserId(docTemplateDto.getUserId());
docTemplate.setUseRange(docTemplateDto.getUseRange());
docTemplate.setRemark(docTemplateDto.getRemark());
docTemplateService.save(docTemplate);
@@ -140,7 +140,7 @@ public class DocTemplateAppServiceImpl implements IDocTemplateAppService {
docTemplate.setOrganizationId(docTemplateDto.getOrganizationId());
docTemplate.setUserId(docTemplateDto.getUserId());
docTemplate.setRemark(docTemplateDto.getRemark());
docTemplateService.save(docTemplate);
docTemplateService.updateById(docTemplate);
return R.ok("更新成功");
}

View File

@@ -780,19 +780,32 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
map.put(e.getStatisticDefinitionCode(), e.getValue());
}
});
// 入院体征赋值
// 入院体征赋值 - 带fallback处理兼容heartRate/hertRate等历史数据
dto.setHeight(map.get(TemperatureChartEnum.HEIGHT.getTypeCode()));
dto.setWeight(map.get(TemperatureChartEnum.WEIGHT.getTypeCode()));
dto.setTemperature(map.get(TemperatureChartEnum.TEMPERATURE.getTypeCode()));
dto.setPulse(map.get(TemperatureChartEnum.PULSE.getTypeCode()));
dto.setHertRate(map.get(TemperatureChartEnum.HEART_RATE.getTypeCode()));
// 心率字段兼容性处理尝试heartRate如果不存在尝试hertRate
String heartRateValue = map.get(TemperatureChartEnum.HEART_RATE.getTypeCode());
if (heartRateValue == null) {
heartRateValue = map.get("hertRate"); // 兼容历史数据中的拼写错误
}
dto.setHertRate(heartRateValue);
String bloodPressure = map.get(TemperatureChartEnum.BLOOD_PRESSURE.getTypeCode());
if (!StringUtils.isEmpty(bloodPressure)) {
if (!StringUtils.isEmpty(bloodPressure) && bloodPressure.contains("/")) {
String[] split = bloodPressure.split("/");
String endBloodPressure = split[0].strip();
String highBloodPressure = split[1].strip();
dto.setEndBloodPressure(endBloodPressure);
dto.setHighBloodPressure(highBloodPressure);
// 安全检查确保分割后有2个元素
if (split.length >= 2) {
String endBloodPressure = split[0].strip();
String highBloodPressure = split[1].strip();
// 进一步检查值不为空
if (!endBloodPressure.isEmpty()) {
dto.setEndBloodPressure(endBloodPressure);
}
if (!highBloodPressure.isEmpty()) {
dto.setHighBloodPressure(highBloodPressure);
}
}
}
}
@@ -833,12 +846,36 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
= ((List<DocStatisticsDto>) docStatisticsAppService.queryByEncounterId(encounterId).getData()).stream()
.filter(item -> DocDefinitionEnum.ADMISSION_VITAL_SIGNS.getValue().equals(item.getSource())).toList();
List<DocStatisticsDto> list = new ArrayList<>(data);
// 先删除所有已有的入院体征记录(重新保存最新数据)
for (DocStatisticsDto existingItem : data) {
if (existingItem.getId() != null) {
docStatisticsMapper.deleteById(existingItem.getId());
}
}
list.clear();
map.keySet().forEach(key -> {
if (map.get(key) != null && !map.get(key).isEmpty()) {
String value = map.get(key);
// 只保存非空值
if (value != null && !value.isEmpty() && !" ".equals(value)) {
DocStatisticsDefinitionDto docStatisticsDefinitionDto = definitionDtoHashMap.get(key);
// 如果找不到定义尝试使用code作为fallback兼容历史数据
if (docStatisticsDefinitionDto == null) {
log.warn("DocStatisticsDefinition not found for key: {}, trying fallback lookup", key);
// 尝试查找任意匹配的定义兼容heartRate/hertRate等差异
docStatisticsDefinitionDto = definitionList.stream()
.filter(d -> key.equalsIgnoreCase(d.getCode()) || key.equalsIgnoreCase(d.getTypeCode()))
.findFirst()
.orElse(null);
}
if (docStatisticsDefinitionDto == null) {
log.error("无法找到DocStatisticsDefinition跳过保存 - key: {}, value: {}", key, value);
return; // 跳过这个字段
}
DocStatisticsDto statistics = new DocStatisticsDto();
statistics.setStatisticDefinitionCode(key);
statistics.setValue(map.get(key));
statistics.setValue(value);
statistics.setEncounterId(encounterId);
statistics.setPatientId(encounter.getPatientId());
statistics.setStatisticDefinitionId(docStatisticsDefinitionDto.getId());

View File

@@ -702,7 +702,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
= medUseExeList.stream().map(MedicationRequestUseExe::getMedicationId).collect(Collectors.toList());
// 医嘱详细信息
List<AdviceBaseDto> medicationInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
medicationDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(1), null).getRecords();
medicationDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(1), null, null).getRecords();
// 当前时间
Date curDate = new Date();
@@ -979,7 +979,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
= actUseExeList.stream().map(ServiceRequestUseExe::getActivityId).collect(Collectors.toList());
// 医嘱详细信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), null).getRecords();
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), null, null).getRecords();
// 当前时间
Date curDate = new Date();
@@ -1146,7 +1146,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
// 耗材医嘱详细信息
List<AdviceBaseDto> deviceInfos = doctorStationAdviceAppService
.getAdviceBaseInfo(null, null, null, deviceIds, 0L, 1, 500, Whether.NO.getValue(), List.of(2), null)
.getAdviceBaseInfo(null, null, null, deviceIds, 0L, 1, 500, Whether.NO.getValue(), List.of(2), null, null)
.getRecords();
DeviceRequest deviceRequest;

View File

@@ -201,7 +201,7 @@ public class EncounterAutoRollAppServiceImpl implements IEncounterAutoRollAppSer
.map(AutoRollNursingDto::getActivityDefinitionId).collect(Collectors.toList());
// 诊疗医嘱信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing).getRecords();
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing, null).getRecords();
// 计费
ChargeItem chargeItem;
@@ -295,7 +295,7 @@ public class EncounterAutoRollAppServiceImpl implements IEncounterAutoRollAppSer
.map(AutoRollBasicServiceDto::getActivityDefinitionId).collect(Collectors.toList());
// 诊疗医嘱信息
List<AdviceBaseDto> activityInfos = doctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing).getRecords();
activityDefinitionIdList, 0L, 1, 500, Whether.NO.getValue(), List.of(3), orderPricing, null).getRecords();
// 计费
ChargeItem chargeItem;
for (AutoRollBasicServiceDto autoRollBasicServiceDto : autoRollBasicService) {

View File

@@ -100,10 +100,12 @@ public class IssueDto {
/** 开始时间 */
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/** 结束时间 */
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/** 单价 */

View File

@@ -4,6 +4,7 @@
package com.openhis.web.inventorymanage.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.core.common.annotation.Excel;
import com.core.common.annotation.ExcelExtra;
@@ -248,7 +249,8 @@ public class ProductTransferDetailDto {
private Date occurrenceTime;
/**
* 单位列表
* 单位列表(非数据库字段,业务逻辑填充)
*/
@TableField(exist = false)
private List<UnitDto> unitList;
}

View File

@@ -0,0 +1,45 @@
package com.openhis.web.lab.appservice;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.core.common.core.domain.R;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 检验项目 AppService 接口(独立操作 lab_activity_definition 表)
*/
public interface ILabActivityDefinitionAppService {
/**
* 分页查询检验项目列表
*/
R<?> getLabActivityDefinitionPage(DiagnosisTreatmentSelParam selParam, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request);
/**
* 根据id查询检验项目详情
*/
R<?> getLabActivityDefinitionOne(Long id);
/**
* 新增检验项目
*/
R<?> addLabActivityDefinition(DiagnosisTreatmentUpDto dto);
/**
* 编辑检验项目
*/
R<?> editLabActivityDefinition(DiagnosisTreatmentUpDto dto);
/**
* 停用检验项目
*/
R<?> stopLabActivityDefinition(List<Long> ids);
/**
* 启用检验项目
*/
R<?> startLabActivityDefinition(List<Long> ids);
}

View File

@@ -0,0 +1,189 @@
package com.openhis.web.lab.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.*;
import com.core.common.utils.bean.BeanUtils;
import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.ActivityType;
import com.openhis.common.enums.PublicationStatus;
import com.openhis.common.enums.Whether;
import com.core.common.utils.ChineseConvertUtils;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.openhis.common.enums.AssignSeqEnum;
import com.openhis.lab.domain.LabActivityDefinition;
import com.openhis.lab.service.ILabActivityDefinitionService;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.openhis.web.lab.appservice.ILabActivityDefinitionAppService;
import com.openhis.web.datadictionary.mapper.LabActivityDefinitionManageMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 检验项目 AppService 实现(独立操作 lab_activity_definition 表)
*/
@Service
public class LabActivityDefinitionAppServiceImpl implements ILabActivityDefinitionAppService {
@Resource
private ILabActivityDefinitionService labActivityDefinitionService;
@Resource
private LabActivityDefinitionManageMapper labActivityDefinitionManageMapper;
@Resource
private AssignSeqUtil assignSeqUtil;
@Override
public R<?> getLabActivityDefinitionPage(DiagnosisTreatmentSelParam selParam, String searchKey,
Integer pageNo, Integer pageSize, HttpServletRequest request) {
if (selParam == null) {
selParam = new DiagnosisTreatmentSelParam();
}
if (selParam.getStatusEnum() == null) {
selParam.setStatusEnum(PublicationStatus.ACTIVE.getValue());
}
// 临时移除需要手动拼别名条件的字段
Long inspectionTypeIdValue = null;
if (selParam.getInspectionTypeId() != null) {
inspectionTypeIdValue = selParam.getInspectionTypeId();
selParam.setInspectionTypeId(null);
}
Integer pricingFlagValue = null;
if (selParam.getPricingFlag() != null) {
pricingFlagValue = selParam.getPricingFlag();
selParam.setPricingFlag(null);
}
QueryWrapper<DiagnosisTreatmentDto> queryWrapper = HisQueryUtils.buildQueryWrapper(selParam,
searchKey, new HashSet<>(Arrays.asList("T1.bus_no", "T1.name", "T1.py_str", "T1.wb_str")), request);
if (inspectionTypeIdValue != null) {
queryWrapper.eq("T1.inspection_type_id", inspectionTypeIdValue);
selParam.setInspectionTypeId(inspectionTypeIdValue);
}
if (pricingFlagValue != null) {
queryWrapper.eq("T1.pricing_flag", pricingFlagValue);
selParam.setPricingFlag(pricingFlagValue);
}
IPage<DiagnosisTreatmentDto> page = labActivityDefinitionManageMapper
.getLabActivityDefinitionPage(new Page<>(pageNo, pageSize), queryWrapper);
page.getRecords().forEach(e -> {
e.setYbFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getYbFlag()));
e.setYbMatchFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getYbMatchFlag()));
e.setTypeEnum_enumText(EnumUtils.getInfoByValue(ActivityType.class, e.getTypeEnum()));
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(PublicationStatus.class, e.getStatusEnum()));
e.setPricingFlag_enumText(EnumUtils.getInfoByValue(Whether.class, e.getPricingFlag()));
});
return R.ok(page);
}
@Override
public R<?> getLabActivityDefinitionOne(Long id) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
DiagnosisTreatmentDto dto = labActivityDefinitionManageMapper.getLabActivityDefinitionOne(id, tenantId);
return R.ok(dto);
}
@Override
public R<?> addLabActivityDefinition(DiagnosisTreatmentUpDto dto) {
if (dto.getOrgId() == null) {
dto.setOrgId(SecurityUtils.getLoginUser().getHospitalId());
}
LabActivityDefinition lab = new LabActivityDefinition();
BeanUtils.copyProperties(dto, lab);
lab.setSortOrder(dto.getSortOrder())
.setServiceRange(dto.getServiceRange())
.setInspectionTypeId(dto.getInspectionTypeId())
.setFeePackageId(dto.getFeePackageId())
.setSubItemId(dto.getSubItemId());
if (StringUtils.isEmpty(lab.getBusNo())) {
lab.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.ACTIVITY_DEFINITION_NUM.getPrefix(), 10));
}
lab.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(lab.getName()));
lab.setWbStr(ChineseConvertUtils.toWBFirstLetter(lab.getName()));
lab.setStatusEnum(PublicationStatus.ACTIVE.getValue());
// 设置创建者和租户ID
String createBy = "system";
Integer tenantId = null;
try {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser != null) {
createBy = loginUser.getUsername();
tenantId = loginUser.getTenantId();
}
} catch (Exception e) {
// 使用默认值
}
lab.setCreateBy(createBy);
lab.setTenantId(tenantId != null ? tenantId : 1);
if (lab.getCreateTime() == null) {
lab.setCreateTime(new java.util.Date());
}
return labActivityDefinitionService.addLabActivityDefinition(lab)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}))
: R.fail(null, "检验编码已存在:" + lab.getBusNo());
}
@Override
public R<?> editLabActivityDefinition(DiagnosisTreatmentUpDto dto) {
LabActivityDefinition lab = new LabActivityDefinition();
BeanUtils.copyProperties(dto, lab);
lab.setSortOrder(dto.getSortOrder())
.setServiceRange(dto.getServiceRange())
.setInspectionTypeId(dto.getInspectionTypeId())
.setFeePackageId(dto.getFeePackageId())
.setSubItemId(dto.getSubItemId())
.setPricingFlag(dto.getPricingFlag());
lab.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(lab.getName()));
lab.setWbStr(ChineseConvertUtils.toWBFirstLetter(lab.getName()));
return labActivityDefinitionService.updateById(lab)
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}))
: R.fail(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
}
@Override
public R<?> stopLabActivityDefinition(List<Long> ids) {
List<LabActivityDefinition> labList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
LabActivityDefinition lab = new LabActivityDefinition();
lab.setId(id).setStatusEnum(PublicationStatus.RETIRED.getValue());
labList.add(lab);
}
labActivityDefinitionService.updateBatchById(labList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}));
}
@Override
public R<?> startLabActivityDefinition(List<Long> ids) {
List<LabActivityDefinition> labList = new CopyOnWriteArrayList<>();
for (Long id : ids) {
LabActivityDefinition lab = new LabActivityDefinition();
lab.setId(id).setStatusEnum(PublicationStatus.ACTIVE.getValue());
labList.add(lab);
}
labActivityDefinitionService.updateBatchById(labList);
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"检验项目"}));
}
}

View File

@@ -146,6 +146,9 @@ public class InspectionPackageController extends BaseController {
if (inspectionPackage.getIsDisabled() != null) {
queryWrapper.eq("is_disabled", inspectionPackage.getIsDisabled());
}
if (inspectionPackage.getUserId() != null && !inspectionPackage.getUserId().isEmpty()) {
queryWrapper.like("user_id", inspectionPackage.getUserId());
}
}
// 默认只查询未删除的记录

View File

@@ -94,7 +94,6 @@ public class InspectionTypeController extends BaseController {
// 查询是否存在相同编码的记录
List<InspectionType> existingRecords = inspectionTypeService.list(queryWrapper);
log.debug("检查编码唯一性code={}, 数据库中存在记录数={}", inspectionType.getCode(), existingRecords.size());
if (!existingRecords.isEmpty()) {
return AjaxResult.error("检验类型编码已存在");
@@ -119,8 +118,6 @@ public class InspectionTypeController extends BaseController {
return toAjax(result);
});
} catch (Exception e) {
log.error("新增检验类型失败code={}, 错误信息:{}", inspectionType.getCode(), e.getMessage(), e);
// 捕获唯一性约束冲突异常
if (e.getMessage().contains("uk_inspection_type_code") ||
e.getMessage().contains("duplicate key value") ||

View File

@@ -0,0 +1,78 @@
package com.openhis.web.lab.controller;
import com.core.common.core.domain.R;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
import com.openhis.web.lab.appservice.ILabActivityDefinitionAppService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 检验项目维护 Controller独立操作 lab_activity_definition 表)
* 路径前缀:/lab/activity-definition
*/
@RestController
@RequestMapping("/lab/activity-definition")
@Slf4j
public class LabActivityDefinitionController {
@Resource
private ILabActivityDefinitionAppService labActivityDefinitionAppService;
/**
* 分页查询检验项目列表
*/
@GetMapping("/page")
public R<?> getPage(DiagnosisTreatmentSelParam selParam,
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest request) {
return labActivityDefinitionAppService.getLabActivityDefinitionPage(selParam, searchKey, pageNo, pageSize, request);
}
/**
* 根据id查询检验项目详情
*/
@GetMapping("/one")
public R<?> getOne(@RequestParam Long id) {
return labActivityDefinitionAppService.getLabActivityDefinitionOne(id);
}
/**
* 新增检验项目
*/
@PostMapping("/add")
public R<?> add(@Validated @RequestBody DiagnosisTreatmentUpDto dto) {
return labActivityDefinitionAppService.addLabActivityDefinition(dto);
}
/**
* 编辑检验项目
*/
@PutMapping("/edit")
public R<?> edit(@RequestBody DiagnosisTreatmentUpDto dto) {
return labActivityDefinitionAppService.editLabActivityDefinition(dto);
}
/**
* 停用检验项目
*/
@PutMapping("/stop")
public R<?> stop(@RequestBody List<Long> ids) {
return labActivityDefinitionAppService.stopLabActivityDefinition(ids);
}
/**
* 启用检验项目
*/
@PutMapping("/start")
public R<?> start(@RequestBody List<Long> ids) {
return labActivityDefinitionAppService.startLabActivityDefinition(ids);
}
}

View File

@@ -55,6 +55,8 @@ import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
import com.openhis.web.paymentmanage.dto.Clinic2207OrderResultDto;
import com.openhis.web.paymentmanage.mapper.PaymentMapper;
import com.openhis.web.personalization.dto.ActivityDeviceDto;
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService;
@@ -81,9 +83,11 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
@@ -149,6 +153,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
@Resource
private OutpatientRegistrationAppMapper outpatientRegistrationAppMapper;
@Resource
private TriageQueueItemService triageQueueItemService;
@Resource
private IRegService iRegService;
@Resource
private IPatientService iPatientService;
@@ -255,7 +261,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// 账户id对应的账单列表
Map<Long, List<ChargeItem>> chargeItemMapByAccountId
= chargeItemList.stream().collect(Collectors.groupingBy(ChargeItem::getAccountId));
= chargeItemList.stream()
.filter(item -> item.getAccountId() != null)
.collect(Collectors.groupingBy(ChargeItem::getAccountId));
// 查询合同信息
List<Contract> contractList = contractService.list();
@@ -1928,6 +1936,80 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// 就诊ID
Long encounterId = iEncounterService.saveEncounterByRegister(encounter);
// 创建 triage_queue_item 队列记录
try {
Integer tenantId = encounter.getTenantId() != null ? encounter.getTenantId() : SecurityUtils.getLoginUser().getTenantId();
LocalDate queueDate = LocalDate.now();
// 查询当前科室当天的最大排队序号
Integer maxOrder = triageQueueItemService.list(
new LambdaQueryWrapper<TriageQueueItem>()
.eq(TriageQueueItem::getTenantId, tenantId)
.eq(TriageQueueItem::getOrganizationId, encounter.getOrganizationId())
.eq(TriageQueueItem::getQueueDate, queueDate)
.eq(TriageQueueItem::getDeleteFlag, "0")
.ne(TriageQueueItem::getStatus, "COMPLETED")
).stream()
.map(TriageQueueItem::getQueueOrder)
.filter(Objects::nonNull)
.max(Integer::compareTo)
.orElse(0);
// 获取患者信息
Patient patient = iPatientService.getById(encounter.getPatientId());
String patientName = patient != null ? patient.getName() : null;
// 获取挂号医生信息
String practitionerName = null;
Long queuePractitionerId = null;
if (encounterParticipantFormData.getPractitionerId() != null) {
Practitioner practitioner = iPractitionerService.getById(encounterParticipantFormData.getPractitionerId());
if (practitioner != null) {
practitionerName = practitioner.getName();
queuePractitionerId = practitioner.getId();
}
}
// 获取科室信息
Organization organization = iOrganizationService.getById(encounter.getOrganizationId());
String organizationName = organization != null ? organization.getName() : null;
// 获取服务项目信息(挂号类型)
String healthcareName = null;
if (encounter.getServiceTypeId() != null) {
HealthcareService healthcareService = healthcareServiceService.getById(encounter.getServiceTypeId());
if (healthcareService != null) {
healthcareName = healthcareService.getName();
}
}
// 创建队列项
TriageQueueItem queueItem = new TriageQueueItem()
.setTenantId(tenantId)
.setQueueDate(queueDate)
.setOrganizationId(encounter.getOrganizationId())
.setOrganizationName(organizationName)
.setEncounterId(encounterId)
.setPatientId(encounter.getPatientId())
.setPatientName(patientName)
.setHealthcareName(healthcareName)
.setPractitionerId(queuePractitionerId)
.setPractitionerName(practitionerName)
.setRoomNo(null)
.setStatus("WAITING")
.setQueueOrder(maxOrder + 1)
.setDeleteFlag("0")
.setCreateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS))
.setUpdateTime(LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
triageQueueItemService.save(queueItem);
logger.info("挂号时创建队列记录成功encounterId={}, queueItemId={}", encounterId, queueItem.getId());
} catch (Exception e) {
logger.error("挂号时创建队列记录失败encounterId={}", encounterId, e);
// 队列记录创建失败不影响挂号流程
}
// 保存就诊位置信息
// 挂号时不选Location了
// encounterLocationFormData.setEncounterId(encounterId);
@@ -2095,7 +2177,7 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
adviceBaseDto.setAdviceDefinitionId(activityDeviceDto.getDevActId());
// 对应的诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null,
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords().get(0);
null, null, organizationId, 1, 1, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords().get(0);
// 价格信息
if (activityAdviceBaseDto != null) {
// 费用定价
@@ -2251,7 +2333,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
= iChargeItemService.getChargeItemBaseInfoByIds(prePaymentDto.getChargeItemIds());
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
= chargeItemBaseInfoByIds.stream()
.filter(dto -> dto.getContractNo() != null && !dto.getContractNo().isEmpty())
.collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
List<InpatientPreSettleDto> yb2303OutputSetInfos = new ArrayList<>();
Yb2303OutputSetInfo yb2303OutputSetInfo;
@@ -2379,13 +2463,17 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
List<ChargeItemBaseInfoDto> chargeItemBaseInfoByIds
= iChargeItemService.getChargeItemBaseInfoByIds(paymentDto.getChargeItemIds());
Map<String, List<ChargeItemBaseInfoDto>> chargeItemKVByContractNo
= chargeItemBaseInfoByIds.stream().collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
= chargeItemBaseInfoByIds.stream()
.filter(dto -> dto.getContractNo() != null && !dto.getContractNo().isEmpty())
.collect(Collectors.groupingBy(ChargeItemBaseInfoDto::getContractNo));
List<Account> accountList = iAccountService.getAccountListByEncounter(paymentDto.getEncounterId());
if (accountList.isEmpty()) {
throw new ServiceException("未查询到账户信息");
}
Map<Long, List<Account>> accountKVById = accountList.stream().collect(Collectors.groupingBy(Account::getId));
Map<Long, List<Account>> accountKVById = accountList.stream()
.filter(acc -> acc.getId() != null)
.collect(Collectors.groupingBy(Account::getId));
com.openhis.financial.model.PaymentResult paymentResult;
List<com.openhis.financial.model.PaymentResult> paymentResultList = new ArrayList<>();
@@ -2395,7 +2483,9 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// <3>收费详情按照收费批次进行分组后结算
Map<Long, List<PaymentRecDetail>> payTransNoMap
= paymentRecDetails.stream().collect(Collectors.groupingBy(PaymentRecDetail::getAccountId));
= paymentRecDetails.stream()
.filter(detail -> detail.getAccountId() != null)
.collect(Collectors.groupingBy(PaymentRecDetail::getAccountId));
for (Map.Entry<Long, List<PaymentRecDetail>> stringListEntry : payTransNoMap.entrySet()) {
// paymentResult = new PaymentResult();

View File

@@ -197,7 +197,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords();
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个
@@ -248,7 +248,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords();
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个
@@ -297,7 +297,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 医嘱下拉详细信息
List<AdviceBaseDto> personalRecords =
iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null, orderDefinitionIdParamList,
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null).getRecords();
organizationId, 1, 100, Whether.NO.getValue(), List.of(1, 2, 3), null, null).getRecords();
// 创建AdviceBaseDto的映射以adviceDefinitionId为key
Map<Long, AdviceBaseDto> adviceMap = personalRecords.stream().collect(Collectors
.toMap(AdviceBaseDto::getAdviceDefinitionId, advice -> advice, (existing, replacement) -> existing // 如果有重复key保留第一个

View File

@@ -394,7 +394,7 @@ public class MedicalDeviceDispenseAppServiceImpl implements IMedicalDeviceDispen
}
List<ChargeItem> chargeItemList = chargeItemService.listByIds(chargeItemIds);
if (chargeItemList == null || chargeItemList.isEmpty()) {
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
return R.fail("未查询到耗材收费项目信息");
}
// 获取发申请id列表

View File

@@ -8,6 +8,7 @@ import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants;
@@ -28,8 +29,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -132,6 +135,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
ChargeItem chargeItem;
// 诊疗集合
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
log.info("保存申请单typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
// 诊疗执行科室配置
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
@@ -211,6 +215,189 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
}
}
// 如果是手术申请单,需要额外生成手术医嘱
log.info("【调试】判断手术医嘱生成条件: typeCode={}, PROCEDURE.code={}, typeCode类型={}, PROCEDURE.code类型={}",
typeCode, ActivityDefCategory.PROCEDURE.getCode(),
typeCode != null ? typeCode.getClass().getName() : "null",
ActivityDefCategory.PROCEDURE.getCode().getClass().getName());
boolean isProcedure = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode);
log.info("【调试】判断结果: isProcedure={}, typeCode字符串={}, PROCEDURE.code字符串={}",
isProcedure,
typeCode != null ? "'" + typeCode + "'" : "null",
"'" + ActivityDefCategory.PROCEDURE.getCode() + "'");
if (isProcedure) {
log.info("开始生成手术医嘱encounterId={}, patientId={}, typeCode={}, activityListSize={}",
encounterId, patientId, typeCode, activityList != null ? activityList.size() : 0);
try {
// 从 descJson 中解析手术信息
String descJson = requestFormSaveDto.getDescJson();
Map<String, Object> descMap = null;
if (descJson != null && !descJson.isEmpty()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
descMap = objectMapper.readValue(descJson, Map.class);
log.info("解析手术申请单 descJson 成功: {}", descMap);
} catch (Exception e) {
log.error("解析手术申请单 descJson 失败: {}", descJson, e);
}
} else {
log.warn("手术申请单 descJson 为空");
}
// 获取手术信息
String surgeryName = descMap != null ? (String) descMap.get("surgeryName") : null;
String surgeryCode = descMap != null ? (String) descMap.get("surgeryCode") : null;
String surgeryFee = descMap != null ? (String) descMap.get("surgeryFee") : null;
String anesthesiaFee = descMap != null ? (String) descMap.get("anesthesiaFee") : null;
String plannedTime = descMap != null ? (String) descMap.get("plannedTime") : null;
String surgeryIndication = descMap != null ? (String) descMap.get("surgeryIndication") : null;
String preoperativeDiagnosis = descMap != null ? (String) descMap.get("preoperativeDiagnosis") : null;
// 🔧 BugFix#318: 从 activityList 获取手术项目名称
String adviceDefinitionName = null;
if (activityList != null && !activityList.isEmpty()) {
adviceDefinitionName = activityList.get(0).getAdviceDefinitionName();
log.info("从 activityList 获取手术项目名称: {}", adviceDefinitionName);
}
log.info("手术信息: surgeryName={}, surgeryCode={}, surgeryFee={}, anesthesiaFee={}, adviceDefinitionName={}",
surgeryName, surgeryCode, surgeryFee, anesthesiaFee, adviceDefinitionName);
// 生成手术医嘱
ServiceRequest surgeryServiceRequest = new ServiceRequest();
surgeryServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
surgeryServiceRequest.setBusNo(String.format("%04d", (int) (Math.random() * 10000)));
surgeryServiceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
surgeryServiceRequest.setPrescriptionNo(prescriptionNo);
surgeryServiceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());
surgeryServiceRequest.setQuantity(BigDecimal.valueOf(1));
surgeryServiceRequest.setUnitCode("");
surgeryServiceRequest.setCategoryEnum(4); // 4-手术
// 优先从 activityList 获取手术 ID
if (activityList != null && !activityList.isEmpty()) {
Long activityId = activityList.get(0).getAdviceDefinitionId();
surgeryServiceRequest.setActivityId(activityId);
log.info("从 activityList 获取手术ID: {}", activityId);
} else {
log.warn("activityList 为空无法获取手术ID");
}
surgeryServiceRequest.setPatientId(patientId);
surgeryServiceRequest.setRequesterId(practitionerId);
surgeryServiceRequest.setEncounterId(encounterId);
surgeryServiceRequest.setAuthoredTime(curDate);
surgeryServiceRequest.setOrgId(orgId);
// 设置手术相关信息到 contentJson 字段
Map<String, String> contentMap = new java.util.HashMap<>();
// 🔧 BugFix#318: 优先使用 activityList 中的手术项目名称
if (adviceDefinitionName != null && !adviceDefinitionName.isEmpty()) {
contentMap.put("surgeryName", adviceDefinitionName);
} else if (surgeryName != null && !surgeryName.isEmpty()) {
contentMap.put("surgeryName", surgeryName);
}
if (surgeryCode != null && !surgeryCode.isEmpty()) {
contentMap.put("surgeryCode", surgeryCode);
}
if (plannedTime != null && !plannedTime.isEmpty()) {
contentMap.put("plannedTime", plannedTime);
}
if (surgeryIndication != null && !surgeryIndication.isEmpty()) {
contentMap.put("surgeryIndication", surgeryIndication);
}
if (preoperativeDiagnosis != null && !preoperativeDiagnosis.isEmpty()) {
contentMap.put("preoperativeDiagnosis", preoperativeDiagnosis);
}
if (!contentMap.isEmpty()) {
try {
ObjectMapper objectMapper = new ObjectMapper();
surgeryServiceRequest.setContentJson(objectMapper.writeValueAsString(contentMap));
} catch (Exception e) {
log.error("序列化手术信息失败", e);
}
}
try {
iServiceRequestService.save(surgeryServiceRequest);
log.info("手术医嘱生成成功serviceRequestId={}, prescriptionNo={}", surgeryServiceRequest.getId(), prescriptionNo);
} catch (Exception e) {
log.error("保存手术医嘱失败", e);
throw new ServiceException("保存手术医嘱失败: " + e.getMessage());
}
// 生成手术收费项目
try {
ChargeItem surgeryChargeItem = new ChargeItem();
surgeryChargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
surgeryChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(surgeryServiceRequest.getBusNo()));
surgeryChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
surgeryChargeItem.setPatientId(patientId);
surgeryChargeItem.setContextEnum(6); // 6-手术
surgeryChargeItem.setEncounterId(encounterId);
surgeryChargeItem.setEntererId(practitionerId);
surgeryChargeItem.setEnteredDate(curDate);
surgeryChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
surgeryChargeItem.setServiceId(surgeryServiceRequest.getId());
surgeryChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
// 优先从 activityList 获取 productId
if (activityList != null && !activityList.isEmpty()) {
surgeryChargeItem.setProductId(activityList.get(0).getAdviceDefinitionId());
surgeryChargeItem.setAccountId(activityList.get(0).getAccountId());
}
surgeryChargeItem.setRequestingOrgId(orgId);
surgeryChargeItem.setQuantityValue(BigDecimal.valueOf(1));
surgeryChargeItem.setQuantityUnit("");
// 设置手术费用
if (surgeryFee != null && !surgeryFee.isEmpty()) {
try {
surgeryChargeItem.setUnitPrice(new BigDecimal(surgeryFee));
surgeryChargeItem.setTotalPrice(new BigDecimal(surgeryFee));
} catch (NumberFormatException e) {
log.warn("手术费用格式不正确:{}", surgeryFee);
}
}
iChargeItemService.save(surgeryChargeItem);
log.info("手术收费项目生成成功chargeItemId={}", surgeryChargeItem.getId());
} catch (Exception e) {
log.error("生成手术收费项目失败", e);
throw new ServiceException("生成手术收费项目失败: " + e.getMessage());
}
// 如果存在麻醉费用,生成麻醉收费项目
if (anesthesiaFee != null && !anesthesiaFee.isEmpty()) {
try {
BigDecimal anesthesiaFeeAmount = new BigDecimal(anesthesiaFee);
if (anesthesiaFeeAmount.compareTo(BigDecimal.ZERO) > 0) {
ChargeItem anesthesiaChargeItem = new ChargeItem();
anesthesiaChargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
anesthesiaChargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(surgeryServiceRequest.getBusNo()));
anesthesiaChargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
anesthesiaChargeItem.setPatientId(patientId);
anesthesiaChargeItem.setContextEnum(3); // 3-诊疗
anesthesiaChargeItem.setEncounterId(encounterId);
anesthesiaChargeItem.setEntererId(practitionerId);
anesthesiaChargeItem.setEnteredDate(curDate);
anesthesiaChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
anesthesiaChargeItem.setServiceId(surgeryServiceRequest.getId());
anesthesiaChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
anesthesiaChargeItem.setRequestingOrgId(orgId);
anesthesiaChargeItem.setQuantityValue(BigDecimal.valueOf(1));
anesthesiaChargeItem.setQuantityUnit("");
anesthesiaChargeItem.setUnitPrice(anesthesiaFeeAmount);
anesthesiaChargeItem.setTotalPrice(anesthesiaFeeAmount);
iChargeItemService.save(anesthesiaChargeItem);
log.info("麻醉收费项目生成成功");
}
} catch (NumberFormatException e) {
log.warn("麻醉费用格式不正确:{}", anesthesiaFee);
}
}
} catch (Exception e) {
log.error("生成手术医嘱过程中发生异常", e);
throw e;
}
} else {
log.info("不是手术申请单跳过手术医嘱生成typeCode={}", typeCode);
}
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"申请单"}));
}

View File

@@ -161,7 +161,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
// 对应的诊疗医嘱信息
activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null,
null, null, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords().get(0);
null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords().get(0);
// 逻辑1---------------------直接新增
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
@@ -208,7 +208,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
// 对应的诊疗医嘱信息
activityAdviceBaseDto = iDoctorStationAdviceAppService
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null)
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
.getRecords().get(0);
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
@@ -348,7 +348,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
adviceBaseDto.setAdviceDefinitionId(transferOrganizationDefinitionId); // 医嘱定义id
// 转科的医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null)
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
.getRecords().get(0);
// 保存转科医嘱请求
ServiceRequest serviceRequest = new ServiceRequest();
@@ -430,7 +430,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
// 出院的医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
List.of(transferOrganizationDefinitionId), null, 1, 1, Whether.NO.getValue(), List.of(3), null).getRecords()
List.of(transferOrganizationDefinitionId), null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords()
.get(0);
// 保存出院医嘱请求
ServiceRequest serviceRequest = new ServiceRequest();

View File

@@ -72,6 +72,7 @@ public class RequestFormManageController {
*/
@PostMapping(value = "/save-surgery")
public R<?> saveSurgeryRequestForm(@RequestBody RequestFormSaveDto requestFormSaveDto) {
log.info("【Controller】保存手术申请单typeCode={}", ActivityDefCategory.PROCEDURE.getCode());
return iRequestFormManageAppService.saveRequestForm(requestFormSaveDto,
ActivityDefCategory.PROCEDURE.getCode());
}

View File

@@ -10,6 +10,10 @@ import java.time.LocalDate;
@AllArgsConstructor
@NoArgsConstructor
public class RequestFormDto {
/**
* 手术单号
*/
private String surgeryNo;
/**
* 申请时间开始
*/

View File

@@ -77,4 +77,25 @@ public class RequestFormPageDto {
* 申请时间
*/
private String applyTime;
/**
* 费用类别
*/
private String feeType;
/**
* 麻醉方式
*/
private Integer anesthesiaTypeEnum;
/**
* 切口等级
*/
private Integer incisionLevel;
/**
* 手术等级
*/
private Integer surgeryLevel;
/**
* 就诊卡号
*/
private String identifierNo;
}

View File

@@ -0,0 +1,98 @@
package com.openhis.web.reportManagement.appservice;
import com.core.common.core.domain.R;
import com.openhis.web.reportManagement.dto.InfectiousCardParam;
import javax.servlet.http.HttpServletResponse;
// import java.util.List; // 批量操作功能暂未实现
/**
* 传染病报卡 AppService 接口
*
* @author system
* @date 2026-03-17
*/
public interface IInfectiousCardAppService {
/**
* 分页查询传染病报卡列表
*
* @param param 查询参数
* @param pageNo 当前页码
* @param pageSize 每页数量
* @return 传染病报卡列表
*/
R<?> listPage(InfectiousCardParam param, Integer pageNo, Integer pageSize);
/**
* 根据 ID 查询传染病报卡详情
*
* @param id 报卡 ID
* @return 传染病报卡详情
*/
R<?> getById(Long id);
/**
* 根据卡号查询传染病报卡详情
*
* @param cardNo 报卡编号
* @return 传染病报卡详情
*/
R<?> getByCardNo(String cardNo);
/**
* 审核传染病报卡(功能暂未实现)
*
* @param cardNo 报卡编号
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 结果
*/
// R<?> audit(String cardNo, String auditOpinion, String status);
/**
* 退回传染病报卡(功能暂未实现)
*
* @param cardNo 报卡编号
* @param returnReason 退回原因
* @param status 审核状态
* @return 结果
*/
// R<?> returnCard(String cardNo, String returnReason, String status);
/**
* 批量审核传染病报卡(功能暂未实现)
*
* @param cardNos 报卡编号列表
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 结果
*/
// R<?> batchAudit(List<String> cardNos, String auditOpinion, String status);
/**
* 批量退回传染病报卡(功能暂未实现)
*
* @param cardNos 报卡编号列表
* @param returnReason 退回原因
* @param status 审核状态
* @return 结果
*/
// R<?> batchReturn(List<String> cardNos, String returnReason, String status);
/**
* 导出传染病报卡
*
* @param param 查询参数
* @param response 响应对象
*/
void export(InfectiousCardParam param, HttpServletResponse response);
/**
* 获取科室树
*
* @return 科室树数据
*/
R<?> getDeptTree();
}

View File

@@ -0,0 +1,269 @@
package com.openhis.web.reportManagement.appservice.impl;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.openhis.administration.domain.InfectiousDiseaseReport;
import com.openhis.administration.domain.Organization;
import com.openhis.administration.mapper.InfectiousDiseaseReportMapper;
import com.openhis.administration.service.IOrganizationService;
import com.openhis.web.reportManagement.appservice.IInfectiousCardAppService;
import com.openhis.web.reportManagement.dto.InfectiousCardDto;
import com.openhis.web.reportManagement.dto.InfectiousCardParam;
import com.openhis.web.reportManagement.mapper.ReportManageCardMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 传染病报卡 AppService 实现
*
* @author system
* @date 2026-03-17
*/
@Service
@Slf4j
public class InfectiousCardAppServiceImpl implements IInfectiousCardAppService {
@Autowired
private ReportManageCardMapper reportManageCardMapper;
@Autowired
private InfectiousDiseaseReportMapper infectiousDiseaseReportMapper;
@Autowired
private IOrganizationService organizationService;
/**
* 分页查询传染病报卡列表
* @param param 查询参数
* @param pageNo 页码
* @param pageSize 每页条数
* @return 报卡列表
*/
@Override
public R<?> listPage(InfectiousCardParam param, Integer pageNo, Integer pageSize) {
try {
Page<InfectiousCardDto> page = new Page<>(pageNo, pageSize);
IPage<InfectiousCardDto> resultPage = reportManageCardMapper.selectCardPage(page, param);
JSONObject result = new JSONObject();
result.put("rows", resultPage.getRecords());
result.put("total", resultPage.getTotal());
return R.ok(result);
} catch (Exception e) {
log.error("查询传染病报卡列表失败", e);
return R.fail("查询失败:" + e.getMessage());
}
}
/**
* 根据 ID 查询传染病报卡详情
* @param id 报卡 ID实际为 cardNo
* @return 报卡详情
*/
@Override
public R<?> getById(Long id) {
try {
// 注id 参数实际是 cardNo需要转换为 String
InfectiousCardDto dto = reportManageCardMapper.selectCardByCardNo(String.valueOf(id));
if (dto == null) {
return R.fail("报卡不存在");
}
return R.ok(dto);
} catch (Exception e) {
log.error("根据 ID 查询传染病报卡失败", e);
return R.fail("查询失败:" + e.getMessage());
}
}
/**
* 根据卡号查询传染病报卡详情
* @param cardNo 卡号
* @return 报卡详情
*/
@Override
public R<?> getByCardNo(String cardNo) {
try {
InfectiousCardDto dto = reportManageCardMapper.selectCardByCardNo(cardNo);
if (dto == null) {
return R.fail("报卡不存在");
}
return R.ok(dto);
} catch (Exception e) {
log.error("根据卡号查询传染病报卡失败", e);
return R.fail("查询失败:" + e.getMessage());
}
}
/**
* 审核传染病报卡(功能暂未实现)
* @param cardNo 卡号
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 审核结果
*/
// @Override
// public R<?> audit(String cardNo, String auditOpinion, String status) {
// try {
// InfectiousDiseaseReport report = infectiousDiseaseReportMapper.selectById(cardNo);
// if (report == null) {
// return R.fail("报卡不存在");
// }
// report.setStatus(Integer.parseInt(status));
// report.setUpdateTime(new Date());
// infectiousDiseaseReportMapper.updateById(report);
// return R.ok("审核成功");
// } catch (Exception e) {
// log.error("审核传染病报卡失败", e);
// return R.fail("审核失败:" + e.getMessage());
// }
// }
/**
* 退回传染病报卡(功能暂未实现)
* @param cardNo 卡号
* @param returnReason 退回原因
* @param status 退回状态
* @return 退回结果
*/
// @Override
// public R<?> returnCard(String cardNo, String returnReason, String status) {
// try {
// InfectiousDiseaseReport report = infectiousDiseaseReportMapper.selectById(cardNo);
// if (report == null) {
// return R.fail("报卡不存在");
// }
// report.setStatus(Integer.parseInt(status));
// report.setWithdrawReason(returnReason);
// report.setUpdateTime(new Date());
// infectiousDiseaseReportMapper.updateById(report);
// return R.ok("退回成功");
// } catch (Exception e) {
// log.error("退回传染病报卡失败", e);
// return R.fail("退回失败:" + e.getMessage());
// }
// }
/**
* 批量审核传染病报卡(功能暂未实现)
* @param cardNos 卡号列表
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 批量审核结果
*/
// @Override
// public R<?> batchAudit(List<String> cardNos, String auditOpinion, String status) {
// try {
// for (String cardNo : cardNos) {
// InfectiousDiseaseReport report = infectiousDiseaseReportMapper.selectById(cardNo);
// if (report != null) {
// report.setStatus(Integer.parseInt(status));
// report.setUpdateTime(new Date());
// infectiousDiseaseReportMapper.updateById(report);
// }
// }
// return R.ok("批量审核成功");
// } catch (Exception e) {
// log.error("批量审核传染病报卡失败", e);
// return R.fail("批量审核失败:" + e.getMessage());
// }
// }
/**
* 批量退回传染病报卡(功能暂未实现)
* @param cardNos 卡号列表
* @param returnReason 退回原因
* @param status 退回状态
* @return 批量退回结果
*/
// @Override
// public R<?> batchReturn(List<String> cardNos, String returnReason, String status) {
// try {
// for (String cardNo : cardNos) {
// InfectiousDiseaseReport report = infectiousDiseaseReportMapper.selectById(cardNo);
// if (report != null) {
// report.setStatus(Integer.parseInt(status));
// report.setWithdrawReason(returnReason);
// report.setUpdateTime(new Date());
// infectiousDiseaseReportMapper.updateById(report);
// }
// }
// return R.ok("批量退回成功");
// } catch (Exception e) {
// log.error("批量退回传染病报卡失败", e);
// return R.fail("批量退回失败:" + e.getMessage());
// }
// }
/**
* 导出传染病报卡数据
* @param param 查询参数
* @param response HTTP 响应对象
*/
@Override
public void export(InfectiousCardParam param, HttpServletResponse response) {
log.warn("导出功能暂未实现");
}
/**
* 获取科室树
* @return 科室树数据
*/
@Override
public R<?> getDeptTree() {
try {
// 查询所有启用的机构/科室
List<Organization> organizations = organizationService.list();
List<TreeNode> tree = buildTree(organizations);
return R.ok(tree);
} catch (Exception e) {
log.error("获取科室树失败", e);
return R.fail("获取科室树失败:" + e.getMessage());
}
}
/**
* 构建树形结构
*/
private List<TreeNode> buildTree(List<Organization> list) {
List<TreeNode> tree = new ArrayList<>();
for (Organization org : list) {
TreeNode node = new TreeNode();
node.value = org.getId();
node.label = org.getName();
node.children = new ArrayList<>();
tree.add(node);
}
return tree;
}
/**
* 树形节点 DTO
*/
private static class TreeNode {
private Long value;
private String label;
private List<TreeNode> children;
public Long getValue() { return value; }
public void setValue(Long value) { this.value = value; }
public String getLabel() { return label; }
public void setLabel(String label) { this.label = label; }
public List<TreeNode> getChildren() { return children; }
public void setChildren(List<TreeNode> children) { this.children = children; }
}
}

View File

@@ -0,0 +1,145 @@
package com.openhis.web.reportManagement.controller;
import com.core.common.core.domain.R;
import com.openhis.web.reportManagement.appservice.IInfectiousCardAppService;
import com.openhis.web.reportManagement.dto.InfectiousCardParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
// import java.util.List; // 批量操作功能暂未实现
/**
* 传染病报卡管理 Controller
*
* @author wangjian963
* @date 2026-03-17
*/
@RestController
@RequestMapping("/report-manage/infectiousDiseaseReport")
@Slf4j
public class reportManagementController {
@Autowired
private IInfectiousCardAppService infectiousCardAppService;
/**
* 分页查询传染病报卡列表
*
* @param param 查询参数
* @param pageNo 当前页码
* @param pageSize 每页数量
* @return 传染病报卡列表
*/
@GetMapping("/list-page")
public R<?> listPage(InfectiousCardParam param,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
return infectiousCardAppService.listPage(param, pageNo, pageSize);
}
/**
* 根据 ID 查询传染病报卡详情
*
* @param id 报卡 ID
* @return 传染病报卡详情
*/
@GetMapping("/{id}")
public R<?> getById(@PathVariable Long id) {
return infectiousCardAppService.getById(id);
}
/**
* 根据卡号查询传染病报卡详情
*
* @param cardNo 报卡编号
* @return 传染病报卡详情
*/
@GetMapping("/detail/{cardNo}")
public R<?> getByCardNo(@PathVariable String cardNo) {
return infectiousCardAppService.getByCardNo(cardNo);
}
/**
* 审核传染病报卡(功能暂未实现)
*
* @param cardNo 报卡编号
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 结果
*/
// @PostMapping("/audit")
// public R<?> audit(@RequestParam String cardNo,
// @RequestParam String auditOpinion,
// @RequestParam String status) {
// return infectiousCardAppService.audit(cardNo, auditOpinion, status);
// }
/**
* 退回传染病报卡(功能暂未实现)
*
* @param cardNo 报卡编号
* @param returnReason 退回原因
* @param status 审核状态
* @return 结果
*/
// @PostMapping("/return")
// public R<?> returnCard(@RequestParam String cardNo,
// @RequestParam String returnReason,
// @RequestParam String status) {
// return infectiousCardAppService.returnCard(cardNo, returnReason, status);
// }
/**
* 批量审核传染病报卡(功能暂未实现)
*
* @param cardNos 报卡编号列表
* @param auditOpinion 审核意见
* @param status 审核状态
* @return 结果
*/
// @PostMapping("/batchAudit")
// public R<?> batchAudit(@RequestBody List<String> cardNos,
// @RequestParam String auditOpinion,
// @RequestParam String status) {
// return infectiousCardAppService.batchAudit(cardNos, auditOpinion, status);
// }
/**
* 批量退回传染病报卡(功能暂未实现)
*
* @param cardNos 报卡编号列表
* @param returnReason 退回原因
* @param status 审核状态
* @return 结果
*/
// @PostMapping("/batchReturn")
// public R<?> batchReturn(@RequestBody List<String> cardNos,
// @RequestParam String returnReason,
// @RequestParam String status) {
// return infectiousCardAppService.batchReturn(cardNos, returnReason, status);
// }
/**
* 导出传染病报卡
*
* @param param 查询参数
* @param response 响应对象
*/
@PostMapping("/export")
public void export(InfectiousCardParam param, HttpServletResponse response) {
infectiousCardAppService.export(param, response);
}
/**
* 获取科室树
*
* @return 科室树数据
*/
@GetMapping("/dept-tree")
public R<?> getDeptTree() {
return infectiousCardAppService.getDeptTree();
}
}

View File

@@ -0,0 +1,142 @@
package com.openhis.web.reportManagement.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.Date;
/**
* 传染病报卡详情 DTO
*
* @author system
* @date 2026-03-17
*/
@Data
public class InfectiousCardDto {
/** 卡片编号(主键) */
private String cardNo;
/** 报卡名称 */
private String cardName;
/** 病种名称 */
private String diseaseName;
/** 疾病编码 */
private String diseaseCode;
/** 患者姓名 */
private String patientName;
/** 性别 (1 男/2 女/0 未知) */
private String sex;
/** 实足年龄 */
private Integer age;
/** 科室 ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long deptId;
/** 科室名称 */
private String deptName;
/** 登记来源 (1 门诊/2 住院/3 急诊/4 体检) */
private Integer registrationSource;
/** 报告日期 */
private Date reportDate;
/** 状态 (0 暂存/1 待审核/2 已审核/3 已上报/4 失败/5 退回) */
private Integer status;
/** 证件类型 */
private Integer idType;
/** 证件号码 */
private String idNo;
/** 家长姓名 */
private String parentName;
/** 出生日期 */
private Date birthday;
/** 年龄单位 (1 岁/2 月/3 天) */
private String ageUnit;
/** 工作单位 */
private String workplace;
/** 联系电话 */
private String phone;
/** 紧急联系人电话 */
private String contactPhone;
/** 现住址省 */
private String addressProv;
/** 现住址市 */
private String addressCity;
/** 现住址县 */
private String addressCounty;
/** 现住址街道 */
private String addressTown;
/** 现住址村/居委 */
private String addressVillage;
/** 现住址门牌号 */
private String addressHouse;
/** 病人属于 */
private Integer patientBelong;
/** 职业 */
private String occupation;
/** 疾病分型 */
private String diseaseType;
/** 病例分类 */
private Integer caseClass;
/** 发病日期 */
private Date onsetDate;
/** 诊断日期 */
private Date diagDate;
/** 死亡日期 */
private Date deathDate;
/** 报告单位 */
private String reportOrg;
/** 报告医生 */
private String reportDoc;
/** 备注 */
private String remark;
/** 审核意见 */
private String auditOpinion;
/** 退回原因 */
private String returnReason;
/** 订正病名 */
private String correctName;
/** 退卡原因 */
private String withdrawReason;
/** 其他传染病 */
private String otherDisease;
}

View File

@@ -0,0 +1,35 @@
package com.openhis.web.reportManagement.dto;
import lombok.Data;
/**
* 传染病报卡查询参数
*
* @author system
* @date 2026-03-17
*/
@Data
public class InfectiousCardParam {
/** 报卡编号 */
private String cardNo;
/** 患者姓名 */
private String patientName;
/** 审核状态 (0 暂存/1 待审核/2 已审核/3 已上报/4 失败/5 退回) */
private Integer status;
/** 登记来源 (1 门诊/2 住院/3 急诊/4 体检) */
private Integer registrationSource;
/** 科室 ID */
private Long deptId;
/** 开始日期 */
private String startDate;
/** 结束日期 */
private String endDate;
}

View File

@@ -0,0 +1,33 @@
package com.openhis.web.reportManagement.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.openhis.web.reportManagement.dto.InfectiousCardDto;
import com.openhis.web.reportManagement.dto.InfectiousCardParam;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
/**
* 传染病报卡 Mapper 接口
*
* @author system
* @date 2026-03-18
*/
@Repository
public interface ReportManageCardMapper {
/**
* 分页查询报卡列表(联查科室名称)
* @param page 分页对象
* @param param 查询参数
* @return 报卡列表
*/
IPage<InfectiousCardDto> selectCardPage(Page<InfectiousCardDto> page, @Param("param") InfectiousCardParam param);
/**
* 根据卡号查询报卡详情(联查科室名称)
* @param cardNo 卡号
* @return 报卡详情
*/
InfectiousCardDto selectCardByCardNo(@Param("cardNo") String cardNo);
}

View File

@@ -23,6 +23,7 @@
is_stopped,
stop_reason,
dept_id,
reg_type,
doctor_id
<if test="createTime != null">, create_time</if>
<if test="updateTime != null">, update_time</if>
@@ -43,6 +44,7 @@
#{isStopped},
#{stopReason},
#{deptId},
#{regType},
#{doctorId}
<if test="createTime != null">, #{createTime}</if>
<if test="updateTime != null">, #{updateTime}</if>
@@ -68,6 +70,7 @@
<if test="isStopped != null">is_stopped = #{isStopped},</if>
<if test="stopReason != null">stop_reason = #{stopReason},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="regType != null">reg_type = #{regType},</if>
<if test="doctorId != null">doctor_id = #{doctorId},</if>
<if test="updateTime != null">update_time = #{updateTime}</if>
</set>
@@ -97,12 +100,16 @@
ds.is_stopped AS is_stopped,
ds.stop_reason AS stop_reason,
ds.dept_id AS dept_id,
ds.reg_type AS reg_type,
sp.doctor_id AS doctor_id,
sp.schedule_date AS schedule_date
FROM adm_doctor_schedule ds
LEFT JOIN adm_schedule_pool sp ON sp.schedule_id = ds.id
AND sp.delete_flag = '0'
LEFT JOIN adm_organization org ON ds.dept_id = org.id
AND org.delete_flag = '0'
WHERE ds.dept_id = #{deptId}
AND ds.delete_flag = '0'
AND sp.schedule_date BETWEEN #{startDate}::date AND #{endDate}::date
ORDER BY sp.schedule_date, ds.time_period
</select>
@@ -129,12 +136,16 @@
ds.is_stopped AS is_stopped,
ds.stop_reason AS stop_reason,
ds.dept_id AS dept_id,
ds.reg_type AS reg_type,
sp.doctor_id AS doctor_id,
sp.schedule_date AS schedule_date
FROM adm_doctor_schedule ds
INNER JOIN adm_schedule_pool sp ON sp.schedule_id = ds.id
AND sp.delete_flag = '0'
LEFT JOIN adm_organization org ON ds.dept_id = org.id
AND org.delete_flag = '0'
WHERE sp.schedule_date = #{today}::date
AND ds.delete_flag = '0'
AND (ds.is_stopped = false OR ds.is_stopped IS NULL)
ORDER BY ds.time_period
</select>
@@ -161,12 +172,16 @@
ds.is_stopped AS is_stopped,
ds.stop_reason AS stop_reason,
ds.dept_id AS dept_id,
ds.reg_type AS reg_type,
sp.doctor_id AS doctor_id,
sp.schedule_date AS schedule_date
FROM adm_doctor_schedule ds
INNER JOIN adm_schedule_pool sp ON sp.schedule_id = ds.id
AND sp.delete_flag = '0'
LEFT JOIN adm_organization org ON ds.dept_id = org.id
AND org.delete_flag = '0'
WHERE sp.schedule_date = #{today}::date
AND ds.delete_flag = '0'
AND sp.doctor_id = #{doctorId}
AND (ds.is_stopped = false OR ds.is_stopped IS NULL)
ORDER BY ds.time_period

View File

@@ -76,11 +76,11 @@
T1.quantity_unit,
T1.unit_price,
T1.total_price,
mmr.prescription_no,
mmr.method_code,
mmr.rate_code,
mmr.dose,
mmr.dose_unit_code,
mmr.prescription_no,
mmr.method_code AS method_code,
mmr.rate_code,
mmr.dose,
mmr.dose_unit_code,
T1.bus_no,
T1.entered_date,
T1.account_id,
@@ -93,16 +93,19 @@
T7.med_type_code,
T8.contract_name,
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
WHEN T1.context_enum = #{activity} THEN T2."name"
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"
END AS item_name,
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
WHEN T1.context_enum = #{activity} THEN T2.yb_no
WHEN T1.context_enum = #{medication} THEN T3.yb_no
WHEN T1.context_enum = #{device} THEN T4.yb_no
END AS yb_no,
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
WHEN T1.context_enum = #{activity} THEN T2.id
WHEN T1.context_enum = #{medication} THEN T3.id
WHEN T1.context_enum = #{device} THEN T4.id
@@ -120,6 +123,10 @@
ON T1.context_enum = #{device}
AND T1.product_id = T4.id
AND T4.delete_flag = '0'
LEFT JOIN cli_surgery AS T9
ON T1.product_table = 'cli_surgery'
AND T1.product_id = T9.id
AND T9.delete_flag = '0'
LEFT JOIN fin_payment_reconciliation AS T5
ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ','))
AND T5.delete_flag = '0'
@@ -134,8 +141,9 @@
LEFT JOIN fin_contract AS T8
ON T6.contract_no = T8.bus_no
AND T8.delete_flag = '0'
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
WHERE T1.encounter_id = #{encounterId}
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND T1.service_table = #{worDeviceRequest} AND wdr.delete_flag = '0'
WHERE T1.encounter_id = #{encounterId}
AND T1.status_enum IN (#{planned}
, #{billable}
, #{billed}
@@ -168,11 +176,11 @@
T1.quantity_unit,
T1.unit_price,
T1.total_price,
mmr.prescription_no,
mmr.method_code,
mmr.rate_code,
mmr.dose,
mmr.dose_unit_code,
mmr.prescription_no,
mmr.method_code AS method_code,
mmr.rate_code,
mmr.dose,
mmr.dose_unit_code,
T1.bus_no,
T1.entered_date,
T1.account_id,
@@ -184,36 +192,43 @@
T6.contract_no,
T7.med_type_code,
T8.contract_name,
CASE
WHEN T1.context_enum = #{activity} THEN T2."name"
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"
END AS item_name,
CASE
WHEN T1.context_enum = #{activity} THEN T2.yb_no
WHEN T1.context_enum = #{medication} THEN T3.yb_no
WHEN T1.context_enum = #{device} THEN T4.yb_no
END AS yb_no,
CASE
WHEN T1.context_enum = #{activity} THEN T2.id
WHEN T1.context_enum = #{medication} THEN T3.id
WHEN T1.context_enum = #{device} THEN T4.id
END AS item_id,
T5.tendered_amount AS receivable_amount
FROM adm_charge_item AS T1
LEFT JOIN wor_activity_definition AS T2
ON T1.context_enum = #{activity}
AND T1.product_id = T2.id
AND T2.delete_flag = '0'
LEFT JOIN med_medication_definition AS T3
ON T1.context_enum = #{medication}
AND T1.product_id = T3.id
AND T3.delete_flag = '0'
LEFT JOIN adm_device_definition AS T4
ON T1.context_enum = #{device}
AND T1.product_id = T4.id
AND T4.delete_flag = '0'
LEFT JOIN fin_payment_reconciliation AS T5
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.surgery_name
WHEN T1.context_enum = #{activity} THEN T2."name"
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"
END AS item_name,
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN NULL
WHEN T1.context_enum = #{activity} THEN T2.yb_no
WHEN T1.context_enum = #{medication} THEN T3.yb_no
WHEN T1.context_enum = #{device} THEN T4.yb_no
END AS yb_no,
CASE
WHEN T1.context_enum = #{activity} AND T1.product_table = 'cli_surgery' THEN T9.id
WHEN T1.context_enum = #{activity} THEN T2.id
WHEN T1.context_enum = #{medication} THEN T3.id
WHEN T1.context_enum = #{device} THEN T4.id
END AS item_id,
T5.tendered_amount AS receivable_amount
FROM adm_charge_item AS T1
LEFT JOIN wor_activity_definition AS T2
ON T1.context_enum = #{activity}
AND T1.product_id = T2.id
AND T2.delete_flag = '0'
LEFT JOIN med_medication_definition AS T3
ON T1.context_enum = #{medication}
AND T1.product_id = T3.id
AND T3.delete_flag = '0'
LEFT JOIN adm_device_definition AS T4
ON T1.context_enum = #{device}
AND T1.product_id = T4.id
AND T4.delete_flag = '0'
LEFT JOIN cli_surgery AS T9
ON T1.product_table = 'cli_surgery'
AND T1.product_id = T9.id
AND T9.delete_flag = '0'
LEFT JOIN fin_payment_reconciliation AS T5
ON T1.id::TEXT = ANY(string_to_array(T5.charge_item_ids, ','))
AND T5.delete_flag = '0'
AND T5.status_enum = 1
@@ -227,8 +242,9 @@
LEFT JOIN fin_contract AS T8
ON T6.contract_no = T8.bus_no
AND T8.delete_flag = '0'
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
WHERE T1.encounter_id = #{encounterId}
LEFT JOIN med_medication_request AS mmr ON mmr.id = T1.service_id AND mmr.delete_flag = '0'
LEFT JOIN wor_device_request AS wdr ON wdr.id = T1.service_id AND T1.service_table = #{worDeviceRequest} AND wdr.delete_flag = '0'
WHERE T1.encounter_id = #{encounterId}
AND T1.status_enum IN (#{planned}
, #{billable}
, #{billed}

View File

@@ -194,7 +194,7 @@
SELECT tad.dev_act_id,
tad.quantity,
tad.unit_code,
addf.device_name,
addf.name AS device_name,
addf.py_str AS device_py_str,
addf.wb_str AS device_wb_str
FROM tmp_activity_device tad

View File

@@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="patientAge" column="patient_age" />
<result property="encounterId" column="encounter_id" />
<result property="encounterNo" column="encounter_no" />
<result property="patientCardNo" column="patient_card_no" />
<result property="applyDoctorId" column="apply_doctor_id" />
<result property="applyDoctorName" column="apply_doctor_name" />
<result property="applyDeptId" column="apply_dept_id" />
@@ -79,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
EXTRACT(YEAR FROM AGE(p.birth_date)) as patient_age,
s.encounter_id,
e.bus_no as encounter_no,
pi.identifier_no as patient_card_no,
s.apply_doctor_id,
COALESCE(s.apply_doctor_name, apply_doc.name) as apply_doctor_name,
s.apply_dept_id,
@@ -177,6 +179,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
FROM cli_surgery s
LEFT JOIN adm_patient p ON s.patient_id = p.id
LEFT JOIN adm_encounter e ON s.encounter_id = e.id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON s.patient_id = pi.patient_id
LEFT JOIN adm_operating_room r ON s.operating_room_id = r.id
LEFT JOIN adm_organization ro ON r.organization_id = ro.id
LEFT JOIN adm_organization o ON s.org_id = o.id
@@ -248,6 +262,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
p.birth_date,
<!-- 就诊编号 -->
e.bus_no as encounter_no,
<!-- 就诊卡号 -->
pi.identifier_no as patient_card_no,
<!-- 字典文本使用CASE WHEN避免额外JOIN -->
CASE s.surgery_type_enum
WHEN 1 THEN '门诊手术'
@@ -302,6 +318,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<!-- 只JOIN必要的表患者和就诊 -->
LEFT JOIN adm_patient p ON s.patient_id = p.id
LEFT JOIN adm_encounter e ON s.encounter_id = e.id
<!-- 关联患者标识表获取就诊卡号 -->
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON s.patient_id = pi.patient_id
<where>
s.delete_flag = '0'
<if test="ew.sqlSegment != null and ew.sqlSegment != ''">

View File

@@ -30,13 +30,26 @@
cs.apply_dept_name,
cs.org_id,
o.name AS org_name,
cs.main_surgeon_name AS surgeon_name
cs.main_surgeon_name AS surgeon_name,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.patient_id = cs.patient_id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN sys_tenant st ON st.id = os.tenant_id
LEFT JOIN sys_user su ON su.user_id = os.creator_id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
<where>
<if test="dto.tenantId != null">
AND os.tenant_id = #{dto.tenantId}
@@ -47,8 +60,14 @@
<if test="dto.applyDeptId != null and dto.applyDeptId != ''">
AND cs.apply_dept_id = #{dto.applyDeptId}
</if>
<if test="dto.scheduleDate != null">
AND os.schedule_date = #{dto.scheduleDate}
<if test="dto.scheduleDateStart != null">
AND DATE(os.schedule_date) &gt;= #{dto.scheduleDateStart}
</if>
<if test="dto.scheduleDateEnd != null">
AND DATE(os.schedule_date) &lt;= #{dto.scheduleDateEnd}
</if>
<if test="dto.operCode != null and dto.operCode != ''">
AND os.oper_code LIKE CONCAT('%', #{dto.operCode}, '%')
</if>
AND os.delete_flag = '0'
</where>
@@ -69,13 +88,27 @@
cs.main_surgeon_name AS surgeon_name,
cs.apply_doctor_name AS apply_doctor_name,
drf.create_time AS apply_time,
os.surgery_nature AS surgeryType
os.surgery_nature AS surgeryType,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.patient_id = cs.patient_id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
WHERE os.schedule_id = #{scheduleId}
LIMIT 1
</select>
<!-- 新增手术安排-->
<insert id="addSurgerySchedule" useGeneratedKeys="true" keyProperty="scheduleId">
@@ -116,13 +149,26 @@
cs.apply_dept_name,
cs.org_id,
o.name AS org_name,
cs.main_surgeon_name AS surgeon_name
cs.main_surgeon_name AS surgeon_name,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id
LEFT JOIN cli_surgery cs ON os.patient_id = cs.patient_id
LEFT JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN sys_tenant st ON st.id = os.tenant_id
LEFT JOIN sys_user su ON su.user_id = os.creator_id
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0'
AND identifier_no IS NOT NULL
AND identifier_no != ''
) t
WHERE rn = 1
) pi ON os.patient_id = pi.patient_id
<where>
AND os.delete_flag = '0'
<if test="dto.patientId != null"> AND os.patient_id = #{dto.patientId}</if>
@@ -130,7 +176,8 @@
<if test="dto.applyId != null"> AND os.apply_id = #{dto.applyId}</if>
<if test="dto.operCode != null and dto.operCode != ''"> AND os.oper_code = #{dto.operCode}</if>
<if test="dto.operName != null and dto.operName != ''"> AND os.oper_name LIKE CONCAT('%', #{dto.operName}, '%')</if>
<if test="dto.scheduleDate != null"> AND os.schedule_date = #{dto.scheduleDate}</if>
<if test="dto.scheduleDateStart != null"> AND DATE(os.schedule_date) &gt;= #{dto.scheduleDateStart}</if>
<if test="dto.scheduleDateEnd != null"> AND DATE(os.schedule_date) &lt;= #{dto.scheduleDateEnd}</if>
<if test="dto.orgId != null and dto.orgId != ''"> AND cs.org_id = #{dto.orgId}</if>
<if test="dto.applyDeptId != null and dto.applyDeptId != ''"> AND cs.apply_dept_id = #{dto.applyDeptId}</if>
<if test="dto.patientName != null and dto.patientName != ''"> AND ap.name LIKE CONCAT('%', #{dto.patientName}, '%')</if>
@@ -139,8 +186,19 @@
</select>
<!-- 查询时间段内该手术室是否被占用-->
<select id="isScheduleConflict" resultType="java.lang.Boolean">
SELECT COUNT(*) > 0 FROM op_schedule WHERE room_code = #{surgeryRoomId}
AND entry_time >= #{startTime}
AND end_time &lt; #{endTime}
SELECT COUNT(*) > 0 FROM op_schedule
WHERE room_code = #{surgeryRoomId}
AND entry_time &lt;= #{endTime}
AND end_time >= #{startTime}
AND delete_flag = '0'
</select>
<!-- 检查是否存在重复的手术安排 -->
<select id="existsDuplicateSchedule" resultType="java.lang.Boolean">
SELECT COUNT(*) > 0 FROM op_schedule
WHERE patient_id = #{patientId}
AND oper_code = #{operCode}
AND oper_name = #{operName}
AND delete_flag = '0'
</select>
</mapper>

View File

@@ -31,12 +31,16 @@
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T1.fee_package_id,
T1.sub_item_id,
T2.type_code as item_type_code,
T2.yb_type,
T2.price_code,
T2.price as retail_price,
T4.amount as maximum_retail_price,
T3.name as test_type
T3.name as test_type,
T5.package_name,
T6.name as sub_item_name
FROM wor_activity_definition T1
/* 只JOIN必要的价格表使用INNER JOIN避免笛卡尔积 */
INNER JOIN adm_charge_item_definition T2
@@ -50,6 +54,14 @@
LEFT JOIN adm_charge_item_def_detail T4
ON T4.definition_id = T2.id
AND T4.condition_code = '4'
/* 费用套餐关联 */
LEFT JOIN inspection_basic_information T5
ON T1.fee_package_id = T5.basic_information_id
AND T5.del_flag = false
/* 下级医技类型关联inspection_type 子类) */
LEFT JOIN inspection_type T6
ON T1.sub_item_id = T6.id
AND T6.valid_flag = 1
<where>
T1.delete_flag = '0'
<if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
@@ -66,6 +78,37 @@
ORDER BY T1.id DESC
</select>
<!-- 诊疗目录下拉列表轻量级只查4个字段1个JOIN -->
<select id="getDiagnosisTreatmentSimpleList" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.bus_no,
T1.name,
T1.permitted_unit_code,
T2.price as retail_price
FROM wor_activity_definition T1
INNER JOIN adm_charge_item_definition T2
ON T1.id = T2.instance_id
AND T2.instance_table = 'wor_activity_definition'
WHERE T1.delete_flag = '0'
AND T1.status_enum = #{statusEnum}
AND T1.tenant_id = #{tenantId}
<if test="searchKey != null and searchKey != ''">
AND (
T1.name LIKE '%' || #{searchKey} || '%'
OR T1.bus_no LIKE '%' || #{searchKey} || '%'
OR T1.py_str LIKE '%' || #{searchKey} || '%'
)
</if>
ORDER BY T1.id DESC
<if test="searchKey != null and searchKey != ''">
LIMIT 1500
</if>
<if test="searchKey == null or searchKey == ''">
LIMIT 500
</if>
</select>
<select id="getDiseaseTreatmentOne" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
@@ -105,11 +148,23 @@
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T3.name as test_type
T1.fee_package_id,
T1.sub_item_id,
T3.name as test_type,
T6.package_name,
T7.name as sub_item_name
FROM wor_activity_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
/* 检验类型关联 */
LEFT JOIN inspection_type T3 ON T1.inspection_type_id = T3.id AND T3.valid_flag = 1
/* 费用套餐关联 */
LEFT JOIN inspection_basic_information T6
ON T1.fee_package_id = T6.basic_information_id
AND T6.del_flag = false
/* 下级医技类型关联inspection_type 子类) */
LEFT JOIN inspection_type T7
ON T1.sub_item_id = T7.id
AND T7.valid_flag = 1
<where>
T1.delete_flag = '0'
AND T2.instance_table = 'wor_activity_definition'

View File

@@ -81,7 +81,20 @@
T1.chrgitm_lv,--医保等级
T2.type_code as item_type_code,
T2.yb_type,
T2.price as retail_price,--零售价,
-- 🔧 Bug #220 修复使用COALESCE优先从多个来源获取零售价
-- 优先顺序1. adm_charge_item_definition.price 2. adm_charge_item_def_detail.amount (condition_code='3')
COALESCE(
T2.price,
(SELECT T4.amount
FROM adm_charge_item_definition T5
LEFT JOIN adm_charge_item_def_detail T4 ON T4.definition_id = T5.id
WHERE T4.condition_code = '3'
AND T5.instance_id = T1.id
AND T5.instance_table = 'adm_device_definition'
AND T4.delete_flag = '0'
ORDER BY T4.create_time DESC
LIMIT 1)
) as retail_price,--零售价,
( SELECT T4.amount
FROM adm_charge_item_definition T5
LEFT JOIN adm_charge_item_def_detail T4 ON T4.definition_id = T5.id
@@ -100,10 +113,11 @@
AND T5.instance_table = 'adm_device_definition'
) as maximum_retail_price--最高零售价
FROM adm_device_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
LEFT JOIN adm_charge_item_definition T2
ON T1.id = T2.instance_id
AND T2.instance_table = 'adm_device_definition'
<where>
T1.delete_flag = '0'
AND T2.instance_table = 'adm_device_definition'
</where>
ORDER BY T1.bus_no
@@ -151,7 +165,19 @@
T1.item_max_quantity,
T2.type_code as item_type_code,
T2.yb_type,
T2.price as retail_price,--零售价,
-- 🔧 Bug #220 修复使用COALESCE优先从多个来源获取零售价
COALESCE(
T2.price,
(SELECT T4.amount
FROM adm_charge_item_definition T5
LEFT JOIN adm_charge_item_def_detail T4 ON T4.definition_id = T5.id
WHERE T4.condition_code = '3'
AND T5.instance_id = T1.id
AND T5.instance_table = 'adm_device_definition'
AND T4.delete_flag = '0'
ORDER BY T4.create_time DESC
LIMIT 1)
) as retail_price,--零售价,
( SELECT T4.amount
FROM adm_charge_item_definition T5
LEFT JOIN adm_charge_item_def_detail T4 ON T4.definition_id = T5.id
@@ -170,10 +196,11 @@
AND T5.instance_table = 'adm_device_definition'
) as maximum_retail_price--最高零售价
FROM adm_device_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
LEFT JOIN adm_charge_item_definition T2
ON T1.id = T2.instance_id
AND T2.instance_table = 'adm_device_definition'
<where>
T1.delete_flag = '0'
AND T2.instance_table = 'adm_device_definition'
<if test="id!= null">
AND T1.id = #{id}

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.datadictionary.mapper.LabActivityDefinitionManageMapper">
<!-- 检验项目分页查询(操作 lab_activity_definition 表,无物理外键) -->
<select id="getLabActivityDefinitionPage" parameterType="java.util.Map"
resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T1.fee_package_id,
T1.sub_item_id,
T3.name AS test_type,
T5.package_name,
T6.name AS sub_item_name
FROM lab_activity_definition T1
/* 检验类型关联(逻辑关联,无外键) */
LEFT JOIN inspection_type T3
ON T1.inspection_type_id = T3.id
AND T3.valid_flag = 1
/* 费用套餐关联(逻辑关联,无外键) */
LEFT JOIN inspection_basic_information T5
ON T1.fee_package_id = T5.basic_information_id
AND T5.del_flag = false
/* 下级医技类型关联(逻辑关联,无外键) */
LEFT JOIN inspection_type T6
ON T1.sub_item_id = T6.id
AND T6.valid_flag = 1
<where>
T1.delete_flag = '0'
<if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
<choose>
<when test="ew.customSqlSegment.contains('tenant_id')">
${ew.customSqlSegment.replaceFirst('tenant_id', 'T1.tenant_id').replaceFirst('status_enum', 'T1.status_enum').replaceFirst('WHERE', 'AND')}
</when>
<otherwise>
${ew.customSqlSegment.replaceFirst('status_enum', 'T1.status_enum').replaceFirst('WHERE', 'AND')}
</otherwise>
</choose>
</if>
</where>
ORDER BY T1.id DESC
</select>
<!-- 检验项目详情 -->
<select id="getLabActivityDefinitionOne" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.category_code,
T1.bus_no,
T1.name,
T1.py_str,
T1.wb_str,
T1.type_enum,
T1.permitted_unit_code,
T1.org_id,
T1.location_id,
T1.yb_flag,
T1.yb_no,
T1.yb_match_flag,
T1.status_enum,
T1.body_site_code,
T1.specimen_code,
T1.description_text,
T1.rule_id,
T1.tenant_id,
T1.chrgitm_lv,
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T1.fee_package_id,
T1.sub_item_id,
T3.name AS test_type,
T5.package_name,
T6.name AS sub_item_name
FROM lab_activity_definition T1
LEFT JOIN inspection_type T3
ON T1.inspection_type_id = T3.id
AND T3.valid_flag = 1
LEFT JOIN inspection_basic_information T5
ON T1.fee_package_id = T5.basic_information_id
AND T5.del_flag = false
LEFT JOIN inspection_type T6
ON T1.sub_item_id = T6.id
AND T6.valid_flag = 1
<where>
T1.delete_flag = '0'
<if test="id != null">
AND T1.id = #{id}
</if>
<if test="tenantId != null">
AND T1.tenant_id = #{tenantId}
</if>
</where>
</select>
<!-- 检验项目下拉列表(轻量级) -->
<select id="getLabActivityDefinitionSimpleList" resultType="com.openhis.web.datadictionary.dto.DiagnosisTreatmentDto">
SELECT
T1.id,
T1.bus_no,
T1.name,
T1.permitted_unit_code
FROM lab_activity_definition T1
WHERE T1.delete_flag = '0'
AND T1.status_enum = #{statusEnum}
AND T1.tenant_id = #{tenantId}
<if test="searchKey != null and searchKey != ''">
AND (
T1.name LIKE CONCAT('%', #{searchKey}, '%')
OR T1.bus_no LIKE CONCAT('%', #{searchKey}, '%')
OR T1.py_str LIKE CONCAT('%', #{searchKey}, '%')
)
</if>
ORDER BY T1.id DESC
<if test="searchKey != null and searchKey != ''">
LIMIT 1500
</if>
<if test="searchKey == null or searchKey == ''">
LIMIT 500
</if>
</select>
</mapper>

View File

@@ -98,9 +98,12 @@
LEFT JOIN adm_organization_location AS T6 ON T6.distribution_category_code = t1.category_code AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum}
<if test="pricingFlag ==1">
<if test="pricingFlag == 1">
AND 1 = 2
</if>
<if test="categoryCode != null and categoryCode != ''">
AND t1.category_code = #{categoryCode}
</if>
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
@@ -110,7 +113,9 @@
#{itemId}
</foreach>
</if>
AND T5.instance_table = #{medicationTableName}
<if test="medicationTableName != null">
AND (T5.instance_table = #{medicationTableName} OR T5.instance_table IS NULL)
</if>
)
<if test="adviceTypes.contains(2) or adviceTypes.contains(3)">UNION ALL</if>
</if>
@@ -119,7 +124,7 @@
(SELECT
DISTINCT ON (T1.ID)
T1.tenant_id,
4 AS advice_type,
2 AS advice_type,
T1.bus_no AS bus_no,
T1.category_code AS category_code,
'' AS pharmacology_category_code,
@@ -172,7 +177,9 @@
#{itemId}
</foreach>
</if>
AND T4.instance_table = #{deviceTableName}
<if test="deviceTableName != null">
AND (T4.instance_table = #{deviceTableName} OR T4.instance_table IS NULL)
</if>
AND T1.status_enum = #{statusEnum}
)
<if test="adviceTypes.contains(3)">UNION ALL</if>
@@ -226,7 +233,7 @@
'' AS manufacturer,
T2.ID AS charge_item_definition_id,
T2.instance_table AS advice_table_name,
T3.organization_id AS position_id,
COALESCE(T3.organization_id, T1.org_id) AS position_id,
0 AS restricted_flag,
'' AS restricted_scope,
'' AS dosage_instruction,
@@ -238,7 +245,11 @@
AND T3.organization_id = #{organizationId}
</if>
WHERE t1.delete_flag = '0'
AND (t1.pricing_flag = 1 OR t1.pricing_flag IS NULL)
<!-- 🔧 Bug #215 修复:当 pricingFlag 为 null 时不添加过滤条件,
避免门诊划价场景查询诊疗项目 (adviceType=3) 返回空结果 -->
<if test="pricingFlag != null">
AND (t1.pricing_flag = #{pricingFlag} OR t1.pricing_flag IS NULL)
</if>
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
@@ -460,6 +471,7 @@
T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id,
T4.unit_price AS unit_price,
T4.total_price AS total_price,
T4.status_enum AS charge_status,
al.id AS position_id,
@@ -469,7 +481,9 @@
ccd.name AS condition_definition_name,
T1.sort_number AS sort_number,
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_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'
@@ -512,6 +526,7 @@
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
al.id AS position_id,
@@ -521,7 +536,9 @@
'' AS condition_definition_name,
99 AS sort_number,
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_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'
@@ -539,7 +556,7 @@
AND T1.refund_device_id IS NULL
ORDER BY T1.status_enum)
UNION ALL
(SELECT 3 AS advice_type,
(SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type,
T1.id AS request_id,
T1.id || '-3' AS unique_key,
'' AS prescription_no,
@@ -550,18 +567,19 @@
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
COALESCE(T2.NAME, CASE WHEN T1.content_json IS NOT NULL AND T1.content_json != '' THEN T1.content_json::json->>'adviceName' ELSE NULL END) AS advice_name,
COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName', T1.content_json::jsonb->>'adviceName') 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,
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.unit_price AS unit_price,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
ao.id AS position_id,
ao.name AS position_name,
@@ -569,9 +587,11 @@
1 AS part_percent,
'' AS condition_definition_name,
99 AS sort_number,
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum
FROM wor_service_request AS T1
T1.based_on_id AS based_on_id,
T1.category_enum AS category_enum,
T1.encounter_id AS encounter_id,
T1.patient_id AS patient_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'

View File

@@ -4,29 +4,54 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.web.doctorstation.mapper.DoctorStationLabApplyMapper">
<!-- 根据申请单号查询检验申请单 -->
<!-- 根据申请单号查询检验申请单(返回完整字段) -->
<select id="getInspectionApplyByApplyNo" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto">
SELECT apply_no AS applyNo
SELECT
id AS applicationId,
apply_no AS applyNo,
patient_id AS patientId,
patient_name AS patientName,
medicalrecord_number AS medicalrecordNumber,
natureof_cost AS natureofCost,
visit_no AS visitNo,
apply_dept_code AS applyDeptCode,
apply_department AS applyDepartment,
apply_doc_code AS applyDocCode,
apply_doc_name AS applyDocName,
apply_time AS applyTime,
clinic_diag AS clinicDiag,
clinic_desc AS clinicDesc,
contraindication AS contraindication,
medical_history_summary AS medicalHistorySummary,
purposeof_inspection AS purposeofInspection,
physical_examination AS physicalExamination,
inspection_item AS inspectionItem,
specimen_type_code AS specimenTypeCode,
specimen_name AS specimenName,
priority_code AS priorityCode,
apply_status AS applyStatus,
apply_remark AS applyRemark,
create_time AS createTime,
operator_id AS operatorId,
create_by AS createBy,
tenant_id AS tenantId
FROM lab_apply
WHERE apply_no = #{applyNo}
AND delete_flag = '0'
</select>
<!-- 分页查询检验申请单列表根据就诊ID查询按申请时间降序
encounterId: 就诊ID作为查询条件查出患者idpatient_id再以患者id对申请单进行查询
对申请单表(lab_apply)、申请单明细表(lab_apply_item)和就诊表(adm_encounter)进行联合查询(申请单号,检验项目,申请医生,申请单优先级码,申请单状态,金额)-->
直接查询申请单表,不关联明细表,避免重复记录-->
<select id="getInspectionApplyListPage" resultType="com.openhis.web.doctorstation.dto.DoctorStationLabApplyDto">
SELECT t1.apply_no AS applyNo,
t1.inspection_item AS inspectionItem,
SELECT t1.id AS applicationId,
t1.apply_no AS applyNo,
t1.inspection_item AS itemName,
t1.apply_doc_name AS applyDocName,
t1.priority_code AS priorityCode,
t2.item_name AS itemName,
t1.apply_status AS applyStatus,
t2.item_amount AS itemAmount
t1.apply_remark AS applyRemark
FROM lab_apply AS t1
INNER JOIN adm_encounter AS t3 ON t1.patient_id::bigint = t3.patient_id
LEFT JOIN lab_apply_item AS t2
ON t1.apply_no = t2.apply_no
WHERE t1.delete_flag = '0'
AND t3.id = #{encounterId}
ORDER BY t1.apply_time DESC

View File

@@ -140,7 +140,7 @@
ON LEFT (T3.bus_no, 6) = T11.bus_no
WHERE T2.id = #{encounterId}
AND T2.class_enum = #{encClassEnum}
AND T3.class_enum = #{orgClassEnum}
AND T3.class_enum = CAST(#{orgClassEnum} AS VARCHAR)
AND T1.delete_flag = '0'
LIMIT 1
</select>

View File

@@ -203,6 +203,7 @@
T1.dose AS dose,
T1.dose_unit_code AS dose_unit_code,
T4.id AS charge_item_id,
T4.unit_price AS unit_price,
T4.total_price AS total_price,
T4.status_enum AS charge_status,
al.id AS position_id,
@@ -254,6 +255,7 @@
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
al.id AS position_id,
@@ -281,7 +283,7 @@
AND T1.refund_device_id IS NULL
ORDER BY T1.status_enum)
UNION ALL
(SELECT 3 AS advice_type,
(SELECT CASE WHEN T1.category_enum = 4 THEN 6 ELSE COALESCE(T1.category_enum, 3) END AS advice_type,
T1.id AS request_id,
T1.id || '-3' AS unique_key,
T1.requester_id AS requester_id,
@@ -291,7 +293,7 @@
null AS skin_test_flag,
null AS inject_flag,
null AS group_id,
T2.NAME AS advice_name,
COALESCE(T2.NAME, T1.content_json::jsonb->>'surgeryName') AS advice_name,
'' AS volume,
'' AS lot_number,
T1.quantity AS quantity,
@@ -302,6 +304,7 @@
NULL AS dose,
'' AS dose_unit_code,
T3.id AS charge_item_id,
T3.unit_price AS unit_price,
T3.total_price AS total_price,
T3.status_enum AS charge_status,
ao.id AS position_id,
@@ -309,7 +312,7 @@
null AS dispense_per_duration,
1 AS part_percent,
'' AS condition_definition_name,
T1.therapy_enum AS therapyEnum,
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

View File

@@ -26,12 +26,13 @@
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">
SELECT wsr.quantity,
wsr.unit_code,
wad.NAME AS advice_name,
COALESCE(wad.NAME, wsr.content_json::jsonb->>'surgeryName') AS advice_name,
aci.total_price
FROM wor_service_request AS wsr
LEFT JOIN wor_activity_definition AS wad ON wad.ID = wsr.activity_id
AND wad.delete_flag = '0'
LEFT JOIN adm_charge_item AS aci ON aci.service_id = wsr.ID
AND aci.service_table = 'wor_service_request'
AND aci.delete_flag = '0'
WHERE wsr.delete_flag = '0'
AND wsr.prescription_no = #{prescriptionNo}
@@ -63,6 +64,11 @@
<result column="apply_dept_name" property="applyDeptName"/>
<result column="encounter_id" property="encounterId"/>
<result column="surgery_type_enum" property="surgeryTypeEnum"/>
<result column="fee_type" property="feeType"/>
<result column="anesthesia_type_enum" property="anesthesiaTypeEnum"/>
<result column="incision_level" property="incisionLevel"/>
<result column="surgery_level" property="surgeryLevel"/>
<result column="identifier_no" property="identifierNo"/>
</resultMap>
<!-- 分页查询申请单 -->
@@ -83,12 +89,33 @@
cs.main_surgeon_name,
cs.apply_dept_id,
cs.apply_dept_name,
cs.surgery_type_enum
cs.surgery_type_enum,
cs.anesthesia_type_enum,
cs.incision_level,
cs.surgery_level,
fc.contract_name AS fee_type,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifier_no
FROM doc_request_form drf
LEFT JOIN cli_surgery cs ON cs.surgery_no = drf.prescription_no
LEFT JOIN adm_patient ap ON ap.id = cs.patient_id
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
LEFT JOIN op_schedule os ON os.apply_id = drf.id AND os.delete_flag = '0'
LEFT JOIN (
SELECT patient_id, identifier_no
FROM (
SELECT patient_id, identifier_no,
ROW_NUMBER() OVER (PARTITION BY patient_id ORDER BY create_time ASC) AS rn
FROM adm_patient_identifier
WHERE delete_flag = '0' AND identifier_no IS NOT NULL AND identifier_no != ''
) t
WHERE rn = 1
) pi ON ap.id = pi.patient_id
<where>
<if test="requestFormDto.surgeryNo != null and requestFormDto.surgeryNo != ''">
AND drf.prescription_no LIKE CONCAT('%', #{requestFormDto.surgeryNo}, '%')
</if>
<if test="requestFormDto.applyTimeStart != null">
AND drf.create_time >= #{requestFormDto.applyTimeStart}
</if>
@@ -102,6 +129,7 @@
AND cs.apply_dept_id = #{requestFormDto.applyDeptId}
</if>
AND drf.delete_flag = '0'
AND os.schedule_id IS NULL
</where>
ORDER BY drf.create_time DESC
</select>

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