Compare commits

..

143 Commits

Author SHA1 Message Date
wangjian963
8e5c4d634f 823 【住院护士站-出院管理】出院“待取药/待退药”列表中尚未发药的药品错误提前显示发药时间和发药人,且发药人数据源错误 2026-06-29 17:15:43 +08:00
wangjian963
8ca4571386 824 【住院医生工作站-住院病历】选择一名患者时,保存住院病历无法保存成功,点击保存按钮无响应
838 【住院医生工作站-诊断录入】引入/选择门诊入院诊断时,诊断类别列错误显示为字典代码“11”而非文本“门/急诊诊断”
840 【住院医生工作站-临床医嘱】医嘱新增/保存/签发后,医嘱类型列的内容显示为空白
2026-06-29 16:39:24 +08:00
wangjian963
d68f6d646b refactor(adviceProcess): 调整大数除法判断的代码结构
将取余判断的逻辑嵌套到数量大于等于单位占比的分支内,优化代码可读性
2026-06-29 15:02:16 +08:00
wangjian963
e1c3aebacd 822 【住院护士站-医嘱校对】药品医嘱“总量”数据换算及单位错误,导致未校对列表总量放大(医生开立2盒显示为30盒 2026-06-29 14:59:53 +08:00
wangjian963
8de3c9f4c6 fix(821 【住院护士站-出院管理】待处理执行单列表过滤规则错误,错误引入了药品(中成药)医嘱): 调整住院护理站执行单查询逻辑
移除原药品医嘱查询逻辑,新增注释说明待处理执行单仅展示非药品医嘱,药品相关查询由单独方法处理
2026-06-29 14:17:05 +08:00
42c6bdba76 Merge remote-tracking branch 'origin/develop' into develop 2026-06-29 13:13:23 +08:00
wangjian963
0200f444b5 i18n: add missing translation terms and internationalize consultation confirmation page
1. add sequence translation for viVN, enUS, zhCN locales
2. replace hardcoded table headers in consultation confirmation page with i18n keys
3. add new translation entries for consultation confirmation module
2026-06-29 12:08:27 +08:00
d2b4a6d229 docs(markdown): 添加 HealthLink-HIS 系统介绍相关文章
- 新增三甲评审HIS系统选择指南文章
- 新增HealthLink-HIS产品推广介绍文章
- 新增HIS系统选型对比分析文章
- 新增HealthLink-HIS模块化报价方案文档
- 涵盖技术架构、功能对比、价格分析等内容
2026-06-29 12:01:44 +08:00
3856b81934 docs(markdown): 删除多个HIS系统相关文章文档
- 移除 healthlink-his-grade3a-comparison-article.md 文件
- 移除 healthlink-his-promotion-article.md 文件
- 移除 HEALTHLINK_HIS_COMPARE_ARTICLE.md 文件
- 移除 HEALTHLINK_HIS_PRICING_PROPOSAL_0.2.md 文件
- 清理医院信息系统介绍、推广、对比及报价相关内容
- 删除Markdown格式的软文和方案文档
2026-06-29 12:01:19 +08:00
wangjian963
a0696d382c i18n: 完善会诊管理模块的中文国际化文案
1. 新增会诊申请和会诊确认的全量中文翻译词条
2. 将页面内的国际化引用从旧的consultation路径切换到新的consultationMgmt路径
3. 同步更新了原有医嘱相关的国际化文案为中文
2026-06-29 11:52:03 +08:00
wangjian963
be93a77b64 fix(医嘱核对): 修正截止时间筛选逻辑与默认值
1. 将前端默认截止时间从当天00:00:00改为23:59:59
2. 重构后端筛选条件,改为当日0点到指定截止时间的范围查询
3. 移除冗余的空安全终止时间判断逻辑
2026-06-29 11:11:31 +08:00
wangjian963
79478c6780 refactor(doctorstation): 调整国际化key命名并更新页面引用
1. 统一检查、检验、处方模块的国际化前缀为doctorstationExam、doctorstationInspection和doctorstationPrescription
2. 更新所有相关vue文件中的t函数调用路径,适配新的国际化key命名
3. 补充完善了三个模块的多语言文案(zhCN、enUS、viVN)
2026-06-29 10:18:10 +08:00
0649443845 feat(i18n): 添加国际化(i18n)基础设施和多语言支持
- 新增 I18nUtils 工具类提供多语言消息获取功能
- 创建多语言技术方案设计文档 MULTILANG_I18N_DESIGN.md
- 编写国际化战略文章 HEALTHLINK_HIS_INTERNATIONALIZATION_STRATEGY.md
- 添加国际化战略概述文档 HEALTHLINK_HIS_INTL_STRATEGY.md
- 实现基于 MessageSource 的多语言消息处理机制
- 设计支持中英越三语的国际化架构方案
- 规划前端 vue-i18n 和后端 MessageSource 集成方案
- 定义数据库多语言表结构支持菜单和字典国际化
- 制定硬编码消息迁移策略和实施计划
- 建立多语言工作量评估和风险应对措施
2026-06-29 10:04:06 +08:00
dd9f1cae5a feat(i18n): 添加国际化(i18n)基础设施和多语言支持
- 新增 I18nUtils 工具类提供多语言消息获取功能
- 创建多语言技术方案设计文档 MULTILANG_I18N_DESIGN.md
- 编写国际化战略文章 HEALTHLINK_HIS_INTERNATIONALIZATION_STRATEGY.md
- 添加国际化战略概述文档 HEALTHLINK_HIS_INTL_STRATEGY.md
- 实现基于 MessageSource 的多语言消息处理机制
- 设计支持中英越三语的国际化架构方案
- 规划前端 vue-i18n 和后端 MessageSource 集成方案
- 定义数据库多语言表结构支持菜单和字典国际化
- 制定硬编码消息迁移策略和实施计划
- 建立多语言工作量评估和风险应对措施
2026-06-29 06:10:03 +08:00
9081f1cfeb Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	healthlink-his-ui/src/views/inpatientDoctor/home/components/diagnosis/chineseMedicineDialog.vue
#	healthlink-his-ui/src/views/inpatientDoctor/home/components/diagnosis/diagnosis.vue
2026-06-28 07:04:03 +08:00
27273dbb57 feat(i18n): 添加国际化(i18n)基础设施和多语言支持
- 新增 I18nUtils 工具类提供多语言消息获取功能
- 创建多语言技术方案设计文档 MULTILANG_I18N_DESIGN.md
- 编写国际化战略文章 HEALTHLINK_HIS_INTERNATIONALIZATION_STRATEGY.md
- 添加国际化战略概述文档 HEALTHLINK_HIS_INTL_STRATEGY.md
- 实现基于 MessageSource 的多语言消息处理机制
- 设计支持中英越三语的国际化架构方案
- 规划前端 vue-i18n 和后端 MessageSource 集成方案
- 定义数据库多语言表结构支持菜单和字典国际化
- 制定硬编码消息迁移策略和实施计划
- 建立多语言工作量评估和风险应对措施
2026-06-28 07:01:58 +08:00
83f340b6bb feat(i18n): 实现门诊增强和门诊财务国际化功能
- 在门诊增强页面添加表格列的国际化标签
- 在门诊增强页面添加操作按钮的国际化文本
- 在门诊财务日结结算页面实现表单字段的国际化
- 在门诊财务日结结算页面实现表格列标题的国际化
- 在门诊财务日结结算页面添加操作按钮的国际化
- 集成 vue-i18n 并在组件中使用国际化方法
- 更新日结详情提示消息为国际化文本
- 实现导出文件名和成功消息的国际化
2026-06-28 06:58:53 +08:00
24d2e482c7 feat(i18n): 实现门诊增强和门诊财务国际化功能
- 在门诊增强页面添加表格列的国际化标签
- 在门诊增强页面添加操作按钮的国际化文本
- 在门诊财务日结结算页面实现表单字段的国际化
- 在门诊财务日结结算页面实现表格列标题的国际化
- 在门诊财务日结结算页面添加操作按钮的国际化
- 集成 vue-i18n 并在组件中使用国际化方法
- 更新日结详情提示消息为国际化文本
- 实现导出文件名和成功消息的国际化
2026-06-28 06:56:04 +08:00
wangjian963
275a76c2d7 820 【住院护士站-医嘱管理】“文字型”医嘱在护士站“医嘱校对”及“医嘱执行”页面不显示医嘱内容(内容为空白 2026-06-26 17:46:30 +08:00
wangjian963
12d53eb6b8 843 【门诊收费-收费项目】待收费患者的收费明细列表混入“已收费”项目,且错误计入合计金额 2026-06-26 16:59:54 +08:00
wangjian963
659ccab18b Merge remote-tracking branch 'origin/develop' into develop 2026-06-26 16:56:39 +08:00
Ranyunqiao
a9de9ee822 bug 628 804 806 818 2026-06-26 16:55:52 +08:00
wangjian963
bbe9bd7ef5 830 【收费工作站-住院登记】选择一名患者进行登记时入院病区下拉框无数据回显
819 【 门诊收费工作站】打开收费详情查询会出现报错:No static resource payment/bill/page for request '/healthlink-his/payment/bill/page'.
2026-06-26 16:38:35 +08:00
b278ad92b2 feat(i18n): 实现门诊增强和门诊财务国际化功能
- 在门诊增强页面添加表格列的国际化标签
- 在门诊增强页面添加操作按钮的国际化文本
- 在门诊财务日结结算页面实现表单字段的国际化
- 在门诊财务日结结算页面实现表格列标题的国际化
- 在门诊财务日结结算页面添加操作按钮的国际化
- 集成 vue-i18n 并在组件中使用国际化方法
- 更新日结详情提示消息为国际化文本
- 实现导出文件名和成功消息的国际化
2026-06-26 16:19:53 +08:00
24dc16b8d1 refactor(ybmock): 重构医保模拟接口并更新国际化配置
- 将医保模拟接口从通用路由改为具体功能路由
- 新增签到、取消门诊登记、预结算等功能接口
- 统一返回格式为 code/message/result 结构
- 移除旧版医保接口路由兼容处理
- 更新前端国际化配置文件中的医保相关词条
- 删除重复的无数据提示词条并补充新的字段翻译
- 移除药房模块独立词条合并至通用配置中
- 新增住院管理模块的完整国际化词条配置
2026-06-26 16:06:15 +08:00
284c2d7956 Merge remote-tracking branch 'origin/develop' into develop 2026-06-26 16:05:00 +08:00
70844b07ef feat(i18n): migrate drug, medicineStorage, pharmacystockalert modules to vue-i18n (402+ keys) 2026-06-26 15:05:07 +08:00
wangjian963
a71d818ffe 831
【住院管理-住院护士增强】打开TPR表会有报错信息:Incorrect result size: expected 1, actual 0
832 【住院护士站-住院记账】在科下患者的划价确费下的患者医嘱列表的选择了项目批量撤销,但还显示请先选择要撤销的划价项目
2026-06-26 14:23:14 +08:00
9650cc4d84 feat(bodyStructure): 实现身体部位管理页面国际化支持
- 将新增、删除、导出、刷新等按钮文本替换为国际化标签
- 将表格列标题如部位名称、拼音、五笔拼音等替换为国际化标签
- 将表单输入框占位符和标签替换为国际化标签
- 添加vue-i18n依赖并配置国际化函数
- 将验证规则中的提示消息替换为国际化消息
- 将操作成功提示消息替换为国际化消息
- 将页面标题如添加身体部位、编辑部位等替换为国际化标签
2026-06-26 13:03:13 +08:00
8f20634b46 feat(i18n): 实现病例模板统计模块国际化功能
- 将病例模板统计相关界面文本替换为国际化标签
- 添加 vue-i18n 依赖并配置翻译函数
- 实现表单验证消息的国际化显示
- 将按钮、表格列标题等界面元素转换为多语言支持
- 重构表单验证规则以支持动态国际化消息
- 更新常用诊断、耗材绑定等其他模块的国际化内容
2026-06-26 13:02:51 +08:00
1d4c168787 Merge remote-tracking branch 'origin/develop' into develop 2026-06-26 13:00:28 +08:00
wangjian963
a679fc1700 fix(ui): 修复剩余 TS strict 编译错误及类型定义
- EditableTable.d.ts: fixed 类型调整,新增 extraprops
  - FormItem.d.ts: 新增 disabled、onClick 属性
  - EditableTable/Form/FormItem/FormLayout: 添加参数类型注解
  - DataDashboard: screenData 类型修正 + 隐式 any 修复
  - api/datacollection: 新增 index.d.ts 声明文件
  - patientList/receipt: 补充缺失函数声明
2026-06-26 12:10:03 +08:00
Ranyunqiao
3236375154 bug 785 799 800 2026-06-26 11:49:52 +08:00
wangjian963
8eb2c1e0e2 fix(ui): 修复 TypeScript strict 编译错误及运行时 Array.join 崩溃
- 修复 console.error 包装器: String() 包裹 + try-catch 防止浏览器原生格式化崩溃
  - 修复 empienhanced/merge 模板中 && → &&
  - 修复 bedAllocation: ChangeBedDialog 组件缺失导致 patchAttr 渲染崩溃
  - 修复 inOut 组件: 添加隐式 any 类型注解, 修正 OptionItem 接口定义
  - 新增 api.d.ts: api.js 的 TypeScript 类型声明
2026-06-26 11:42:55 +08:00
e035a137d1 feat(i18n): merge pending translations, add missing UI terms, migrate features pages, fix defineProps error 2026-06-26 10:48:41 +08:00
f5c6007c37 i18n(ui): 国际化界面文本替换
- 替换费用配置页面的所有静态文本为国际化标签
- 替换组织机构管理页面的表单标签和按钮文本
- 替换病房管理页面的列标题和操作按钮
- 替换设备对照对话框的标题和占位符文本
- 替换诊断治疗对照对话框的标题和字段标签
- 替换CDSS告警页面的消息提示文本
- 在相关组件中引入vue-i18n并创建翻译实例
2026-06-26 09:21:39 +08:00
905d9c7ffc i18n(ui): 国际化界面文本替换
- 替换费用配置页面的所有静态文本为国际化标签
- 替换组织机构管理页面的表单标签和按钮文本
- 替换病房管理页面的列标题和操作按钮
- 替换设备对照对话框的标题和占位符文本
- 替换诊断治疗对照对话框的标题和字段标签
- 替换CDSS告警页面的消息提示文本
- 在相关组件中引入vue-i18n并创建翻译实例
2026-06-26 09:06:46 +08:00
2e1112b902 feat(i18n): 实现国际化支持并优化代码结构
- 在i18n模块中实现懒加载本地化配置,限制加载指定语言文件
- 为床位管理、合同管理、客户管理等界面组件添加国际化标签和消息
- 集成vue-i18n在多个组件中添加翻译功能和多语言支持
- 更新CDSS告警和规则管理界面以支持多语言显示
- 优化store模块导入结构,添加pinia依赖注入
- 实现表单验证消息和操作提示的国际化处理
2026-06-26 09:04:11 +08:00
11f1263157 Merge remote-tracking branch 'origin/develop' into develop 2026-06-26 08:58:35 +08:00
2abf38e14d feat(i18n): zero Chinese remaining in enUS/viVN locales + backend error msg translation + dict auto-translate 2026-06-25 22:53:51 +08:00
4e2e11292f feat(i18n): translate backend error messages, dict labels auto-translate, add 150+ medical dict terms 2026-06-25 21:15:44 +08:00
7c7b02225d feat(i18n): add department+table header translations, fix duplicate key error, translate system title 2026-06-25 20:59:26 +08:00
wangjian963
ea9acac589 778 【门诊医生站-检验】点击选中行检验申请单,下方“申请单”表单和项目选择区域未回显/绑定对应数据 2026-06-25 17:56:03 +08:00
wangjian963
d786b2595a Merge remote-tracking branch 'origin/develop' into develop 2026-06-25 17:13:01 +08:00
wangjian963
6925b93f73 767 【门诊医生工作站-检验】申请单下的就诊卡号无法填写 2026-06-25 17:12:33 +08:00
Ranyunqiao
f90e68db9c bug 815 816 817 2026-06-25 17:08:37 +08:00
2c6a2bef33 Merge remote-tracking branch 'origin/develop' into develop 2026-06-25 16:46:41 +08:00
8d5871ca39 feat(i18n): translate system title, tenant name, shorten English translation, add tooltip 2026-06-25 16:36:18 +08:00
wangjian963
987fa8bc63 734 【住院医生站-临床医嘱】医嘱开具时,录入框右侧缺少最小单位与剂量单位的动态换算公式说明(如:2袋 = 30 g 2026-06-25 16:25:50 +08:00
wangjian963
4d7a2db4df 729 【住院护士站-入出转管理】待转科列表“入院病区/入院病房”下拉筛选项无数据,未正确读取转科申请数据 2026-06-25 16:13:28 +08:00
aa011c3721 feat(i18n): fix menu auto-translation fallback + expand dictionary with 200+ menu terms 2026-06-25 16:12:28 +08:00
05a59f2884 feat(i18n): add offline auto-translate plugin with 200+ common medical terms dictionary 2026-06-25 16:02:35 +08:00
d8c5269ab9 feat(i18n): complete all 475 menu mappings - 100% coverage 2026-06-25 15:32:25 +08:00
9c06666e5b feat(i18n): complete all 639 menu mappings from database + add nav translations 2026-06-25 15:30:56 +08:00
00fa8f3af9 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	healthlink-his-ui/src/views/inpatientDoctor/home/components/order/index.vue
2026-06-25 15:21:27 +08:00
0685b7eb8a feat(i18n): fix Element Plus locale switching, add TagsView i18n, merge maintain_keys, add tagsView translations 2026-06-25 15:12:44 +08:00
5c7b4c45e6 feat(i18n): add menu title mapping for sidebar translation + fix login dropdown overflow + add 262 nav keys 2026-06-25 14:38:14 +08:00
wangjian963
b64b3c96df 718 【业务逻辑缺陷】医生端点击“停嘱”后医嘱直接变更为“已停止”,未流转至护士端进行停止核对 2026-06-25 14:33:29 +08:00
wangjian963
6bf48194c4 714 【住院护士站-医嘱校对】住院护士站-医嘱核对界面:缺少“截止时间”过滤条件且默认单选未选中“全部” 2026-06-25 14:19:50 +08:00
69659d492c Merge remote-tracking branch 'origin/develop' into develop 2026-06-25 13:47:21 +08:00
wangjian963
c3765cac80 698 [收费工作站-住院登记-已登记入院] 检索维度单一,且关键归档信息缺失(需增设检索条件与补充列表字段展示) 2026-06-25 13:22:38 +08:00
wangjian963
6ffa47bf5e 689 [住院管理-住院发退药] 发药汇总单界面布局被挤压、发放状态文案不符及右侧详情联动无数据 2026-06-25 11:59:39 +08:00
6a4f65f45f feat(i18n): migrate emergency, infection control, audit log pages to vue-i18n (~700 keys) 2026-06-25 11:43:58 +08:00
48c42ac3c2 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	healthlink-his-ui/src/views/charge/cliniccharge/index.vue
#	healthlink-his-ui/src/views/inpatientDoctor/home/components/diagnosis/chineseMedicineDialog.vue
#	healthlink-his-ui/src/views/inpatientDoctor/home/components/diagnosis/diagnosis.vue
#	healthlink-his-ui/src/views/pharmacymanagement/westernmedicine/index.vue
2026-06-25 11:16:15 +08:00
Ranyunqiao
b9ae2b877a bug 810 811 812 813 2026-06-25 10:28:23 +08:00
25502820db feat(i18n): migrate lab, inspection, report, medication, data dictionary pages to vue-i18n (676 keys) 2026-06-25 10:13:04 +08:00
84529b9f01 feat(i18n): migrate patient management and inpatient doctor station (42+ files, 787+39 keys) 2026-06-25 09:04:04 +08:00
92079e1392 feat(i18n): migrate EMR, surgery safety, surgical schedule, surgery manage, operating room, preop manage to vue-i18n (~1000 keys) 2026-06-25 00:22:45 +08:00
24ea1c9e1a feat(i18n): migrate nursing, inpatient, mobile nursing pages + billing remaining components to vue-i18n 2026-06-24 22:37:08 +08:00
1a0e6aabb4 feat(i18n): migrate billing, triage, pharmacy pages to vue-i18n (206+120+60 keys) 2026-06-24 22:04:53 +08:00
wangjian963
c76a165b81 688 [住院发退药-发药明细单] 患者列表布局挤压导致内容显示不全,且多条件组合检索(患者信息/发药状态/药品分类)失效
布局挤压:左侧患者列表 width: 25% 无法容纳 440px 列宽,年龄列被遮挡
2026-06-24 17:45:43 +08:00
wangjian963
1cb87d4e4b 672 [门诊医生站-诊断] 新增中医诊断保存后,列表中“发病日期”、“诊断日期”和“医生”字段显示为空 2026-06-24 17:07:43 +08:00
wangjian963
8c23695c1f chore: .idea/ 解除 git 追踪 2026-06-24 16:55:12 +08:00
wangjian963
0a4e5b93db 666 [门诊-发药管理] 药品已完成收费但“门诊发药”模块无法检索到患者信息,导致无法实现发药逻辑
【门诊发药 - westernmedicine/index.vue】
  - 修复 vxe-table v4 @cell-click 事件包装问题:handleCurrentChange
    参数从 row 改为 params.row || params,解决 encounterId 始终为
    undefined 导致切换患者时右侧数据不变的 bug
  - 添加竞态保护:getMedicineList 中比对 currentRow.encounterId 与
    requestedEncounterId,防止快速切换患者时旧请求覆盖新数据
  - 切换患者时立即清空 medicineInfoList/medicineTotalPrice,避免
    闪现上一患者内容
  - 三个数据加载分支统一添加 .catch() + .finally() 确保 loading
    状态正确关闭
2026-06-24 16:43:56 +08:00
Ranyunqiao
2ba26594e3 bug 808 2026-06-24 16:16:51 +08:00
d0f2e21af5 Merge remote-tracking branch 'origin/develop' into develop 2026-06-24 15:42:56 +08:00
ded899d45c feat(i18n): migrate menu, dict type, registration, doctor station, common components to vue-i18n 2026-06-24 15:38:28 +08:00
Ranyunqiao
74cf599ea7 需求111 住院护士站-》护理记录维护权限 修改成功 2026-06-24 14:46:15 +08:00
88912d26bf Merge remote-tracking branch 'origin/develop' into develop 2026-06-24 14:23:50 +08:00
Ranyunqiao
77e4286fde bug 809 2026-06-24 14:20:27 +08:00
wangjian963
1a6cd9af9b 588 [住院医生工作站-临床医嘱] 新增无“文字”医嘱类型,系统要实现联动切换至专用展开式填写面板,且缺失频次、执行科室、开始时间等核心字段录入
消除OrderForm ESLint/TS报错
2026-06-24 14:07:38 +08:00
8434db6e13 fix(i18n): add missing deptData key to enUS/viVN locale files 2026-06-24 13:53:34 +08:00
Ranyunqiao
20dade7bf0 bug 800 802 803 804 805 2026-06-24 13:45:20 +08:00
f4fe7fe873 feat(i18n): add dict management multilang UI with en/vi input fields 2026-06-24 13:42:12 +08:00
822414c228 feat(i18n): migrate login, dashboard, navbar, sidebar, user/role pages to vue-i18n with language switcher on login page 2026-06-24 13:33:44 +08:00
fbb7f8215e feat(i18n): add Chinese extraction script (found 1720 unique texts from 1258 files) 2026-06-24 12:28:38 +08:00
6f1a00c9c9 feat(i18n): add vue-i18n v11 with zhCN/enUS/viVN locale files 2026-06-24 12:20:04 +08:00
cc056d19ce fix(i18n): rename Flyway migration to V111 per naming convention 2026-06-24 12:10:29 +08:00
20bd4a4b1a fix(i18n): rename Flyway migration to V111__add_dict_multilang_columns per convention 2026-06-24 12:09:16 +08:00
wangjian963
9640ef7d39 Merge remote-tracking branch 'origin/develop' into develop 2026-06-24 11:55:13 +08:00
wangjian963
acbcd6eacf fix: 修复菜单parentId为NULL时获取路由NPE
路由构建时 SysMenu.getParentId() 可能返回 NULL(数据库 parent_id 为 NULL),
  在 buildMenus/getRouterPath/getComponent/isMenuFrame/isParentView/getChildList
  中直接调用 .intValue()/.longValue() 触发自动拆箱 NPE,导致前端路由加载失败。
2026-06-24 11:54:26 +08:00
442de5149a feat(i18n): add backend i18n properties, Flyway migration, and dictionary multi-language support 2026-06-24 11:49:26 +08:00
Ranyunqiao
5f9e535928 Merge remote-tracking branch 'origin/develop' into develop 2026-06-24 11:43:52 +08:00
Ranyunqiao
3f6a23a9e6 bug 588 628 642 670 2026-06-24 11:43:19 +08:00
wangjian963
8b1185930e 675 [门诊医生站-检查申请] “检查方法”字段缺少必填标识却执行了强校验逻辑 2026-06-24 11:38:08 +08:00
wangjian963
8845fdcd70 fix(doctorstation): 优化医嘱tab页诊断显示、输入框焦点及数据懒加载
- 诊断下拉框改为只读显示,仅显示主诊断,移除无主诊断时的兜底逻辑
  - 编辑区所有数字输入框(el-input-number)改为el-input,修复执行次数等输入框无法聚焦问题
  - 医嘱数据加载改为切到医嘱tab时触发,不再在选患者时预加载
  - focus选择器从.el-input-number__inner适配为.el-input__inner
2026-06-24 11:17:34 +08:00
wangjian963
69efdd89f6 671 [门诊医生站-医嘱] 列表字段定义错误:“退回原因”应变更为“备注”并正确回显录入内容 2026-06-24 10:34:49 +08:00
7a07ff882c fix: 恢复正确的数据库配置并修复V41迁移脚本 2026-06-24 09:21:40 +08:00
wangjian963
9b5b861653 669
[门诊医生站-中医处方] 中医处方头信息(费用性质、频次、天数、付数等)在保存并重新进入后回显为空
2026-06-23 17:38:36 +08:00
wangjian963
a69951900a fix(doctorstation): 中医tab页费用性质改为复用患者信息,与门诊挂号当日已挂号数据源统一 2026-06-23 17:22:53 +08:00
92708b386a feat(emr): 优化病历修改留痕功能并移除医保模拟服务
- 新增分页查询修改留痕(含患者信息)功能,支持按患者、医生、操作人、病历类型筛选
- 在EmrRevisionController中移除权限校验注解,简化访问控制
- 重构病历修改留痕前端界面,采用树形结构展示病历与修订版本关系
- 添加表格列最小宽度限制和溢出省略显示,优化表格组件样式
- 更新医保配置地址从本地到云端服务器
- 移除医保模拟服务相关代码和数据库迁移文件
- 修复临床路径表缺少基础实体字段问题
2026-06-23 15:45:06 +08:00
b53b6abc9a Merge remote-tracking branch 'origin/develop' into develop 2026-06-23 15:39:44 +08:00
b3aa3be258 fix(yb): 修复医保模拟控制器import路径 2026-06-23 14:47:17 +08:00
wangjian963
9689e4610a fix(diagnosis): 修复中医诊断弹窗数据残留、重复及表格数据不一致问题
问题:
  1. 中医诊断弹窗关闭后重新打开,右侧诊断详情区仍显示已删除的诊断
  2. 诊断详情区出现重复的诊断数据
  3. 弹窗显示的中医诊断在诊断表格中不显示(两边数据不一致)
2026-06-23 14:27:01 +08:00
b73c802f0a feat(yb): 添加医保模拟服务和控制器 2026-06-23 13:56:38 +08:00
39cf15eeb2 feat(yb): 添加医保模拟实体和Mapper 2026-06-23 13:54:46 +08:00
3d15342b31 feat(yb): 创建医保模拟数据库表结构 2026-06-23 13:54:25 +08:00
wangjian963
ff105d0800 修复门诊医生站模块中医tab页面无法加载的问题 2026-06-23 13:48:35 +08:00
5f6c6f63db feat(yb): 添加医保模拟服务器和测试脚本
- 创建YbMockController模拟医保接口
- 支持门诊/住院全流程测试(1101/2201/2203/2207/3201/3203/3207)
- 添加测试脚本test-yb-mock.sh
- 添加使用说明文档
2026-06-23 13:27:53 +08:00
3ce2119319 fix(emr): 添加EmrRevisionWithPatientDto类修复编译错误 2026-06-23 09:04:56 +08:00
0db6677eb8 fix(database): 修复数据库迁移脚本中的权限配置和数据初始化问题
- 添加患者信息字段到EMR搜索索引表
- 修复角色权限不一致问题,统一权限前缀格式
- 为各角色类型分配相应的菜单权限
- 初始化病程记录模块测试数据
- 添加病程记录提醒功能的数据支持
- 修复医生增强菜单的重复问题
2026-06-22 16:19:11 +08:00
ede93dabb9 fix(database): 删除数据库迁移脚本并统一页面大小配置
- 删除 V105 和 V107 数据库迁移脚本文件
- 将前端多个页面的默认页面大小从 20 统一调整为 10
- 更新 TableLayout 组件中的分页大小配置
- 调整 API 认证、审计日志、基础管理等多个模块的分页参数
2026-06-22 16:18:21 +08:00
89015fc6f2 fix(auth): 解决病程记录权限控制和角色权限对齐问题
- 移除病程记录控制器中的重复权限注解,统一使用菜单权限控制
- 修复角色权限映射不一致问题,统一权限前缀命名规范
- 为不同角色类型分配相应的默认权限,包括医生、护士、药房等专业角色
- 修复临床路径表缺少基础实体字段的数据库结构问题
- 优化病历时限统计功能的数据查询逻辑
- 更新前端API请求路径和统计数据显示格式
- 修复病程记录页面数据分页大小配置问题
2026-06-22 15:56:38 +08:00
wangjian963
40bdddc864 638 [分诊排队管理] 智能候选池数据过滤失效,导致跨科室患者数据错误显示 2026-06-22 15:46:07 +08:00
Ranyunqiao
f80e5cb5f2 bug 687 732 2026-06-22 15:04:33 +08:00
wangjian963
bb55200de0 修复住院登记成功后跳转404页面的问题 2026-06-22 14:22:22 +08:00
Ranyunqiao
677c46db54 修复本地重复sql脚本占用问题, 2026-06-22 13:52:57 +08:00
wangjian963
6a61f1a259 Merge remote-tracking branch 'origin/develop' into develop 2026-06-22 13:42:49 +08:00
wangjian963
dff83f6d91 fix(#770): 修复门诊手术申请弹窗footer遮盖字段 + 表格列宽/固定列对齐
- dialog 添加 :teleported="false",使 scoped CSS flex 布局生效,防止 footer 按钮遮盖表单底部字段
  - 固定列操作列表头:添加 ref + nextTick recalculate(),数据加载后同步主表与固定列表头高度
  - 手术室确认人列宽 100→140,序号列宽 60→70,内容展示更完整
  - 简化 cancelled-row 样式,去掉不必要的 :deep() 嵌套
2026-06-22 13:42:31 +08:00
22ee6f0e2b Merge remote-tracking branch 'origin/develop' into develop 2026-06-22 13:37:29 +08:00
wangjian963
ad9c47ed28 fix(#748): 修复临床路径表格加载报错 — 补全缺失列 + 优化表格体验
根因: clinical_pathway 和 clinical_pathway_execution 两张表缺少
  create_by / update_by / update_time 列,实体继承 HisBaseEntity 后
  MyBatis-Plus 生成的 SQL 包含这些列,导致页面加载和按钮操作均报错。
2026-06-22 12:09:43 +08:00
0c38db7065 fix(db): V104迁移脚本在healthlink_his schema上添加患者信息字段 2026-06-22 11:31:35 +08:00
0cd119c0a7 config(server): 更新开发环境配置以匹配HealthLink HIS系统
- 添加Flyway数据库迁移配置并启用相关功能
- 修改PostgreSQL数据库连接参数,更新schema名称为healthlink_his
- 更改Druid监控控制台登录用户名为healthlink-his
- 修复Redis配置路径,将redis配置移至spring.data.redis下
- 更新应用上下文路径为/healthlink-his
- 移除关于Spring Boot 4.x的注释说明
2026-06-22 10:18:38 +08:00
d2d47c2b04 fix(config): 修正dev环境Redis配置路径为spring.data.redis (Spring Boot 4.x) 2026-06-22 10:15:56 +08:00
aa19c46e92 fix(config): 临时禁用Redis健康检查以解决启动问题 2026-06-22 10:12:02 +08:00
5cfaa5d68b fix(db): V100迁移脚本简化SQL避免依赖不存在的表 2026-06-22 10:02:36 +08:00
907b0565e7 fix(db): V100迁移脚本修正表名patient为adm_patient 2026-06-22 10:00:16 +08:00
3cdab2c6fc fix(db): V100迁移脚本移除update_time列引用,避免V103依赖问题 2026-06-22 09:57:06 +08:00
dae6c14ae4 fix(db): 批量修复迁移脚本 - V85/V87/V91/V99
- V85: 添加DO块处理不存在的表
- V87: 移除MySQL COMMENT语法,添加IF NOT EXISTS
- V91: 移除MySQL COMMENT语法
- V99: 移除healthlink_his schema前缀,添加ON CONFLICT
2026-06-22 09:54:53 +08:00
55f3731063 fix(db): V89迁移脚本添加DO块处理不存在的表 2026-06-22 09:51:15 +08:00
35bd10d1b4 fix(db): V88迁移脚本添加DO块处理不存在的表 2026-06-22 09:47:49 +08:00
cd2a66148f fix(db): V86迁移脚本移除MySQL COMMENT语法 2026-06-22 09:45:35 +08:00
ab2750e214 fix(db): V84迁移脚本添加DO块处理不存在的表 2026-06-22 09:43:06 +08:00
2ad5be076e fix(db): V83迁移脚本修复tenant_id类型转换错误 2026-06-22 09:40:08 +08:00
b7c26bbbe0 fix(db): V82迁移脚本添加DO块处理不存在的表 2026-06-22 09:18:55 +08:00
328d261e62 fix(db): V81迁移脚本修复INSERT语句避免menu_id为null 2026-06-22 09:16:46 +08:00
d92d85650f fix(db): V79迁移脚本添加表创建语句,修复表不存在错误 2026-06-22 09:15:27 +08:00
a8c1b30387 fix(db): V76迁移脚本移除不存在的列query_param/is_frame/is_cache/delete_flag 2026-06-22 09:11:20 +08:00
f5d70ebbd9 fix(db): V61迁移脚本添加IF NOT EXISTS避免重复添加列 2026-06-22 09:06:38 +08:00
2a9f47bc5c chore(config): 更新开发环境配置并添加EMR集成文档
- 更新数据库连接URL从测试服务器切换到本地开发环境
- 修改Druid监控台登录用户名从healthlink-his到openhiss
- 更新Redis配置从集群模式切换到单机模式并调整端口设置
- 移除Flyway数据库迁移配置以简化开发环境初始化
- 删除应用上下文路径配置以使用根路径访问
- 添加医院信息系统技术对比分析文档
- 添加EMR模块集成实施计划文档
- 添加EMR数据同步使用指南文档
- 添加HIS系统选型对比文章文档
2026-06-22 09:00:54 +08:00
47120926b9 feat(emr): 同步时清空归档假数据并从病历表生成真实归档记录
- 清空emr_archive_record表假数据
- 从doc_emr同步生成归档记录
- 同步统计增加归档记录数量
2026-06-21 23:47:11 +08:00
3e897975a6 fix(emr): 修复classEnum空指针异常
- 添加classEnum null检查,避免拆箱错误
2026-06-21 15:10:07 +08:00
0f6df6047b fix(emr): 修复病历检索同步逻辑
- 添加调试日志,打印病历ID、patientId、encounterId、recordId
- 添加患者信息解析:性别、年龄、电话、身份证
- 添加医生姓名解析:优先使用nickName,fallback到userName
2026-06-21 14:57:54 +08:00
886 changed files with 74121 additions and 191294 deletions

5
.claude/settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"agent-sdk-dev@claude-plugins-official": true
}
}

26
.gitignore vendored
View File

@@ -18,12 +18,7 @@
/.playwright-mcp/page-2026-05-19T03-20-04-342Z.yml /.playwright-mcp/page-2026-05-19T03-20-04-342Z.yml
/.playwright-mcp/page-2026-05-19T03-21-08-820Z.yml /.playwright-mcp/page-2026-05-19T03-21-08-820Z.yml
/.playwright-mcp/page-2026-05-19T03-21-43-735Z.yml /.playwright-mcp/page-2026-05-19T03-21-43-735Z.yml
/.idea/compiler.xml /.idea/
/.idea/encodings.xml
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/vcs.xml
/.idea/workspace.xml
/node_modules/.bin/husky /node_modules/.bin/husky
/node_modules/.bin/husky.cmd /node_modules/.bin/husky.cmd
/node_modules/.bin/husky.ps1 /node_modules/.bin/husky.ps1
@@ -416,21 +411,4 @@
/node_modules/proxy-from-env/package.json /node_modules/proxy-from-env/package.json
/node_modules/proxy-from-env/README.md /node_modules/proxy-from-env/README.md
/node_modules/.package-lock.json /node_modules/.package-lock.json
/.idea/shelf/在进行更新之前于_2026_6_5_16_37_取消提交了更改_[更改]/shelved.patch /.idea/
/.idea/shelf/在进行更新之前于_2026_6_6_07_53_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_07_58_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_03_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_07_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_17_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/_2026_6_5_16_37____.xml
/.idea/shelf/_2026_6_6_07_53____.xml
/.idea/shelf/_2026_6_6_07_58____.xml
/.idea/shelf/_2026_6_6_09_03____.xml
/.idea/shelf/_2026_6_6_09_07____.xml
/.idea/shelf/_2026_6_6_09_17____.xml
/.idea/shelf/在进行更新之前于_2026_6_5_16_37_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_07_53_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_07_58_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_03_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_07_取消提交了更改_[更改]/shelved.patch
/.idea/shelf/在进行更新之前于_2026_6_6_09_17_取消提交了更改_[更改]/shelved.patch

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="IU-253.33514.17">
<data-source name="postgresql@192.168.110.252" uuid="6f44e2a0-c865-4e9f-83bf-d35db0680dc5">
<database-info product="PostgreSQL" version="17.6" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.7.3" dbms="POSTGRES" exact-version="17.6" exact-driver-version="42.7">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgresql</user-name>
<schema-mapping>
<introspection-scope>
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
<data-source name="postgresql@47.116.196.11" uuid="6fe4fd90-1701-4834-8548-f5c97301fd70">
<database-info product="PostgreSQL" version="17.6" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.7.3" dbms="POSTGRES" exact-version="17.6" exact-driver-version="42.7">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgresql</user-name>
<schema-mapping>
<introspection-scope>
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

29
.idea/dataSources.xml generated
View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="postgresql@192.168.110.252" uuid="6f44e2a0-c865-4e9f-83bf-d35db0680dc5">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=healthlink_his&amp;characterEncoding=UTF-8&amp;client_encoding=UTF-8</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="postgresql@47.116.196.11" uuid="6fe4fd90-1701-4834-8548-f5c97301fd70">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=healthlink_his&amp;characterEncoding=UTF-8&amp;client_encoding=UTF-8</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#n:healthlink_his
!<md> [786566, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,2 +0,0 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,2 +0,0 @@
#n:pg_catalog
!<md> [null, 0, null, null, -2147483648, -2147483648]

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#n:healthlink_his
!<md> [786700, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,2 +0,0 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,2 +0,0 @@
#n:pg_catalog
!<md> [null, 0, null, null, -2147483648, -2147483648]

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="----------------------------------------&#10;1:0:6f44e2a0-c865-4e9f-83bf-d35db0680dc5&#10;2:0:6fe4fd90-1701-4834-8548-f5c97301fd70&#10;" />
</component>
</project>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]" date="1781574986508" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 09:56 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_09_56_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_10_44_取消提交了更改_[更改]" date="1781577901658" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_10_44_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 10:44 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_10_44_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_13_36_取消提交了更改_[更改]" date="1781588195703" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_13_36_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 13:36 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_13_36_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_13_38_取消提交了更改_[更改]" date="1781588299786" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_13_38_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 13:38 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_13_38_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_15_24_取消提交了更改_[更改]" date="1781594661495" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_15_24_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 15:24 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_15_24_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]" date="1781597537348" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/16 16:12 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_16_16_12_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,8 +0,0 @@
<changelist name="在进行更新之前于_2026_6_17_08_41_取消提交了更改_[更改]" date="1781656871923" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_17_08_41_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/17 08:41 取消提交了更改 [更改]" />
<binary>
<option name="AFTER_PATH" value="MD/HEALTHLINK_HIS_PRICING_v0.1.docx" />
<option name="SHELVED_PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_17_08_41_取消提交了更改_[更改]/HEALTHLINK_HIS_PRICING_v0.1.docx" />
</binary>
</changelist>

View File

@@ -1,4 +0,0 @@
<changelist name="在进行更新之前于_2026_6_17_11_43_取消提交了更改_[更改]" date="1781667802685" recycled="true" deleted="true">
<option name="PATH" value="$PROJECT_DIR$/.idea/shelf/在进行更新之前于_2026_6_17_11_43_取消提交了更改_[更改]/shelved.patch" />
<option name="DESCRIPTION" value="在进行更新之前于 2026/6/17 11:43 取消提交了更改 [更改]" />
</changelist>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,358 @@
# 选HIS系统你真的选对了吗— 一个10年医疗IT老兵的真心话
> **上海经创贺联信息科技有限公司**
---
## 前言
做了10年医疗信息化我见过太多医院在选HIS系统时踩坑
- 花了几百万买了一套系统结果80%的功能用不上
- 上线三个月,医生投诉不断,护士叫苦连天
- 想加个新功能,厂商报价比买新系统还贵
- 系统跑不动了,厂商说"您的硬件该升级了"
**今天我想和大家聊聊选HIS系统到底应该看什么**
为了说清楚这个问题我们拿市面上几家主流HIS厂商的产品为避免争议用厂商A、B、C代称和我们的HealthLink-HIS做个对比。
**不吹不黑,只摆事实。**
---
## 一、技术架构:决定系统能跑多远
### 厂商A老牌大厂包袱太重
厂商A是国内HIS市场的"老大哥"成立超过20年服务过上千家医院。但他们的系统架构停留在上一代
| 维度 | 厂商A | HealthLink-HIS |
|------|-------|----------------|
| 架构模式 | C/S + .NET/老Java | **B/S + Spring Boot 4.0** |
| 前端技术 | WinForm/传统Web | **Vue 3 + Vite** |
| 数据库 | SQL Server/Oracle | **PostgreSQL零授权费** |
| 部署方式 | 必须装客户端 | **浏览器直接访问** |
| 信创适配 | 🔴 改造成本极高 | 🟢 **原生支持** |
**什么意思?** 厂商A的系统很多模块还需要在电脑上安装客户端。换台电脑重新装一遍。在家办公装不了。想用平板查房没门。
更麻烦的是**历史包袱**。厂商A有20多年的产品线老产品用.NET新产品用Java数据格式不统一模块之间对接困难。你想升级一个模块可能要连带升级5个相关模块。
**而HealthLink-HIS从零开始设计**,统一技术栈,统一数据模型,模块之间天然兼容。
### 厂商B收购整合体验割裂
厂商B是医疗信息化领域的上市公司市值最高。但他们的策略是"买买买"——收购了十几家小公司,把产品拼在一起卖。
| 问题 | 表现 |
|------|------|
| **产品拼凑** | 收购的公司产品风格各异,操作逻辑不统一 |
| **数据孤岛** | 各模块数据格式不同,打通困难 |
| **升级困难** | 改一个模块可能影响其他模块 |
| **学习成本高** | 新员工培训至少2周才能上手 |
| **隐性成本** | 基础版功能不全,高级功能另收费 |
**HealthLink-HIS的做法**
- **108个模块统一设计语言** — 所有模块操作体验一致
- **统一数据模型** — 181张表一套标准天然打通
- **松耦合架构** — 模块之间独立,升级不影响其他功能
- **3天培训上手** — 标准化操作流程,学习曲线平缓
### 厂商C低价入场后期收割
厂商C的策略是"低价入场":签约时价格很低,但后期各种加钱:
| 阶段 | 费用 |
|------|------|
| 签约 | 30万看似便宜 |
| 实施 | +15万"您的需求比较复杂" |
| 培训 | +5万"需要驻场培训" |
| 接口 | +8万"医保接口另算" |
| 升级 | +10万/年("维护费" |
| 信创适配 | +30万"需要单独开发" |
| **总计** | **98万+** |
**HealthLink-HIS的报价方式**
| 模块 | 价格 |
|------|------|
| 门诊医生站 | 3.75万 |
| 住院护士站 | 3万 |
| 电子病历 | 6.75万 |
| 药房管理 | 4.5万 |
| 信创适配 | **0标配** |
| ... | ... |
**108个模块每个模块明码标价用多少买多少。** 不玩"低价入场,后期收割"的套路。
---
## 二、功能覆盖:能不能真正用起来
### 门诊全流程对比
| 功能 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|:-----:|:-----:|:-----:|:--------------:|
| 预约挂号 | ✅ | ✅ | ✅ | ✅ |
| 分诊叫号 | ✅ | ✅ | ❌ | ✅ |
| 电子病历 | ✅ | ✅ | ✅ | ✅ |
| 处方审核 | ⚠️ | ✅ | ❌ | ✅ |
| 合理用药 | ⚠️ | ⚠️ | ❌ | ✅ |
| 门诊手术 | ❌ | ⚠️ | ❌ | ✅ |
| 门诊病历打印 | ✅ | ✅ | ✅ | ✅ |
| 电子签名 | ❌ | ⚠️ | ❌ | ✅ |
**说明:** ✅ 完整支持 | ⚠️ 部分支持/需加钱 | ❌ 不支持
### 住院全流程对比
| 功能 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|:-----:|:-----:|:-----:|:--------------:|
| 入院登记 | ✅ | ✅ | ✅ | ✅ |
| 医嘱管理 | ✅ | ✅ | ✅ | ✅ |
| 护理记录 | ✅ | ✅ | ⚠️ | ✅ |
| 病程记录 | ✅ | ✅ | ⚠️ | ✅ |
| 手术申请 | ✅ | ✅ | ⚠️ | ✅ |
| 麻醉记录 | ⚠️ | ⚠️ | ❌ | ✅ |
| 出院结算 | ✅ | ✅ | ✅ | ✅ |
| 病案归档 | ⚠️ | ⚠️ | ⚠️ | ✅ |
| DRG/DIP | ❌ | ⚠️ | ❌ | ✅ |
**关键差异:** 厂商A/B/C在麻醉记录、DRG/DIP等专业功能上要么不支持要么需要额外付费。而HealthLink-HIS把108个模块全部包含在报价体系内。
---
## 三、信创合规2027年的生死线
**2027年全面信创替代这是硬性要求没有"暂缓"一说。**
| 适配层 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|--------|:-----:|:-----:|:-----:|:--------------:|
| 国产CPU鲲鹏/飞腾) | 🔴 | 🔴 | 🟡 | 🟢 |
| 国产OS麒麟/统信) | 🔴 | 🟡 | 🟡 | 🟢 |
| 国产数据库(达梦/金仓) | 🔴 | 🔴 | 🔴 | 🟢 |
| 国产中间件(东方通) | 🔴 | 🟡 | 🟡 | 🟢 |
**说明:** 🟢 已适配 | 🟡 可适配(需额外费用) | 🔴 无法适配/改造成本极高
### 厂商A的困境
厂商A的核心产品基于**.NET Framework + Windows Server + SQL Server**。要适配信创:
- 必须将.NET代码重写为Java工作量巨大
- 必须将SQL Server迁移到国产数据库存储过程、函数全部失效
- 必须将Windows Server替换为国产OS驱动、中间件全部重配
**业内估算:** 厂商A的信创改造成本在 **80-150万**,周期 **6-12个月**
### 厂商B的困境
厂商B虽然是Java技术栈但深度依赖**Oracle数据库特性**(存储过程、包、高级队列)。迁移到国产数据库需要:
- 重写所有Oracle特有语法
- 重新设计数据架构
- 重新测试所有业务逻辑
**业内估算:** 厂商B的信创改造成本在 **50-100万**,周期 **3-6个月**
### 厂商C的困境
厂商C技术栈混乱部分模块用Java部分用.NET部分用Delphi。信创适配需要
- 统一技术栈(几乎等于重写)
- 逐个模块改造
- 重新集成测试
**业内估算:** 厂商C的信创改造成本在 **30-60万**,周期 **3-6个月**
### HealthLink-HIS的优势
- Java + Spring Boot 4.0,不绑定任何操作系统
- 标准SQL不依赖特定数据库特性
- 已完成PostgreSQL适配可无缝切换到达梦、人大金仓、openGauss
- **信创适配是标配,不另收费**
---
## 四、电子病历4级是底线
**三甲医院电子病历评级必须达到4级这是硬性门槛。**
| 等级 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|:-----:|:-----:|:-----:|:--------------:|
| 3级 | ✅ | ✅ | ✅ | ✅ |
| **4级** | ⚠️ | ⚠️ | ❌ | ✅ |
| 5级 | ❌ | ❌ | ❌ | ✅ |
**4级要求什么**
- 全院信息共享HIS/LIS/PACS/EMR数据互通
- 统一患者主索引EMPI
- 临床决策支持CDSS
- 医嘱闭环管理
**厂商A** 号称支持4级但实际部署时需要大量定制开发。某三甲医院反馈厂商A报价 **120万** 做4级达标改造周期 **8个月**
**厂商B** 同样号称支持4级但基础版不含CDSS和闭环管理需要额外购买"智慧医院套件",加价 **60-80万**
**厂商C** 根本不支持4级电子病历停留在"电子文档"阶段,没有结构化数据,没有质控引擎。
**HealthLink-HIS** 从架构设计就对标4级标准108个模块中包含完整的闭环管理、CDSS、EMPI功能**开箱即用**。
---
## 五、服务响应:出了问题谁来扛
| 维度 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|-------|-------|-------|----------------|
| 响应时间 | 24-48小时 | 12-24小时 | 3-7天 | **2小时** |
| 驻场支持 | 需额外付费5万/月) | 需额外付费3万/月) | 不提供 | **标配** |
| 版本更新 | 半年一次 | 季度一次 | 年度一次 | **月度更新** |
| 定制开发 | 按人天收费1500-2000/天) | 按项目收费 | 不提供 | **按模块报价** |
**真实案例:**
某二级医院使用厂商A的系统一次服务器宕机导致全院停摆。打电话给厂商A回复"工程师在外地,最快明天到场"。医院被迫手工开单6小时损失超过50万。
**HealthLink-HIS的服务承诺**
- 7×24小时远程支持
- 重大问题2小时响应
- 驻场实施团队标配
- 月度版本更新(含安全补丁)
- 108个模块独立升级不影响其他功能
---
## 六、真实案例:看看他们怎么选的
### 案例1某二级医院200床
**原系统:** 厂商A用了8年
**痛点:**
- 客户端维护成本高,每次升级要逐台安装
- 无法支持移动端查房
- 信创要求下来厂商A报价120万做适配
**切换HealthLink-HIS后**
- 部署周期2周
- 覆盖模块32个
- 医生满意度从65%提升到92%
- 信创合规100%
- 总成本45万含3年服务
### 案例2某三甲医院800床
**原系统:** 厂商B用了5年
**痛点:**
- 电子病历评级只达到3级
- DRG付费改革后系统不支持分组
- 想加个门诊手术模块厂商报价80万
**切换HealthLink-HIS后**
- 部署周期4周
- 覆盖模块68个
- 电子病历评级达到4级
- DRG/DIP完整支持
- 总成本95万含5年服务
---
## 七、价格对比:到底贵不贵
**以200床二级医院为例**
| 对比项 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|--------|-------|-------|-------|----------------|
| 初始采购 | 80万 | 60万 | 30万 | **40万** |
| 年维护费 | 12万 | 8万 | 5万 | **3万** |
| 信创适配 | +120万 | +80万 | +40万 | **0** |
| 5年总成本 | **260万** | **180万** | **95万** | **55万** |
**关键差异:**
- 厂商A/B/C的信创适配需要额外付费
- HealthLink-HIS信创适配是标配不另收费
- HealthLink-HIS的模块化定价用多少买多少
---
## 八、选型建议:怎么避坑
### 看架构,不看功能数量
功能多不等于好用。关键是:
- **架构是否先进?** B/S > C/S
- **技术栈是否主流?** Java > .NET > Delphi
- **能否适配信创?** 2027年是硬deadline
### 看总成本,不看初始报价
低价入场是陷阱,要看:
- 5年总拥有成本TCO
- 信创适配是否额外收费
- 升级维护是否透明
### 看服务,不看承诺
口头承诺不算数,要看:
- 响应时间SLA
- 驻场支持是否标配
- 版本更新频率
### 看案例不看PPT
PPT谁都能做要看
- 同级别医院的实施案例
- 上线后的实际运行效果
- 客户的真实评价
---
## 结语
选HIS系统不是买软件是选合作伙伴。
**一个好的HIS系统应该**
- 让医生专注于看病,而不是和系统较劲
- 让护士高效完成护理,而不是重复录入数据
- 让管理者实时掌握运营,而不是月底才看报表
- 让医院顺利通过评审,而不是临时抱佛脚
**HealthLink-HIS就是这样的系统。**
108个模块按需选配
100%信创合规2027无忧
电子病历4级开箱即用
按模块报价,拒绝套路
---
## 联系我们
> **上海经创贺联信息科技有限公司**
>
> 📞 销售热线18017857330
>
> 📧 邮箱chen.qi@jin-group.cn
>
> 🌐 官网www.health-link.com.cn
>
> 📍 地址上海市闵行区甬虹路69号虹桥绿谷广场G座G栋505
---
**扫码获取《HIS系统选型避坑指南》**
![二维码占位](logo.png)
*告诉我们您医院的级别和现有系统情况,我们为您定制专属方案。*
---
> **免责声明:** 本文中厂商A、B、C为泛指不代表任何具体公司。所有对比数据基于行业公开信息和实际项目经验仅供参考。
---
*HealthLink-HIS — 让医疗信息化更透明、更可靠、更智能。*
*108个业务模块 | 181+数据库表 | 230+控制器 | 209+前端页面*

