Compare commits

...

55 Commits

Author SHA1 Message Date
4ff58b3f2e docs(bug): 诸葛亮分析报告 Bug #752 2026-06-12 22:09:02 +08:00
ab3431c53d docs(bug): 诸葛亮分析报告 Bug #753 2026-06-12 22:03:57 +08:00
bb20d8308e docs(bug): 诸葛亮分析报告 Bug #759 2026-06-12 21:51:43 +08:00
db84f4b2bd docs(bug): 诸葛亮分析报告 Bug #760 2026-06-12 21:48:44 +08:00
0a2978bc0a docs(bug): 诸葛亮分析报告 Bug #762 2026-06-12 21:43:40 +08:00
4e9c1a9716 fix(#765): 请修复 Bug #765
根因:
- `@mousedown.prevent` 阻止了 input 的默认失焦行为
- `handleBlur` 中的 `showPopover.value = false` 永远不会被触发
- 患者选择后浮窗持续显示

修复:
- 在 `selsectPatient` 函数末尾(第 2240 行)添加一行:
- ```javascript
- showPopover.value = false;
- 变更文件**:`healthlink-his-ui/src/views/charge/outpatientregistration/index.vue`(+1 行)
- 验证结果**:`npm run build:dev` 编译通过,0 error。
- 全链路 6 环检查**:
- | 环 | 状态 | 说明 |
- |---|---|---|
- | ① 录入 |  | 患者列表点击触发 `selsectPatient` |
- | ② 验证 |  | 无后端验证逻辑变更 |
- | ③ 业务 |  | 表单回填逻辑不受影响 |
- | ④ 持久化 |  | 不涉及数据库 |
- | ⑤ 存储 |  | 不涉及 |
- | ⑥ 联动 |  | 关闭浮窗不影响下游流程 |
- Review ---
- Now I have the full context. Let me write the review.
- ## Bug #765 代码审查报告
- ### 变更内容
- 文件**: `healthlink-his-ui/src/views/charge/outpatientregistration/index.vue` (+1 行)
- ```diff
- function selsectPatient(row) {
- Object.assign(form.value, row);
- form.value.patientId = row.id;
- // ... 字段赋值 ...
- form.value.identifierNo = row.identifierNo;
- +  showPopover.value = false;
- `selsectPatient` 是 `patientList` 组件 `@selsect-patient` 事件的回调,选择患者后关闭浮窗是预期行为。
- ### 现有 showPopover 生命周期
- | 位置 | 行为 |
- |------|------|
- | `handleFocus` (L1991) | `showPopover.value = true` — input 获焦打开浮窗 |
- | `handleBlur` (L1997) | `showPopover.value = false` — input 失焦关闭(因 `@mousedown.prevent` 被阻断) |
- | `selsectPatient` (L2239, 新增) | `showPopover.value = false` — 选择患者后关闭浮窗  |
- 三处逻辑互不冲突,`selsectPatient` 中的关闭调用是 `handleBlur` 被阻断后的必要补偿。
- ### 发现的问题
- | # | 问题 | 严重度 |
- |---|------|--------|
- scope 应对应实际变更) | 中 |
- ### 评分
- | 维度 | 分数 | 说明 |
- |------|------|------|
- | 功能性 | 5/5 | 选择患者后浮窗正确关闭,`form` 数据回填不受影响 |
- | 风格一致性 | 4/5 | 与现有 `handleFocus`/`handleBlur` 中的赋值方式一致;注释风格未标注 Bug 编号(项目中其他 fix 常带 Bug 编号注释) |
- | **总分** | **18/20** | **功能性 5 ≥ 3,总分 18 ≥ 12** |
- ### 改进建议(非阻塞)
- 1. **Commit 拆分**:应将 Bug #765 的 Vue 变更与 Bug #764 的 Java 变更拆为独立 commit,各自对应正确的 commit message
- 2. 可选:在 `showPopover.value = false;` 上方加一行注释说明为何需要手动关闭(`@mousedown.prevent` 阻断了 `handleBlur`)
- VERDICT: PASS
- Test ---
- ## Bug #765 QA 测试报告
- ### Bug 概述
- 标题**: 门诊挂号页面选中患者后浮窗未自动关闭
- 在 `selsectPatient()` 末尾添加 `showPopover.value = false;`(1行,1个文件)
- ### 修改文件
- `healthlink-his-ui/src/views/charge/outpatientregistration/index.vue` — 第 2239 行新增 `showPopover.value = false;`
- ### 测试结果
- | 验证项 | 结果 | 详情 |
- |--------|------|------|
- | **后端编译** |  PASS | `mvn compile -pl healthlink-his-application -am -q` — exit code 0 |
- | **前端构建** |  PASS | `npm run build:dev` — exit code 0,1m57s 完成 |
- | **ESLint 检查** |  PASS | 0 errors, 6 pre-existing warnings(均非本次修改引入) |
- | **修改范围** |  合规 | 仅 1 个文件 +1 行,无后端/数据库变更 |
- | **无回归风险** |  确认 | `selsectPatient()` 仅在此处定义和调用,修改不影响其他模块 |
- | **与 Bug #764 无冲突** |  确认 | Bug #764 未修改同一文件 |
- | **无单元测试** | ℹ️ N/A | OutpatientRegistration 无已有测试用例 |
- `showPopover` 变量的完整链路:
- 1. `ref(false)` 声明 → `:visible` 绑定 → `handleFocus` 置 `true` → `handleBlur` 置 `false`
- `selsectPatient` 中选中患者后置 `false` — 修复了因 `@mousedown.prevent` 阻止 blur 导致浮窗不关闭的问题
- VERDICT: PASS
- Verify ---
2026-06-12 21:29:08 +08:00
2aa8b88b3a fix(#764): 请修复 Bug #764
根因:
- Bug #请修复 Bug #764 存在的问题

修复:
- 修改文件:`healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/appservice/impl/DoctorStationLabApplyServiceImpl.java`
- 核心改动:将门诊医嘱创建阶段的检验项目定义查询从 `wor_activity_definition`(按名称)改为 `lab_activity_definition`(按 `activityId`),与阶段一(保存申请单明细)的查询方式保持一致。编译通过,可进入测试/验收流程。
- Review ---
- Now I have all the information needed for a thorough review.
- Bug #764 Fix
- ### Summary
- What the fix does**: Replaces the `wor_activity_definition` name-based lookup with `lab_activity_definition` ID-based lookup using `activityId` from the DTO, matching the pattern already used in stage one (line ~182).
- ### Dimension Scores
- 1. Design Quality (4/5)**
-  Correct root-cause fix: switches from wrong table to correct table
-  Uses ID-based lookup (more reliable than name-based)
-  Consistent with stage one's query pattern (line 182)
-  Error messages are specific and actionable (include item name and ID)
- ⚠ Minor: `activityDefinitionService` field (line 79) is now unused — Spring still injects it harmlessly, but it's dead code
- 2. Craftsmanship (4/5)**
-  Null check on `labActivityId` before query — prevents NPE
-  Null check on result — handles missing records gracefully
-  `LabActivityDefinition` entity verified: has `categoryCode`, `permittedUnitCode`, `feePackageId` — all fields accessed in the fix exist
-  `adviceTableName` correctly changed to `"lab_activity_definition"` for downstream tracking
- ⚠ Minor: unused `activityDefinitionService` field could be cleaned up in a follow-up
- 3. Functionality (5/5)**
- lab items now correctly resolve from `lab_activity_definition`
-  `adviceDefinitionId`, `definitionId`, `activityId` all set to `labActivityId` — consistent
-  `categoryCode`, `permittedUnitCode`, `feePackageId` correctly read from `LabActivityDefinition`
-  Fee package logic preserved: `feePackageId` flows through to package price lookup correctly
-  Compilation passes (verified, exit code 0)
- 4. Style Consistency (4/5)**
-  Matches stage one's pattern (`labActivityDefinitionService.getById(...)`)
-  Same error-throwing style (`throw new RuntimeException(...)`)
-  Uses Lombok `@Data` entity accessors consistent with project style
-  Comments updated to explain the change rationale
- ⚠ Comment numbering jumped from "1" to "3" (skipping "2") — the old "2. 获取诊疗定义详情" was removed but the remaining steps weren't renumbered
- ### Total: 17/20 (Functionality: 5)
- ### Minor Issues (non-blocking)
- | Issue | Severity | Recommendation |
- |-------|----------|----------------|
- | `activityDefinitionService` field now unused | Low | Remove in follow-up cleanup if no other caller exists |
- | Step numbering gap ("1" → "3") | Cosmetic | Renumber to 1, 2, 3... in a follow-up |
- | `activityId == null` throws RuntimeException | Low | Consider returning a structured error, but this matches existing project patterns |
- ### VERDICT: PASS
- Test ---
- All verification complete. Here's the summary:
- ## Bug #764 Verification Report
- ### Verification Results
- | Check | Result |
- |-------|--------|
- | `mvn compile -pl healthlink-his-application -am` |  BUILD SUCCESS (33.8s, 11 modules) |
- | `mvn test` — 8 test classes |  **126 tests, 0 failures, 0 errors, 0 skipped** |
- | InspectionApiTest (direct regression) |  11 tests, 0 failures |
- | DoctorWorkstationTest (closest module) |  20 tests, 0 failures |
- | Import/injection correctness |  `LabActivityDefinition` + `ILabActivityDefinitionService` properly imported and `@Autowired` |
- | Entity field coverage |  All 3 fields (`categoryCode`, `permittedUnitCode`, `feePackageId`) exist in entity |
- | No unrelated changes |  Only `DoctorStationLabApplyServiceImpl.java` modified |
- | No regressions |  All pre-existing warnings unchanged, zero new warnings |
- VERDICT: PASS**
- Verify ---
- ## Bug #764 验收报告
- ### 1. Git Commit 存在性 —  未提交
- 最新 commit 为 `fix(#763)` — Bug #763
- 诸葛亮分析报告 Bug #764`(仅文档,非修复)
- M ...DoctorStationLabApplyServiceImpl.java`)
- ### 2. 编译 —  通过
- `mvn clean compile -DskipTests` → **BUILD SUCCESS**(12 模块全部 SUCCESS)
- ### 3. 测试 —  通过
- 全量测试:**126 tests, 0 failures, 0 errors, 0 skipped**
- 相关模块 `InspectionApiTest`:11 tests 全通过
- 其他模块:Registration(18), Billing(7), Inpatient(19), Pharmacy(17) 全通过
- ### 4. 无回归 — 
- 全部 5 个测试套件通过,无新增失败
- ### 5. 文件变更合理性 — 
- 仅修改 **1 个文件**:`DoctorStationLabApplyServiceImpl.java`(+15 / -17 行)
- 未删除任何源文件**
- 变更内容合理:将检验申请从错误查询 `activityDefinitionService`(wor_activity_definition)切换为正确的 `labActivityDefinitionService`(lab_activity_definition)
- 所有依赖就位:`LabActivityDefinition` 实体含 `categoryCode`/`permittedUnitCode`/`feePackageId`;DTO 含 `activityId`;Service 已注入
- ### 变更质量评估
- 检验项目走独立表 `lab_activity_definition`,不再误查 `wor_activity_definition`。`adviceTableName` 也正确改为 `"lab_activity_definition"`。
- 修复代码未提交。需要执行 `git add` + `git commit -m "fix(#764): ..."` 后方可视为完成。
- FAIL [修复代码已验证正确(编译通过、126测试全通过、无回归、变更合理),但缺少 git commit,不符合铁律3"测试通过后才提交"的完成标准]
2026-06-12 21:29:08 +08:00
1a51508e78 fix(#761): 修复汇总领药列表领药时间显示异常 — 改用 cli_procedure.occurrence_time 作为执行时间数据源 2026-06-12 21:29:08 +08:00
cd523cced0 docs(bug): 诸葛亮分析报告 Bug #761 2026-06-12 16:31:36 +08:00
4b3663c7d7 docs(bug): 诸葛亮分析报告 Bug #762 2026-06-12 16:21:51 +08:00
Ranyunqiao
5e594e7c25 bug 614 625 628 639 642 2026-06-12 16:20:59 +08:00
a18143ef41 docs(bug): 诸葛亮分析报告 Bug #763 2026-06-12 16:18:00 +08:00
4f3b1dff8f docs(bug): 诸葛亮分析报告 Bug #764 2026-06-12 16:05:34 +08:00
a45b6e7955 revert: 恢复误删文件(回退 81f500160) 2026-06-12 16:00:06 +08:00
cec2f47a1f docs(bug): 诸葛亮分析报告 Bug #765 2026-06-12 15:59:00 +08:00
a350095ced docs(bug): 诸葛亮分析报告 Bug #760 2026-06-12 15:57:13 +08:00
3f52a98a32 docs(bug): 诸葛亮分析报告 Bug #761 2026-06-12 15:52:41 +08:00
d60b579dcd docs(bug): 诸葛亮分析报告 Bug #762 2026-06-12 15:45:31 +08:00
d413a4cd60 docs(bug): 诸葛亮分析报告 Bug #757 2026-06-12 15:42:57 +08:00
93447b0e46 docs(bug): 诸葛亮分析报告 Bug #758 2026-06-12 15:34:50 +08:00
2921d4535a docs(bug): 诸葛亮分析报告 Bug #759 2026-06-12 15:14:05 +08:00
b71354d3b6 docs(bug): 诸葛亮分析报告 Bug #760 2026-06-12 15:09:37 +08:00
57a33e0baa docs(bug): 诸葛亮分析报告 Bug #761 2026-06-12 15:05:32 +08:00
759f10d9d0 docs(bug): 诸葛亮分析报告 Bug #762 2026-06-12 15:01:01 +08:00
d8e2c485a4 docs(bug): 诸葛亮分析报告 Bug #762 2026-06-12 14:59:21 +08:00
69e24ba2b4 docs(bug): 诸葛亮分析报告 Bug #757 2026-06-12 14:58:17 +08:00
81f5001601 docs(bug): 诸葛亮分析报告 Bug #759-762 2026-06-12 14:47:52 +08:00
96087d8dac docs(bug): 诸葛亮完整分析报告 Bug #741 (LLM深度分析) 2026-06-12 13:14:29 +08:00
9331dc7525 docs(bug): 诸葛亮分析报告 Bug #741 2026-06-12 13:14:29 +08:00
6372e3c80f docs(bug): 诸葛亮分析报告 Bug #741 2026-06-12 13:14:29 +08:00
615be87c71 docs(bug): 诸葛亮分析报告 Bug #741 2026-06-12 13:14:29 +08:00
c0ab80bd4d docs(bug): 诸葛亮分析报告 Bug #741 2026-06-12 13:14:29 +08:00
772119e320 docs(bug): 诸葛亮分析报告 Bug #698 2026-06-12 13:14:29 +08:00
256791348c docs: 新增代码模块索引表供 LLM 快速定位 2026-06-12 11:15:06 +08:00
Ranyunqiao
a08808b41d bug 588 2026-06-12 11:09:03 +08:00
Ranyunqiao
f407a2a886 700 710 711 713 bug 修复 2026-06-12 10:40:39 +08:00
babd8d0c04 fix(bug): 修复诊疗目录 SysDictData 反序列化错误
根因: commit 68cfa4882 将 Jackson 配置从 Jackson2ObjectMapperBuilderCustomizer
改为直接定义 ObjectMapper bean,导致 Spring Boot 自动配置失效。

修复: 改回 Jackson2ObjectMapperBuilderCustomizer,保留 Spring Boot 默认设置。

同时提交分析报告到 MD/bugs/
2026-06-11 17:32:01 +08:00
1f738c969a feat(orderclosedloop): 优化订单闭环统计数据查询和添加催办提醒功能
- 重构统计查询逻辑,支持按类型、分组和分页查询统计数据
- 添加催办提醒功能,支持对未完成订单进行提醒操作
- 新增多个数据库查询方法,包括按类型、科室、医生分组统计和未关闭警告查询
- 添加前端催办提醒和查看详情功能界面
- 优化临床路径完成和变更接口的查询逻辑,使用条件查询替代ID查询
- 添加分页组件和相关样式配置
2026-06-11 15:57:20 +08:00
3f67753344 fix(doctorstation): 修正页面跳转路径并优化术前讨论管理界面
- 修正住院医生工作站跳转路径从 /inHospital/inpatientDoctor/home 到 /inHospital/inpatientDoctor
- 为术前讨论管理界面添加响应式表格高度计算功能
- 添加窗口大小调整事件监听器以动态更新表格高度
- 优化术前讨论管理界面的CSS样式布局
- 添加 content-card 样式类增强界面显示效果
2026-06-11 15:15:14 +08:00
3e650dd041 perf(utils): 优化字典工具类性能并移除重复依赖
- 在DictUtils中添加类型检查避免不必要的序列化反序列化操作
- 移除pom.xml中的重复jackson-databind依赖配置
- 提升字典数据获取的执行效率
2026-06-11 14:49:42 +08:00
773a485114 refactor(redis): 重构Redis配置以兼容fastjson格式
- 移除Jackson多态类型验证器配置
- 使用FastjsonCompatibleRedisSerializer替代GenericJacksonJsonRedisSerializer
- 添加Primary注解优化Bean注入
- 移除不必要的ValueOperations Bean定义
- 更新限流脚本中的变量名提高可读性
- 在TokenService中添加对多种缓存格式的兼容性支持
- 创建FastjsonCompatibleRedisSerializer类处理不同数据格式的反序列化
- 添加数据库迁移脚本为相关表增加基础字段和删除标识
2026-06-11 14:49:19 +08:00
9675106d4b Merge remote-tracking branch 'origin/develop' into develop 2026-06-11 14:17:01 +08:00
Ranyunqiao
f655f06871 修复缓存报错问题 2026-06-11 13:07:28 +08:00
2c2dbd7542 fix(antibiotic): 修复实体类字段映射和接口路径配置问题
- 修正了AntibioticApproval实体类中的字段名从delFlag改为deleteFlag
- 移除了SysAuditLog实体类中多余的空行
- 为YbDao中的结算明细方法添加了医疗类型参数校验
- 统一了前端API接口路径,移除了healthlink-his前缀
- 更新了麻醉、抗菌药物、CA签名等多个模块的接口调用路径
- 修正了医嘱闭环、危急值管理等页面的API请求地址
2026-06-11 12:02:10 +08:00
8b47a8ab55 fix(common): 修复公共服务返回空值问题并优化查询条件
- 修复 CommonServiceImpl 中返回空集合而非 null 的问题
- 添加新的国际化错误消息用于目录不存在的情况
- 重构门诊挂号查询的时间和状态过滤条件为动态SQL
- 更新支付对账分组字段以包含合同编号
- 调整会诊相关API端点路径和请求方法
- 优化技术工作站退费审批接口路径和参数传递方式
- 重构待处理EMR页面跳转逻辑,移除确认对话框直接跳转住院医生工作站
2026-06-11 10:51:34 +08:00
5ebe6c6333 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	healthlink-his-server/core-common/src/main/java/com/core/common/utils/DictUtils.java
#	healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inhospitalcharge/appservice/impl/InHospitalRegisterAppServiceImpl.java
#	healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inhospitalcharge/mapper/InHospitalRegisterAppMapper.java
2026-06-11 10:06:54 +08:00
65a52e9742 fix(common): 修复字典缓存类型转换异常并优化住院登记查询
- 修复 DictUtils 中的字典缓存获取逻辑,添加类型检查避免 ClassCastException
- 优化住院登记查询接口,增加时间范围和机构ID参数支持
- 修改审核趋势接口,将开始日期参数设为可选
- 更新申请单分页查询接口,使用 ModelAttribute 替代 RequestBody
- 修复住院注册查询方法的参数传递问题
2026-06-11 10:06:05 +08:00
Ranyunqiao
d04be6062b Merge remote-tracking branch 'origin/develop' into develop 2026-06-11 10:05:41 +08:00
Ranyunqiao
defab36cca bug 737 740 2026-06-11 10:05:20 +08:00
681107ca64 fix(#666): 请修复 Bug #666:门诊发药模块无法检索到患者信息
由 AI Agent (guanyu) 自动修复,请查看 diff 确认变更内容。
2026-06-11 09:26:51 +08:00
f75133369a fix(#697): 请修复 Bug #697
由 AI Agent (guanyu) 自动修复,请查看 diff 确认变更内容。
2026-06-11 09:26:51 +08:00
ca812421d2 fix(#665): 请修复 Bug #665
由 AI Agent (guanyu) 自动修复,请查看 diff 确认变更内容。
2026-06-11 09:26:51 +08:00
ae12cb2135 fix(#735): 停嘱医生字段修复(解决 PatientManageMapper 冲突) 2026-06-11 09:26:51 +08:00
d2a1cd6f29 fix(#670): 中医处方煎药方式下拉框数据为空 — 添加 method_of_decocting_medicine 字典加载 2026-06-10 23:55:12 +08:00
d9d2b83c5b docs(agents): 更新AI开发规范文档
- 添加铁律22: 端到端验证必须有实际输出证据
- 添加铁律23: 文件读写强制UTF-8编码规范
- 重写文档头部说明AI工具自动读取规范
- 补充完整的HealthLink-HIS项目概览表格
- 扩展P0和P1铁律章节,包含更多开发约束
- 添加Karpathy编码准则指导原则
- 建立全链路6环分析方法论
- 完善Harness Engineering方法论四层约束
- 增加五层质量门禁检查体系
- 补充系统化调试流程规范
- 添加后端和前端开发详细规范
- 建立Agent角色与协作流水线
- 完善审查与审计体系
- 制定标准工作循环流程
- 添加
2026-06-10 21:17:23 +08:00
109 changed files with 4503 additions and 1251 deletions

544
AGENTS.md
View File

@@ -107,6 +107,143 @@
- **违规判定**: 因修改导致原有代码编译失败或运行报错视为违反铁律18必须立即回滚修复
**铁律19: 编译错误不区分来源Bug #698 教训)**
- `mvn compile``vite build``vue-tsc` 等构建命令报错 = 不过关,**不管是自己引入的还是历史遗留的**
- 禁止说"这是预存问题""不是我改的""原有bug"——构建通不过就不能宣称完成
- 正确做法:定位错误 → 修复 → 重新构建确认通过 → 然后才能继续
- **违规判定**: 构建命令有 ERROR 但未修复就报告"编译通过",视为违反铁律
**铁律20: 数据来源必须验证Bug #698 教训)**
- 涉及数据查询/提取时,必须先确认数据实际存储位置,不能假设
- 案例:从 `raw_steps_html` 提取 fileID而不是从 `steps`(纯文本,已被 strip
- 修复前必须:打印/检查原始数据结构 → 确认字段存在 → 再写提取逻辑
- 禁止:凭代码推断数据位置、假设"应该在这里"
**铁律21: 外部配置值必须实测验证Bug #698 教训)**
- 使用外部服务API、模型、数据库的配置值必须实际调用验证不能仅凭记忆或推测
- 案例:模型名 `mino-v2.5` 应为 `mimo-v2.5`,拼写错误导致 400
- 配置变更后必须:发起一次真实请求 → 确认返回 200 → 再宣称配置正确
- 禁止:改完配置不测试、假设"应该能用"
**铁律22: 端到端验证必须有实际输出证据Bug #698 教训)**
- 声称功能生效前,必须有实际的端到端输出证据
- 不能仅凭代码路径推断"应该走了 vision"——必须看到实际返回内容
- 验证方式:运行命令 → 检查输出中包含预期关键词(如 vision 分析结果、图片识别文字)
- 禁止:只检查代码路径可达就算"验证通
**铁律23: 文件读写强制 UTF-8 编码(必遵守)**
- **禁止**使用 Get-Content -Raw不带 -Encoding UTF8读取源文件
- **禁止**使用 Out-File -Encoding utf8会写 BOM
- **正确写法**
- 读取:[System.IO.File]::ReadAllText(, [System.Text.Encoding]::UTF8)
- 写入:[System.IO.File]::WriteAllText(, # HealthLink-HIS — AI 开发规范
> 🤖 本文件由 Codex CLI、Claude Code 等工具自动读取。
> 工具进入项目目录时会自动加载此文件作为开发规范上下文。
---
# HealthLink-HIS — AI 开发规范(自动加载)
> 🤖 **本文件供所有 AI 编码工具自动读取**。进入本项目后必须遵守以下规范。
>
> **模型决定上限Harness 决定底线。**
---
## 一、项目概览
| 属性 | 值 |
|------|------|
| 项目名 | HealthLink-HIS医院信息系统 |
| 后端路径 | `healthlink-his-server/` |
| 前端路径 | `healthlink-his-ui/` |
| 文档路径 | `MD/` |
| JDK | 25 (OpenJDK) |
| Spring Boot | 4.0.6 |
| MyBatis-Plus | 3.5.16 |
| Vue | 3.x + Vite + Element Plus |
| 数据库 | PostgreSQL 15+ |
| 包名 | `com.healthlink.his` |
| 后端端口 | 18082 |
| 前端端口 | 81 |
---
## 二、铁律(必须遵守,违反即失败)
### 🔴 P0 铁律 — 不可违反
**铁律1: 修改完必须测试**
```
后端: mvn clean compile -DskipTests → mvn install -DskipTests → mvn test
前端: npm run build:dev → npm run lint
```
- 白盒:编译通过,无 ERROR
- 黑盒:关键接口返回 `{code:200, data:...}`,验证业务逻辑
- 冒烟:应用正常启动,核心流程通畅
**铁律2: Flyway 数据库迁移**
- 凡是新建表、新增字段,必须创建 Flyway 迁移脚本
- 路径:`healthlink-his-domain/src/main/resources/db/migration/`
- 命名:`V{版本号}__{描述}.sql`(双下划线)
**铁律3: 测试通过后才提交**
- 编译 + 测试全部通过后才能 git commit
- 不提交未完成的功能、调试代码、临时文件
**铁律4: 前后端API路径对齐**
- 后端前缀:`/healthlink-his/api/v1/`
- 前端 `request.js` 的 baseURL 必须与后端匹配
**铁律5: 状态值一致性Bug #574 教训)**
- 修改任何状态值前,必须先列出完整的状态流转链路
- 检查项:枚举定义 → Service 设置 → 查询映射 → 前端 STATUS_CLASS_MAP → 前端 v-if → 统计SQL
- 禁止:只改一端不检查其他端
**铁律6: 禁止删除源文件Bug #574 教训)**
- 绝对禁止删除项目中已有的 Java/Vue/SQL 源文件
- 编译错误 → 修复错误;重复文件 → 重构合并
- 唯一例外:明确由人类确认删除的文件
**铁律7: 禁止修改已有公开方法签名**
- 不能删除/重命名已有的 public 方法,不能修改参数列表
- 需要新功能 → 添加重载方法;需要改行为 → 修改内部实现
**铁律8: 验证后才宣称完成Verification Before Completion**
- **没有跑过验证命令,就不能说"完成了""通过了""没问题"**
- 禁止使用"应该可以""大概没问题""看起来正确"
- 必须:运行命令 → 读取输出 → 确认结果 → 才能宣称
- 这是诚实原则,不是效率问题
**铁律9: 开发前必须审核原有代码P0 — 铁律)**
- **任何新功能开发前,必须先搜索项目中是否已有相关代码**
- 搜索路径Controller / AppService / Service / Mapper / Entity / 前端页面 / API接口
- 如果已有部分功能 → 在原有代码基础上**升级优化完善**,禁止另起炉灶
- 如果已有接口但前端缺失 → 只补前端,不重复建后端
- 如果已有前端但后端缺失 → 只补后端,不重写前端
- 搜索命令:`rg -l "关键词" healthlink-his-server/ healthlink-his-ui/src/`
- 禁止:不看代码就新建模块、重复实现已有功能、废弃原有代码另写一套
**铁律12: 设计文档确认后自主开发(铁律)**
- 设计文档(如 `MD/architecture/GRADE3A_GAP_ANALYSIS_AND_DESIGN.md`)一旦确认,后续开发**必须按文档自主执行**
- **禁止反复询问"是否继续""下一步做什么""是否开始"**——直接按计划推进
- 每完成一个 Sprint自动提交推送然后立即开始下一个 Sprint
- 只在遇到**无法解决的阻塞**(如技术选型冲突、需求不明确、第三方依赖不可用)时才暂停询问
- 设计文档是"**已签合同**",不是"参考意见"。铁律执行优先级:设计文档 > 人类临时指令 > AI 自行判断
**铁律18: 禁止破坏原有功能P0绝对铁律**
- **完善增加功能和流程时,绝对不能破坏或者让原有功能不能用**
- 修改已有实体前必须对比原始文件(`git show HEAD~N:./file.java`),保留所有原有字段和方法
- 新增字段只能追加,不能删除或重命名已有字段
- SQL迁移只允许 `ALTER TABLE ADD COLUMN`,不允许 `DROP COLUMN``RENAME COLUMN`
- Controller新端点不能修改已有端点的路径或参数
- 前端新页面不能修改已有页面的组件结构
- 每次修改后必须 `mvn clean compile -DskipTests` 验证
- **违规判定**: 因修改导致原有代码编译失败或运行报错视为违反铁律18必须立即回滚修复
**铁律19: 编译错误不区分来源Bug #698 教训)**
- `mvn compile``vite build``vue-tsc` 等构建命令报错 = 不过关,**不管是自己引入的还是历史遗留的**
- 禁止说"这是预存问题""不是我改的""原有bug"——构建通不过就不能宣称完成
@@ -533,3 +670,410 @@ git status && git add -A && git commit -m "feat(module): desc" && git push origi
---
> 📅 最后同步: 2026-06-06 15:09 | 源文件: RULES.md | 重新同步: `bash scripts/sync-ai-rules.sh`
, [System.Text.UTF8Encoding]::new(False))
- git提取 Out-File -Encoding utf8 保存到临时文件再用 [System.IO.File]::ReadAllText() 读取
- **根因**PowerShell 管道会丢失换行符git show | Out-File 会将多行文件压缩为一行
"
### 🟡 P1 铁律 — 强烈建议
**铁律9: 先分解再行动**
- 修改超过3个文件涉及多模块数据库变更必须先制定计划
**铁律10: 验证后信**
- 每次修改后必须验证编译通过不信记忆
**铁律13: 文档统一管理**
- 所有文档存储在 `MD/` 目录
- 文件名大写英文+下划线 `BACKEND_CHECKLIST.md`
- 文档头部必须包含元数据块文档类型版本日期
---
**铁律14: 设计文档必须包含UI设计和调用流程**
- 所有新模块/页面的设计文档必须包含UI布局描述交互效果清单前后端调用流程
- 没有明确UI设计的模块禁止直接编码
- 详见
- 设计文档必须写清楚系统调用关系方法函数调用关系完整业务流程
- 设计文档中每个用户操作必须对应前端事件 API调用 后端处理链路 返回数据 UI渲染
---
## 三、Karpathy 编码准则
> 减少 LLM 常见编码错误。偏向谨慎而非速度。
### 3.1 先想再写
- 明确陈述假设不确定就问
- 多种解读时都列出来不要默默选一种
- 有更简单的方案就说出来该推回就推回
- 不清楚的地方停下来说清楚哪里不清楚
### 3.2 简洁优先
- 不做没要求的功能不做一次性代码的抽象
- 不加没要求的"灵活性""可配置性"
- 200 行能 50 行搞定就重写
- 自问"高级工程师会不会觉得这过度设计"
### 3.3 精准修改
- 只改必须改的"顺手改进"相邻代码
- 匹配现有代码风格即使你有不同的偏好
- 每行改动都能追溯到用户的请求
- 只清理你自己改动产生的无用代码
### 3.4 目标驱动
- 把任务转化为可验证目标
- 多步任务声明计划`[步骤] → 验证: [检查]`
- 强验收标准让 Agent 能独立循环弱标准需要持续澄清
---
## 四、全链路 6 环分析
> ⚠️ **涉及数据库字段的 Bug / 需求,必须走完整链路。**
```
前端/页面 → Controller → Service → Mapper → DB/SQL → 关联模块
①录入 ②验证 ③业务 ④持久化 ⑤存储 ⑥联动
```
| | 检查内容 |
|----|---------|
| 录入 | 前端有无输入入口弹窗表格行编辑表单 |
| 验证 | Controller 参数校验@Valid权限控制 |
| 业务 | Service 业务逻辑事务边界多个 Service 实现类入口 |
| 持久化 | Mapper XMLDTO 字段映射类型转换 |
| 存储 | 数据库表结构索引NOT NULL 约束 |
| 联动 | 上游医嘱护士站)、下游打印计费报表是否同步 |
**修复后的验证顺序**
1. 数据库确认状态值已正确写入
2. 后端接口确认返回的状态映射正确
3. 前端显示确认页面显示正确状态文本
4. 前端交互确认按钮/操作基于正确状态启用/禁用
5. 统计数据确认池/报表统计包含新状态
---
## 五、Harness Engineering 方法论
> Harness = 约束 + 反馈 + 控制平面 + 持久执行
### 5.1 四层约束金字塔
| 层级 | 内容 | 落地方式 |
|------|------|---------|
| **L1 架构约束** | 接口合约包结构命名规范禁止模式 | 本文件铁律 |
| **L2 代码质量** | 圈复杂度代码风格类型提示 | 编译门禁 + ESLint |
| **L3 安全约束** | 敏感信息检测权限检查输入验证 | 配置不可硬编码 |
| **L4 业务规则** | 领域逻辑数据一致性事务边界 | 全链路 6 环验证 |
**约束设计原则**
- **可验证**每条约束必须能被自动化检查"覆盖率>90%"✅ "质量要高"❌)
- **无歧义**"每函数不超过50行"✅ "函数不要太长"❌
- **优先级**:安全(1) > 架构(2) > 业务(3) > 质量(4) > 性能(5)
- **渐进增强**L1编译通过 → L2+命名规范 → L3+测试覆盖 → L4+安全扫描
### 5.2 三层反馈系统
| 层级 | 速度 | 覆盖范围 | 失败处理 |
|------|------|---------|---------|
| **L1 编译检查** | <30秒 | 语法类型签名 | 立即阻断自行修复 |
| **L2 数据流验证** | <5分钟 | 全链路字段Mapper XMLDTO | 修复后上报 |
| **L3 人工审查** | 10-30分钟 | 架构设计业务正确性 | 驳回/指导/批准 |
**反馈铁律**
- 反馈必须可行动文件 + 行号 + 错误类型 + 修复方向
- 失败后先回滚到最近检查点再重试
- 持续失败3次 上报人类
### 5.3 控制平面
```
战略层(人类) → 设定目标、审批决策、异常升级
战术层Agent → 任务分解、update_plan、依赖协调、检查点保存
执行层Agent → 代码生成、测试执行、错误恢复、幂等重试
```
### 5.4 持久执行
- 每个关键步骤保存检查点`update_plan` 进度
- 失败后从最新检查点恢复不从头开始
- 幂等设计同一操作重复执行结果一致
- **三层状态管理**系统层(工作流ID/超时/重试) 执行层(当前活动/进度) 业务层(已完成工作/中间产物)
---
## 六、五层质量门禁
| 门禁 | 时间 | 范围 | 失败处理 |
|------|------|------|---------|
| **L1 编译检查** | <30秒 | 语法类型导入 | Agent 自行修复 |
| **L2 静态分析** | <2分钟 | 代码风格复杂度安全 | Agent 修复 |
| **L3 单元测试** | <5分钟 | 功能正确性边界条件 | 自动修复或上报 |
| **L4 集成测试** | <15分钟 | 模块间交互数据流 | 上报人工 |
| **L5 生产验证** | 持续 | 监控告警性能 | 自动回滚 |
**提交铁律**L1-L2 必须通过才能 commitL3如有DB变更必须通过才能 push
---
## 七、系统化调试Systematic Debugging
> **铁律:没有根因调查,不能提出修复方案。**
### 四阶段流程
**阶段1根因调查**修复前必须完成
1. 仔细阅读错误信息堆栈行号错误码
2. 稳定复现能否可靠触发步骤每次
3. 检查最近变更git diff新依赖配置变更
4. 多组件系统在每个组件边界加诊断日志定位哪一层断裂
5. 追踪数据流坏值从哪里来谁调用的一直追溯到源头
**阶段2模式分析**
- 找到同代码库中类似的正常工作代码
- 逐项对比差异
- 理解依赖关系
**阶段3假设与测试**
- 形成单一假设"我认为X是根因因为Y"
- 做最小改动测试
- 有效 阶段4无效 新假设
**阶段4实施**
- 创建失败测试用例
- 修复根因不是症状
- 验证修复
---
## 八、后端开发规范
### 分层架构
```
Controller → AppService → Service → Mapper → Entity
```
### 命名规范
| 类型 | 规则 | 示例 |
|------|------|------|
| Controller | `XxxController` | `RegistrationController` |
| AppService | `IXxxAppService` / `XxxAppServiceImpl` | `IRegistrationAppService` |
| Service | `IXxxService` / `XxxServiceImpl` | `IRegistrationService` |
| Mapper | `XxxMapper` | `RegistrationMapper` |
| Entity | `Xxx` | `Registration` |
| DTO | `XxxDto` / `XxxQueryDto` | `RegistrationDto` |
### 包结构
```
com.healthlink.his.web.{module}.controller
com.healthlink.his.web.{module}.appservice
com.healthlink.his.web.{module}.service
com.healthlink.his.web.{module}.mapper
com.healthlink.his.web.{module}.dto
com.healthlink.his.domain.{module}
com.healthlink.his.common.enums
```
### 关键约束
- 所有查询使用 `LambdaQueryWrapper`禁止字符串拼接 SQL
- `@Transactional(rollbackFor = Exception.class)` 管理事务
- 所有接口标注 `@PreAuthorize` 权限控制
- 患者敏感信息在日志中脱敏
- **扩展功能不修改原有函数签名**
---
## 九、前端开发规范
### 技术栈
- Vue 3 + Vite + Element Plus + Pinia + Axios基于 RuoYi-Vue3
### 目录结构
```
src/api/{module}/ # API接口
src/views/{module}/ # 页面组件
src/store/modules/ # Pinia状态管理
src/components/ # 公共组件
```
### 关键约束
- API前缀`/healthlink-his/api/v1/`
- 路由懒加载`() => import('@/views/xxx/index.vue')`
- 页面使用 `<script setup>` 语法
- 按钮权限使用 `v-hasPermi` 指令
- `onMounted` 中注册的事件在 `onUnmounted` 中移除
---
## 十、Agent 体系
### 角色与路由
| 代号 | 名称 | 角色 | 路由关键词 |
|------|------|------|-----------|
| liubei | 刘备 | 项目经理 | 协调分派异常升级 |
| zhugeliang | 诸葛亮 | 架构师 | 分析路由设计 |
| guanyu | 关羽 | 后端开发 | java, api, spring, service, controller |
| zhaoyun | 赵云 | 前端开发 | vue, 界面, 显示, 弹窗, 按钮 |
| xunyu | 荀彧 | DBA | 数据库, sql, 迁移, mapper xml |
| zhangfei | 张飞 | 测试 | 测试, QA, 回归 |
| huatuo | 华佗 | 验收 | 需求验收质量确认 |
| chenlin | 陈琳 | 文档 | 文档归档Git提交 |
### 协作流水线
```
刘备(协调) → 诸葛亮(分析路由) → {关羽|赵云}(修复) → 荀彧(DB审查) → 张飞(测试) → 华佗(验收) → 陈琳(归档)
```
### Bug 修复完整管线BDT 方法论)
```
获取Bug → 设计测试用例 → 基线测试(应失败) → 全链路修复 → 回归测试(应通过) → 扩展测试(无回归) → 提交
```
### Bug 状态管理铁律
- 人类提的 Bug只加备注不改状态不改分配
- 智能体提的 Bug可以改分配和加备注
- 已关闭/已解决的 Bug 不再处理
---
## 十一、审查与审计
### 三层审查体系
| 层级 | 内容 | 时机 |
|------|------|------|
| **L1 自审** | Agent 对照约束逐条检查 | 每次提交前 |
| **L2 配对审查** | Agent 生成变更摘要人类终审 | PR/提交时 |
| **L3 合规审查** | 审计追踪记录所有 AI 操作 | 持续 |
### L1 自审清单
```yaml
self_review:
- "所有修改能通过编译?"
- "遵守命名规范?"
- "测试覆盖达标?"
- "没有遗漏的 TODO / DEBUG"
- "变更范围没超出任务边界?"
```
### 评审评分维度
| 维度 | 问题 |
|------|------|
| 正确性 | 行为是否符合目标功能 |
| 验证 | 检查是否真的跑过并留下证据 |
| 范围纪律 | 是否保持在选定功能范围内 |
| 可靠性 | 结果能否在重启后继续工作 |
| 可维护性 | 代码和文档是否清楚到可交接 |
---
## 十二、标准工作循环
```
开始会话
├→ 1. Init — 读 AGENTS.md + PROGRESS.md + git log
├→ 2. Select — 只选一个未完成功能
├→ 3. Implement — 一次只做一个,不扩大范围
├→ 4. Verify — 运行验证命令,有证据才标记完成
└→ 5. Cleanup — 更新进度 + clean-state-checklist + git commit
```
### 会话结束前必须运行 Clean State Checklist
```
□ 标准启动路径仍然可用
□ 标准验证路径仍然可运行
□ 当前进度已记录到进度日志
□ 无半成品步骤处于未记录状态
□ 下一轮会话无需人工修复即可继续
```
---
## 十三、开发流程
```
收到任务
├→ ① 分析需求 → 读相关文档(MD/)、读全链路6环
├→ ② 制定计划 → update_plan (3-6个阶段)
├→ ③ 后端开发 → Controller → AppService → Service → Mapper → Entity → Flyway
├→ ④ 后端测试 → mvn test → 接口测试(业务逻辑验证)
├→ ⑤ 前端开发 → API接口 → 页面组件 → 路由配置
├→ ⑥ 前端测试 → npm run build:dev → 功能验证
├→ ⑦ 质量门禁 → L1编译 → L2测试 → L3DB审查 → L4验收 → L5归档
└→ ⑧ 提交代码 → git commit(规范格式) → git push → 文档更新
```
### Git Commit 格式
```
<type>(<scope>): <subject>
type: feat|fix|docs|refactor|test|chore
scope: 模块名(如 registration, billing, pharmacy)
```
---
## 十四、快速参考命令
```bash
# === 后端 ===
export JAVA_HOME=/opt/jdk-25
mvn clean compile -DskipTests # 编译
mvn install -DskipTests # 构建
mvn test -pl healthlink-his-application -Dtest="XxxTest" -Dsurefire.failIfNoSpecifiedTests=false
# === 前端 ===
cd healthlink-his-ui
npm run dev && npm run build:dev && npm run lint && npm run test:run
# === Git ===
git status && git add -A && git commit -m "feat(module): desc" && git push origin develop
```
---
## 十五、详细规范文档索引
| 文档 | 路径 | 用途 |
|------|------|------|
| 执行铁律 | `MD/specs/IRON_RULES.md` | 铁律完整版 |
| 后端规范 | `MD/specs/BACKEND_DEVELOPMENT_STANDARD.md` | 后端编码标准 |
| 前端规范 | `MD/specs/FRONTEND_DEVELOPMENT_STANDARD.md` | 前端编码标准 |
| Harness方法论 | `MD/specs/HARNESS_ENGINEERING.md` | 完整Harness+Agent方法论 |
| 文档规范 | `MD/DOCUMENTATION_STANDARD.md` | 文档管理标准 |
| 后端清单 | `MD/specs/BACKEND_CHECKLIST.md` | 发布前检查 |
| 前端清单 | `MD/specs/FRONTEND_CHECKLIST.md` | 发布前检查 |
| 三甲标准 | `MD/standards/GRADE3A_HIS_STANDARD.md` | 三甲医院达标标准 |
| Flyway指南 | `MD/guides/FLYWAY_USAGE_GUIDE.md` | 数据库迁移指南 |
---
## 十六、过往教训
| 教训 | 内容 |
|------|------|
| 状态链路断裂 | Bug#574: 签到设 BOOKED(1) 而非 CHECKED_IN(3)前端映射缺失 必须走完整状态链路 |
| 盲删源文件 | AI 看到编译错误直接删文件没检查 baseline 必须先确认文件来源 |
| 修复方向偏差 | 多次 fallback 改的是错误的 Service 必须用 rg 搜索所有相关代码路径 |
| bug_reports 缺列 | INSERT 静默失败 必须检查表结构 |
| 禅道 comment API | API 不存在 resolve+activate workaround |
| SQLite WAL 并发 | 多进程并发写需要 checkpoint |
| UTF-8 切片 | 多字节字符不能用 byte index 切片 |
| 上下文焦虑 | Agent 感觉上下文快满时会匆忙结束跳过验证 注意 context 40% 阈值 |
| 过早宣告胜利 | 自评验证分开"干活""检查" |
| 覆盖率幻觉 | 覆盖率达标但逻辑没测 引入变异测试 |
---
> ⚠️ 本文件是 AI 开发规范的唯一信源。各工具配置文件由 `bash scripts/sync-ai-rules.sh` 同步。
---
> 📅 最后同步: 2026-06-06 15:09 | 源文件: RULES.md | 重新同步: `bash scripts/sync-ai-rules.sh`

424
MD/MODULE_INDEX.md Normal file
View File

@@ -0,0 +1,424 @@
# HealthLink-HIS 代码模块索引
> 供 LLM 快速定位代码。每个模块列出 Controller → Service → Mapper 关键文件。
## 关键词 → 模块速查
| 关键词 | 后端模块 | 前端目录 |
|---|---|---|
| 门诊医生站/门诊医嘱/门诊处方/诊断/检查申请 | `doctorstation` | `doctorstation` |
| 住院医生站/住院医嘱/临床医嘱/签发/停嘱 | `regdoctorstation` | `inpatientDoctor` |
| 住院护士站/医嘱校对/医嘱执行/护理/换床 | `inhospitalnursestation` | `inpatientNurse` |
| 挂号/门诊收费/门诊结算 | `chargemanage` | `charge` |
| 住院收费/住院结算/预交金 | `inhospitalcharge` | `inHospitalManagement` |
| 收费管理/计费/退费 | `paymentmanage` | `outpatientFinance` |
| 药品/药房/药库/发药/取药 | `pharmacymanage` | `pharmacymanagement` |
| 药房发药/门诊发药 | `pharmacyDispensarymanage` | `drug` |
| 药库管理/库存 | `pharmacyWarehousemanage` | `medicineStorage` |
| 库存管理/盘点/出入库 | `inventorymanage` | `medicineStorage` |
| 物资管理/耗材 | `materialmanage` | `` |
| 字典/数据字典/诊疗目录/基础数据 | `datadictionary` | `datadictionary` |
| 部门/科室管理 | `departmentmanage` | `system` |
| 卡管理/就诊卡 | `cardmanagement` | `cardmanagement` |
| 检验/化验/标本 | `lab` | `inspection` |
| 检查/影像/放射 | `Inspection` | `inspection` |
| 手术/手术安排/手术申请 | `surgicalschedule` | `surgerymanage` |
| 病历/电子病历/EMR | `emr` | `emr` |
| 护理记录/护理评估 | `nursing` | `nursing` |
| 分诊/排队/叫号 | `triageandqueuemanage` | `triageandqueuemanage` |
| 医保/医保对码/医保目录 | `ybmanage` | `ybmanagement` |
| 会诊/会诊申请 | `consultation` | `consultationmanagement` |
| 院感/感染上报 | `infection` | `infection` |
| 合理用药/处方审核 | `rationaldrug` | `rationaldrug` |
| 中医/中医处方 | `tcm` | `tcm` |
| 患者管理/患者信息 | `patientmanage` | `patientmanagement` |
| 预约/挂号预约 | `appointmentmanage` | `appoinmentmanage` |
| 报告/报告管理 | `reportmanage` | `` |
| 质控/质量 | `quality` | `quality` |
| 系统管理/用户/角色/权限 | `basicmanage` | `system` |
| 门诊管理/门诊工作站 | `outpatientmanage` | `doctorstation` |
| 前置手术/术前管理 | `preopmanage` | `preopmanage` |
| 危急值 | `criticalvalue` | `criticalvalue` |
| 抗菌药 | `antibiotic` | `antibiotic` |
| 随访 | `followup` | `followup` |
| request.js/请求拦截/响应拦截 | `common` | `crossmodule` |
## 后端模块详情
### `Inspection` (40 files)
- **Controller**: `Inspection/controller/SampleCollectController.java` `Inspection/controller/ObservationDefController.java` `Inspection/controller/LabReferenceRangeController.java`
- **AppService**: `Inspection/appservice/ISampleCollectAppManageAppService.java` `Inspection/appservice/ILisConfigManageAppService.java` `Inspection/appservice/IInstrumentManageAppService.java`
- **ServiceImpl**: `Inspection/appservice/impl/LisConfigManageAppServiceImpl.java` `Inspection/appservice/impl/ObservationManageAppServiceImpl.java` `Inspection/appservice/impl/SpecimenManageAppServiceImpl.java`
- **Mapper**: `Inspection/mapper/SampleCollectMapper.java` `Inspection/mapper/LisReportMapper.java` `Inspection/mapper/GroupRecMapper.java`
- **DTO**: `Inspection/dto/SampleCollectManageDto.java` `Inspection/dto/SpecimenDefManageDto.java` `Inspection/dto/InstrumentManageDto.java` `Inspection/dto/LisConfigManageDto.java` `Inspection/dto/InstrumentSelParam.java`
### `adjustprice` (10 files)
- **Controller**: `adjustprice/controller/ChangePriceController.java` `adjustprice/controller/ChangePriceDataListPageController.java`
- **ServiceImpl**: `adjustprice/appservice/impl/AdjustPriceServiceImpl.java`
- **Mapper**: `adjustprice/mapper/AdjustPriceMapper.java`
- **DTO**: `adjustprice/dto/ChangePriceDataDto.java` `adjustprice/dto/AdjustPriceManagerSearchParam.java` `adjustprice/dto/ChangePricePageDto.java`
### `anesthesia` (4 files)
- **Controller**: `anesthesia/controller/AnesthesiaController.java` `anesthesia/controller/AnesthesiaEnhancedController.java`
- **AppService**: `anesthesia/appservice/IAnesthesiaAppService.java`
- **ServiceImpl**: `anesthesia/appservice/impl/AnesthesiaAppServiceImpl.java`
### `antibiotic` (3 files)
- **Controller**: `antibiotic/controller/AntibioticController.java`
- **AppService**: `antibiotic/appservice/IAntibioticAppService.java`
- **ServiceImpl**: `antibiotic/appservice/impl/AntibioticAppServiceImpl.java`
### `appointmentmanage` (29 files)
- **Controller**: `appointmentmanage/controller/ScheduleSlotController.java` `appointmentmanage/controller/DeptAppthoursController.java` `appointmentmanage/controller/SchedulePoolController.java`
- **AppService**: `appointmentmanage/appservice/IDeptAppService.java` `appointmentmanage/appservice/IDoctorScheduleAppService.java` `appointmentmanage/appservice/IClinicRoomAppService.java`
- **ServiceImpl**: `appointmentmanage/appservice/impl/DoctorScheduleAppServiceImpl.java` `appointmentmanage/appservice/impl/DeptAppointmentHoursAppServiceImpl.java` `appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
- **Mapper**: `appointmentmanage/mapper/DoctorScheduleAppMapper.java` `appointmentmanage/mapper/SchedulePoolAppMapper.java` `appointmentmanage/mapper/DeptAppMapper.java`
- **DTO**: `appointmentmanage/dto/TicketDto.java` `appointmentmanage/dto/SchedulePoolDto.java`
### `basedatamanage` (44 files)
- **Controller**: `basedatamanage/controller/OrganizationLocationController.java` `basedatamanage/controller/BodyStructureController.java` `basedatamanage/controller/OperatingRoomController.java`
- **AppService**: `basedatamanage/appservice/IOrganizationAppService.java` `basedatamanage/appservice/IBodyStructureAppService.java` `basedatamanage/appservice/ILocationAppService.java`
- **ServiceImpl**: `basedatamanage/appservice/impl/PractitionerAppServiceImpl.java` `basedatamanage/appservice/impl/BodyStructureAppServiceImpl.java` `basedatamanage/appservice/impl/OrganizationAppServiceImpl.java`
- **Mapper**: `basedatamanage/mapper/PractitionerAppAppMapper.java`
- **DTO**: `basedatamanage/dto/SelectableOrgDto.java` `basedatamanage/dto/PractitionerOrgAndLocationDto.java` `basedatamanage/dto/OrganizationInitDto.java` `basedatamanage/dto/OperatingRoomDto.java` `basedatamanage/dto/LocationInitDto.java`
### `basicmanage` (5 files)
- **Controller**: `basicmanage/controller/BedController.java` `basicmanage/controller/InvoiceController.java` `basicmanage/controller/InvoiceSegmentController.java`
### `basicservice` (7 files)
- **Controller**: `basicservice/controller/HealthcareServiceController.java`
- **Mapper**: `basicservice/mapper/HealthcareServiceBizMapper.java`
- **DTO**: `basicservice/dto/HealthcareServiceAddOrUpdateParam.java` `basicservice/dto/HealthcareServiceDto.java` `basicservice/dto/HealthcareServiceInitDto.java`
### `ca` (3 files)
- **Controller**: `ca/controller/CaSignatureController.java`
- **AppService**: `ca/appservice/ICaSignatureAppService.java`
- **ServiceImpl**: `ca/appservice/impl/CaSignatureAppServiceImpl.java`
### `cardmanagement` (17 files)
- **Controller**: `cardmanagement/controller/CardManageController.java`
- **AppService**: `cardmanagement/appservice/ICardManageAppService.java`
- **ServiceImpl**: `cardmanagement/appservice/impl/CardManageAppServiceImpl.java`
- **Mapper**: `cardmanagement/mapper/InfectiousAuditMapper.java` `cardmanagement/mapper/InfectiousCardMapper.java`
- **DTO**: `cardmanagement/dto/InfectiousCardDto.java` `cardmanagement/dto/DoctorCardQueryDto.java` `cardmanagement/dto/DoctorCardListDto.java` `cardmanagement/dto/SingleReturnDto.java` `cardmanagement/dto/CardStatisticsDto.java`
### `catalogmanage` (4 files)
- **Controller**: `catalogmanage/controller/CatalogController.java`
- **ServiceImpl**: `catalogmanage/appservice/impl/CatalogServiceImpl.java`
- **Mapper**: `catalogmanage/mapper/CatalogMapper.java`
### `charge` (4 files)
- **Controller**: `charge/patientcardrenewal/PatientCardRenewalController.java`
- **ServiceImpl**: `charge/patientcardrenewal/PatientCardRenewalServiceImpl.java`
### `chargemanage` (46 files)
- **Controller**: `chargemanage/controller/OutpatientRegistrationController.java` `chargemanage/controller/OutpatientPricingController.java` `chargemanage/controller/InpatientChargeController.java`
- **AppService**: `chargemanage/appservice/IInpatientChargeAppService.java` `chargemanage/appservice/IOutpatientRegistrationAppService.java` `chargemanage/appservice/IOutpatientRefundAppService.java`
- **ServiceImpl**: `chargemanage/appservice/impl/OutpatientChargeAppServiceImpl.java` `chargemanage/appservice/impl/InpatientChargeAppServiceImpl.java` `chargemanage/appservice/impl/OutpatientRefundAppServiceImpl.java`
- **Mapper**: `chargemanage/mapper/OutpatientRefundAppMapper.java` `chargemanage/mapper/OutpatientRegistrationAppMapper.java` `chargemanage/mapper/OutpatientChargeAppMapper.java`
- **DTO**: `chargemanage/dto/ReprintRegistrationDto.java` `chargemanage/dto/EncounterPatientRefundDto.java` `chargemanage/dto/OutpatientPricingPriceDto.java` `chargemanage/dto/OutpatientPricingInventoryDto.java` `chargemanage/dto/RefundItemParam.java`
### `check` (27 files)
- **Controller**: `check/controller/CheckMethodController.java` `check/controller/SpecimenBarcodeController.java` `check/controller/RadiologyEnhancedController.java`
- **AppService**: `check/appservice/ILisGroupInfoAppService.java` `check/appservice/ICheckPartAppService.java` `check/appservice/ICheckMethodAppService.java`
- **ServiceImpl**: `check/appservice/impl/CheckMethodAppServiceImpl.java` `check/appservice/impl/CheckPartAppServiceImpl.java` `check/appservice/impl/CheckPackageAppServiceImpl.java`
- **Mapper**: `check/mapper/LisGroupInfoAppMapper.java` `check/mapper/CheckMethodAppMapper.java` `check/mapper/CheckPartAppMapper.java`
- **DTO**: `check/dto/CheckPackageDetailDto.java` `check/dto/ExamApplyDto.java` `check/dto/ExamApplyItemDto.java` `check/dto/CheckPackageDto.java` `check/dto/CheckMethodDto.java`
### `clinical` (2 files)
- **Controller**: `clinical/controller/KnowledgeBaseController.java` `clinical/controller/ClinicalPathwayController.java`
### `clinicalmanage` (11 files)
- **Controller**: `clinicalmanage/controller/SurgicalScheduleController.java` `clinicalmanage/controller/SurgeryController.java`
- **AppService**: `clinicalmanage/appservice/ISurgicalScheduleAppService.java` `clinicalmanage/appservice/ISurgeryAppService.java`
- **ServiceImpl**: `clinicalmanage/appservice/impl/SurgicalScheduleAppServiceImpl.java` `clinicalmanage/appservice/impl/SurgeryAppServiceImpl.java`
- **Mapper**: `clinicalmanage/mapper/SurgicalScheduleAppMapper.java` `clinicalmanage/mapper/SurgeryAppMapper.java`
- **DTO**: `clinicalmanage/dto/SurgeryDto.java` `clinicalmanage/dto/OpScheduleDto.java` `clinicalmanage/dto/OpCreateScheduleDto.java`
### `common` (17 files)
- **Controller**: `common/controller/CommonAppController.java`
- **ServiceImpl**: `common/appservice/impl/CommonServiceImpl.java`
- **Mapper**: `common/mapper/CommonAppMapper.java`
- **DTO**: `common/dto/ActivityDefinitionDto.java` `common/dto/PerformInfoDto.java` `common/dto/PractitionerInfoDto.java` `common/dto/LocationInventoryDto.java` `common/dto/PerformRecordDto.java`
### `consultation` (19 files)
- **Controller**: `consultation/controller/ConsultationController.java`
- **AppService**: `consultation/appservice/IConsultationAppService.java`
- **ServiceImpl**: `consultation/appservice/impl/ConsultationAppServiceImpl.java`
- **Mapper**: `consultation/mapper/ConsultationInvitedMapper.java` `consultation/mapper/ConsultationConfirmationMapper.java` `consultation/mapper/ConsultationRequestMapper.java`
- **DTO**: `consultation/dto/PhysicianNodeDto.java` `consultation/dto/InvitedObjectDto.java` `consultation/dto/ConsultationActivityDto.java` `consultation/dto/DepartmentTreeDto.java` `consultation/dto/ConsultationRequestDto.java`
### `controller` (2 files)
- **Controller**: `controller/WorkflowController.java` `controller/HomeStatisticsController.java`
### `criticalvalue` (3 files)
- **Controller**: `criticalvalue/controller/CriticalValueController.java`
- **AppService**: `criticalvalue/appservice/ICriticalValueAppService.java`
- **ServiceImpl**: `criticalvalue/appservice/impl/CriticalValueAppServiceImpl.java`
### `crossmodule` (3 files)
- **Controller**: `crossmodule/controller/CrossModuleController.java` `crossmodule/controller/EnhancementController.java` `crossmodule/controller/IntegrationController.java`
### `datadictionary` (65 files)
- **Controller**: `datadictionary/controller/DiagnosisTreatmentController.java` `datadictionary/controller/MedicationManageController.java` `datadictionary/controller/DiseaseManageController.java`
- **AppService**: `datadictionary/appservice/IDeviceManageAppService.java` `datadictionary/appservice/IDiagTreatMAppService.java` `datadictionary/appservice/ItemDefinitionAppService.java`
- **ServiceImpl**: `datadictionary/appservice/impl/DiagTreatMAppServiceImpl.java` `datadictionary/appservice/impl/SupplierManagementAppServiceImpl.java` `datadictionary/appservice/impl/ItemDefinitionAppServiceImpl.java`
- **Mapper**: `datadictionary/mapper/MedicationManageSearchMapper.java` `datadictionary/mapper/ICDCodeMapper.java` `datadictionary/mapper/ActivityDefinitionManageMapper.java`
- **DTO**: `datadictionary/dto/DeviceManageUpDto.java` `datadictionary/dto/ChargeItemOptionDto.java` `datadictionary/dto/SupplierDto.java` `datadictionary/dto/DiagnosisTreatmentInitDto.java` `datadictionary/dto/DiagnosisTreatmentSelParam.java`
### `departmentmanage` (42 files)
- **Controller**: `departmentmanage/controller/DepartmentTransferOutOrderController.java` `departmentmanage/controller/DepartmentReturnToWarehouseOrderController.java` `departmentmanage/controller/DepartmentStocktakingOrderController.java`
- **ServiceImpl**: `departmentmanage/appservice/impl/DepartmentReceiptApprovalServiceImpl.java` `departmentmanage/appservice/impl/DepartmentStockInOrderServiceImpl.java` `departmentmanage/appservice/impl/DepartmentCommonServiceImpl.java`
- **Mapper**: `departmentmanage/mapper/DepartmentTransferInOrderMapper.java` `departmentmanage/mapper/DepartmentStocktakingOrderMapper.java` `departmentmanage/mapper/DepartmentTransferOutOrderMapper.java`
- **DTO**: `departmentmanage/dto/DepartmentDeviceInfoDto.java` `departmentmanage/dto/DepartmentDetailDto.java` `departmentmanage/dto/DepartmentInitDto.java` `departmentmanage/dto/DepartmentSearchParam.java` `departmentmanage/dto/DepartmentDto.java`
### `doctorstation` (91 files)
- **Controller**: `doctorstation/controller/DoctorStationDiagnosisController.java` `doctorstation/controller/DoctorStationInspectionLabApplyController.java` `doctorstation/controller/DoctorStationChineseMedicalController.java`
- **AppService**: `doctorstation/appservice/IDoctorPhraseAppService.java` `doctorstation/appservice/IDoctorStationEmrAppService.java` `doctorstation/appservice/IDoctorStationMainAppService.java`
- **ServiceImpl**: `doctorstation/appservice/impl/DoctorStationPtDetailsAppServiceImpl.java` `doctorstation/appservice/impl/DoctorStationElepPrescriptionServiceImpl.java` `doctorstation/appservice/impl/DoctorPhraseAppServiceImpl.java`
- **Mapper**: `doctorstation/mapper/DoctorStationAdviceAppMapper.java` `doctorstation/mapper/DoctorStationEmrAppMapper.java` `doctorstation/mapper/DoctorStationDiagnosisAppMapper.java`
- **DTO**: `doctorstation/dto/EncounterContractDto.java` `doctorstation/dto/AdviceInventoryDto.java` `doctorstation/dto/ActivityChildrenJsonParams.java` `doctorstation/dto/DoctorStationLabApplyItemDto.java` `doctorstation/dto/DoctorStationInitDto.java`
### `document` (47 files)
- **Controller**: `document/controller/DocRecordController.java` `document/controller/DocDefinitionController.java` `document/controller/InformedConsentController.java`
- **AppService**: `document/appservice/IDocStatisticsAppService.java` `document/appservice/IDocRecordAppService.java` `document/appservice/IDocTemplateAppService.java`
- **ServiceImpl**: `document/appservice/impl/DocStatisticsDefinitionAppServiceImpl.java` `document/appservice/impl/DocRecordAppServiceImpl.java` `document/appservice/impl/DocStatisticsAppServiceImpl.java`
- **Mapper**: `document/mapper/DocRecordAppMapper.java` `document/mapper/DocStatisticsDefinitionAppMapper.java` `document/mapper/DocDefinitionAppMapper.java`
- **DTO**: `document/dto/DocStatisticsDefinitionDto.java` `document/dto/DocRecordPatientQueryParam.java` `document/dto/DocDefinitionOrganizationDto.java` `document/dto/DocRecordDto.java` `document/dto/DocTemplateDto.java`
### `empi` (5 files)
- **Controller**: `empi/controller/EmpiController.java` `empi/controller/EmpiIdVerificationController.java` `empi/controller/EmpiEnhancedController.java`
- **AppService**: `empi/appservice/IEmpiAppService.java`
- **ServiceImpl**: `empi/appservice/impl/EmpiAppServiceImpl.java`
### `emr` (6 files)
- **Controller**: `emr/controller/EmrArchiveController.java` `emr/controller/StructuredEmrController.java` `emr/controller/EmrRevisionController.java`
- **AppService**: `emr/appservice/IStructuredEmrAppService.java`
- **ServiceImpl**: `emr/appservice/impl/StructuredEmrAppServiceImpl.java`
### `epidemic` (3 files)
- **Controller**: `epidemic/controller/EpidemicController.java`
- **AppService**: `epidemic/appservice/IEpidemicAppService.java`
- **ServiceImpl**: `epidemic/appservice/impl/EpidemicAppServiceImpl.java`
### `esbmanage` (4 files)
- **Controller**: `esbmanage/controller/EsbReliabilityController.java` `esbmanage/controller/EsbMessageController.java` `esbmanage/controller/EsbServiceRegistryController.java`
### `externalintegration` (18 files)
- **Controller**: `externalintegration/controller/FoodborneAcquisitionAppController.java`
- **AppService**: `externalintegration/appservice/IBankPosCloudAppService.java` `externalintegration/appservice/IFoodborneAcquisitionAppService.java`
- **ServiceImpl**: `externalintegration/appservice/impl/FoodborneAcquisitionAppServiceImpl.java` `externalintegration/appservice/impl/BankPosCloudAppServiceImpl.java`
- **Mapper**: `externalintegration/mapper/FoodborneAcquisitionAppMapper.java`
- **DTO**: `externalintegration/dto/BpcTransactionResponseDto.java` `externalintegration/dto/BpcPaymentScanNotifyDto.java` `externalintegration/dto/FaSimplediseaseAddNopwParam.java` `externalintegration/dto/BpcTransactionRequestDto.java` `externalintegration/dto/BpcDataElementDto.java`
### `infection` (4 files)
- **Controller**: `infection/controller/InfectionEnhancedController.java` `infection/controller/InfectionController.java`
- **AppService**: `infection/appservice/IInfectionAppService.java`
- **ServiceImpl**: `infection/appservice/impl/InfectionAppServiceImpl.java`
### `inhospitalcharge` (17 files)
- **Controller**: `inhospitalcharge/controller/AdvancePaymentManageController.java` `inhospitalcharge/controller/InHospitalRegisterController.java`
- **AppService**: `inhospitalcharge/appservice/IInHospitalRegisterAppService.java` `inhospitalcharge/appservice/IAdvancePaymentManageAppService.java`
- **ServiceImpl**: `inhospitalcharge/appservice/impl/AdvancePaymentManageAppServiceImpl.java` `inhospitalcharge/appservice/impl/InHospitalRegisterAppServiceImpl.java`
- **Mapper**: `inhospitalcharge/mapper/InHospitalRegisterAppMapper.java` `inhospitalcharge/mapper/AdvancePaymentManageAppMapper.java`
- **DTO**: `inhospitalcharge/dto/AdvancePaymentInAndOutDto.java` `inhospitalcharge/dto/PatientUpdateDto.java` `inhospitalcharge/dto/NoFilesRegisterDto.java` `inhospitalcharge/dto/InHospitalPatientInfoDto.java` `inhospitalcharge/dto/InHospitalRegisterQueryDto.java`
### `inhospitalnursestation` (52 files)
- **Controller**: `inhospitalnursestation/controller/AdviceProcessController.java` `inhospitalnursestation/controller/NurseBillingController.java` `inhospitalnursestation/controller/EncounterAutoRollAppController.java`
- **AppService**: `inhospitalnursestation/appservice/IOrgDeviceStockTakeAppService.java` `inhospitalnursestation/appservice/IAdviceProcessAppService.java` `inhospitalnursestation/appservice/INurseBillingAppService.java`
- **ServiceImpl**: `inhospitalnursestation/appservice/impl/OrgDeviceStockTakeAppServiceImpl.java` `inhospitalnursestation/appservice/impl/ATDManageAppServiceImpl.java` `inhospitalnursestation/appservice/impl/EncounterAutoRollAppServiceImpl.java`
- **Mapper**: `inhospitalnursestation/mapper/ATDManageAppMapper.java` `inhospitalnursestation/mapper/EncounterAutoRollAppMapper.java` `inhospitalnursestation/mapper/MedicineSummaryAppMapper.java`
- **DTO**: `inhospitalnursestation/dto/AdmissionBedPageDto.java` `inhospitalnursestation/dto/AdviceExecuteParam.java` `inhospitalnursestation/dto/InpatientAdviceParam.java` `inhospitalnursestation/dto/DispenseFormSearchParam.java` `inhospitalnursestation/dto/AutoRollNursingDto.java`
### `inpatientmanage` (40 files)
- **Controller**: `inpatientmanage/controller/NursingVitalSignsChartController.java` `inpatientmanage/controller/VitalSignsController.java` `inpatientmanage/controller/PatientHomeController.java`
- **AppService**: `inpatientmanage/appservice/IPatientHomeAppService.java` `inpatientmanage/appservice/IDepositAppService.java` `inpatientmanage/appservice/INursingRecordAppService.java`
- **ServiceImpl**: `inpatientmanage/appservice/impl/DepositAppServiceImpl.java` `inpatientmanage/appservice/impl/NursingRecordAppServiceImpl.java` `inpatientmanage/appservice/impl/PatientHomeAppServiceImpl.java`
- **Mapper**: `inpatientmanage/mapper/VitalSignsAppMapper.java` `inpatientmanage/mapper/DepositMapper.java` `inpatientmanage/mapper/NursingRecordAppMapper.java`
- **DTO**: `inpatientmanage/dto/DepositDetailDto.java` `inpatientmanage/dto/VitalSignsChartSmallDto.java` `inpatientmanage/dto/VitalSignsSaveDto.java` `inpatientmanage/dto/PatientHomeSearchParam.java` `inpatientmanage/dto/PatientHomeEmptyBedDto.java`
### `inventorymanage` (107 files)
- **Controller**: `inventorymanage/controller/PurchaseReturnController.java` `inventorymanage/controller/InventorySettlementController.java` `inventorymanage/controller/ReturnIssueController.java`
- **AppService**: `inventorymanage/appservice/IProductStocktakingAppService.java` `inventorymanage/appservice/IInventoryDetailsAppService.java` `inventorymanage/appservice/IReturnIssueAppService.java`
- **ServiceImpl**: `inventorymanage/appservice/impl/InventoryDetailsAppServiceImpl.java` `inventorymanage/appservice/impl/ProductTransferAppServiceImpl.java` `inventorymanage/appservice/impl/ReceiptApprovalAppServiceImpl.java`
- **Mapper**: `inventorymanage/mapper/ProductDetailAppMapper.java` `inventorymanage/mapper/RequisitionIssueMapper.java` `inventorymanage/mapper/PurchaseReturnMapper.java`
- **DTO**: `inventorymanage/dto/ProductTransferPageDto.java` `inventorymanage/dto/PurchaseInventoryDto.java` `inventorymanage/dto/ReceiptDetailDto.java` `inventorymanage/dto/RequisitionOutDetailDto.java` `inventorymanage/dto/InventoryReceiptDetailDto.java`
### `jlau` (5 files)
- **Controller**: `jlau/controller/ReviewPrescriptionRecordsController.java`
- **AppService**: `jlau/appservice/IReviewPrescriptionRecordsAppService.java`
- **ServiceImpl**: `jlau/appservice/impl/ReviewPrescriptionRecordsAppServiceImpl.java`
- **Mapper**: `jlau/mapper/ReviewPrescriptionRecordsAppMapper.java`
- **DTO**: `jlau/dto/ReviewPrescriptionRecordsDto.java`
### `lab` (7 files)
- **Controller**: `lab/controller/LabActivityDefinitionController.java` `lab/controller/LabHistoryController.java` `lab/controller/LabEnhancedController.java`
- **AppService**: `lab/appservice/ILabActivityDefinitionAppService.java`
- **ServiceImpl**: `lab/appservice/impl/LabActivityDefinitionAppServiceImpl.java`
### `materialmanage` (46 files)
- **Controller**: `materialmanage/controller/MaterialReturnOrderController.java` `materialmanage/controller/MaterialTransferInOrderController.java` `materialmanage/controller/MaterialTransferOutOrderController.java`
- **ServiceImpl**: `materialmanage/appservice/impl/MaterialPurchaseOrderServiceImpl.java` `materialmanage/appservice/impl/MaterialTransferOutOrderServiceImpl.java` `materialmanage/appservice/impl/MaterialReturnToWarehouseOrderServiceImpl.java`
- **Mapper**: `materialmanage/mapper/MaterialCommonMapper.java` `materialmanage/mapper/MaterialProfitLossOrderMapper.java` `materialmanage/mapper/MaterialTransferOutOrderMapper.java`
- **DTO**: `materialmanage/dto/MaterialInitDto.java` `materialmanage/dto/MaterialSearchParam.java` `materialmanage/dto/MaterialDto.java` `materialmanage/dto/MaterialDetailDto.java` `materialmanage/dto/MaterialDeviceInfoDto.java`
### `mrhomepage` (6 files)
- **Controller**: `mrhomepage/controller/DrgAnalysisController.java` `mrhomepage/controller/MrManagementController.java` `mrhomepage/controller/MrHomepageController.java`
- **AppService**: `mrhomepage/appservice/IMrHomepageAppService.java`
- **ServiceImpl**: `mrhomepage/appservice/impl/MrHomepageAppServiceImpl.java`
### `nenu` (22 files)
- **Controller**: `nenu/controller/GfRatioApplicationRecordController.java` `nenu/controller/GfStudentListController.java` `nenu/controller/GfRatioManageController.java`
- **AppService**: `nenu/appservice/IGfRatioManageAppService.java` `nenu/appservice/IGfRatioApplicationRecordAppService.java` `nenu/appservice/IGfStudentListAppService.java`
- **ServiceImpl**: `nenu/appservice/impl/GfRatioApplicationRecordAppServiceImpl.java` `nenu/appservice/impl/GfRatioManageAppServiceImpl.java` `nenu/appservice/impl/GfStudentListAppServiceImpl.java`
- **Mapper**: `nenu/mapper/GfStudentListAppMapper.java` `nenu/mapper/GfRatioManageAppMapper.java` `nenu/mapper/GfRatioApplicationRecordAppMapper.java`
- **DTO**: `nenu/dto/GfIndividualRatioDto.java` `nenu/dto/GfRatioApplicationRecordDto.java` `nenu/dto/GfStudentListImportDto.java` `nenu/dto/GfRatioApplicationProcessDto.java` `nenu/dto/GfStudentPeisDto.java`
### `nursing` (8 files)
- **Controller**: `nursing/controller/NursingExecutionController.java` `nursing/controller/NursingAssessmentEnhancedController.java` `nursing/controller/NursingEnhancedController.java`
- **AppService**: `nursing/appservice/INursingAppService.java`
- **ServiceImpl**: `nursing/appservice/impl/NursingAppServiceImpl.java`
### `orderclosedloop` (3 files)
- **Controller**: `orderclosedloop/controller/OrderClosedLoopController.java`
- **AppService**: `orderclosedloop/appservice/IOrderClosedLoopAppService.java`
- **ServiceImpl**: `orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java`
### `outpatientmanage` (22 files)
- **Controller**: `outpatientmanage/controller/OutpatientTreatmentController.java` `outpatientmanage/controller/OutpatientSkinTestAppController.java` `outpatientmanage/controller/OutpatientInfusionController.java`
- **AppService**: `outpatientmanage/appservice/IOutpatientTreatmentAppService.java` `outpatientmanage/appservice/IOutpatientInfusionAppService.java` `outpatientmanage/appservice/IOutpatientSkinTestAppService.java`
- **ServiceImpl**: `outpatientmanage/appservice/impl/OutpatientTreatmentAppServiceImpl.java` `outpatientmanage/appservice/impl/OutpatientSkinTestAppServiceImpl.java` `outpatientmanage/appservice/impl/OutpatientInfusionAppServiceImpl.java`
- **Mapper**: `outpatientmanage/mapper/OutpatientTreatmentAppMapper.java` `outpatientmanage/mapper/OutpatientInfusionAppMapper.java` `outpatientmanage/mapper/OutpatientSkinTestAppMapper.java`
- **DTO**: `outpatientmanage/dto/SkinTestMedLotNumberDto.java` `outpatientmanage/dto/OutpatientInfusionRecordDto.java` `outpatientmanage/dto/SkinTestSaveDto.java` `outpatientmanage/dto/OutpatientTreatmentInfoDto.java` `outpatientmanage/dto/OutpatientStationInitDto.java`
### `patientmanage` (13 files)
- **Controller**: `patientmanage/controller/PatientInformationController.java` `patientmanage/controller/OutpatientRecordController.java`
- **ServiceImpl**: `patientmanage/appservice/impl/OutpatientRecordServiceImpl.java` `patientmanage/appservice/impl/PatientInformationServiceImpl.java`
- **Mapper**: `patientmanage/mapper/PatientManageMapper.java`
- **DTO**: `patientmanage/dto/PatientInfoInitDto.java` `patientmanage/dto/PatientIdInfoDto.java` `patientmanage/dto/OutpatientRecordSearchParam.java` `patientmanage/dto/PatientBaseInfoDto.java` `patientmanage/dto/OutpatientRecordDto.java`
### `paymentmanage` (57 files)
- **Controller**: `paymentmanage/controller/EleInvoiceController.java` `paymentmanage/controller/ChargeBillController.java` `paymentmanage/controller/PaymentContractController.java`
- **ServiceImpl**: `paymentmanage/appservice/impl/PaymentRecServiceImpl.java` `paymentmanage/appservice/impl/IChargeBillServiceImpl.java` `paymentmanage/appservice/impl/EleInvoiceServiceImpl.java`
- **Mapper**: `paymentmanage/mapper/EleInvoiceMapper.java` `paymentmanage/mapper/ThreePartPayMapper.java` `paymentmanage/mapper/ChangePriceMapper.java`
- **DTO**: `paymentmanage/dto/NenuBpcPayDto.java` `paymentmanage/dto/EleInvoiceResultDto.java` `paymentmanage/dto/ChargeSummaryDto.java` `paymentmanage/dto/EleInvoicePaymentInfoDto.java` `paymentmanage/dto/Clinic2207OrderResultInfoDto.java`
### `personalization` (22 files)
- **Controller**: `personalization/controller/ActivityDeviceController.java` `personalization/controller/OrdersGroupPackageController.java` `personalization/controller/OrderGroupController.java`
- **AppService**: `personalization/appservice/IOrderGroupAppService.java` `personalization/appservice/IOrdersGroupPackageAppService.java` `personalization/appservice/IActivityDeviceAppService.java`
- **ServiceImpl**: `personalization/appservice/impl/OrdersGroupPackageAppServiceImpl.java` `personalization/appservice/impl/ActivityDeviceAppServiceImpl.java` `personalization/appservice/impl/IOrderGroupAppServiceImpl.java`
- **Mapper**: `personalization/mapper/OrdersGroupPackageAppMapper.java` `personalization/mapper/OrderGroupAppMapper.java` `personalization/mapper/ActivityDeviceAppMapper.java`
- **DTO**: `personalization/dto/OrdersGroupPackageDetailSaveDto.java` `personalization/dto/OrderGroupDto.java` `personalization/dto/OrdersGroupPackageDto.java` `personalization/dto/OrderGroupInitDto.java` `personalization/dto/OrdersGroupPackageDetailQueryDto.java`
### `pharmacyDispensarymanage` (42 files)
- **Controller**: `pharmacyDispensarymanage/controller/PharmacyDispensaryTransferOutOrderController.java` `pharmacyDispensarymanage/controller/PharmacyDispensaryDispensingOrderController.java` `pharmacyDispensarymanage/controller/PharmacyDispensaryStocktakingOrderController.java`
- **ServiceImpl**: `pharmacyDispensarymanage/appservice/impl/PharmacyDispensaryStocktakingOrderServiceImpl.java` `pharmacyDispensarymanage/appservice/impl/PharmacyDispensaryTransferInOrderServiceImpl.java` `pharmacyDispensarymanage/appservice/impl/PharmacyDispensaryStockInOrderServiceImpl.java`
- **Mapper**: `pharmacyDispensarymanage/mapper/PharmacyDispensaryReturnToWarehouseOrderMapper.java` `pharmacyDispensarymanage/mapper/PharmacyDispensaryTransferOutOrderMapper.java` `pharmacyDispensarymanage/mapper/PharmacyDispensaryRequisitionOrderMapper.java`
- **DTO**: `pharmacyDispensarymanage/dto/PharmacyDispensaryDto.java` `pharmacyDispensarymanage/dto/PharmacyDispensaryDetailDto.java` `pharmacyDispensarymanage/dto/PharmacyDispensarySearchParam.java` `pharmacyDispensarymanage/dto/PharmacyDispensaryMedicationInfoDto.java` `pharmacyDispensarymanage/dto/PharmacyDispensaryInitDto.java`
### `pharmacyWarehousemanage` (42 files)
- **Controller**: `pharmacyWarehousemanage/controller/PharmacyWarehouseProfitLossOrderController.java` `pharmacyWarehousemanage/controller/PharmacyWarehouseReturnToWarehouseOrderController.java` `pharmacyWarehousemanage/controller/PharmacyWarehouseStockOutOrderController.java`
- **ServiceImpl**: `pharmacyWarehousemanage/appservice/impl/PharmacyWarehousePurchaseOrderServiceImpl.java` `pharmacyWarehousemanage/appservice/impl/PharmacyWarehouseDocumentManagementServiceImpl.java` `pharmacyWarehousemanage/appservice/impl/PharmacyWarehouseProfitLossOrderServiceImpl.java`
- **Mapper**: `pharmacyWarehousemanage/mapper/PharmacyWarehousePurchaseOrderMapper.java` `pharmacyWarehousemanage/mapper/PharmacyWarehouseDocumentManagementMapper.java` `pharmacyWarehousemanage/mapper/PharmacyWarehouseStockInOrderMapper.java`
- **DTO**: `pharmacyWarehousemanage/dto/PharmacyWarehouseDto.java` `pharmacyWarehousemanage/dto/PharmacyWarehouseDetailDto.java` `pharmacyWarehousemanage/dto/PharmacyWarehouseMedicationInfoDto.java` `pharmacyWarehousemanage/dto/PharmacyWarehouseInitDto.java` `pharmacyWarehousemanage/dto/PharmacyWarehouseSearchParam.java`
### `pharmacymanage` (53 files)
- **Controller**: `pharmacymanage/controller/InHospitalReturnMedicineController.java` `pharmacymanage/controller/PharmacyStockAlertController.java` `pharmacymanage/controller/MedicationDetailsController.java`
- **AppService**: `pharmacymanage/appservice/ISummaryDispenseMedicineAppService.java` `pharmacymanage/appservice/IPendingMedicationDetailsAppService.java` `pharmacymanage/appservice/IInHospitalReturnMedicineAppService.java`
- **ServiceImpl**: `pharmacymanage/appservice/impl/ReturnMedicineAppServiceImpl.java` `pharmacymanage/appservice/impl/MedicationDetailsAppServiceImpl.java` `pharmacymanage/appservice/impl/WesternMedicineDispenseAppServiceImpl.java`
- **Mapper**: `pharmacymanage/mapper/PendingMedicationDetailsMapper.java` `pharmacymanage/mapper/MedicalDeviceDispenseMapper.java` `pharmacymanage/mapper/SummaryDispenseMedicineMapper.java`
- **DTO**: `pharmacymanage/dto/MedDetailsInitDto.java` `pharmacymanage/dto/EncounterInfoSearchParam.java` `pharmacymanage/dto/ItemDispenseOrderDto.java` `pharmacymanage/dto/MedicineSummaryDto.java` `pharmacymanage/dto/MedicineSummarySearchParam.java`
### `quality` (5 files)
- **Controller**: `quality/controller/BusinessAnalyticsController.java` `quality/controller/QualityEnhancedController.java` `quality/controller/EmrQualityController.java`
- **AppService**: `quality/appservice/IEmrQualityAppService.java`
- **ServiceImpl**: `quality/appservice/impl/EmrQualityAppServiceImpl.java`
### `rationaldrug` (3 files)
- **Controller**: `rationaldrug/controller/RationalDrugController.java`
- **AppService**: `rationaldrug/appservice/IRationalDrugAppService.java`
- **ServiceImpl**: `rationaldrug/appservice/impl/RationalDrugAppServiceImpl.java`
### `regdoctorstation` (38 files)
- **Controller**: `regdoctorstation/controller/NurseManageController.java` `regdoctorstation/controller/AdviceManageController.java` `regdoctorstation/controller/SpecialAdviceController.java`
- **AppService**: `regdoctorstation/appservice/IAdviceManageAppService.java` `regdoctorstation/appservice/IRequestFormManageAppService.java` `regdoctorstation/appservice/ISpecialAdviceAppService.java`
- **ServiceImpl**: `regdoctorstation/appservice/impl/SpecialAdviceAppServiceImpl.java` `regdoctorstation/appservice/impl/RequestFormManageAppServiceImpl.java` `regdoctorstation/appservice/impl/NurseManageServiceImpl.java`
- **Mapper**: `regdoctorstation/mapper/RequestFormManageAppMapper.java` `regdoctorstation/mapper/AdviceManageAppMapper.java` `regdoctorstation/mapper/SpecialAdviceAppMapper.java`
- **DTO**: `regdoctorstation/dto/RegPatientMainInfoDto.java` `regdoctorstation/dto/NursingOrdersDetailDto.java` `regdoctorstation/dto/LeaveHospitalParam.java` `regdoctorstation/dto/NursingOrdersSaveDto.java` `regdoctorstation/dto/NursingOrdersEncounterDto.java`
### `reportManagement` (11 files)
- **Controller**: `reportManagement/controller/reportManagementController.java`
- **AppService**: `reportManagement/appservice/IInfectiousCardAppService.java`
- **ServiceImpl**: `reportManagement/appservice/impl/InfectiousCardAppServiceImpl.java`
- **Mapper**: `reportManagement/mapper/ReportManageCardMapper.java`
- **DTO**: `reportManagement/dto/InfectiousCardDto.java` `reportManagement/dto/InfectiousCardParam.java`
### `reportmanage` (164 files)
- **Controller**: `reportmanage/controller/AmbAdviceStatisticsAppController.java` `reportmanage/controller/MonthlySettlementController.java` `reportmanage/controller/PurchaseReturnReportController.java`
- **AppService**: `reportmanage/appservice/PurchaseReturnReportAppService.java` `reportmanage/appservice/IDrugDosageSettlementAppService.java` `reportmanage/appservice/IDepartmentRevenueStatisticsAppService.java`
- **ServiceImpl**: `reportmanage/appservice/impl/InboundReportAppServiceImpl.java` `reportmanage/appservice/impl/MedicationInboundReportAppServiceImpl.java` `reportmanage/appservice/impl/ReportStatisticsAppServiceImpl.java`
- **Mapper**: `reportmanage/mapper/PrintReportMapper.java` `reportmanage/mapper/ReportStatisticsMapper.java` `reportmanage/mapper/LossReportMapper.java`
- **DTO**: `reportmanage/dto/ReportDiseaseDetailsDto.java` `reportmanage/dto/InboundReportSearchParam.java` `reportmanage/dto/InpatientMedicalRecordHomePageCollectionDto.java` `reportmanage/dto/ZyCostDetailParam.java` `reportmanage/dto/BottleLabelDto.java`
### `review` (3 files)
- **Controller**: `review/controller/ReviewController.java`
- **AppService**: `review/appservice/IReviewAppService.java`
- **ServiceImpl**: `review/appservice/impl/ReviewAppServiceImpl.java`
### `service` (2 files)
- **ServiceImpl**: `service/impl/HomeStatisticsServiceImpl.java`
### `system` (5 files)
- **Controller**: `system/controller/ApiAuthController.java` `system/controller/DashboardController.java` `system/controller/SysAuditLogController.java`
### `tcm` (3 files)
- **Controller**: `tcm/controller/TcmController.java`
- **AppService**: `tcm/appservice/ITcmAppService.java`
- **ServiceImpl**: `tcm/appservice/impl/TcmAppServiceImpl.java`
### `tencentJH` (13 files)
- **Controller**: `tencentJH/controller/TencentController.java`
- **AppService**: `tencentJH/appservice/ITencentAppService.java`
- **ServiceImpl**: `tencentJH/appservice/impl/TencentAppServiceImpl.java`
- **Mapper**: `tencentJH/mapper/TencentAppMapper.java`
- **DTO**: `tencentJH/dto/PatientInfoTencentDto.java` `tencentJH/dto/CurrentDayEncounterTencentDto.java`
### `triageandqueuemanage` (13 files)
- **Controller**: `triageandqueuemanage/controller/CallNumberVoiceConfigController.java` `triageandqueuemanage/controller/TriageQueueController.java`
- **AppService**: `triageandqueuemanage/appservice/CallNumberVoiceConfigAppService.java` `triageandqueuemanage/appservice/TriageQueueAppService.java`
- **ServiceImpl**: `triageandqueuemanage/appservice/impl/CallNumberVoiceConfigAppServiceImpl.java` `triageandqueuemanage/appservice/impl/TriageQueueAppServiceImpl.java`
- **Mapper**: `triageandqueuemanage/mapper/CallNumberVoiceConfigAppMapper.java`
### `ybmanage` (55 files)
- **Controller**: `ybmanage/controller/YbInpatientController.java` `ybmanage/controller/YbElepController.java` `ybmanage/controller/YbController.java`
- **ServiceImpl**: `ybmanage/service/impl/YbEleHttpServiceImpl.java` `ybmanage/service/impl/YbServiceImpl.java` `ybmanage/service/impl/YbElepBaseServiceImpl.java`
- **Mapper**: `ybmanage/mapper/YbElepMapper.java` `ybmanage/mapper/YbMapper.java`
- **DTO**: `ybmanage/dto/FinancialHand3203AWebParam.java` `ybmanage/dto/FinancialHand3201WebParam.java` `ybmanage/dto/Financial13203WebParam.java` `ybmanage/dto/VeriPrescriptionInfoDto.java` `ybmanage/dto/YbInHospitalRegisterQueryDto.java`
## 前端关键文件
| 目录 | 说明 |
|---|---|
| `src/utils/request.js` | Axios 请求/响应拦截器 |
| `src/api/` | API 接口定义 |
| `src/components/` | 公共组件 |
| `src/views/doctorstation/` | 门诊医生站 |
| `src/views/inpatientDoctor/` | 住院医生站 |
| `src/views/inpatientNurse/` | 住院护士站 |
| `src/views/charge/` | 收费工作站 |
| `src/views/datadictionary/` | 数据字典 |
| `src/views/system/` | 系统管理 |
## 公共/通用文件
- `com.core.common.core.domain.R` — 统一响应封装
- `com.core.common.core.domain.entity.SysDictData` — 字典数据实体
- `com.core.common.utils.SecurityUtils` — 安全工具(获取当前用户)
- `com.core.common.enums.*` — 枚举定义
- `com.healthlink.his.common.constant.CommonConstants` — 公共常量
- `com.healthlink.his.common.utils.HisQueryUtils` — 查询工具
- `com.healthlink.his.common.utils.HisPageUtils` — 分页工具
- `com.healthlink.his.web.doctorstation.utils.AdviceUtils` — 医嘱工具类
=== 已生成 421 行索引 ===

View File

@@ -0,0 +1,25 @@
# Bug #698 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-11 16:51:39
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 698
- **标题**: [收费工作站-住院登记-已登记入院] 检索维度单一,且关键归档信息缺失(需增设检索条件与补充列表字段展示)
- **模块**: 住院登记管理
- **提出人**: 陈显精
---
LLM 失败,关键词分析)
Bug: [收费工作站-住院登记-已登记入院] 检索维度单一,且关键归档信息缺失(需增设检索条件与补充列表字段展示)
模块: 住院登记管理
---
## 路由决策
- **修复 Agent**: zhaoyun
- **原因**: 关键词: 前端

View File

@@ -0,0 +1,84 @@
# Bug #741 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 11:46:17
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 741
- **标题**: 【住院医生工作站】打开门诊医生工作站会有代码列表报错
- **模块**: 住院医生工作站
- **提出人**: 王栩坤
---
### 一、Bug 理解
用户登录 doctor1 账号进入**住院医生工作站**页面时,页面加载过程中某个 API 请求返回了 500 错误,错误信息为 `class java.util.ArrayList cannot be cast to class com.fasterxml.jackson.databind.JsonNode`。这个 Java ClassCastException 被前端 `request.js` 拦截器捕获并弹出错误提示,导致页面功能异常。期望是页面能正常加载,患者列表和各 Tab 页正常工作。
### 二、根因分析
**直接原因**:后端某个 API 接口在处理请求或序列化响应时,抛出了 `ClassCastException: ArrayList cannot be cast to JsonNode`。异常被 `GlobalExceptionHandler` 捕获后返回 `{code:500, msg:"class java.util.ArrayList cannot be cast to class com.fasterxml.jackson.databind.JsonNode"}`
**根因定位**commit `68cfa4882` 修改了 `ApplicationConfig`,将 `Jackson2ObjectMapperBuilderCustomizer` 替换为直接创建 `new ObjectMapper()``@Bean`
```java
// 旧代码 — 定制 Spring Boot 自动配置的 ObjectMapper
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> { ... };
}
// 新代码 — 直接覆盖 Spring Boot 自动配置的 ObjectMapper
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
...
}
```
**问题机制**
1. 定义 `@Bean ObjectMapper` 会**替换** Spring Boot 自动配置的 `ObjectMapper`,丢失大量自动配置(模块注册、类型解析器、序列化注解处理等)
2. Spring Boot 4.x 的 `MappingJackson2HttpMessageConverter` 使用此 Bean 做响应序列化
3.`DictAspect`(拦截所有 `@GetMapping`/`@PostMapping`)处理 `R<IPage<RegPatientMainInfoDto>>` 响应时,`IPage` 的泛型信息在新 `ObjectMapper` 下无法正确解析
4. Jackson 内部在序列化过程中尝试将 `ArrayList``IPage` 内部的 records 列表)强转为 `JsonNode`,导致 `ClassCastException`
**涉及文件**
- `core-framework/.../ApplicationConfig.java`**根因所在**ObjectMapper Bean 配置不当
- `healthlink-his-common/.../DictAspect.java` — 拦截所有 Controller 方法,触发序列化链路
- `regdoctorstation/.../AdviceManageController.java``/reg-patient-zk` 端点
- `regdoctorstation/.../AdviceManageAppServiceImpl.java``getRegPatientMainInfo()` 返回 `IPage`
- `utils/request.js` — 前端拦截器line 186 抛出 Promise reject
### 三、修复方案
**修改文件**`core-framework/src/main/java/com/core/framework/config/ApplicationConfig.java`
**修复方式**:将 `@Bean ObjectMapper` 回退为 `Jackson2ObjectMapperBuilderCustomizer`,这样 Spring Boot 自动配置的 ObjectMapper 保持不变,只在其基础上追加自定义配置:
```java
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> {
builder.timeZone(TimeZone.getDefault());
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.modules(javaTimeModule);
};
}
```
**验证步骤**
1. `mvn clean compile -DskipTests` — 编译通过
2. 启动应用 → 以 doctor1 登录 → 进入住院医生工作站 → 确认无报错
3. 验证患者列表正常加载、诊断/医嘱 Tab 页正常切换
### 四、路由决策
- **修复 Agent**: guanyu后端开发
- **原因**: 修复点在 `ApplicationConfig.java` 的 ObjectMapper Bean 配置,属于后端 Spring 配置问题,需要回退 Jackson 序列化配置并验证编译通过。