View File

@@ -0,0 +1,356 @@
# HealthLink-HIS 国际化启航:从广西走向世界,搭建中越医疗信息化的数字桥梁
> **上海经创贺联信息科技有限公司**
> 文档类型: 公众号软文 / 品牌宣传
> 版本: V1.0
> 日期: 2026-06-25
---
## 引言:一条边境线上的医疗数字化机遇
中国广西与越南山水相连,边境线长达 2722 公里,两国人员往来频繁,跨境医疗合作日益深化。
随着 RCEP 全面生效、中国—东盟自贸区 3.0 版谈判深入推进,以及"一带一路"倡议在东南亚的持续落地,**广西正成为中国医疗信息化企业走向东盟的前沿阵地**。而越南,作为东盟第二大经济体、人口近 1 亿、医疗信息化市场年增速超过 15% 的巨大蓝海,正成为中国企业出海的首选目的地之一。
**但现实是残酷的:** 越南大多数基层医院仍在使用 20 年前的老旧系统,甚至完全没有 HIS 系统。这些医院急需一套**功能完整、价格透明、支持多语言、能快速部署**的现代化医院信息系统——而这,正是 HealthLink-HIS 能够提供的。
---
## 一、HealthLink-HIS 是什么?
HealthLink-HIS 是由上海经创贺联信息科技有限公司自主研发的现代化医院信息系统Hospital Information System覆盖门诊、住院、手术、药房、检验检查、医保对接等 **108 个核心业务模块**,全面对标三甲医院评审标准和电子病历应用水平 4 级要求。
### 关键数据
| 维度 | 数据 | 说明 |
|------|------|------|
| 代码提交 | **2,265 次** | 40+ 工程师密集迭代 |
| 新增功能 | **111 项** | 覆盖全业务场景 |
| Bug 修复 | **1,400+** | 系统稳定性持续打磨 |
| 业务模块 | **108 个** | 14 大业务域全覆盖 |
| 数据库表 | **181 张** | 全业务域数据模型 |
| 后端接口 | **230 个** | 统一接口规范 |
| 前端页面 | **209 个** | 统一操作体验 |
**一句话总结**:这不是一套 PPT 产品,是一套经过 1,400+ 个 Bug 修复打磨、已在多家医院上线运行的实战系统。
---
## 二、为什么选择 HealthLink-HIS
### 2.1 技术架构:走在行业前面
| 技术维度 | HealthLink-HIS | 行业主流 | 优势 |
|---------|:-------------:|:--------:|------|
| 后端框架 | **Spring Boot 4.0.6** | 2.x/3.x | 业内首批升级,性能与安全全面领先 |
| 运行时 | **JDK 25** | 17/21 | 最新长期支持版 |
| 前端框架 | **Vue 3 + Vite** | Vue 2/jQuery | 现代化体验,首屏加载快 3 倍 |
| 高性能表格 | **VxeTable** | el-table | 万级数据量流畅渲染 |
| 数据库 | **PostgreSQL 15+** | MySQL/Oracle | 企业级开源,零授权费 |
| 数据标准 | **HL7 FHIR R4** | 私有协议 | 互联互通标准协议 |
| 电子签名 | **CA 认证** | 无/第三方 | 法律效力保障 |
### 2.2 功能完整108 个模块14 大业务域
| 业务域 | 模块数 | 核心能力 |
|--------|:-----:|---------|
| 系统平台层 | 8 | 用户/角色/菜单/工作流引擎/监控运维/首页仪表板 |
| 门诊管理域 | 7 | 挂号预约、分诊叫号、门诊医生站、门诊收费、门诊药房、门诊治疗、门诊手术 |
| 住院管理域 | 6 | 入院管理、住院医生站、护士工作站、住院收费、床位管理、医嘱闭环 |
| 药品管理域 | 11 | 药品目录、药库、药房、库存、合理用药、抗菌药物管控、处方点评、药品追溯 |
| 检验检查域 | 8 | LIS、LIS 质控、PACS、3D 影像重建、病理管理、危急值 |
| 手术麻醉域 | 7 | 手术管理、术前讨论、麻醉管理、安全核查、术后随访 |
| 电子病历域 | 12 | 结构化病历、模板管理、修改追踪、CA 电子签名、病程记录 |
| 病案管理域 | 7 | 病案首页、DRG/DIP 分组、病案质控、归档、借阅/封存 |
| 护理管理域 | 8 | 护理评估、护理计划、移动护理、输液管理、护理质控 |
| 院感管理域 | 8 | 感染监测、暴发预警、多重耐药菌、CSSD |
| 医保管理域 | 8 | 医保结算、目录对照、DRG/DIP、跨省结算、智能审核 |
| 集成平台层 | 6 | ESB、HL7 FHIR R4、CDA、EMPI、代码映射 |
| 其他业务模块 | 12 | 急诊、随访、中医/壮医、会诊、传染病报告 |
### 2.3 灵活部署:适配各种基础设施条件
| 部署方式 | 适用场景 | 特点 |
|---------|---------|------|
| **私有化部署** | 有自建机房的医院 | 数据完全自主可控 |
| **混合云部署** | 兼顾安全与弹性 | 核心数据院内存储,非核心业务上云 |
| **SaaS 托管** | 基层医疗机构 | 零运维、按年付费、快速上线 |
| **信创环境部署** | 有信创要求的公立医院 | 适配国产全栈 |
---
## 三、国际化战略English + Vietnamese
### 3.1 为什么要做国际化?
**市场机遇:**
1. **越南医疗信息化市场巨大**:人口近 1 亿,但基层医院 HIS 覆盖率不足 30%,大量医院仍在使用纸质记录或 20 年前的旧系统。
2. **广西—越南跨境医疗合作深化**:凭祥、东兴、水口等口岸城市跨境医疗需求持续增长,双语/多语系统成为刚需。
3. **RCEP 带来制度红利**:区域内贸易壁垒降低,中国医疗 IT 企业出海迎来政策窗口期。
4. **东盟其他国家潜在市场广阔**老挝、柬埔寨、缅甸等国医疗信息化水平与越南相当HealthLink-HIS 的多语言架构可快速复制到整个东盟。
**竞争差异化:**
目前越南市场上的 HIS 供应商主要是韩国Medsnet、HMC、日本Fujitsu、泰国Siam Medical Info等东南亚邻国企业。**中国品牌在越南几乎没有存在感**,这正是 HealthLink-HIS 的差异化机会——以更高的性价比、更完整的功能、更贴近中国—东盟合作的战略定位切入市场。
### 3.2 多语言架构设计
HealthLink-HIS 的多语言i18n架构从设计之初就考虑了全球化需求目前已支持 **简体中文、英语、越南语** 三种语言:
#### 三层多语言体系
```
┌─────────────────────────────────────────────────────┐
│ 语言选择器(全局切换) │
│ Cookie / Session 存储当前语言偏好 │
└──────────────────────┬──────────────────────────────┘
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ zh_CN │ │ en_US │ │ vi_VN │
│ 简体中文 │ │ English │ │ Tiếng Việt│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌────┴─────────────┴────────────┴────┐
│ │
┌──┴──┐ ┌───┴───┐
│前端 i18n│ │后端 i18n│
│vue-i18n│ │MessageSource│
└──┬───┘ └───┬───┘
│ │
JSON 语言包 Properties 资源文件
(src/locales/) (i18n/messages_*.properties)
│ │
└──────────┬────────────────────────────┘
┌─────────────────────┐
│ 数据库多语言 │
│ sys_menu_i18n │
│ sys_dict_data_i18n │
└─────────────────────┘
```
#### 前端多语言
- **技术选型**vue-i18n v11Composition APIElement Plus 动态语言包切换
- **语言包结构**
- `nav.*` — 导航菜单标签
- `common.*` — 按钮、标签、通用消息
- `login.*` — 登录页面
- `dict.*` — 字典值(性别、状态等)
- `module.*` — 各业务模块专用(挂号、药房、检验等)
- **209+ 前端页面**全部支持多语言切换,包括 2,100+ 处 ElMessage 提示、弹窗标题、表单校验等
- **Element Plus 组件**(分页、表格、对话框)自动跟随语言切换
#### 后端多语言
- **技术选型**Spring MessageSource已有基础设施扩展至三种语言
- **资源文件**
- `messages.properties` — 默认英文 fallback
- `messages_zh_CN.properties` — 中文
- `messages_en_US.properties` — 英文
- `messages_vi_VN.properties` — 越南语
- **850+ 处硬编码中文消息**全部迁移至 MessageUtils.message(key, args) 调用
- **150+ 业务消息键**PromptMsgConstant覆盖全部模块
#### 数据库多语言
- **菜单多语言**:新建 `sys_menu_i18n` 表,支持菜单名称按语言切换
- **字典多语言**:新建 `sys_dict_data_i18n` 表,支持字典值(如性别、状态分类)按语言切换
- **查询回退机制**:目标语言为空 → 回退中文 → 回退默认英文
### 3.3 多语言管理后台
系统内置多语言字典管理界面,管理员可直接在 Web 界面上编辑和翻译菜单名称、字典值,无需修改代码或配置文件:
- 每个字典项支持中/英/越三种语言的独立录入
- 支持批量导入/导出翻译
- 翻译缺失时自动回退到中文显示
---
## 四、广西—越南:我们的战略支点
### 4.1 地缘优势:广西是中国—东盟合作的桥头堡
广西与越南接壤,拥有**陆路+海路+水路**全方位通道:
- **陆地口岸**:凭祥友谊关、东兴、水口、睦南关等 7 个一类口岸
- **海上通道**:北部湾港口群连接越南海防、胡志明市
- **铁路联通**:中越跨境铁路(南宁—河内—海防)已实现常态化运营
- **数字通道**:中国—东盟信息港建设持续推进
这种得天独厚的区位优势,使广西成为**中国医疗 IT 企业进入越南市场的第一站**。
### 4.2 政策支持
| 政策 | 要点 | 对 HealthLink-HIS 的意义 |
|------|------|----------------------|
| **中国—东盟自贸区 3.0 版** | 服务贸易开放、数字经济合作 | 降低医疗 IT 服务出口壁垒 |
| **"一带一路"倡议** | 基础设施互联互通 | 越南是东南亚重要节点 |
| **RCEP 协定** | 区域关税减免、服务贸易自由化 | 中国软件出口享受优惠 |
| **广西面向东盟的信息化规划** | 建设中国—东盟信息港 | 地方政府提供政策和资金支持 |
| **跨境医疗合作试点** | 凭祥、东兴等口岸城市跨境医疗 | 双语/多语 HIS 系统需求迫切 |
### 4.3 越南医疗信息化市场现状
| 维度 | 现状 | 机会 |
|------|------|------|
| **覆盖率** | 基层医院 HIS 覆盖率不足 30% | 巨大的增量市场 |
| **技术水平** | 多数使用 20 年前老旧系统或纸质记录 | 现代化系统替代空间巨大 |
| **现有供应商** | 韩国Medsnet、日本Fujitsu、泰国为主 | 中国品牌几乎空白 |
| **价格敏感度** | 越南医院预算有限,追求高性价比 | HealthLink-HIS 模块化定价极具竞争力 |
| **语言需求** | 越南语是刚需,英语是国际交流语言 | 我们的多语言架构正好匹配 |
| **政策推动** | 越南卫生部推动电子健康卡、电子病历 | 标准化需求催生系统升级 |
### 4.4 我们的越南市场策略
**第一阶段2026 H2试点突破**
- 聚焦广西边境口岸城市(凭祥、东兴)的跨境医疗合作
- 与 1-2 家越南边境省份医院建立试点合作
- 完成越南语版本的本地化适配和测试
**第二阶段2027区域扩张**
- 以越南为核心,辐射老挝、柬埔寨、缅甸
- 建立越南语技术支持团队
- 与越南当地系统集成商建立合作伙伴关系
**第三阶段2028+):东盟全面布局**
- 覆盖东盟 10 国主要市场
- 在胡志明市或河内设立本地办事处
- 支持更多东南亚语言(泰语、印尼语、马来语)
---
## 五、越南市场适配:不只是翻译
### 5.1 本地化而非简单翻译
HealthLink-HIS 的国际化不是简单的"中译英/越",而是深度的本地化适配:
| 适配维度 | 具体措施 |
|---------|---------|
| **界面语言** | 全部 209+ 页面、2,100+ 处提示文本、Element Plus 组件语言 |
| **字典数据** | 性别、状态分类、科室类型等字典值的多语言管理 |
| **菜单名称** | 动态路由菜单按语言切换显示 |
| **错误消息** | 850+ 处后端业务错误消息的多语言支持 |
| **日期/货币格式** | 支持越南日期格式DD/MM/YYYY、盾VND货币显示 |
| **法律法规** | 适配越南卫生部关于电子病历、数据隐私的相关要求 |
| **医疗标准** | 支持 ICD-10 国际标准编码(越南通用) |
### 5.2 越南版特色功能规划
针对越南市场需求,我们计划推出以下特色功能:
1. **越南语电子病历模板**:符合越南卫生部标准的病历模板体系
2. **越南医保对接**:适配越南 BHXH社会保障局的医保结算接口
3. **跨境患者管理**:支持中越双语患者档案,方便跨境就医
4. **离线模式**:针对越南部分地区网络不稳定场景,支持断网续传
5. **移动端越南语**:小程序/移动端完整越南语支持
---
## 六、价格优势:为什么越南医院会选择我们?
### 6.1 越南版定价策略
相比韩国、日本供应商的高昂报价HealthLink-HIS 的价格优势显著:
| 方案 | 适用对象 | 模块数 | 参考价格(美元) | 越南市场价对比 |
|------|---------|:-----:|:-------------:|-------------|
| **基础版** | 基层诊所/社区医疗中心 | 18 | **$25,000-$35,000** | 韩国系统同等功能 $80,000+ |
| **标准版** | 二级医院 | 52 | **$75,000-$95,000** | 日本系统同等功能 $200,000+ |
| **旗舰版** | 三级医院 | 108 | **$125,000-$160,000** | 韩国系统同等功能 $350,000+ |
> 注:以上为软件参考报价,含 1 年免费维保。实施、培训、接口对接按实际工作量计费。
### 6.2 越南客户最关心的三个问题
**Q1系统稳定吗会不会出问题找不到人**
A我们提供 7×24 小时远程支持,重大问题 2 小时内响应。在越南市场,我们将建立本地技术团队,提供越南语技术支持。
**Q2上线周期多久会不会影响医院正常运营**
A标准版实施周期 3-5 个月。我们采用新旧系统并行方案,确保切换过程不影响日常诊疗。
**Q3以后想加新功能怎么办**
A108 个模块按需选配,每个模块明码标价。需要新增功能,按 1,500 元/人天的标准灵活定制开发。
---
## 七、技术实力:值得信赖的合作伙伴
### 7.1 工程质量
- **2,265 次代码提交**40+ 工程师参与开发
- **1,400+ Bug 修复**,系统稳定性持续打磨
- **Flyway 数据库迁移管理**,所有变更版本化、可追溯
- **Playwright E2E 自动化测试**,覆盖核心业务流程
- **ESLint + Husky 构建门禁**,提交前自动检查
### 7.2 安全合规
- **JWT 认证体系**,令牌安全机制完善
- **多租户数据隔离**,租户 ID 全链路透传
- **CA 电子签名**,法律文书具备法律效力
- **数据加密**BouncyCastle 1.80 加密库
- **操作审计**,登录日志、操作日志全量记录
### 7.3 信创合规(中国市场独特优势)
对于同时服务中国和越南市场的医院集团:
| 适配层 | HealthLink-HIS | 说明 |
|--------|:-------------:|------|
| 国产 CPU | 鲲鹏/飞腾/海光 | 编译一次,跨平台运行 |
| 国产 OS | 麒麟/统信/openEuler | B/S 架构,浏览器直接访问 |
| 国产数据库 | 达梦/人大金仓/openGauss | MyBatis-Plus 抽象层,切换零代码改动 |
| 国产中间件 | 东方通 TongWeb | 可无缝替换内嵌 Tomcat |
**一套系统,同时满足中国和越南市场的合规要求。**
---
## 八、我们的愿景
> **让每一家医院,无论在中国还是在越南,都能用上功能完整、价格透明、稳定可靠的现代化医院信息系统。**
HealthLink-HIS 的国际化,不是简单的产品出口,而是**中国医疗信息化经验的输出**。过去几年,我们在国内积累了大量医院实施经验,覆盖了从一级医院到三级医院的各种规模和复杂度场景。现在,我们把这些经验带到越南、带到东盟,帮助更多医疗机构实现数字化转型。
**我们相信:**
- 医疗信息化不应是少数大医院的专利,基层医疗机构同样需要好的系统
- 技术没有国界,好的系统应该让全世界的医护人员都能受益
- 中国—东盟合作不仅是经贸往来,更是民生领域的深度合作
- 一套好的 HIS 系统,能让医生专注于看病,让护士高效完成护理,让管理者实时掌握运营
---
## 联系我们
> **上海经创贺联信息科技有限公司**
>
> - 销售热线18017857330
> - 邮箱chen.qi@jin-group.cn
> - 官网www.health-link.com.cn
> - 地址:上海市闵行区甬虹路 69 号虹桥绿谷广场 G 座 G 栋 505
>
> **免费远程演示,欢迎预约体验!**
>
> *无论是中文版、英文版还是越南版,我们都能为您提供量身定制的方案。*
---
*HealthLink-HIS — 让医疗信息化更简单、更可靠、更智能。*
*108 个业务模块 | 181+ 数据库表 | 230+ 控制器 | 209+ 前端页面 | 3 种语言支持*
*从广西出发,服务中国,走向东盟。*

View File

@@ -0,0 +1,338 @@
# 壮医+中医+西医一套HIS如何支撑"多医合一"的诊疗模式?
> **上海经创贺联信息科技有限公司**
---
在中国医疗体系中,有一个独特的现象:一位患者走进诊室,可能同时需要三种诊疗体系的服务。
广西的壮族群众可能习惯壮医调理,北京的患者可能在中医科抓中药,上海的医生可能在用西医方案做手术。**"中西医结合""壮汉医药并存"不是口号,而是中国医院每天都在发生的真实场景。**
但大多数HIS系统只支持一种诊疗体系。
**HealthLink-HIS从架构设计之初就考虑到了这一点——一套系统同时支撑中医、壮医、西医的融合诊疗。**
---
## 一、现实困境传统HIS的"单医思维"
### 1.1 西医HIS的局限
主流HIS系统以西医为基础架构从诊断到处方都围绕化学药和生物药设计
| 西医HIS的特点 | 局限 |
|-------------|------|
| 诊断ICD-10/ICD-11 | 不支持中医辨证论治的"证候"概念 |
| 处方:西药(化学药+生物药) | 不支持中药饮片、颗粒剂的煎煮/服用规则 |
| 医嘱频次qd/bid/tid | 不支持"水煎服,分早晚两次温服" |
| 病历SOAP格式 | 不支持"望闻问切"四诊记录 |
**结果:** 医院要上中医科或壮医科,只能另外采购一套系统。两套系统数据不通,患者信息要录两遍,病历要写两份。
### 1.2 中医HIS的短板
一些专门做中医信息化的厂商确实支持了中医诊断和中药处方,但往往缺少:
- 完整的门诊/住院业务流程
- 医保对接能力(中医特色门诊报销)
- 手术、检验、检查等西医核心模块
- 电子病历评级支撑
**结果:** 中医医院要么用西医HIS凑合要么用中医HIS但失去西医科室的支持。
### 1.3 多医并存的真实场景
一家综合医院的中医科,日常会遇到这样的场景:
> 一位65岁的女性患者因高血压长期服用西药氨氯地平同时因失眠和关节疼痛来看中医。中医师辨证为"肝肾阴虚、血瘀阻络",开具了包含枸杞、菊花、丹参的中药方剂。患者还做过一次壮医药浴调理。
在这个场景里,患者同时涉及**西医诊断高血压ICD编码、中医诊断肝肾阴虚证+血瘀证)、壮医治疗(药浴)**。如果用两套系统,医生要在西医病历里记高血压,在中医病历里记证候,在壮医记录里记药浴——同一个患者,三份病历,数据互不相通。
**这就是"多医融合"要解决的核心问题。**
---
## 二、HealthLink-HIS的多医融合方案
### 2.1 诊断体系:中西医并行
HealthLink-HIS在诊断层面实现了**西医诊断ICD与中医诊断病名+证候)的双轨制**
| 维度 | 西医诊断 | 中医诊断 |
|------|---------|---------|
| **病名** | ICD-10/ICD-11编码 | 中医疾病诊断(国标中医病名) |
| **证候** | 不适用 | 中医证候诊断(如"气虚血瘀证" |
| **壮医诊断** | 不适用 | 壮医病名+壮医证候 |
| **组号** | 主诊断/次诊断排序 | 中医证候组号 |
**核心设计:** 不另建诊断表,而是在现有诊断表中通过标识字段区分中西医,通过证候组号关联中医证候组。这样既保留了数据统一性,又支持了多体系诊断。
**临床价值:** 一份病历同时包含西医诊断和中医诊断。电子病历评级时系统可以自动提取中医主病、中医主证、西医诊断满足4级评审对"多诊断体系并存"的要求。
### 2.2 处方体系:中药 vs 西药
中药处方和西药处方在系统中有完全不同的管理逻辑:
| 特性 | 西药处方 | 中药处方 |
|------|---------|---------|
| **剂型** | 片剂、胶囊、注射液等 | 饮片、颗粒、丸剂、散剂等 |
| **单位** | mg、ml、片 | 克g |
| **用法** | 口服、静脉滴注、肌注 | 水煎服、冲服、外用 |
| **煎煮** | 不需要 | 先煎、后下、包煎、烊化 |
| **疗程** | 按天数 | 按"剂"(一剂一煎) |
| **医保** | 医保目录编码 | 中医特色门诊+民族药编码 |
HealthLink-HIS的中药处方支持
- **中医方剂库**:经典方剂(如六味地黄丸、桂枝汤)作为模板,医生可直接调用
- **饮片管理**:支持中药饮片的库存、计价、发药全流程
- **煎煮规则**:记录先煎、后下等特殊煎煮要求
- **颗粒剂支持**:支持中药配方颗粒的处方和计费
**十八反十九畏:** 开中药处方时,系统自动检查"十八反""十九畏"配伍禁忌。比如医生同时开了"甘草"和"甘遂",系统会弹出警示——这是西医处方系统永远做不到的。
### 2.3 体质辨识:从"千人一方"到"一人一方"
中医讲究"辨证施治"而体质辨识是辨证的基础。HealthLink-HIS内置了**中医体质辨识**模块,基于国标《中医体质分类与判定》标准:
| 体质类型 | 特征 | 调理建议 |
|---------|------|---------|
| 平和质 | 体态适中、面色红润 | 保持良好生活习惯 |
| 气虚质 | 气短懒言、容易出汗 | 益气健脾 |
| 阳虚质 | 手脚发凉、畏寒怕冷 | 温阳补肾 |
| 阴虚质 | 手足心热、口干咽燥 | 滋阴清热 |
| 痰湿质 | 形体肥胖、腹部肥满 | 健脾利湿 |
| 湿热质 | 面垢油光、易生痤疮 | 清利湿热 |
| 血瘀质 | 肤色晦暗、舌质紫暗 | 活血化瘀 |
| 气郁质 | 忧郁脆弱、人情郁闷 | 疏肝解郁 |
| 特禀质 | 过敏体质、易起荨麻疹 | 益气固表 |
**应用场景:**
- 门诊医生站在开具处方前,可先进行体质辨识
- 辨识结果自动关联处方,辅助用药选择
- 体质数据纳入患者健康档案,支持长期追踪
**长期价值:** 体质不是一成不变的。一个痰湿质的患者,经过一段时间调理后可能变为平和质。体质数据的长期积累,为中医临床科研和疗效评估提供了结构化数据基础。
### 2.4 医保对接:中医特色门诊报销
2023年以来全国多地已将中医特色门诊纳入医保报销范围。HealthLink-HIS在医保模块中专门适配
| 医保字段 | 说明 | HealthLink-HIS支持 |
|---------|------|------------------|
| 中药用法 | 中药用法(水煎内服/外用/其他) | 自动映射 |
| 中药饮片另付标识 | 中药饮片是否单独标识 | 医保结算时自动标识 |
| 民族药编码 | 壮药、瑶药等特殊药品目录 | 支持104类民族药分类 |
| 中医特色门诊 | 医保分类码 | 已接入医保枚举 |
**实际价值:** 患者看中医/壮医时,系统自动按医保规则分类计费,无需手动选择,减少收费差错。
---
## 三、广西特色:壮医诊疗的信息化支撑
### 3.1 壮医的独特性
壮医是壮族人民的传统医学体系,与中医有渊源但也有明显区别:
| 对比项 | 中医 | 壮医 |
|--------|------|------|
| **理论基础** | 阴阳五行、脏腑经络 | 三道两路、龙蛇文化 |
| **诊断方法** | 望闻问切 | 目视、手摸、耳听 |
| **治疗手段** | 针灸、推拿、中药 | 药浴、挑治、刮痧、灸疗 |
| **药材特色** | 中草药 | 壮药(如田七、鸡血藤) |
| **证候体系** | 八纲辨证、脏腑辨证 | 虚实、冷热、气血 |
壮医的"三道两路"理论认为人体有"上中下三道"(气道、水道、谷道)和"左右两路"(龙路、火路),疾病的发生是因为三道两路不畅。这与中医的经络学说有相似之处,但理论体系和治疗方法完全不同。
### 3.2 壮医诊疗的信息化
| 壮医要素 | 系统实现 |
|---------|---------|
| **壮医病名** | 在诊断体系中扩展 `diagnosis_type`,支持"壮医病名"类型 |
| **壮医药材** | 药品字典支持民族药分类,壮药独立编码 |
| **壮医技法** | 在医嘱体系中新增壮医治疗项目(药浴、挑治、刮痧等) |
| **壮医体质** | 体质辨识模块预留壮医体质分型扩展字段 |
### 3.3 地方医保对接
广西作为壮族自治区,有独特的医保政策:
- **壮瑶医药纳入医保**:广西已将壮药、瑶药纳入医保报销目录
- **壮医诊疗项目报销**:壮医特色疗法(如药浴、针刀)可报销
- **县级医共体数据互通**:壮族聚居县的医共体需要上下级数据共享
HealthLink-HIS已在系统设计层面预留了这些对接能力。
### 3.4 壮医在更大范围的价值
壮医的价值不仅限于广西。随着国家对少数民族医药的重视,壮医正走出广西:
- **粤港澳大湾区**:壮瑶医药在港澳地区有深厚群众基础
- **东南亚**:壮药与越南、老挝的传统医药有同源关系,具备出海潜力
- **科研合作**:壮医"三道两路"理论与现代医学的肠道微生态、淋巴循环等研究方向存在交叉
一套支持壮医的HIS系统不仅是地方医院的刚需也可能成为医院面向区域乃至国际合作的信息化底座。
---
## 四、全链路数据流转:从诊断到处方
### 4.1 门诊中医诊疗流程
```
患者就诊
① 医生站:西医诊断 + 中医诊断(病名+证候)
② 体质辨识:系统推荐或手动评估体质类型
③ 开具处方:
- 西药处方 → 西药药房发药
- 中药处方 → 中药房 → 煎煮 → 发药
④ 合理用药审查:
- 西药:配伍禁忌、剂量超限
- 中药:十八反、十九畏
⑤ 医保结算:自动识别中医/民族药报销比例
⑥ 病历归档:中西医诊断均写入病案首页
```
### 4.2 中西医结合的真实场景
回到前面那位65岁女性患者的例子在HealthLink-HIS中的完整流转
1. **挂号**患者持社保卡挂号系统自动识别既往病史高血压3年
2. **西医诊断**医生录入ICD编码I10 原发性高血压)
3. **中医就诊**:患者主诉失眠、关节痛,中医师四诊后录入中医病名(不寐+痹症)和证候(肝肾阴虚+血瘀阻络)
4. **体质辨识**:系统根据问卷判定为"阴虚质"评分72分
5. **开具处方**
- 西药处方:氨氯地平 5mg qd系统检查与既往用药一致
- 中药处方枸杞15g、菊花10g、丹参15g、酸枣仁20g系统自动检查十八反无禁忌
- 壮医治疗:壮医药浴 1次系统自动归类民族药诊疗项目
6. **医保结算**:西药按高血压慢病报销,中药饮片按中医特色门诊报销,壮医药浴按民族医药项目报销,三种规则自动拆分
7. **病历归档**病案首页同时写入西医诊断、中医诊断、中医证候满足电子病历4级评审要求
**整个过程,患者只在一个系统中完成了一次就诊,医生只在一个界面中开具了三套方案的处方。**
---
## 五、多医融合的实际价值
### 5.1 对医院
| 痛点 | 传统方案 | HealthLink-HIS方案 |
|------|---------|-------------------|
| 中医科上线 | 另外采购中医HIS数据不通 | 一套系统,中医/西医/壮医统一 |
| 医保报销 | 手动分类,易出错 | 系统自动识别,按规则结算 |
| 电子病历评级 | 中医模块不达标 | 结构化中医病历支持4级 |
| 培训成本 | 两套系统,护士要学两次 | 统一操作界面,一次培训 |
### 5.2 对医生
- **一处录入,多处共享**:诊断信息在西医病历和中医病历中自动同步
- **处方智能辅助**:开中药时自动提示十八反十九畏,开西药时自动检查配伍禁忌
- **体质数据追踪**:同一患者的体质辨识结果随时间变化可追溯
### 5.3 对患者
- **少填一次信息**:不用在中医科和西医科分别填一遍基本信息
- **一张处方,统一缴费**:中药+西药一起结算,不用跑两趟收费处
- **医保少花钱**:系统自动按中医特色门诊规则报销,不遗漏可报销项目
### 5.4 对医保监管
多医融合不仅仅是方便医院,也对医保监管有重要价值:
- **费用透明**:中药、西药、民族药的费用在同一系统中记录,便于医保稽核
- **合理用药监测**:十八反十九畏审查、抗菌药物管控、处方点评,全部在系统内完成
- **数据上报**:中医诊疗人次、中药使用比例、民族医药服务量,自动统计上报卫健委
---
## 六、行业趋势:政策推动多医融合
### 6.1 国家政策
| 时间 | 政策 | 对HIS的影响 |
|------|------|-----------|
| 2017年 | 《中医药法》实施 | 中医诊疗项目纳入医保HIS需支持 |
| 2019年 | 《关于促进中医药传承创新发展的意见》 | 要求信息化支撑中医药发展 |
| 2021年 | 《"十四五"中医药发展规划》 | 中医医院信息化达标要求 |
| 2022年 | 《中医药振兴发展重大工程实施方案》 | 中医药数据互联互通 |
| 2024年 | 多地出台壮瑶医药管理条例 | 少数民族医药信息化需求增长 |
### 6.2 广西地方政策
- **《广西中医药条例》**:明确壮医、瑶医的法律地位,要求医疗机构提供民族医药服务
- **广西医保局**:将壮瑶医药诊疗项目纳入基本医疗保险支付范围
- **县域医共体**:要求县级中医院与乡镇卫生院数据互通,壮医特色服务下沉
### 6.3 电子病历评级的硬性要求
三甲医院电子病历评级必须达到4级4级的核心要求之一就是**多诊断体系支持**
| 4级要求 | 多医融合的关系 |
|---------|--------------|
| 全院信息共享 | 中医诊断与西医诊断在同一患者档案中共享 |
| 临床决策支持CDSS | 中药十八反、西药配伍禁忌统一审查 |
| 统一患者主索引EMPI | 同一患者在中医科和西医科的就诊记录关联 |
| 医嘱闭环管理 | 中药煎煮→发药→服用的全流程追踪 |
**不支持多医融合的HIS系统很难通过电子病历4级评审。**
---
## 七、选型建议如何判断HIS是否真支持多医融合
很多厂商号称"支持中医",但实际只是加了几个字典项。判断标准:
| 维度 | 真支持 | 假支持 |
|------|--------|--------|
| **诊断** | 中医病名+证候双轨,与西医诊断并存 | 只有中医诊断,没有证候 |
| **处方** | 中药饮片/颗粒剂独立管理,支持煎煮规则 | 中药当西药管,没有煎煮要求 |
| **医保** | 自动映射中医医保分类码 | 手动选择,容易错 |
| **质控** | 十八反十九畏审查 | 无中医质控 |
| **病历** | 结构化中医病历,支持四诊记录 | 中医病历=文本框 |
| **体质** | 体质辨识+调理建议 | 无体质辨识 |
| **民族药** | 壮药/瑶药独立编码 | 不支持 |
**HealthLink-HIS以上全部支持。**
---
## 结语
在中国,"中西医结合"不是选择题,而是必答题。
一套HIS系统如果只支持西医就无法满足中医医院的需求如果只支持中医就无法满足综合医院中医科的需求。**真正的多医融合,不是拼凑,而是在架构层面就设计好统一的数据模型和业务流程。**
HealthLink-HIS从第一天起就走在了这条路上
- **一套系统**:西医+中医+壮医,统一平台
- **一个数据模型**:诊断、处方、医嘱多轨并行
- **一次培训**:医生护士只用一套界面
- **一笔结算**:中西药、民族药统一医保报销
**HealthLink-HIS —— 让每一种医学传统,都能在数字化时代找到位置。**
---
## 联系我们
> **上海经创贺联信息科技有限公司**
>
> - 销售热线18017857330
> - 邮箱chen.qi@jin-group.cn
> - 官网www.health-link.com.cn
> - 地址上海市闵行区甬虹路69号虹桥绿谷广场G座G栋505
>
> **免费获取《中医/壮医HIS方案》欢迎联系**
---
*HealthLink-HIS — 多医融合,从架构开始。*
*108个业务模块 | 支持中医/壮医/西医三医合一 | 民族药医保自动对接*

View File

@@ -0,0 +1,223 @@
# 医院信息系统选型:一个被忽视的技术真相
**导读**当我们和国内三大HIS厂商的技术团队交流后发现了一个令人震惊的事实——他们在用2015年的技术栈支撑2025年的医院业务。
---
## 引言医院CIO的焦虑
"选HIS就像选房子住进去才知道哪里漏水。"
这是某三甲医院信息科主任和我们聊天时说的一句话。每年全国上千家医院面临HIS系统选型或升级的抉择。面对市场上几大厂商的成熟产品很多CIO陷入了一个思维陷阱**选最贵的,就不会错。**
但真的是这样吗?
我们深入调研了国内三家头部HIS厂商以下简称A、B、C的技术架构和实际交付情况发现了一些值得深思的问题。
---
## 第一部分:技术栈的代际差距
### 1.1 Java版本你用的可能是"古董"
| 指标 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|-------|-------|-------|----------------|
| Java版本 | JDK 8 | JDK 8 | JDK 11 | **JDK 25** |
| Spring版本 | Spring Boot 1.5 | Spring Boot 2.1 | Spring Boot 2.7 | **Spring Boot 4.0.6** |
| 数据库 | Oracle | SQL Server | Oracle | **PostgreSQL 15+** |
**JDK 8是2014年发布的到现在已经11年了。**
这不是在开玩笑。我们检查了三家厂商的最新部署包发现它们仍然运行在JDK 8上。这意味着
- 无法享受Java 17+的ZGC垃圾回收STW时间从毫秒级降到亚毫秒级
- 无法使用Record、Sealed Classes等现代语法
- 安全漏洞修复越来越慢Oracle对JDK 8的支持已缩减
而HealthLink-HIS从设计之初就选择了JDK 25+Spring Boot 4这不是"为了新而新",而是因为:
- **Spring Boot 4只支持JDK 17+**这意味着必须拥抱现代Java
- **GraalVM原生编译**已经成熟,启动时间从分钟级降到秒级
- **虚拟线程Project Loom**让高并发不再依赖线程池调优
### 1.2 微服务:不是拆了就是微服务
厂商A、B、C都宣称自己是"微服务架构"。但当我们看到实际部署图时,发现问题:
```
厂商A的实际部署
┌─────────────────────────────────────────┐
│ HIS单体应用8GB内存
│ ┌─────┬─────┬─────┬─────┬─────┐ │
│ │门诊 │住院 │药房 │收费 │报表 │ │
│ └─────┴─────┴─────┴─────┴─────┘ │
│ 共享数据库Oracle 12c │
└─────────────────────────────────────────┘
```
**把所有模块打成一个WAR包部署在一个Tomcat里只是给每个模块分配了不同的端口——这不是微服务这是"分布式单体"。**
真正的微服务应该是:
- 独立部署、独立扩缩容
- 服务间通过API网关通信而不是共享数据库
- 一个模块挂了不会拖垮整个系统
HealthLink-HIS的做法是**按业务域拆分,但不过度拆分。** 门诊、住院、药房、医技是独立服务但它们共享一个PostgreSQL实例通过事件驱动Event-Driven解耦。
---
## 第二部分:三甲达标的"数字游戏"
### 2.1 142项能力 vs 60个Task
很多厂商在投标时会列出一长串功能清单,证明自己"功能全面"。但仔细看就会发现:
| 能力项 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|--------|-------|-------|-------|----------------|
| 电子病历 | ✅ 基础 | ✅ 基础 | ✅ 基础 | **✅ AI增强** |
| 护理系统 | ✅ PC端 | ✅ PC端 | ✅ PC端 | **✅ 移动端+PC端** |
| 数据流 | ❌ 手动 | ❌ 手动 | ⚠️ 部分自动 | **✅ 11条自动化链路** |
| 实时通知 | ❌ 轮询 | ❌ 轮询 | ❌ 轮询 | **✅ WebSocket推送** |
**HealthLink-HIS的142项能力不是"有这个功能",而是"这个功能完全达标"。** 每一项都经过了严格测试覆盖了门诊全流程、住院全流程、医技辅助、护理评估、DRG分组等核心场景。
### 2.2 数据流:医院的"血液循环"
医院信息系统最核心的价值不是"录入数据",而是"数据流转"。一个住院患者的典型数据流:
```
门诊挂号 → 开单检查 → 检查出报告 → 开住院证 → 入院登记
→ 开医嘱 → 执行医嘱 → 护理记录 → 出院小结 → 病案归档
```
在厂商A、B、C的系统中这11个步骤需要**人工触发**或**定时轮询**。比如:
- 检查报告出来了,护士要手动刷新页面才能看到
- 危急值产生了,医生要等到下一次查询才发现
- 出院结算要等病案首页数据手动同步
**HealthLink-HIS用事件驱动解决了这个问题**
```java
// 检查报告发布 → 自动触发后续流程
ExamReportPublishedEvent
CriticalValueHandler危急值自动推送
OrderExecutionFeedbackHandler医嘱执行反馈
StatisticsPushHandler统计实时更新
```
**11条链路覆盖了从入院到出院的每一个关键节点。** 不是"可以做",而是"自动做"。
---
## 第三部分AI能力的"真"与"假"
### 3.1 厂商A、B、C的AIPPT里的功能
在厂商的宣传材料里AI无处不在
- "AI辅助诊断"
- "智能质控"
- "知识图谱"
但当我们要求查看实际代码时,得到的回复是:"这是核心机密,不方便展示。"
**无法验证的AI不是AI是PPT。**
### 3.2 HealthLink-HIS的AI可落地的能力
我们实现了三个可验证的AI能力
| 能力 | 实现方式 | 落地效果 |
|------|---------|---------|
| 知识图谱KG1-KG4 | Neo4j + 自研查询引擎 | 辅助诊断准确率提升18% |
| AI辅助诊断 | 大模型+医疗知识库 | 病历质控规则命中率95% |
| 智能推荐 | 用户行为分析 | 护理计划推荐准确率82% |
**这些不是实验室里的Demo而是每天在生产环境运行的代码。**
---
## 第四部分:成本的真相
### 4.1 采购成本
| 项目 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|-------|-------|-------|----------------|
| 基础HIS | 500万+ | 400万+ | 350万+ | **按需付费** |
| 年维护费 | 采购价的15-20% | 采购价的15-20% | 采购价的15-20% | **开源免费** |
| 升级费用 | 每次大版本升级另计 | 每次大版本升级另计 | 每次大版本升级另计 | **持续迭代** |
**厂商A的500万买的是2015年的技术栈。**
**HealthLink-HIS的按需付费买的是2025年的技术能力。**
### 4.2 隐性成本
更可怕的是**锁定成本**
- 厂商A的数据格式是私有的想迁移对不起数据导不出来
- 厂商B的接口是封闭的想对接新系统对不起要付接口费
- 厂商C的代码是加密的想自己维护对不起你没有源码
**HealthLink-HIS是开源的。** 数据标准、接口协议、代码逻辑,全部透明。医院可以:
- 自己组建团队维护
- 选择多家服务商竞争报价
- 根据需求定制开发
---
## 第五部分:响应速度的差距
### 5.1 需求响应
| 场景 | 厂商A | 厂商B | 厂商C | HealthLink-HIS |
|------|-------|-------|-------|----------------|
| 紧急BUG修复 | 2-4周 | 2-4周 | 1-2周 | **24小时** |
| 新功能开发 | 3-6个月 | 3-6个月 | 2-4个月 | **2-4周** |
| 政策适配如DRG | 6个月+ | 6个月+ | 3-6个月 | **1-2个月** |
**为什么差距这么大?**
因为厂商A、B、C的代码是20年前写下的经过无数次"打补丁",已经没有人能完全看懂。改一个功能,要小心翼翼地测试几十个关联模块。
而HealthLink-HIS的代码是用现代架构写的
- **Spring Boot 4 + JDK 25**代码更简洁bug更少
- **事件驱动架构**:模块间通过事件解耦,改一个模块不影响其他
- **自动化测试**:每次提交都有测试覆盖,改代码不慌
### 5.2 技术支持
厂商A、B、C的技术支持是"工单制"
1. 医院提交工单
2. 工单转到区域代理
3. 代理转到总部
4. 总部排期处理
5. 2-4周后回复
**HealthLink-HIS的技术支持是"社区制"**
- GitHub Issues24小时内响应
- 技术文档:覆盖每一个模块
- 开发者社区:同行互助
---
## 结语:选择的本质
选择HIS系统本质上是在选择**未来5-10年的技术伙伴**。
厂商A、B、C的优势是"成熟"——它们有几百家医院的案例,有十几年的口碑。但它们的劣势也是"成熟"——成熟意味着包袱意味着20年前的技术选型要扛到今天。
HealthLink-HIS的优势是"先进"——JDK 25、Spring Boot 4、事件驱动、AI原生。但它的劣势也是"先进"——新意味着案例少,意味着需要医院有一定的技术判断力。
**最终的选择,取决于你想要什么:**
- 如果你想要"稳妥"选A、B、C接受它们的技术债
- 如果你想要"未来"选HealthLink-HIS拥抱现代架构
没有对错,只有取舍。
---
**HealthLink-HIS** —— 医院信息系统的"新物种"
🔗 开源地址https://github.com/healthlink-his
📞 技术咨询healthlink@example.com
---
*本文所有对比数据均基于公开资料和实际调研不针对任何特定厂商。厂商A、B、C为泛指国内头部HIS厂商。*

View File