View File

@@ -0,0 +1,26 @@
# Bug #752 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 22:09:00
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 752
- **标题**: 【门诊医生站-检查项目】保存检查申请单时报JSON解析错误
- **模块**: 门诊医生工作站
- **提出人**: 陈显精
---
分析完成。根因明确、修复方案清晰,建议直接分派给赵云执行修复。
---
## 路由决策
- **FIXER_ID**: guanyu
- **修复 Agent**: guanyu后端
- **原因**: LLM 分析决策
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。

174
MD/bugs/BUG_753_ANALYSIS.md Normal file
View File

@@ -0,0 +1,174 @@
# Bug #753 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 22:03:57
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 753
- **标题**: 【收费工作站-住院登记】“待登记入院”与“已登记入院”页签下页面容器高度异常,系统自动无限下扩产生大量空白区
- **模块**: 住院登记管理
- **提出人**: 陈显精
---
---
## 一、Bug 理解
**原文引用:**
> **标题**:【收费工作站-住院登记】"待登记入院"与"已登记入院"页签下页面容器高度异常,系统自动无限下扩产生大量空白区
>
> **重现步骤**
> 1. 登录医院信息管理系统账号。
> 2. 在左侧主菜单栏中依次点击:「收费工作站」->「住院登记」。
> 3. 在右侧主工作区中,首先查看默认的「待登记入院」页签,往下滑动页面或观察底部数据区域。
> 4. 接着点击切换至「已登记入院」页签,同样向下滑动并观察底部。
>
> **结果**:表格与尾底部分页控件(写有"共 59 条 10条/页..."的控制条)之间存在异常巨大的空白间隔。页面右侧纵向滚动条极长,系统自动无限往下拉高窗口,给用户视觉呈瞬间呈现"断层/留白过长"现象。(注:在"待登记入院"和"已登记入院"两个子标签下均存在该问题。)
>
> **期望**:页面各组件高度计算正确,自适应浏览器窗口大小。分页控件行应紧跟紧随在表格区域底部显示,或者将表格区固定在一定高度内(超出出现局部滚动条),不能出现无限制的页面底部空白区和全局滚动条。
**附图分析**
- 已登记入院页签10行数据的表格下方出现大面积空白区域分页控件被推到页面最底部右侧纵向滚动条极长。
- 待登记入院页签:表格显示"暂无数据",但页面同样存在高度异常。
- 两个标签页下均有"系统自动无限往下扩高窗口"的标注。
**综合总结**:用户在住院登记页面的两个页签(待登记/已登记)中,表格下方出现大面积异常空白,分页控件与表格严重脱节,页面滚动条极长。这是一个纯前端布局问题,由 CSS 高度约束缺失导致 vxe-table 在无约束父容器中无限撑开高度。
---
## 二、根因分析
**直接原因**:两个组件(`accomplishList.vue``awaitList.vue`)的 `.table-container` 缺少高度约束和溢出控制。
**详细技术分析**
布局链条高度约束断裂:
```
.app-container (min-height: calc(100vh - 84px), 无固定height, 无overflow)
└─ el-tabs (auto height)
└─ .awaitList-container (height: 100%, 但父元素无固定高度 → 实际等于 auto)
├─ .operate (height: 40px, 固定)
└─ .table-container (无高度约束, 仅 padding)
├─ vxe-table (height="100%", 父容器auto → 无法正确约束)
└─ pagination
```
关键问题:
1. **`.awaitList-container` 使用 `height: 100%`但其父元素el-tabs content无固定高度**——导致 `100%` 实际解析为 auto无法约束子元素。
2. **`.table-container``overflow` 约束、无 flex 布局**——内容可以无限撑高。
3. **`vxe-table``height="100%"` 属性**——在无固定高度的父容器中无法创建滚动区域,表格内容直接撑开容器。
**涉及文件**(均为纯前端 CSS 问题):
- `healthlink-his-ui/src/views/inHospitalManagement/charge/register/components/accomplishList.vue` — 已登记入院列表
- `healthlink-his-ui/src/views/inHospitalManagement/charge/register/components/awaitList.vue` — 待登记入院列表
---
## 三、修复方案
**核心思路**:用 flexbox 将 `.awaitList-container` 改为纵向弹性布局,让 `.table-container` 自动填充剩余空间并限制溢出,使 vxe-table 的 `height="100%"` 能正确约束在固定高度内。
### 修改文件 1`accomplishList.vue`
```scss
<!-- 修改前 -->
<style lang="scss" scoped>
.awaitList-container {
width: 100%;
height: 100%;
.operate {
display: flex;
justify-content: space-between;
height: 40px;
padding: 0 16px;
align-items: center;
}
.table-container {
padding: 8px 16px;
}
}
</style>
<!-- 修改后 -->
<style lang="scss" scoped>
.awaitList-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.operate {
flex-shrink: 0;
display: flex;
justify-content: space-between;
height: 40px;
padding: 0 16px;
align-items: center;
}
.table-container {
flex: 1;
min-height: 0;
overflow: hidden;
padding: 8px 16px;
}
}
</style>
```
### 修改文件 2`awaitList.vue`
同样的 CSS 修改(两个文件的 style 完全一致):
```scss
<!-- 修改后 -->
<style lang="scss" scoped>
.awaitList-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.operate {
flex-shrink: 0;
display: flex;
justify-content: space-between;
height: 40px;
padding: 0 16px;
align-items: center;
}
.table-container {
flex: 1;
min-height: 0;
overflow: hidden;
padding: 8px 16px;
}
}
</style>
```
**修改要点**3处 CSS 变更,两文件相同):
1. `.awaitList-container``display: flex; flex-direction: column;` — 建立纵向弹性布局
2. `.operate``flex-shrink: 0;` — 防止搜索栏被压缩
3. `.table-container``flex: 1; min-height: 0; overflow: hidden;`**关键修复**:自动填充剩余空间 + 禁止溢出撑高 + `min-height: 0` 确保 flex 子元素可缩小
这样 vxe-table 的 `height="100%"` 就能在固定的 `.table-container` 内正确创建滚动区域,表格内容超出时在表格内部滚动,分页控件紧随表格下方,不再撑开整个页面。
---
## 四、路由决策
**FIXER**: zhaoyun
**REASON**: 这是纯前端 CSS 布局问题,只涉及两个 Vue 组件的 `<style>` 块修改,无后端变更、无数据库变更,由前端开发修复最合适。
---
## 路由决策
- **FIXER_ID**: guanyu
- **修复 Agent**: guanyu后端
- **原因**: LLM 分析决策
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。