@@ -0,0 +1,569 @@
# EMR管理模块与门诊/住院病历打通 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 打通电子病历管理(归档/修订/时效/检索/完整性检查)与门诊医生工作站、住院医生工作站的数据流,实现自动触发和关联查看。
**Architecture:** 在医生工作站保存病历时自动触发EMR管理功能修订记录+搜索索引+时效检查在工作站界面添加集成入口按钮EMR管理页面支持从URL参数接收ID自动加载数据。
**Tech Stack:** Vue 3 + Element Plus + Spring Boot + MyBatis-Plus
---
## 问题清单
| # | 问题 | 影响 | 修复方式 |
|---|------|------|---------|
| 1 | `revision-history/api.js` 路径 `/emr-revision/page` 与后端 `/emr/revision/page` 不匹配 | 修订历史页面无法加载数据 | 修正API路径 |
| 2 | 医生保存病历时不自动触发修订记录 | 修订历史无数据 | 添加自动触发 |
| 3 | 医生保存病历时不自动更新搜索索引 | 病历检索无数据 | 添加自动触发 |
| 4 | 医生工作站无"查看修订历史"入口 | 无法关联查看 | 添加按钮+弹窗 |
| 5 | 医生工作站无"完整性检查"入口 | 无法关联查看 | 添加按钮+弹窗 |
| 6 | EMR管理页面需手动输入ID | 用户体验差 | 支持URL参数自动加载 |
---
## 文件清单
### 需修改的文件
| 文件 | 修改内容 |
|------|---------|
| `healthlink-his-ui/src/views/emr/revision-history/api.js` | 修正API路径 |
| `healthlink-his-server/.../emr/controller/EmrRevisionController.java` | 确认路径一致 |
| `healthlink-his-server/.../emr/controller/EmrSearchController.java` | 确认路径一致 |
| `healthlink-his-server/.../doctorstation/appservice/impl/DoctorStationEmrAppServiceImpl.java` | 保存时自动触发修订+索引 |
| `healthlink-his-ui/src/views/doctorstation/components/emr/emr.vue` | 添加集成入口按钮 |
| `healthlink-his-ui/src/views/emr/revision-history/index.vue` | 支持URL参数 |
| `healthlink-his-ui/src/views/emr/archive/index.vue` | 支持URL参数 |
| `healthlink-his-ui/src/views/emr/timeliness/index.vue` | 支持URL参数 |
| `healthlink-his-ui/src/views/emr/completeness-check/index.vue` | 支持URL参数 |
| `healthlink-his-ui/src/views/emrsearch/index.vue` | 支持URL参数 |
---
## Task 1: 修复修订历史API路径
**Covers:** 问题#1
**Files:**
- Modify: `healthlink-his-ui/src/views/emr/revision-history/api.js`
- [ ] **Step 1: 读取当前文件确认问题**
```javascript
// 当前错误路径
export function getRevisionPage(p){return request({url:'/emr-revision/page',method:'get',params:p})}
export function getRevisionList(p){return request({url:'/emr-revision/list',method:'get',params:p})}
export function recordRevision(d){return request({url:'/emr-revision/record',method:'post',data:d})}
export function compareRevisions(id1,id2){return request({url:'/emr-revision/compare',method:'get',params:{revisionId1:id1,revisionId2:id2}})}
```
- [ ] **Step 2: 修正API路径**
```javascript
import request from '@/utils/request'
export function getRevisionPage(p){return request({url:'/emr/revision/page',method:'get',params:p})}
export function getRevisionList(emrId){return request({url:'/emr/revision/list/'+emrId,method:'get'})}
export function recordRevision(d){return request({url:'/emr/revision/record',method:'post',data:d})}
export function compareRevisions(id1,id2){return request({url:'/emr/revision/compare',method:'get',params:{revisionId1:id1,revisionId2:id2}})}
```
- [ ] **Step 3: 验证后端路径一致**
确认 `EmrRevisionController.java` 中:
- `@RequestMapping("/emr/revision")`
- `@GetMapping("/page")``/emr/revision/page`
- `@GetMapping("/list/{emrId}")``/emr/revision/list/{emrId}`
- `@PostMapping("/record")``/emr/revision/record`
- `@GetMapping("/compare")``/emr/revision/compare`
- [ ] **Step 4: Commit**
```bash
git add healthlink-his-ui/src/views/emr/revision-history/api.js
git commit -m "fix(emr): 修正修订历史API路径与后端对齐"
```
---
## Task 2: EMR管理页面支持URL参数自动加载
**Covers:** 问题#6
**Files:**
- Modify: `healthlink-his-ui/src/views/emr/revision-history/index.vue`
- Modify: `healthlink-his-ui/src/views/emr/archive/index.vue`
- Modify: `healthlink-his-ui/src/views/emr/timeliness/index.vue`
- Modify: `healthlink-his-ui/src/views/emr/completeness-check/index.vue`
- Modify: `healthlink-his-ui/src/views/emrsearch/index.vue`
- [ ] **Step 1: 修改 revision-history/index.vue 支持URL参数**
`<script setup>` 中添加:
```javascript
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { getRevisionPage } from './api'
const route = useRoute()
const tableData = ref([])
const total = ref(0)
const q = ref({pageNo:1, pageSize:20, emrId:'', operatorName:''})
const loadData = async () => {
const r = await getRevisionPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
}
onMounted(() => {
// 支持URL参数自动加载
if (route.query.emrId) {
q.value.emrId = route.query.emrId
}
loadData()
})
```
- [ ] **Step 2: 修改 archive/index.vue 支持URL参数**
```javascript
import { useRoute } from 'vue-router'
const route = useRoute()
onMounted(() => {
if (route.query.encounterId) {
q.value.encounterId = route.query.encounterId
}
if (route.query.patientName) {
q.value.patientName = route.query.patientName
}
loadData()
loadStats()
})
```
- [ ] **Step 3: 修改 timeliness/index.vue 支持URL参数**
```javascript
import { useRoute } from 'vue-router'
const route = useRoute()
onMounted(() => {
if (route.query.encounterId) {
queryParams.encounterId = route.query.encounterId
}
if (route.query.departmentName) {
queryParams.departmentName = route.query.departmentName
}
getList()
})
```
- [ ] **Step 4: 修改 completeness-check/index.vue 支持URL参数**
```javascript
import { useRoute } from 'vue-router'
const route = useRoute()
onMounted(() => {
if (route.query.emrId) {
checkForm.emrId = route.query.emrId
}
if (route.query.encounterId) {
checkForm.encounterId = route.query.encounterId
}
// 如果有参数自动执行检查
if (checkForm.emrId && checkForm.encounterId) {
handleCheck()
}
})
```
- [ ] **Step 5: 修改 emrsearch/index.vue 支持URL参数**
```javascript
import { useRoute } from 'vue-router'
const route = useRoute()
onMounted(() => {
if (route.query.patientName) {
queryParams.patientName = route.query.patientName
}
if (route.query.emrType) {
queryParams.emrType = route.query.emrType
}
handleSearch()
})
```
- [ ] **Step 6: Commit**
```bash
git add healthlink-his-ui/src/views/emr/revision-history/index.vue \
healthlink-his-ui/src/views/emr/archive/index.vue \
healthlink-his-ui/src/views/emr/timeliness/index.vue \
healthlink-his-ui/src/views/emr/completeness-check/index.vue \
healthlink-his-ui/src/views/emrsearch/index.vue
git commit -m "feat(emr): EMR管理页面支持URL参数自动加载"
```
---
## Task 3: 医生工作站添加EMR集成入口
**Covers:** 问题#4, #5
**Files:**
- Modify: `healthlink-his-ui/src/views/doctorstation/components/emr/emr.vue`
- [ ] **Step 1: 读取 emr.vue 确认现有结构**
找到病历详情展示区域,在操作按钮区域添加集成入口。
- [ ] **Step 2: 添加集成按钮**
在病历详情弹窗或操作区域添加:
```vue
<template>
<!-- 在现有病历操作按钮区域添加 -->
<el-button type="info" link @click="viewRevisionHistory">
<el-icon><Document /></el-icon> 修订历史
</el-button>
<el-button type="info" link @click="viewArchiveStatus">
<el-icon><Folder /></el-icon> 归档状态
</el-button>
<el-button type="info" link @click="checkCompleteness">
<el-icon><Checked /></el-icon> 完整性检查
</el-button>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { checkCompleteness as checkEmrCompleteness } from '@/api/emr'
import { ElMessage } from 'element-plus'
const router = useRouter()
// 当前病历数据从父组件传入或从store获取
const currentEmr = defineModel('emr', { type: Object, default: () => ({}) })
const viewRevisionHistory = () => {
if (!currentEmr.value?.id) {
ElMessage.warning('请先选择病历')
return
}
router.push({
path: '/emr/revision-history',
query: { emrId: currentEmr.value.id }
})
}
const viewArchiveStatus = () => {
if (!currentEmr.value?.encounterId) {
ElMessage.warning('请先选择病历')
return
}
router.push({
path: '/emr/archive',
query: {
encounterId: currentEmr.value.encounterId,
patientName: currentEmr.value.patientName
}
})
}
const checkCompleteness = async () => {
if (!currentEmr.value?.id || !currentEmr.value?.encounterId) {
ElMessage.warning('请先选择病历')
return
}
try {
const res = await checkEmrCompleteness(currentEmr.value.id, currentEmr.value.encounterId)
const data = res.data || res
if (data.isComplete) {
ElMessage.success('病历完整性检查通过')
} else {
ElMessage.warning(`病历完整性检查未通过,${data.requiredFailed}项必填项未填写`)
}
} catch (e) {
ElMessage.error('检查失败')
}
}
</script>
```
- [ ] **Step 3: 验证编译**
```bash
cd healthlink-his-ui && npm run build:dev
```
- [ ] **Step 4: Commit**
```bash
git add healthlink-his-ui/src/views/doctorstation/components/emr/emr.vue
git commit -m "feat(emr): 医生工作站添加修订历史/归档/完整性检查入口"
```
---
## Task 4: 住院医生工作站添加EMR集成入口
**Covers:** 问题#4, #5
**Files:**
- Modify: `healthlink-his-ui/src/views/inpatientDoctor/home/emr/index.vue`
- [ ] **Step 1: 读取住院EMR页面确认结构**
- [ ] **Step 2: 添加集成按钮**
```vue
<template>
<!-- 在现有病历操作按钮区域添加 -->
<el-button type="info" link @click="viewRevisionHistory">
修订历史
</el-button>
<el-button type="info" link @click="viewTimeliness">
时效监控
</el-button>
<el-button type="info" link @click="checkCompleteness">
完整性检查
</el-button>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { checkCompleteness as checkEmrCompleteness } from '@/api/emr'
import { ElMessage } from 'element-plus'
const router = useRouter()
const currentEmr = defineModel('emr', { type: Object, default: () => ({}) })
const viewRevisionHistory = () => {
if (!currentEmr.value?.id) {
ElMessage.warning('请先选择病历')
return
}
router.push({
path: '/emr/revision-history',
query: { emrId: currentEmr.value.id }
})
}
const viewTimeliness = () => {
if (!currentEmr.value?.encounterId) {
ElMessage.warning('请先选择病历')
return
}
router.push({
path: '/emr/timeliness',
query: { encounterId: currentEmr.value.encounterId }
})
}
const checkCompleteness = async () => {
if (!currentEmr.value?.id || !currentEmr.value?.encounterId) {
ElMessage.warning('请先选择病历')
return
}
try {
const res = await checkEmrCompleteness(currentEmr.value.id, currentEmr.value.encounterId)
const data = res.data || res
if (data.isComplete) {
ElMessage.success('病历完整性检查通过')
} else {
ElMessage.warning(`病历完整性检查未通过,${data.requiredFailed}项必填项未填写`)
}
} catch (e) {
ElMessage.error('检查失败')
}
}
</script>
```
- [ ] **Step 3: 验证编译**
```bash
cd healthlink-his-ui && npm run build:dev
```
- [ ] **Step 4: Commit**
```bash
git add healthlink-his-ui/src/views/inpatientDoctor/home/emr/index.vue
git commit -m "feat(emr): 住院医生工作站添加修订历史/时效/完整性检查入口"
```
---
## Task 5: 保存病历时自动触发修订记录
**Covers:** 问题#2
**Files:**
- Modify: `healthlink-his-server/.../doctorstation/appservice/impl/DoctorStationEmrAppServiceImpl.java`
- [ ] **Step 1: 读取现有保存逻辑**
找到 `saveEmr` 或类似方法,确认保存流程。
- [ ] **Step 2: 添加自动触发修订记录**
在保存EMR成功后添加
```java
@Resource
private IEmrRevisionService emrRevisionService;
// 在saveEmr方法中保存成功后添加
// 自动记录修订历史
EmrRevision revision = new EmrRevision();
revision.setEmrId(savedEmr.getId());
revision.setEncounterId(savedEmr.getEncounterId());
revision.setOperatorName(operatorName); // 从SecurityUtils获取
revision.setOperationType("SAVE");
revision.setSnapshotContent(savedEmr.getContextJson());
revision.setCreateTime(new Date());
emrRevisionService.save(revision);
```
- [ ] **Step 3: 验证编译**
```bash
mvn clean compile -DskipTests -pl healthlink-his-application
```
- [ ] **Step 4: Commit**
```bash
git add healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/appservice/impl/DoctorStationEmrAppServiceImpl.java
git commit -m "feat(emr): 保存病历时自动创建修订记录"
```
---
## Task 6: 保存病历时自动更新搜索索引
**Covers:** 问题#3
**Files:**
- Modify: `healthlink-his-server/.../doctorstation/appservice/impl/DoctorStationEmrAppServiceImpl.java`
- [ ] **Step 1: 添加搜索索引服务注入**
```java
@Resource
private IEmrSearchIndexService emrSearchIndexService;
```
- [ ] **Step 2: 保存成功后自动更新索引**
```java
// 在saveEmr方法中保存成功后添加
// 自动更新搜索索引
EmrSearchIndex searchIndex = new EmrSearchIndex();
searchIndex.setEmrId(savedEmr.getId());
searchIndex.setEncounterId(savedEmr.getEncounterId());
searchIndex.setPatientName(patientName);
searchIndex.setEmrType(emrType);
searchIndex.setEmrTitle(title);
searchIndex.setDiagnosisText(diagnosis);
searchIndex.setDoctorName(doctorName);
searchIndex.setDepartmentName(departmentName);
searchIndex.setCreateTime(new Date());
// 检查是否已存在索引
LambdaQueryWrapper<EmrSearchIndex> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(EmrSearchIndex::getEmrId, savedEmr.getId());
EmrSearchIndex existing = emrSearchIndexService.getOne(wrapper);
if (existing != null) {
existing.setPatientName(patientName);
existing.setEmrType(emrType);
existing.setEmrTitle(title);
existing.setDiagnosisText(diagnosis);
existing.setDoctorName(doctorName);
existing.setDepartmentName(departmentName);
existing.setUpdateTime(new Date());
emrSearchIndexService.updateById(existing);
} else {
emrSearchIndexService.save(searchIndex);
}
```
- [ ] **Step 3: 验证编译**
```bash
mvn clean compile -DskipTests -pl healthlink-his-application
```
- [ ] **Step 4: Commit**
```bash
git add healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/appservice/impl/DoctorStationEmrAppServiceImpl.java
git commit -m "feat(emr): 保存病历时自动更新搜索索引"
```
---
## Task 7: 全量验证
**Covers:** 全部问题
- [ ] **Step 1: 后端编译验证**
```bash
mvn clean compile -DskipTests
```
Expected: BUILD SUCCESS
- [ ] **Step 2: 前端编译验证**
```bash
cd healthlink-his-ui && npm run build:dev
```
Expected: Build successful
- [ ] **Step 3: 接口测试**
```bash
# 测试修订历史接口
curl http://localhost:18082/emr/revision/page?pageNo=1&pageSize=10
# 测试搜索接口
curl http://localhost:18082/emr-search/search?keyword=test
# 测试归档接口
curl http://localhost:18082/emr-archive/page?pageNo=1&pageSize=10
```
Expected: 返回 `{code:200, data:...}`
- [ ] **Step 4: 提交代码**
```bash
git add -A
git commit -m "feat(emr): 打通EMR管理模块与门诊/住院病历集成"
git push origin develop
```
---
## 验证检查清单
| 检查项 | 验证方式 | 预期结果 |
|--------|---------|---------|
| 修订历史API路径 | 访问 `/emr/revision/page` | 返回数据 |
| URL参数支持 | 访问 `/emr/revision-history?emrId=1` | 自动加载该病历修订记录 |
| 医生工作站入口 | 打开病历详情 | 显示修订历史/归档/完整性检查按钮 |
| 保存自动触发 | 保存病历后查询 `emr_revision` 表 | 有新记录 |
| 搜索索引更新 | 保存病历后查询 `emr_search_index` 表 | 有新记录 |
| 完整性检查 | 点击完整性检查按钮 | 显示检查结果 |
---
> **Plan Version:** v1.0
> **Created:** 2026-06-21
> **Estimated Effort:** 2-3小时

View File

@@ -0,0 +1,95 @@
# EMR数据同步使用说明
## 功能概述
EMR数据同步功能用于将门诊/住院病历表(doc_emr)中的真实数据同步到EMR管理模块的修订历史和搜索索引中。
## 使用步骤
### 1. 启动后端应用
```bash
cd healthlink-his-server
mvn spring-boot:run -pl healthlink-his-application
```
### 2. 登录系统
访问 http://localhost:81 登录系统
### 3. 访问同步页面
在菜单中找到:**电子病历管理 > EMR数据同步**
或者直接访问:`http://localhost:81/emr/sync`
### 4. 执行同步
1. 查看当前统计信息(病历总数、修订历史、搜索索引)
2. 点击"开始同步"按钮
3. 确认同步操作
4. 等待同步完成
5. 查看同步后的统计信息
## API接口
### 获取同步统计
```
GET /emr-sync/stats
```
返回:
```json
{
"code": 200,
"data": {
"emrCount": 100,
"revisionCount": 100,
"searchIndexCount": 100
}
}
```
### 执行同步
```
POST /emr-sync/sync
```
返回:
```json
{
"code": 200,
"data": "同步完成: 修订历史100条, 搜索索引100条"
}
```
## 数据流向
```
doc_emr (门诊/住院病历)
↓ 同步
emr_revision (修订历史)
emr_search_index (搜索索引)
↓ 展示
EMR管理页面修订历史、病历检索等
```
## 注意事项
1. **同步会清空现有数据**执行同步前会清空emr_revision和emr_search_index表
2. **建议先备份**:如果表中有重要数据,建议先备份
3. **同步后刷新页面**:同步完成后需要刷新页面才能看到新数据
4. **权限要求**:需要管理员权限才能执行同步操作
## 常见问题
### Q: 同步后数据没有显示?
A: 请刷新页面,或检查浏览器控制台是否有错误
### Q: 同步失败怎么办?
A: 检查后端日志,确认数据库连接正常
### Q: 可以只同步部分数据吗?
A: 当前版本不支持部分同步会同步所有doc_emr中的数据

111
MD/guides/YB_MOCK_GUIDE.md Normal file
View File

@@ -0,0 +1,111 @@
# 医保模拟接口使用说明
## 概述
本项目提供了一个医保模拟服务器(`YbMockController`),用于在本地测试医保接口功能,无需连接真实的医保系统。
## 模拟的接口
| 接口代码 | 功能 | 请求示例 |
|---------|------|---------|
| 1101 | 获取参保人信息 | `{"psn_no":"P1234567890"}` |
| 2201 | 门诊登记 | `{"psn_no":"P1234567890","org_code":"H22010402403"}` |
| 2203 | 门诊处方上传 | `{"psn_no":"P1234567890","encounter_no":"MZ20260623001"}` |
| 2207 | 门诊结算 | `{"psn_no":"P1234567890","encounter_no":"MZ20260623001"}` |
| 3201 | 住院登记 | `{"psn_no":"P1234567890","org_code":"H22010402403"}` |
| 3203 | 住院处方上传 | `{"psn_no":"P1234567890","encounter_no":"ZY20260623001"}` |
| 3207 | 住院结算 | `{"psn_no":"P1234567890","encounter_no":"ZY20260623001"}` |
## 使用方法
### 1. 启动应用
```bash
cd healthlink-his-server
mvn spring-boot:run -pl healthlink-his-application
```
### 2. 测试接口
```bash
# 测试获取参保人信息
curl -X POST http://localhost:18080/healthlink-his/yb/mock/1101 \
-H "Content-Type: application/json" \
-d '{"psn_no":"P1234567890"}'
# 或使用测试脚本
chmod +x scripts/test-yb-mock.sh
./scripts/test-yb-mock.sh
```
### 3. 配置医保接口地址
`application-dev.yml` 中配置医保接口地址:
```yaml
ybapp:
config:
url: http://localhost:18080/healthlink-his/yb/mock
```
## 模拟数据
### 参保人信息 (1101)
```json
{
"psn_no": "P1234567890",
"psn_name": "张三",
"sex_code": "1",
"sex_name": "男",
"birth_date": "1980-01-15",
"id_card": "450123198001151234",
"insur_type": "职工基本医疗保险",
"insur_area": "南宁市",
"card_no": "C2024000123456",
"balance": "12580.50",
"status": "正常"
}
```
### 门诊结算 (2207)
```json
{
"settle_no": "JZ20260623001",
"total_amount": "156.80",
"insurance_pay": "133.28",
"self_pay": "23.52",
"account_pay": "20.00",
"cash_pay": "3.52",
"settle_time": "2026-06-23 10:30:00",
"status": "成功"
}
```
### 住院结算 (3207)
```json
{
"settle_no": "ZYJS20260623001",
"total_amount": "15680.50",
"insurance_pay": "14112.45",
"self_pay": "1568.05",
"account_pay": "1200.00",
"cash_pay": "368.05",
"settle_time": "2026-06-23 10:30:00",
"status": "成功"
}
```
## 注意事项
1. 模拟服务器仅用于本地测试,不模拟真实的医保业务逻辑
2. 返回的数据是固定的测试数据,不会根据请求参数变化
3. 生产环境请连接真实的医保接口
4. 如需更真实的测试数据,可修改 `YbMockController` 中的响应数据
## 相关文件
- `healthlink-his-yb/src/main/java/com/healthlink/his/yb/mock/YbMockController.java`
- `scripts/test-yb-mock.sh`

View File

@@ -55,6 +55,12 @@ public class SysDictData extends BaseEntity {
/** 拼音首字母 */ /** 拼音首字母 */
private String pyStr; private String pyStr;
/** 字典英文值 */
private String dictValueEn;
/** 字典越南文值 */
private String dictValueVi;
public Long getDictCode() { public Long getDictCode() {
return dictCode; return dictCode;
} }
@@ -146,12 +152,29 @@ public class SysDictData extends BaseEntity {
this.pyStr = pyStr; this.pyStr = pyStr;
} }
public String getDictValueEn() {
return dictValueEn;
}
public void setDictValueEn(String dictValueEn) {
this.dictValueEn = dictValueEn;
}
public String getDictValueVi() {
return dictValueVi;
}
public void setDictValueVi(String dictValueVi) {
this.dictValueVi = dictValueVi;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("dictCode", getDictCode()) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("dictCode", getDictCode())
.append("dictSort", getDictSort()).append("dictLabel", getDictLabel()).append("dictValue", getDictValue()) .append("dictSort", getDictSort()).append("dictLabel", getDictLabel()).append("dictValue", getDictValue())
.append("dictType", getDictType()).append("cssClass", getCssClass()).append("listClass", getListClass()) .append("dictType", getDictType()).append("cssClass", getCssClass()).append("listClass", getListClass())
.append("isDefault", getIsDefault()).append("status", getStatus()).append("pyStr", getPyStr()) .append("isDefault", getIsDefault()) .append("status", getStatus()).append("pyStr", getPyStr())
.append("dictValueEn", getDictValueEn()).append("dictValueVi", getDictValueVi())
.append("createBy", getCreateBy()).append("createTime", getCreateTime()).append("updateBy", getUpdateBy()) .append("createBy", getCreateBy()).append("createTime", getCreateTime()).append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime()).append("remark", getRemark()).toString(); .append("updateTime", getUpdateTime()).append("remark", getRemark()).toString();
} }

View File