164
MD/bugs/BUG_757_ANALYSIS.md Normal file
View File

@@ -0,0 +1,164 @@
# Bug #757 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 14:58:17
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 757
- **标题**: 【门诊医生工作站】中医诊断中没有相同的诊断和症候,但就无法新增中医诊断
- **模块**: 门诊医生工作站
- **提出人**: 王栩坤
---
I have enough information now to produce the analysis. Let me compile the findings.
---
## 一、Bug 理解
用户在门诊医生站打开"中医诊断"对话框,选择中医诊断和中医证候后点击确定,**无法成功保存**。同时,已经保存过的中医诊断也无法正确回显诊断详情。期望:能正常选择诊断和证候、保存成功,并回显诊断详情数据。
## 二、根因分析
核心问题在 `tcmdiagnosisDialog.vue``submit()` 函数,它向后端发送的字段名与后端 DTO 完全不匹配:
**前端发送的数据结构:**
```js
const diagnosisChildList = [{
conditionCode: condition.value, // ← 字段名错误
syndromeCode: syndrome.value, // ← 字段名错误
}];
```
**后端 `SaveDiagnosisChildParam` 期望的数据结构:**
- `definitionId` (Long) — 诊断定义 ID
- `ybNo` (String) — 医保编码
- `syndromeGroupNo` (String) — 中医证候组号(用于关联病和证)
- `maindiseFlag` (Integer) — 主诊断标记
- `diagSrtNo` (Integer) — 排序号
**具体根因列表:**
| # | 问题 | 位置 | 影响 |
|---|------|------|------|
| 1 | `submit()` 发送 `conditionCode`/`syndromeCode`,后端接收不到 `definitionId``ybNo` | `tcmdiagnosisDialog.vue:submit()` | 保存时 Condition 记录的 `definitionId` 为 null保存失败或数据损坏 |
| 2 | 没有传 `syndromeGroupNo` 来关联"病"和"证" | `tcmdiagnosisDialog.vue:submit()` | 后端无法将诊断和证候配对成一组 |
| 3 | `openDialog()` 获取下拉选项时用 `item.ybNo` 作为 value但没有保存 `definitionId` | `tcmdiagnosisDialog.vue:openDialog()` | 丢失了关键的 `definitionId` |
| 4 | 没有传 `patientInfo` propdialog 用 `defineProps` 声明了但父组件可能未传) | `tcmdiagnosisDialog.vue` | `saveTcmDiagnosis` 请求中 `patientId`/`encounterId` 为 null |
**可能涉及的文件:**
- `healthlink-his-ui/src/views/doctorstation/components/tcm/tcmdiagnosisDialog.vue` — 主要 Bug 所在
- `healthlink-his-ui/src/views/doctorstation/components/api.js` — API 定义(无误)
- `healthlink-his-server/.../appservice/impl/DoctorStationChineseMedicalAppServiceImpl.java` — 后端 `saveTcmDiagnosis` 方法
## 三、修复方案
### 修复 1`tcmdiagnosisDialog.vue` — `openDialog()` 方法
将下拉选项的 value 从 `ybNo` 改为同时保存 `definitionId`
```js
// 修改前
conditionOptions.value = res.data.records.map((item) => ({
value: item.ybNo,
label: item.name,
}));
// 修改后
conditionOptions.value = res.data.records.map((item) => ({
value: item.id, // 用 definition ID 作为 value
ybNo: item.ybNo, // 保留医保编码
label: item.name,
}));
syndromeOptions.value = res.data.records.map((item) => ({
value: item.id,
ybNo: item.ybNo,
label: item.name,
}));
```
### 修复 2`tcmdiagnosisDialog.vue` — `submit()` 方法
重写提交逻辑,匹配后端 `SaveDiagnosisChildParam` 的字段名:
```js
function submit() {
if (!condition.value || !syndrome.value) {
proxy.$modal.msgWarning('请选择诊断和证候');
return;
}
// 找到选中的诊断和证候的完整信息
const selectedCondition = conditionOptions.value.find(item => item.value === condition.value);
const selectedSyndrome = syndromeOptions.value.find(item => item.value === syndrome.value);
// 生成证候组号(时间戳)
const syndromeGroupNo = 'TCM' + Date.now();
const diagnosisChildList = [
{
definitionId: condition.value, // 中医诊断 definition ID
ybNo: selectedCondition?.ybNo, // 中医诊断医保编码
syndromeGroupNo: syndromeGroupNo,
maindiseFlag: 1, // 主诊断标记
diagSrtNo: 1, // 排序号(病)
},
{
definitionId: syndrome.value, // 中医证候 definition ID
ybNo: selectedSyndrome?.ybNo, // 中医证候医保编码
syndromeGroupNo: syndromeGroupNo, // 同一组号
maindiseFlag: 0,
diagSrtNo: 2, // 排序号(证)
},
];
saveTcmDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: diagnosisChildList,
}).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功');
emit('flush');
close();
} else {
proxy.$modal.msgError(res.msg || '保存失败');
}
}).catch((error) => {
console.error('保存中医诊断失败:', error);
proxy.$modal.msgError('保存失败,请重试');
});
}
```
### 修复 3确保父组件传递 `patientInfo` prop
检查父组件(`tcmAdvice.vue` 或使用 `tcmdiagnosisDialog` 的页面)是否正确传递了 `patientInfo` prop。`tcmAdvice.vue` 已有 `patientInfo` prop 定义,所以如果是从 `tcmAdvice` 中使用该 dialog需要通过 `:patientInfo="patientInfo"` 传递。
### 修复 4可选后端防御性处理
后端 `saveTcmDiagnosis` 方法可以增加对 `definitionId` 为空的校验,避免写入脏数据:
```java
// 在 saveTcmDiagnosis 方法开头增加
for (SaveDiagnosisChildParam param : diagnosisChildList) {
if (param.getDefinitionId() == null) {
return R.fail("诊断定义ID不能为空请重新选择诊断");
}
}
```
## 四、路由决策
**FIXER: zhaoyun**(前端开发)
**REASON:** Bug 根因在前端对话框组件 `tcmdiagnosisDialog.vue` 的字段映射错误和提交逻辑缺陷,需要修改 Vue 前端代码openDialog 数据映射 + submit 参数构建 + 父组件 prop 传递),属于前端界面和 API 调用层修复。
---
## 路由决策
- **修复 Agent**: zhaoyun
- **原因**: ** Bug 根因在前端对话框组件 `tcmdiagnosisDialog.vue` 的字段映射错误和提交逻辑缺陷,需要修改 Vue 前端代码openDialog 数据映射 + submit 参数构建 + 父组件 prop 传递),属于前端界面和 API 调用层修复。