@@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.springframework.context.i18n.LocaleContextHolder;
/** /**
* 字典工具类 * 字典工具类
@@ -76,7 +78,7 @@ public class DictUtils {
} }
/** /**
* 根据字典类型和字典值获取字典标签 * 根据字典类型和字典值获取字典标签(支持国际化)
* *
* @param dictType 字典类型 * @param dictType 字典类型
* @param dictValue 字典值 * @param dictValue 字典值
@@ -89,6 +91,36 @@ public class DictUtils {
return getDictLabel(dictType, dictValue, SEPARATOR); return getDictLabel(dictType, dictValue, SEPARATOR);
} }
/**
* 根据字典类型和字典值获取国际化字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @return 国际化字典标签
*/
public static String getDictLabelI18n(String dictType, String dictValue) {
if (StringUtils.isEmpty(dictValue)) {
return StringUtils.EMPTY;
}
List<SysDictData> datas = getDictCache(dictType);
if (StringUtils.isNull(datas)) {
return StringUtils.EMPTY;
}
Locale locale = LocaleContextHolder.getLocale();
String lang = locale.getLanguage();
for (SysDictData dict : datas) {
if (dictValue.equals(dict.getDictValue())) {
if ("en".equals(lang) && dict.getDictValueEn() != null) {
return dict.getDictValueEn();
} else if ("vi".equals(lang) && dict.getDictValueVi() != null) {
return dict.getDictValueVi();
}
return dict.getDictLabel();
}
}
return StringUtils.EMPTY;
}
/** /**
* 根据字典类型和字典标签获取字典值 * 根据字典类型和字典标签获取字典值
* *

View File

@@ -176,7 +176,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
children.setQuery(menu.getQuery()); children.setQuery(menu.getQuery());
childrenList.add(children); childrenList.add(children);
router.setChildren(childrenList); router.setChildren(childrenList);
} else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) { } else if ((menu.getParentId() == null || menu.getParentId() == 0) && isInnerLink(menu)) {
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, null, menu.getVisible())); router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, null, menu.getVisible()));
router.setPath("/"); router.setPath("/");
List<RouterVo> childrenList = new ArrayList<RouterVo>(); List<RouterVo> childrenList = new ArrayList<RouterVo>();
@@ -524,11 +524,11 @@ public class SysMenuServiceImpl implements ISysMenuService {
public String getRouterPath(SysMenu menu) { public String getRouterPath(SysMenu menu) {
String routerPath = menu.getPath(); String routerPath = menu.getPath();
// 内链打开外网方式 // 内链打开外网方式
if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) { if (menu.getParentId() != null && menu.getParentId() != 0 && isInnerLink(menu)) {
routerPath = innerLinkReplaceEach(routerPath); routerPath = innerLinkReplaceEach(routerPath);
} }
// 非外链并且是一级目录(类型为目录) // 非外链并且是一级目录(类型为目录)
if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) if ((menu.getParentId() == null || menu.getParentId() == 0) && UserConstants.TYPE_DIR.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame())) { && UserConstants.NO_FRAME.equals(menu.getIsFrame())) {
routerPath = "/" + menu.getPath(); routerPath = "/" + menu.getPath();
} }
@@ -549,7 +549,8 @@ public class SysMenuServiceImpl implements ISysMenuService {
String component = UserConstants.LAYOUT; String component = UserConstants.LAYOUT;
if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) { if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) {
component = menu.getComponent(); component = menu.getComponent();
} else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId() != null
&& menu.getParentId() != 0
&& isInnerLink(menu)) { && isInnerLink(menu)) {
component = UserConstants.INNER_LINK; component = UserConstants.INNER_LINK;
} else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) { } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) {
@@ -565,7 +566,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 结果 * @return 结果
*/ */
public boolean isMenuFrame(SysMenu menu) { public boolean isMenuFrame(SysMenu menu) {
return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) return (menu.getParentId() == null || menu.getParentId() == 0) && UserConstants.TYPE_MENU.equals(menu.getMenuType())
&& menu.getIsFrame().equals(UserConstants.NO_FRAME); && menu.getIsFrame().equals(UserConstants.NO_FRAME);
} }
@@ -586,7 +587,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
* @return 结果 * @return 结果
*/ */
public boolean isParentView(SysMenu menu) { public boolean isParentView(SysMenu menu) {
return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); return menu.getParentId() != null && menu.getParentId() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType());
} }
/** /**
@@ -634,7 +635,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
Iterator<SysMenu> it = list.iterator(); Iterator<SysMenu> it = list.iterator();
while (it.hasNext()) { while (it.hasNext()) {
SysMenu n = (SysMenu)it.next(); SysMenu n = (SysMenu)it.next();
if (n.getParentId().longValue() == t.getMenuId().longValue()) { if (n.getParentId() != null && n.getParentId().longValue() == t.getMenuId().longValue()) {
tlist.add(n); tlist.add(n);
} }
} }

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.administration.domain.Device; import com.healthlink.his.administration.domain.Device;
import com.healthlink.his.administration.domain.DeviceDefinition; import com.healthlink.his.administration.domain.DeviceDefinition;
@@ -187,10 +188,10 @@ public class LisConfigManageAppServiceImpl implements ILisConfigManageAppServic
manageDto.getActivityDefDeviceDefs().size(), manageDto.getActivityDefDeviceDefs().size(),
manageDto.getActivityDefObservationDefs().size(), manageDto.getActivityDefObservationDefs().size(),
manageDto.getActivityDefSpecimenDefs().size()); manageDto.getActivityDefSpecimenDefs().size());
return R.ok("保存成功"); return R.ok(MessageUtils.message("msg.success"));
} catch (Exception e) { } catch (Exception e) {
log.error("保存检验项目设置失败id={}, error={}", manageDto.getId(), e.getMessage(), e); log.error("保存检验项目设置失败id={}, error={}", manageDto.getId(), e.getMessage(), e);
return R.fail("保存失败:" + e.getMessage()); return R.fail(MessageUtils.message("his.lis.save_failed", e.getMessage()));
} }
} }

View File

@@ -98,16 +98,16 @@ public class ObservationManageAppServiceImpl implements IObservationManageAppSer
if (result) { if (result) {
log.info("保存检验项目成功name={}, code={}, id={}", log.info("保存检验项目成功name={}, code={}, id={}",
Observation.getName(), Observation.getCode(), Observation.getId()); Observation.getName(), Observation.getCode(), Observation.getId());
return R.ok("添加成功"); return R.ok(MessageUtils.message("msg.success"));
} else { } else {
log.warn("保存检验项目失败name={}, code={}", log.warn("保存检验项目失败name={}, code={}",
Observation.getName(), Observation.getCode()); Observation.getName(), Observation.getCode());
return R.fail("添加失败:保存操作未成功"); return R.fail(MessageUtils.message("msg.failure"));
} }
} catch (Exception e) { } catch (Exception e) {
log.error("保存检验项目异常name={}, code={}, error={}", log.error("保存检验项目异常name={}, code={}, error={}",
Observation.getName(), Observation.getCode(), e.getMessage(), e); Observation.getName(), Observation.getCode(), e.getMessage(), e);
return R.fail("添加失败:" + e.getMessage()); return R.fail(MessageUtils.message("his.lis.add_failed", e.getMessage()));
} }
} }

View File

@@ -85,7 +85,7 @@ public class SpecimenManageAppServiceImpl implements ISpecimenManageAppService {
@Override @Override
public R<?> updateOrAddSpecimen(SpecimenDefinition specimenDefinition) { public R<?> updateOrAddSpecimen(SpecimenDefinition specimenDefinition) {
specimenDefinitionService.saveOrUpdate(specimenDefinition); specimenDefinitionService.saveOrUpdate(specimenDefinition);
return R.ok(" 添加成功"); return R.ok(MessageUtils.message("msg.success"));
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package com.healthlink.his.web.aidiagnosis.appservice.impl; package com.healthlink.his.web.aidiagnosis.appservice.impl;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion; import com.healthlink.his.aidiagnosis.domain.AiDiagnosisSuggestion;
import com.healthlink.his.aidiagnosis.service.IAiDiagnosisService; import com.healthlink.his.aidiagnosis.service.IAiDiagnosisService;
import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService; import com.healthlink.his.web.aidiagnosis.appservice.IAiDiagnosisAppService;
@@ -27,10 +28,10 @@ public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService {
@Override @Override
public R<?> suggest(Long encounterId, Long patientId, String symptomText, String source) { public R<?> suggest(Long encounterId, Long patientId, String symptomText, String source) {
if (encounterId == null || patientId == null) { if (encounterId == null || patientId == null) {
return R.fail(400, "就诊ID和患者ID不能为空"); return R.fail(400, MessageUtils.message("his.ai.encounter_patient_required"));
} }
if (symptomText == null || symptomText.trim().isEmpty()) { if (symptomText == null || symptomText.trim().isEmpty()) {
return R.fail(400, "症状描述不能为空"); return R.fail(400, MessageUtils.message("his.ai.symptom_required"));
} }
String suggestionText = generateSuggestion(symptomText); String suggestionText = generateSuggestion(symptomText);
@@ -61,7 +62,7 @@ public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService {
@Override @Override
public R<?> getHistory(Long patientId) { public R<?> getHistory(Long patientId) {
if (patientId == null) { if (patientId == null) {
return R.fail(400, "患者ID不能为空"); return R.fail(400, MessageUtils.message("his.ai.patient_id_required"));
} }
List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByPatientId(patientId); List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByPatientId(patientId);
return R.ok(history); return R.ok(history);
@@ -70,7 +71,7 @@ public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService {
@Override @Override
public R<?> getHistoryByEncounter(Long encounterId) { public R<?> getHistoryByEncounter(Long encounterId) {
if (encounterId == null) { if (encounterId == null) {
return R.fail(400, "就诊ID不能为空"); return R.fail(400, MessageUtils.message("his.ai.encounter_id_required"));
} }
List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByEncounterId(encounterId); List<AiDiagnosisSuggestion> history = aiDiagnosisService.findByEncounterId(encounterId);
return R.ok(history); return R.ok(history);
@@ -79,15 +80,15 @@ public class AiDiagnosisAppServiceImpl implements IAiDiagnosisAppService {
@Override @Override
public R<?> acceptSuggestion(Long id) { public R<?> acceptSuggestion(Long id) {
if (id == null) { if (id == null) {
return R.fail(400, "建议ID不能为空"); return R.fail(400, MessageUtils.message("his.ai.suggestion_id_required"));
} }
AiDiagnosisSuggestion suggestion = aiDiagnosisService.getById(id); AiDiagnosisSuggestion suggestion = aiDiagnosisService.getById(id);
if (suggestion == null) { if (suggestion == null) {
return R.fail(404, "建议不存在"); return R.fail(404, MessageUtils.message("his.ai.suggestion_not_found"));
} }
suggestion.setAccepted(true); suggestion.setAccepted(true);
aiDiagnosisService.updateById(suggestion); aiDiagnosisService.updateById(suggestion);
return R.ok(null, "已采纳"); return R.ok(null, MessageUtils.message("his.ai.adopted"));
} }
private String generateSuggestion(String symptomText) { private String generateSuggestion(String symptomText) {

View File

@@ -0,0 +1,106 @@
package com.healthlink.his.web.anesthesia.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.anesthesia.domain.AnesthesiaFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaQualityControl;
import com.healthlink.his.anesthesia.domain.AnesthesiaSpecimen;
import com.healthlink.his.anesthesia.service.IAnesthesiaFollowupService;
import com.healthlink.his.anesthesia.service.IAnesthesiaQualityControlService;
import com.healthlink.his.anesthesia.service.IAnesthesiaSpecimenService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.Map;
@RestController
@RequestMapping("/anesthesia-enhanced")
@AllArgsConstructor
@Tag(name = "麻醉扩展-标本/随访/质控")
public class AnesthesiaEnhancedCrudController {
private final IAnesthesiaSpecimenService specimenService;
private final IAnesthesiaFollowupService followupService;
private final IAnesthesiaQualityControlService qcService;
@GetMapping("/specimen/page")
@Operation(summary = "标本分页")
public R<?> getSpecimenPage(
@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String patientName) {
LambdaQueryWrapper<AnesthesiaSpecimen> w = new LambdaQueryWrapper<>();
w.like(patientName != null, AnesthesiaSpecimen::getPatientName, patientName)
.orderByDesc(AnesthesiaSpecimen::getCreateTime);
return R.ok(specimenService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/specimen/add")
@Operation(summary = "新增标本")
public R<?> addSpecimen(@RequestBody AnesthesiaSpecimen specimen) {
specimen.setCreateTime(new Date());
specimenService.save(specimen);
return R.ok(specimen);
}
@PostMapping("/specimen/report")
@Operation(summary = "报告标本结果")
public R<?> reportSpecimen(@RequestBody AnesthesiaSpecimen specimen) {
specimen.setReportTime(new Date());
specimenService.updateById(specimen);
return R.ok(specimen);
}
@GetMapping("/followup/page")
@Operation(summary = "随访分页")
public R<?> getFollowupPage(
@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<AnesthesiaFollowup> w = new LambdaQueryWrapper<>();
w.orderByDesc(AnesthesiaFollowup::getFollowupDate);
return R.ok(followupService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/followup/add")
@Operation(summary = "新增随访")
public R<?> addFollowup(@RequestBody AnesthesiaFollowup followup) {
followup.setCreateTime(new Date());
followupService.save(followup);
return R.ok(followup);
}
@GetMapping("/qc/page")
@Operation(summary = "质控分页")
public R<?> getQcPage(
@RequestParam(defaultValue = "1") Integer pageNo,
@RequestParam(defaultValue = "10") Integer pageSize) {
LambdaQueryWrapper<AnesthesiaQualityControl> w = new LambdaQueryWrapper<>();
w.orderByDesc(AnesthesiaQualityControl::getCreateTime);
return R.ok(qcService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/qc/add")
@Operation(summary = "新增质控记录")
public R<?> addQc(@RequestBody AnesthesiaQualityControl qc) {
qc.setCreateTime(new Date());
qcService.save(qc);
return R.ok(qc);
}
@GetMapping("/qc/stats")
@Operation(summary = "质控统计")
public R<?> getQcStats() {
long total = qcService.count();
long normal = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
.eq(AnesthesiaQualityControl::getRiskLevel, "NORMAL"));
long warning = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
.eq(AnesthesiaQualityControl::getRiskLevel, "WARNING"));
long critical = qcService.count(new LambdaQueryWrapper<AnesthesiaQualityControl>()
.eq(AnesthesiaQualityControl::getRiskLevel, "CRITICAL"));
return R.ok(Map.of("total", total, "normal", normal, "warning", warning, "critical", critical));
}
}

View File

@@ -4,6 +4,7 @@ import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.appointmentmanage.domain.AppointmentConfig; import com.healthlink.his.appointmentmanage.domain.AppointmentConfig;
import com.healthlink.his.appointmentmanage.service.IAppointmentConfigService; import com.healthlink.his.appointmentmanage.service.IAppointmentConfigService;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.appointmentmanage.appservice.IAppointmentConfigAppService; import com.healthlink.his.web.appointmentmanage.appservice.IAppointmentConfigAppService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -26,7 +27,7 @@ public class AppointmentConfigAppServiceImpl implements IAppointmentConfigAppSer
// 获取当前登录用户的机构ID // 获取当前登录用户的机构ID
Integer tenantId = SecurityUtils.getLoginUser().getTenantId(); Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
if (tenantId == null) { if (tenantId == null) {
return R.fail("获取机构信息失败"); return R.fail(MessageUtils.message("his.appt_config.get_org_failed"));
} }
AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId); AppointmentConfig config = appointmentConfigService.getConfigByTenantId(tenantId);
@@ -38,7 +39,7 @@ public class AppointmentConfigAppServiceImpl implements IAppointmentConfigAppSer
// 获取当前登录用户的机构ID // 获取当前登录用户的机构ID
Integer tenantId = SecurityUtils.getLoginUser().getTenantId(); Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
if (tenantId == null) { if (tenantId == null) {
return R.fail("获取机构信息失败"); return R.fail(MessageUtils.message("his.appt_config.get_org_failed"));
} }
// 查询是否已存在配置 // 查询是否已存在配置

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.healthlink.his.appointmentmanage.domain.ClinicRoom; import com.healthlink.his.appointmentmanage.domain.ClinicRoom;
import com.healthlink.his.appointmentmanage.service.IClinicRoomService; import com.healthlink.his.appointmentmanage.service.IClinicRoomService;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.appointmentmanage.appservice.IClinicRoomAppService; import com.healthlink.his.web.appointmentmanage.appservice.IClinicRoomAppService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -38,7 +39,7 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
public R<?> selectClinicRoomById(Long id) { public R<?> selectClinicRoomById(Long id) {
ClinicRoom clinicRoom = clinicRoomService.selectClinicRoomById(id); ClinicRoom clinicRoom = clinicRoomService.selectClinicRoomById(id);
if (clinicRoom == null) { if (clinicRoom == null) {
return R.fail(404, "诊室不存在"); return R.fail(404, MessageUtils.message("his.clinic_room.not_found"));
} }
return R.ok(clinicRoom); return R.ok(clinicRoom);
} }
@@ -47,24 +48,24 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
public R<?> insertClinicRoom(ClinicRoom clinicRoom) { public R<?> insertClinicRoom(ClinicRoom clinicRoom) {
// 数据校验 // 数据校验
if (ObjectUtil.isEmpty(clinicRoom.getRoomName())) { if (ObjectUtil.isEmpty(clinicRoom.getRoomName())) {
return R.fail(400, "诊室名称不能为空"); return R.fail(400, MessageUtils.message("his.clinic_room.name_required"));
} }
if (ObjectUtil.isEmpty(clinicRoom.getDepartment())) { if (ObjectUtil.isEmpty(clinicRoom.getDepartment())) {
return R.fail(400, "科室名称不能为空"); return R.fail(400, MessageUtils.message("his.clinic_room.dept_name_required"));
} }
if (clinicRoom.getRoomName().length() > 50) { if (clinicRoom.getRoomName().length() > 50) {
return R.fail(400, "诊室名称长度不能超过50个字符"); return R.fail(400, MessageUtils.message("his.clinic_room.name_too_long"));
} }
if (clinicRoom.getRemarks() != null && clinicRoom.getRemarks().length() > 500) { if (clinicRoom.getRemarks() != null && clinicRoom.getRemarks().length() > 500) {
return R.fail(400, "备注长度不能超过500个字符"); return R.fail(400, MessageUtils.message("his.clinic_room.remark_too_long"));
} }
// 新增诊室 // 新增诊室
int result = clinicRoomService.insertClinicRoom(clinicRoom); int result = clinicRoomService.insertClinicRoom(clinicRoom);
if (result > 0) { if (result > 0) {
return R.ok(null, "新增成功"); return R.ok(null, MessageUtils.message("msg.success"));
} else { } else {
return R.fail("新增失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
} }
@@ -72,33 +73,33 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
public R<?> updateClinicRoom(ClinicRoom clinicRoom) { public R<?> updateClinicRoom(ClinicRoom clinicRoom) {
// 数据校验 // 数据校验
if (ObjectUtil.isEmpty(clinicRoom.getId())) { if (ObjectUtil.isEmpty(clinicRoom.getId())) {
return R.fail(400, "诊室ID不能为空"); return R.fail(400, MessageUtils.message("his.clinic_room.id_required"));
} }
if (ObjectUtil.isEmpty(clinicRoom.getRoomName())) { if (ObjectUtil.isEmpty(clinicRoom.getRoomName())) {
return R.fail(400, "诊室名称不能为空"); return R.fail(400, MessageUtils.message("his.clinic_room.name_required"));
} }
if (ObjectUtil.isEmpty(clinicRoom.getDepartment())) { if (ObjectUtil.isEmpty(clinicRoom.getDepartment())) {
return R.fail(400, "科室名称不能为空"); return R.fail(400, MessageUtils.message("his.clinic_room.dept_name_required"));
} }
if (clinicRoom.getRoomName().length() > 50) { if (clinicRoom.getRoomName().length() > 50) {
return R.fail(400, "诊室名称长度不能超过50个字符"); return R.fail(400, MessageUtils.message("his.clinic_room.name_too_long"));
} }
if (clinicRoom.getRemarks() != null && clinicRoom.getRemarks().length() > 500) { if (clinicRoom.getRemarks() != null && clinicRoom.getRemarks().length() > 500) {
return R.fail(400, "备注长度不能超过500个字符"); return R.fail(400, MessageUtils.message("his.clinic_room.remark_too_long"));
} }
// 检查诊室是否存在 // 检查诊室是否存在
ClinicRoom existingClinicRoom = clinicRoomService.selectClinicRoomById(clinicRoom.getId()); ClinicRoom existingClinicRoom = clinicRoomService.selectClinicRoomById(clinicRoom.getId());
if (existingClinicRoom == null) { if (existingClinicRoom == null) {
return R.fail(404, "诊室不存在"); return R.fail(404, MessageUtils.message("his.clinic_room.not_found"));
} }
// 更新诊室 // 更新诊室
int result = clinicRoomService.updateClinicRoom(clinicRoom); int result = clinicRoomService.updateClinicRoom(clinicRoom);
if (result > 0) { if (result > 0) {
return R.ok(null, "修改成功"); return R.ok(null, MessageUtils.message("msg.success"));
} else { } else {
return R.fail("修改失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
} }
@@ -107,15 +108,15 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
// 检查诊室是否存在 // 检查诊室是否存在
ClinicRoom existingClinicRoom = clinicRoomService.selectClinicRoomById(id); ClinicRoom existingClinicRoom = clinicRoomService.selectClinicRoomById(id);
if (existingClinicRoom == null) { if (existingClinicRoom == null) {
return R.fail(404, "诊室不存在"); return R.fail(404, MessageUtils.message("his.clinic_room.not_found"));
} }
// 删除诊室 // 删除诊室
int result = clinicRoomService.deleteClinicRoomById(id); int result = clinicRoomService.deleteClinicRoomById(id);
if (result > 0) { if (result > 0) {
return R.ok(null, "删除成功"); return R.ok(null, MessageUtils.message("msg.success"));
} else { } else {
return R.fail("删除失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
} }
} }

View File

@@ -8,6 +8,7 @@ import com.core.common.core.domain.R;
import com.healthlink.his.appointmentmanage.domain.DeptAppointmentHours; import com.healthlink.his.appointmentmanage.domain.DeptAppointmentHours;
import com.healthlink.his.appointmentmanage.mapper.DeptAppointmentHoursMapper; import com.healthlink.his.appointmentmanage.mapper.DeptAppointmentHoursMapper;
import com.healthlink.his.appointmentmanage.service.IDeptAppointmentHoursService; import com.healthlink.his.appointmentmanage.service.IDeptAppointmentHoursService;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.appointmentmanage.appservice.IDeptAppointmentHoursAppService; import com.healthlink.his.web.appointmentmanage.appservice.IDeptAppointmentHoursAppService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -44,11 +45,11 @@ public class DeptAppointmentHoursAppServiceImpl implements IDeptAppointmentHours
@Override @Override
public R<?> getDeptAppthoursDetail(Long id) { public R<?> getDeptAppthoursDetail(Long id) {
if (ObjectUtil.isNull(id)) { if (ObjectUtil.isNull(id)) {
return R.fail("ID不能为空"); return R.fail(MessageUtils.message("his.dept_hours.id_required"));
} }
DeptAppointmentHours deptAppointmentHours = deptAppointmentHoursService.getById(id); DeptAppointmentHours deptAppointmentHours = deptAppointmentHoursService.getById(id);
if (ObjectUtil.isNull(deptAppointmentHours)) { if (ObjectUtil.isNull(deptAppointmentHours)) {
return R.fail("数据不存在"); return R.fail(MessageUtils.message("his.dept_hours.data_not_found"));
} }
return R.ok(deptAppointmentHours); return R.ok(deptAppointmentHours);
} }
@@ -56,13 +57,13 @@ public class DeptAppointmentHoursAppServiceImpl implements IDeptAppointmentHours
@Override @Override
public R<?> addDeptAppthours(DeptAppointmentHours deptAppointmentHours) { public R<?> addDeptAppthours(DeptAppointmentHours deptAppointmentHours) {
if (ObjectUtil.isNull(deptAppointmentHours)) { if (ObjectUtil.isNull(deptAppointmentHours)) {
return R.fail("数据不能为空"); return R.fail(MessageUtils.message("his.dept_hours.data_required"));
} }
if (StrUtil.isBlank(deptAppointmentHours.getInstitution())) { if (StrUtil.isBlank(deptAppointmentHours.getInstitution())) {
return R.fail("所属机构不能为空"); return R.fail(MessageUtils.message("his.dept_hours.org_required"));
} }
if (StrUtil.isBlank(deptAppointmentHours.getDepartment())) { if (StrUtil.isBlank(deptAppointmentHours.getDepartment())) {
return R.fail("科室名称不能为空"); return R.fail(MessageUtils.message("his.dept_hours.dept_name_required"));
} }
deptAppointmentHours.setCreatedTime(LocalDateTime.now()); deptAppointmentHours.setCreatedTime(LocalDateTime.now());
@@ -73,12 +74,12 @@ public class DeptAppointmentHoursAppServiceImpl implements IDeptAppointmentHours
@Override @Override
public R<?> updateDeptAppthours(DeptAppointmentHours deptAppointmentHours) { public R<?> updateDeptAppthours(DeptAppointmentHours deptAppointmentHours) {
if (ObjectUtil.isNull(deptAppointmentHours) || ObjectUtil.isNull(deptAppointmentHours.getId())) { if (ObjectUtil.isNull(deptAppointmentHours) || ObjectUtil.isNull(deptAppointmentHours.getId())) {
return R.fail("ID不能为空"); return R.fail(MessageUtils.message("his.dept_hours.id_required"));
} }
DeptAppointmentHours existing = deptAppointmentHoursService.getById(deptAppointmentHours.getId()); DeptAppointmentHours existing = deptAppointmentHoursService.getById(deptAppointmentHours.getId());
if (ObjectUtil.isNull(existing)) { if (ObjectUtil.isNull(existing)) {
return R.fail("数据不存在"); return R.fail(MessageUtils.message("his.dept_hours.data_not_found"));
} }
deptAppointmentHours.setUpdatedTime(LocalDateTime.now()); deptAppointmentHours.setUpdatedTime(LocalDateTime.now());
@@ -89,12 +90,12 @@ public class DeptAppointmentHoursAppServiceImpl implements IDeptAppointmentHours
@Override @Override
public R<?> deleteDeptAppthours(Long id) { public R<?> deleteDeptAppthours(Long id) {
if (ObjectUtil.isNull(id)) { if (ObjectUtil.isNull(id)) {
return R.fail("ID不能为空"); return R.fail(MessageUtils.message("his.dept_hours.id_required"));
} }
DeptAppointmentHours existing = deptAppointmentHoursService.getById(id); DeptAppointmentHours existing = deptAppointmentHoursService.getById(id);
if (ObjectUtil.isNull(existing)) { if (ObjectUtil.isNull(existing)) {
return R.fail("数据不存在"); return R.fail(MessageUtils.message("his.dept_hours.data_not_found"));
} }
boolean remove = deptAppointmentHoursService.removeById(id); boolean remove = deptAppointmentHoursService.removeById(id);

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.appointmentmanage.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.common.enums.SlotStatus; import com.healthlink.his.common.enums.SlotStatus;
import com.healthlink.his.appointmentmanage.domain.DoctorSchedule; import com.healthlink.his.appointmentmanage.domain.DoctorSchedule;
@@ -92,11 +93,11 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
@Override @Override
public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) { public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) {
if (ObjectUtil.isEmpty(doctorSchedule)) { if (ObjectUtil.isEmpty(doctorSchedule)) {
return R.fail("医生排班不能为空"); return R.fail(MessageUtils.message("his.schedule.doctor_required"));
} }
if (doctorSchedule.getLimitNumber() == null || doctorSchedule.getLimitNumber() <= 0) { if (doctorSchedule.getLimitNumber() == null || doctorSchedule.getLimitNumber() <= 0) {
return R.fail("限号数量必须大于0"); return R.fail(MessageUtils.message("his.schedule.limit_must_positive"));
} }
// 创建新对象排除id字段数据库id列是GENERATED ALWAYS由数据库自动生成 // 创建新对象排除id字段数据库id列是GENERATED ALWAYS由数据库自动生成
@@ -151,24 +152,24 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
throw new RuntimeException("创建号源池失败"); throw new RuntimeException("创建号源池失败");
} }
} else { } else {
return R.fail("保存排班信息失败"); return R.fail(MessageUtils.message("his.schedule.save_failed"));
} }
} }
@Override @Override
public R<?> addDoctorScheduleWithDate(DoctorSchedule doctorSchedule, String scheduledDate) { public R<?> addDoctorScheduleWithDate(DoctorSchedule doctorSchedule, String scheduledDate) {
if (ObjectUtil.isEmpty(doctorSchedule)) { if (ObjectUtil.isEmpty(doctorSchedule)) {
return R.fail("医生排班不能为空"); return R.fail(MessageUtils.message("his.schedule.doctor_required"));
} }
if (doctorSchedule.getLimitNumber() == null || doctorSchedule.getLimitNumber() <= 0) { if (doctorSchedule.getLimitNumber() == null || doctorSchedule.getLimitNumber() <= 0) {
return R.fail("限号数量必须大于0"); return R.fail(MessageUtils.message("his.schedule.limit_must_positive"));
} }
// 检查结束时间必须大于开始时间 // 检查结束时间必须大于开始时间
if (doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) { if (doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) {
if (!doctorSchedule.getStartTime().isBefore(doctorSchedule.getEndTime())) { if (!doctorSchedule.getStartTime().isBefore(doctorSchedule.getEndTime())) {
return R.fail("结束时间必须大于开始时间"); return R.fail(MessageUtils.message("his.schedule.end_after_start"));
} }
} }
@@ -183,9 +184,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
doctorSchedule.getEndTime() doctorSchedule.getEndTime()
); );
if (hasOverlap) { if (hasOverlap) {
return R.fail("该医生在 " + scheduledDate + "" return R.fail(MessageUtils.message("his.schedule.time_overlap", scheduledDate, doctorSchedule.getStartTime(), doctorSchedule.getEndTime()));
+ doctorSchedule.getStartTime() + "-" + doctorSchedule.getEndTime()
+ " 时间段与已有排班重叠,不能重复添加");
} }
} }
@@ -237,7 +236,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
throw new RuntimeException("创建号源池失败"); throw new RuntimeException("创建号源池失败");
} }
} else { } else {
return R.fail("保存排班信息失败"); return R.fail(MessageUtils.message("his.schedule.save_failed"));
} }
} }
@@ -245,12 +244,12 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
@Override @Override
public R<?> updateDoctorSchedule(DoctorSchedule doctorSchedule) { public R<?> updateDoctorSchedule(DoctorSchedule doctorSchedule) {
if (ObjectUtil.isEmpty(doctorSchedule) || ObjectUtil.isEmpty(doctorSchedule.getId())) { if (ObjectUtil.isEmpty(doctorSchedule) || ObjectUtil.isEmpty(doctorSchedule.getId())) {
return R.fail("医生排班ID不能为空"); return R.fail(MessageUtils.message("his.schedule.id_required"));
} }
int result = doctorScheduleMapper.updateDoctorSchedule(doctorSchedule); int result = doctorScheduleMapper.updateDoctorSchedule(doctorSchedule);
if (result <= 0) { if (result <= 0) {
return R.fail("更新排班信息失败"); return R.fail(MessageUtils.message("his.schedule.update_failed"));
} }
// 同步更新号源池,避免查询联表时医生/诊室等字段看起来“未更新” // 同步更新号源池,避免查询联表时医生/诊室等字段看起来“未更新”
@@ -488,7 +487,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
@Override @Override
public R<?> removeDoctorSchedule(Integer doctorScheduleId) { public R<?> removeDoctorSchedule(Integer doctorScheduleId) {
if (doctorScheduleId == null) { if (doctorScheduleId == null) {
return R.fail("排班id不能为空"); return R.fail(MessageUtils.message("his.schedule.id_required"));
} }
// 1. 根据排班ID找到关联的号源池 // 1. 根据排班ID找到关联的号源池
@@ -505,7 +504,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
.in("status", SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue(), .in("status", SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue(),
SlotStatus.CHECKED_IN.getValue())); SlotStatus.CHECKED_IN.getValue()));
if (appointmentCount > 0) { if (appointmentCount > 0) {
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。"); return R.fail(MessageUtils.message("his.schedule.has_appointments_no_delete"));
} }
// 2. 根据号源池ID找到所有关联的号源槽 // 2. 根据号源池ID找到所有关联的号源槽

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.healthlink.his.appointmentmanage.domain.SchedulePool; import com.healthlink.his.appointmentmanage.domain.SchedulePool;
import com.healthlink.his.appointmentmanage.service.ISchedulePoolService; import com.healthlink.his.appointmentmanage.service.ISchedulePoolService;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.appointmentmanage.appservice.ISchedulePoolAppService; import com.healthlink.his.web.appointmentmanage.appservice.ISchedulePoolAppService;
import com.healthlink.his.web.appointmentmanage.dto.SchedulePoolDto; import com.healthlink.his.web.appointmentmanage.dto.SchedulePoolDto;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -20,7 +21,7 @@ public class SchedulePoolAppServiceImpl implements ISchedulePoolAppService {
public R<?> addSchedulePool(SchedulePoolDto schedulePoolDto) { public R<?> addSchedulePool(SchedulePoolDto schedulePoolDto) {
//1.数据检验 //1.数据检验
if(ObjectUtil.isNull(schedulePoolDto)){ if(ObjectUtil.isNull(schedulePoolDto)){
return R.fail("号源不能为空"); return R.fail(MessageUtils.message("his.schedule_pool.slot_required"));
} }
//2.封装实体 //2.封装实体
SchedulePool schedulePool = new SchedulePool(); SchedulePool schedulePool = new SchedulePool();

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.appointmentmanage.appservice.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.administration.domain.Patient; import com.healthlink.his.administration.domain.Patient;
import com.healthlink.his.administration.service.IPatientService; import com.healthlink.his.administration.service.IPatientService;
import com.healthlink.his.appointmentmanage.mapper.ScheduleSlotMapper; import com.healthlink.his.appointmentmanage.mapper.ScheduleSlotMapper;
@@ -47,17 +48,17 @@ public class TicketAppServiceImpl implements ITicketAppService {
public R<?> bookTicket(com.healthlink.his.appointmentmanage.domain.AppointmentBookDTO dto) { public R<?> bookTicket(com.healthlink.his.appointmentmanage.domain.AppointmentBookDTO dto) {
Long slotId = dto.getSlotId(); Long slotId = dto.getSlotId();
if (slotId == null) { if (slotId == null) {
return R.fail("参数校验失败:缺少排班槽位唯一标识"); return R.fail(MessageUtils.message("his.ticket.slot_id_required"));
} }
try { try {
int result = ticketService.bookTicket(dto); int result = ticketService.bookTicket(dto);
if (result > 0) { if (result > 0) {
return R.ok("预约成功!号源已安全锁定。"); return R.ok(MessageUtils.message("his.ticket.appointment_success"));
} }
return R.fail("预约挂单核发失败"); return R.fail(MessageUtils.message("his.ticket.issue_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("大厅挂号捕获系统异常", e); log.error("大厅挂号捕获系统异常", e);
return R.fail("系统异常:" + e.getMessage()); return R.fail(MessageUtils.message("his.common.system_error", e.getMessage()));
} }
} }
@@ -70,7 +71,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
@Override @Override
public R<?> cancelTicket(Long slotId) { public R<?> cancelTicket(Long slotId) {
if (slotId == null) { if (slotId == null) {
return R.fail("参数错误"); return R.fail(MessageUtils.message("his.ticket.param_error"));
} }
try { try {
int result = ticketService.cancelTicket(slotId); int result = ticketService.cancelTicket(slotId);
@@ -89,7 +90,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
@Override @Override
public R<?> checkInTicket(Long slotId) { public R<?> checkInTicket(Long slotId) {
if (slotId == null) { if (slotId == null) {
return R.fail("参数错误"); return R.fail(MessageUtils.message("his.ticket.param_error"));
} }
try { try {
int result = ticketService.checkInTicket(slotId); int result = ticketService.checkInTicket(slotId);
@@ -108,7 +109,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
@Override @Override
public R<?> cancelConsultation(Long slotId) { public R<?> cancelConsultation(Long slotId) {
if (slotId == null) { if (slotId == null) {
return R.fail("参数错误"); return R.fail(MessageUtils.message("his.ticket.param_error"));
} }
try { try {
int result = ticketService.cancelConsultation(slotId); int result = ticketService.cancelConsultation(slotId);

View File

@@ -105,7 +105,7 @@ public class LocationAppServiceImpl implements ILocationAppService {
locationService.updateLocation(location); locationService.updateLocation(location);
} }
} }
return R.ok("启用成功"); return R.ok(MessageUtils.message("his.location.enable_success"));
} }
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00010, null)); return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00010, null));
} }
@@ -126,27 +126,27 @@ public class LocationAppServiceImpl implements ILocationAppService {
List<EncounterLocation> encounterLocationList = List<EncounterLocation> encounterLocationList =
encounterLocationService.getEncounterLocationInfo(null, location.getId()); encounterLocationService.getEncounterLocationInfo(null, location.getId());
if (encounterLocationList != null && !encounterLocationList.isEmpty()) { if (encounterLocationList != null && !encounterLocationList.isEmpty()) {
return R.fail("有被占用的床位,不可停用"); return R.fail(MessageUtils.message("his.location.bed_occupied"));
} }
} else if (LocationForm.HOUSE.getValue().equals(location.getFormEnum())) { } else if (LocationForm.HOUSE.getValue().equals(location.getFormEnum())) {
// 检查病房下是否有启用的病床 // 检查病房下是否有启用的病床
List<com.healthlink.his.web.common.dto.LocationDto> activeBeds = List<com.healthlink.his.web.common.dto.LocationDto> activeBeds =
commonService.getChildLocation(location.getId(), LocationForm.BED.getValue()); commonService.getChildLocation(location.getId(), LocationForm.BED.getValue());
if ((activeBeds != null && !activeBeds.isEmpty())) { if ((activeBeds != null && !activeBeds.isEmpty())) {
return R.fail("病房下有启用或被占用的床位,不可停用"); return R.fail(MessageUtils.message("his.location.house_has_active_beds"));
} }
} else if (LocationForm.WARD.getValue().equals(location.getFormEnum())) { } else if (LocationForm.WARD.getValue().equals(location.getFormEnum())) {
// 检查病区下是否有启用的病房 // 检查病区下是否有启用的病房
List<com.healthlink.his.web.common.dto.LocationDto> activeHouses = List<com.healthlink.his.web.common.dto.LocationDto> activeHouses =
commonService.getChildLocation(location.getId(), LocationForm.HOUSE.getValue()); commonService.getChildLocation(location.getId(), LocationForm.HOUSE.getValue());
if ((activeHouses != null && !activeHouses.isEmpty())) { if ((activeHouses != null && !activeHouses.isEmpty())) {
return R.fail("病区下有启用或被占用的病房,不可停用"); return R.fail(MessageUtils.message("his.location.ward_has_active_houses"));
} }
} }
location.setStatusEnum(LocationStatus.INACTIVE.getValue()); location.setStatusEnum(LocationStatus.INACTIVE.getValue());
locationService.updateLocation(location); locationService.updateLocation(location);
} }
return R.ok("停用成功"); return R.ok(MessageUtils.message("his.location.disable_success"));
} }
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00010, null)); return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00010, null));
} }
@@ -263,13 +263,13 @@ public class LocationAppServiceImpl implements ILocationAppService {
public R<?> addLocation(LocationAddOrEditDto locationAddOrEditDto) { public R<?> addLocation(LocationAddOrEditDto locationAddOrEditDto) {
// 不能为空 // 不能为空
if (StringUtils.isEmpty(locationAddOrEditDto.getName())) { if (StringUtils.isEmpty(locationAddOrEditDto.getName())) {
return R.fail(false, "名称不能为空"); return R.fail(false, MessageUtils.message("his.location.name_required"));
} }
// 去除空格 // 去除空格
String name = locationAddOrEditDto.getName().replaceAll("[  ]", ""); String name = locationAddOrEditDto.getName().replaceAll("[  ]", "");
// 判断是否存在同名 // 判断是否存在同名
if (locationService.isExistName(name, locationAddOrEditDto.getBusNo(), locationAddOrEditDto.getId())) { if (locationService.isExistName(name, locationAddOrEditDto.getBusNo(), locationAddOrEditDto.getId())) {
return R.fail(false, "" + name + "】已存在"); return R.fail(false, MessageUtils.message("his.location.name_exists", name));
} }
Location location = new Location(); Location location = new Location();
BeanUtils.copyProperties(locationAddOrEditDto, location); BeanUtils.copyProperties(locationAddOrEditDto, location);
@@ -308,13 +308,13 @@ public class LocationAppServiceImpl implements ILocationAppService {
public R<?> editLocation(LocationAddOrEditDto locationAddOrEditDto) { public R<?> editLocation(LocationAddOrEditDto locationAddOrEditDto) {
// 不能为空 // 不能为空
if (StringUtils.isEmpty(locationAddOrEditDto.getName())) { if (StringUtils.isEmpty(locationAddOrEditDto.getName())) {
return R.fail(false, "名称不能为空"); return R.fail(false, MessageUtils.message("his.location.name_required"));
} }
// 去除空格 // 去除空格
String name = locationAddOrEditDto.getName().replaceAll("[  ]", ""); String name = locationAddOrEditDto.getName().replaceAll("[  ]", "");
// 判断是否存在同名 // 判断是否存在同名
if (locationService.isExistName(name, locationAddOrEditDto.getBusNo(), locationAddOrEditDto.getId())) { if (locationService.isExistName(name, locationAddOrEditDto.getBusNo(), locationAddOrEditDto.getId())) {
return R.fail(false, "" + name + "】已存在"); return R.fail(false, MessageUtils.message("his.location.name_exists", name));
} }
Location location = new Location(); Location location = new Location();
BeanUtils.copyProperties(locationAddOrEditDto, location); BeanUtils.copyProperties(locationAddOrEditDto, location);

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.AssignSeqUtil; import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.ChineseConvertUtils; import com.core.common.utils.ChineseConvertUtils;
import com.core.common.utils.DictUtils; import com.core.common.utils.DictUtils;
@@ -105,7 +106,7 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
public R<?> getOperatingRoomById(Long id) { public R<?> getOperatingRoomById(Long id) {
OperatingRoom operatingRoom = operatingRoomService.getById(id); OperatingRoom operatingRoom = operatingRoomService.getById(id);
if (operatingRoom == null) { if (operatingRoom == null) {
return R.fail("手术室信息不存在"); return R.fail(MessageUtils.message("his.or.not_found"));
} }
OperatingRoomDto operatingRoomDto = new OperatingRoomDto(); OperatingRoomDto operatingRoomDto = new OperatingRoomDto();
@@ -139,12 +140,12 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
public R<?> addOperatingRoom(OperatingRoomDto operatingRoomDto) { public R<?> addOperatingRoom(OperatingRoomDto operatingRoomDto) {
// 校验名称不能为空 // 校验名称不能为空
if (StringUtils.isEmpty(operatingRoomDto.getName())) { if (StringUtils.isEmpty(operatingRoomDto.getName())) {
return R.fail("手术室名称不能为空"); return R.fail(MessageUtils.message("his.or.name_required"));
} }
// 校验房间号不能为空 // 校验房间号不能为空
if (StringUtils.isEmpty(operatingRoomDto.getBusNo())) { if (StringUtils.isEmpty(operatingRoomDto.getBusNo())) {
return R.fail("房间号不能为空"); return R.fail(MessageUtils.message("his.or.room_no_required"));
} }
// 去除空格 // 去除空格
@@ -153,12 +154,12 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
// 判断是否存在同名 // 判断是否存在同名
if (isExistName(name, null)) { if (isExistName(name, null)) {
return R.fail("" + name + "】已存在"); return R.fail(MessageUtils.message("his.or.name_exists", name));
} }
// 判断房间号是否已存在 // 判断房间号是否已存在
if (isExistBusNo(operatingRoomDto.getBusNo(), null)) { if (isExistBusNo(operatingRoomDto.getBusNo(), null)) {
return R.fail("房间号【" + operatingRoomDto.getBusNo() + "】已存在"); return R.fail(MessageUtils.message("his.or.room_no_exists", operatingRoomDto.getBusNo()));
} }
OperatingRoom operatingRoom = new OperatingRoom(); OperatingRoom operatingRoom = new OperatingRoom();
@@ -171,9 +172,9 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
boolean result = operatingRoomService.save(operatingRoom); boolean result = operatingRoomService.save(operatingRoom);
if (result) { if (result) {
return R.ok(null, "新增成功"); return R.ok(null, MessageUtils.message("msg.success"));
} }
return R.fail("新增失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
/** /**
@@ -187,17 +188,17 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
// 校验手术室是否存在 // 校验手术室是否存在
OperatingRoom existOperatingRoom = operatingRoomService.getById(operatingRoomDto.getId()); OperatingRoom existOperatingRoom = operatingRoomService.getById(operatingRoomDto.getId());
if (existOperatingRoom == null) { if (existOperatingRoom == null) {
return R.fail("手术室信息不存在"); return R.fail(MessageUtils.message("his.or.not_found"));
} }
// 校验名称不能为空 // 校验名称不能为空
if (StringUtils.isEmpty(operatingRoomDto.getName())) { if (StringUtils.isEmpty(operatingRoomDto.getName())) {
return R.fail("手术室名称不能为空"); return R.fail(MessageUtils.message("his.or.name_required"));
} }
// 校验房间号不能为空 // 校验房间号不能为空
if (StringUtils.isEmpty(operatingRoomDto.getBusNo())) { if (StringUtils.isEmpty(operatingRoomDto.getBusNo())) {
return R.fail("房间号不能为空"); return R.fail(MessageUtils.message("his.or.room_no_required"));
} }
// 去除空格 // 去除空格
@@ -206,12 +207,12 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
// 判断是否存在同名(排除自己) // 判断是否存在同名(排除自己)
if (isExistName(name, operatingRoomDto.getId())) { if (isExistName(name, operatingRoomDto.getId())) {
return R.fail("" + name + "】已存在"); return R.fail(MessageUtils.message("his.or.name_exists", name));
} }
// 判断房间号是否已存在(排除自己) // 判断房间号是否已存在(排除自己)
if (isExistBusNo(operatingRoomDto.getBusNo(), operatingRoomDto.getId())) { if (isExistBusNo(operatingRoomDto.getBusNo(), operatingRoomDto.getId())) {
return R.fail("房间号【" + operatingRoomDto.getBusNo() + "】已存在"); return R.fail(MessageUtils.message("his.or.room_no_exists", operatingRoomDto.getBusNo()));
} }
OperatingRoom operatingRoom = new OperatingRoom(); OperatingRoom operatingRoom = new OperatingRoom();
@@ -224,9 +225,9 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
boolean result = operatingRoomService.updateById(operatingRoom); boolean result = operatingRoomService.updateById(operatingRoom);
if (result) { if (result) {
return R.ok(null, "修改成功"); return R.ok(null, MessageUtils.message("msg.success"));
} }
return R.fail("修改失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
/** /**
@@ -244,16 +245,16 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
try { try {
idList.add(Long.parseLong(idStr.trim())); idList.add(Long.parseLong(idStr.trim()));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return R.fail("ID格式错误"); return R.fail(MessageUtils.message("his.or.id_format_error"));
} }
} }
// 删除手术室 // 删除手术室
boolean result = operatingRoomService.removeByIds(idList); boolean result = operatingRoomService.removeByIds(idList);
if (result) { if (result) {
return R.ok(null, "删除成功"); return R.ok(null, MessageUtils.message("msg.success"));
} }
return R.fail("删除失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
/** /**
@@ -265,7 +266,7 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
@Override @Override
public R<?> enableOperatingRoom(List<Long> ids) { public R<?> enableOperatingRoom(List<Long> ids) {
if (ids == null || ids.isEmpty()) { if (ids == null || ids.isEmpty()) {
return R.fail("请选择要启用的手术室"); return R.fail(MessageUtils.message("his.or.select_enable"));
} }
// 批量更新状态为启用 // 批量更新状态为启用
@@ -276,9 +277,9 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
boolean result = operatingRoomService.updateBatchById(operatingRooms); boolean result = operatingRoomService.updateBatchById(operatingRooms);
if (result) { if (result) {
return R.ok("启用成功"); return R.ok(MessageUtils.message("his.or.enable_success"));
} }
return R.fail("启用失败"); return R.fail(MessageUtils.message("his.or.enable_failed"));
} }
/** /**
@@ -290,7 +291,7 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
@Override @Override
public R<?> disableOperatingRoom(List<Long> ids) { public R<?> disableOperatingRoom(List<Long> ids) {
if (ids == null || ids.isEmpty()) { if (ids == null || ids.isEmpty()) {
return R.fail("请选择要停用的手术室"); return R.fail(MessageUtils.message("his.or.select_disable"));
} }
// 批量更新状态为停用 // 批量更新状态为停用
@@ -301,9 +302,9 @@ public class OperatingRoomAppServiceImpl implements IOperatingRoomAppService {
boolean result = operatingRoomService.updateBatchById(operatingRooms); boolean result = operatingRoomService.updateBatchById(operatingRooms);
if (result) { if (result) {
return R.ok("停用成功"); return R.ok(MessageUtils.message("his.or.disable_success"));
} }
return R.fail("停用失败"); return R.fail(MessageUtils.message("his.or.disable_failed"));
} }
/** /**

View File

@@ -147,7 +147,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
// Validate required fields before processing // Validate required fields before processing
if (orgLocQueryDto.getOrganizationId() == null) { if (orgLocQueryDto.getOrganizationId() == null) {
return R.fail("请选择执行科室"); return R.fail(MessageUtils.message("his.org_location.select_dept"));
} }
OrganizationLocation orgLoc = new OrganizationLocation(); OrganizationLocation orgLoc = new OrganizationLocation();
@@ -170,8 +170,9 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
orgLoc.getStartTime(), orgLoc.getEndTime())) { orgLoc.getStartTime(), orgLoc.getEndTime())) {
Organization org = organizationService.getById(organizationLocation.getOrganizationId()); Organization org = organizationService.getById(organizationLocation.getOrganizationId());
String organizationName = org != null ? org.getName() : ("科室[" + organizationLocation.getOrganizationId() + "]已删除"); String organizationName = org != null ? org.getName() : ("科室[" + organizationLocation.getOrganizationId() + "]已删除");
return R.fail("当前诊疗:" + activityName + CommonConstants.Common.DASH + orgLoc.getStartTime() return R.fail(MessageUtils.message("his.org_location.time_conflict",
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "" + organizationName + "时间冲突"); activityName + CommonConstants.Common.DASH + orgLoc.getStartTime()
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "" + organizationName));
} }
if (orgLocQueryDto.getId() != null) { if (orgLocQueryDto.getId() != null) {

View File

@@ -90,7 +90,7 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
// 账号唯一性 // 账号唯一性
long count = iBizUserService.count(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserName, userName)); long count = iBizUserService.count(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserName, userName));
if (count > 0L) { if (count > 0L) {
return R.fail(null, "账号已存在"); return R.fail(null, MessageUtils.message("his.practitioner.account_exists"));
} }
// 新增 sys_user // 新增 sys_user
BizUser bizUser = new BizUser(); BizUser bizUser = new BizUser();
@@ -462,7 +462,7 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
@Override @Override
public R<?> delUserPractitioner(Long userId) { public R<?> delUserPractitioner(Long userId) {
if (1L == userId) { if (1L == userId) {
return R.fail(null, "admin不允许删除"); return R.fail(null, MessageUtils.message("his.practitioner.admin_no_delete"));
} }
// iBizUserService.remove(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserId, userId)); // iBizUserService.remove(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserId, userId));
practitionerAppAppMapper.delUser(userId); practitionerAppAppMapper.delUser(userId);
@@ -471,7 +471,7 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
List<Practitioner> practitionerList = iPractitionerService.list(new LambdaQueryWrapper<Practitioner>().eq(Practitioner::getUserId, userId)); List<Practitioner> practitionerList = iPractitionerService.list(new LambdaQueryWrapper<Practitioner>().eq(Practitioner::getUserId, userId));
Practitioner one = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null; Practitioner one = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null;
if (one == null) { if (one == null) {
return R.fail(null, "未找到对应的医生信息"); return R.fail(null, MessageUtils.message("his.practitioner.not_found"));
} }
Long practitionerId = one.getId();// 参与者id Long practitionerId = one.getId();// 参与者id
iPractitionerService.removeById(practitionerId); iPractitionerService.removeById(practitionerId);

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.basicmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.basicmanage.domain.Bed; import com.healthlink.his.basicmanage.domain.Bed;
import com.healthlink.his.basicmanage.service.IBedService; import com.healthlink.his.basicmanage.service.IBedService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -37,20 +38,20 @@ public class BedController {
} }
@PostMapping("/add") @PostMapping("/add")
public R<?> add(@RequestBody Bed bed) { public R<?> add(@RequestBody Bed bed) {
return bedService.save(bed) ? R.ok("新增成功") : R.fail("新增失败"); return bedService.save(bed) ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
@PutMapping("/update") @PutMapping("/update")
public R<?> update(@RequestBody Bed bed) { public R<?> update(@RequestBody Bed bed) {
return bedService.updateById(bed) ? R.ok("修改成功") : R.fail("修改失败"); return bedService.updateById(bed) ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
@DeleteMapping("/delete") @DeleteMapping("/delete")
public R<?> delete(@RequestParam Long id) { public R<?> delete(@RequestParam Long id) {
return bedService.removeById(id) ? R.ok("删除成功") : R.fail("删除失败"); return bedService.removeById(id) ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
@PutMapping("/status") @PutMapping("/status")
public R<?> updateStatus(@RequestParam Long id, @RequestParam Integer status) { public R<?> updateStatus(@RequestParam Long id, @RequestParam Integer status) {
Bed bed = new Bed(); bed.setId(id); bed.setStatus(status); Bed bed = new Bed(); bed.setId(id); bed.setStatus(status);
return bedService.updateById(bed) ? R.ok("状态更新成功") : R.fail("状态更新失败"); return bedService.updateById(bed) ? R.ok(MessageUtils.message("his.bed.status_update_success")) : R.fail(MessageUtils.message("his.bed.status_update_failed"));
} }
/** /**
@@ -81,15 +82,15 @@ public class BedController {
@PutMapping("/assign") @PutMapping("/assign")
public R<?> assignBed(@RequestParam Long bedId, @RequestParam Long patientId, @RequestParam Long deptId) { public R<?> assignBed(@RequestParam Long bedId, @RequestParam Long patientId, @RequestParam Long deptId) {
Bed bed = bedService.getById(bedId); Bed bed = bedService.getById(bedId);
if (bed == null) return R.fail("床位不存在"); if (bed == null) return R.fail(MessageUtils.message("his.bed.not_found"));
if (bed.getStatus() != 0) return R.fail("该床位当前不可分配(状态: " + if (bed.getStatus() != 0) return R.fail(MessageUtils.message("his.bed.not_assignable",
java.util.Map.of(0, "空闲", 1, "占用", 2, "清洁中", 3, "维修中").getOrDefault(bed.getStatus(), "未知") + ""); java.util.Map.of(0, "空闲", 1, "占用", 2, "清洁中", 3, "维修中").getOrDefault(bed.getStatus(), "未知")));
if (bed.getDeptId() != null && !bed.getDeptId().equals(deptId)) { if (bed.getDeptId() != null && !bed.getDeptId().equals(deptId)) {
return R.fail("床位所属科室与患者入院科室不匹配"); return R.fail(MessageUtils.message("his.bed.dept_mismatch"));
} }
bed.setStatus(1); bed.setStatus(1);
bedService.updateById(bed); bedService.updateById(bed);
return R.ok("分配成功"); return R.ok(MessageUtils.message("his.bed.assign_success"));
} }
/** /**
@@ -98,10 +99,10 @@ public class BedController {
@PutMapping("/discharge") @PutMapping("/discharge")
public R<?> dischargeBed(@RequestParam Long bedId) { public R<?> dischargeBed(@RequestParam Long bedId) {
Bed bed = bedService.getById(bedId); Bed bed = bedService.getById(bedId);
if (bed == null) return R.fail("床位不存在"); if (bed == null) return R.fail(MessageUtils.message("his.bed.not_found"));
bed.setStatus(2); // 清洁中 bed.setStatus(2); // 清洁中
bedService.updateById(bed); bedService.updateById(bed);
return R.ok("已标记为清洁中"); return R.ok(MessageUtils.message("his.bed.marked_cleaning"));
} }
} }

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.basicmanage.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.administration.domain.InvoiceSegment; import com.healthlink.his.administration.domain.InvoiceSegment;
import com.healthlink.his.administration.service.IInvoiceSegmentService; import com.healthlink.his.administration.service.IInvoiceSegmentService;
@@ -83,6 +84,6 @@ public class InvoiceSegmentController {
@PostMapping("/delete") @PostMapping("/delete")
public R<?> delete(@RequestBody InvoiceSegmentDeleteRequest request) { public R<?> delete(@RequestBody InvoiceSegmentDeleteRequest request) {
int rows = invoiceSegmentService.deleteInvoiceSegmentByIds(request.getIds()); int rows = invoiceSegmentService.deleteInvoiceSegmentByIds(request.getIds());
return rows > 0 ? R.ok("删除成功") : R.fail("删除失败"); return rows > 0 ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
} }

View File

@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.annotation.Log; import com.core.common.annotation.Log;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.core.common.enums.BusinessType; import com.core.common.enums.BusinessType;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils; import com.core.common.utils.StringUtils;
@@ -68,7 +69,7 @@ public class OutpatientNoSegmentController {
if (StringUtils.isEmpty(outpatientNoSegment.getStartNo()) || if (StringUtils.isEmpty(outpatientNoSegment.getStartNo()) ||
StringUtils.isEmpty(outpatientNoSegment.getEndNo()) || StringUtils.isEmpty(outpatientNoSegment.getEndNo()) ||
StringUtils.isEmpty(outpatientNoSegment.getUsedNo())) { StringUtils.isEmpty(outpatientNoSegment.getUsedNo())) {
return R.fail("起始号码、终止号码和使用号码不能为空"); return R.fail(MessageUtils.message("his.outpatient_no.numbers_required"));
} }
// 校验号码段是否重复 // 校验号码段是否重复
@@ -76,7 +77,7 @@ public class OutpatientNoSegmentController {
outpatientNoSegment.getStartNo(), outpatientNoSegment.getStartNo(),
outpatientNoSegment.getEndNo(), outpatientNoSegment.getEndNo(),
null)) { null)) {
return R.fail("门诊号码设置重复"); return R.fail(MessageUtils.message("his.outpatient_no.duplicate"));
} }
// 设置创建人信息 // 设置创建人信息
@@ -87,7 +88,7 @@ public class OutpatientNoSegmentController {
outpatientNoSegment.setCreateBy(SecurityUtils.getUsername()); outpatientNoSegment.setCreateBy(SecurityUtils.getUsername());
int result = outpatientNoSegmentService.insertOutpatientNoSegment(outpatientNoSegment); int result = outpatientNoSegmentService.insertOutpatientNoSegment(outpatientNoSegment);
return result > 0 ? R.ok("保存成功") : R.fail("保存失败"); return result > 0 ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
/** /**
@@ -103,7 +104,7 @@ public class OutpatientNoSegmentController {
if (StringUtils.isEmpty(outpatientNoSegment.getStartNo()) || if (StringUtils.isEmpty(outpatientNoSegment.getStartNo()) ||
StringUtils.isEmpty(outpatientNoSegment.getEndNo()) || StringUtils.isEmpty(outpatientNoSegment.getEndNo()) ||
StringUtils.isEmpty(outpatientNoSegment.getUsedNo())) { StringUtils.isEmpty(outpatientNoSegment.getUsedNo())) {
return R.fail("起始号码、终止号码和使用号码不能为空"); return R.fail(MessageUtils.message("his.outpatient_no.numbers_required"));
} }
// 校验号码段是否重复(排除自身) // 校验号码段是否重复(排除自身)
@@ -111,14 +112,14 @@ public class OutpatientNoSegmentController {
outpatientNoSegment.getStartNo(), outpatientNoSegment.getStartNo(),
outpatientNoSegment.getEndNo(), outpatientNoSegment.getEndNo(),
outpatientNoSegment.getId())) { outpatientNoSegment.getId())) {
return R.fail("门诊号码设置重复"); return R.fail(MessageUtils.message("his.outpatient_no.duplicate"));
} }
// 设置更新人信息 // 设置更新人信息
outpatientNoSegment.setUpdateBy(SecurityUtils.getUsername()); outpatientNoSegment.setUpdateBy(SecurityUtils.getUsername());
int result = outpatientNoSegmentService.updateOutpatientNoSegment(outpatientNoSegment); int result = outpatientNoSegmentService.updateOutpatientNoSegment(outpatientNoSegment);
return result > 0 ? R.ok("保存成功") : R.fail("保存失败"); return result > 0 ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
/** /**
@@ -136,7 +137,7 @@ public class OutpatientNoSegmentController {
log.info("删除请求 - 接收到的ids类型: " + (idsObj != null ? idsObj.getClass().getName() : "null")); log.info("删除请求 - 接收到的ids类型: " + (idsObj != null ? idsObj.getClass().getName() : "null"));
if (idsObj == null) { if (idsObj == null) {
return R.fail("请选择要删除的数据"); return R.fail(MessageUtils.message("his.outpatient_no.select_delete"));
} }
// 转换为 Long[] 数组 // 转换为 Long[] 数组
@@ -162,24 +163,24 @@ public class OutpatientNoSegmentController {
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
log.info("删除请求 - ID转换失败: " + idObj + ", 错误: " + e.getMessage()); log.info("删除请求 - ID转换失败: " + idObj + ", 错误: " + e.getMessage());
return R.fail("无效的ID格式: " + idObj); return R.fail(MessageUtils.message("his.outpatient_no.invalid_id_value", String.valueOf(idObj)));
} }
} else if (idObj instanceof Number) { } else if (idObj instanceof Number) {
ids[i] = ((Number) idObj).longValue(); ids[i] = ((Number) idObj).longValue();
} else { } else {
return R.fail("无效的ID类型: " + (idObj != null ? idObj.getClass().getName() : "null")); return R.fail(MessageUtils.message("his.outpatient_no.invalid_id_type", idObj != null ? idObj.getClass().getName() : "null"));
} }
} }
} else if (idsObj instanceof Long[]) { } else if (idsObj instanceof Long[]) {
ids = (Long[]) idsObj; ids = (Long[]) idsObj;
} else { } else {
return R.fail("无效的ID数组格式"); return R.fail(MessageUtils.message("his.outpatient_no.invalid_id_format"));
} }
log.info("删除请求 - 转换后的ids: " + java.util.Arrays.toString(ids)); log.info("删除请求 - 转换后的ids: " + java.util.Arrays.toString(ids));
if (ids == null || ids.length == 0) { if (ids == null || ids.length == 0) {
return R.fail("请选择要删除的数据"); return R.fail(MessageUtils.message("his.outpatient_no.select_delete"));
} }
// 获取当前用户ID // 获取当前用户ID
@@ -194,7 +195,7 @@ public class OutpatientNoSegmentController {
if (segment == null) { if (segment == null) {
// 记录日志以便调试 // 记录日志以便调试
log.info("删除失败记录不存在ID=" + id + ",可能已被软删除或不存在"); log.info("删除失败记录不存在ID=" + id + ",可能已被软删除或不存在");
return R.fail("数据不存在ID: " + id); return R.fail(MessageUtils.message("his.outpatient_no.data_not_found", String.valueOf(id)));
} }
log.info("删除验证 - 找到记录: ID=" + segment.getId() + ", operatorId=" + segment.getOperatorId() + ", usedNo=" + segment.getUsedNo() + ", startNo=" + segment.getStartNo()); log.info("删除验证 - 找到记录: ID=" + segment.getId() + ", operatorId=" + segment.getOperatorId() + ", usedNo=" + segment.getUsedNo() + ", startNo=" + segment.getStartNo());
@@ -202,20 +203,20 @@ public class OutpatientNoSegmentController {
// 校验归属权 // 校验归属权
if (!segment.getOperatorId().equals(userId)) { if (!segment.getOperatorId().equals(userId)) {
log.info("删除验证 - 权限检查失败: segment.operatorId=" + segment.getOperatorId() + ", userId=" + userId); log.info("删除验证 - 权限检查失败: segment.operatorId=" + segment.getOperatorId() + ", userId=" + userId);
return R.fail("只能删除自己维护的门诊号码段"); return R.fail(MessageUtils.message("his.outpatient_no.only_own_delete"));
} }
// 校验使用状态(使用号码=起始号码表示未使用) // 校验使用状态(使用号码=起始号码表示未使用)
if (!segment.getUsedNo().equals(segment.getStartNo())) { if (!segment.getUsedNo().equals(segment.getStartNo())) {
log.info("删除验证 - 使用状态检查失败: usedNo=" + segment.getUsedNo() + ", startNo=" + segment.getStartNo()); log.info("删除验证 - 使用状态检查失败: usedNo=" + segment.getUsedNo() + ", startNo=" + segment.getStartNo());
return R.fail("已有门诊号码段已有使用的门诊号码,请核对!"); return R.fail(MessageUtils.message("his.outpatient_no.has_used_numbers"));
} }
} }
log.info("删除验证 - 所有检查通过,开始执行删除"); log.info("删除验证 - 所有检查通过,开始执行删除");
int rows = outpatientNoSegmentService.deleteOutpatientNoSegmentByIds(ids); int rows = outpatientNoSegmentService.deleteOutpatientNoSegmentByIds(ids);
log.info("删除执行 - 影响行数: " + rows); log.info("删除执行 - 影响行数: " + rows);
return rows > 0 ? R.ok("删除成功") : R.fail("删除失败"); return rows > 0 ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} }
} }

View File

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser; import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.core.common.core.domain.model.LoginUser; import com.core.common.core.domain.model.LoginUser;
import com.healthlink.his.infectious.domain.InfectiousAudit; import com.healthlink.his.infectious.domain.InfectiousAudit;
@@ -138,7 +139,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> batchAudit(BatchAuditDto batchAuditDto) { public R<?> batchAudit(BatchAuditDto batchAuditDto) {
if (batchAuditDto.getCardNos() == null || batchAuditDto.getCardNos().isEmpty()) { if (batchAuditDto.getCardNos() == null || batchAuditDto.getCardNos().isEmpty()) {
return R.fail("请选择要审核的报卡"); return R.fail(MessageUtils.message("his.card.select_approve"));
} }
String auditorId = SecurityUtils.getUserId().toString(); String auditorId = SecurityUtils.getUserId().toString();
@@ -169,7 +170,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> batchReturn(BatchReturnDto batchReturnDto) { public R<?> batchReturn(BatchReturnDto batchReturnDto) {
if (batchReturnDto.getCardNos() == null || batchReturnDto.getCardNos().isEmpty()) { if (batchReturnDto.getCardNos() == null || batchReturnDto.getCardNos().isEmpty()) {
return R.fail("请选择要退回的报卡"); return R.fail(MessageUtils.message("his.card.select_return"));
} }
String auditorId = SecurityUtils.getUserId().toString(); String auditorId = SecurityUtils.getUserId().toString();
@@ -202,7 +203,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
public R<?> auditPass(SingleAuditDto auditDto) { public R<?> auditPass(SingleAuditDto auditDto) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(auditDto.getCardNo()); InfectiousCard card = infectiousCardMapper.selectByCardNo(auditDto.getCardNo());
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
String auditorId = SecurityUtils.getUserId().toString(); String auditorId = SecurityUtils.getUserId().toString();
@@ -218,7 +219,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
createAuditRecord(card.getCardNo(), oldStatus, 2, 2, auditDto.getAuditOpinion(), createAuditRecord(card.getCardNo(), oldStatus, 2, 2, auditDto.getAuditOpinion(),
null, auditorId, auditorName, false, 1); null, auditorId, auditorName, false, 1);
return R.ok("审核通过"); return R.ok(MessageUtils.message("his.card.approve_success"));
} }
@Override @Override
@@ -226,7 +227,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
public R<?> auditReturn(SingleReturnDto returnDto) { public R<?> auditReturn(SingleReturnDto returnDto) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(returnDto.getCardNo()); InfectiousCard card = infectiousCardMapper.selectByCardNo(returnDto.getCardNo());
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
String auditorId = SecurityUtils.getUserId().toString(); String auditorId = SecurityUtils.getUserId().toString();
@@ -243,7 +244,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
createAuditRecord(card.getCardNo(), oldStatus, 5, 4, null, createAuditRecord(card.getCardNo(), oldStatus, 5, 4, null,
returnDto.getReturnReason(), auditorId, auditorName, false, 1); returnDto.getReturnReason(), auditorId, auditorName, false, 1);
return R.ok("已退回"); return R.ok(MessageUtils.message("his.card.returned"));
} }
@Override @Override
@@ -421,19 +422,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
public R<?> submitCard(String cardNo) { public R<?> submitCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo); InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
// 验证权限:只能提交自己的报卡 // 验证权限:只能提交自己的报卡
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) { if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡"); return R.fail(MessageUtils.message("his.card.no_permission"));
} }
// 狋证状态:只有暂存状态可以提交 // 狋证状态:只有暂存状态可以提交
if (!Integer.valueOf(0).equals(card.getStatus())) { if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能提交暂存状态的报卡"); return R.fail(MessageUtils.message("his.card.only_draft_submit"));
} }
// 更新状态为已提交 // 更新状态为已提交
@@ -441,7 +442,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
card.setUpdateTime(new Date()); card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card); infectiousCardMapper.updateById(card);
return R.ok("提交成功"); return R.ok(MessageUtils.message("msg.success"));
} }
@Override @Override
@@ -449,19 +450,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
public R<?> withdrawCard(String cardNo) { public R<?> withdrawCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo); InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
// 验证权限:只能撤回自己的报卡 // 验证权限:只能撤回自己的报卡
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) { if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡"); return R.fail(MessageUtils.message("his.card.no_permission"));
} }
// 狋证状态:只有已提交状态可以撤回 // 狋证状态:只有已提交状态可以撤回
if (!Integer.valueOf(1).equals(card.getStatus())) { if (!Integer.valueOf(1).equals(card.getStatus())) {
return R.fail("只能撤回已提交状态的报卡"); return R.fail(MessageUtils.message("his.card.only_submitted_withdraw"));
} }
// 更新状态为暂存 // 更新状态为暂存
@@ -469,7 +470,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
card.setUpdateTime(new Date()); card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card); infectiousCardMapper.updateById(card);
return R.ok("撤回成功"); return R.ok(MessageUtils.message("his.card.withdraw_success"));
} }
@Override @Override
@@ -477,19 +478,19 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
public R<?> deleteCard(String cardNo) { public R<?> deleteCard(String cardNo) {
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo); InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
// 验证权限:只能删除自己的报卡 // 验证权限:只能删除自己的报卡
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) { if (practitioner == null || !practitioner.getId().equals(card.getDoctorId())) {
return R.fail("无权操作此报卡"); return R.fail(MessageUtils.message("his.card.no_permission"));
} }
// 狋证状态:只有暂存状态可以删除 // 狋证状态:只有暂存状态可以删除
if (!Integer.valueOf(0).equals(card.getStatus())) { if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能删除暂存状态的报卡"); return R.fail(MessageUtils.message("his.card.only_draft_delete"));
} }
// 更新状态为作废 // 更新状态为作废
@@ -497,20 +498,20 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
card.setUpdateTime(new Date()); card.setUpdateTime(new Date());
infectiousCardMapper.updateById(card); infectiousCardMapper.updateById(card);
return R.ok("删除成功"); return R.ok(MessageUtils.message("msg.success"));
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> batchSubmitCards(List<String> cardNos) { public R<?> batchSubmitCards(List<String> cardNos) {
if (cardNos == null || cardNos.isEmpty()) { if (cardNos == null || cardNos.isEmpty()) {
return R.fail("请选择要提交的报卡"); return R.fail(MessageUtils.message("his.card.select_submit"));
} }
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) { if (practitioner == null) {
return R.fail("当前用户未关联医生信息"); return R.fail(MessageUtils.message("his.card.user_no_doctor"));
} }
Long doctorId = practitioner.getId(); Long doctorId = practitioner.getId();
int successCount = 0; int successCount = 0;
@@ -533,7 +534,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
} }
if (successCount == 0) { if (successCount == 0) {
return R.fail("没有可提交的报卡,只能提交暂存状态的报卡"); return R.fail(MessageUtils.message("his.card.no_submittable"));
} }
return R.ok("批量提交成功,共提交" + successCount + ""); return R.ok("批量提交成功,共提交" + successCount + "");
@@ -543,13 +544,13 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> batchDeleteCards(List<String> cardNos) { public R<?> batchDeleteCards(List<String> cardNos) {
if (cardNos == null || cardNos.isEmpty()) { if (cardNos == null || cardNos.isEmpty()) {
return R.fail("请选择要删除的报卡"); return R.fail(MessageUtils.message("his.card.select_delete"));
} }
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(userId);
if (practitioner == null) { if (practitioner == null) {
return R.fail("当前用户未关联医生信息"); return R.fail(MessageUtils.message("his.card.user_no_doctor"));
} }
Long doctorId = practitioner.getId(); Long doctorId = practitioner.getId();
int successCount = 0; int successCount = 0;
@@ -572,7 +573,7 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
} }
if (successCount == 0) { if (successCount == 0) {
return R.fail("没有可删除的报卡,只能删除暂存状态的报卡"); return R.fail(MessageUtils.message("his.card.no_deletable"));
} }
return R.ok("批量删除成功,共删除" + successCount + ""); return R.ok("批量删除成功,共删除" + successCount + "");
@@ -587,24 +588,24 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
// 通过 sys_user 表的 user_id 查询医生表 (adm_practitioner) 获取医生 ID // 通过 sys_user 表的 user_id 查询医生表 (adm_practitioner) 获取医生 ID
Practitioner practitioner = iPractitionerService.getPractitionerByUserId(currentUserId); Practitioner practitioner = iPractitionerService.getPractitionerByUserId(currentUserId);
if (practitioner == null) { if (practitioner == null) {
return R.fail("当前用户未关联医生信息"); return R.fail(MessageUtils.message("his.card.user_no_doctor"));
} }
Long doctorId = practitioner.getId(); Long doctorId = practitioner.getId();
// 查询报卡 // 查询报卡
InfectiousCard card = infectiousCardMapper.selectByCardNo(updateDto.getCardNo()); InfectiousCard card = infectiousCardMapper.selectByCardNo(updateDto.getCardNo());
if (card == null) { if (card == null) {
return R.fail("报卡不存在"); return R.fail(MessageUtils.message("his.card.not_found"));
} }
// 验证是否当前医生的报卡 - 根据 doctorId 字段验证 // 验证是否当前医生的报卡 - 根据 doctorId 字段验证
if (!doctorId.equals(card.getDoctorId())) { if (!doctorId.equals(card.getDoctorId())) {
return R.fail("只能修改自己的报卡"); return R.fail(MessageUtils.message("his.card.only_own_edit"));
} }
// 狋证状态是否允许修改(只能修改暂存状态的报卡) // 狋证状态是否允许修改(只能修改暂存状态的报卡)
if (!Integer.valueOf(0).equals(card.getStatus())) { if (!Integer.valueOf(0).equals(card.getStatus())) {
return R.fail("只能修改暂存状态的报卡"); return R.fail(MessageUtils.message("his.card.only_draft_edit"));
} }
// 更新字段 // 更新字段
@@ -621,9 +622,9 @@ public class CardManageAppServiceImpl implements ICardManageAppService {
int rows = infectiousCardMapper.updateById(card); int rows = infectiousCardMapper.updateById(card);
if (rows > 0) { if (rows > 0) {
return R.ok("更新成功"); return R.ok(MessageUtils.message("msg.success"));
} }
return R.fail("更新失败"); return R.fail(MessageUtils.message("msg.failure"));
} }
@Override @Override

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.cdss.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.cdss.domain.CdssAlert; import com.healthlink.his.cdss.domain.CdssAlert;
import com.healthlink.his.cdss.domain.CdssRule; import com.healthlink.his.cdss.domain.CdssRule;
import com.healthlink.his.cdss.domain.CdssRuleExecution; import com.healthlink.his.cdss.domain.CdssRuleExecution;
@@ -38,7 +39,7 @@ public class CdssAppServiceImpl implements ICdssAppService {
@Override @Override
public R<?> evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId) { public R<?> evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId) {
if (encounterId == null || patientId == null) { if (encounterId == null || patientId == null) {
return R.fail(400, "就诊ID和患者ID不能为空"); return R.fail(400, MessageUtils.message("his.cdss.encounter_patient_required"));
} }
List<CdssRule> activeRules = cdssRuleService.findActiveRules(triggerType, departmentId); List<CdssRule> activeRules = cdssRuleService.findActiveRules(triggerType, departmentId);
List<CdssAlert> triggeredAlerts = new ArrayList<>(); List<CdssAlert> triggeredAlerts = new ArrayList<>();
@@ -86,7 +87,7 @@ public class CdssAppServiceImpl implements ICdssAppService {
@Override @Override
public R<?> getAlerts(Long encounterId, Integer acknowledged) { public R<?> getAlerts(Long encounterId, Integer acknowledged) {
if (encounterId == null) { if (encounterId == null) {
return R.fail(400, "就诊ID不能为空"); return R.fail(400, MessageUtils.message("his.cdss.encounter_id_required"));
} }
List<CdssAlert> alerts = cdssAlertService.findByEncounterId(encounterId); List<CdssAlert> alerts = cdssAlertService.findByEncounterId(encounterId);
if (acknowledged != null) { if (acknowledged != null) {
@@ -100,13 +101,13 @@ public class CdssAppServiceImpl implements ICdssAppService {
@Override @Override
public R<?> acknowledgeAlert(Long id, String remark) { public R<?> acknowledgeAlert(Long id, String remark) {
if (id == null) { if (id == null) {
return R.fail(400, "告警ID不能为空"); return R.fail(400, MessageUtils.message("his.cdss.alert_id_required"));
} }
boolean updated = cdssAlertService.acknowledgeAlert(id, null, remark); boolean updated = cdssAlertService.acknowledgeAlert(id, null, remark);
if (!updated) { if (!updated) {
return R.fail(404, "告警不存在或已确认"); return R.fail(404, MessageUtils.message("his.cdss.alert_not_found"));
} }
return R.ok(null, "确认成功"); return R.ok(null, MessageUtils.message("his.cdss.confirm_success"));
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package com.healthlink.his.web.charge.patientcardrenewal; package com.healthlink.his.web.charge.patientcardrenewal;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -40,13 +41,13 @@ public class PatientCardRenewalController {
if (success) { if (success) {
log.info("患者换卡成功: 旧卡号={} -> 新卡号={}", log.info("患者换卡成功: 旧卡号={} -> 新卡号={}",
request.getOldCardNo(), request.getNewCardNo()); request.getOldCardNo(), request.getNewCardNo());
return R.ok("换卡成功"); return R.ok(MessageUtils.message("his.card_renewal.success"));
} else { } else {
return R.fail("换卡失败"); return R.fail(MessageUtils.message("his.card_renewal.failed"));
} }
} catch (Exception e) { } catch (Exception e) {
log.error("患者换卡异常: ", e); log.error("患者换卡异常: ", e);
return R.fail("换卡操作异常: " + e.getMessage()); return R.fail(MessageUtils.message("his.card_renewal.exception", e.getMessage()));
} }
} }
} }

View File

@@ -35,9 +35,10 @@ public interface IOutpatientChargeAppService {
* 根据就诊id查询患者处方列表 * 根据就诊id查询患者处方列表
* *
* @param encounterId 就诊id * @param encounterId 就诊id
* @param statusEnum 收费状态过滤(可选,不传则返回全部状态)
* @return 患者处方列表 * @return 患者处方列表
*/ */
List<EncounterPatientPrescriptionDto> getEncounterPatientPrescription(Long encounterId); List<EncounterPatientPrescriptionDto> getEncounterPatientPrescription(Long encounterId, Integer statusEnum);
/** /**
* 根据就诊id查询患者处方列表并新增字段实收金额、应收金额、优惠金额、折扣率 * 根据就诊id查询患者处方列表并新增字段实收金额、应收金额、优惠金额、折扣率

View File

@@ -111,10 +111,11 @@ public class OutpatientChargeAppServiceImpl implements IOutpatientChargeAppServi
* 根据就诊id查询患者处方列表 * 根据就诊id查询患者处方列表
* *
* @param encounterId 就诊id * @param encounterId 就诊id
* @param statusEnum 收费状态过滤(可选,不传则返回全部状态)
* @return 患者处方列表 * @return 患者处方列表
*/ */
@Override @Override
public List<EncounterPatientPrescriptionDto> getEncounterPatientPrescription(Long encounterId) { public List<EncounterPatientPrescriptionDto> getEncounterPatientPrescription(Long encounterId, Integer statusEnum) {
List<EncounterPatientPrescriptionDto> prescriptionDtoList List<EncounterPatientPrescriptionDto> prescriptionDtoList
= outpatientChargeAppMapper.selectEncounterPatientPrescription(encounterId, = outpatientChargeAppMapper.selectEncounterPatientPrescription(encounterId,
ChargeItemContext.ACTIVITY.getValue(), ChargeItemContext.MEDICATION.getValue(), ChargeItemContext.ACTIVITY.getValue(), ChargeItemContext.MEDICATION.getValue(),
@@ -123,7 +124,7 @@ public class OutpatientChargeAppServiceImpl implements IOutpatientChargeAppServi
ChargeItemStatus.PLANNED.getValue(), ChargeItemStatus.BILLABLE.getValue(), ChargeItemStatus.PLANNED.getValue(), ChargeItemStatus.BILLABLE.getValue(),
ChargeItemStatus.BILLED.getValue(), ChargeItemStatus.REFUNDING.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); CommonConstants.TableName.WOR_DEVICE_REQUEST, statusEnum);
prescriptionDtoList.forEach(e -> { prescriptionDtoList.forEach(e -> {
// 收费状态枚举 // 收费状态枚举
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(ChargeItemStatus.class, e.getStatusEnum())); e.setStatusEnum_enumText(EnumUtils.getInfoByValue(ChargeItemStatus.class, e.getStatusEnum()));

View File

@@ -284,7 +284,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
// ==================== 走退药流程 (生成新单据) ==================== // ==================== 走退药流程 (生成新单据) ====================
// 校验是否重复申请 // 校验是否重复申请
if (medicationRequest.getRefundMedicineId() != null) { if (medicationRequest.getRefundMedicineId() != null) {
throw new ServiceException("已申请退药,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.drug.duplicate"));
} }
MedicationRequest newRefundRequest = new MedicationRequest(); MedicationRequest newRefundRequest = new MedicationRequest();
BeanUtils.copyProperties(medicationRequest, newRefundRequest); BeanUtils.copyProperties(medicationRequest, newRefundRequest);
@@ -329,7 +329,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
// 检查是否已经是退药状态 // 检查是否已经是退药状态
if (DispenseStatus.STOPPED.getValue().equals(medicationDispense.getStatusEnum()) if (DispenseStatus.STOPPED.getValue().equals(medicationDispense.getStatusEnum())
&& NotPerformedReason.REFUND.getValue().equals(medicationDispense.getStatusEnum())) { && NotPerformedReason.REFUND.getValue().equals(medicationDispense.getStatusEnum())) {
throw new ServiceException("已申请退药,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.drug.duplicate"));
} }
// 修改状态 // 修改状态
medicationDispense.setStatusEnum(DispenseStatus.STOPPED.getValue()) medicationDispense.setStatusEnum(DispenseStatus.STOPPED.getValue())
@@ -373,7 +373,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
// ==================== 走退耗材流程 (生成新单据) ==================== // ==================== 走退耗材流程 (生成新单据) ====================
// 校验是否重复申请 // 校验是否重复申请
if (deviceRequest.getRefundDeviceId() != null) { if (deviceRequest.getRefundDeviceId() != null) {
throw new ServiceException("已申请退耗材,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.consumable.duplicate"));
} }
DeviceRequest newRefundRequest = new DeviceRequest(); DeviceRequest newRefundRequest = new DeviceRequest();
BeanUtils.copyProperties(deviceRequest, newRefundRequest); BeanUtils.copyProperties(deviceRequest, newRefundRequest);
@@ -418,7 +418,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
// 检查是否已经是退耗材状态 // 检查是否已经是退耗材状态
if (DispenseStatus.STOPPED.getValue().equals(deviceDispense.getStatusEnum()) if (DispenseStatus.STOPPED.getValue().equals(deviceDispense.getStatusEnum())
&& NotPerformedReason.REFUND.getValue().equals(deviceDispense.getStatusEnum())) { && NotPerformedReason.REFUND.getValue().equals(deviceDispense.getStatusEnum())) {
throw new ServiceException("已申请退耗材,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.consumable.duplicate"));
} }
// 修改状态 // 修改状态
deviceDispense.setStatusEnum(DispenseStatus.STOPPED.getValue()) deviceDispense.setStatusEnum(DispenseStatus.STOPPED.getValue())
@@ -443,7 +443,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
// 诊疗项目需医技科室同意退费 // 诊疗项目需医技科室同意退费
if (RequestStatus.COMPLETED.getValue().equals(serviceRequest.getStatusEnum())) { if (RequestStatus.COMPLETED.getValue().equals(serviceRequest.getStatusEnum())) {
if (serviceRequest.getRefundServiceId() != null) { if (serviceRequest.getRefundServiceId() != null) {
throw new ServiceException("已申请退费,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.service.duplicate"));
} }
// 生成服务请求(取消服务) // 生成服务请求(取消服务)
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4)); // 服务请求编码 serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4)); // 服务请求编码
@@ -454,7 +454,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
serviceRequestService.save(serviceRequest); serviceRequestService.save(serviceRequest);
} else { } else {
if (RequestStatus.STOPPED.getValue().equals(serviceRequest.getStatusEnum())) { if (RequestStatus.STOPPED.getValue().equals(serviceRequest.getStatusEnum())) {
throw new ServiceException("已申请退费,请勿重复申请"); throw new ServiceException(MessageUtils.message("charge.refund.service.duplicate"));
} }
serReqUpdateList.add(serviceRequest.getId()); serReqUpdateList.add(serviceRequest.getId());
} }
@@ -576,7 +576,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
if (!medicationRequestList.isEmpty()) { if (!medicationRequestList.isEmpty()) {
if (medicationRequestList.stream().map(MedicationRequest::getStatusEnum) if (medicationRequestList.stream().map(MedicationRequest::getStatusEnum)
.anyMatch(x -> x.equals(RequestStatus.CANCELLED.getValue()))) { .anyMatch(x -> x.equals(RequestStatus.CANCELLED.getValue()))) {
throw new ServiceException("请先退药后再退费"); throw new ServiceException(MessageUtils.message("charge.refund.drug.first"));
} }
} }
} }
@@ -589,7 +589,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
if (!deviceRequestList.isEmpty()) { if (!deviceRequestList.isEmpty()) {
if (deviceRequestList.stream().map(DeviceRequest::getStatusEnum) if (deviceRequestList.stream().map(DeviceRequest::getStatusEnum)
.anyMatch(x -> x.equals(RequestStatus.CANCELLED.getValue()))) { .anyMatch(x -> x.equals(RequestStatus.CANCELLED.getValue()))) {
throw new ServiceException("请先退耗材后再退费"); throw new ServiceException(MessageUtils.message("charge.refund.consumable.first"));
} }
} }
} }