122
MD/bugs/BUG_758_ANALYSIS.md Normal file
View File

@@ -0,0 +1,122 @@
# Bug #758 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 15:34:50
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 758
- **标题**: 【门诊医生工作站-检验】检验信息下的新增的字段按钮点击会出现报错Cannot deserialize value of type `com.core.common.core.domain.entity.SysDictData` from Array value
- **模块**: 门诊医生工作站
- **提出人**: 王栩坤
---
I now have a complete understanding of the root cause. Let me provide the analysis.
---
### 一、Bug 理解
用户在门诊医生工作站的"检验信息"区域点击"新增"按钮时,页面弹出 Jackson 反序列化错误 `Cannot deserialize value of type SysDictData from Array value`,无法正常新增检验信息。期望点击新增后能正常清空表单、准备录入新检验申请单。
### 二、根因分析
**错误触发链路:**
```
用户点击"新增"
→ handleNewApplication()
→ resetForm()
→ getEncounterDiagnosis(encounterId) [API 调用]
→ 后端返回 R<List<DiagnosisQueryDto>>
→ DictAspect 拦截 @GetMapping 响应
→ processDict() 发现 DiagnosisQueryDto.medTypeCode 有 @Dict(dictCode = "med_type")
→ DictUtils.getDictLabel("med_type", value)
→ DictUtils.getDictCache("med_type")
→ mapper.convertValue(cached, TypeReference<List<SysDictData>>) ← 💥 这里抛异常
→ DictAspect 无 try-catch异常直接传播到前端
```
**根因:`DictUtils.getDictCache()` 缺少异常处理**
- 文件:`core-common/src/main/java/com/core/common/utils/DictUtils.java:38-62`
- Redis 中的字典缓存数据结构异常(可能是旧 Fastjson 序列化格式遗留、嵌套数组等)
- Jackson `ObjectMapper.convertValue()` 无法将异常结构转为 `List<SysDictData>`,抛出 `JsonMappingException`
- 该异常未被 `DictUtils``DictAspect` 捕获,直接传播为 HTTP 500 错误
**涉及的关键文件:**
1. `core-common/src/main/java/com/core/common/utils/DictUtils.java``getDictCache()` 方法(核心问题)
2. `healthlink-his-common/src/main/java/com/healthlink/his/common/aspectj/DictAspect.java``processDict()` 方法(缺少异常保护)
3. `healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/controller/DoctorStationDiagnosisController.java` — 被 DictAspect 拦截的控制器
4. `healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/dto/DiagnosisQueryDto.java` — 含 `@Dict` 注解的 DTO
**与历史 Bug 的关联:** 此前 commit `babd8d0c0` 修复了类似问题Jackson 配置从 ObjectMapper bean 改回 Jackson2ObjectMapperBuilderCustomizer`DictUtils.getDictCache()``convertValue` 仍缺少防御性异常处理。
### 三、修复方案
**修改 1`DictUtils.getDictCache()` 添加 try-catch 防御**(核心修复)
文件:`core-common/src/main/java/com/core/common/utils/DictUtils.java`
`getDictCache()` 方法中,为 `mapper.convertValue()` 添加 try-catch失败时清理损坏缓存并返回 null
```java
public static List<SysDictData> getDictCache(String key) {
Object cached = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNull(cached)) {
return null;
}
if (cached instanceof List && ((List<?>) cached).stream().allMatch(e -> e instanceof SysDictData)) {
@SuppressWarnings("unchecked")
List<SysDictData> result = (List<SysDictData>) cached;
return result;
}
try {
com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>> typeRef =
new com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>>() {};
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (cached instanceof JsonNode jsonNode) {
return mapper.convertValue(jsonNode, typeRef);
}
return mapper.convertValue(cached, typeRef);
} catch (Exception e) {
// 缓存数据格式异常,清理损坏缓存,下次重新加载
org.slf4j.LoggerFactory.getLogger(DictUtils.class)
.warn("字典缓存转换失败(key={}),已清理: {}", key, e.getMessage());
removeDictCache(key);
return null;
}
}
```
**修改 2`DictAspect.processDict()` 添加 try-catch 防御**(防止字典翻译失败影响 API 响应)
文件:`healthlink-his-common/src/main/java/com/healthlink/his/common/aspectj/DictAspect.java`
`processDict()` 方法中,对字典查询部分包裹 try-catch
```java
} else if (field.isAnnotationPresent(Dict.class)) {
try {
// ... 原有字典翻译逻辑 ...
} catch (Exception e) {
log.debug("字段 {} 字典翻译失败,跳过: {}", field.getName(), e.getMessage());
}
}
```
### 四、路由决策
**FIXER: guanyu后端开发**
**REASON:** 修复涉及 `DictUtils`core-common 模块)和 `DictAspect`healthlink-his-common 模块)两个后端 Java 文件的异常处理逻辑,纯后端修复,无需前端改动。
---
## 路由决策
- **修复 Agent**: guanyu
- **原因**: ** 修复涉及 `DictUtils`core-common 模块)和 `DictAspect`healthlink-his-common 模块)两个后端 Java 文件的异常处理逻辑,纯后端修复,无需前端改动。

104
MD/bugs/BUG_759_ANALYSIS.md Normal file
View File

@@ -0,0 +1,104 @@
# Bug #759 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 21:51:43
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 759
- **标题**: 【门诊医生工作站】电子处方点击新增处方报卡在100%卡出屏幕且诊断等下拉框无数据回显
- **模块**: 门诊医生工作站
- **提出人**: 王栩坤
---
I now have a complete understanding. Here is my analysis:
---
## 一、Bug 理解
**禅道 Bug #759 原文:**
- **标题**【门诊医生工作站】电子处方点击新增处方报卡在100%卡出屏幕且诊断等下拉框无数据回显
- **重现步骤**登录内科医生1账号doctor1/123456→ 进入门诊医生工作站 → 选择一名患者 → 点击处方 → 点击新增处方
- **实际结果**:报卡界面边距超出,下拉框无数据回显
- **期望结果**能够在100%视图下,新增处方的报卡能够完全展示出来;诊断和慢性诊断的下拉框有数据回显
**附图关键信息**
- 截图显示"新增处方"弹窗右侧被截断,"服药时间(结束)"和"途径"列不可见,右下角"删除"按钮被裁剪
- "诊断"下拉框显示占位文本"诊断""慢病诊断"下拉框点击后显示"无数据"
**综合总结**:用户在门诊医生工作站新增处方时,弹窗宽度固定为 `1840px`,在 100% 缩放下超出屏幕可视区域导致内容溢出;同时"诊断"和"慢病诊断"两个下拉框均无数据可选,无法正常使用处方开具功能。
---
## 二、根因分析
**涉及唯一文件**`healthlink-his-ui/src/views/doctorstation/components/eprescriptiondialog.vue`
### 问题1弹窗宽度溢出
- **根因**`el-dialog` 第5行设置 `width="1840px"` 为固定像素值。标准 1920px 屏幕减去浏览器 UI 和滚动条后可用宽度不足 1840px导致右侧内容被截断。此外第240行药品搜索弹出框 `popover` 设置 `width="1200"`,也加剧了溢出。
- **证据**第5行 `width="1840px"`第240行 `:width="1200"`
### 问题2诊断下拉框无数据
- **根因**"诊断"下拉框使用 `remote-method="getInit"`第134行这是一个远程搜索模式 —— **只有用户输入关键字时才会触发 `getInit` 函数调用 API**。弹窗打开时 `open()` 函数调用了 `getDiagnosisInfo()`,其中 `getEncounterDiagnosis()` 获取的诊断数据存入了 `diagnosisList`第764行**没有同步填充 `diagnosisListOption`**(下拉框 v-for 绑定的数据源第138行。因此弹窗打开时下拉框列表为空。
- **代码证据**`getDiagnosisInfo()` 第764行 `diagnosisList.value = res.data`,但 `diagnosisListOption` 仅在 `getInit` 的 remote 回调中被赋值第716行
### 问题3慢病诊断下拉框无数据
- **根因**`getChronicDisease` 调用医保接口 `/yb-request/getConditionDefinition`第779行。如果医保服务未连接或返回空数据catch 中静默处理并设为空数组第786行。这可能是医保环境未配置的预期行为但即使医保已连接如果 `encounterId` 传参有问题也会导致返回空。
---
## 三、修复方案
### 修复1弹窗宽度改为响应式
**文件**`eprescriptiondialog.vue` 第5行
```diff
- width="1840px"
+ width="90vw"
+ style="max-width: 1840px"
```
同时将第240行药品搜索 popover 宽度从 `1200` 缩减为 `min(1200px, 80vw)` 或直接改为 `"100%"`
### 修复2诊断下拉框打开时加载初始数据
**文件**`eprescriptiondialog.vue` `getDiagnosisInfo()` 函数约第763行
`getEncounterDiagnosis` 成功回调中,增加 `diagnosisListOption` 的赋值:
```diff
getEncounterDiagnosis(props.patient.encounterId).then((res) => {
diagnosisList.value = res.data;
+ diagnosisListOption.value = res.data || [];
let diagnosisInfo = diagnosisList.value.filter((item) => {
```
### 修复3慢病诊断 — 确认 API 返回
此问题取决于医保服务是否可用。如果医保服务已连接但仍无数据,需检查后端 `/yb-request/getConditionDefinition` 接口的返回值。如果是医保未配置环境,则属于已知限制,可考虑在下拉框上增加"医保未连接"的提示文案。
---
## 四、路由决策
**FIXER: zhaoyun赵云 — 前端开发)**
**REASON**:两个问题的根因全部在前端 `eprescriptiondialog.vue` 的样式和数据初始化逻辑中,不需要后端改动,属于纯前端修复任务。
---
## 路由决策
- **FIXER_ID**: zhaoyun
- **修复 Agent**: zhaoyun前端
- **原因**: LLM 分析决策
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。

114
MD/bugs/BUG_760_ANALYSIS.md Normal file
View File

@@ -0,0 +1,114 @@
# Bug #760 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 21:48:44
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 760
- **标题**: [住院护士站-护理记录] 选中患者时系统报错
- **模块**: 病区护士工作站
- **提出人**: 陈显精
---
Analysis complete. Here's the full report.
---
### 一、Bug 理解
**禅道 Bug #760[住院护士站-护理记录] 选中患者时系统报错**
**重现步骤:**
1. 登录账号wx密码123456登录系统进入【住院护士站】
2. 点击【护理记录】页签
3. 在左侧列表中选中任意已入院的患者
**期望结果:**
1. 选中患者后,系统正常响应,无报错弹窗
2. 成功加载并展示该患者对应的护理记录表单及历史数据
**实际结果:**
- 页面弹出红色报错:`No static resource nursing/statistics/summary/list for request '/healthlink-his/nursing/statistics/summary/list'`
- 右侧护理记录列表显示"暂无数据"
**截图确认:** 顶部红色报错横幅清晰可见,错误 URL 为 `/healthlink-his/nursing/statistics/summary/list`,选中患者"陈显精"后右侧表格为空。
**总结:** 用户在住院护士站的「护理记录」页签选中患者时,前端发起了一个后端不存在的 API 请求 `/nursing/statistics/summary/list`,导致 404 报错,护理记录数据无法加载。
---
### 二、根因分析
**根因Tab 组件映射错误 — "护理记录"页签加载了错误的组件。**
`healthlink-his-ui/src/views/inpatientNurse/inpatientNurseStation/index.vue` 中:
```html
<!-- 第72-76行 -->
<el-tab-pane label="护理记录" name="NursingRecord">
<Criticalrecord v-if="activeTabName === 'NursingRecord'" />
</el-tab-pane>
```
这里渲染的是 `Criticalrecord` 组件,而不是 `NursingRecord` 组件。
**关键文件对比:**
| 文件 | API 路径 | 后端是否有对应 Controller |
|------|----------|--------------------------|
| `nursingstatistics/criticalrecord.vue``nursingstatistics/api.js` | `/nursing/statistics/summary/list` | ❌ **不存在** |
| `nursingRecord/index.vue``nursingRecord/components/api.js` | `/nursing-record/nursing-patient-page` 等 | ✅ `NursingRecordController` |
- `inpatientNurseStation/index.js` 中已经同时导入了两个组件:
- `NursingRecord` from `@/views/inpatientNurse/nursingRecord/index.vue`
- `Criticalrecord` from `@/views/inpatientNurse/nursingstatistics/criticalrecord.vue`
-`index.vue` 的模板中**用错了组件名**`<Criticalrecord>` 应该是 `<NursingRecord>`
- 后端 `NursingRecordController` 映射在 `/nursing-record`,有完整的 patient-page、nursing-patient-page、save-nursing、delete-nursing 等端点
**涉及的文件:**
- `healthlink-his-ui/src/views/inpatientNurse/inpatientNurseStation/index.vue` — 模板中 Tab 渲染了错误组件
---
### 三、修复方案
**修改 1 个文件1 处改动:**
**文件:** `healthlink-his-ui/src/views/inpatientNurse/inpatientNurseStation/index.vue`
**改动:** 将第 74 行的 `<Criticalrecord>` 替换为 `<NursingRecord>`
```diff
- <el-tab-pane label="护理记录" name="NursingRecord">
- <Criticalrecord v-if="activeTabName === 'NursingRecord'" />
+ <el-tab-pane label="护理记录" name="NursingRecord">
+ <NursingRecord v-if="activeTabName === 'NursingRecord'" />
</el-tab-pane>
```
`NursingRecord` 已在同文件的 `index.js` 中导入第3行无需额外添加 import。
**验证:**
1. `cd healthlink-his-ui && npm run build:dev` — 编译通过
2. 登录后进入住院护士站 → 护理记录 → 选中患者 → 应加载出患者护理记录列表,无红色报错
---
### 四、路由决策
**FIXER: zhaoyun**
**REASON:** 纯前端组件映射修复,仅改动 1 个 Vue 文件中的 1 行模板标签(`Criticalrecord``NursingRecord`),属于前端界面/组件层级的问题。
---
## 路由决策
- **FIXER_ID**: zhaoyun
- **修复 Agent**: zhaoyun前端
- **原因**: ** 纯前端组件映射修复,仅改动 1 个 Vue 文件中的 1 行模板标签(`Criticalrecord``NursingRecord`),属于前端界面/组件层级的问题。
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。

132
MD/bugs/BUG_761_ANALYSIS.md Normal file
View File

@@ -0,0 +1,132 @@
# Bug #761 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 16:31:36
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 761
- **标题**: [住院护士站-汇总领药]领药明细列表,“领药时间”显示逻辑异常且早于医嘱开立时间
- **模块**: 病区护士工作站
- **提出人**: 陈显精
---
I now have a complete understanding of the bug. Let me produce the analysis.
---
### 一、Bug 理解
用户在"住院护士站 → 汇总领药"页面看到"领药时间"列显示了错误的日期("06-09"早于医嘱开立时间06-10 05:18:44和实际执行时间06-10 05:18。期望①列名改为"执行时间";②时间值应与医嘱执行界面的执行时间一致;③未发药/未汇总前不应展示"领药时间"。
### 二、根因分析
**全链路追踪:**
```
前端 prescriptionList.vue → API: getPrescriptionList
→ 后端 MedicineSummaryController.getMedicineDispenseFormPage
→ MedicineSummaryAppServiceImpl.getMedicineDispenseFormPage
→ MedicineSummaryAppMapper.selectMedicineDispenseFormPage (SQL)
→ 返回 MedicineDispenseFormDto.medicineSummaryParamList
→ 其中 dispenseTime 来自 med_medication_dispense.planned_dispense_time
```
**核心问题2处**
**问题1数据源错误 — `planned_dispense_time` ≠ 执行时间**
- Mapper XML 第39行`<result property="dispenseTime" column="planned_dispense_time"/>`,将 `med_medication_dispense.planned_dispense_time` 映射为 `dispenseTime`
- `planned_dispense_time` 是在 `AdviceProcessAppServiceImpl` 调用 `generateMedicationDispense()` 时通过 `parseExecuteTime(executeTime)` 设置的,来自前端传入的执行时间字符串
- 实际执行时间存储在 `cli_procedure.occurrence_time``Procedure.java` 第54行这才是医嘱执行界面显示的"执行时间"
- `planned_dispense_time` 的值可能因时区转换、字符串解析精度等原因与 `occurrence_time` 不一致(差一天即 "06-09" vs "06-10"
**问题2列名语义错误 — "领药时间"应为"执行时间"**
- `prescriptionList.vue` 第164行`title="领药时间"`,此列展示的是每条医嘱的执行时间点,不是"领药时间"
- 在未生成汇总领药单之前,不存在"领药"动作,显示"领药时间"不符合业务逻辑
**涉及文件:**
| 文件 | 行号 | 问题 |
|------|------|------|
| `prescriptionList.vue` | 164 | 列名 "领药时间" 应改为 "执行时间" |
| `MedicineSummaryAppMapper.xml` | 39, 78, ~125, 203 | `planned_dispense_time` 应改为 `cli_procedure.occurrence_time` |
| `MedicineSummaryParam.java` | 22 | 字段名 `dispenseTime` 可保持不变(仅改数据源) |
### 三、修复方案
#### 修改1后端 Mapper XML — 改用执行时间
**文件**: `healthlink-his-server/healthlink-his-application/src/main/resources/mapper/inhospitalnursestation/MedicineSummaryAppMapper.xml`
**改动A**resultMap collection 映射,将 `dispenseTime` 的数据源从 `planned_dispense_time` 改为 `occurrence_time`(来自 `cli_procedure` 表):
```xml
<!-- 第36-41行collection 改为: -->
<collection property="medicineSummaryParamList" ofType="com.healthlink.his.web.inhospitalnursestation.dto.MedicineSummaryParam">
<result property="procedureId" column="procedure_id"/>
<result property="dispenseId" column="dispense_id"/>
<result property="dispenseTime" column="execution_time"/>
<result property="dispenseStatus" column="dispense_status"/>
</collection>
```
**改动B**:内层 SQL 增加 `cli_procedure` JOIN 并选取 `occurrence_time`
在内层 SELECT约第98行 `mmd.status_enum AS dispense_status` 之后)添加:
```sql
cp.occurrence_time AS execution_time,
```
在 FROM 子句中 `med_medication_dispense AS mmd` 之后添加 LEFT JOIN
```sql
LEFT JOIN cli_procedure cp
ON cp.id = mmd.procedure_id
AND cp.delete_flag = '0'
```
**改动C**:外层 SELECT第78行`ii.planned_dispense_time` 改为 `ii.execution_time`
```sql
ii.execution_time,
```
**改动D**ORDER BY第203行`mmd.planned_dispense_time` 改为 `cp.occurrence_time`
```sql
ORDER BY cp.occurrence_time )) AS ii
```
#### 修改2前端列名 — "领药时间" → "执行时间"
**文件**: `healthlink-his-ui/src/views/inpatientNurse/drugDistribution/components/prescriptionList.vue`
第164行
```vue
<!-- 修改前 -->
<vxe-column title="领药时间" field="times">
<!-- 修改后 -->
<vxe-column title="执行时间" field="times">
```
#### 无需修改的部分
- `MedicineSummaryParam.java` — 字段名 `dispenseTime` 保持不变,仅后端数据源改变
- `MedicineSummaryAppServiceImpl.java` — 无变化(`medicineSummary()` 方法提交时自行用 `new Date()` 覆盖)
- 前端时间格式化逻辑 — `item.dispenseTime?.substring(5, 10)` 从 "yyyy-MM-dd HH:mm:ss" 截取 "MM-dd",格式兼容
### 四、路由决策
**FIXER: guanyu后端开发**
**REASON**: 修复核心在 Mapper XML SQL 改写JOIN `cli_procedure``occurrence_time` 替代 `planned_dispense_time`),属于后端数据查询层改动。前端仅改一个列名文本,可一并处理但主要工作量在后端 SQL。
---
## 路由决策
- **修复 Agent**: guanyu
- **原因**: LLM 分析决策

102
MD/bugs/BUG_762_ANALYSIS.md Normal file
View File

@@ -0,0 +1,102 @@
# Bug #762 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 16:21:51
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 762
- **标题**: [住院护士站-领药汇总]“汇总领药”界面的筛选标签文字丢失且功能失效
- **模块**: 病区护士工作站
- **提出人**: 陈显精
---
Now I have the complete picture. Here's my analysis:
---
### 一、Bug 理解
住院护士站的「汇总领药」页面(`drugDistribution/index.vue`)顶部筛选栏有 4 个按钮——"西药""中药""明细""汇总",但目前这 4 个按钮**仅显示空白**,用户看不到文字标签,也无法通过点击"西药/中药"进行药物类别筛选。期望按钮正常显示中文标签,且点击可触发对应的数据筛选。
### 二、根因分析
**根因完全在前端**`healthlink-his-ui/src/views/inpatientNurse/drugDistribution/index.vue` 存在两个问题:
**问题1`el-radio-button` 缺少标签文字**
```html
<!-- 第50~64行当前代码 -->
<el-radio-group v-model="drugType" class="ml10">
<el-radio-button value="1" /> <!-- ← 缺少标签文字 -->
<el-radio-button value="2" /> <!-- ← 缺少标签文字 -->
</el-radio-group>
<el-radio-group v-model="isDetails" class="ml20" @change="handleRadioChange">
<el-radio-button value="1" /> <!-- ← 缺少标签文字 -->
<el-radio-button value="2" /> <!-- ← 缺少标签文字 -->
</el-radio-group>
```
Element Plus 的 `el-radio-button` 需要通过默认 slot 或 `label` 属性显示文字,仅设 `value` 不会渲染任何可见文本。
**问题2`drugType` 未传递给子组件,筛选功能失效**
- `drugType``index.vue:178` 定义(`ref('1')`),但 `PrescriptionList``SummaryMedicineList` 组件在模板中**未接收此 prop**
```html
<PrescriptionList v-if="isDetails == 1" ref="prescriptionRefs"
:exe-status="exeStatus" :request-status="requestStatus"
:deadline="deadline" :therapy-enum="therapyEnum" />
<!-- 缺少 :drug-type="drugType" -->
```
- `prescriptionList.vue` 和 `summaryMedicineList.vue` 的 `defineProps` 中均未定义 `drugType`
- API 调用时也未传 `tcmFlag` 参数
**后端已具备筛选能力**`DispenseFormSearchParam` 已有 `tcmFlag` 字段第34行`HisQueryUtils.buildQueryWrapper` 会自动生成 WHERE 条件Mapper XML 使用 `${ew.customSqlSegment}` 动态拼接。前端只需传参即可。
### 三、修复方案
纯前端修复,涉及 3 个文件,后端无需改动。
**文件1`healthlink-his-ui/src/views/inpatientNurse/drugDistribution/index.vue`**
| 修改点 | 内容 |
|--------|------|
| 第53行 `el-radio-button value="1"` | 改为 `<el-radio-button value="1">西药</el-radio-button>` |
| 第56行 `el-radio-button value="2"` | 改为 `<el-radio-button value="2">中药</el-radio-button>` |
| 第62行 `el-radio-button value="1"` | 改为 `<el-radio-button value="1">明细</el-radio-button>` |
| 第65行 `el-radio-button value="2"` | 改为 `<el-radio-button value="2">汇总</el-radio-button>` |
| `PrescriptionList` 组件标签 | 添加 `:drug-type="drugType"` prop |
| `SummaryMedicineList` 组件标签 | 添加 `:drug-type="drugType"` prop |
**文件2`healthlink-his-ui/src/views/inpatientNurse/drugDistribution/components/prescriptionList.vue`**
| 修改点 | 内容 |
|--------|------|
| `defineProps` 块第259行 | 新增 `drugType: { type: String, default: '1' }` |
| `handleGetPrescription` 函数第281行 | API 调用参数中添加 `tcmFlag: props.drugType === '1' ? 0 : 1`(西药=0中药=1 |
**文件3`healthlink-his-ui/src/views/inpatientNurse/drugDistribution/components/summaryMedicineList.vue`**
| 修改点 | 内容 |
|--------|------|
| `defineProps` 块第168行 | 新增 `drugType: { type: String, default: '1' }` |
| `handleGetPrescription` 函数第186行 | API 调用参数中添加 `tcmFlag: props.drugType === '1' ? 0 : 1` |
**验证方式:**
1. `cd healthlink-his-ui && npm run build:dev` 编译通过
2. `npm run lint` 无报错
3. 手动验证进入汇总领药页4 个按钮显示"西药""中药""明细""汇总"文字;点击切换正常
### 四、路由决策
**FIXER: zhaoyun**(前端开发)
**REASON:** 此 Bug 纯前端问题,涉及 3 个 Vue 组件的模板和 props 修复,属于前端 UI/交互层面,无需后端改动,最适合由赵云处理。
---
## 路由决策
- **修复 Agent**: zhaoyun
- **原因**: ** 此 Bug 纯前端问题,涉及 3 个 Vue 组件的模板和 props 修复,属于前端 UI/交互层面,无需后端改动,最适合由赵云处理。

View File

@@ -0,0 +1,90 @@
# Bug #763 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 16:18:00
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 763
- **标题**: [住院护士站-医嘱校对]医生工作站已签发的临时医嘱在护士站“医嘱校对”中无法查询
- **模块**: 病区护士工作站
- **提出人**: 陈显精
---
This confirms `effectiveDoseEnd` is only set in outpatient prescriptions, NOT in inpatient order signing. Now I have the complete root cause.
---
### 一、Bug 理解
护士在"医嘱校对"界面选择患者并点击查询时,医生已签发的临时医嘱(药品类,如注射用头孢)无法显示,列表为空。期望行为:已签发的临时医嘱应出现在"未校对"列表中,供护士校对。
### 二、根因分析
**根因Bug #665 修复引入的 `deadline` 过滤条件未处理 `NULL` 值。**
完整数据链路追踪:
1. **医生签发临时医嘱**`AdviceManageAppServiceImpl.handMedication()``tempMedicationRequest.setStatusEnum(ACTIVE=2)` → 调用 `saveOrUpdate()` → 写入 `med_medication_request`
2. **关键遗漏**`effectiveDoseEnd`(服药结束时间)在整个签发流程中**从未被设置**`setEffectiveDoseEnd` 仅在门诊处方 `DoctorStationElepPrescriptionServiceImpl` 中调用,住院医嘱路径不涉及),因此数据库中 `effective_dose_end = NULL`
3. **护士站查询** → 前端 `prescriptionList.vue` 默认发送 `deadline = "2026-06-12 23:59:59"` → 后端 `AdviceProcessAppServiceImpl.getInpatientAdvicePage()` 拼接条件:
```java
queryWrapper.le("end_time", deadlineTime); // Bug #665 引入
```
生成 SQL`end_time <= '2026-06-12 23:59:59'`
4. **NULL 比较失败**PostgreSQL 中 `NULL <= anything` 结果为 `NULL`(等价于 `FALSE`WHERE 子句排除该行 → 查询结果为空
**涉及文件:**
- `AdviceProcessAppServiceImpl.java`(第 235-243 行)— deadline 条件拼接
- `AdviceProcessAppMapper.xml` — UNION 查询,`T1.effective_dose_end AS end_time`
### 三、修复方案
**修改文件**`healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/inhospitalnursestation/appservice/impl/AdviceProcessAppServiceImpl.java`
**修改内容**:将 deadline 过滤条件从 `le` 改为 NULL 安全的写法:
```java
// 修改前Bug #665 代码):
if (deadline != null && !deadline.isEmpty()) {
try {
LocalDateTime deadlineTime = LocalDateTime.parse(deadline,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
queryWrapper.le("end_time", deadlineTime);
} catch (DateTimeParseException e) {
// 忽略无效的日期格式
}
}
// 修改后:
if (deadline != null && !deadline.isEmpty()) {
try {
LocalDateTime deadlineTime = LocalDateTime.parse(deadline,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
queryWrapper.and(w -> w.le("end_time", deadlineTime).or().isNull("end_time"));
} catch (DateTimeParseException e) {
// 忽略无效的日期格式
}
}
```
**原理**`end_time <= deadline OR end_time IS NULL`,确保 `effective_dose_end` 为 NULL 的新签发医嘱也能被查询到。
**验证**
1. `mvn clean compile -DskipTests` → 编译通过
2. 签发临时医嘱 → 护士站查询 → 应能查到
3. 长期医嘱、有截止时间的医嘱 → deadline 筛选仍然生效
### 四、路由决策
**FIXER**: guanyu
**REASON**: 修复范围仅涉及后端 Java Service 层一处 SQL 条件修改,属于纯后端逻辑修复,由后端开发关羽执行。
---
## 路由决策
- **修复 Agent**: guanyu
- **原因**: LLM 分析决策

121
MD/bugs/BUG_764_ANALYSIS.md Normal file
View File

@@ -0,0 +1,121 @@
# Bug #764 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 16:05:34
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 764
- **标题**: [门诊医生工作站-检验] 已维护诊疗项目的检验项目“肾功能5项”在开立保存时报错“未找到对应的诊疗定义”
- **模块**: 门诊医生工作站
- **提出人**: 陈显精
---
Now I have a complete understanding of the bug. Let me output the analysis.
---
### 一、Bug 理解
用户在门诊医生工作站选择检验项目"肾功能5项"并点击保存时,系统报错"未找到检验项目 '肾功能5项' 对应的诊疗定义"。该检验项目已在后台正确维护,期望保存成功并自动联动生成门诊医嘱记录。
### 二、根因分析
**根因**`DoctorStationLabApplyServiceImpl.java` 的检验申请保存方法包含两个阶段:
1. **阶段一(保存申请单明细)**:正确使用 `labActivityDefinitionService.getById(activityId)``lab_activity_definition` 表查询检验项目定义第182行
2. **阶段二(自动创建门诊医嘱)****错误地**调用 `activityDefinitionService.getAppointActivityDefinitionId(itemName)`,该方法按名称精确匹配 `wor_activity_definition`SQL: `WHERE NAME = #{activityName}`)。
**关键问题**:检验项目(如"肾功能5项")存储在独立的 `lab_activity_definition` 表中,**不存在于** `wor_activity_definition` 表中。按名称查询 `wor_activity_definition` 必然返回 null触发 RuntimeException。
**对比参考**`ExamApplyController`(检查申请)已正确处理类似情况——使用 `activityId = 0L` 占位,不依赖 `wor_activity_definition`第224行注释"检查申请不走诊疗定义设置为0占位")。
**涉及文件**
- `DoctorStationLabApplyServiceImpl.java:254-259` — 查询逻辑错误(根因)
- `ActivityDefinitionMapper.xml:7-13` — SQL 查询 `wor_activity_definition` by name
- `ActivityDefinitionServiceImpl.java:76-77``getAppointActivityDefinitionId` 方法
### 三、修复方案
修改 `DoctorStationLabApplyServiceImpl.java` 中阶段二(门诊医嘱创建循环),将从 `wor_activity_definition` 按名称查询改为使用 `lab_activity_definition``activityId` 直接查询:
**修改文件**`healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/doctorstation/appservice/impl/DoctorStationLabApplyServiceImpl.java`
**具体改动**
将第254-370行附近的代码从
```java
// 1. 根据检验项目名称查询诊疗定义(检验项目)
String itemName = labApplyItemDto.getItemName();
Long activityDefinitionId = activityDefinitionService.getAppointActivityDefinitionId(itemName);
if (activityDefinitionId == null) {
throw new RuntimeException("未找到检验项目 '" + itemName + "' 对应的诊疗定义");
}
// 2. 获取诊疗定义详情
ActivityDefinition activityDefinition = activityDefinitionService.getById(activityDefinitionId);
if (activityDefinition == null) {
throw new RuntimeException("诊疗定义不存在");
}
...
adviceSaveDto.setAdviceDefinitionId(activityDefinitionId);
adviceSaveDto.setDefinitionId(activityDefinitionId);
adviceSaveDto.setCategoryCode(activityDefinition.getCategoryCode());
adviceSaveDto.setActivityId(activityDefinitionId);
adviceSaveDto.setAdviceTableName("wor_activity_definition");
...
adviceSaveDto.setUnitCode(activityDefinition.getPermittedUnitCode());
Long feePackageId = activityDefinition.getFeePackageId();
```
改为:
```java
// 1. 获取检验项目定义(从 lab_activity_definition 表,不走 wor_activity_definition
String itemName = labApplyItemDto.getItemName();
Long labActivityId = labApplyItemDto.getActivityId();
if (labActivityId == null) {
throw new RuntimeException("检验项目 '" + itemName + "' 未传入 activityId请重新选择检验项目");
}
LabActivityDefinition labActivityDef = labActivityDefinitionService.getById(labActivityId);
if (labActivityDef == null) {
throw new RuntimeException("检验项目定义不存在activityId=" + labActivityId);
}
...
// 医嘱定义 ID使用 lab_activity_definition 的 ID
adviceSaveDto.setAdviceDefinitionId(labActivityId);
// 费用定价 ID检验项目不走 wor_activity_definition 定价体系,用 0 占位(与 ExamApplyController 一致)
adviceSaveDto.setDefinitionId(0L);
adviceSaveDto.setCategoryCode(labActivityDef.getCategoryCode());
adviceSaveDto.setActivityId(labActivityId);
adviceSaveDto.setAdviceTableName("lab_activity_definition");
...
adviceSaveDto.setUnitCode(labActivityDef.getPermittedUnitCode());
Long feePackageId = labActivityDef.getFeePackageId();
```
**核心改动点**
| 字段 | 原值 | 新值 | 原因 |
|------|------|------|------|
| 数据源 | `wor_activity_definition` (by name) | `lab_activity_definition` (by ID) | 检验项目独立存储 |
| `adviceDefinitionId` | `wor_activity_definition.id` | `lab_activity_definition.id` | 正确引用产品表 |
| `definitionId` | `wor_activity_definition.id` | `0L` 占位 | 检验项目不走诊疗定价体系 |
| `adviceTableName` | `"wor_activity_definition"` | `"lab_activity_definition"` | 正确指向产品来源表 |
| `categoryCode` | `ActivityDefinition.categoryCode` | `LabActivityDefinition.categoryCode` | 同名字段,来源切换 |
| `unitCode` | `ActivityDefinition.permittedUnitCode` | `LabActivityDefinition.permittedUnitCode` | 同名字段,来源切换 |
| `feePackageId` | `ActivityDefinition.feePackageId` | `LabActivityDefinition.feePackageId` | 同名字段,来源切换 |
### 四、路由决策
**FIXER**: `guanyu`(后端开发)
**REASON**: 此 Bug 仅涉及后端 Java 代码修改(`DoctorStationLabApplyServiceImpl.java` 一个文件),核心是修正检验项目的数据源查询逻辑——从 `wor_activity_definition` 切换到 `lab_activity_definition`。不需要前端改动(前端已正确传递 `activityId`),不需要数据库变更。属于后端 Service 层逻辑修复,由关羽执行最合适。
---
## 路由决策
- **修复 Agent**: guanyu
- **原因**: LLM 分析决策

View File

@@ -0,0 +1,74 @@
# Bug #765 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-12 15:58:59
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **Bug #**: 765
- **标题**: [收费工作站-门诊挂号] 在“患者身份信息”框中选中检索的患者后,患者列表浮窗未自动隐藏
- **模块**: 建档挂号管理
- **提出人**: 陈显精
---
Now I have the complete picture. Here's my analysis:
---
### 一、Bug 理解
在门诊挂号页面的"患者身份信息"输入框中,用户输入关键字后弹出患者列表浮窗(`el-popover`),点击选中某行患者后,表单信息已正确回填,但浮窗没有自动关闭,继续遮挡下方录入项。用户期望选中后浮窗自动收起。
### 二、根因分析
问题出在 `outpatientregistration/index.vue` 中的 **popover 可见性控制逻辑**
**关键代码链路:**
| 位置 | 代码 | 作用 |
|------|------|------|
| `index.vue:118` | `:visible="showPopover"` | popover 显隐由 `showPopover` 控制 |
| `index.vue:125` | `@mousedown.prevent` | 阻止 mousedown 冒泡到 input防止点击列表时 input 失焦 |
| `index.vue:1990` | `handleFocus() → showPopover = true` | input 获焦显示浮窗 |
| `index.vue:1996` | `handleBlur() → showPopover = false` | input 失焦隐藏浮窗 |
| `index.vue:2228` | `selsectPatient(row)`**只回填表单,未关闭浮窗** | ⬅️ 根因 |
**根因**`selsectPatient` 函数只做了 `Object.assign(form.value, row)` 等字段回填,**没有设置 `showPopover.value = false`**。由于 `@mousedown.prevent` 阻止了点击列表时 input 的 blur 事件,`handleBlur` 不会被触发,浮窗就一直保持显示。
### 三、修复方案
**修改文件**`healthlink-his-ui/src/views/charge/outpatientregistration/index.vue`
**修改内容**:在 `selsectPatient` 函数末尾添加一行,关闭 popover
```javascript
function selsectPatient(row) {
Object.assign(form.value, row);
form.value.patientId = row.id;
form.value.searchKey = row.name;
form.value.name = row.name;
form.value.idCard = row.idCard;
form.value.genderEnum_enumText = row.genderEnum_enumText;
form.value.phone = row.phone;
form.value.firstEnum_enumText = row.firstEnum_enumText;
form.value.age = row.age;
form.value.identifierNo = row.identifierNo;
showPopover.value = false; // ← 新增:选中患者后关闭浮窗
}
```
**影响范围**:仅一处,不涉及后端、不涉及数据库、不涉及其他组件。
### 四、路由决策
**FIXER**: `zhaoyun`(前端开发)
**REASON**: 纯前端 Vue 组件修复,只需在 `index.vue``selsectPatient` 函数中增加一行 `showPopover.value = false;`,属于 Element Plus popover 交互问题,赵云负责前端界面修复。
---
## 路由决策
- **修复 Agent**: guanyu
- **原因**: LLM 分析决策

View File

@@ -0,0 +1,43 @@
# 诊疗目录 SysDictData 反序列化错误 诸葛亮分析报告
> **文档类型**: Bug分析
> **分析时间**: 2026-06-11
> **分析模型**: mimo-v2.5 (LLM深度分析)
---
## 基本信息
- **标题**: 诊疗目录 /system/catalog/diagnosistreatment 进入报错
- **错误**: Cannot deserialize value of type SysDictData from Array value
- **模块**: 诊疗目录管理
---
## 根因分析
**根本原因**: commit `68cfa4882` 将 Jackson 配置从 `Jackson2ObjectMapperBuilderCustomizer` 改为直接定义 `ObjectMapper` bean。
直接定义 `ObjectMapper` bean 会导致 Spring Boot 的 Jackson 自动配置完全失效:
1. Spring Boot 默认的 Jackson 模块(如 `jackson-datatype-jdk8``jackson-datatype-jsr310`)不会自动注册
2. 默认的序列化/反序列化特性设置丢失
3. `ObjectMapper` 的默认可见性设置可能不同
`DictAspect` 处理 `@Dict` 注解的 DTO 时Jackson 在序列化/反序列化过程中遇到 `SysDictData` 类型,由于缺少正确的模块配置,无法正确处理嵌套的数组/对象结构。
## 修复方案
`ApplicationConfig.java` 中的 `ObjectMapper` bean 改回 `Jackson2ObjectMapperBuilderCustomizer`,让 Spring Boot 自动配置保持生效。
### 修改文件
- `core-framework/src/main/java/com/core/framework/config/ApplicationConfig.java`
### 修改内容
- `public ObjectMapper objectMapper()``public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()`
- 移除手动创建的 `ObjectMapper` 实例
- 使用 builder 模式配置,保留 Spring Boot 默认设置
---
## 路由决策
- **修复 Agent**: guanyu (后端)
- **原因**: Jackson 配置问题,纯后端修改

View File

@@ -115,10 +115,6 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- éÜÿéââ¬Â¡Ã…’JSONèçãæžÐå™è -->
<!-- ioåøøçââ¬ÂÃ¨Ã¥Ã·Ã¥Ã¥ââ¬Â¦Ã·Ã§Ã±Ã» -->

View File

@@ -1,6 +1,7 @@
package com.core.common.core.redis;
import com.core.common.exception.UtilException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
@@ -18,6 +19,7 @@ import java.util.concurrent.TimeUnit;
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@Slf4j
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
@@ -94,8 +96,20 @@ public class RedisCache {
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
try {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
} catch (Exception e) {
log.error("Redis获取对象异常, key: {}, 错误信息: {}", key, e.getMessage());
// 如果发生序列化等异常,可能是旧版本数据或损坏数据,直接删除以防止程序崩溃
try {
redisTemplate.delete(key);
log.info("已自动清理损坏的缓存Key: {}", key);
} catch (Exception ex) {
log.error("自动清理损坏缓存失败, key: {}", key, ex);
}
return null;
}
}
/**

View File

@@ -1,5 +1,6 @@
package com.core.common.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.core.common.constant.CacheConstants;
@@ -38,11 +39,24 @@ public class DictUtils {
* @return dictDatas 字典数据列表
*/
public static List<SysDictData> getDictCache(String key) {
JsonNode arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(arrayCache)) {
return new ObjectMapper().convertValue(arrayCache, new com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>>() {});
Object cached = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNull(cached)) {
return null;
}
return null;
// 如果已经是目标类型,直接返回
if (cached instanceof List && ((List<?>) cached).stream().allMatch(e -> e instanceof SysDictData)) {
@SuppressWarnings("unchecked")
List<SysDictData> result = (List<SysDictData>) cached;
return result;
}
com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>> typeRef =
new com.fasterxml.jackson.core.type.TypeReference<List<SysDictData>>() {};
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (cached instanceof JsonNode jsonNode) {
return mapper.convertValue(jsonNode, typeRef);
}
return mapper.convertValue(cached, typeRef);
}
/**

View File

@@ -3,17 +3,15 @@ package com.core.framework.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.jackson2.autoconfigure.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
@@ -40,19 +38,14 @@ public class ApplicationConfig {
};
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
mapper.setDateFormat(sdf);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
mapper.registerModule(javaTimeModule);
return mapper;
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return builder -> {
builder.timeZone(TimeZone.getDefault());
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class, LOCAL_DATE_TIME_DESERIALIZER);
builder.modules(javaTimeModule);
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
}
}

View File

@@ -0,0 +1,79 @@
package com.core.framework.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.StandardCharsets;
/**
* Jackson Redis序列化器 - 兼容fastjson旧格式
*
* 新数据: 纯JSON (无类型包装),调用方用 convertValue 转换
* 旧fastjson: 去除L后缀后按JSON解析
* 旧Jackson activateDefaultTyping: 解包 ["className",{data}] 后取data部分
*/
public class FastjsonCompatibleRedisSerializer implements RedisSerializer<Object> {
private static final Logger log = LoggerFactory.getLogger(FastjsonCompatibleRedisSerializer.class);
private final ObjectMapper objectMapper;
/** 全局ObjectMapper供外部调用方做 convertValue 转换 */
private static final ObjectMapper sharedMapper = createMapper();
public FastjsonCompatibleRedisSerializer() { log.info("[INIT] FastjsonCompatibleRedisSerializer loaded - plain JSON mode");
this.objectMapper = createMapper();
}
private static ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
/** 获取共享ObjectMapper供 DictUtils / TokenService 等做 convertValue */
public static ObjectMapper getSharedMapper() {
return sharedMapper;
}
@Override
public byte[] serialize(Object object) throws SerializationException {
if (object == null) {
return new byte[0];
}
try {
return objectMapper.writeValueAsBytes(object);
} catch (Exception e) {
throw new SerializationException("Redis序列化失败: " + e.getMessage(), e);
}
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String json = new String(bytes, StandardCharsets.UTF_8);
// 移除fastjson特有的Long L后缀: 123L -> 123
String cleaned = json.replaceAll("(\\d+)L", "$1");
try {
// 处理旧Jackson activateDefaultTyping格式: ["className", {data}]
if (cleaned.startsWith("[\"") && cleaned.length() > 10) {
com.fasterxml.jackson.databind.JsonNode node = objectMapper.readTree(cleaned);
if (node.isArray() && node.size() >= 2 && node.get(0).isTextual()) {
// 取data部分第2个元素忽略className
return objectMapper.treeToValue(node.get(1), Object.class);
}
}
return objectMapper.readValue(cleaned, Object.class);
} catch (Exception e) {
log.warn("Redis数据反序列化失败(已忽略): {}", e.getMessage());
return null;
}
}
}

View File

@@ -4,51 +4,24 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJacksonJsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.DatabindContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.PolymorphicTypeValidator;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private static final PolymorphicTypeValidator ALLOW_ALL = new PolymorphicTypeValidator() {
@Override
public Validity validateBaseType(DatabindContext ctxt, JavaType baseType) {
return Validity.ALLOWED;
}
@Override
public Validity validateSubClassName(DatabindContext ctxt, JavaType baseType, String subClassName) {
return Validity.ALLOWED;
}
@Override
public Validity validateSubType(DatabindContext ctxt, JavaType baseType, JavaType subType) {
return Validity.ALLOWED;
}
};
@Bean
@Primary
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
tools.jackson.databind.ObjectMapper objectMapper = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.activateDefaultTyping(ALLOW_ALL, tools.jackson.databind.DefaultTyping.NON_FINAL)
.build();
GenericJacksonJsonRedisSerializer serializer = new GenericJacksonJsonRedisSerializer(objectMapper);
FastjsonCompatibleRedisSerializer serializer = new FastjsonCompatibleRedisSerializer();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
@@ -68,16 +41,18 @@ public class RedisConfig extends CachingConfigurerSupport {
return redisScript;
}
@Bean
public ValueOperations<Object, Object> valueOperations(RedisTemplate<Object, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
private String limitScriptText() {
return "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local time = tonumber(ARGV[2])\n"
+ "local current = redis.call('get', key);\n" + "if current and tonumber(current) > count then\n"
+ " return tonumber(current);\n" + "end\n" + "current = redis.call('incr', key)\n"
+ "if tonumber(current) == 1 then\n" + " redis.call('expire', key, time)\n" + "end\n"
+ "return tonumber(current);";
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local ttl = tonumber(ARGV[2])\n" +
"local current = redis.call('get', KEYS[1]);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', KEYS[1]);\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', KEYS[1], ttl);\n" +
"end\n" +
"return tonumber(current);\n";
}
}

View File

@@ -63,8 +63,28 @@ public class TokenService {
// 解析对应的权限以及用户信息
String uuid = (String)claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
return user;
Object cached = redisCache.getCacheObject(userKey);
if (cached instanceof LoginUser) {
return (LoginUser) cached;
}
// 兼容旧Jackson activateDefaultTyping格式: ["className",{data}]
if (cached instanceof java.util.List<?> list && list.size() >= 2 && list.get(0) instanceof String) {
Object data = list.get(1);
if (data instanceof java.util.Map) {
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper.convertValue(data, LoginUser.class);
}
}
// 兼容纯JSON格式: LinkedHashMap -> LoginUser
if (cached instanceof java.util.Map) {
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper.convertValue(cached, LoginUser.class);
}
return null;
} catch (Exception e) {
log.error("获取用户信息异常'{}'", e.getMessage());
}

View File

@@ -28,7 +28,7 @@ public class AntibioticAppServiceImpl implements IAntibioticAppService {
}
@Override
public AntibioticApproval requestApproval(AntibioticApproval a) {
a.setStatus("PENDING"); a.setDelFlag("0"); approvalService.save(a); return a;
a.setStatus("PENDING"); a.setDeleteFlag("0"); approvalService.save(a); return a;
}
@Override
public void approve(Long id, Long approverId, String approverName, String result) {

View File

@@ -153,7 +153,7 @@ public class TicketAppServiceImpl implements ITicketAppService {
dto.setIdCard(raw.getIdCard());
dto.setDoctorId(raw.getDoctorId());
dto.setDepartmentId(raw.getDepartmentId());
dto.setRealPatientId(raw.getPatientId());
dto.setRealPatientId(raw.getRealPatientId() != null ? raw.getRealPatientId() : raw.getPatientId());
dto.setOrderId(raw.getOrderId());
dto.setOrderNo(raw.getOrderNo());

View File

@@ -515,29 +515,28 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
// 构建查询条件
QueryWrapper<CurrentDayEncounterDto> queryWrapper = HisQueryUtils.buildQueryWrapper(null, searchKey,
new HashSet<>(Arrays.asList("patient_name", "organization_name", "practitioner_name", "healthcare_name", "identifier_no")),
request);
null); // registerTimeSTime/ETime 已下推到 SQL 内层 WHERE跳过 buildQueryWrapper 的自动 *STime/*ETime 处理
// 手动处理 statusEnum 参数(用于过滤退号记录
// 提取statusEnum参数下推到内层WHERE避免外层重复过滤
Integer statusFilter = null;
String statusEnumParam = request.getParameter("statusEnum");
if (statusEnumParam != null && !statusEnumParam.isEmpty()) {
try {
Integer statusEnum = Integer.parseInt(statusEnumParam);
if (statusEnum == -1) {
// -1 表示排除退号记录(正常挂号)
queryWrapper.ne("status_enum", 6);
} else {
// 其他值表示精确匹配
queryWrapper.eq("status_enum", statusEnum);
}
statusFilter = Integer.parseInt(statusEnumParam);
} catch (NumberFormatException e) {
// 忽略无效的参数值
}
}
// 提取日期范围参数下推到内层WHERE以优化性能避免全表JOIN后再过滤
String registerTimeSTime = request.getParameter("registerTimeSTime");
String registerTimeETime = request.getParameter("registerTimeETime");
IPage<CurrentDayEncounterDto> currentDayEncounter = outpatientRegistrationAppMapper.getCurrentDayEncounter(
new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(),
ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper,
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue());
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue(),
registerTimeSTime, registerTimeETime, statusFilter);
// 过滤候选池排除列表
// 仅当调用方显式传 excludeFromCandidatePool=true 时才过滤,避免非分诊场景(挂号/收费)

View File

@@ -54,7 +54,10 @@ public interface OutpatientRegistrationAppMapper {
@Param("classEnum") Integer classEnum, @Param("statusEnum") Integer statusEnum,
@Param("participantType1") String participantType1, @Param("participantType2") String participantType2,
@Param(Constants.WRAPPER) QueryWrapper<CurrentDayEncounterDto> queryWrapper,
@Param("register") Integer register, @Param("paymentStatus") Integer paymentStatus);
@Param("register") Integer register, @Param("paymentStatus") Integer paymentStatus,
@Param("registerTimeSTime") String registerTimeSTime,
@Param("registerTimeETime") String registerTimeETime,
@Param("statusFilter") Integer statusFilter);
/**
* 查询item绑定的信息(耗材或诊疗)

View File

@@ -30,13 +30,17 @@ public class ClinicalPathwayController {
}
@PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> completePathway(@PathVariable Long id) {
ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在");
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
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("执行记录不存在");
e.setStatus("COMPLETED"); e.setCompleteDate(java.time.LocalDate.now());
executionService.updateById(e); return R.ok();
}
@PutMapping("/vary/{id}") @Transactional(rollbackFor=Exception.class)
public R<?> varyPathway(@PathVariable Long id, @RequestParam("reason") String reason) {
ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在");
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
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("执行记录不存在");
e.setStatus("VARIATION"); e.setVariationReason(reason); executionService.updateById(e); return R.ok();
}
@GetMapping("/stats")
@@ -44,11 +48,12 @@ public class ClinicalPathwayController {
Map<String, Object> stats = new HashMap<>();
stats.put("totalPathways", pathwayService.count());
stats.put("totalExecutions", executionService.count());
LambdaQueryWrapper<ClinicalPathwayExecution> cw = new LambdaQueryWrapper<>();
cw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED");
stats.put("completedExecutions", executionService.count(cw));
cw.eq(ClinicalPathwayExecution::getStatus, "VARIATION");
stats.put("variedExecutions", executionService.count(cw));
LambdaQueryWrapper<ClinicalPathwayExecution> ccw = new LambdaQueryWrapper<>();
ccw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED");
stats.put("completedExecutions", executionService.count(ccw));
LambdaQueryWrapper<ClinicalPathwayExecution> vcw = new LambdaQueryWrapper<>();
vcw.eq(ClinicalPathwayExecution::getStatus, "VARIATION");
stats.put("variedExecutions", executionService.count(vcw));
long total = executionService.count();
long completed = stats.containsKey("completedExecutions") ? (long) stats.get("completedExecutions") : 0;
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);

View File

@@ -666,7 +666,7 @@ public class CommonServiceImpl implements ICommonService {
.eq(DocInventoryItemStatic::getDeleteFlag, DelFlag.NO.getCode())
.eq(DocInventoryItemStatic::getTenantId, SecurityUtils.getLoginUser().getTenantId()));
if (docInventoryItemStaticList.isEmpty()) {
return null;
return R.ok(Collections.emptyList());
}
// 直接去重并按BusNo倒序排序
List<String> busNoList = docInventoryItemStaticList.stream().map(DocInventoryItemStatic::getBusNo).distinct() // 去重

View File

@@ -451,10 +451,12 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 删除费用项
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.MED_MEDICATION_REQUEST,
adviceSaveDto.getRequestId());
// 删除代煎费
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getPrescriptionNo, adviceSaveDto.getPrescriptionNo())
.eq(ChargeItem::getProductId, sufferingDefinitionId));
// 删除代煎费(按处方号精确清理)
if (adviceSaveDto.getPrescriptionNo() != null) {
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getPrescriptionNo, adviceSaveDto.getPrescriptionNo())
.eq(ChargeItem::getProductId, sufferingDefinitionId));
}
}
@@ -614,7 +616,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
Long encounterDiagnosisId = medicineList.get(0).getEncounterDiagnosisId();
// 中药付数
BigDecimal chineseHerbsDoseQuantity = medicineList.get(0).getChineseHerbsDoseQuantity();
// 🔧 Bug Fix #668: 收集所有处方号(不同分组可能有不同处方号)
// 收集所有处方号(不同分组可能有不同处方号)
List<String> prescriptionNos = insertOrUpdateList.stream()
.map(AdviceSaveDto::getPrescriptionNo)
.filter(Objects::nonNull)
@@ -628,12 +630,10 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
AdviceBaseDto adviceBaseDto = new AdviceBaseDto();
adviceBaseDto.setAdviceDefinitionId(sufferingDefinitionId); // 医嘱定义id
// 🔧 Bug Fix #668: 先删除所有处方号关联的中药代煎账单
if (!prescriptionNos.isEmpty()) {
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.in(ChargeItem::getPrescriptionNo, prescriptionNos)
.eq(ChargeItem::getProductId, sufferingDefinitionId));
}
// 先删除该就诊关联的所有中药代煎账单
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getEncounterId, encounterId)
.eq(ChargeItem::getProductId, sufferingDefinitionId));
// 对应的诊疗医嘱信息
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null,
@@ -642,7 +642,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
// 费用定价
AdvicePriceDto advicePriceDto = activityAdviceBaseDto.getPriceList().get(0);
if (advicePriceDto != null) {
// 🔧 Bug Fix #668: 为每个处方号分别生成代煎账单
// 为每个处方号分别生成代煎账单
for (String prescriptionNo : prescriptionNos) {
chargeItem = new ChargeItem();
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
@@ -674,12 +674,10 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
}
}
} else if (Whether.NO.getValue().equals(sufferingFlag)) {
// 🔧 Bug Fix #668: 删除所有处方号关联的中药代煎账单
if (!prescriptionNos.isEmpty()) {
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.in(ChargeItem::getPrescriptionNo, prescriptionNos)
.eq(ChargeItem::getProductId, sufferingDefinitionId));
}
// 删除该就诊关联的所有中药代煎账单
iChargeItemService.remove(new LambdaQueryWrapper<ChargeItem>()
.eq(ChargeItem::getEncounterId, encounterId)
.eq(ChargeItem::getProductId, sufferingDefinitionId));
}
// 签发时,把草稿状态的账单更新为待收费[中医]

View File

@@ -251,18 +251,16 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 遍历检验申请单明细,为每个检验项目创建对应的门诊医嘱记录
for (DoctorStationLabApplyItemDto labApplyItemDto : doctorStationLabApplyDto.getLabApplyItemList()) {
// 1. 根据检验项目名称查询诊疗定义(检验项目
// 1. Bug #764: 使用 lab_activity_definition 表的 activityId 查询检验项目定义
// 检验项目存储在 lab_activity_definition 独立表中,不走 wor_activity_definition
String itemName = labApplyItemDto.getItemName();
Long activityDefinitionId = activityDefinitionService.getAppointActivityDefinitionId(itemName);
if (activityDefinitionId == null) {
throw new RuntimeException("未找到检验项目 '" + itemName + "' 对应的诊疗定义");
Long labActivityId = labApplyItemDto.getActivityId();
if (labActivityId == null) {
throw new RuntimeException("检验项目 '" + itemName + "' 未传入 activityId请重新选择检验项目");
}
// 2. 获取诊疗定义详情
ActivityDefinition activityDefinition = activityDefinitionService.getById(activityDefinitionId);
if (activityDefinition == null) {
throw new RuntimeException("诊疗定义不存在");
LabActivityDefinition labActivityDef = labActivityDefinitionService.getById(labActivityId);
if (labActivityDef == null) {
throw new RuntimeException("检验项目 '" + itemName + "' 对应的检验项目定义不存在ID: " + labActivityId + "");
}
// 3. 根据执行科室代码获取科室 IDpositionId
@@ -297,17 +295,17 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 设置检验项目相关信息
// 医嘱定义 ID诊疗定义 ID
adviceSaveDto.setAdviceDefinitionId(activityDefinitionId);
adviceSaveDto.setAdviceDefinitionId(labActivityId);
// 费用定价主表 ID对应 adm_charge_item 表的 definition_id 字段)
adviceSaveDto.setDefinitionId(activityDefinitionId);
adviceSaveDto.setDefinitionId(labActivityId);
// 医嘱名称
adviceSaveDto.setAdviceName(itemName);
// 医嘱详细分类
adviceSaveDto.setCategoryCode(activityDefinition.getCategoryCode());
adviceSaveDto.setCategoryCode(labActivityDef.getCategoryCode());
// 活动 ID诊疗定义 ID
adviceSaveDto.setActivityId(activityDefinitionId);
adviceSaveDto.setActivityId(labActivityId);
// 医嘱定义对应表名
adviceSaveDto.setAdviceTableName("wor_activity_definition");
adviceSaveDto.setAdviceTableName("lab_activity_definition");
// 执行科室 ID
adviceSaveDto.setPositionId(positionId);
@@ -346,13 +344,13 @@ public class DoctorStationLabApplyServiceImpl implements IDoctorStationInspectio
// 请求数量
adviceSaveDto.setQuantity(labApplyItemDto.getItemQty() != null ? labApplyItemDto.getItemQty() : java.math.BigDecimal.ONE);
// 请求单位编码(使用诊疗定义的使用单位)
adviceSaveDto.setUnitCode(activityDefinition.getPermittedUnitCode());
adviceSaveDto.setUnitCode(labActivityDef.getPermittedUnitCode());
// 单价处理BugFix#CodeReview: 根据套餐ID从正确的数据源获取价格
// 套餐项目:从 inspection_basic_information 表获取 package_amount
// 普通项目:使用前端传入的 itemPrice已从诊疗项目获取
java.math.BigDecimal unitPrice;
Long feePackageId = activityDefinition.getFeePackageId();
Long feePackageId = labActivityDef.getFeePackageId();
if (feePackageId != null) {
// 套餐项目:查询套餐价格

View File

@@ -304,6 +304,9 @@ public class EmergencyController {
@PostMapping("/green-channel/activate")
@Transactional(rollbackFor = Exception.class)
public R<?> activateGreenChannel(@RequestBody EmergencyGreenChannel gc) {
if (gc.getPatientId() == null) {
return R.fail("患者ID不能为空请选择患者后再激活绿色通道");
}
gc.setActivateTime(new Date());
gc.setCreateTime(new Date());
greenChannelService.save(gc);

View File

@@ -7,6 +7,7 @@ import com.healthlink.his.web.inhospitalcharge.dto.*;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Date;
import java.util.List;
/**
@@ -30,11 +31,15 @@ public interface IInHospitalRegisterAppService {
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
* @param pageNo 当前页
* @param pageSize 每页多少条
* @param startTime 开始时间
* @param endTime 结束时间
* @param organizationId 入院科室ID
* @param request 请求
* @return 住院登记信息
*/
IPage<InHospitalRegisterQueryDto> getRegisterInfo(InHospitalRegisterQueryDto inHospitalRegisterQueryDto,
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize, HttpServletRequest request);
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize,
Date startTime, Date endTime, Long organizationId, HttpServletRequest request);
/**
* 查询患者基本信息

View File

@@ -173,7 +173,8 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
*/
@Override
public IPage<InHospitalRegisterQueryDto> getRegisterInfo(InHospitalRegisterQueryDto inHospitalRegisterQueryDto,
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize, HttpServletRequest request) {
String searchKey, String registeredFlag, Integer pageNo, Integer pageSize,
Date startTime, Date endTime, Long organizationId, HttpServletRequest request) {
Integer encounterStatus = EncounterZyStatus.TO_BE_REGISTERED.getValue(); // 待登记
// 构建查询条件
QueryWrapper<InHospitalRegisterQueryDto> queryWrapper
@@ -182,7 +183,8 @@ public class InHospitalRegisterAppServiceImpl implements IInHospitalRegisterAppS
IPage<InHospitalRegisterQueryDto> inHospitalRegisterInfo = inHospitalRegisterAppMapper
.getInHospitalRegisterInfo(new Page<>(pageNo, pageSize), EncounterClass.IMP.getValue(), encounterStatus,
registeredFlag, LocationForm.WARD.getValue(), queryWrapper);
registeredFlag, LocationForm.WARD.getValue(), startTime, endTime, organizationId,
queryWrapper);
inHospitalRegisterInfo.getRecords().forEach(e -> {
// 性别
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));

View File