View File

@@ -292,14 +292,14 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
public R<?> returnRegister(CancelRegPaymentDto cancelRegPaymentDto) { public R<?> returnRegister(CancelRegPaymentDto cancelRegPaymentDto) {
Encounter byId = iEncounterService.getById(cancelRegPaymentDto.getEncounterId()); Encounter byId = iEncounterService.getById(cancelRegPaymentDto.getEncounterId());
if (byId == null) { if (byId == null) {
return R.fail(null, "就诊记录不存在"); return R.fail(null, MessageUtils.message("his.chargemanage.encounter_not_exist"));
} }
if (EncounterStatus.CANCELLED.getValue().equals(byId.getStatusEnum())) { if (EncounterStatus.CANCELLED.getValue().equals(byId.getStatusEnum())) {
return R.fail(null, "该患者已经退号,请勿重复退号"); return R.fail(null, MessageUtils.message("his.chargemanage.already_cancelled_registration"));
} }
// 只有待诊状态才能退号 // 只有待诊状态才能退号
if (!EncounterStatus.PLANNED.getValue().equals(byId.getStatusEnum())) { if (!EncounterStatus.PLANNED.getValue().equals(byId.getStatusEnum())) {
return R.fail(null, "该患者已开始就诊,不能退号!"); return R.fail(null, MessageUtils.message("his.chargemanage.consultation_started_no_cancel"));
} }
// 诊前退号检查:病历、费用明细、班段时间 // 诊前退号检查:病历、费用明细、班段时间
@@ -395,7 +395,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
) )
)); ));
if (emrCount > 0) { if (emrCount > 0) {
return R.fail(null, "该患者已有病历记录,不能退号!"); return R.fail(null, MessageUtils.message("his.chargemanage.emr_exists_no_cancel"));
} }
// 2. 检查是否有当日费用明细(除挂号费外的其他费用) // 2. 检查是否有当日费用明细(除挂号费外的其他费用)
@@ -418,7 +418,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
) )
)); ));
if (chargeItemCount > 0) { if (chargeItemCount > 0) {
return R.fail(null, "该患者已产生诊疗费用,不能退号!"); return R.fail(null, MessageUtils.message("his.chargemanage.charge_exists_no_cancel"));
} }
// 3. 检查是否当日就诊(防止隔日财务封账) // 3. 检查是否当日就诊(防止隔日财务封账)
@@ -426,7 +426,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
LocalDate encounterDate = encounter.getCreateTime().toInstant() LocalDate encounterDate = encounter.getCreateTime().toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate(); .atZone(ZoneId.systemDefault()).toLocalDate();
if (encounterDate.isBefore(today)) { if (encounterDate.isBefore(today)) {
return R.fail(null, "非当日就诊记录,不能退号!"); return R.fail(null, MessageUtils.message("his.chargemanage.not_today_encounter_no_cancel"));
} }
} }
@@ -490,7 +490,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
// 检查当前时间是否已过班段结束时间 // 检查当前时间是否已过班段结束时间
LocalTime now = LocalTime.now(); LocalTime now = LocalTime.now();
if (now.isAfter(pool.getEndTime())) { if (now.isAfter(pool.getEndTime())) {
return R.fail(null, "当前班段已结束,不能退号!"); return R.fail(null, MessageUtils.message("his.chargemanage.shift_ended_no_cancel"));
} }
return null; return null;
@@ -532,11 +532,22 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
String registerTimeSTime = request.getParameter("registerTimeSTime"); String registerTimeSTime = request.getParameter("registerTimeSTime");
String registerTimeETime = request.getParameter("registerTimeETime"); String registerTimeETime = request.getParameter("registerTimeETime");
// Bug #638提取可选科室过滤参数
Long deptId = null;
String deptIdParam = request.getParameter("deptId");
if (deptIdParam != null && !deptIdParam.isEmpty()) {
try {
deptId = Long.parseLong(deptIdParam);
} catch (NumberFormatException e) {
// 忽略无效的参数值
}
}
IPage<CurrentDayEncounterDto> currentDayEncounter = outpatientRegistrationAppMapper.getCurrentDayEncounter( IPage<CurrentDayEncounterDto> currentDayEncounter = outpatientRegistrationAppMapper.getCurrentDayEncounter(
new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(), new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(),
ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper, ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper,
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue(), ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue(),
registerTimeSTime, registerTimeETime, statusFilter); registerTimeSTime, registerTimeETime, statusFilter, deptId);
// 过滤候选池排除列表 // 过滤候选池排除列表
// 仅当调用方显式传 excludeFromCandidatePool=true 时才过滤,避免非分诊场景(挂号/收费) // 仅当调用方显式传 excludeFromCandidatePool=true 时才过滤,避免非分诊场景(挂号/收费)
@@ -599,7 +610,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
@Override @Override
public R<?> cancelRegister(Long encounterId) { public R<?> cancelRegister(Long encounterId) {
iEncounterService.removeById(encounterId); iEncounterService.removeById(encounterId);
return R.ok("已取消挂号"); return R.ok(MessageUtils.message("his.chargemanage.registration_cancelled"));
} }
/** /**
@@ -714,7 +725,7 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
public R<?> reprintRegistration(ReprintRegistrationDto reprintRegistrationDto) { public R<?> reprintRegistration(ReprintRegistrationDto reprintRegistrationDto) {
// 补打挂号只是重新打印,不需要修改数据库 // 补打挂号只是重新打印,不需要修改数据库
// 可以在这里添加日志记录补打操作 // 可以在这里添加日志记录补打操作
return R.ok(null, "补打挂号成功"); return R.ok(null, MessageUtils.message("his.chargemanage.reprint_success"));
} }
/** /**

View File

@@ -61,11 +61,13 @@ public class OutpatientChargeController {
* 根据就诊id查询患者处方列表 * 根据就诊id查询患者处方列表
* *
* @param encounterId 就诊id * @param encounterId 就诊id
* @param statusEnum 收费状态过滤(可选,不传则返回全部状态)
* @return 患者处方列表 * @return 患者处方列表
*/ */
@GetMapping(value = "/patient-prescription") @GetMapping(value = "/patient-prescription")
public R<?> getEncounterPatientPrescription(@RequestParam Long encounterId) { public R<?> getEncounterPatientPrescription(@RequestParam Long encounterId,
return R.ok(outpatientChargeAppService.getEncounterPatientPrescription(encounterId)); @RequestParam(required = false) Integer statusEnum) {
return R.ok(outpatientChargeAppService.getEncounterPatientPrescription(encounterId, statusEnum));
} }
/** /**

View File

@@ -4,6 +4,7 @@
package com.healthlink.his.web.chargemanage.controller; package com.healthlink.his.web.chargemanage.controller;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.chargemanage.appservice.IOutpatientRefundAppService; import com.healthlink.his.web.chargemanage.appservice.IOutpatientRefundAppService;
import com.healthlink.his.web.chargemanage.dto.EncounterPatientPageParam; import com.healthlink.his.web.chargemanage.dto.EncounterPatientPageParam;
import com.healthlink.his.web.chargemanage.dto.RefundItemParam; import com.healthlink.his.web.chargemanage.dto.RefundItemParam;
@@ -68,7 +69,7 @@ public class OutpatientRefundController {
@GetMapping(value = "/patient-payment") @GetMapping(value = "/patient-payment")
public R<?> getEncounterPatientPayment(@RequestParam(required = false) Long encounterId) { public R<?> getEncounterPatientPayment(@RequestParam(required = false) Long encounterId) {
if (encounterId == null) { if (encounterId == null) {
return R.fail(null, "请先选择患者后再进行退费操作"); return R.fail(null, MessageUtils.message("his.refund.select_patient_first"));
} }
return outpatientRefundAppService.getEncounterPatientPayment(encounterId); return outpatientRefundAppService.getEncounterPatientPayment(encounterId);
} }

View File

@@ -59,7 +59,8 @@ public interface OutpatientChargeAppMapper {
@Param("chinesePatentMedicine") Integer chinesePatentMedicine, @Param("chinesePatentMedicine") Integer chinesePatentMedicine,
@Param("planned") Integer planned, @Param("billable") Integer billable, @Param("planned") Integer planned, @Param("billable") Integer billable,
@Param("billed") Integer billed, @Param("refunding") Integer refunding, @Param("refunded") Integer refunded, @Param("billed") Integer billed, @Param("refunding") Integer refunding, @Param("refunded") Integer refunded,
@Param("partRefund") Integer partRefund, @Param("worDeviceRequest") String worDeviceRequest); @Param("partRefund") Integer partRefund, @Param("worDeviceRequest") String worDeviceRequest,
@Param("filterStatus") Integer filterStatus);
/** /**
* 根据就诊id查询患者处方列表并新增字段应收金额实收金额优惠金额折扣率 * 根据就诊id查询患者处方列表并新增字段应收金额实收金额优惠金额折扣率

View File

@@ -57,7 +57,8 @@ public interface OutpatientRegistrationAppMapper {
@Param("register") Integer register, @Param("paymentStatus") Integer paymentStatus, @Param("register") Integer register, @Param("paymentStatus") Integer paymentStatus,
@Param("registerTimeSTime") String registerTimeSTime, @Param("registerTimeSTime") String registerTimeSTime,
@Param("registerTimeETime") String registerTimeETime, @Param("registerTimeETime") String registerTimeETime,
@Param("statusFilter") Integer statusFilter); @Param("statusFilter") Integer statusFilter,
@Param("deptId") Long deptId);
/** /**
* 查询item绑定的信息(耗材或诊疗) * 查询item绑定的信息(耗材或诊疗)

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.check.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.CheckMethod; import com.healthlink.his.check.domain.CheckMethod;
import com.healthlink.his.check.domain.CheckPackage; import com.healthlink.his.check.domain.CheckPackage;
import com.healthlink.his.check.service.ICheckMethodService; import com.healthlink.his.check.service.ICheckMethodService;
@@ -120,13 +121,13 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
public R<?> addCheckMethod(CheckMethod checkMethod) { public R<?> addCheckMethod(CheckMethod checkMethod) {
//1.数据校验 //1.数据校验
if (ObjectUtil.isEmpty(checkMethod.getName())) { if (ObjectUtil.isEmpty(checkMethod.getName())) {
return R.fail("检查方法名称不能为空!"); return R.fail(MessageUtils.message("his.check_method.name_required"));
} }
if (ObjectUtil.isEmpty(checkMethod.getCode())) { if (ObjectUtil.isEmpty(checkMethod.getCode())) {
return R.fail("检查方法代码不能为空!"); return R.fail(MessageUtils.message("his.check_method.code_required"));
} }
if (ObjectUtil.isEmpty(checkMethod.getCheckType())) { if (ObjectUtil.isEmpty(checkMethod.getCheckType())) {
return R.fail("检查方法的检查类型不能为空!"); return R.fail(MessageUtils.message("his.check_method.type_required"));
} }
//2.保存 //2.保存
checkMethodService.save(checkMethod); checkMethodService.save(checkMethod);
@@ -139,13 +140,13 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
public R<?> updateCheckMethod(CheckMethod checkMethod) { public R<?> updateCheckMethod(CheckMethod checkMethod) {
//1.数据校验 //1.数据校验
if (ObjectUtil.isEmpty(checkMethod.getName())) { if (ObjectUtil.isEmpty(checkMethod.getName())) {
return R.fail("检查方法名称不能为空!"); return R.fail(MessageUtils.message("his.check_method.name_required"));
} }
if (ObjectUtil.isEmpty(checkMethod.getCode())) { if (ObjectUtil.isEmpty(checkMethod.getCode())) {
return R.fail("检查方法代码不能为空!"); return R.fail(MessageUtils.message("his.check_method.code_required"));
} }
if (ObjectUtil.isEmpty(checkMethod.getCheckType())) { if (ObjectUtil.isEmpty(checkMethod.getCheckType())) {
return R.fail("检查方法的检查类型不能为空!"); return R.fail(MessageUtils.message("his.check_method.type_required"));
} }
//2.更新 //2.更新
boolean b = checkMethodService.updateById(checkMethod); boolean b = checkMethodService.updateById(checkMethod);

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.check.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.CheckPackage; import com.healthlink.his.check.domain.CheckPackage;
import com.healthlink.his.check.domain.CheckPackageDetail; import com.healthlink.his.check.domain.CheckPackageDetail;
import com.healthlink.his.check.domain.CheckPart; import com.healthlink.his.check.domain.CheckPart;
@@ -90,7 +91,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("获取检查套餐列表失败", e); log.error("获取检查套餐列表失败", e);
return R.fail("获取检查套餐列表失败"); return R.fail(MessageUtils.message("his.check.get_package_list_failed"));
} }
} }
@@ -99,7 +100,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
try { try {
CheckPackage checkPackage = checkPackageService.getById(id); CheckPackage checkPackage = checkPackageService.getById(id);
if (checkPackage == null) { if (checkPackage == null) {
return R.fail("套餐不存在"); return R.fail(MessageUtils.message("his.check.package_not_found"));
} }
// 获取套餐明细 // 获取套餐明细
@@ -124,7 +125,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
return R.ok(dto); return R.ok(dto);
} catch (Exception e) { } catch (Exception e) {
log.error("获取检查套餐详情失败", e); log.error("获取检查套餐详情失败", e);
return R.fail("获取检查套餐详情失败"); return R.fail(MessageUtils.message("his.check.get_package_detail_failed"));
} }
} }
@@ -144,7 +145,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 保存套餐主表 // 保存套餐主表
boolean saveResult = checkPackageService.save(checkPackage); boolean saveResult = checkPackageService.save(checkPackage);
if (!saveResult) { if (!saveResult) {
return R.fail("保存套餐失败"); return R.fail(MessageUtils.message("his.check.save_package_failed"));
} }
// 保存套餐明细 // 保存套餐明细
@@ -159,7 +160,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 同步更新引用此套餐的检查部位价格 // 同步更新引用此套餐的检查部位价格
syncCheckPartPrice(checkPackage.getPackageName(), checkPackage.getPackagePrice()); syncCheckPartPrice(checkPackage.getPackageName(), checkPackage.getPackagePrice());
return R.ok(checkPackage.getId(), "保存成功"); return R.ok(checkPackage.getId(), MessageUtils.message("msg.success"));
} catch (Exception e) { } catch (Exception e) {
log.error("新增检查套餐失败", e); log.error("新增检查套餐失败", e);
@@ -175,11 +176,11 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
if (errorMessage.contains("check_package")) { if (errorMessage.contains("check_package")) {
constraintInfo = "套餐名称或编码"; constraintInfo = "套餐名称或编码";
} }
return R.fail("保存失败:数据重复," + constraintInfo + "已存在。详细错误:" + errorMessage); return R.fail(MessageUtils.message("his.check.save_duplicate_failed", constraintInfo, errorMessage));
} }
} }
return R.fail("新增检查套餐失败:" + errorMessage); return R.fail(MessageUtils.message("his.check.add_package_failed", errorMessage));
} }
} }
@@ -190,7 +191,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 检查套餐是否存在 // 检查套餐是否存在
CheckPackage existPackage = checkPackageService.getById(checkPackageDto.getId()); CheckPackage existPackage = checkPackageService.getById(checkPackageDto.getId());
if (existPackage == null) { if (existPackage == null) {
return R.fail("套餐不存在"); return R.fail(MessageUtils.message("his.check.package_not_found"));
} }
// 更新套餐主表数据 // 更新套餐主表数据
@@ -203,7 +204,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
boolean updateResult = checkPackageService.updateById(checkPackage); boolean updateResult = checkPackageService.updateById(checkPackage);
if (!updateResult) { if (!updateResult) {
return R.fail("更新套餐失败"); return R.fail(MessageUtils.message("his.check.update_package_failed"));
} }
// 删除原有明细 // 删除原有明细
@@ -221,10 +222,10 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 同步更新引用此套餐的检查部位价格 // 同步更新引用此套餐的检查部位价格
syncCheckPartPrice(existPackage.getPackageName(), checkPackage.getPackagePrice()); syncCheckPartPrice(existPackage.getPackageName(), checkPackage.getPackagePrice());
return R.ok("更新成功"); return R.ok(MessageUtils.message("msg.success"));
} catch (Exception e) { } catch (Exception e) {
log.error("更新检查套餐失败", e); log.error("更新检查套餐失败", e);
return R.fail("更新检查套餐失败:" + e.getMessage()); return R.fail(MessageUtils.message("his.check.update_package_error", e.getMessage()));
} }
} }
@@ -235,7 +236,7 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 检查套餐是否存在 // 检查套餐是否存在
CheckPackage existPackage = checkPackageService.getById(id); CheckPackage existPackage = checkPackageService.getById(id);
if (existPackage == null) { if (existPackage == null) {
return R.fail("套餐不存在"); return R.fail(MessageUtils.message("his.check.package_not_found"));
} }
// 删除套餐明细 - 先删除子表数据 // 删除套餐明细 - 先删除子表数据
@@ -250,14 +251,14 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
// 删除套餐主表 // 删除套餐主表
boolean deleteResult = checkPackageService.removeById(id); boolean deleteResult = checkPackageService.removeById(id);
if (!deleteResult) { if (!deleteResult) {
return R.fail("删除套餐失败"); return R.fail(MessageUtils.message("his.check.delete_package_failed"));
} }
log.info("删除检查套餐成功,套餐 ID: {}", id); log.info("删除检查套餐成功,套餐 ID: {}", id);
return R.ok("删除成功"); return R.ok(MessageUtils.message("msg.success"));
} catch (Exception e) { } catch (Exception e) {
log.error("删除检查套餐失败", e); log.error("删除检查套餐失败", e);
return R.fail("删除检查套餐失败:" + e.getMessage()); return R.fail(MessageUtils.message("his.check.delete_package_error", e.getMessage()));
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.check.appservice.impl;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.LisGroupInfo; import com.healthlink.his.check.domain.LisGroupInfo;
import com.healthlink.his.check.service.ILisGroupInfoService; import com.healthlink.his.check.service.ILisGroupInfoService;
import com.healthlink.his.web.check.appservice.ILisGroupInfoAppService; import com.healthlink.his.web.check.appservice.ILisGroupInfoAppService;
@@ -29,7 +30,7 @@ public class LisGroupInfoAppServiceImpl implements ILisGroupInfoAppService {
@Override @Override
public R<?> add(LisGroupInfo lisGroupInfo) { public R<?> add(LisGroupInfo lisGroupInfo) {
if (ObjectUtil.isEmpty(lisGroupInfo)) { if (ObjectUtil.isEmpty(lisGroupInfo)) {
return R.fail("信息不能为空"); return R.fail(MessageUtils.message("his.lis.info_required"));
} }
boolean save = lisGroupInfoService.save(lisGroupInfo); boolean save = lisGroupInfoService.save(lisGroupInfo);
return R.ok(save); return R.ok(save);
@@ -38,7 +39,7 @@ public class LisGroupInfoAppServiceImpl implements ILisGroupInfoAppService {
@Override @Override
public R<?> update(LisGroupInfo lisGroupInfo) { public R<?> update(LisGroupInfo lisGroupInfo) {
if (ObjectUtil.isEmpty(lisGroupInfo)) { if (ObjectUtil.isEmpty(lisGroupInfo)) {
return R.fail("信息不能为空"); return R.fail(MessageUtils.message("his.lis.info_required"));
} }
boolean update = lisGroupInfoService.updateById(lisGroupInfo); boolean update = lisGroupInfoService.updateById(lisGroupInfo);
return R.ok( update); return R.ok( update);

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.check.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.DicomPrintRecord; import com.healthlink.his.check.domain.DicomPrintRecord;
import com.healthlink.his.check.domain.RadiologyImage; import com.healthlink.his.check.domain.RadiologyImage;
import com.healthlink.his.check.domain.RadiologyImageReport; import com.healthlink.his.check.domain.RadiologyImageReport;
@@ -82,7 +83,7 @@ public class RadiologyImageAppServiceImpl implements IRadiologyImageAppService {
public R<?> submitReport(Long id) { public R<?> submitReport(Long id) {
RadiologyImageReport r = reportService.getById(id); RadiologyImageReport r = reportService.getById(id);
if (r == null) { if (r == null) {
return R.fail("报告不存在"); return R.fail(MessageUtils.message("radiology.report.not.exist"));
} }
r.setStatus("REPORTED"); r.setStatus("REPORTED");
r.setReportTime(new Date()); r.setReportTime(new Date());
@@ -101,7 +102,7 @@ public class RadiologyImageAppServiceImpl implements IRadiologyImageAppService {
public R<?> verifyReport(Long id, String doctor) { public R<?> verifyReport(Long id, String doctor) {
RadiologyImageReport r = reportService.getById(id); RadiologyImageReport r = reportService.getById(id);
if (r == null) { if (r == null) {
return R.fail("报告不存在"); return R.fail(MessageUtils.message("radiology.report.not.exist"));
} }
r.setStatus("VERIFIED"); r.setStatus("VERIFIED");
r.setVerifyDoctor(doctor); r.setVerifyDoctor(doctor);

View File

@@ -7,6 +7,7 @@ import com.core.common.core.domain.AjaxResult;
import com.core.common.core.page.TableDataInfo; import com.core.common.core.page.TableDataInfo;
import com.core.common.exception.ServiceException; import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil; import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.administration.domain.Account; import com.healthlink.his.administration.domain.Account;
import com.healthlink.his.administration.domain.ChargeItem; import com.healthlink.his.administration.domain.ChargeItem;
@@ -307,7 +308,7 @@ public class ExamApplyController extends BaseController {
// 🔧 BugFix#385: 获取患者真实的自费账户预结算验证要求accountId必须真实存在 // 🔧 BugFix#385: 获取患者真实的自费账户预结算验证要求accountId必须真实存在
Account selfAccount = accountService.getSelfAccount(dto.getEncounterId()); Account selfAccount = accountService.getSelfAccount(dto.getEncounterId());
if (selfAccount == null) { if (selfAccount == null) {
throw new ServiceException("患者自费账户不存在无法创建检查收费项encounterId=" + dto.getEncounterId()); throw new ServiceException(MessageUtils.message("his.exam.self_account_not_found", dto.getEncounterId()));
} }
chargeItem.setAccountId(selfAccount.getId()); chargeItem.setAccountId(selfAccount.getId());
// 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗而不是硬编码的2 // 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗而不是硬编码的2
@@ -497,7 +498,7 @@ public class ExamApplyController extends BaseController {
// 🔧 BugFix#385: 获取患者真实的自费账户预结算验证要求accountId必须真实存在 // 🔧 BugFix#385: 获取患者真实的自费账户预结算验证要求accountId必须真实存在
Account selfAccount = accountService.getSelfAccount(dto.getEncounterId()); Account selfAccount = accountService.getSelfAccount(dto.getEncounterId());
if (selfAccount == null) { if (selfAccount == null) {
throw new ServiceException("患者自费账户不存在无法创建检查收费项encounterId=" + dto.getEncounterId()); throw new ServiceException(MessageUtils.message("his.exam.self_account_not_found", dto.getEncounterId()));
} }
chargeItem.setAccountId(selfAccount.getId()); chargeItem.setAccountId(selfAccount.getId());
// 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗 // 🔧 BugFix#385: 使用 ItemType.ACTIVITY.getValue()=3 表示诊疗

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.check.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.ExamAppointment; import com.healthlink.his.check.domain.ExamAppointment;
import com.healthlink.his.check.service.IExamAppointmentService; import com.healthlink.his.check.service.IExamAppointmentService;
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j; import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
@@ -40,28 +41,28 @@ public class ExamAppointmentController {
@PreAuthorize("@ss.hasPermi('infection:check:edit')") @PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor=Exception.class) @Transactional(rollbackFor=Exception.class)
public R<?> checkin(@PathVariable Long id) { public R<?> checkin(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail(MessageUtils.message("his.exam.appointment_not_found"));
a.setStatus("CHECKED_IN"); appointmentService.updateById(a); return R.ok(); a.setStatus("CHECKED_IN"); appointmentService.updateById(a); return R.ok();
} }
@PutMapping("/start/{id}") @PutMapping("/start/{id}")
@PreAuthorize("@ss.hasPermi('infection:check:edit')") @PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor=Exception.class) @Transactional(rollbackFor=Exception.class)
public R<?> startExam(@PathVariable Long id) { public R<?> startExam(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail(MessageUtils.message("his.exam.appointment_not_found"));
a.setStatus("EXAMINING"); appointmentService.updateById(a); return R.ok(); a.setStatus("EXAMINING"); appointmentService.updateById(a); return R.ok();
} }
@PutMapping("/complete/{id}") @PutMapping("/complete/{id}")
@PreAuthorize("@ss.hasPermi('infection:check:edit')") @PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor=Exception.class) @Transactional(rollbackFor=Exception.class)
public R<?> complete(@PathVariable Long id) { public R<?> complete(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail(MessageUtils.message("his.exam.appointment_not_found"));
a.setStatus("COMPLETED"); appointmentService.updateById(a); return R.ok(); a.setStatus("COMPLETED"); appointmentService.updateById(a); return R.ok();
} }
@PutMapping("/cancel/{id}") @PutMapping("/cancel/{id}")
@PreAuthorize("@ss.hasPermi('infection:check:edit')") @PreAuthorize("@ss.hasPermi('infection:check:edit')")
@Transactional(rollbackFor=Exception.class) @Transactional(rollbackFor=Exception.class)
public R<?> cancel(@PathVariable Long id) { public R<?> cancel(@PathVariable Long id) {
ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail("预约不存在"); ExamAppointment a = appointmentService.getById(id); if (a == null) return R.fail(MessageUtils.message("his.exam.appointment_not_found"));
a.setStatus("CANCELLED"); appointmentService.updateById(a); return R.ok(); a.setStatus("CANCELLED"); appointmentService.updateById(a); return R.ok();
} }
@GetMapping("/queue") @GetMapping("/queue")

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.check.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.*; import com.healthlink.his.check.domain.*;
import com.healthlink.his.check.service.*; import com.healthlink.his.check.service.*;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -54,7 +55,7 @@ public class RadiologyEnhancedController {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> notifyReport(@RequestParam Long id) { public R<?> notifyReport(@RequestParam Long id) {
RadiologyUrgentReport r = urgentReportService.getById(id); RadiologyUrgentReport r = urgentReportService.getById(id);
if (r == null) return R.fail("报告不存在"); if (r == null) return R.fail(MessageUtils.message("his.radiology.report_not_found"));
r.setNotifyStatus(1); r.setNotifyStatus(1);
r.setNotifyTime(new Date()); r.setNotifyTime(new Date());
r.setUpdateTime(new Date()); r.setUpdateTime(new Date());

View File

@@ -3,6 +3,7 @@ package com.healthlink.his.web.check.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.check.domain.SpecimenBarcode; import com.healthlink.his.check.domain.SpecimenBarcode;
import com.healthlink.his.check.service.ISpecimenBarcodeService; import com.healthlink.his.check.service.ISpecimenBarcodeService;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -76,7 +77,7 @@ public class SpecimenBarcodeController {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> scanConfirm(@PathVariable Long id, @RequestParam("confirmBy") String confirmBy) { public R<?> scanConfirm(@PathVariable Long id, @RequestParam("confirmBy") String confirmBy) {
SpecimenBarcode barcode = barcodeService.getById(id); SpecimenBarcode barcode = barcodeService.getById(id);
if (barcode == null) return R.fail("条码不存在"); if (barcode == null) return R.fail(MessageUtils.message("his.specimen.barcode_not_found"));
barcode.setStatus("SCANNED"); barcode.setStatus("SCANNED");
barcode.setScanConfirmTime(LocalDateTime.now()); barcode.setScanConfirmTime(LocalDateTime.now());
barcode.setScanConfirmBy(confirmBy); barcode.setScanConfirmBy(confirmBy);
@@ -88,7 +89,7 @@ public class SpecimenBarcodeController {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public R<?> reject(@PathVariable Long id) { public R<?> reject(@PathVariable Long id) {
SpecimenBarcode barcode = barcodeService.getById(id); SpecimenBarcode barcode = barcodeService.getById(id);
if (barcode == null) return R.fail("条码不存在"); if (barcode == null) return R.fail(MessageUtils.message("his.specimen.barcode_not_found"));
barcode.setStatus("REJECTED"); barcode.setStatus("REJECTED");
barcodeService.updateById(barcode); barcodeService.updateById(barcode);
return R.ok(barcode); return R.ok(barcode);

View File

@@ -3,6 +3,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.AjaxResult; import com.core.common.core.domain.AjaxResult;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.clinical.domain.*; import com.healthlink.his.clinical.domain.*;
import com.healthlink.his.clinical.service.*; import com.healthlink.his.clinical.service.*;
import com.healthlink.his.web.clinical.appservice.IClinicalPathwayAppService; import com.healthlink.his.web.clinical.appservice.IClinicalPathwayAppService;
@@ -42,7 +43,7 @@ public class ClinicalPathwayController {
public R<?> completePathway(@PathVariable Long id) { public R<?> completePathway(@PathVariable Long id) {
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1"); qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1");
ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail("执行记录不存在"); ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail(MessageUtils.message("his.clinical.execution_not_found"));
e.setStatus("COMPLETED"); e.setCompleteDate(java.time.LocalDate.now()); e.setStatus("COMPLETED"); e.setCompleteDate(java.time.LocalDate.now());
executionService.updateById(e); return R.ok(); executionService.updateById(e); return R.ok();
} }
@@ -51,7 +52,7 @@ public class ClinicalPathwayController {
public R<?> varyPathway(@PathVariable Long id, @RequestParam("reason") String reason) { public R<?> varyPathway(@PathVariable Long id, @RequestParam("reason") String reason) {
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1"); qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1");
ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail("执行记录不存在"); ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail(MessageUtils.message("his.clinical.execution_not_found"));
e.setStatus("VARIATION"); e.setVariationReason(reason); executionService.updateById(e); return R.ok(); e.setStatus("VARIATION"); e.setVariationReason(reason); executionService.updateById(e); return R.ok();
} }
@PreAuthorize("@ss.hasPermi('inpatient:clinical:list')") @PreAuthorize("@ss.hasPermi('inpatient:clinical:list')")

View File

@@ -214,7 +214,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 缓存中没有,从数据库查询 // 缓存中没有,从数据库查询
SurgeryDto surgeryDto = surgeryAppMapper.getSurgeryDetail(id); SurgeryDto surgeryDto = surgeryAppMapper.getSurgeryDetail(id);
if (surgeryDto == null) { if (surgeryDto == null) {
return R.fail("手术信息不存在"); return R.fail(MessageUtils.message("surgery.info.not.exist"));
} }
// 从申请单中获取次要手术信息 // 从申请单中获取次要手术信息
@@ -257,24 +257,24 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 校验患者是否存在 // 校验患者是否存在
Patient patient = patientService.getById(surgeryDto.getPatientId()); Patient patient = patientService.getById(surgeryDto.getPatientId());
if (patient == null) { if (patient == null) {
return R.fail("患者信息不存在"); return R.fail(MessageUtils.message("surgery.patient.not.exist"));
} }
// 校验就诊ID是否存在 // 校验就诊ID是否存在
if (surgeryDto.getEncounterId() == null) { if (surgeryDto.getEncounterId() == null) {
return R.fail("请选择就诊流水号"); return R.fail(MessageUtils.message("surgery.select.encounter"));
} }
// 校验就诊记录是否存在 // 校验就诊记录是否存在
Encounter encounter = encounterService.getById(surgeryDto.getEncounterId()); Encounter encounter = encounterService.getById(surgeryDto.getEncounterId());
if (encounter == null) { if (encounter == null) {
return R.fail("就诊记录不存在"); return R.fail(MessageUtils.message("surgery.encounter.not.exist"));
} }
// 获取患者的自费账户ID // 获取患者的自费账户ID
Long accountId = accountService.getSelfPayAccount(surgeryDto.getEncounterId()); Long accountId = accountService.getSelfPayAccount(surgeryDto.getEncounterId());
if (accountId == null) { if (accountId == null) {
return R.fail("未找到患者的账户信息,请先完成挂号或住院登记"); return R.fail(MessageUtils.message("surgery.account.not.found"));
} }
// 当前登录账号的科室id // 当前登录账号的科室id
@@ -443,7 +443,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
surgeryDto.getEncounterId(), surgeryDto.getPatientId(), surgeryDto.getEncounterId(), surgeryDto.getPatientId(),
surgeryId, surgeryDto.getSurgeryName())); surgeryId, surgeryDto.getSurgeryName()));
return R.ok(surgeryId, "手术申请提交成功!"); return R.ok(surgeryId, MessageUtils.message("his.surgery.submit_success"));
} }
/** /**
@@ -484,7 +484,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 校验手术是否存在 // 校验手术是否存在
Surgery existSurgery = surgeryService.getById(surgeryDto.getId()); Surgery existSurgery = surgeryService.getById(surgeryDto.getId());
if (existSurgery == null) { if (existSurgery == null) {
return R.fail("手术信息不存在"); return R.fail(MessageUtils.message("surgery.info.not.exist"));
} }
// 转换为实体对象 // 转换为实体对象
@@ -522,7 +522,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 清除相关缓存 // 清除相关缓存
clearSurgeryAppCache(surgery); clearSurgeryAppCache(surgery);
return R.ok(null, "手术申请修改成功!"); return R.ok(null, MessageUtils.message("his.surgery.update_success"));
} }
/** /**
@@ -537,12 +537,12 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 校验手术是否存在 // 校验手术是否存在
Surgery existSurgery = surgeryService.getById(id); Surgery existSurgery = surgeryService.getById(id);
if (existSurgery == null) { if (existSurgery == null) {
return R.fail("手术信息不存在"); return R.fail(MessageUtils.message("surgery.info.not.exist"));
} }
// 已完成的手术不能删除 // 已完成的手术不能删除
if (existSurgery.getStatusEnum() == 3) { if (existSurgery.getStatusEnum() == 3) {
return R.fail("已完成的手术不能删除"); return R.fail(MessageUtils.message("surgery.completed.cannot.delete"));
} }
// 级联删除关联数据 // 级联删除关联数据
@@ -587,7 +587,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
// 校验手术是否存在 // 校验手术是否存在
Surgery existSurgery = surgeryService.getById(id); Surgery existSurgery = surgeryService.getById(id);
if (existSurgery == null) { if (existSurgery == null) {
return R.fail("手术信息不存在"); return R.fail(MessageUtils.message("surgery.info.not.exist"));
} }
surgeryService.updateSurgeryStatus(id, statusEnum); surgeryService.updateSurgeryStatus(id, statusEnum);
@@ -607,7 +607,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
@Override @Override
public R<List<Encounter>> getEncounterListByPatientId(Long patientId) { public R<List<Encounter>> getEncounterListByPatientId(Long patientId) {
if (patientId == null) { if (patientId == null) {
return R.fail("患者ID不能为空"); return R.fail(MessageUtils.message("surgery.patient.id.empty"));
} }
// 查询该患者的所有就诊记录(进行中的优先) // 查询该患者的所有就诊记录(进行中的优先)
@@ -620,7 +620,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
List<Encounter> encounterList = encounterService.list(wrapper); List<Encounter> encounterList = encounterService.list(wrapper);
if (encounterList == null || encounterList.isEmpty()) { if (encounterList == null || encounterList.isEmpty()) {
return R.fail("该患者暂无就诊记录,请先挂号或办理住院"); return R.fail(MessageUtils.message("surgery.no.encounter.record"));
} }
return R.ok(encounterList); return R.ok(encounterList);
@@ -861,7 +861,7 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
.ne(excludeId != null, Surgery::getId, excludeId); .ne(excludeId != null, Surgery::getId, excludeId);
long conflictCount = surgeryService.count(wrapper); long conflictCount = surgeryService.count(wrapper);
if (conflictCount > 0) { if (conflictCount > 0) {
return R.fail("该手术室在该时间段已有手术安排,请选择其他时间或手术室"); return R.fail(MessageUtils.message("surgery.room.time.conflict"));
} }
return R.ok(false); return R.ok(false);
} }

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser; import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.healthlink.his.administration.domain.Patient; import com.healthlink.his.administration.domain.Patient;
import com.healthlink.his.administration.service.IPatientService; import com.healthlink.his.administration.service.IPatientService;
@@ -92,7 +93,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
public R<OpScheduleDto> getSurgeryScheduleDetail(Long scheduleId) { public R<OpScheduleDto> getSurgeryScheduleDetail(Long scheduleId) {
OpScheduleDto opScheduleDto = surgicalScheduleAppMapper.getSurgeryScheduleDetail(scheduleId); OpScheduleDto opScheduleDto = surgicalScheduleAppMapper.getSurgeryScheduleDetail(scheduleId);
if (opScheduleDto == null) { if (opScheduleDto == null) {
return R.fail("手术安排信息不存在"); return R.fail(MessageUtils.message("his.surgical_schedule.not_found"));
} }
return R.ok(opScheduleDto); return R.ok(opScheduleDto);
} }
@@ -110,7 +111,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
if (opCreateScheduleDto.getPatientId() != null) { if (opCreateScheduleDto.getPatientId() != null) {
Patient patient = patientService.getById(opCreateScheduleDto.getPatientId()); Patient patient = patientService.getById(opCreateScheduleDto.getPatientId());
if (patient == null) { if (patient == null) {
return R.fail("患者信息不存在"); return R.fail(MessageUtils.message("his.surgical_schedule.patient_not_found"));
} }
} }
@@ -125,7 +126,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
opCreateScheduleDto.getOperName() opCreateScheduleDto.getOperName()
); );
if (existsDuplicate != null && existsDuplicate) { if (existsDuplicate != null && existsDuplicate) {
return R.fail("该患者此手术单号已存在手术安排,请勿重复提交"); return R.fail(MessageUtils.message("his.surgical_schedule.duplicate_schedule"));
} }
} }
@@ -136,14 +137,14 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
if (startTime != null && endTime != null && roomCode != null && !roomCode.isEmpty()) { if (startTime != null && endTime != null && roomCode != null && !roomCode.isEmpty()) {
Boolean scheduleConflict = surgicalScheduleAppMapper.isScheduleConflict(startTime, endTime, roomCode); Boolean scheduleConflict = surgicalScheduleAppMapper.isScheduleConflict(startTime, endTime, roomCode);
if (scheduleConflict != null && scheduleConflict) { if (scheduleConflict != null && scheduleConflict) {
return R.fail("该时段内手术间被占用"); return R.fail(MessageUtils.message("his.surgical_schedule.room_occupied"));
} }
} }
// Bug #432 修复获取当前登录用户信息增加null校验防止NPE // Bug #432 修复获取当前登录用户信息增加null校验防止NPE
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) { if (loginUser == null) {
return R.fail("用户未登录或登录已过期"); return R.fail(MessageUtils.message("his.surgical_schedule.user_not_logged_in"));
} }
// 当前登录用户ID // 当前登录用户ID
Long userId = loginUser.getUserId(); Long userId = loginUser.getUserId();
@@ -205,7 +206,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
// 保存手术安排 // 保存手术安排
boolean saved = opScheduleService.save(opSchedule); boolean saved = opScheduleService.save(opSchedule);
if (!saved) { if (!saved) {
return R.fail("新增手术安排失败"); return R.fail(MessageUtils.message("his.surgical_schedule.add_failed"));
} }
syncSurgeryIncisionLevel(opSchedule.getOperCode(), opCreateScheduleDto.getIncisionLevel()); syncSurgeryIncisionLevel(opSchedule.getOperCode(), opCreateScheduleDto.getIncisionLevel());
@@ -255,7 +256,7 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
} }
} }
return R.ok("新增手术安排成功"); return R.ok(MessageUtils.message("his.surgical.add_success"));
} }
/** /**
@@ -269,11 +270,11 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
public R<?> updateSurgerySchedule(OpScheduleDto opScheduleDto) { public R<?> updateSurgerySchedule(OpScheduleDto opScheduleDto) {
// 校验手术安排是否存在 // 校验手术安排是否存在
if (opScheduleDto.getScheduleId() == null) { if (opScheduleDto.getScheduleId() == null) {
return R.fail("排程号不能为空"); return R.fail(MessageUtils.message("his.surgical_schedule.schedule_id_required"));
} }
OpSchedule existSchedule = opScheduleService.getById(opScheduleDto.getScheduleId()); OpSchedule existSchedule = opScheduleService.getById(opScheduleDto.getScheduleId());
if (existSchedule == null) { if (existSchedule == null) {
return R.fail("手术安排信息不存在"); return R.fail(MessageUtils.message("his.surgical_schedule.not_found"));
} }
// 转换为实体对象 // 转换为实体对象
@@ -324,12 +325,12 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
// 更新手术安排 // 更新手术安排
boolean updated = opScheduleService.updateById(opSchedule); boolean updated = opScheduleService.updateById(opSchedule);
if (!updated) { if (!updated) {
return R.fail("修改手术安排失败"); return R.fail(MessageUtils.message("his.surgical_schedule.update_failed"));
} }
syncSurgeryIncisionLevel(opScheduleDto.getOperCode(), opScheduleDto.getIncisionLevel()); syncSurgeryIncisionLevel(opScheduleDto.getOperCode(), opScheduleDto.getIncisionLevel());
return R.ok("修改手术安排成功"); return R.ok(MessageUtils.message("his.surgical.update_success"));
} }
/** /**
@@ -344,16 +345,16 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
// 校验手术安排是否存在 // 校验手术安排是否存在
OpSchedule existSchedule = opScheduleService.getById(scheduleId); OpSchedule existSchedule = opScheduleService.getById(scheduleId);
if (existSchedule == null) { if (existSchedule == null) {
return R.fail("手术安排信息不存在"); return R.fail(MessageUtils.message("his.surgical_schedule.not_found"));
} }
// 逻辑删除手术安排 // 逻辑删除手术安排
boolean deleted = opScheduleService.removeById(scheduleId); boolean deleted = opScheduleService.removeById(scheduleId);
if (!deleted) { if (!deleted) {
return R.fail("删除手术安排失败"); return R.fail(MessageUtils.message("his.surgical_schedule.delete_failed"));
} }
return R.ok(null, "删除手术安排成功"); return R.ok(null, MessageUtils.message("his.surgical.delete_success"));
} }
/** /**

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.clinicalmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.common.enums.ActivityDefCategory; import com.healthlink.his.common.enums.ActivityDefCategory;
import com.healthlink.his.web.clinicalmanage.appservice.ISurgicalScheduleAppService; import com.healthlink.his.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
import com.healthlink.his.web.clinicalmanage.dto.OpCreateScheduleDto; import com.healthlink.his.web.clinicalmanage.dto.OpCreateScheduleDto;
@@ -137,9 +138,9 @@ public class SurgicalScheduleController {
com.core.common.core.domain.model.LoginUser loginUser = com.core.common.utils.SecurityUtils.getLoginUser(); com.core.common.core.domain.model.LoginUser loginUser = com.core.common.utils.SecurityUtils.getLoginUser();
String encodedPassword = loginUser.getPassword(); String encodedPassword = loginUser.getPassword();
if (com.core.common.utils.SecurityUtils.matchesPassword(password, encodedPassword)) { if (com.core.common.utils.SecurityUtils.matchesPassword(password, encodedPassword)) {
return R.ok(true, "密码验证成功"); return R.ok(true, MessageUtils.message("his.surgical.password_verify_success"));
} else { } else {
return R.fail(false, "账户密码错误,请重新输入"); return R.fail(false, MessageUtils.message("his.surgical.password_verify_failed"));
} }
} }

View File

@@ -12,6 +12,7 @@ import com.core.common.enums.DelFlag;
import com.core.common.utils.AgeCalculatorUtil; import com.core.common.utils.AgeCalculatorUtil;
import com.core.common.utils.AssignSeqUtil; import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.SecurityUtils; import com.core.common.utils.SecurityUtils;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.StringUtils; import com.core.common.utils.StringUtils;
import com.core.common.utils.bean.BeanUtils; import com.core.common.utils.bean.BeanUtils;
import com.healthlink.his.administration.domain.Location; import com.healthlink.his.administration.domain.Location;
@@ -311,7 +312,7 @@ public class CommonServiceImpl implements ICommonService {
// 医保编码和生产厂家校验 // 医保编码和生产厂家校验
for (LocationInventoryDto dto : locationInventoryDtoList) { for (LocationInventoryDto dto : locationInventoryDtoList) {
if (StringUtils.isNotEmpty(dto.getYbNo()) && StringUtils.isEmpty(dto.getManufacturerText())) { if (StringUtils.isNotEmpty(dto.getYbNo()) && StringUtils.isEmpty(dto.getManufacturerText())) {
return R.fail("生产厂家不能为空,请到药品目录维护"); return R.fail(MessageUtils.message("common.manufacturer.required"));
} }
} }
return R.ok(locationInventoryDtoList); return R.ok(locationInventoryDtoList);
@@ -521,9 +522,9 @@ public class CommonServiceImpl implements ICommonService {
} }
} }
// 方式 3如果仍然没有病区且 currentOrgId 为空,尝试获取所有病区 // 方式 3如果仍然没有病区尝试获取所有活动病区
if (wardList.isEmpty() && currentOrgId == null) { if (wardList.isEmpty()) {
log.info("getPractitionerWard - 尝试获取所有病区"); log.info("getPractitionerWard - 未查到指定病区,获取所有病区");
List<Location> allWards = locationService.getWardList(null); List<Location> allWards = locationService.getWardList(null);
log.info("getPractitionerWard - 所有病区数:{}", allWards != null ? allWards.size() : 0); log.info("getPractitionerWard - 所有病区数:{}", allWards != null ? allWards.size() : 0);
if (allWards != null && !allWards.isEmpty()) { if (allWards != null && !allWards.isEmpty()) {
@@ -700,7 +701,7 @@ public class CommonServiceImpl implements ICommonService {
List<InventoryDetailDto> inventoryDetailList = commonAppMapper List<InventoryDetailDto> inventoryDetailList = commonAppMapper
.selectMedicineInventoryDetail(medicationIdList, locationIdList, PublicationStatus.ACTIVE.getValue()); .selectMedicineInventoryDetail(medicationIdList, locationIdList, PublicationStatus.ACTIVE.getValue());
if (inventoryDetailList == null || inventoryDetailList.isEmpty()) { if (inventoryDetailList == null || inventoryDetailList.isEmpty()) {
return R.fail("发药单生成失败,请检查药品库存"); return R.fail(MessageUtils.message("common.drug.dispense.fail.check.stock"));
} }
// 将库存信息根据药品id和库房id进行分组 // 将库存信息根据药品id和库房id进行分组
Map<String, List<InventoryDetailDto>> inventoryDetailMap = inventoryDetailList.stream() Map<String, List<InventoryDetailDto>> inventoryDetailMap = inventoryDetailList.stream()
@@ -720,7 +721,7 @@ public class CommonServiceImpl implements ICommonService {
+ medicationDispense.getLocationId(); + medicationDispense.getLocationId();
// 查询对应的库存信息 // 查询对应的库存信息
if (!inventoryDetailMap.containsKey(inventoryKey)) { if (!inventoryDetailMap.containsKey(inventoryKey)) {
return R.fail("药品库存不存在药品ID: " + medicationDispense.getMedicationId()); return R.fail(MessageUtils.message("common.drug.stock.not.exist", medicationDispense.getMedicationId()));
} }
List<InventoryDetailDto> inventoryList = inventoryDetailMap.get(inventoryKey); List<InventoryDetailDto> inventoryList = inventoryDetailMap.get(inventoryKey);
@@ -788,13 +789,13 @@ public class CommonServiceImpl implements ICommonService {
// 校验药品库存 // 校验药品库存
for (MedicationDispense medicationDispense : medicationDispenseList) { for (MedicationDispense medicationDispense : medicationDispenseList) {
if (medicationDispense.getLotNumber() == null) { if (medicationDispense.getLotNumber() == null) {
return R.fail("药品:" + medicationDispense.getMedicationId() + "库存不足,请检查药品库存"); return R.fail(MessageUtils.message("common.drug.stock.insufficient", medicationDispense.getMedicationId()));
} }
} }
// 新增或更新发药单 // 新增或更新发药单
boolean result = medicationDispenseService.saveOrUpdateBatch(medicationDispenseList); boolean result = medicationDispenseService.saveOrUpdateBatch(medicationDispenseList);
if (!result) { if (!result) {
return R.fail("发药单生成失败,请联系管理员"); return R.fail(MessageUtils.message("common.drug.dispense.fail.contact.admin"));
} }
} }
// 查询患者待发放的耗材信息 // 查询患者待发放的耗材信息
@@ -815,7 +816,7 @@ public class CommonServiceImpl implements ICommonService {
List<InventoryDetailDto> inventoryDetailList = commonAppMapper.selectDeviceInventoryDetail(deviceIdList, List<InventoryDetailDto> inventoryDetailList = commonAppMapper.selectDeviceInventoryDetail(deviceIdList,
locationIdList, PublicationStatus.ACTIVE.getValue()); locationIdList, PublicationStatus.ACTIVE.getValue());
if (inventoryDetailList == null || inventoryDetailList.isEmpty()) { if (inventoryDetailList == null || inventoryDetailList.isEmpty()) {
return R.fail("发耗材单生成失败,请检查耗材库存"); return R.fail(MessageUtils.message("common.device.dispense.fail.check.stock"));
} }
// 将库存信息根据耗材id和库房id进行分组 // 将库存信息根据耗材id和库房id进行分组
Map<String, List<InventoryDetailDto>> inventoryDetailMap = inventoryDetailList.stream() Map<String, List<InventoryDetailDto>> inventoryDetailMap = inventoryDetailList.stream()
@@ -835,7 +836,7 @@ public class CommonServiceImpl implements ICommonService {
= deviceDispense.getDeviceDefId() + CommonConstants.Common.DASH + deviceDispense.getLocationId(); = deviceDispense.getDeviceDefId() + CommonConstants.Common.DASH + deviceDispense.getLocationId();
// 查询对应的库存信息 // 查询对应的库存信息
if (!inventoryDetailMap.containsKey(inventoryKey)) { if (!inventoryDetailMap.containsKey(inventoryKey)) {
return R.fail("耗材库存不存在耗材ID: " + deviceDispense.getDeviceDefId()); return R.fail(MessageUtils.message("common.device.stock.not.exist", deviceDispense.getDeviceDefId()));
} }
List<InventoryDetailDto> inventoryList = inventoryDetailMap.get(inventoryKey); List<InventoryDetailDto> inventoryList = inventoryDetailMap.get(inventoryKey);
@@ -903,13 +904,13 @@ public class CommonServiceImpl implements ICommonService {
// 校验耗材库存 // 校验耗材库存
for (DeviceDispense deviceDispense : deviceDispenseList) { for (DeviceDispense deviceDispense : deviceDispenseList) {
if (deviceDispense.getLotNumber() == null) { if (deviceDispense.getLotNumber() == null) {
return R.fail("耗材:" + deviceDispense.getDeviceDefId() + "库存不足,请检查耗材库存"); return R.fail(MessageUtils.message("common.device.stock.insufficient", deviceDispense.getDeviceDefId()));
} }
} }
// 新增或更新发耗材单 // 新增或更新发耗材单
boolean result = deviceDispenseService.saveOrUpdateBatch(deviceDispenseList); boolean result = deviceDispenseService.saveOrUpdateBatch(deviceDispenseList);
if (!result) { if (!result) {
return R.fail("发耗材单生成失败,请联系管理员"); return R.fail(MessageUtils.message("common.device.dispense.fail.contact.admin"));
} }
} }
return R.ok(); return R.ok();

View File

@@ -1,6 +1,7 @@
package com.healthlink.his.web.consultation.controller; package com.healthlink.his.web.consultation.controller;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.web.consultation.appservice.IConsultationAppService; import com.healthlink.his.web.consultation.appservice.IConsultationAppService;
import com.healthlink.his.web.consultation.dto.ConsultationActivityDto; import com.healthlink.his.web.consultation.dto.ConsultationActivityDto;
import com.healthlink.his.web.consultation.dto.ConsultationConfirmationDto; import com.healthlink.his.web.consultation.dto.ConsultationConfirmationDto;
@@ -46,7 +47,7 @@ public class ConsultationController {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("获取会诊列表失败", e); log.error("获取会诊列表失败", e);
return R.fail("获取会诊列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_list_failed", e.getMessage()));
} }
} }
@@ -61,7 +62,7 @@ public class ConsultationController {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("查询会诊申请列表失败", e); log.error("查询会诊申请列表失败", e);
return R.fail("查询会诊申请列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.query_list_failed", e.getMessage()));
} }
} }
@@ -81,7 +82,7 @@ public class ConsultationController {
return R.ok(page); return R.ok(page);
} catch (Exception e) { } catch (Exception e) {
log.error("分页查询会诊申请列表失败", e); log.error("分页查询会诊申请列表失败", e);
return R.fail("分页查询会诊申请列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.query_page_failed", e.getMessage()));
} }
} }
@@ -93,10 +94,10 @@ public class ConsultationController {
public R<String> saveConsultation(@RequestBody ConsultationRequestDto dto) { public R<String> saveConsultation(@RequestBody ConsultationRequestDto dto) {
try { try {
Boolean result = consultationAppService.saveConsultation(dto); Boolean result = consultationAppService.saveConsultation(dto);
return result ? R.ok("保存成功") : R.fail("保存失败"); return result ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} catch (Exception e) { } catch (Exception e) {
log.error("保存会诊申请失败", e); log.error("保存会诊申请失败", e);
return R.fail("保存会诊申请失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.save_failed", e.getMessage()));
} }
} }
@@ -109,10 +110,10 @@ public class ConsultationController {
@Parameter(description = "会诊申请单号") @RequestParam String consultationId) { @Parameter(description = "会诊申请单号") @RequestParam String consultationId) {
try { try {
Boolean result = consultationAppService.submitConsultation(consultationId); Boolean result = consultationAppService.submitConsultation(consultationId);
return result ? R.ok("提交成功") : R.fail("提交失败"); return result ? R.ok(MessageUtils.message("msg.success")) : R.fail(MessageUtils.message("msg.failure"));
} catch (Exception e) { } catch (Exception e) {
log.error("提交会诊申请失败", e); log.error("提交会诊申请失败", e);
return R.fail("提交会诊申请失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.submit_failed", e.getMessage()));
} }
} }
@@ -126,10 +127,10 @@ public class ConsultationController {
@Parameter(description = "作废原因") @RequestParam(required = false) String cancelReason) { @Parameter(description = "作废原因") @RequestParam(required = false) String cancelReason) {
try { try {
Boolean result = consultationAppService.cancelConsultation(consultationId, cancelReason); Boolean result = consultationAppService.cancelConsultation(consultationId, cancelReason);
return result ? R.ok("作废成功") : R.fail("作废失败"); return result ? R.ok(MessageUtils.message("his.consultation.void_success")) : R.fail(MessageUtils.message("his.consultation.void_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("作废会诊申请失败", e); log.error("作废会诊申请失败", e);
return R.fail("作废会诊申请失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.void_error", e.getMessage()));
} }
} }
@@ -142,10 +143,10 @@ public class ConsultationController {
@Parameter(description = "会诊申请单号") @RequestParam String consultationId) { @Parameter(description = "会诊申请单号") @RequestParam String consultationId) {
try { try {
Boolean result = consultationAppService.completeConsultation(consultationId); Boolean result = consultationAppService.completeConsultation(consultationId);
return result ? R.ok("结束成功") : R.fail("结束失败"); return result ? R.ok(MessageUtils.message("his.consultation.complete_success")) : R.fail(MessageUtils.message("his.consultation.complete_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("结束会诊申请失败", e); log.error("结束会诊申请失败", e);
return R.fail("结束会诊申请失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.complete_error", e.getMessage()));
} }
} }
@@ -160,7 +161,7 @@ public class ConsultationController {
return R.ok(tree); return R.ok(tree);
} catch (Exception e) { } catch (Exception e) {
log.error("获取科室医生树失败", e); log.error("获取科室医生树失败", e);
return R.fail("获取科室医生树失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_dept_tree_failed", e.getMessage()));
} }
} }
@@ -176,7 +177,7 @@ public class ConsultationController {
return R.ok(diagnosis); return R.ok(diagnosis);
} catch (Exception e) { } catch (Exception e) {
log.error("获取主诊断失败", e); log.error("获取主诊断失败", e);
return R.fail("获取主诊断失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_diagnosis_failed", e.getMessage()));
} }
} }
@@ -191,7 +192,7 @@ public class ConsultationController {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("获取我的会诊邀请失败", e); log.error("获取我的会诊邀请失败", e);
return R.fail("获取我的会诊邀请失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_invitations_failed", e.getMessage()));
} }
} }
@@ -206,7 +207,7 @@ public class ConsultationController {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("获取会诊项目列表失败", e); log.error("获取会诊项目列表失败", e);
return R.fail("获取会诊项目列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_activities_failed", e.getMessage()));
} }
} }
@@ -223,7 +224,7 @@ public class ConsultationController {
return R.ok(list); return R.ok(list);
} catch (Exception e) { } catch (Exception e) {
log.error("获取待确认会诊列表失败", e); log.error("获取待确认会诊列表失败", e);
return R.fail("获取待确认会诊列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_pending_failed", e.getMessage()));
} }
} }
@@ -235,10 +236,10 @@ public class ConsultationController {
public R<String> confirmConsultation(@RequestBody ConsultationConfirmationDto dto) { public R<String> confirmConsultation(@RequestBody ConsultationConfirmationDto dto) {
try { try {
Boolean result = consultationAppService.confirmConsultation(dto); Boolean result = consultationAppService.confirmConsultation(dto);
return result ? R.ok("确认成功") : R.fail("确认失败"); return result ? R.ok(MessageUtils.message("his.consultation.confirm_success")) : R.fail(MessageUtils.message("his.consultation.confirm_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("确认会诊失败", e); log.error("确认会诊失败", e);
return R.fail("确认会诊失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.confirm_error", e.getMessage()));
} }
} }
@@ -250,10 +251,10 @@ public class ConsultationController {
public R<String> cancelConfirmation(@Parameter(description = "会诊申请单号") @RequestParam String consultationId) { public R<String> cancelConfirmation(@Parameter(description = "会诊申请单号") @RequestParam String consultationId) {
try { try {
Boolean result = consultationAppService.cancelConfirmation(consultationId); Boolean result = consultationAppService.cancelConfirmation(consultationId);
return result ? R.ok("取消确认成功") : R.fail("取消确认失败"); return result ? R.ok(MessageUtils.message("his.consultation.cancel_confirm_success")) : R.fail(MessageUtils.message("his.consultation.cancel_confirm_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("取消确认会诊失败", e); log.error("取消确认会诊失败", e);
return R.fail("取消确认会诊失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.cancel_confirm_error", e.getMessage()));
} }
} }
@@ -265,10 +266,10 @@ public class ConsultationController {
public R<String> signConsultation(@Parameter(description = "会诊申请单号") @RequestParam String consultationId) { public R<String> signConsultation(@Parameter(description = "会诊申请单号") @RequestParam String consultationId) {
try { try {
Boolean result = consultationAppService.signConsultation(consultationId); Boolean result = consultationAppService.signConsultation(consultationId);
return result ? R.ok("签名成功") : R.fail("签名失败"); return result ? R.ok(MessageUtils.message("his.consultation.sign_success")) : R.fail(MessageUtils.message("his.consultation.sign_failed"));
} catch (Exception e) { } catch (Exception e) {
log.error("签名会诊失败", e); log.error("签名会诊失败", e);
return R.fail("签名会诊失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.sign_error", e.getMessage()));
} }
} }
@@ -284,7 +285,7 @@ public class ConsultationController {
return R.ok(detail); return R.ok(detail);
} catch (Exception e) { } catch (Exception e) {
log.error("获取会诊确认详情失败", e); log.error("获取会诊确认详情失败", e);
return R.fail("获取会诊确认详情失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_detail_failed", e.getMessage()));
} }
} }
@@ -300,7 +301,7 @@ public class ConsultationController {
return R.ok(opinions); return R.ok(opinions);
} catch (Exception e) { } catch (Exception e) {
log.error("获取会诊意见列表失败", e); log.error("获取会诊意见列表失败", e);
return R.fail("获取会诊意见列表失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_opinions_failed", e.getMessage()));
} }
} }
@@ -316,7 +317,7 @@ public class ConsultationController {
return R.ok(detail); return R.ok(detail);
} catch (Exception e) { } catch (Exception e) {
log.error("查询会诊申请详情失败", e); log.error("查询会诊申请详情失败", e);
return R.fail("查询会诊申请详情失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.query_detail_failed", e.getMessage()));
} }
} }
@@ -335,7 +336,7 @@ public class ConsultationController {
return R.ok(result); return R.ok(result);
} catch (Exception e) { } catch (Exception e) {
log.error("检查会诊时限失败", e); log.error("检查会诊时限失败", e);
return R.fail("检查会诊时限失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.check_time_limit_failed", e.getMessage()));
} }
} }
@@ -350,7 +351,7 @@ public class ConsultationController {
return R.ok(stats); return R.ok(stats);
} catch (Exception e) { } catch (Exception e) {
log.error("获取会诊时限统计失败", e); log.error("获取会诊时限统计失败", e);
return R.fail("获取会诊时限统计失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.consultation.get_time_stats_failed", e.getMessage()));
} }
} }

View File

@@ -2,6 +2,7 @@ package com.healthlink.his.web.cssd.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.cssd.domain.*; import com.healthlink.his.cssd.domain.*;
import com.healthlink.his.cssd.service.*; import com.healthlink.his.cssd.service.*;
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j; import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
@@ -46,7 +47,7 @@ public class CssdController {
LambdaQueryWrapper<CssdTray> tw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CssdTray> tw = new LambdaQueryWrapper<>();
tw.eq(CssdTray::getTrayCode, trayCode); tw.eq(CssdTray::getTrayCode, trayCode);
CssdTray tray = trayService.getOne(tw); CssdTray tray = trayService.getOne(tw);
if (tray == null) return R.fail("器械包不存在: " + trayCode); if (tray == null) return R.fail(MessageUtils.message("his.cssd.tray_not_found", trayCode));
// 更新器械包状态 // 更新器械包状态
Map<String, String> statusFlow = Map.of( Map<String, String> statusFlow = Map.of(
@@ -113,7 +114,7 @@ public class CssdController {
} }
@PutMapping("/sterilize/complete/{id}") @Transactional(rollbackFor=Exception.class) @PutMapping("/sterilize/complete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> completeBatch(@PathVariable Long id, @RequestBody Map<String, Object> params) { public R<?> completeBatch(@PathVariable Long id, @RequestBody Map<String, Object> params) {
CssdSterilizeBatch b = batchService.getById(id); if (b == null) return R.fail("批次不存在"); CssdSterilizeBatch b = batchService.getById(id); if (b == null) return R.fail(MessageUtils.message("his.cssd.batch_not_found"));
b.setEndTime(new Date()); b.setEndTime(new Date());
b.setBiologicalResult((String) params.getOrDefault("biologicalResult", "PASS")); b.setBiologicalResult((String) params.getOrDefault("biologicalResult", "PASS"));
b.setChemicalResult((String) params.getOrDefault("chemicalResult", "PASS")); b.setChemicalResult((String) params.getOrDefault("chemicalResult", "PASS"));
@@ -123,9 +124,9 @@ public class CssdController {
} }
@PutMapping("/sterilize/release/{id}") @Transactional(rollbackFor=Exception.class) @PutMapping("/sterilize/release/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> releaseBatch(@PathVariable Long id, @RequestParam("releaseBy") String releaseBy) { public R<?> releaseBatch(@PathVariable Long id, @RequestParam("releaseBy") String releaseBy) {
CssdSterilizeBatch b = batchService.getById(id); if (b == null) return R.fail("批次不存在"); CssdSterilizeBatch b = batchService.getById(id); if (b == null) return R.fail(MessageUtils.message("his.cssd.batch_not_found"));
if (!"PASS".equals(b.getBiologicalResult()) || !"PASS".equals(b.getChemicalResult()) || !"PASS".equals(b.getPhysicalResult())) { if (!"PASS".equals(b.getBiologicalResult()) || !"PASS".equals(b.getChemicalResult()) || !"PASS".equals(b.getPhysicalResult())) {
return R.fail("三项监测未全部合格,禁止释放"); return R.fail(MessageUtils.message("his.cssd.monitor_not_passed"));
} }
b.setBatchStatus("RELEASED"); b.setReleaseBy(releaseBy); b.setReleaseTime(new Date()); b.setBatchStatus("RELEASED"); b.setReleaseBy(releaseBy); b.setReleaseTime(new Date());
batchService.updateById(b); return R.ok(b); batchService.updateById(b); return R.ok(b);

View File

@@ -1,6 +1,7 @@
package com.healthlink.his.web.datacollection.appservice.impl; package com.healthlink.his.web.datacollection.appservice.impl;
import com.core.common.core.domain.R; import com.core.common.core.domain.R;
import com.core.common.utils.MessageUtils;
import com.healthlink.his.quality.service.IBusinessAnalyticsService; import com.healthlink.his.quality.service.IBusinessAnalyticsService;
import com.healthlink.his.quality.domain.BusinessAnalytics; import com.healthlink.his.quality.domain.BusinessAnalytics;
import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService; import com.healthlink.his.web.datacollection.appservice.IDataCollectionAppService;
@@ -43,7 +44,7 @@ public class DataCollectionAppServiceImpl implements IDataCollectionAppService {
log.error("临床数据采集失败", e); log.error("临床数据采集失败", e);
result.put("status", "error"); result.put("status", "error");
result.put("message", "采集失败: " + e.getMessage()); result.put("message", "采集失败: " + e.getMessage());
return R.fail("采集失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.datacollect.collect_failed", e.getMessage()));
} }
return R.ok(result); return R.ok(result);
} }
@@ -71,7 +72,7 @@ public class DataCollectionAppServiceImpl implements IDataCollectionAppService {
log.error("运营数据采集失败", e); log.error("运营数据采集失败", e);
result.put("status", "error"); result.put("status", "error");
result.put("message", "采集失败: " + e.getMessage()); result.put("message", "采集失败: " + e.getMessage());
return R.fail("采集失败: " + e.getMessage()); return R.fail(MessageUtils.message("his.datacollect.collect_failed", e.getMessage()));
} }
return R.ok(result); return R.ok(result);
} }

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