@@ -10,6 +10,8 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import jakarta.servlet.http.HttpServletRequest;
/**
@@ -53,6 +55,9 @@ public class InHospitalRegisterController {
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
* @param pageNo 当前页
* @param pageSize 每页多少条
* @param startTime 开始时间
* @param endTime 结束时间
* @param organizationId 入院科室ID
* @param request 请求
* @return 住院登记信息
*/
@@ -61,9 +66,13 @@ public class InHospitalRegisterController {
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "registeredFlag") String registeredFlag,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest request) {
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "startTime", required = false) Date startTime,
@RequestParam(value = "endTime", required = false) Date endTime,
@RequestParam(value = "organizationId", required = false) Long organizationId,
HttpServletRequest request) {
return R.ok(iInHospitalRegisterAppService.getRegisterInfo(inHospitalRegisterQueryDto, searchKey, registeredFlag,
pageNo, pageSize, request));
pageNo, pageSize, startTime, endTime, organizationId, request));
}
/**

View File

@@ -100,5 +100,24 @@ public class InHospitalRegisterQueryDto {
*/
private Integer statusEnum;
}
// PLACEHOLDER_FOR_NEW_FIELDS
/**
* 开始时间(查询条件)
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 结束时间(查询条件)
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 组织ID查询条件
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long organizationId;
}

View File

@@ -11,6 +11,7 @@ import com.healthlink.his.web.inhospitalcharge.dto.InHospitalRegisterQueryDto;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
/**
@@ -27,12 +28,17 @@ public interface InHospitalRegisterAppMapper {
* @param encounterStatus 登记状态
* @param registeredFlag 已登记标识,已登记传 1 ,待登记传 0
* @param formEnum 物理位置
* @param startTime 开始时间
* @param endTime 结束时间
* @param organizationId 入院科室ID
* @param queryWrapper 查询条件
* @return 住院登记信息
*/
IPage<InHospitalRegisterQueryDto> getInHospitalRegisterInfo(@Param("page") Page<InHospitalRegisterQueryDto> page,
@Param("encounterClass") Integer encounterClass, @Param("encounterStatus") Integer encounterStatus,
@Param("registeredFlag") String registeredFlag, @Param("formEnum") Integer formEnum,
@Param("startTime") Date startTime, @Param("endTime") Date endTime,
@Param("organizationId") Long organizationId,
@Param(Constants.WRAPPER) QueryWrapper<InHospitalRegisterQueryDto> queryWrapper);
/**

View File

@@ -192,6 +192,9 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
// 提取requestStatus手动处理支持COMPLETED(3)和CHECK_VERIFIED(10)同时查询
Integer requestStatus = inpatientAdviceParam.getRequestStatus();
inpatientAdviceParam.setRequestStatus(null);
// Bug #714: 提取deadline手动处理UNION子查询列名为end_time
String deadline = inpatientAdviceParam.getDeadline();
inpatientAdviceParam.setDeadline(null);
// 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
@@ -223,6 +226,16 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
if (therapyEnum != null) {
queryWrapper.and(w -> w.eq("therapy_enum", therapyEnum).or().isNull("therapy_enum"));
}
// Bug #714: 手动拼接deadline条件按医嘱截止时间筛选
if (deadline != null && !deadline.isEmpty()) {
try {
LocalDateTime deadlineTime = LocalDateTime.parse(deadline,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
queryWrapper.le("end_time", deadlineTime);
} catch (DateTimeParseException e) {
// 忽略无效的日期格式
}
}
// 患者医嘱分页列表
Page<InpatientAdviceDto> inpatientAdvicePage
= adviceProcessAppMapper.selectInpatientAdvicePage(new Page<>(pageNo, pageSize), queryWrapper,
@@ -642,6 +655,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
* @return 操作结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> adviceCancel(AdviceExecuteParam adviceExecuteParam) {
// 获取当前执行时间
Date exeDate = DateUtils.getNowDate();
@@ -688,6 +702,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
longMedDispensedList.add(medicationDispense);
} else if (DispenseStatus.EXECUTED.getValue().equals(medicationDispense.getStatusEnum())
|| DispenseStatus.SUBMITTED.getValue().equals(medicationDispense.getStatusEnum())) {
longMedDispensedList.add(medicationDispense);
longMedUndispenseList.add(medicationDispense);
}
}
@@ -716,6 +731,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
tempMedDispensedList.add(medicationDispense);
} else if (DispenseStatus.EXECUTED.getValue().equals(medicationDispense.getStatusEnum())
|| DispenseStatus.SUBMITTED.getValue().equals(medicationDispense.getStatusEnum())) {
tempMedDispensedList.add(medicationDispense);
tempMedUndispenseList.add(medicationDispense);
}
}
@@ -863,7 +879,7 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
if (!longMedUndispenseList.isEmpty()) {
// 排除已汇总的药品
List<MedicationDispense> medicationDispenseList
= tempMedUndispenseList.stream().filter(x -> x.getSummaryNo() == null).toList();
= longMedUndispenseList.stream().filter(x -> x.getSummaryNo() == null).toList();
medicationDispenseService
.removeByIds(medicationDispenseList.stream().map(MedicationDispense::getId).toList());
}

View File

@@ -150,10 +150,21 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
@Override
public R<?> getMedicineSummaryFormPage(DispenseFormSearchParam dispenseFormSearchParam, Integer pageNo,
Integer pageSize, String searchKey, HttpServletRequest request) {
// 就诊ID集合
String encounterIds = dispenseFormSearchParam.getEncounterIds();
dispenseFormSearchParam.setEncounterIds(null);
// 构建查询条件
QueryWrapper<DispenseFormSearchParam> queryWrapper = HisQueryUtils.buildQueryWrapper(dispenseFormSearchParam,
searchKey, new HashSet<>(List.of(CommonConstants.FieldName.BusNo)), request);
// 如果传了就诊ID过滤关联的汇总单
if (StringUtils.isNotEmpty(encounterIds)) {
queryWrapper.inSql(CommonConstants.FieldName.BusNo,
"SELECT DISTINCT summary_no FROM med_medication_dispense " +
"WHERE encounter_id IN (" + encounterIds + ") AND summary_no IS NOT NULL");
}
// 汇总单分页列表
Page<MedicineSummaryFormDto> medicineSummaryFormPage = medicineSummaryAppMapper.selectMedicineSummaryFormPage(
new Page<>(pageNo, pageSize), queryWrapper, DispenseStatus.PREPARATION.getValue(),

View File

@@ -11,5 +11,6 @@ public interface IOrderClosedLoopAppService {
void executeOrder(OrderExecuteRecord record);
void completeOrder(OrderExecuteRecord record);
void cancelOrder(OrderExecuteRecord record);
Map<String, Object> getStatistics();
Map<String, Object> getStatistics(String type, String groupBy, Integer pageNum, Integer pageSize);
void remindOrder(Map<String, Object> params);
}

View File

@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.jdbc.support.JdbcUtils;
@Service
public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService {
@Autowired private IOrderExecuteRecordService recordService;
@@ -90,20 +91,128 @@ public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService
}
}
@Autowired
private com.healthlink.his.orderclosedloop.mapper.OrderExecuteRecordMapper recordMapper;
private long getLong(java.util.Map<String, Object> map, String key) {
Object val = map.get(key);
if (val == null) {
// Try lowercase key (PostgreSQL returns lowercase column names)
val = map.get(key.toLowerCase());
}
return val instanceof Number ? ((Number) val).longValue() : 0L;
}
private double getDouble(java.util.Map<String, Object> map, String key) {
Object val = map.get(key);
if (val == null) {
val = map.get(key.toLowerCase());
}
return val instanceof Number ? ((Number) val).doubleValue() : 0.0;
}
private String getString(java.util.Map<String, Object> map, String key) {
Object val = map.get(key);
if (val == null) {
val = map.get(key.toLowerCase());
}
return val != null ? val.toString() : "";
}
@Override
public Map<String, Object> getStatistics() {
public Map<String, Object> getStatistics(String type, String groupBy, Integer pageNum, Integer pageSize) {
if ("unclosedWarnings".equals(type)) {
return getUnclosedWarnings(pageNum, pageSize);
}
if (groupBy != null && !groupBy.isEmpty()) {
return getGroupStats(groupBy);
}
return getOverviewStats();
}
private Map<String, Object> getOverviewStats() {
Map<String, Object> stats = new HashMap<>();
long total = recordService.count();
long pending = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "pending"));
long executing = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "executing"));
long completed = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "completed"));
long cancelled = recordService.count(new LambdaQueryWrapper<OrderExecuteRecord>().eq(OrderExecuteRecord::getExecuteStatus, "cancelled"));
List<Map<String, Object>> rows = recordMapper.selectOverviewByType();
String[] rateKeys = {"drugClosedRate", "labClosedRate", "examClosedRate", "treatmentClosedRate"};
String[] types = {"drug", "lab", "exam", "treatment"};
long total = 0;
for (Map<String, Object> row : rows) {
String orderType = getString(row, "orderType");
long totalOrders = getLong(row, "totalOrders");
long closedCount = getLong(row, "closedCount");
total += totalOrders;
for (int i = 0; i < types.length; i++) {
if (types[i].equals(orderType)) {
double rate = totalOrders > 0 ? Math.round(closedCount * 1000.0 / totalOrders) / 10.0 : 0;
stats.put(rateKeys[i], rate);
}
}
}
stats.put("total", total);
stats.put("pending", pending);
stats.put("executing", executing);
stats.put("completed", completed);
stats.put("cancelled", cancelled);
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);
return stats;
}
private Map<String, Object> getGroupStats(String groupBy) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> dbRows;
if ("doctor".equals(groupBy)) {
dbRows = recordMapper.selectGroupByDoctor();
} else {
dbRows = recordMapper.selectGroupByDepartment();
}
List<Map<String, Object>> records = new ArrayList<>();
for (Map<String, Object> row : dbRows) {
Map<String, Object> item = new LinkedHashMap<>(row);
long totalOrders = getLong(row, "totalOrders");
long closedCount = getLong(row, "closedCount");
item.put("unclosedCount", totalOrders - closedCount);
item.put("closedRate", totalOrders > 0 ? Math.round(closedCount * 1000.0 / totalOrders) / 10.0 : 0);
records.add(item);
}
result.put("records", records);
return result;
}
private Map<String, Object> getUnclosedWarnings(Integer pageNum, Integer pageSize) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> rows = recordMapper.selectUnclosedWarnings();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Map<String, Object>> warnings = new ArrayList<>();
for (Map<String, Object> row : rows) {
Map<String, Object> warning = new LinkedHashMap<>(row);
Object orderTimeObj = row.get("orderTime");
if (orderTimeObj instanceof java.sql.Timestamp) {
long hours = (System.currentTimeMillis() - ((java.sql.Timestamp) orderTimeObj).getTime()) / (1000 * 60 * 60);
if (hours > 24) {
warning.put("overdueDuration", (hours / 24) + "" + (hours % 24) + "小时");
} else {
warning.put("overdueDuration", hours + "小时");
}
warning.put("orderTime", sdf.format((java.sql.Timestamp) orderTimeObj));
} else {
warning.put("overdueDuration", "未知");
warning.put("orderTime", "");
}
warnings.add(warning);
}
result.put("records", warnings);
return result;
}
@Override
public void remindOrder(Map<String, Object> params) {
String orderNo = params.get("orderNo") != null ? params.get("orderNo").toString() : null;
if (orderNo == null || orderNo.isEmpty()) {
return;
}
LambdaQueryWrapper<OrderExecuteRecord> w = new LambdaQueryWrapper<>();
w.eq(OrderExecuteRecord::getOrderNo, orderNo);
OrderExecuteRecord record = recordService.getOne(w);
if (record != null) {
record.setUpdateBy("system");
record.setUpdateTime(new Date());
recordService.updateById(record);
}
}
}

View File

@@ -50,7 +50,17 @@ public class OrderClosedLoopController {
@Operation(summary = "统计")
@GetMapping("/statistics")
public AjaxResult statistics() {
return AjaxResult.success(appService.getStatistics());
public AjaxResult statistics(@RequestParam(required = false) String type,
@RequestParam(required = false) String groupBy,
@RequestParam(required = false) Integer pageNum,
@RequestParam(required = false) Integer pageSize) {
return AjaxResult.success(appService.getStatistics(type, groupBy, pageNum, pageSize));
}
@Operation(summary = "催办提醒")
@PostMapping("/remind")
public AjaxResult remind(@RequestBody Map<String, Object> params) {
appService.remindOrder(params);
return AjaxResult.success("催办提醒已发送");
}
}

View File

@@ -13,6 +13,7 @@ import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -227,6 +228,7 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
* @return 处理结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> medicineReturn(List<ReturnMedicineDto> medicineReturnList) {
if (medicineReturnList == null || medicineReturnList.isEmpty()) {
return R.ok();
@@ -249,6 +251,10 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
// 进销存参数
List<SupplyItemDetailDto> supplyItemDetailList = new ArrayList<>();
// 记录未发药无库存变动的发药单ID
Set<Long> noInventoryUpdateMedIds = new HashSet<>();
Set<Long> noInventoryUpdateDevIds = new HashSet<>();
// 处理退药
// 获取药品退药id列表
List<Long> medReturnIdList = new ArrayList<>();
@@ -278,6 +284,14 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
throw new ServiceException("药品已退药,请勿重复退药");
}
// 在更新退药单状态前统计出实际没有发药已发药数量为0/空)的退药单,避免后续加回库存和报错
for (MedicationDispense medicationDispense : refundMedList) {
if (medicationDispense.getDispenseQuantity() == null
|| medicationDispense.getDispenseQuantity().compareTo(BigDecimal.ZERO) == 0) {
noInventoryUpdateMedIds.add(medicationDispense.getId());
}
}
// 更新退药单
for (MedicationDispense medicationDispense : refundMedList) {
// 退药状态
@@ -291,20 +305,22 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
// 退药人
medicationDispense.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
// 设置库存变更参数
SupplyItemDetailDto supplyItemDetailDto = new SupplyItemDetailDto();
for (MedicationRequest medicationRequest : refundMedRequestList) {
// 根据退药id查询退药请求id用于医保关联
if (medicationDispense.getMedReqId().equals(medicationRequest.getId())) {
supplyItemDetailDto.setRequestId(medicationRequest.getRefundMedicineId());
// 设置库存变更参数(仅针对实际发过药的药品)
if (!noInventoryUpdateMedIds.contains(medicationDispense.getId())) {
SupplyItemDetailDto supplyItemDetailDto = new SupplyItemDetailDto();
for (MedicationRequest medicationRequest : refundMedRequestList) {
// 根据退药id查询退药请求id用于医保关联
if (medicationDispense.getMedReqId().equals(medicationRequest.getId())) {
supplyItemDetailDto.setRequestId(medicationRequest.getRefundMedicineId());
}
}
supplyItemDetailDto.setItemTable(CommonConstants.TableName.MED_MEDICATION_DEFINITION)
.setItemId(medicationDispense.getMedicationId()).setLotNumber(medicationDispense.getLotNumber());
supplyItemDetailList.add(supplyItemDetailDto);
}
supplyItemDetailDto.setItemTable(CommonConstants.TableName.MED_MEDICATION_DEFINITION)
.setItemId(medicationDispense.getMedicationId()).setLotNumber(medicationDispense.getLotNumber());
supplyItemDetailList.add(supplyItemDetailDto);
// 追溯码表相关处理
if (medicationDispense.getTraceNo() != null) {
// 追溯码表相关处理(仅针对实际发过药的药品)
if (!noInventoryUpdateMedIds.contains(medicationDispense.getId()) && medicationDispense.getTraceNo() != null) {
// 使用逗号分割追溯码并转换为List
String[] traceNoList = medicationDispense.getTraceNo().split(CommonConstants.Common.COMMA);
for (String item : traceNoList) {
@@ -345,7 +361,7 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
devReturnIdList =
returnDeviceList.stream().map(ReturnMedicineDto::getDispenseId).collect(Collectors.toList());
// 获取退耗材请求id列表
List<Long> devRequestIdList = returnDeviceList.stream().map(ReturnMedicineDto::getRequestId).toList();
List<Long> devRequestIdList = returnDeviceList.stream().map(ReturnMedicineDto::getRequestId).collect(Collectors.toList());
if (devReturnIdList.isEmpty()) {
throw new ServiceException("请选择要退的耗材");
}
@@ -362,6 +378,15 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
.anyMatch(x -> x.equals(DispenseStatus.REFUNDED.getValue()))) {
throw new ServiceException("耗材已退,请勿重复操作");
}
// 在更新退耗材单状态前统计出实际没有发放已发放数量为0/空)的退耗材单
for (DeviceDispense deviceDispense : refundDevList) {
if (deviceDispense.getDispenseQuantity() == null
|| deviceDispense.getDispenseQuantity().compareTo(BigDecimal.ZERO) == 0) {
noInventoryUpdateDevIds.add(deviceDispense.getId());
}
}
// 更新退耗材单状态
for (DeviceDispense deviceDispense : refundDevList) {
// 退药时间
@@ -370,40 +395,20 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
deviceDispense.setDispenseQuantity(deviceDispense.getQuantity());
// 退药状态
deviceDispense.setStatusEnum(DispenseStatus.REFUNDED.getValue());
// 设置库存变更参数
supplyItemDetailList
.add(new SupplyItemDetailDto().setItemTable(CommonConstants.TableName.ADM_DEVICE_DEFINITION)
.setItemId(deviceDispense.getDeviceDefId()).setLotNumber(deviceDispense.getLotNumber()));
// // 使用逗号分割追溯码并转换为List
// String[] traceNoList = deviceDispense.getTraceNo().split(CommonConstants.Common.COMMA);
// for (String item : traceNoList) {
// traceNoManage = new TraceNoManage();
// // 追溯码处理
// traceNoManage.setItemTable(CommonConstants.TableName.ADM_DEVICE_DEFINITION)
// // 项目id
// .setItemId(deviceDispense.getDeviceDefId())
// // 仓库类型
// .setLocationTypeEnum(LocationForm.PHARMACY.getValue())
// // 仓库
// .setLocationId(deviceDispense.getLocationId())
// // 产品批号
// .setLotNumber(deviceDispense.getLotNumber())
// // 追溯码
// .setTraceNo(item)
// // 追溯码状态
// .setStatusEnum(TraceNoStatus.IN.getValue())
// // 追溯码单位
// .setUnitCode(deviceDispense.getUnitCode())
// // 操作类型
// .setOperationType(SupplyType.RETURN_MEDICATION.getValue());
// traceNoManageList.add(traceNoManage);
// }
// 设置库存变更参数(仅针对实际发放过的耗材)
if (!noInventoryUpdateDevIds.contains(deviceDispense.getId())) {
supplyItemDetailList
.add(new SupplyItemDetailDto().setItemTable(CommonConstants.TableName.ADM_DEVICE_DEFINITION)
.setItemId(deviceDispense.getDeviceDefId()).setLotNumber(deviceDispense.getLotNumber()));
}
}
deviceDispenseService.updateBatchById(refundDevList);
}
// 追溯码管理表数据追加
traceNoManageService.saveBatch(traceNoManageList);
if (!traceNoManageList.isEmpty()) {
traceNoManageService.saveBatch(traceNoManageList);
}
// 处理退库存
// 获取库存信息
@@ -424,6 +429,11 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
BigDecimal minQuantity = BigDecimal.ZERO;
for (UnDispenseInventoryDto unDispenseInventoryDto : inventoryList) {
// 过滤未实际发药/发耗材的项目,其库存不加回
if (noInventoryUpdateMedIds.contains(unDispenseInventoryDto.getDispenseId())
|| noInventoryUpdateDevIds.contains(unDispenseInventoryDto.getDispenseId())) {
continue;
}
BigDecimal quantity = unDispenseInventoryDto.getQuantity();
if (!unDispenseInventoryDto.getDispenseUnit()
.equals(unDispenseInventoryDto.getInventoryUnitCode())) {
@@ -433,14 +443,19 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
}
minQuantity = minQuantity.add(quantity);
}
// 理论上不出bug的情况下以项目id批号仓库进行分组处理库存一定唯一所以get(0)
// 设置待更新的库存信息
inventoryItemList.add(new InventoryItem().setId(inventoryList.get(0).getInventoryId())
.setQuantity(inventoryList.get(0).getInventoryQuantity().add(minQuantity)));
// 只有当有需要恢复库存的药品/器材时才加回库存
if (minQuantity.compareTo(BigDecimal.ZERO) > 0) {
// 理论上不出bug的情况下以项目id批号仓库进行分组处理库存一定唯一所以get(0)
// 设置待更新的库存信息
inventoryItemList.add(new InventoryItem().setId(inventoryList.get(0).getInventoryId())
.setQuantity(inventoryList.get(0).getInventoryQuantity().add(minQuantity)));
}
}
}
// 库存更新
iInventoryItemService.updateBatchById(inventoryItemList);
if (!inventoryItemList.isEmpty()) {
iInventoryItemService.updateBatchById(inventoryItemList);
}
} else {
throw new ServiceException("请检查库存信息");
}
@@ -497,13 +512,26 @@ public class InHospitalReturnMedicineAppServiceImpl implements IInHospitalReturn
}
}
}
uploadFailedNoList = this.ybReturnIntegrated(medReturnIdList, null);
uploadFailedNoList = receiptApprovalAppService.ybInventoryIntegrated(supplyItemDetailList,
YbInvChgType.OTHER_OUT, DateUtils.getNowDate(), true);
if (uploadFailedNoList != null) {
returnMsg = "3506商品销售退货上传错误错误项目编码" + uploadFailedNoList;
} else {
returnMsg = "3506商品销售退货上传成功";
// 仅对实际发过药(生成了收费记录且计费的)调用医保退货接口
List<Long> medReturnIdsForYb = medReturnIdList.stream()
.filter(id -> !noInventoryUpdateMedIds.contains(id))
.collect(Collectors.toList());
if (!medReturnIdsForYb.isEmpty()) {
uploadFailedNoList = this.ybReturnIntegrated(medReturnIdsForYb, null);
if (uploadFailedNoList != null) {
returnMsg = "3506商品销售退货上传错误错误项目编码" + uploadFailedNoList;
} else {
returnMsg = "3506商品销售退货上传成功";
}
}
if (!supplyItemDetailList.isEmpty()) {
uploadFailedNoList = receiptApprovalAppService.ybInventoryIntegrated(supplyItemDetailList,
YbInvChgType.OTHER_OUT, DateUtils.getNowDate(), true);
if (uploadFailedNoList != null) {
returnMsg = (returnMsg != null ? returnMsg + "" : "") + "医保进销存集成上传错误: " + uploadFailedNoList;
}
}
}
// 返回退药成功信息

View File

@@ -61,7 +61,7 @@ public class RationalDrugController {
@GetMapping("/trend")
@Operation(summary = "审核趋势")
public AjaxResult getAuditTrend(@RequestParam String startDate) {
public AjaxResult getAuditTrend(@RequestParam(required = false) String startDate) {
List<Map<String, Object>> trend = rationalDrugAppService.getAuditTrend(startDate);
return AjaxResult.success(trend);
}

View File

@@ -712,6 +712,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
tempServiceRequest.setUnitCode(regAdviceSaveDto.getUnitCode()); // 请求单位编码
tempServiceRequest.setCategoryEnum(regAdviceSaveDto.getCategoryEnum()); // 请求类型
tempServiceRequest.setTherapyEnum(regAdviceSaveDto.getTherapyEnum()); // 治疗类型,临时(需要前端传)
tempServiceRequest.setRateCode(regAdviceSaveDto.getRateCode()); // 用药频次
// 文字医嘱(type=8)不走定价体系activityId设置为0L占位
if (ItemType.TEXT.getValue().equals(regAdviceSaveDto.getAdviceType())) {
tempServiceRequest.setActivityId(0L);

View File

@@ -192,7 +192,7 @@ public class RequestFormManageController {
* @return 申请单
*/
@RequestMapping(value = "/page")
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
public R<IPage<RequestFormPageDto>> getRequestFormPage(@ModelAttribute RequestFormDto requestFormDto) {
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
}

View File

@@ -0,0 +1,7 @@
-- Fix: prescription_intercept_log missing HisBaseEntity columns
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS delete_flag VARCHAR(1) DEFAULT '0';
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS create_by VARCHAR(64) DEFAULT '';
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS update_by VARCHAR(64) DEFAULT '';
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS update_time TIMESTAMP;
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS tenant_id BIGINT DEFAULT 1;

View File

@@ -0,0 +1,9 @@
-- Bug #735: 新医嘱签发后"停嘱医生"字段错误生成数据
-- 原因stopper_name 映射到 update_by 字段,签发时 MyBatis-Plus 自动填充导致错误赋值
-- 修复:添加专用 stopper_id 字段,仅在停嘱操作时设置
-- 药品请求表添加停嘱医生ID字段
ALTER TABLE med_medication_request ADD COLUMN IF NOT EXISTS stopper_id BIGINT;
-- 服务请求表添加停嘱医生ID字段
ALTER TABLE wor_service_request ADD COLUMN IF NOT EXISTS stopper_id BIGINT;

View File

@@ -0,0 +1,10 @@
-- 为 sys_audit_log 和 antibiotic_approval 添加 delete_flag 列以匹配 HisBaseEntity 默认映射
-- HisBaseEntity.deleteFlag 映射到 delete_flag (MyBatis-Plus camelCase默认)
ALTER TABLE sys_audit_log ADD COLUMN IF NOT EXISTS delete_flag CHAR(1) DEFAULT '0';
COMMENT ON COLUMN sys_audit_log.delete_flag IS '删除标识(0=正常,1=删除)';
UPDATE sys_audit_log SET delete_flag = '0' WHERE delete_flag IS NULL;
-- antibiotic_approval 表原有 del_flag 列,新增 delete_flag 列供 HisBaseEntity 使用
ALTER TABLE antibiotic_approval ADD COLUMN IF NOT EXISTS delete_flag CHAR(1) DEFAULT '0';
COMMENT ON COLUMN antibiotic_approval.delete_flag IS '删除标识(0=正常,1=删除)';
UPDATE antibiotic_approval SET delete_flag = '0' WHERE delete_flag IS NULL;

View File

@@ -0,0 +1,5 @@
-- 为 clinical_pathway_execution 添加 HisBaseEntity 所需的基础字段
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_by VARCHAR(64);
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_by VARCHAR(64);
ALTER TABLE clinical_pathway_execution ADD COLUMN IF NOT EXISTS update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;

View File

@@ -0,0 +1,4 @@
-- 为 prescription_intercept_log 添加 delete_flag 列以匹配 HisBaseEntity 默认映射
ALTER TABLE prescription_intercept_log ADD COLUMN IF NOT EXISTS delete_flag CHAR(1) DEFAULT '0';
COMMENT ON COLUMN prescription_intercept_log.delete_flag IS '删除标识(0=正常,1=删除)';
UPDATE prescription_intercept_log SET delete_flag = '0' WHERE delete_flag IS NULL;

View File

@@ -52,4 +52,5 @@ apl.payment.M00007=\u672A\u67E5\u8BE2\u5230\u6536\u8D39\u9879\u76EE
apl.payment.M00008=\u672A\u67E5\u8BE2\u5230{0}\u8D26\u6237\u4FE1\u606F
apl.payment.M00009=\u672A\u67E5\u8BE2\u5230\u6536\u8D39\u9879\u76EE\uFF0C\u4E0D\u9700\u8981\u8F6C\u6362\u8D26\u6237
apl.adjustPrice.M00001=\u6267\u884C\u5931\u8D25\uFF0C\u672A\u52A0\u8F7D\u5230\u4EFB\u4F55\u6570\u636E\uFF01
apl.adjustPrice.M00002=\u6267\u884C\u5931\u8D25\uFF0C\u6539\u4EF7\u5355\u4E2D\u6709\u6B63\u5728\u5BA1\u6838\u4E2D\u7684\u8D27\u54C1\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u63D0\u4EA4\uFF01
apl.adjustPrice.M00002=\u6267\u884C\u5931\u8D25\uFF0C\u6539\u4EF7\u5355\u4E2D\u6709\u6B63\u5728\u5BA1\u6838\u4E2D\u7684\u8D27\u54C1\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u63D0\u4EA4\uFF01
apl.yb.M00001={0} catalog does not exist, please contact administrator

View File

@@ -52,4 +52,5 @@ apl.payment.M00007=\u672A\u67E5\u8BE2\u5230\u6536\u8D39\u9879\u76EE
apl.payment.M00008=\u672A\u67E5\u8BE2\u5230{0}\u8D26\u6237\u4FE1\u606F
apl.payment.M00009=\u672A\u67E5\u8BE2\u5230\u6536\u8D39\u9879\u76EE\uFF0C\u4E0D\u9700\u8981\u8F6C\u6362\u8D26\u6237
apl.adjustPrice.M00001=\u6267\u884C\u5931\u8D25\uFF0C\u672A\u52A0\u8F7D\u5230\u4EFB\u4F55\u6570\u636E\uFF01
apl.adjustPrice.M00002=\u6267\u884C\u5931\u8D25\uFF0C\u6539\u4EF7\u5355\u4E2D\u6709\u6B63\u5728\u5BA1\u6838\u4E2D\u7684\u8D27\u54C1\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u63D0\u4EA4\uFF01
apl.adjustPrice.M00002=\u6267\u884C\u5931\u8D25\uFF0C\u6539\u4EF7\u5355\u4E2D\u6709\u6B63\u5728\u5BA1\u6838\u4E2D\u7684\u8D27\u54C1\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u65B0\u63D0\u4EA4\uFF01
apl.yb.M00001={0}\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458

View File

@@ -217,6 +217,20 @@
WHERE T1.delete_flag = '0'
AND T1.class_enum = #{classEnum}
AND T10.context_enum = #{register}
<if test='registerTimeSTime != null'>
AND T1.create_time &gt;= CAST(#{registerTimeSTime} AS TIMESTAMP)
</if>
<if test='registerTimeETime != null'>
AND T1.create_time &lt;= CAST(#{registerTimeETime} AS TIMESTAMP)
</if>
<if test='statusFilter != null'>
<if test='statusFilter &gt;= 0'>
AND T1.status_enum = #{statusFilter}
</if>
<if test='statusFilter == -1'>
AND T1.status_enum != 6
</if>
</if>
) AS T9
${ew.customSqlSegment}
ORDER BY T9.register_time DESC

View File

@@ -512,7 +512,7 @@
personal_account.balance_amount,
personal_account.id AS account_id,
T2.category_code,
ao.name AS org_name
COALESCE(ao.name, al1."name") AS org_name
FROM med_medication_request AS T1
LEFT JOIN med_medication_definition AS T2
ON T2.id = T1.medication_id

View File

@@ -36,7 +36,7 @@
<collection property="medicineSummaryParamList" ofType="com.healthlink.his.web.inhospitalnursestation.dto.MedicineSummaryParam">
<result property="procedureId" column="procedure_id"/>
<result property="dispenseId" column="dispense_id"/>
<result property="dispenseTime" column="planned_dispense_time"/>
<result property="dispenseTime" column="execution_time"/>
<result property="dispenseStatus" column="dispense_status"/>
</collection>
</resultMap>
@@ -75,7 +75,7 @@
ii.admitting_doctor_name,
ii.balance_amount,
ii.dispense_id,
ii.planned_dispense_time,
ii.execution_time,
ii.procedure_id,
ii.dispense_status
FROM (( SELECT T1.encounter_id,
@@ -113,7 +113,7 @@
pra."name" AS admitting_doctor_name,
personal_account.balance_amount,
mmd.id AS dispense_id,
mmd.planned_dispense_time,
cp.occurrence_time AS execution_time,
mmd.procedure_id,
mmd.status_enum AS dispense_status
FROM med_medication_request AS T1
@@ -121,6 +121,9 @@
ON T1.id = mmd.med_req_id
AND mmd.delete_flag = '0'
AND mmd.status_enum != #{summarized}
LEFT JOIN cli_procedure cp
ON cp.id = mmd.procedure_id
AND cp.delete_flag = '0'
LEFT JOIN med_medication_definition AS T2
ON T2.id = T1.medication_id
AND T2.delete_flag = '0'
@@ -200,7 +203,7 @@
AND T1.status_enum = #{completed}
AND T1.refund_medicine_id IS NULL
AND mmd.procedure_id IS NOT NULL
ORDER BY mmd.planned_dispense_time )) AS ii
ORDER BY cp.occurrence_time )) AS ii
${ew.customSqlSegment}
</select>
<select id="selectMedicineSummaryFormPage"

View File

@@ -2,11 +2,11 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.web.pharmacymanage.mapper.InHospitalReturnMedicineAppMapper">
<select id="selectEncounterInfoListPage" resultType="com.healthlink.his.web.pharmacymanage.dto.EncounterInfoDto">
SELECT ii.reception_time,
ii.start_time,
SELECT MAX(ii.reception_time) AS reception_time,
MAX(ii.start_time) AS start_time,
ii.encounter_id,
ii.encounter_no,
ii.refund_enum,
MAX(ii.refund_enum) AS refund_enum,
ii.patient_name,
ii.patient_wb_str,
ii.patient_py_str,
@@ -15,11 +15,11 @@
ii.birth_date,
ii.department_name
FROM (
SELECT ae.reception_time,
SELECT mmd.create_time AS reception_time,
ae.id AS encounter_id,
ae.bus_no AS encounter_no,
ae.tenant_id,
ae.start_time,
mmd.create_time AS start_time,
ae.class_enum,
mmd.status_enum AS refund_enum,
ap."name" AS patient_name,
@@ -52,11 +52,11 @@
AND mmd.status_enum = #{refundStatus}
</if>
UNION
SELECT ae.reception_time,
SELECT wdd.create_time AS reception_time,
ae.id AS encounter_id,
ae.bus_no AS encounter_no,
ae.tenant_id,
ae.start_time,
wdd.create_time AS start_time,
ae.class_enum,
wdd.status_enum AS refund_enum,
ap."name" AS patient_name,
@@ -90,7 +90,16 @@
</if>
) AS ii
${ew.customSqlSegment}
ORDER BY ii.reception_time DESC
GROUP BY ii.encounter_id,
ii.encounter_no,
ii.patient_name,
ii.patient_wb_str,
ii.patient_py_str,
ii.id_card,
ii.gender_enum,
ii.birth_date,
ii.department_name
ORDER BY reception_time DESC
</select>
<select id="selectReturnMedicineInfo"
resultType="com.healthlink.his.web.pharmacymanage.dto.ReturnMedicineInfoDto">

View File

@@ -108,8 +108,6 @@
<if test="statusEnum == 18">
T4.status_enum = #{submitted}
</if>
AND T4.summary_no IS NOT NULL
AND T4.summary_no != ''
) AS ii
${ew.customSqlSegment}
GROUP BY ii.encounter_id,
@@ -268,8 +266,6 @@
AND T15.delete_flag = '0'
WHERE T1.delete_flag = '0'
-- 因发药配药合并,前台只能看到待发药,已发药状态,但是后台配药发药状态都查
AND T1.summary_no IS NOT NULL
AND T1.summary_no != ''
AND
<if test="dispenseStatus == null">
T1.status_enum IN (#{inProgress},#{completed},#{preparation},#{prepared},#{summarized})

View File

@@ -222,8 +222,8 @@
T1.based_on_id AS based_on_id,
T1.medication_id AS advice_definition_id,
T1.content_json::jsonb ->> 'remark' AS remark,
T1.effective_dose_end AS stop_time,
T1.update_by AS stop_user_name
CASE WHEN T1.status_enum = 6 THEN T1.effective_dose_end ELSE NULL END AS stop_time,
CASE WHEN T1.status_enum = 6 THEN T1.update_by ELSE NULL END AS stop_user_name
FROM med_medication_request AS T1
LEFT JOIN adm_practitioner AS ap ON ap.id = T1.practitioner_id AND ap.delete_flag = '0'
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
@@ -339,8 +339,8 @@
T1.based_on_id AS based_on_id,
T1.activity_id AS advice_definition_id,
T1.remark AS remark,
T1.occurrence_end_time AS stop_time,
T1.update_by AS stop_user_name
CASE WHEN T1.status_enum = 6 THEN T1.occurrence_end_time ELSE NULL END AS stop_time,
CASE WHEN T1.status_enum = 6 THEN T1.update_by ELSE NULL END AS stop_user_name
FROM wor_service_request AS T1
LEFT JOIN adm_practitioner AS ap ON ap.id = T1.requester_id AND ap.delete_flag = '0'
LEFT JOIN wor_activity_definition AS T2

View File

@@ -1,16 +1,31 @@
package com.healthlink.his.antibiotic.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
@Data @TableName("antibiotic_approval") @Accessors(chain = true) @EqualsAndHashCode(callSuper = false)
@Data
@TableName("antibiotic_approval")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class AntibioticApproval extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID) private Long id;
private Long encounterId; private Long patientId;
private String drugCode; private String drugName; private String antibioticClass;
private Long requesterId; private String requesterName;
private Long approverId; private String approverName; private Date approvalTime;
private String approvalResult; private String reason; private String status; private String delFlag;
}
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long encounterId;
private Long patientId;
private String drugCode;
private String drugName;
private String antibioticClass;
private Long requesterId;
private String requesterName;
private Long approverId;
private String approverName;
private Date approvalTime;
private String approvalResult;
private String reason;
private String status;
}

View File

@@ -21,6 +21,7 @@ public class TicketSlotDTO {
private String patientName;
private String medicalCard;
private Long patientId;
private Long realPatientId;
private String phone;
private Integer orderStatus;
private Long orderId;

View File

@@ -1,5 +1,54 @@
package com.healthlink.his.orderclosedloop.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface OrderExecuteRecordMapper extends BaseMapper<OrderExecuteRecord> {}
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface OrderExecuteRecordMapper extends BaseMapper<OrderExecuteRecord> {
@Select("SELECT m.department_name FROM order_main m WHERE m.order_no = #{orderNo} AND m.delete_flag = '0' LIMIT 1")
String findDepartmentByOrderNo(@Param("orderNo") String orderNo);
@Select("SELECT e.order_type AS orderType, " +
"COUNT(*) AS totalOrders, " +
"COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " +
"FROM order_execute_record e " +
"WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " +
"GROUP BY e.order_type")
List<Map<String, Object>> selectOverviewByType();
@Select("SELECT COALESCE(m.department_name, '未知') AS department, " +
"COUNT(*) AS totalOrders, " +
"COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " +
"FROM order_execute_record e " +
"LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " +
"WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " +
"GROUP BY m.department_name ORDER BY totalOrders DESC")
List<Map<String, Object>> selectGroupByDepartment();
@Select("SELECT COALESCE(m.doctor_name, '未知') AS doctorName, " +
"COUNT(*) AS totalOrders, " +
"COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " +
"FROM order_execute_record e " +
"LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " +
"WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " +
"GROUP BY m.doctor_name ORDER BY totalOrders DESC")
List<Map<String, Object>> selectGroupByDoctor();
@Select("SELECT e.order_no AS orderNo, e.patient_name AS patientName, e.order_type AS orderType, " +
"COALESCE(m.department_name, '未知') AS department, " +
"COALESCE(m.doctor_name, '未知') AS doctorName, " +
"e.current_step AS currentStep, e.create_time AS orderTime " +
"FROM order_execute_record e " +
"LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " +
"WHERE e.delete_flag = '0' " +
"AND e.execute_status IN ('pending', 'in_progress', 'overdue', 'executing') " +
"ORDER BY e.create_time DESC")
List<Map<String, Object>> selectUnclosedWarnings();
}

View File

@@ -25,4 +25,4 @@ public class SysAuditLog extends HisBaseEntity {
private String result;
private String errorMsg;
private Integer durationMs;
}
}

View File

@@ -1317,6 +1317,9 @@ public class YbDao {
public List<Settlement3201DetailDto> reconcileGeneralLedgerDetail(Settlement3201WebParam settlement3201WebParam) {
// 获取条件
String clrType = settlement3201WebParam.getClrType();// 住院 or 门诊
if (StringUtils.isEmpty(clrType)) {
throw new ServiceException("请选择医疗类型:住院/门诊");
}
Integer kindEnum;
if (clrType.equals(YbClrType.OUTPATIENT_CLINIC.getValue())) {
kindEnum = PaymentKind.OUTPATIENT_CLINIC.getValue();

View File

@@ -111,6 +111,7 @@
o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id AS realPatientId,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,
@@ -230,6 +231,7 @@
o.order_no AS orderNo,
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
pinfo.gender_enum AS genderEnum,
pinfo.id AS realPatientId,
pinfo.id_card AS idCard,
o.appointment_time AS appointmentTime,
<include refid="orderStatusNormExpr" /> AS orderStatus,

View File

@@ -24,6 +24,7 @@
AND account.contract_no = #{contractNo}
</if>
GROUP BY payment.ID,
account.ID
account.ID,
account.contract_no
</select>
</mapper>

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request'
export function createRecord(data) { return request({ url: '/healthlink-his/api/v1/anesthesia/record', method: 'post', data }) }
export function updateRecord(data) { return request({ url: '/healthlink-his/api/v1/anesthesia/record', method: 'put', data }) }
export function getRecordDetail(id) { return request({ url: '/healthlink-his/api/v1/anesthesia/record/' + id, method: 'get' }) }
export function getByEncounter(encounterId) { return request({ url: '/healthlink-his/api/v1/anesthesia/record/encounter/' + encounterId, method: 'get' }) }
export function getVitalSigns(recordId) { return request({ url: '/healthlink-his/api/v1/anesthesia/vital-sign/' + recordId, method: 'get' }) }
export function getMedications(recordId) { return request({ url: '/healthlink-his/api/v1/anesthesia/medication/' + recordId, method: 'get' }) }
export function getIoSummary(recordId) { return request({ url: '/healthlink-his/api/v1/anesthesia/io-summary/' + recordId, method: 'get' }) }
export function completeRecord(id) { return request({ url: '/healthlink-his/api/v1/anesthesia/complete/' + id, method: 'put' }) }
export function createRecord(data) { return request({ url: '/api/v1/anesthesia/record', method: 'post', data }) }
export function updateRecord(data) { return request({ url: '/api/v1/anesthesia/record', method: 'put', data }) }
export function getRecordDetail(id) { return request({ url: '/api/v1/anesthesia/record/' + id, method: 'get' }) }
export function getByEncounter(encounterId) { return request({ url: '/api/v1/anesthesia/record/encounter/' + encounterId, method: 'get' }) }
export function getVitalSigns(recordId) { return request({ url: '/api/v1/anesthesia/vital-sign/' + recordId, method: 'get' }) }
export function getMedications(recordId) { return request({ url: '/api/v1/anesthesia/medication/' + recordId, method: 'get' }) }
export function getIoSummary(recordId) { return request({ url: '/api/v1/anesthesia/io-summary/' + recordId, method: 'get' }) }
export function completeRecord(id) { return request({ url: '/api/v1/anesthesia/complete/' + id, method: 'put' }) }

View File

@@ -2,23 +2,23 @@ import request from '@/utils/request'
// ==================== 抗菌药物管控 ====================
export function getRules(drugCode) {
return request({ url: `/healthlink-his/api/v1/antibiotic/rules/${drugCode}`, method: 'get' })
return request({ url: `/api/v1/antibiotic/rules/${drugCode}`, method: 'get' })
}
export function checkRestriction(drugCode, doctorLevel) {
return request({ url: '/healthlink-his/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } })
return request({ url: '/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } })
}
export function requestApproval(data) {
return request({ url: '/healthlink-his/api/v1/antibiotic/approval', method: 'post', data })
return request({ url: '/api/v1/antibiotic/approval', method: 'post', data })
}
export function approve(id, approverId, approverName, result) {
return request({ url: `/healthlink-his/api/v1/antibiotic/approval/${id}`, method: 'put', params: { approverId, approverName, result } })
return request({ url: `/api/v1/antibiotic/approval/${id}`, method: 'put', params: { approverId, approverName, result } })
}
export function getStatistics(startDate, endDate) {
return request({ url: '/healthlink-his/api/v1/antibiotic/statistics', method: 'get', params: { startDate, endDate } })
return request({ url: '/api/v1/antibiotic/statistics', method: 'get', params: { startDate, endDate } })
}
// 新增抗菌药物规则

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request'
export function getRules(drugCode) { return request({ url: '/healthlink-his/api/v1/antibiotic/rules/' + drugCode, method: 'get' }) }
export function checkRestriction(drugCode, doctorLevel) { return request({ url: '/healthlink-his/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } }) }
export function requestApproval(data) { return request({ url: '/healthlink-his/api/v1/antibiotic/approval', method: 'post', data }) }
export function approve(id, params) { return request({ url: '/healthlink-his/api/v1/antibiotic/approval/' + id, method: 'put', params }) }
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/antibiotic/statistics', method: 'get' }) }
export function getRules(drugCode) { return request({ url: '/api/v1/antibiotic/rules/' + drugCode, method: 'get' }) }
export function checkRestriction(drugCode, doctorLevel) { return request({ url: '/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } }) }
export function requestApproval(data) { return request({ url: '/api/v1/antibiotic/approval', method: 'post', data }) }
export function approve(id, params) { return request({ url: '/api/v1/antibiotic/approval/' + id, method: 'put', params }) }
export function getStatistics() { return request({ url: '/api/v1/antibiotic/statistics', method: 'get' }) }

View File

@@ -1,5 +1,5 @@
import request from '@/utils/request'
export function verifySignature(documentType, documentId) { return request({ url: '/healthlink-his/api/v1/ca-signature/verify/' + documentType + '/' + documentId, method: 'get' }) }
export function getSignatureHistory(documentType, documentId) { return request({ url: '/healthlink-his/api/v1/ca-signature/history/' + documentType + '/' + documentId, method: 'get' }) }
export function revokeSignature(id) { return request({ url: '/healthlink-his/api/v1/ca-signature/revoke/' + id, method: 'put' }) }
export function getSignatureStatistics() { return request({ url: '/healthlink-his/api/v1/ca-signature/statistics', method: 'get' }) }
export function verifySignature(documentType, documentId) { return request({ url: '/api/v1/ca-signature/verify/' + documentType + '/' + documentId, method: 'get' }) }
export function getSignatureHistory(documentType, documentId) { return request({ url: '/api/v1/ca-signature/history/' + documentType + '/' + documentId, method: 'get' }) }
export function revokeSignature(id) { return request({ url: '/api/v1/ca-signature/revoke/' + id, method: 'put' }) }
export function getSignatureStatistics() { return request({ url: '/api/v1/ca-signature/statistics', method: 'get' }) }

View File

@@ -3,7 +3,7 @@ import request from '@/utils/request'
// 查询会诊申请列表
export function listRequest(query) {
return request({
url: '/consultation/request/list',
url: '/consultation/list',
method: 'get',
params: query
})
@@ -12,7 +12,7 @@ export function listRequest(query) {
// 查询会诊申请详细
export function getRequest(id) {
return request({
url: '/consultation/request/' + id,
url: '/consultation/detail/' + id,
method: 'get'
})
}
@@ -20,7 +20,7 @@ export function getRequest(id) {
// 新增会诊申请
export function addRequest(data) {
return request({
url: '/consultation/request',
url: '/consultation/save',
method: 'post',
data: data
})
@@ -29,8 +29,8 @@ export function addRequest(data) {
// 修改会诊申请
export function updateRequest(data) {
return request({
url: '/consultation/request',
method: 'put',
url: '/consultation/save',
method: 'post',
data: data
})
}
@@ -38,48 +38,48 @@ export function updateRequest(data) {
// 删除会诊申请
export function delRequest(id) {
return request({
url: '/consultation/request/' + id,
method: 'delete'
url: '/consultation/cancel/' + id,
method: 'post'
})
}
// 提交会诊申请
export function submitRequest(id) {
return request({
url: '/consultation/request/submit/' + id,
method: 'put'
url: '/consultation/submit/' + id,
method: 'post'
})
}
// 取消提交会诊申请
export function cancelSubmitRequest(id) {
return request({
url: '/consultation/request/cancelSubmit/' + id,
method: 'put'
url: '/consultation/cancel/' + id,
method: 'post'
})
}
// 结束会诊申请
export function endRequest(id) {
return request({
url: '/consultation/request/end/' + id,
method: 'put'
url: '/consultation/complete/' + id,
method: 'post'
})
}
// 作废会诊申请
export function cancelRequest(id) {
return request({
url: '/consultation/request/cancel/' + id,
method: 'put'
url: '/consultation/cancel/' + id,
method: 'post'
})
}
// 确认会诊
export function confirmRequest(data) {
return request({
url: '/consultation/request/confirm',
method: 'put',
url: '/consultation/confirmation/confirm',
method: 'post',
data: data
})
}
@@ -87,8 +87,8 @@ export function confirmRequest(data) {
// 签名会诊
export function signRequest(data) {
return request({
url: '/consultation/request/sign',
method: 'put',
data: data
url: '/consultation/confirmation/sign',
method: 'post',
params: { consultationId: data.consultationId }
})
}

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request'
export function getPendingList() { return request({ url: '/healthlink-his/api/v1/critical-value/pending', method: 'get' }) }
export function confirmValue(id, params) { return request({ url: '/healthlink-his/api/v1/critical-value/confirm/' + id, method: 'put', params }) }
export function closeValue(id) { return request({ url: '/healthlink-his/api/v1/critical-value/close/' + id, method: 'put' }) }
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/critical-value/statistics', method: 'get' }) }
export function getOverdueList() { return request({ url: '/healthlink-his/api/v1/critical-value/overdue', method: 'get' }) }
export function getPendingList() { return request({ url: '/api/v1/critical-value/pending', method: 'get' }) }
export function confirmValue(id, params) { return request({ url: '/api/v1/critical-value/confirm/' + id, method: 'put', params }) }
export function closeValue(id) { return request({ url: '/api/v1/critical-value/close/' + id, method: 'put' }) }
export function getStatistics() { return request({ url: '/api/v1/critical-value/statistics', method: 'get' }) }
export function getOverdueList() { return request({ url: '/api/v1/critical-value/overdue', method: 'get' }) }

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request'
export function createRevision(data) { return request({ url: '/healthlink-his/api/v1/emr/revision', method: 'post', data }) }
export function getRevisionHistory(emrId) { return request({ url: '/healthlink-his/api/v1/emr/revision/' + emrId, method: 'get' }) }
export function executeCompletenessCheck(emrId) { return request({ url: '/healthlink-his/api/v1/emr/completeness-check/' + emrId, method: 'post' }) }
export function getCompletenessCheck(emrId) { return request({ url: '/healthlink-his/api/v1/emr/completeness-check/' + emrId, method: 'get' }) }
export function getTimelinessByEncounter(encounterId) { return request({ url: '/healthlink-his/api/v1/emr/timeliness/encounter/' + encounterId, method: 'get' }) }
export function getOverdueList() { return request({ url: '/healthlink-his/api/v1/emr/timeliness/overdue', method: 'get' }) }
export function getTimelinessStatistics(params) { return request({ url: '/healthlink-his/api/v1/emr/timeliness/statistics', method: 'get', params }) }
export function checkTimeliness(data) { return request({ url: '/healthlink-his/api/v1/emr/timeliness/check', method: 'post', data }) }
export function createRevision(data) { return request({ url: '/api/v1/emr/revision', method: 'post', data }) }
export function getRevisionHistory(emrId) { return request({ url: '/api/v1/emr/revision/' + emrId, method: 'get' }) }
export function executeCompletenessCheck(emrId) { return request({ url: '/api/v1/emr/completeness-check/' + emrId, method: 'post' }) }
export function getCompletenessCheck(emrId) { return request({ url: '/api/v1/emr/completeness-check/' + emrId, method: 'get' }) }
export function getTimelinessByEncounter(encounterId) { return request({ url: '/api/v1/emr/timeliness/encounter/' + encounterId, method: 'get' }) }
export function getOverdueList() { return request({ url: '/api/v1/emr/timeliness/overdue', method: 'get' }) }
export function getTimelinessStatistics(params) { return request({ url: '/api/v1/emr/timeliness/statistics', method: 'get', params }) }
export function checkTimeliness(data) { return request({ url: '/api/v1/emr/timeliness/check', method: 'post', data }) }

View File

@@ -1,4 +1,4 @@
import request from '@/utils/request'
export function getCaseList(params) { return request({ url: '/healthlink-his/api/v1/infection/case', method: 'get', params }) }
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/infection/statistics', method: 'get' }) }
export function getExposureList() { return request({ url: '/healthlink-his/api/v1/infection/exposure', method: 'get' }) }
export function getCaseList(params) { return request({ url: '/api/v1/infection/case', method: 'get', params }) }
export function getStatistics() { return request({ url: '/api/v1/infection/statistics', method: 'get' }) }
export function getExposureList() { return request({ url: '/api/v1/infection/exposure', method: 'get' }) }

View File

@@ -1,8 +1,8 @@
import request from '@/utils/request'
export function generateHomepage(data) { return request({ url: '/healthlink-his/api/v1/mr-homepage/generate', method: 'post', data }) }
export function updateHomepage(data) { return request({ url: '/healthlink-his/api/v1/mr-homepage', method: 'put', data }) }
export function getHomepageDetail(id) { return request({ url: '/healthlink-his/api/v1/mr-homepage/' + id, method: 'get' }) }
export function executeQualityCheck(id) { return request({ url: '/healthlink-his/api/v1/mr-homepage/quality-check/' + id, method: 'post' }) }
export function getQualityCheck(homepageId) { return request({ url: '/healthlink-his/api/v1/mr-homepage/quality-check/' + homepageId, method: 'get' }) }
export function getStatistics(params) { return request({ url: '/healthlink-his/api/v1/mr-homepage/statistics', method: 'get', params }) }
export function submitHomepage(id) { return request({ url: '/healthlink-his/api/v1/mr-homepage/submit/' + id, method: 'put' }) }
export function generateHomepage(data) { return request({ url: '/api/v1/mr-homepage/generate', method: 'post', data }) }
export function updateHomepage(data) { return request({ url: '/api/v1/mr-homepage', method: 'put', data }) }
export function getHomepageDetail(id) { return request({ url: '/api/v1/mr-homepage/' + id, method: 'get' }) }
export function executeQualityCheck(id) { return request({ url: '/api/v1/mr-homepage/quality-check/' + id, method: 'post' }) }
export function getQualityCheck(homepageId) { return request({ url: '/api/v1/mr-homepage/quality-check/' + homepageId, method: 'get' }) }
export function getStatistics(params) { return request({ url: '/api/v1/mr-homepage/statistics', method: 'get', params }) }
export function submitHomepage(id) { return request({ url: '/api/v1/mr-homepage/submit/' + id, method: 'put' }) }

View File

@@ -1,7 +1,7 @@
import request from '@/utils/request'
export function createAssessment(data) { return request({ url: '/healthlink-his/api/v1/nursing/assessment', method: 'post', data }) }
export function getAssessmentsByEncounter(encounterId) { return request({ url: '/healthlink-his/api/v1/nursing/assessment/encounter/' + encounterId, method: 'get' }) }
export function createCarePlan(data) { return request({ url: '/healthlink-his/api/v1/nursing/care-plan', method: 'post', data }) }
export function getCarePlansByEncounter(encounterId) { return request({ url: '/healthlink-his/api/v1/nursing/care-plan/encounter/' + encounterId, method: 'get' }) }
export function createHandoff(data) { return request({ url: '/healthlink-his/api/v1/nursing/handoff', method: 'post', data }) }
export function getHandoffList(params) { return request({ url: '/healthlink-his/api/v1/nursing/handoff', method: 'get', params }) }
export function createAssessment(data) { return request({ url: '/api/v1/nursing/assessment', method: 'post', data }) }
export function getAssessmentsByEncounter(encounterId) { return request({ url: '/api/v1/nursing/assessment/encounter/' + encounterId, method: 'get' }) }
export function createCarePlan(data) { return request({ url: '/api/v1/nursing/care-plan', method: 'post', data }) }
export function getCarePlansByEncounter(encounterId) { return request({ url: '/api/v1/nursing/care-plan/encounter/' + encounterId, method: 'get' }) }
export function createHandoff(data) { return request({ url: '/api/v1/nursing/handoff', method: 'post', data }) }
export function getHandoffList(params) { return request({ url: '/api/v1/nursing/handoff', method: 'get', params }) }

View File

@@ -28,3 +28,7 @@ export function cancelOrder(data) {
export function getClosedLoopStatistics(params) {
return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get', params })
}
export function remindOrder(data) {
return request({ url: '/api/v1/order-closed-loop/remind', method: 'post', data })
}

View File

@@ -3,7 +3,7 @@ import request from '@/utils/request'
// 医嘱执行记录列表
export function listOrderExecuteRecord(params) {
return request({
url: '/healthlink-his/api/v1/order-closed-loop/list',
url: '/api/v1/order-closed-loop/list',
method: 'get',
params: params
})
@@ -12,7 +12,7 @@ export function listOrderExecuteRecord(params) {
// 医嘱闭环状态查询
export function getOrderClosedLoopStatus(orderId) {
return request({
url: '/healthlink-his/api/v1/order-closed-loop/status/' + orderId,
url: '/api/v1/order-closed-loop/status/' + orderId,
method: 'get'
})
}
@@ -20,7 +20,7 @@ export function getOrderClosedLoopStatus(orderId) {
// 执行医嘱步骤
export function executeOrderStep(data) {
return request({
url: '/healthlink-his/api/v1/order-closed-loop/execute',
url: '/api/v1/order-closed-loop/execute',
method: 'post',
data: data
})
@@ -29,7 +29,7 @@ export function executeOrderStep(data) {
// 闭环统计
export function getClosedLoopStatistics(params) {
return request({
url: '/healthlink-his/api/v1/order-closed-loop/statistics',
url: '/api/v1/order-closed-loop/statistics',
method: 'get',
params: params
})

View File

@@ -1,4 +1,4 @@
import request from '@/utils/request'
export function createPlan(data) { return request({ url: '/healthlink-his/api/v1/review/plan', method: 'post', data }) }
export function getRecords(planId) { return request({ url: '/healthlink-his/api/v1/review/records/' + planId, method: 'get' }) }
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/review/statistics', method: 'get' }) }
export function createPlan(data) { return request({ url: '/api/v1/review/plan', method: 'post', data }) }
export function getRecords(planId) { return request({ url: '/api/v1/review/records/' + planId, method: 'get' }) }
export function getStatistics() { return request({ url: '/api/v1/review/statistics', method: 'get' }) }

View File

@@ -10,16 +10,16 @@ export function executeExamOrder(data) { return request({ url: "/tech-station/ex
export function executeLabOrder(data) { return request({ url: "/tech-station/execute/lab", method: "post", data }) }
// 查询退费审批列表
export function listRefundApproveOrders(params) { return request({ url: "/tech-station/refund/approve/list", method: "get", params }) }
export function listRefundApproveOrders(params) { return request({ url: "/tech-station/refund-approve/list", method: "get", params }) }
// 审批通过检查退费
export function approveExamRefund(data) { return request({ url: "/tech-station/refund/approve/exam", method: "post", data }) }
export function approveExamRefund(data) { return request({ url: "/tech-station/refund-approve/approve/exam/" + data.applyNo, method: "put", data }) }
// 审批驳回检查退费
export function rejectExamRefund(data) { return request({ url: "/tech-station/refund/reject/exam", method: "post", data }) }
export function rejectExamRefund(data) { return request({ url: "/tech-station/refund-approve/reject/exam/" + data.applyNo, method: "put", data }) }
// 审批通过检验退费
export function approveLabRefund(data) { return request({ url: "/tech-station/refund/approve/lab", method: "post", data }) }
export function approveLabRefund(data) { return request({ url: "/tech-station/refund-approve/approve/lab/" + data.applyNo, method: "put", data }) }
// 审批驳回检验退费
export function rejectLabRefund(data) { return request({ url: "/tech-station/refund/reject/lab", method: "post", data }) }
export function rejectLabRefund(data) { return request({ url: "/tech-station/refund-approve/reject/lab/" + data.applyNo, method: "put", data }) }

View File

@@ -3,33 +3,32 @@
{
"index": 0,
"name": 1,
"paperType": "自定义",
"height": 80,
"width": 279,
"paperType": "A5",
"height": 148,
"width": 210,
"paperList": {
"type": "自定义",
"width": 279,
"height": 80
"type": "A5",
"width": 210,
"height": 148
},
"panelPageRule": "none",
"paperHeader": 0,
"paperFooter": 422.3622047244095,
"paperFooter": 419.53,
"paperNumberDisabled": true,
"paperNumberContinue": true,
"panelAngle": 0,
"overPrintOptions": {
"content": "",
"opacity": 0.7,
"opacity": 0.01,
"type": 1
},
"watermarkOptions": {
"content": "",
"fillStyle": "rgba(87, 13, 248, 0.5)",
"fontSize": "36px",
"rotate": 25,
"width": 413,
"height": 310,
"timestamp": true,
"fontSize": "10px",
"rotate": 0,
"width": 100,
"height": 100,
"timestamp": false,
"format": "YYYY-MM-DD HH:mm"
},
"panelLayoutOptions": {
@@ -40,238 +39,30 @@
"printElements": [
{
"options": {
"left": 0,
"top": 15,
"height": 16.5,
"width": 792,
"title": "{{HOSPITAL_NAME}}预交金收据",
"coordinateSync": false,
"widthHeightSync": false,
"fontWeight": "bold",
"letterSpacing": 0.75,
"textAlign": "center",
"qrCodeLevel": 0,
"fontSize": 15,
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 46.5,
"height": 14,
"width": 151.5,
"title": "姓名",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "patientName",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 295.5,
"top": 48,
"height": 14,
"width": 148.5,
"title": "住院号",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "encounterNosd",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 480,
"top": 48,
"height": 14,
"width": 162,
"title": "科室",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "inHospitalOrgName",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 73.5,
"height": 14,
"width": 153,
"title": "ID号",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "patientId",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 295.5,
"top": 73.5,
"height": 14,
"width": 147,
"title": "医保类别",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "contractName",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 480,
"top": 73.5,
"height": 14,
"width": 163.5,
"title": "时间",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "currentTime",
"fontFamily": "Microsoft YaHei"
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 105,
"height": 25,
"width": 120,
"title": "金额",
"coordinateSync": false,
"widthHeightSync": false,
"textAlign": "center",
"textContentVerticalAlign": "middle",
"borderLeft": "solid",
"borderTop": "solid",
"borderRight": "solid",
"borderBottom": "solid",
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 231,
"top": 105,
"height": 25,
"width": 393,
"title": "金额",
"coordinateSync": false,
"widthHeightSync": false,
"textAlign": "center",
"textContentVerticalAlign": "middle",
"borderTop": "solid",
"borderRight": "solid",
"borderBottom": "solid",
"qrCodeLevel": 0,
"field": "balanceAmount",
"hideTitle": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 129,
"height": 30,
"width": 120,
"title": "人民币(大写)",
"coordinateSync": false,
"widthHeightSync": false,
"textAlign": "center",
"textContentVerticalAlign": "middle",
"borderLeft": "solid",
"borderTop": "solid",
"borderRight": "solid",
"qrCodeLevel": 0
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 231,
"top": 129,
"height": 30,
"width": 393,
"title": "金额",
"coordinateSync": false,
"widthHeightSync": false,
"textAlign": "center",
"textContentVerticalAlign": "middle",
"borderTop": "solid",
"borderRight": "solid",
"qrCodeLevel": 0,
"field": "amountInWords",
"hideTitle": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 111,
"top": 159,
"height": 30,
"width": 513,
"left": 505.5,
"top": 20,
"height": 20,
"width": 60,
"title": " ",
"field": "reprintTag",
"hideTitle": true,
"customClass": "reprint-tag",
"coordinateSync": false,
"widthHeightSync": false,
"textAlign": "center",
"textContentVerticalAlign": "middle",
"fontSize": 9,
"color": "#ff0000",
"fontWeight": "bold",
"borderLeft": "solid",
"borderRight": "solid",
"borderTop": "solid",
"borderBottom": "solid",
"borderColor": "#ff0000",
"borderWidth": 1.5,
"qrCodeLevel": 0,
"field": "paymentDetails",
"hideTitle": true
"fixed": true,
"styler": "function(value, options, target, templateData, paperNo) { if (!value || value.trim() === '') { return { display: 'none' }; } return {}; }"
},
"printElementType": {
"title": "文本",
@@ -280,16 +71,20 @@
},
{
"options": {
"left": 111,
"top": 198,
"height": 14,
"width": 120,
"title": "签章",
"left": 30,
"top": 20,
"height": 30,
"width": 535.5,
"title": "{{hospitalName}}",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "patientNamesfs",
"fontFamily": "Microsoft YaHei"
"textAlign": "center",
"fontWeight": "bold",
"fontSize": 19.5,
"fontFamily": "SimSun",
"letterSpacing": 5,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
@@ -298,16 +93,20 @@
},
{
"options": {
"left": 297,
"top": 198,
"height": 14,
"width": 132,
"title": "交款人",
"left": 30,
"top": 52,
"height": 20,
"width": 535.5,
"title": "住院预缴金收据(收执联)",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "patientNameada",
"fontFamily": "Microsoft YaHei"
"textAlign": "center",
"fontSize": 12,
"fontFamily": "SimSun",
"fontWeight": "normal",
"letterSpacing": 2,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
@@ -316,16 +115,143 @@
},
{
"options": {
"left": 481.5,
"top": 198,
"height": 14,
"width": 124.5,
"title": "收款人",
"coordinateSync": false,
"widthHeightSync": false,
"qrCodeLevel": 0,
"field": "cashier",
"fontFamily": "Microsoft YaHei"
"left": 30,
"top": 78,
"height": 15,
"width": 250,
"title": "收据号:{{receiptNo}}",
"fontSize": 9,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 288.35,
"top": 78,
"height": 15,
"width": 277.15,
"title": "收款日期:{{currentTime}}",
"textAlign": "right",
"fontSize": 9,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 14.17,
"top": 96.38,
"width": 566.92,
"height": 185.0,
"title": "<table style='width:100%; border-collapse:collapse; border:1px solid #000000; table-layout:fixed; font-family:SimSun, serif; font-size:11px; color:#000000;'><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; width:65px; font-weight:bold; color:#000000;'>住院号</td><td style='border:1px solid #000; padding:0 4px; width:130px; white-space:normal; word-break:break-all; color:#000000;'>{{encounterNosd}}</td><td style='border:1px solid #000; background:#F5F5F5; text-align:center; width:50px; font-weight:bold; color:#000000;'>姓名</td><td style='border:1px solid #000; padding:0 4px; width:110px; white-space:normal; word-break:break-all; color:#000000;'>{{patientName}}</td><td style='border:1px solid #000; background:#F5F5F5; text-align:center; width:50px; font-weight:bold; color:#000000;'>性别</td><td style='border:1px solid #000; text-align:center; width:50px; white-space:normal; word-break:break-all; color:#000000;'>{{gender}}</td><td style='border:1px solid #000; background:#F5F5F5; text-align:center; width:60px; font-weight:bold; color:#000000;'>年龄</td><td style='border:1px solid #000; text-align:center; width:51.92px; white-space:normal; word-break:break-all; color:#000000;'>{{age}}</td></tr><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>病区/科室</td><td style='border:1px solid #000; padding:0 4px; white-space:normal; word-break:break-all; color:#000000;' colspan='3'>{{inHospitalOrgName}}</td><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>床号</td><td style='border:1px solid #000; text-align:center; white-space:normal; word-break:break-all; color:#000000;'>{{bedName}}</td><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>医保类型</td><td style='border:1px solid #000; text-align:center; white-space:normal; word-break:break-all; color:#000000;'>{{contractName}}</td></tr><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>收费项目</td><td style='border:1px solid #000; padding:0 4px; white-space:normal; word-break:break-all; color:#000000;' colspan='7'>住院预缴款</td></tr><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>支付方式</td><td style='border:1px solid #000; padding:0 4px; white-space:normal; word-break:break-all; color:#000000;' colspan='7'>{{paymentMethod}}</td></tr><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>金额(大写)</td><td style='border:1px solid #000; padding:0 4px; font-weight:normal; white-space:normal; word-break:break-all; color:#000000;' colspan='7'>{{amountInWords}}</td></tr><tr style='height:28px;'><td style='border:1px solid #000; background:#F5F5F5; text-align:center; font-weight:bold; color:#000000;'>金额(小写)</td><td style='border:1px solid #000; padding:0 4px; font-weight:normal; white-space:normal; word-break:break-all; color:#000000;' colspan='7'>{{balanceAmount}}</td></tr></table>",
"fixed": true
},
"printElementType": {
"title": "HTML表格",
"type": "text"
}
},
{
"options": {
"left": 25.0,
"top": 300.0,
"height": 55.0,
"width": 55.0,
"field": "receiptNo",
"hideTitle": true,
"fixed": true
},
"printElementType": {
"type": "qrcode"
}
},
{
"options": {
"left": 15.0,
"top": 359.0,
"height": 11.34,
"width": 80.0,
"title": "扫码查验电子票据",
"textAlign": "center",
"fontSize": 7,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 113.39,
"top": 300.0,
"height": 65.0,
"width": 467.7,
"title": "说明/备注:\n1. 本收据为预收款凭证,非最终医疗自费/统筹消费发票。\n2. 患者出院结算时,须凭此收据联原件退回换取正式的住院发票。\n3. 请妥善保管此收据。如若遗失,请及时前往收费处办理挂失及证明审核。",
"fontSize": 8,
"lineHeight": 14,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 14.17,
"top": 376.0,
"height": 14.17,
"width": 566.92,
"title": "根据《中华人民共和国电子签名法》规定,本电子票据由医院开具并经国家电子认证中心认证,具有法律效力。请妥善保管。",
"textAlign": "center",
"fontSize": 7,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 14.17,
"top": 396.0,
"height": 17.01,
"width": 320.0,
"title": "收款单位:{{hospitalName}}财务结算专用章(电子印章)",
"fontSize": 9,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
"type": "text"
}
},
{
"options": {
"left": 330.0,
"top": 396.0,
"height": 17.01,
"width": 251.1,
"title": "收款员:{{cashier}}",
"textAlign": "right",
"fontSize": 9,
"color": "#000000",
"fixed": true
},
"printElementType": {
"title": "文本",
@@ -335,4 +261,4 @@
]
}
]
}
}

View File

@@ -172,13 +172,13 @@ function getPatchedVxeTable() {
// 修补 cell-click在 dispatchEvent 前注入参数归一化
code = code.replace(
"dispatchEvent('cell-click', params, evnt);",
`params.row = params; params.column = params.column; dispatchEvent('cell-click', params, evnt);`
`var _orgRow = params.row; Object.assign(params, _orgRow); params.row = _orgRow; dispatchEvent('cell-click', params, evnt);`
)
// 修补 current-change在 dispatchEvent 前注入参数归一化
code = code.replace(
"dispatchEvent('current-change', Object.assign({ oldValue, newValue }, params), evnt);",
`var _ccp = Object.assign({ oldValue, newValue }, params); _ccp.newValue = _ccp; _ccp.oldValue = oldValue; dispatchEvent('current-change', _ccp, evnt);`
`var _ccp = Object.assign({ oldValue, newValue }, params); Object.assign(_ccp, newValue); _ccp.newValue = newValue; _ccp.oldValue = oldValue; dispatchEvent('current-change', _ccp, evnt);`
)
cachedVxeTable = code

View File

@@ -301,9 +301,19 @@ export function executePrint(data, template, printerName, options = {}, business
let processedTemplate;
try {
processedTemplate = JSON.parse(
JSON.stringify(template).replace(/{{HOSPITAL_NAME}}/g, userStore.hospitalName)
);
let templateStr = JSON.stringify(template);
// 统一处理医院名称占位符(支持大小写)
const hospitalName = userStore.hospitalName || data.hospitalName || "中联医院";
templateStr = templateStr.replace(/\{\{HOSPITAL_NAME\}\}/gi, hospitalName);
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => {
// 使用更安全的替换方式
const val = data[key] ?? '';
templateStr = templateStr.split(`{{${key}}}`).join(val);
});
}
processedTemplate = JSON.parse(templateStr);
console.log('[4] 模板处理成功');
} catch (parseError) {
console.error('[4] 模板处理失败:', parseError);
@@ -332,8 +342,8 @@ export function executePrint(data, template, printerName, options = {}, business
const printOptions = {
title: '打印标题',
height: 210,
width: 148,
width: 210,
height: 297,
...options,
};
console.log('[7] 打印选项:', printOptions);
@@ -385,7 +395,13 @@ export function executePrint(data, template, printerName, options = {}, business
hiprintTemplate.print(data, printOptions, {
styleHandler: () => {
console.log('[10] styleHandler被调用');
return '<style>@media print { @page { margin: 0; } }</style>';
// 从 printOptions 获取纸张尺寸mm用于 @page size
const pageWidth = printOptions.width || 210;
const pageHeight = printOptions.height || 297;
const pageStyle = `<style>@media print { @page { size: ${pageWidth}mm ${pageHeight}mm; margin: 0; } }</style>`;
// 合并外部传入的 styleHandler包含元素定位样式与内部 @page 样式
const externalStyle = (options && typeof options.styleHandler === 'function') ? options.styleHandler() : '';
return pageStyle + externalStyle;
},
callback: (e) => {
console.log('[10] 打印回调被调用:', e);

View File

@@ -35,7 +35,7 @@ const open = (data) => { formData.value = data || {}; visible.value = true }
const handleSign = async () => {
try {
await request({ url: '/healthlink-his/api/v1/ca-signature/sign', method: 'post', data: formData.value })
await request({ url: '/api/v1/ca-signature/sign', method: 'post', data: formData.value })
ElMessage.success('签名成功')
visible.value = false
} catch (e) { ElMessage.error('签名失败') }

View File

@@ -61,7 +61,7 @@
ref="patientListRef"
height="620"
:data="patientList"
:row-config="{ keyField: 'encounterId', keyField: 'id' }"
:row-config="{ keyField: 'encounterId' }"
@cell-click="clickRow"
>
<vxe-column
@@ -447,7 +447,8 @@ function checkSelectable(row, index) {
/**
* 点击患者列表行 获取处方列表
*/
function clickRow(row) {
function clickRow(params) {
const row = params.row || params;
patientInfo.value = row;
chargeLoading.value = true;
encounterId.value = row.encounterId;

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div
v-loading="readCardLoading"
class="app-container"
@@ -122,6 +122,7 @@
<patientList
:searchkey="patientSearchKey"
@selsect-patient="selsectPatient"
@mousedown.prevent
/>
<template #reference>
<el-input
@@ -2079,10 +2080,20 @@ async function confirmCheckIn() {
// 每次开始新的签到流程先清理残留 slotId避免历史脏值串单
currentSlotId.value = null;
// 防御性校验:确保关键字段存在
if (!patient.departmentId) {
ElMessage.error('该号源缺少科室信息,无法完成签到,请联系管理员');
return;
}
if (!patient.realPatientId) {
ElMessage.error('该号源缺少患者信息,无法完成签到,请联系管理员');
return;
}
// 弹出确认提示
try {
await ElMessageBox.confirm(
`确认为患者【${patient.patientName}】办理签到挂号?\n` +
`确认为患者【${patient.patientName || '未知患者'}】办理签到挂号?\n` +
`科室:${patient.department || '-'}\n` +
`医生:${patient.doctor || '-'}\n` +
`费用:¥${patient.fee || '0.00'}`,
@@ -2215,7 +2226,7 @@ async function confirmCheckIn() {
* 点击患者列表给表单赋值
*/
function selsectPatient(row) {
form.value = { ...form.value, ...row };
Object.assign(form.value, row);
form.value.patientId = row.id;
form.value.searchKey = row.name;
form.value.name = row.name;
@@ -2225,6 +2236,7 @@ function selsectPatient(row) {
form.value.firstEnum_enumText = row.firstEnum_enumText;
form.value.age = row.age;
form.value.identifierNo = row.identifierNo;
showPopover.value = false;
}
// 设置新增参数

View File

@@ -52,40 +52,43 @@
搜索
</el-button>
</div>
<vxe-table
:row-config="{ isCurrent: true }" :data="encounterList"
border
style="width: 100%"
height="calc(100vh - 300px)"
@cell-click="handleGetReturnDrugList"
>
<vxe-column
field="patientName"
align="center"
title="姓名"
width="130"
show-overflow
/>
<vxe-column
field="genderEnum_enumText"
align="center"
title="性别"
show-overflow
/>
<vxe-column
align="center"
width="140"
title="就诊日期"
show-overflow
<div style="flex: 1; overflow: hidden; margin-top: 10px;">
<vxe-table
:row-config="{ isCurrent: true }"
:data="encounterList"
border
style="width: 100%"
height="100%"
@cell-click="handleGetReturnDrugList"
>
<template #default="scope">
{{
scope.row.receptionTime ? formatDateStr(scope.row.receptionTime, 'YYYY-MM-DD') : '-'
}}
</template>
</vxe-column>
<!-- <vxe-column title="状态" align="center" field="refundEnum_enumText" /> -->
</vxe-table>
<vxe-column
field="patientName"
align="center"
title="姓名"
width="130"
show-overflow
/>
<vxe-column
field="genderEnum_enumText"
align="center"
title="性别"
show-overflow
/>
<vxe-column
align="center"
width="140"
title="就诊日期"
show-overflow
>
<template #default="scope">
{{
scope.row.receptionTime ? formatDateStr(scope.row.receptionTime, 'YYYY-MM-DD') : '-'
}}
</template>
</vxe-column>
<!-- <vxe-column title="状态" align="center" field="refundEnum_enumText" /> -->
</vxe-table>
</div>
</el-card>
<!-- 右侧退药列表 -->
@@ -100,130 +103,132 @@
</div>
</template>
<el-button
type="primary"
:disabled="!selectedMedicines.length"
style="margin-bottom: 10px"
@click="handleReturnDrug(undefined)"
>
确认退药
</el-button>
<el-button
type="primary"
style="margin-bottom: 10px"
@click="handleScan()"
>
扫码
</el-button>
<vxe-table
ref="returnDrugRef"
:data="returDrugList"
style="width: 100%"
height="calc(100vh - 300px)"
border
:span-method="handelSpanMethod"
class="no-hover-table"
@select="handleSelection"
@checkbox-change="handelSelectRows"
>
<vxe-column
type="checkbox"
width="55"
align="center"
/>
<vxe-column
field="itemName"
title="药品名称"
show-overflow
align="center"
/>
<vxe-column
field="totalPrice"
title="总价"
width="100"
align="right"
header-align="center"
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<el-button
type="primary"
:disabled="!selectedMedicines.length"
@click="handleReturnDrug(undefined)"
>
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice.toFixed(2) + ' 元' : '-' }}
</template>
</vxe-column>
<vxe-column
field="lotNumber"
title="批号"
width="180"
align="center"
/>
<vxe-column
field="traceNo"
title="追溯码"
width="180"
align="center"
确认退药
</el-button>
<el-button
type="primary"
@click="handleScan()"
>
<template #default="scope">
<el-input
v-model="scope.row.traceNo"
placeholder="请输入追溯码"
/>
</template>
</vxe-column>
<vxe-column
field="reqStatus_enumText"
title="退药状态"
width="100"
align="center"
扫码
</el-button>
</div>
<div style="flex: 1; overflow: hidden; margin-bottom: 10px;">
<vxe-table
ref="returnDrugRef"
:data="returDrugList"
style="width: 100%"
height="100%"
border
:span-method="handelSpanMethod"
class="no-hover-table"
@checkbox-all="handelSelectRows"
@checkbox-change="handelSelectRows"
>
<template #default="scope">
{{ scope.row.refundEnum_enumText }}
</template>
</vxe-column>
<vxe-column
field="waitingQuantity"
title="退药数量"
width="100"
align="center"
>
<template #default="scope">
<span>{{
scope.row.quantity
? Math.abs(scope.row.quantity) + ' ' + scope.row.unitCode_dictText
: '0' + ' ' + scope.row.unitCode_dictText
}}</span>
</template>
</vxe-column>
<vxe-column
field="doctorName"
title="开单医生"
align="center"
width="180"
/>
<vxe-column
title="操作"
width="100"
align="center"
fixed="right"
>
<template #default="scope">
<el-popconfirm
width="150"
hide-after="10"
title="操作确认"
placement="top-start"
@confirm="handleReturnDrug(scope.row)"
>
<template #reference>
<el-button
type="primary"
link
:disabled="scope.row.refundEnum != 16"
>
退药
</el-button>
</template>
</el-popconfirm>
</template>
</vxe-column>
</vxe-table>
<vxe-column
type="checkbox"
width="55"
align="center"
/>
<vxe-column
field="itemName"
title="药品名称"
show-overflow
align="center"
/>
<vxe-column
field="totalPrice"
title="总价"
width="100"
align="right"
header-align="center"
>
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice.toFixed(2) + ' 元' : '-' }}
</template>
</vxe-column>
<vxe-column
field="lotNumber"
title="批号"
width="180"
align="center"
/>
<vxe-column
field="traceNo"
title="追溯码"
width="180"
align="center"
>
<template #default="scope">
<el-input
v-model="scope.row.traceNo"
placeholder="请输入追溯码"
/>
</template>
</vxe-column>
<vxe-column
field="reqStatus_enumText"
title="退药状态"
width="100"
align="center"
>
<template #default="scope">
{{ scope.row.refundEnum_enumText }}
</template>
</vxe-column>
<vxe-column
field="waitingQuantity"
title="退药数量"
width="100"
align="center"
>
<template #default="scope">
<span>{{
scope.row.quantity
? Math.abs(scope.row.quantity) + ' ' + scope.row.unitCode_dictText
: '0' + ' ' + scope.row.unitCode_dictText
}}</span>
</template>
</vxe-column>
<vxe-column
field="doctorName"
title="开单医生"
align="center"
width="180"
/>
<vxe-column
title="操作"
width="100"
align="center"
fixed="right"
>
<template #default="scope">
<el-popconfirm
width="150"
hide-after="10"
title="操作确认"
placement="top-start"
@confirm="handleReturnDrug(scope.row)"
>
<template #reference>
<el-button
type="primary"
link
:disabled="scope.row.refundEnum != 16"
>
退药
</el-button>
</template>
</el-popconfirm>
</template>
</vxe-column>
</vxe-table>
</div>
<!-- 底部操作栏 -->
<div class="footer">
@@ -288,7 +293,7 @@ function initOptions() {
});
}
function handleGetReturnDrugList(row) {
function handleGetReturnDrugList({ row }) {
encounterId.value = row.encounterId;
getReturnDrugList({
encounterId: row.encounterId,
@@ -363,7 +368,7 @@ function handleReturnDrug(row) {
};
});
} else {
saveList = proxy.$refs.returnDrugRef.getSelectionRows().map((item) => {
saveList = proxy.$refs.returnDrugRef.getCheckboxRecords().map((item) => {
return {
requestId: item.requestId,
dispenseId: item.dispenseId,
@@ -387,31 +392,22 @@ function handleReturnDrug(row) {
});
}
// 选择框改变时的处理
function handleSelection(selection, row) {
const isSelected = selection.some((item) => item.dispenseId === row.dispenseId);
returDrugList.value
.filter((item) => {
return item.requestId == row.requestId;
})
.forEach((row) => {
proxy.$refs['returnDrugRef'].toggleCheckboxRow(row, isSelected);
});
function handelSelectRows({ checked, row }) {
if (row) {
returDrugList.value
.filter((item) => item.requestId == row.requestId)
.forEach((r) => {
proxy.$refs['returnDrugRef'].setCheckboxRow(r, checked);
});
}
nextTick(() => {
selectedMedicines.value = proxy.$refs['returnDrugRef'].getSelectionRows();
selectedMedicines.value = proxy.$refs['returnDrugRef'].getCheckboxRecords();
totalAmount.value = selectedMedicines.value.reduce((accumulator, currentRow) => {
return accumulator + (currentRow.totalPrice || 0);
}, 0);
});
}
function handelSelectRows(selection) {
selectedMedicines.value = selection;
totalAmount.value = selectedMedicines.value.reduce((accumulator, currentRow) => {
return accumulator + (currentRow.totalPrice || 0);
}, 0);
}
// 根据 requestId 合并相同行的方法(只合并药品名称和总价列)
function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
// 定义需要合并的列索引
@@ -465,7 +461,7 @@ function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
<style lang="scss" scoped>
.container {
display: flex;
height: 90vh;
height: calc(100vh - 150px);
gap: 16px;
padding: 16px;
background: #f0f2f5;
@@ -474,10 +470,24 @@ function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
.patient-list {
width: 600px;
flex-shrink: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.refund-list {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 16px;
}
.patient-item {

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request'
export function reportCriticalValue(data) { return request({ url: '/healthlink-his/api/v1/critical-value/report', method: 'post', data }) }
export function confirmCriticalValue(id, params) { return request({ url: '/healthlink-his/api/v1/critical-value/confirm/' + id, method: 'put', params }) }
export function processCriticalValue(id, params) { return request({ url: '/healthlink-his/api/v1/critical-value/process/' + id, method: 'put', params }) }
export function closeCriticalValue(id) { return request({ url: '/healthlink-his/api/v1/critical-value/close/' + id, method: 'put' }) }
export function getPendingList() { return request({ url: '/healthlink-his/api/v1/critical-value/pending', method: 'get' }) }
export function getOverdueList() { return request({ url: '/healthlink-his/api/v1/critical-value/overdue', method: 'get' }) }
export function getStatistics(params) { return request({ url: '/healthlink-his/api/v1/critical-value/statistics', method: 'get', params }) }
export function reportCriticalValue(data) { return request({ url: '/api/v1/critical-value/report', method: 'post', data }) }
export function confirmCriticalValue(id, params) { return request({ url: '/api/v1/critical-value/confirm/' + id, method: 'put', params }) }
export function processCriticalValue(id, params) { return request({ url: '/api/v1/critical-value/process/' + id, method: 'put', params }) }
export function closeCriticalValue(id) { return request({ url: '/api/v1/critical-value/close/' + id, method: 'put' }) }
export function getPendingList() { return request({ url: '/api/v1/critical-value/pending', method: 'get' }) }
export function getOverdueList() { return request({ url: '/api/v1/critical-value/overdue', method: 'get' }) }
export function getStatistics(params) { return request({ url: '/api/v1/critical-value/statistics', method: 'get', params }) }

View File

@@ -394,7 +394,7 @@
>
皮试<el-checkbox
v-model="scope.row.skinTestFlag"
:true-value="true"
:true-value="1"
:false-value="0"
@change="handleSkinTestChange(scope.row, scope.rowIndex)"
>
@@ -837,7 +837,7 @@
>
皮试<el-checkbox
v-model="scope.row.skinTestFlag"
:true-value="true"
:true-value="1"
:false-value="0"
@change="handleSkinTestChange(scope.row, scope.rowIndex)"
>
@@ -1332,7 +1332,7 @@
<template v-if="scope.row.isEdit">
<el-checkbox
v-model="scope.row.skinTestFlag"
:true-value="true"
:true-value="1"
:false-value="0"
@change="handleSkinTestChange(scope.row, scope.rowIndex)"
>

View File

@@ -1,4 +1,4 @@
<!--
<!--
* @Description: 门诊手术申请
-->
<template>
@@ -339,9 +339,11 @@
<el-select
v-model="form.surgeryName"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索手术"
:filter-method="filterSurgery"
:remote-method="remoteSearchSurgery"
:loading="surgeryLoading"
style="width: 100%"
@change="handleSurgeryChange"
>
@@ -414,9 +416,11 @@
<el-select
v-model="scope.row.surgeryName"
filterable
remote
reserve-keyword
placeholder="搜索次要手术"
:filter-method="filterSurgery"
:remote-method="remoteSearchSurgery"
:loading="surgeryLoading"
style="width: 100%"
@change="(val) => handleSecondarySurgeryChange(val, scope.row)"
>
@@ -1344,6 +1348,8 @@ function remoteSearchSurgery(query) {
function doSearchSurgery(query) {
surgeryLoading.value = true
getDiagnosisTreatmentList({
categoryCode: '24',
statusEnum: 2,
searchKey: query || '',
pageNo: 1,
pageSize: 100
@@ -1438,10 +1444,13 @@ function filterAnesthesia(query) {
// 加载手术和麻醉全量数据供本地过滤
function loadSurgeryAndAnesthesiaOptions() {
// 1. 初始加载前 100 个启用状态的手术项目,供下拉菜单默认展示,避免全量加载 10,102 条数据导致卡顿
getDiagnosisTreatmentList({
categoryCode: '24',
statusEnum: 2,
searchKey: '',
pageNo: 1,
pageSize: 1000
pageSize: 100
}).then(res => {
let data = []
if (res.data && res.data.records) {
@@ -1455,14 +1464,34 @@ function loadSurgeryAndAnesthesiaOptions() {
(item.categoryCode === '24' || item.categoryCode_dictText === '手术') &&
(item.statusEnum === 2 || item.statusEnum_enumText === '启用' || !item.statusEnum)
)
surgeryNameList.value = allSurgeryItems.value
}).catch(error => {
console.error('加载手术选项失败:', error)
})
// 2. 加载全部启用状态的麻醉项目(麻醉数量较少,可以直接加载全部)
getDiagnosisTreatmentList({
categoryCode: '25',
statusEnum: 2,
searchKey: '',
pageNo: 1,
pageSize: 200
}).then(res => {
let data = []
if (res.data && res.data.records) {
data = res.data.records
} else if (res.data && Array.isArray(res.data)) {
data = res.data
} else if (res.records && Array.isArray(res.records)) {
data = res.records
}
allAnesthesiaItems.value = data.filter(item =>
(item.categoryCode === '25' || item.categoryCode_dictText === '麻醉') &&
(item.statusEnum === 2 || item.statusEnum_enumText === '启用' || !item.statusEnum)
)
surgeryNameList.value = allSurgeryItems.value
anesthesiaNameList.value = allAnesthesiaItems.value
}).catch(error => {
console.error('加载手术/麻醉选项失败:', error)
console.error('加载麻醉选项失败:', error)
})
}
@@ -1664,6 +1693,8 @@ function reset() {
if (surgeryRef.value) {
surgeryRef.value.resetFields()
}
surgeryNameList.value = allSurgeryItems.value
anesthesiaNameList.value = allAnesthesiaItems.value
}
// 获取状态标签类型

View File

@@ -636,13 +636,14 @@ const inputRefs = ref({}); // 存储输入框实例
const requiredProps = ref([]); // 存储必填项 prop 顺序
const totalAmount = ref(0);
const tcmDianosis = ref();
const { method_code, unit_code, rate_code, distribution_category_code, dosage_instruction } =
const { method_code, unit_code, rate_code, distribution_category_code, dosage_instruction, method_of_decocting_medicine } =
proxy.useDict(
'method_code',
'unit_code',
'rate_code',
'distribution_category_code',
'dosage_instruction'
'dosage_instruction',
'method_of_decocting_medicine'
);
onMounted(() => {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="pending-emr-page">
<!-- 页面头部 -->
<div class="page-header">
@@ -139,8 +139,12 @@ import { parseTime } from '@/utils/index.js'
import Pagination from '@/components/Pagination'
import { Document, Refresh, Search, Delete } from '@element-plus/icons-vue'
import { ElDivider } from 'element-plus'
import { useRouter } from 'vue-router'
import { updatePatientInfo } from '@/views/inpatientDoctor/home/store/patient'
import { updateLocalPatientInfo } from '@/views/inpatientDoctor/home/store/localPatient'
// 响应式数据
const router = useRouter()
const loading = ref(true)
const total = ref(0)
const emrList = ref([])
@@ -209,38 +213,18 @@ const handleRowClick = (row) => {
// 写病历
const handleWriteEmr = (row) => {
console.log('写病历:', row)
// 弹出写病历弹窗
ElMessageBox.confirm('确定要为患者 ' + row.patientName + ' 写病历吗?', '确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
// 这里可以跳转到病历编辑页面或弹出病历编辑弹窗
ElMessage.success('正在打开病历编辑页面...')
// TODO: 实现写病历的具体逻辑
// 例如router.push({ path: '/doctorstation/emr', query: { encounterId: row.encounterId } })
}).catch(() => {
// 取消操作
})
// 存储患者信息并跳转到住院医生工作站
updatePatientInfo(row)
updateLocalPatientInfo(row)
router.push({ path: '/inHospital/inpatientDoctor' })
}
// 查看患者
const handleViewPatient = (row) => {
console.log('查看患者:', row)
// 弹出查看患者弹窗
ElMessageBox.confirm('确定要查看患者 ' + row.patientName + ' 的详细信息吗?', '确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'
}).then(() => {
// 这里可以跳转到患者详情页面或弹出患者详情弹窗
ElMessage.success('正在打开患者详情页面...')
// TODO: 实现查看患者的具体逻辑
// 例如router.push({ path: '/doctorstation/patient-details', query: { encounterId: row.encounterId } })
}).catch(() => {
// 取消操作
})
// 存储患者信息并跳转到住院医生工作站
updatePatientInfo(row)
updateLocalPatientInfo(row)
router.push({ path: '/inHospital/inpatientDoctor' })
}
// 获取性别文本

View File

@@ -52,40 +52,43 @@
搜索
</el-button>
</div>
<vxe-table
:row-config="{ isCurrent: true }" :data="encounterList"
border
style="width: 100%"
height="calc(100vh - 300px)"
@cell-click="handleGetReturnDrugList"
>
<vxe-column
field="patientName"
align="center"
title="姓名"
width="130"
show-overflow
/>
<vxe-column
field="genderEnum_enumText"
align="center"
title="性别"
show-overflow
/>
<vxe-column
align="center"
width="140"
title="就诊日期"
show-overflow
<div style="flex: 1; overflow: hidden; margin-top: 10px;">
<vxe-table
:row-config="{ isCurrent: true }"
:data="encounterList"
border
style="width: 100%"
height="100%"
@cell-click="handleGetReturnDrugList"
>
<template #default="scope">
{{
scope.row.receptionTime ? formatDateStr(scope.row.receptionTime, 'YYYY-MM-DD') : '-'
}}
</template>
</vxe-column>
<!-- <vxe-column title="状态" align="center" field="refundEnum_enumText" /> -->
</vxe-table>
<vxe-column
field="patientName"
align="center"
title="姓名"
width="130"
show-overflow
/>
<vxe-column
field="genderEnum_enumText"
align="center"
title="性别"
show-overflow
/>
<vxe-column
align="center"
width="140"
title="就诊日期"
show-overflow
>
<template #default="scope">
{{
scope.row.receptionTime ? formatDateStr(scope.row.receptionTime, 'YYYY-MM-DD') : '-'
}}
</template>
</vxe-column>
<!-- <vxe-column title="状态" align="center" field="refundEnum_enumText" /> -->
</vxe-table>
</div>
</el-card>
<!-- 右侧退药列表 -->
@@ -100,130 +103,132 @@
</div>
</template>
<el-button
type="primary"
:disabled="!selectedMedicines.length"
style="margin-bottom: 10px"
@click="handleReturnDrug(undefined)"
>
确认退药
</el-button>
<el-button
type="primary"
style="margin-bottom: 10px"
@click="handleScan()"
>
扫码
</el-button>
<vxe-table
ref="returnDrugRef"
:data="returDrugList"
style="width: 100%"
height="calc(100vh - 300px)"
border
:span-method="handelSpanMethod"
class="no-hover-table"
@select="handleSelection"
@checkbox-change="handelSelectRows"
>
<vxe-column
type="checkbox"
width="55"
align="center"
/>
<vxe-column
field="itemName"
title="药品名称"
show-overflow
align="center"
/>
<vxe-column
field="totalPrice"
title="总价"
width="100"
align="right"
header-align="center"
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
<el-button
type="primary"
:disabled="!selectedMedicines.length"
@click="handleReturnDrug(undefined)"
>
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice.toFixed(2) + ' 元' : '-' }}
</template>
</vxe-column>
<vxe-column
field="lotNumber"
title="批号"
width="180"
align="center"
/>
<vxe-column
field="traceNo"
title="追溯码"
width="180"
align="center"
确认退药
</el-button>
<el-button
type="primary"
@click="handleScan()"
>
<template #default="scope">
<el-input
v-model="scope.row.traceNo"
placeholder="请输入追溯码"
/>
</template>
</vxe-column>
<vxe-column
field="reqStatus_enumText"
title="退药状态"
width="100"
align="center"
扫码
</el-button>
</div>
<div style="flex: 1; overflow: hidden; margin-bottom: 10px;">
<vxe-table
ref="returnDrugRef"
:data="returDrugList"
style="width: 100%"
height="100%"
border
:span-method="handelSpanMethod"
class="no-hover-table"
@checkbox-all="handelSelectRows"
@checkbox-change="handelSelectRows"
>
<template #default="scope">
{{ scope.row.refundEnum_enumText }}
</template>
</vxe-column>
<vxe-column
field="waitingQuantity"
title="退药数量"
width="100"
align="center"
>
<template #default="scope">
<span>{{
scope.row.quantity
? Math.abs(scope.row.quantity) + ' ' + scope.row.unitCode_dictText
: '0' + ' ' + scope.row.unitCode_dictText
}}</span>
</template>
</vxe-column>
<vxe-column
field="doctorName"
title="开单医生"
align="center"
width="180"
/>
<vxe-column
title="操作"
width="100"
align="center"
fixed="right"
>
<template #default="scope">
<el-popconfirm
width="150"
hide-after="10"
title="操作确认"
placement="top-start"
@confirm="handleReturnDrug(scope.row)"
>
<template #reference>
<el-button
type="primary"
link
:disabled="scope.row.refundEnum != 16"
>
退药
</el-button>
</template>
</el-popconfirm>
</template>
</vxe-column>
</vxe-table>
<vxe-column
type="checkbox"
width="55"
align="center"
/>
<vxe-column
field="itemName"
title="药品名称"
show-overflow
align="center"
/>
<vxe-column
field="totalPrice"
title="总价"
width="100"
align="right"
header-align="center"
>
<template #default="scope">
{{ scope.row.totalPrice ? scope.row.totalPrice.toFixed(2) + ' 元' : '-' }}
</template>
</vxe-column>
<vxe-column
field="lotNumber"
title="批号"
width="180"
align="center"
/>
<vxe-column
field="traceNo"
title="追溯码"
width="180"
align="center"
>
<template #default="scope">
<el-input
v-model="scope.row.traceNo"
placeholder="请输入追溯码"
/>
</template>
</vxe-column>
<vxe-column
field="reqStatus_enumText"
title="退药状态"
width="100"
align="center"
>
<template #default="scope">
{{ scope.row.refundEnum_enumText }}
</template>
</vxe-column>
<vxe-column
field="waitingQuantity"
title="退药数量"
width="100"
align="center"
>
<template #default="scope">
<span>{{
scope.row.quantity
? Math.abs(scope.row.quantity) + ' ' + scope.row.unitCode_dictText
: '0' + ' ' + scope.row.unitCode_dictText
}}</span>
</template>
</vxe-column>
<vxe-column
field="doctorName"
title="开单医生"
align="center"
width="180"
/>
<vxe-column
title="操作"
width="100"
align="center"
fixed="right"
>
<template #default="scope">
<el-popconfirm
width="150"
hide-after="10"
title="操作确认"
placement="top-start"
@confirm="handleReturnDrug(scope.row)"
>
<template #reference>
<el-button
type="primary"
link
:disabled="scope.row.refundEnum != 16"
>
退药
</el-button>
</template>
</el-popconfirm>
</template>
</vxe-column>
</vxe-table>
</div>
<!-- 底部操作栏 -->
<div class="footer">
@@ -289,7 +294,7 @@ function initOptions() {
});
}
function handleGetReturnDrugList(row) {
function handleGetReturnDrugList({ row }) {
encounterId.value = row.encounterId;
getReturnDrugList({
encounterId: row.encounterId,
@@ -364,7 +369,7 @@ function handleReturnDrug(row) {
};
});
} else {
saveList = proxy.$refs.returnDrugRef.getSelectionRows().map((item) => {
saveList = proxy.$refs.returnDrugRef.getCheckboxRecords().map((item) => {
return {
requestId: item.requestId,
dispenseId: item.dispenseId,
@@ -388,31 +393,22 @@ function handleReturnDrug(row) {
});
}
// 选择框改变时的处理
function handleSelection(selection, row) {
const isSelected = selection.some((item) => item.dispenseId === row.dispenseId);
returDrugList.value
.filter((item) => {
return item.requestId == row.requestId;
})
.forEach((row) => {
proxy.$refs['returnDrugRef'].toggleCheckboxRow(row, isSelected);
});
function handelSelectRows({ checked, row }) {
if (row) {
returDrugList.value
.filter((item) => item.requestId == row.requestId)
.forEach((r) => {
proxy.$refs['returnDrugRef'].setCheckboxRow(r, checked);
});
}
nextTick(() => {
selectedMedicines.value = proxy.$refs['returnDrugRef'].getSelectionRows();
selectedMedicines.value = proxy.$refs['returnDrugRef'].getCheckboxRecords();
totalAmount.value = selectedMedicines.value.reduce((accumulator, currentRow) => {
return accumulator + (currentRow.totalPrice || 0);
}, 0);
});
}
function handelSelectRows(selection) {
selectedMedicines.value = selection;
totalAmount.value = selectedMedicines.value.reduce((accumulator, currentRow) => {
return accumulator + (currentRow.totalPrice || 0);
}, 0);
}
// 根据 requestId 合并相同行的方法(只合并药品名称和总价列)
function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
// 定义需要合并的列索引
@@ -466,17 +462,31 @@ function handelSpanMethod({ row, column, rowIndex, columnIndex }) {
<style lang="scss" scoped>
.container {
display: flex;
height: 80vh;
height: calc(100vh - 150px);
gap: 16px;
}
.patient-list {
width: 600px;
flex-shrink: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.refund-list {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 16px;
}
.patient-item {

View File

@@ -247,13 +247,13 @@ export function medicineMatch(data) {
});
}
// ----------------------------------------------门诊退药接口----------------------------------------------------------------------
// ----------------------------------------------住院退药接口----------------------------------------------------------------------
/**
* 获取患者列表
*/
export function getList(queryParams) {
return request({
url: '/pharmacy-manage/return-medicine/return-patient-page',
url: '/pharmacy-manage/inHospital-return-medicine/return-patient-page',
method: 'get',
params: queryParams,
});
@@ -264,7 +264,7 @@ export function getList(queryParams) {
*/
export function getReturnDrugList(params) {
return request({
url: '/pharmacy-manage/return-medicine/medicine-return-list',
url: '/pharmacy-manage/inHospital-return-medicine/medicine-return-list',
method: 'get',
params: params,
});
@@ -275,7 +275,7 @@ export function getReturnDrugList(params) {
*/
export function returnDrug(data) {
return request({
url: '/pharmacy-manage/return-medicine/medicine-return',
url: '/pharmacy-manage/inHospital-return-medicine/medicine-return',
method: 'put',
data: data,
});
@@ -286,7 +286,7 @@ export function returnDrug(data) {
*/
export function init() {
return request({
url: '/pharmacy-manage/return-medicine/init',
url: '/pharmacy-manage/inHospital-return-medicine/init',
method: 'get',
});
}

View File

@@ -47,8 +47,8 @@
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-dialog v-model="addVisible" title="激活绿色通道" width="500px">
<el-form :model="addForm" label-width="120px">
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
<el-form ref="addFormRef" :model="addForm" :rules="addFormRules" label-width="120px">
<el-form-item label="患者ID" prop="patientId"><el-input-number v-model="addForm.patientId" :min="1" style="width:100%"/></el-form-item>
<el-form-item label="疾病类型"><el-input v-model="addForm.diseaseType" placeholder="如: 急性心肌梗死、脑卒中"/></el-form-item>
<el-form-item label="目标时间(min)"><el-input-number v-model="addForm.targetTime" :min="1"/></el-form-item>
<el-form-item label="医生"><el-input v-model="addForm.doctor"/></el-form-item>
@@ -76,13 +76,15 @@ import {getPage,activate,complete,getStats,del} from './api'
const tableData=ref([]);const total=ref(0);const stats=ref({})
const addVisible=ref(false);const completeVisible=ref(false)
const addForm=ref({patientId:null,diseaseType:'',targetTime:90,doctor:''})
const addFormRef=ref(null)
const addFormRules={patientId:[{required:true,message:'请选择患者',trigger:'blur'}]}
const completeForm=ref({doorToTreatmentTime:60});let currentId=null
const q=ref({pageNo:1,pageSize:20,diseaseType:'',isAchieved:null})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
const refreshStats=async()=>{const r=await getStats({});stats.value=r.data||{}}
const showAdd=()=>{addForm.value={patientId:null,diseaseType:'',targetTime:90,doctor:''};addVisible.value=true}
const showComplete=(row)=>{currentId=row.id;completeForm.value={doorToTreatmentTime:60};completeVisible.value=true}
const submitAdd=async()=>{await activate(addForm.value);ElMessage.success('绿色通道已激活');addVisible.value=false;loadData();refreshStats()}
const submitAdd=async()=>{if(addFormRef.value){try{await addFormRef.value.validate()}catch{return}}await activate(addForm.value);ElMessage.success('绿色通道已激活');addVisible.value=false;loadData();refreshStats()}
const doComplete=async()=>{await complete(currentId,completeForm.value);ElMessage.success('评估完成');completeVisible.value=false;loadData();refreshStats()}
const delItem=async(id)=>{await del(id);ElMessage.success('已删除');loadData();refreshStats()}
onMounted(()=>{loadData();refreshStats()})

View File

@@ -1,11 +1,11 @@
import request from '@/utils/request'
export function registerPerson(data) { return request({ url: '/healthlink-his/api/v1/empi/person', method: 'post', data }) }
export function mergePersons(primaryId, secondaryIds) { return request({ url: '/healthlink-his/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) }
export function findByGlobalId(globalId) { return request({ url: '/healthlink-his/api/v1/empi/person/global/' + globalId, method: 'get' }) }
export function findByIdCard(idCardNo) { return request({ url: '/healthlink-his/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) }
export function getMappings(globalId) { return request({ url: '/healthlink-his/api/v1/empi/mappings/' + globalId, method: 'get' }) }
export function getStatistics() { return request({ url: '/healthlink-his/api/v1/empi/statistics', method: 'get' }) }
export function registerPerson(data) { return request({ url: '/api/v1/empi/person', method: 'post', data }) }
export function mergePersons(primaryId, secondaryIds) { return request({ url: '/api/v1/empi/merge', method: 'post', params: { primaryId, secondaryIds: secondaryIds.join(',') } }) }
export function findByGlobalId(globalId) { return request({ url: '/api/v1/empi/person/global/' + globalId, method: 'get' }) }
export function findByIdCard(idCardNo) { return request({ url: '/api/v1/empi/person/idcard/' + idCardNo, method: 'get' }) }
export function getMappings(globalId) { return request({ url: '/api/v1/empi/mappings/' + globalId, method: 'get' }) }
export function getStatistics() { return request({ url: '/api/v1/empi/statistics', method: 'get' }) }
export function getPhotos(patientId) { return request({ url: '/empi-enhanced/photo/list', method: 'get', params: { patientId } }) }
export function addPhoto(data) { return request({ url: '/empi-enhanced/photo/add', method: 'post', data }) }

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="container">
<!-- 顶部操作区域 -->
<el-card
@@ -252,6 +252,7 @@
import {nextTick, ref} from 'vue';
import {simplePrint, PRINT_TEMPLATE} from '@/utils/printUtils';
import {getDepositInfo, getDepositInfoPage} from './components/api';
import useUserStore from '@/store/modules/user';
import PatientList from './components/patientList.vue';
import ChargeDialog from './components/chargeDialog.vue';
import RefundDialog from './components/refundDialog.vue';
@@ -343,18 +344,44 @@ async function handlePrint(row) {
return;
}
try {
const userStore = useUserStore();
const amountValue = row.tenderedAmount || 0;
const printData = {
patientName: patientInfo.value.patientName || "",
encounterNosd: patientInfo.value.busNo || "",
inHospitalOrgName: patientInfo.value.inHospitalOrgName || "",
patientId: patientInfo.value.patientId || "",
contractName: patientInfo.value.contractName || "",
hospitalName: userStore.hospitalName || "中联医院",
receiptNo: row.paymentNo || "",
currentTime: row.operateTime || new Date().toLocaleString(),
balanceAmount: row.tenderedAmount ? row.tenderedAmount.toFixed(2) : "0.00",
amountInWords: "",
paymentDetails: "收据号: " + (row.paymentNo || ""),
// 补打标记
reprintTag: row.printCount && row.printCount > 0 ? `[ 补打第 ${row.printCount} 次 ]` : `[ 补打第 1 次 ]`,
// 患者信息
encounterNosd: patientInfo.value.busNo || "",
patientName: patientInfo.value.patientName || "",
gender: patientInfo.value.genderEnum_enumText || "",
age: patientInfo.value.age ? patientInfo.value.age + "岁" : "",
// 科室信息
inHospitalOrgName: patientInfo.value.inHospitalOrgName || "",
bedName: patientInfo.value.bedName || "",
contractName: patientInfo.value.contractName || "自费",
// 费用信息
chargeItem: "住院预缴款",
paymentMethod: row.paymentEnum_enumText || "现金",
amountInWords: "人民币:" + convertToChineseNumber(amountValue),
balanceAmount: "¥ " + amountValue.toFixed(2) + " 元",
cashier: userStore.nickName || "",
};
await simplePrint(PRINT_TEMPLATE.ADVANCE_PAYMENT, printData);
// 切换为 A5 横向尺寸 (210mm x 148mm)
await simplePrint(PRINT_TEMPLATE.ADVANCE_PAYMENT, printData, undefined, {
width: 210,
height: 148,
styleHandler: () => {
return `
<style>
.hiprint-printElement { position: absolute !important; }
.hiprint-printElement * { color: #000000 !important; }
.hiprint-printElement[style*="color"] * { color: inherit !important; }
</style>
`;
}
});
proxy.$modal.msgSuccess("打印成功");
} catch (error) {
console.error("预交金打印失败:", error);
@@ -362,6 +389,60 @@ async function handlePrint(row) {
}
}
/**
* 将数字金额转换为人民币大写
*/
function convertToChineseNumber(amount) {
const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const units = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿'];
const decimalUnits = ['角', '分'];
const absAmount = Math.abs(amount);
let [integer, decimal] = absAmount.toFixed(2).split('.');
let result = amount < 0 ? '负' : '';
// 处理整数部分
if (parseInt(integer) === 0) {
result += '零元';
} else {
for (let i = 0; i < integer.length; i++) {
const digit = parseInt(integer[i]);
const position = integer.length - i - 1;
if (digit !== 0) {
result += digits[digit] + units[position];
} else {
if (i > 0 && parseInt(integer[i - 1]) !== 0) {
result += digits[digit];
}
if (position % 4 === 0 && position > 0) {
result += units[position];
}
}
}
result += '元';
}
// 处理小数部分
if (parseInt(decimal) === 0) {
result += '整';
} else {
if (parseInt(decimal[0]) > 0) {
result += digits[parseInt(decimal[0])] + decimalUnits[0];
} else if (parseInt(decimal[1]) > 0) {
result += digits[0];
}
if (parseInt(decimal[1]) > 0) {
result += digits[parseInt(decimal[1])] + decimalUnits[1];
} else if (parseInt(decimal[0]) > 0) {
result += '整';
}
}
return result;
}
/** 选择病人 */
function handlePatientSelected(row) {
// console.log(row, 'rowwwwwwwwhandlePatientSelected');

View File

@@ -569,17 +569,21 @@ const printDepositReceipt = async (patientInfo, inHospitalInfo, medicalInsurance
patientName: patientInfo.patientName || '', // 姓名
patientId: patientInfo.idCard || patientInfo.patientCode || '', // ID号
contractName: patientInfo.contractName || '自费', // 医保类别
gender: patientInfo.genderEnum_enumText || '', // 性别
age: patientInfo.age ? patientInfo.age + '岁' : '', // 年龄
// 住院信息
encounterNo: inHospitalInfo.encounterNo || '', // 住院号
encounterNosd: inHospitalInfo.encounterNo || '', // 住院号
inHospitalOrgName: inHospitalInfo.inHospitalOrgName || '', // 机构名称
bedName: inHospitalInfo.bedName || '', // 床号
// 费用信息
balanceAmount: advance.value || '0.00', // 金额
amountInWords: convertToChineseNumber(advance.value || '0.00'), // 人民币大写
balanceAmount: '¥ ' + parseFloat(advance.value || 0).toFixed(2) + ' 元', // 金额
amountInWords: '人民币:' + convertToChineseNumber(advance.value || '0.00'), // 人民币大写
// 支付方式详情
paymentDetails: paymentDetails,
paymentMethod: paymentDetails, // 支付方式绑定
// 时间信息
currentTime: new Date().toLocaleString('zh-CN', {
@@ -593,17 +597,27 @@ const printDepositReceipt = async (patientInfo, inHospitalInfo, medicalInsurance
// 其他信息
cashier: userStore?.nickName || '', // 收款人(收费员)
medicalInsuranceTitle: medicalInsuranceTitle || '', // 医保标题信息
hospitalName: userStore?.hospitalName || '', // 医院名称
receiptNo: '', // 挂号处打印可能还没有收据号
reprintTag: '', // 首次打印为空
};
console.log(printData, 'dayin 预交金printData');
// 直接导入并使用指定的预交金打印模板
const templateModule = await import('@/components/Print/AdvancePayment.json');
let template = templateModule.default || templateModule;
// 使用printUtils执行打印
await printUtils.executePrint(printData, template);
// 使用 simplePrint 执行打印,并指定 A5 横向尺寸
await printUtils.simplePrint(printUtils.TEMPLATE.ADVANCE_PAYMENT, printData, undefined, {
width: 210,
height: 148,
styleHandler: () => {
return `
<style>
.hiprint-printElement { position: absolute !important; }
.hiprint-printElement * { color: #000000 !important; }
.hiprint-printElement[style*="color"] * { color: inherit !important; }
</style>
`;
}
});
console.log('预交金收据打印成功');
} catch (error) {
console.error('打印失败:', error);

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div>
<el-row :gutter="24">
<el-col
@@ -187,7 +187,7 @@
style="width: 150px"
>
<el-option
v-for="item in diag_type"
v-for="item in inpatient_diag_category"
:key="item.value"
:label="item.label"
:value="item.value"
@@ -423,7 +423,7 @@ const emits = defineEmits(['diagnosisSave']);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
// 获取诊断类型字典(住院诊断类别)
const { diag_type } = proxy.useDict('diag_type');
const { inpatient_diag_category } = proxy.useDict('inpatient_diag_category');
const rules = ref({
name: [{ required: true, message: '请选择诊断', trigger: 'change' }],
medTypeCode: [{ required: true, message: '请选择诊断类型', trigger: 'change' }],

View File

@@ -95,7 +95,7 @@
style="width: 100%"
>
<el-option
v-for="item in diag_type"
v-for="item in inpatient_diag_category"
:key="item.value"
:label="item.label"
:value="item.value"
@@ -324,7 +324,7 @@ const syndromeSearchkey = ref('')
const syndromeList = ref([])
// 获取诊断类型字典(住院诊断类别)
const { diag_type } = proxy.useDict('diag_type')
const { inpatient_diag_category } = proxy.useDict('inpatient_diag_category')
const filteredSyndromeList = computed(() => {
if (!syndromeSearchkey.value) {

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