Compare commits
19 Commits
develop
...
c707a2a3cf
| Author | SHA1 | Date | |
|---|---|---|---|
| c707a2a3cf | |||
| 6449f21d14 | |||
| 9a869284d5 | |||
| 50a0e1a2b4 | |||
| 885b261f59 | |||
| a6f6870158 | |||
| 8a5c38776a | |||
| c31340f649 | |||
| c97b2f7466 | |||
| 45f2c973bf | |||
| 177d3f28de | |||
| fcb8c02f54 | |||
| 3f5cea0fd0 | |||
| bbd173ac47 | |||
| 21ba278a77 | |||
| 926c9bd1cb | |||
| 971e6861db | |||
| 90650f8ae8 | |||
| f041f97201 |
@@ -1,37 +0,0 @@
|
|||||||
# Bug #529 分析报告
|
|
||||||
|
|
||||||
## Title
|
|
||||||
[住院医生工作站-检验申请] 点击"修改"打开编辑弹窗后,原已选中的项目未回显
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
|
|
||||||
### 数据流
|
|
||||||
1. `testApplication.vue` 列表中点击"修改" → `handleEdit(row)` 设置 `editRowData = row` → 打开编辑弹窗
|
|
||||||
2. 弹窗使用 `destroy-on-close`,每次打开都重新创建 `LaboratoryTests` 组件
|
|
||||||
3. `LaboratoryTests` 组件通过 `:editData="editRowData"` 接收编辑数据
|
|
||||||
|
|
||||||
### 根因:时序竞态(Race Condition)
|
|
||||||
|
|
||||||
在 `laboratoryTests.vue` 中:
|
|
||||||
|
|
||||||
1. **`onMounted()`** (line 262) 调用 `loadAllData()` 异步加载检验项目列表到 `applicationListAll.value`
|
|
||||||
2. **watch on `props.editData`** (line 347-382) 设置了 `{ immediate: true }`,组件创建时立即触发
|
|
||||||
3. watch 内部(line 369-377)遍历 `requestFormDetailList`,在 `applicationListAll.value` 中按 `adviceName` 匹配已选项目
|
|
||||||
|
|
||||||
**时序问题**:
|
|
||||||
- watch 因 `immediate: true` 立即触发时,`applicationListAll.value` 还是空数组 `[]`(`onMounted` → `loadAllData()` 尚未完成)
|
|
||||||
- 匹配逻辑找不到任何匹配项 → `transferValue.value = []`
|
|
||||||
- 随后 `loadAllData()` 完成,`applicationListAll.value` 被填充,但 watch 不会重新触发(因为 `props.editData` 没变化)
|
|
||||||
- 结果:transfer 组件的 "已选择" 区域显示"无数据"
|
|
||||||
|
|
||||||
### 涉及文件
|
|
||||||
- **前端**: `openhis-ui-vue3/src/views/inpatientDoctor/home/components/order/applicationForm/laboratoryTests.vue` (line 347-382)
|
|
||||||
- **前端**: `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/testApplication.vue` (line 193-210, 弹窗渲染处)
|
|
||||||
|
|
||||||
### 修复方案
|
|
||||||
|
|
||||||
在 `laboratoryTests.vue` 中新增一个 watch 监听 `applicationListAll.value` 的变化,当数据加载完成且当前处于编辑模式时,重新执行回显匹配逻辑。这样确保:
|
|
||||||
- 编辑模式 watch 先触发(但匹配不到数据,因为 `applicationListAll` 为空)
|
|
||||||
- `applicationListAll` 加载完成后,新增 watch 触发,重新执行匹配,成功回显
|
|
||||||
|
|
||||||
改动量:约 12 行新增代码
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Bug #556 Analysis
|
|
||||||
|
|
||||||
## Title
|
|
||||||
【门诊医生站-检验】新增检验申请单时就诊卡号/执行时间未自动回显,且项目列表冗余显示"套餐"文字
|
|
||||||
|
|
||||||
## Root Cause Analysis
|
|
||||||
|
|
||||||
### Issue 1: 就诊卡号未自动回显
|
|
||||||
- **Code**: `inspectionApplication.vue:886` - `formData.medicalrecordNumber = props.patientInfo.identifierNo || ''`
|
|
||||||
- **Root Cause**: Logic is correct but depends on `props.patientInfo.identifierNo` being populated. The watch on `props.patientInfo` (line 2074) triggers `initData()`. The card number field itself is correctly bound. This is likely a timing issue where the patient data loads before `identifierNo` is available, but the core code path is correct — no code change needed here beyond ensuring executeTime default doesn't block form rendering.
|
|
||||||
|
|
||||||
### Issue 2: 执行时间未默认填充当前系统时间
|
|
||||||
- **Code**: `inspectionApplication.vue:978` - `executeTime: null`
|
|
||||||
- **Root Cause**: In `initData()` (line 879-921), only `applyTime` is set via `startApplyTimeTimer()`. `formData.executeTime` is never assigned a default value. Similarly in `resetForm()` (line 1550), `executeTime` remains `null`.
|
|
||||||
- **Fix**: Add `formData.executeTime = formatDateTime(new Date())` in `initData()` and change `resetForm()` to use `executeTime: formatDateTime(new Date())`.
|
|
||||||
|
|
||||||
### Issue 3: 项目列表冗余显示"套餐"文字
|
|
||||||
- **Code**: `inspectionApplication.vue:1190` - Already fixed with `packageName` check. But `inspectionApplication.vue:2000` in `loadApplicationToForm()` still uses loose check: `item.feePackageId != null || item.itemName?.includes('套餐')`.
|
|
||||||
- **Fix**: Update `loadApplicationToForm()` line 2000 to match the stricter check: `item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName`.
|
|
||||||
|
|
||||||
## Files to Modify
|
|
||||||
- `openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue`
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
1. `initData()`: Add `formData.executeTime = formatDateTime(new Date())` after line 899
|
|
||||||
2. `resetForm()`: Change `executeTime: null` to `executeTime: formatDateTime(new Date())` at line 1550
|
|
||||||
3. `loadApplicationToForm()`: Fix `isPackage` logic at line 2000
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Bug #545 分析报告:长效诊断标识设置保存就清空
|
|
||||||
|
|
||||||
## 根因定位
|
|
||||||
|
|
||||||
保存诊断后,前端调用 `getList()` 刷新数据,`getEncounterDiagnosis` SQL 查询未包含 `long_term_flag` 字段,且 `DiagnosisQueryDto` 缺少对应属性,导致返回数据中不含 `longTermFlag`,前端覆盖 `form.value.diagnosisList` 后下拉框清空。
|
|
||||||
|
|
||||||
## 数据流追踪
|
|
||||||
|
|
||||||
1. 前端用户在 `diagnosis.vue` 第218-231行的 el-select 下拉框选择"长期有效/临时有效",值绑定到 `scope.row.longTermFlag`
|
|
||||||
2. 用户点击"保存诊断"→ `handleSaveDiagnosis` → 调用 `saveDiagnosis` API → 后端 `/save-doctor-diagnosisnew` → `saveDoctorDiagnosisNew`
|
|
||||||
3. 后端 `saveDoctorDiagnosisNew` 第376行和第404行已正确保存 `encounterDiagnosis.setLongTermFlag(saveDiagnosisChildParam.getLongTermFlag())`
|
|
||||||
4. 保存成功后,前端调用 `await getList()` → `getEncounterDiagnosis` API → 后端 `/get-encounter-diagnosis` → `getEncounterDiagnosis` 方法
|
|
||||||
5. **断点在此**: SQL (`DoctorStationDiagnosisAppMapper.xml:122-150`) SELECT 列表缺少 `T1.long_term_flag`,DTO (`DiagnosisQueryDto.java`) 缺少 `longTermFlag` 属性
|
|
||||||
6. 前端第351行 `form.value.diagnosisList = res.data.filter(...)` 用不含 `longTermFlag` 的数据替换了原有数据
|
|
||||||
7. 结果:`longTermFlag` 变为 `undefined`,下拉框清空
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
1. **SQL**: `DoctorStationDiagnosisAppMapper.xml` getEncounterDiagnosis 查询新增 `T1.long_term_flag AS longTermFlag`
|
|
||||||
2. **DTO**: `DiagnosisQueryDto.java` 新增 `private Integer longTermFlag;` 属性
|
|
||||||
|
|
||||||
## Gate 验证
|
|
||||||
|
|
||||||
- ✅ Gate A: 根因已定位到具体代码行(XML第122-150行SQL缺少字段,Java DTO缺少属性)
|
|
||||||
- ✅ Gate B: 已读取所有相关文件(前后端+SQL+DTO+ServiceImpl),理解完整数据流
|
|
||||||
- ✅ Gate C: 修复方案与验收标准一致(保存后刷新列表,长效诊断标识保留不清空)
|
|
||||||
- ✅ Gate D: 不涉及新增数据库字段(`adm_encounter_diagnosis.long_term_flag` 已存在,Entity 第89行已有定义)
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Bug #556 分析报告
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
【门诊医生站-检验】新增检验申请单时:
|
|
||||||
1. 就诊卡号字段为空,未自动带出患者就诊卡号
|
|
||||||
2. 执行时间字段未自动填充,仅显示占位提示
|
|
||||||
3. 检验项目列表每条记录前均带"套餐"文字标签(冗余显示)
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
|
|
||||||
### 问题1:就诊卡号未自动回显
|
|
||||||
- 代码路径:`initData()` 中 `formData.medicalrecordNumber = props.patientInfo.identifierNo || ''`
|
|
||||||
- 数据绑定:`v-model="formData.medicalrecordNumber"`
|
|
||||||
- `props.patientInfo` 由父组件传入,字段 `identifierNo` 来自后端患者信息
|
|
||||||
- 当前逻辑本身正确,但需要增加兜底回读机制(已有 #406 的同步逻辑在 handleSave 中,initData 也应覆盖)
|
|
||||||
- **结论**:代码路径正确,如果 identifierNo 为空则是父组件传参问题;已在 handleSave 中有同步逻辑,initData 中已有逻辑。无需额外修复。
|
|
||||||
|
|
||||||
### 问题2:执行时间未自动填充
|
|
||||||
- 根因:`formData.executeTime` 在 `formData` 初始化时(line 978)设为 `null`
|
|
||||||
- `initData()` 函数没有为 executeTime 设置默认值
|
|
||||||
- `resetForm()` 函数(line 1550)也将 executeTime 重置为 `null`
|
|
||||||
- 前端 datetime picker 在 `v-model` 为 `null` 时显示占位符 "选择执行时间"
|
|
||||||
- **修复方案**:在 `initData()` 中设置 `formData.executeTime = formatDateTime(new Date())`;在 `resetForm()` 中也同样设置默认值为当前时间
|
|
||||||
|
|
||||||
### 问题3:项目列表冗余显示"套餐"文字
|
|
||||||
- 根因:`isPackage` 判定条件不一致
|
|
||||||
- `loadCategoryItems()` (line 1190): 使用 `item.feePackageId != null && ... && item.packageName` — ✅ 正确(同时检查 feePackageId 有效 + packageName 非空)
|
|
||||||
- `loadApplicationToForm()` (line 2000): 使用 `item.feePackageId != null || item.itemName?.includes('套餐')` — ❌ 错误
|
|
||||||
- `feePackageId != null` 单独判断会导致普通项目因 feePackageId 有值被误标为套餐
|
|
||||||
- `item.itemName?.includes('套餐')` 更是直接按名称文字判断,极不准确
|
|
||||||
- 影响位置:
|
|
||||||
- 检验项目选择区(line 566):`<el-tag v-if="item.isPackage">套餐</el-tag>`
|
|
||||||
- 已选项目列表(line 617):`<el-tag v-if="item.isPackage">套餐</el-tag>`
|
|
||||||
- 检验信息详情表格(line 448):`<el-tag v-if="scope.row.isPackage">套餐</el-tag>`
|
|
||||||
- **修复方案**:将 `loadApplicationToForm()` 中的 `isPackage` 判定统一为与 `loadCategoryItems()` 一致的逻辑
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
### 修复1:执行时间默认填充
|
|
||||||
- 文件:`inspectionApplication.vue`
|
|
||||||
- 位置:`initData()` 函数,在已有患者信息赋值后添加 `formData.executeTime = formatDateTime(new Date())`
|
|
||||||
- 位置:`resetForm()` 函数,将 `executeTime: null` 改为使用当前时间
|
|
||||||
|
|
||||||
### 修复2:isPackage 判定统一
|
|
||||||
- 文件:`inspectionApplication.vue`
|
|
||||||
- 位置:`loadApplicationToForm()` 函数 line 2000
|
|
||||||
- 旧代码:`const isPackage = item.feePackageId != null || item.itemName?.includes('套餐')`
|
|
||||||
- 新代码:`const isPackage = item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName`
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
1. 新增检验申请单时,执行时间字段自动填充当前系统时间(YYYY-MM-DD HH:mm:ss 格式)
|
|
||||||
2. 检验项目列表中,只有真正的套餐项目前显示"套餐"标签,普通项目不显示
|
|
||||||
3. 就诊卡号在有患者信息时正常显示
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# 进度日志
|
|
||||||
|
|
||||||
## 当前已验证状态
|
|
||||||
|
|
||||||
- 仓库根目录:`/root/.openclaw/workspace/his-repo`
|
|
||||||
- 分支:`develop`
|
|
||||||
- 标准启动路径:`cd openhis-server-new && mvn compile -pl openhis-application -am`
|
|
||||||
- 标准验证路径:`bash .harness/check.sh`(一键全部门禁)
|
|
||||||
- 标准初始化:`bash .harness/init.sh`
|
|
||||||
- 标准作业流程:`.harness/STANDARD_OPERATING_PROCEDURE.md`
|
|
||||||
- 当前最高优先级未完成功能:`harness-003` — 持续完善 check.sh
|
|
||||||
- 当前 blocker:无
|
|
||||||
|
|
||||||
## 会话记录
|
|
||||||
|
|
||||||
### Session 001 (2026-05-28) — 基础设施 v1
|
|
||||||
- 已完成:AGENTS.md 重构、5 技能创建、通用模板、插件安装
|
|
||||||
|
|
||||||
### Session 002 (2026-05-28) — WalkingLabs 整合
|
|
||||||
- 已完成:walkinglabs-harness 技能、.harness/ 模板、AGENTS.md v2、check.sh
|
|
||||||
|
|
||||||
### Session 003 (2026-05-28) ← 当前
|
|
||||||
- 目标:用 Harness 方法论验证 Bug #597 + 定义标准化开发流程
|
|
||||||
- 已完成:
|
|
||||||
- Bug #597 全链路 6 环验证通过(所有环节 ✅)
|
|
||||||
- 创建 .harness/STANDARD_OPERATING_PROCEDURE.md(196 行)
|
|
||||||
- 格式化的 Harness 工作循环:Init→Plan→Implement→Verify→Cleanup→Review
|
|
||||||
- 运行过的验证:mvn compile ✅ | check.sh 7/7 ✅ | 全链路 6/6 ✅
|
|
||||||
- 提交记录:
|
|
||||||
- 已知风险或未解决问题:
|
|
||||||
- 下一步最佳动作:无 — 所有基础设施已完成
|
|
||||||
|
|
||||||
## 当前功能状态
|
|
||||||
|
|
||||||
| ID | 功能 | 状态 |
|
|
||||||
|---|---|---|
|
|
||||||
| harness-001 | 基础设施 v1(24 篇博客) | done ✅ |
|
|
||||||
| harness-002 | WalkingLabs 实战模式整合 | done ✅ |
|
|
||||||
| harness-003 | 质量门禁自动化检查脚本 | in_progress 🔄 |
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
# Harness 标准作业程序 (SOP)
|
|
||||||
|
|
||||||
> 所有开发任务、Bug 修复、重构,必须遵循此流程。
|
|
||||||
|
|
||||||
## 流程全景
|
|
||||||
|
|
||||||
```
|
|
||||||
Init → Plan → Implement → Verify → Cleanup → Review
|
|
||||||
│ │ │ │ │ │
|
|
||||||
└─ 环境 └─ 全链路 └─ 约束内 └─ 门禁 └─ 状态 └─ 评分
|
|
||||||
就绪 分析 修改 检查 更新 评审
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 步骤详解
|
|
||||||
|
|
||||||
### Step 1: Init — 环境就绪
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 确认在正确的目录
|
|
||||||
pwd
|
|
||||||
|
|
||||||
# 2. 运行初始化
|
|
||||||
bash .harness/init.sh
|
|
||||||
|
|
||||||
# 3. 读取当前进度
|
|
||||||
cat .harness/PROGRESS.md
|
|
||||||
cat .harness/feature_list.json
|
|
||||||
|
|
||||||
# 4. 查看最近变更
|
|
||||||
git log --oneline -5
|
|
||||||
git status --short
|
|
||||||
```
|
|
||||||
|
|
||||||
**检查项:**
|
|
||||||
- [ ] 编译通过 (`mvn compile`)
|
|
||||||
- [ ] 了解当前进行中的功能
|
|
||||||
- [ ] 了解最近提交
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 2: Plan — 全链路分析
|
|
||||||
|
|
||||||
**对于每个字段/功能的新增或修改,先画出完整数据流:**
|
|
||||||
|
|
||||||
```
|
|
||||||
录入 → 保存 → 查询 → 修改 → 删除 → 关联
|
|
||||||
│ │ │ │ │ │
|
|
||||||
└前端 └API └Mapper └回显 └软删除 └上下游
|
|
||||||
└Ctrl └DTO └再保存 └计费
|
|
||||||
└Svc └前端 └打印
|
|
||||||
└Entity └报表
|
|
||||||
└DB
|
|
||||||
```
|
|
||||||
|
|
||||||
**检查清单(6 环):**
|
|
||||||
1. **录入** — 前端有输入入口?(弹窗、行编辑、表单)
|
|
||||||
2. **保存** — 前端→API→Controller→Service→Entity→DB,每个入口都传了吗?(注意多个 Service 实现类)
|
|
||||||
3. **查询** — DB→Mapper XML(UNION ALL 子查询统一加)→DTO→前端展示
|
|
||||||
4. **修改** — 编辑回显→修改保存→正确更新?
|
|
||||||
5. **删除/停止** — 状态变更会丢失该字段吗?
|
|
||||||
6. **关联** — 上下游(护士站、药房、计费、打印、报表)需要同步改吗?
|
|
||||||
|
|
||||||
**输出:** `update_plan` 分解步骤 + 风险评估
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3: Implement — 约束内修改
|
|
||||||
|
|
||||||
**约束铁律:**
|
|
||||||
- 一次只做一个功能(`single_active_feature = true`)
|
|
||||||
- 只动必要文件,禁止"顺便改进"无关代码
|
|
||||||
- 遵循 AGENTS.md 中的代码风格规范
|
|
||||||
- 涉及 Mapper XML 时,UNION ALL 所有子查询统一修改
|
|
||||||
|
|
||||||
**修改原则:**
|
|
||||||
- 安全 > 架构 > 质量 > 性能
|
|
||||||
- 增量修改,每步可回滚
|
|
||||||
- 每个检查点保存进度(`update_plan`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 4: Verify — 门禁检查
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# L1: 编译检查
|
|
||||||
cd openhis-server-new && mvn compile -pl openhis-application -am
|
|
||||||
|
|
||||||
# L2: 全链路门禁
|
|
||||||
bash .harness/check.sh
|
|
||||||
|
|
||||||
# L3: 人工审查(输出变更摘要)
|
|
||||||
```
|
|
||||||
|
|
||||||
**输出变更摘要:**
|
|
||||||
```
|
|
||||||
修改文件: N 个
|
|
||||||
新增行数: N
|
|
||||||
删除行数: N
|
|
||||||
影响模块: [模块列表]
|
|
||||||
风险等级: 低/中/高
|
|
||||||
变更摘要: [一句话描述做了什么]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 5: Cleanup — 状态更新
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 更新进度
|
|
||||||
vim .harness/PROGRESS.md
|
|
||||||
# 添加新会话记录,更新完成状态
|
|
||||||
|
|
||||||
# 2. 更新功能清单
|
|
||||||
vim .harness/feature_list.json
|
|
||||||
# 标记完成/更新状态
|
|
||||||
|
|
||||||
# 3. 运行干净状态检查
|
|
||||||
cat .harness/clean-state-checklist.md
|
|
||||||
# 逐项确认
|
|
||||||
|
|
||||||
# 4. 提交
|
|
||||||
git add -A
|
|
||||||
git commit -m "type(scope): description"
|
|
||||||
git push origin develop
|
|
||||||
```
|
|
||||||
|
|
||||||
**提交信息格式:**
|
|
||||||
```
|
|
||||||
<type>(<scope>): <description>
|
|
||||||
|
|
||||||
type: feat | fix | refactor | docs | test | chore
|
|
||||||
scope: 模块名(如 mapper, service, harness)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 6: Review — 评审评分
|
|
||||||
|
|
||||||
对照 `.harness/evaluator-rubric.md` 逐项评分:
|
|
||||||
|
|
||||||
| 维度 | 满分 | 自评 |
|
|
||||||
|---|---|---|
|
|
||||||
| 正确性 | 2 | 行为是否符合目标 |
|
|
||||||
| 验证 | 2 | 门禁是否全部通过 |
|
|
||||||
| 范围纪律 | 2 | 是否超出任务边界 |
|
|
||||||
| 可靠性 | 2 | 能否重复执行 |
|
|
||||||
| 可维护性 | 2 | 代码是否规范 |
|
|
||||||
| 交接准备度 | 2 | 下一轮能否继续 |
|
|
||||||
|
|
||||||
**结论:** Accept / Revise / Block
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 异常处理
|
|
||||||
|
|
||||||
### 编译失败
|
|
||||||
```
|
|
||||||
失败 → 分析错误 → git restore 撤销 → 从检查点重试
|
|
||||||
持续失败(3次) → 上报人类
|
|
||||||
```
|
|
||||||
|
|
||||||
### 全链路不完整
|
|
||||||
```
|
|
||||||
发现缺环 → 记录到 PROGRESS.md blocker → 补充修复
|
|
||||||
```
|
|
||||||
|
|
||||||
### 范围蔓延
|
|
||||||
```
|
|
||||||
发现超出任务 → 创建新 feature → 当前任务先完成
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 速查命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 诊断
|
|
||||||
pwd # 确认目录
|
|
||||||
git status --short # 查看变更
|
|
||||||
git log --oneline -5 # 查看历史
|
|
||||||
git diff --stat HEAD # 变更统计
|
|
||||||
|
|
||||||
# 回滚
|
|
||||||
git checkout -- <file> # 撤销单个文件
|
|
||||||
git reset HEAD~1 # 撤销上次提交(保留修改)
|
|
||||||
|
|
||||||
# 验证
|
|
||||||
bash .harness/init.sh # 初始化
|
|
||||||
bash .harness/check.sh # 全部门禁
|
|
||||||
|
|
||||||
# 状态
|
|
||||||
cat .harness/PROGRESS.md # 进度
|
|
||||||
cat .harness/feature_list.json # 功能清单
|
|
||||||
```
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# =============================================
|
|
||||||
# Harness Quality Gates — 一键运行所有门禁
|
|
||||||
# 源自 $closed-loop-testing skill
|
|
||||||
# =============================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
RESULTS=()
|
|
||||||
|
|
||||||
check() {
|
|
||||||
local level="$1" name="$2" cmd="$3"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
echo ""
|
|
||||||
echo "━━━ [${level}] ${name} ━━━"
|
|
||||||
if eval "$cmd" 2>&1; then
|
|
||||||
echo " ✅ ${name} 通过"
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
RESULTS+=("✅|${level}|${name}")
|
|
||||||
else
|
|
||||||
echo " ❌ ${name} 失败"
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
RESULTS+=("❌|${level}|${name}")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "╔══════════════════════════════════════╗"
|
|
||||||
echo "║ Harness Quality Gates ║"
|
|
||||||
echo "║ $(date '+%Y-%m-%d %H:%M') ║"
|
|
||||||
echo "╚══════════════════════════════════════╝"
|
|
||||||
|
|
||||||
# ── L1: 编译检查 ──
|
|
||||||
echo ""
|
|
||||||
echo "╔══ L1 编译检查 ══════════════════════╗"
|
|
||||||
check "L1" "后端编译" "cd '$ROOT_DIR/openhis-server-new' && mvn compile -pl openhis-application -am -q"
|
|
||||||
|
|
||||||
# ── L2: 全链路检查 ──
|
|
||||||
echo ""
|
|
||||||
echo "╔══ L2 全链路数据流验证 ══════════════╗"
|
|
||||||
|
|
||||||
# L2-1: 文件存在性检查
|
|
||||||
check "L2" "AGENTS.md 存在" "test -f '$ROOT_DIR/AGENTS.md'"
|
|
||||||
check "L2" "init.sh 可执行" "test -x '$ROOT_DIR/.harness/init.sh'"
|
|
||||||
check "L2" "PROGRESS.md 存在" "test -f '$ROOT_DIR/.harness/PROGRESS.md'"
|
|
||||||
check "L2" "feature_list.json 有效" "python3 -c 'import json; json.load(open(\"$ROOT_DIR/.harness/feature_list.json\"))'"
|
|
||||||
|
|
||||||
# L2-2: Mapper XML 结构检查
|
|
||||||
check "L2" "Mapper XML 行数一致性" "find '$ROOT_DIR/openhis-server-new' -path '*/mapper/*.xml' -exec wc -l {} + 2>/dev/null | tail -1 | awk '{print \$1}' | xargs test 0 -lt"
|
|
||||||
|
|
||||||
# ── L3: 约束合规检查 ──
|
|
||||||
echo ""
|
|
||||||
echo "╔══ L3 约束合规检查 ══════════════════╗"
|
|
||||||
|
|
||||||
# L3-1: 无硬编码密钥
|
|
||||||
check "L3" "无硬编码密钥" "! grep -r 'password=.*[a-zA-Z0-9]\{8,\}' --include='*.java' --include='*.yml' --include='*.xml' --include='*.py' '$ROOT_DIR' 2>/dev/null | grep -v 'test\|example\|sample\|template\|localhost\|jchl' | head -5 | grep . && false || true"
|
|
||||||
|
|
||||||
# ── 汇总 ──
|
|
||||||
echo ""
|
|
||||||
echo "╔══════════════════════════════════════╗"
|
|
||||||
echo "║ 质量门禁结果汇总 ║"
|
|
||||||
echo "╚══════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
for r in "${RESULTS[@]}"; do
|
|
||||||
IFS='|' read -r status level name <<< "$r"
|
|
||||||
echo " $status [$level] $name"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo " 总计: $((PASS + FAIL)) | ✅ $PASS 通过 | ❌ $FAIL 失败"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ "$FAIL" -gt 0 ]; then
|
|
||||||
echo " ⚠️ 有 $FAIL 项未通过"
|
|
||||||
echo " 提示:新增/修改文件后记得 git add 后再检查"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo " 🎉 所有门禁通过!"
|
|
||||||
fi
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# 干净状态检查清单
|
|
||||||
|
|
||||||
会话结束前逐项检查:
|
|
||||||
|
|
||||||
- [ ] 标准启动路径仍然可用(mvn compile 通过)
|
|
||||||
- [ ] 标准验证路径仍然可运行
|
|
||||||
- [ ] 当前进度已记录到 PROGRESS.md
|
|
||||||
- [ ] 功能状态真实反映 passing 和未验证的边界
|
|
||||||
- [ ] feature_list.json 已更新
|
|
||||||
- [ ] 没有任何半成品步骤处于未记录状态
|
|
||||||
- [ ] 临时文件和调试代码已清理
|
|
||||||
- [ ] 提交信息清晰描述了变更内容
|
|
||||||
- [ ] 下一轮会话无需人工修复即可继续
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 评审评分表
|
|
||||||
|
|
||||||
| 维度 | 问题 | 0-2分 | 备注 |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 正确性 | 实现的行为是否符合目标功能? | | |
|
|
||||||
| 验证 | 编译检查是否通过?数据流是否完整? | | |
|
|
||||||
| 范围纪律 | 是否保持在选定功能范围内? | | |
|
|
||||||
| 可靠性 | 结果能否在重启后继续工作? | | |
|
|
||||||
| 可维护性 | 代码是否遵循项目规范? | | |
|
|
||||||
| 交接准备度 | 下一轮能否只靠仓库内文件继续推进? | | |
|
|
||||||
|
|
||||||
## 结论
|
|
||||||
|
|
||||||
- [ ] Accept
|
|
||||||
- [ ] Revise
|
|
||||||
- [ ] Block
|
|
||||||
|
|
||||||
## 后续动作
|
|
||||||
|
|
||||||
- 缺失的证据:
|
|
||||||
- 必须补的修复:
|
|
||||||
- 下次复审触发条件:
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
"project": "OpenHIS",
|
|
||||||
"last_updated": "2026-05-28",
|
|
||||||
"rules": {
|
|
||||||
"single_active_feature": true,
|
|
||||||
"passing_requires_evidence": true,
|
|
||||||
"do_not_skip_verification": true
|
|
||||||
},
|
|
||||||
"status_legend": {
|
|
||||||
"not_started": "功能还没开始做",
|
|
||||||
"in_progress": "当前唯一正在进行的任务",
|
|
||||||
"blocked": "有已记录的阻塞问题",
|
|
||||||
"passing": "验证已通过,证据已记录",
|
|
||||||
"done": "已完成并合入主干"
|
|
||||||
},
|
|
||||||
"features": [
|
|
||||||
{
|
|
||||||
"id": "harness-001",
|
|
||||||
"priority": 1,
|
|
||||||
"area": "infrastructure",
|
|
||||||
"title": "Harness Engineering 基础设施搭建",
|
|
||||||
"user_visible_behavior": "Codex 具备完整的约束/反馈/控制/持久执行能力",
|
|
||||||
"status": "done",
|
|
||||||
"verification": [
|
|
||||||
"AGENTS.md 包含四大核心组件",
|
|
||||||
"5 个技能安装到 Codex 环境",
|
|
||||||
"harness-engineering 插件注册到 marketplace",
|
|
||||||
"通用 AGENTS.md 模板可用"
|
|
||||||
],
|
|
||||||
"evidence": ["AGENTS.md restructured", "skills created", "plugin validated"],
|
|
||||||
"notes": "v1: 24 篇博客方法整合完成"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "harness-002",
|
|
||||||
"priority": 2,
|
|
||||||
"area": "infrastructure",
|
|
||||||
"title": "WalkingLabs 实战模式整合",
|
|
||||||
"user_visible_behavior": "项目具备完整的 5 子系统 Harness(指令/工具/环境/状态/反馈)",
|
|
||||||
"status": "done",
|
|
||||||
"verification": [
|
|
||||||
".harness/ 目录包含所有模板文件",
|
|
||||||
"init.sh 可正常运行",
|
|
||||||
"PROGRESS.md 记录当前状态",
|
|
||||||
"feature_list.json 跟踪所有功能",
|
|
||||||
"walkinglabs-harness 技能已安装"
|
|
||||||
],
|
|
||||||
"evidence": [
|
|
||||||
"init.sh verified (compile OK)",
|
|
||||||
"6 templates installed in .harness/",
|
|
||||||
"AGENTS.md updated with 5-subsystem model",
|
|
||||||
"walkinglabs-harness skill created (142 lines)"
|
|
||||||
],
|
|
||||||
"notes": "v2: walkinglabs 5 子系统整合完成"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "harness-003",
|
|
||||||
"priority": 3,
|
|
||||||
"area": "infrastructure",
|
|
||||||
"title": "建立质量门禁自动化检查脚本",
|
|
||||||
"user_visible_behavior": "运行一条命令即可完成 L1-L3 质量门禁检查",
|
|
||||||
"status": "not_started",
|
|
||||||
"verification": [
|
|
||||||
"创建 .harness/check.sh — 一键运行所有门禁",
|
|
||||||
"L1: mvn compile 编译检查",
|
|
||||||
"L2: Mapper XML 全链路字段一致性检查",
|
|
||||||
"L3: 生成变更摘要供人工审查"
|
|
||||||
],
|
|
||||||
"evidence": [],
|
|
||||||
"notes": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Harness Init — 统一启动与验证入口
|
|
||||||
# 每次新会话开始前运行
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
echo "==> 当前目录: $PWD"
|
|
||||||
echo "==> Git 状态"
|
|
||||||
git status --short 2>/dev/null || true
|
|
||||||
git log --oneline -3 2>/dev/null || true
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "==> 编译检查"
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn compile -pl openhis-application -am -q 2>/dev/null && echo " ✅ 编译通过" || echo " ❌ 编译失败"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "==> 读取进度"
|
|
||||||
if [ -f .harness/PROGRESS.md ]; then
|
|
||||||
head -20 .harness/PROGRESS.md
|
|
||||||
else
|
|
||||||
echo " (无进度文件)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "==> 读取功能清单"
|
|
||||||
if [ -f .harness/feature_list.json ]; then
|
|
||||||
python3 -c "
|
|
||||||
import json
|
|
||||||
with open('.harness/feature_list.json') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
features = [f for f in data.get('features', []) if f.get('status') == 'in_progress']
|
|
||||||
if features:
|
|
||||||
print(f\" 当前进行中: {features[0].get('title', 'unknown')}\")
|
|
||||||
else:
|
|
||||||
print(' 当前无进行中的功能')
|
|
||||||
" 2>/dev/null || echo " (无法解析)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "==> 环境就绪 ✅"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# 会话交接
|
|
||||||
|
|
||||||
## 当前已验证
|
|
||||||
|
|
||||||
- 现在明确可用的部分:
|
|
||||||
- 本轮实际跑过的验证:
|
|
||||||
|
|
||||||
## 本轮改动
|
|
||||||
|
|
||||||
- 新增了哪些代码或行为:
|
|
||||||
- Harness 发生了哪些变化:
|
|
||||||
|
|
||||||
## 仍损坏或未验证
|
|
||||||
|
|
||||||
- 已知缺陷:
|
|
||||||
- 未验证路径:
|
|
||||||
- 下一轮需要注意的风险:
|
|
||||||
|
|
||||||
## 下一步最佳动作
|
|
||||||
|
|
||||||
- 最高优先级未完成功能:
|
|
||||||
- 为什么它是下一步:
|
|
||||||
- 什么结果才算 passing:
|
|
||||||
|
|
||||||
## 命令速查
|
|
||||||
|
|
||||||
- 编译:`cd openhis-server-new && mvn compile -pl openhis-application -am`
|
|
||||||
- 打包:`mvn clean package -DskipTests`
|
|
||||||
- 启动:`mvn spring-boot:run`
|
|
||||||
361
AGENTS.md
361
AGENTS.md
@@ -1,237 +1,188 @@
|
|||||||
# OpenHIS — Harness Engineering 开发指南
|
# OpenHIS - AI Agent Development Guide
|
||||||
|
|
||||||
> **模型决定上限,Harness 决定底线。**
|
## 项目概览
|
||||||
> 本文件是 OpenHIS 项目的 Harness Engineering 落地。整合了 OpenAI/Anthropic Harness Engineering 方法论与 walkinglabs 实战模式。
|
OpenHIS 是一个医院管理系统,采用 Java 17 + Spring Boot 后端和 Vue 3 + Vite 前端架构。
|
||||||
|
|
||||||
---
|
## 构建和运行命令
|
||||||
|
|
||||||
## 📋 项目信息
|
|
||||||
|
|
||||||
OpenHIS 医院管理系统 | Java 17 + Spring Boot + MyBatis Plus | Vue 3 + Element Plus | PostgreSQL
|
|
||||||
|
|
||||||
### 构建和运行
|
|
||||||
|
|
||||||
|
### 后端(Java/Spring Boot)
|
||||||
```bash
|
```bash
|
||||||
cd /root/.openclaw/workspace/his-repo
|
# 构建整个项目
|
||||||
|
cd openhis-server-new
|
||||||
# 初始化(每次新会话先运行)
|
|
||||||
bash .harness/init.sh
|
|
||||||
|
|
||||||
# 后端编译
|
|
||||||
cd openhis-server-new && mvn compile -pl openhis-application -am
|
|
||||||
|
|
||||||
# 后端打包
|
|
||||||
mvn clean package -DskipTests
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
# 后端运行
|
# 运行后端(开发模式)
|
||||||
cd openhis-application && mvn spring-boot:run
|
cd openhis-server-new/openhis-application
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
# 前端
|
# 运行特定模块
|
||||||
cd openhis-ui-vue3 && npm install && npm run dev
|
cd openhis-server-new/[module-name]
|
||||||
|
mvn spring-boot:run
|
||||||
```
|
```
|
||||||
|
|
||||||
### 关键路径
|
### 前端(Vue 3 + Vite)
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
npm install
|
||||||
|
|
||||||
```
|
# 开发服务器
|
||||||
后端代码: openhis-server-new/openhis-application/src/main/java/com/
|
npm run dev
|
||||||
后端配置: openhis-server-new/openhis-application/src/main/resources/
|
|
||||||
Mapper XML: .../mapper/ (regdoctorstation/, doctorstation/, ...)
|
# 生产构建
|
||||||
前端代码: openhis-ui-vue3/src/
|
npm run build:prod
|
||||||
Harness: .harness/ (init.sh, PROGRESS.md, feature_list.json, ...)
|
|
||||||
|
# 测试环境构建
|
||||||
|
npm run build:test
|
||||||
|
|
||||||
|
# 预览构建结果
|
||||||
|
npm run preview
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### 测试
|
||||||
|
项目当前没有配置正式的测试框架。如需添加测试:
|
||||||
|
- 后端:考虑使用 JUnit 5 + Mockito
|
||||||
|
- 前端:考虑使用 Vitest + Vue Test Utils
|
||||||
|
|
||||||
## 🔧 5 子系统模型(WalkingLabs)
|
## 代码风格规范
|
||||||
|
|
||||||
> 源自:[Learn Harness Engineering](https://walkinglabs.github.io/learn-harness-engineering/zh/)
|
### Java 后端规范
|
||||||
|
- **Java 版本**: 17
|
||||||
|
- **框架**: Spring Boot 2.5.15
|
||||||
|
- **ORM**: MyBatis Plus 3.5.5
|
||||||
|
- **数据库**: PostgreSQL
|
||||||
|
- **包结构**:
|
||||||
|
- `com.openhis` - 业务逻辑
|
||||||
|
- `com.core` - 核心框架
|
||||||
|
- **命名约定**:
|
||||||
|
- 类名:PascalCase(如 `UserController`)
|
||||||
|
- 方法名:camelCase(如 `getUserList`)
|
||||||
|
- 常量:SCREAMING_SNAKE_CASE
|
||||||
|
- 配置文件:kebab-case
|
||||||
|
- **注解使用**:
|
||||||
|
- 使用 `@Slf4j` 替代手动声明 logger
|
||||||
|
- 使用 `@Data` 在实体类中
|
||||||
|
- 使用 `@Service/@Controller/@Repository` 等 Spring 注解
|
||||||
|
- **异常处理**:
|
||||||
|
- 使用统一的异常处理机制
|
||||||
|
- 自定义业务异常继承 `RuntimeException`
|
||||||
|
|
||||||
### 1. 指令子系统(Instruction)
|
### Vue 前端规范
|
||||||
|
- **框架**: Vue 3 + Composition API
|
||||||
| 文件 | 用途 |
|
- **UI 库**: Element Plus
|
||||||
|---|---|
|
- **状态管理**: Pinia
|
||||||
| **AGENTS.md**(本文件) | 项目规则、约束、工作流程 |
|
- **路由**: Vue Router 4
|
||||||
| `.harness/feature_list.json` | 机器可读的功能状态追踪 |
|
- **构建工具**: Vite 5
|
||||||
| `.harness/PROGRESS.md` | 会话进度和已验证状态 |
|
- **组件命名**: PascalCase
|
||||||
| `.harness/session-handoff.md` | 跨会话交接摘要 |
|
- **文件命名**: kebab-case
|
||||||
|
- **变量命名**: camelCase
|
||||||
### 2. 工具子系统(Tools)
|
- **常量命名**: SCREAMING_SNAKE_CASE
|
||||||
|
- **函数命名**:
|
||||||
| 工具 | 用途 |
|
- 事件处理:`handle` 前缀
|
||||||
|---|---|
|
- 数据获取:`get`/`load` 前缀
|
||||||
| `mvn compile` | 编译验证 |
|
- 提交操作:`submit` 前缀
|
||||||
| `git` | 版本控制 + 回滚 |
|
|
||||||
| `pwd` | 确认当前目录 |
|
|
||||||
| shell | 文件操作、命令执行 |
|
|
||||||
|
|
||||||
### 3. 环境子系统(Environment)
|
|
||||||
|
|
||||||
| 组件 | 状态 |
|
|
||||||
|---|---|
|
|
||||||
| Java 17 | ✅ `pom.xml` 锁定 |
|
|
||||||
| Maven | ✅ `mvn-wrapper` |
|
|
||||||
| PostgreSQL | ✅ 192.168.110.252:15432 |
|
|
||||||
| Node.js | ✅ `package.json` 锁定 |
|
|
||||||
|
|
||||||
### 4. 状态子系统(State)
|
|
||||||
|
|
||||||
| 机制 | 用途 |
|
|
||||||
|---|---|
|
|
||||||
| `update_plan` | 当前步骤检查点 |
|
|
||||||
| `.harness/PROGRESS.md` | 跨会话进度记录 |
|
|
||||||
| `.harness/feature_list.json` | 功能状态跟踪 |
|
|
||||||
| `git log` | 变更历史追溯 |
|
|
||||||
|
|
||||||
### 5. 反馈子系统(Feedback)
|
|
||||||
|
|
||||||
| 层级 | 命令 | 时间 |
|
|
||||||
|---|---|---|
|
|
||||||
| L1 编译 | `mvn compile -pl openhis-application -am` | <30 秒 |
|
|
||||||
| L2 全链路 | 六环检查清单(见下文) | <5 分钟 |
|
|
||||||
| L3 审查 | 你人工审查 diff | 10-30 分钟 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 标准工作循环
|
|
||||||
|
|
||||||
```
|
|
||||||
开始会话
|
|
||||||
│
|
|
||||||
├→ 1. Init
|
|
||||||
│ ├── bash .harness/init.sh
|
|
||||||
│ ├── 读取 PROGRESS.md / feature_list.json
|
|
||||||
│ ├── git log --oneline -5
|
|
||||||
│ └── 确认编译通过
|
|
||||||
│
|
|
||||||
├→ 2. Plan
|
|
||||||
│ ├── update_plan / checklist_write 分解步骤
|
|
||||||
│ ├── 评估复杂度/风险
|
|
||||||
│ └── 设定检查点
|
|
||||||
│
|
|
||||||
├→ 3. Implement
|
|
||||||
│ ├── 一次只做一个功能
|
|
||||||
│ ├── 全链路检查清单核对
|
|
||||||
│ └── 增量修改,只动必要文件
|
|
||||||
│
|
|
||||||
├→ 4. Verify
|
|
||||||
│ ├── L1: mvn compile
|
|
||||||
│ ├── L2: 全链路数据流验证
|
|
||||||
│ └── 生成变更摘要
|
|
||||||
│
|
|
||||||
└→ 5. Cleanup
|
|
||||||
├── 运行 clean-state-checklist.md
|
|
||||||
├── 更新 PROGRESS.md + feature_list.json
|
|
||||||
├── git add + commit + push
|
|
||||||
└── init.sh 确认干净状态
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 全链路修复原则
|
|
||||||
|
|
||||||
修 Bug 时,不得"就事论事",必须走通完整的**数据流全链路**:
|
|
||||||
|
|
||||||
### 六环检查清单
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 录入 → 前端有无输入入口?(弹窗、行编辑、表单...)
|
|
||||||
2. 保存 → 前端 → API → Controller → Service → Entity → DB,
|
|
||||||
每个保存入口都传了该字段吗?
|
|
||||||
3. 查询 → DB → Mapper XML(UNION ALL 子查询统一加)→ DTO → 前端展示
|
|
||||||
4. 修改 → 编辑回显 → 修改保存 → 正确更新?
|
|
||||||
5. 删除 → 状态变更会丢失该字段吗?
|
|
||||||
6. 关联 → 上下游(护士站、计费、打印、报表)需要同步改吗?
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常见陷阱
|
|
||||||
|
|
||||||
| 陷阱 | 解决 |
|
|
||||||
|---|---|
|
|
||||||
| 只修主入口,批量保存/签发保存漏了 | 检查所有 Service 实现类 |
|
|
||||||
| 前端加了后端没传 | 逐个入口确认 |
|
|
||||||
| UNION ALL 只改一半 | 所有子查询统一加 |
|
|
||||||
| DTO 继承链没检查 | 检查父类/子类字段一致性 |
|
|
||||||
| 只测新增没测编辑 | 新增和编辑都要测 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 代码风格规范
|
|
||||||
|
|
||||||
### Java 后端
|
|
||||||
|
|
||||||
| 项目 | 规范 |
|
|
||||||
|---|---|
|
|
||||||
| 包结构 | `com.openhis`(业务)、`com.core`(核心) |
|
|
||||||
| 命名 | 类 PascalCase、方法 camelCase、常量 SCREAMING_SNAKE_CASE |
|
|
||||||
| 注解 | `@Slf4j`、`@Data`、`@Service/@Controller/@Repository` |
|
|
||||||
| 异常 | 统一异常处理,业务异常继承 `RuntimeException` |
|
|
||||||
| 缩进 | 4 空格,行 120 字符 |
|
|
||||||
|
|
||||||
### Vue 前端
|
|
||||||
|
|
||||||
| 项目 | 规范 |
|
|
||||||
|---|---|
|
|
||||||
| 框架 | Vue 3 + Composition API + Element Plus + Pinia |
|
|
||||||
| 命名 | 组件 PascalCase、文件 kebab-case、变量 camelCase |
|
|
||||||
| 缩进 | 2 空格,单引号,行 100 字符 |
|
|
||||||
|
|
||||||
### 导入顺序
|
### 导入顺序
|
||||||
|
#### Java
|
||||||
|
1. `java.*`
|
||||||
|
2. `javax.*`
|
||||||
|
3. 第三方库
|
||||||
|
4. `com.core.*`
|
||||||
|
5. `com.openhis.*`
|
||||||
|
6. `*.*`(其他包)
|
||||||
|
|
||||||
**Java:** `java.*` → `javax.*` → 第三方 → `com.core.*` → `com.openhis.*`
|
#### JavaScript/Vue
|
||||||
**Vue:** `vue` 相关 → 第三方 → `@/` 别名 → 相对路径
|
1. `vue` 相关
|
||||||
|
2. 第三方库
|
||||||
|
3. `@/` 别名导入
|
||||||
|
4. 相对路径导入
|
||||||
|
|
||||||
---
|
### 代码格式
|
||||||
|
#### Java
|
||||||
|
- 缩进:4个空格
|
||||||
|
- 行长度:120字符
|
||||||
|
- 左大括号不换行
|
||||||
|
|
||||||
## 🏗️ 开发约定
|
#### Vue/JavaScript
|
||||||
|
- 缩进:2个空格
|
||||||
|
- 字符串:优先使用单引号
|
||||||
|
- 行长度:100字符
|
||||||
|
|
||||||
| 领域 | 约定 |
|
## 关键配置文件
|
||||||
|---|---|
|
|
||||||
| API | RESTful,统一响应格式,Swagger 文档 |
|
|
||||||
| 数据库 | snake_case 命名,主键 `id`,软删除 `valid_flag` |
|
|
||||||
| 安全 | 所有 API 需权限验证,SQL 注入/XSS 防护 |
|
|
||||||
| 性能 | Druid 连接池,路由懒加载,虚拟滚动 |
|
|
||||||
|
|
||||||
---
|
### 后端配置
|
||||||
|
- 主配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||||
|
- 环境配置:`application-{profile}.yml`
|
||||||
|
- Maven 父 POM:`openhis-server-new/pom.xml`
|
||||||
|
|
||||||
## ⚙️ 关键配置
|
### 前端配置
|
||||||
|
- Vite 配置:`openhis-ui-vue3/vite.config.js`
|
||||||
|
- 环境变量:`.env.*` 文件
|
||||||
|
- 路由配置:`openhis-ui-vue3/src/router/index.js`
|
||||||
|
|
||||||
| 项目 | 值 |
|
## 开发约定
|
||||||
|---|---|
|
|
||||||
| 后端端口 | 18080 |
|
|
||||||
| 前端端口 | 81 |
|
|
||||||
| API 前缀 | `/openhis` |
|
|
||||||
| Swagger | `/openhis/swagger-ui/index.html` |
|
|
||||||
| 后端配置 | `application.yml` / `application-{profile}.yml` |
|
|
||||||
| 前端配置 | `vite.config.js` / `.env.*` |
|
|
||||||
|
|
||||||
---
|
### API 设计
|
||||||
|
- RESTful API 风格
|
||||||
|
- 统一响应格式
|
||||||
|
- 使用 Swagger 文档
|
||||||
|
- 错误码统一管理
|
||||||
|
|
||||||
## 📈 成熟度追踪
|
### 数据库
|
||||||
|
- 表名:snake_case
|
||||||
|
- 字段名:snake_case
|
||||||
|
- 主键:使用 `id`
|
||||||
|
- 软删除:使用 `valid_flag` 字段
|
||||||
|
|
||||||
| 等级 | 特征 | 本项目 |
|
### 前端组件
|
||||||
|---|---|---|
|
- 单一职责原则
|
||||||
| **L1 初始** | 零星使用 AI 工具 | ✅ 已超越 |
|
- Props 使用 camelCase
|
||||||
| **L2 管理** | 基础约束 + 反馈 + 控制 | ✅ **当前** |
|
- Events 使用 kebab-case
|
||||||
| **L3 定义** | 标准化、可复用 | 🔄 walkinglabs 5 子系统整合 |
|
- 使用 Composition API
|
||||||
| **L4 量化** | 数据驱动优化 | ⏳ |
|
- 组件文档使用 JSDoc
|
||||||
| **L5 优化** | AI 自主优化 Harness | ⏳ |
|
|
||||||
|
|
||||||
---
|
### 状态管理
|
||||||
|
- 模块化设计
|
||||||
|
- 异步操作使用 actions
|
||||||
|
- 避免在组件中直接修改状态
|
||||||
|
|
||||||
## 📚 技能索引(Codex 内置)
|
## 环境变量
|
||||||
|
|
||||||
| 技能 | 用途 |
|
### 前端
|
||||||
|---|---|
|
- `VITE_APP_BASE_API`: API 基础路径
|
||||||
| `$harness-engineering` | 主方法论 — 约束 + 反馈 + 控制 + 持久 |
|
- `VITE_APP_ENV`: 环境标识
|
||||||
| `$walkinglabs-harness` | 实战模式 — 5 子系统 + 模板 + 会话持续 |
|
|
||||||
| `$durable-execution` | 检查点、幂等性、事件溯源 |
|
|
||||||
| `$closed-loop-testing` | 质量门禁、测试策略、反馈循环 |
|
|
||||||
| `$constraint-design` | DSL 设计、策略模式、约束编排 |
|
|
||||||
| `$review-audit` | 审查工作流、审计追踪、合规检查 |
|
|
||||||
| `$full-chain-fix` | 全链路数据流修复 |
|
|
||||||
| `$karpathy-guidelines` | 减少 LLM 编码常见错误 |
|
|
||||||
|
|
||||||
---
|
### 后端
|
||||||
|
- `spring.profiles.active`: 激活的配置文件
|
||||||
|
- `core.name`: 应用名称
|
||||||
|
- `core.version`: 应用版本
|
||||||
|
|
||||||
> **总纲:** 你负责"做什么"和"为什么",Agent 负责"怎么做"和"做多好"
|
## 安全规范
|
||||||
> **工作循环:** Init → Plan → Implement → Verify → Cleanup
|
- 所有 API 接口需要权限验证
|
||||||
|
- 敏感信息使用环境变量
|
||||||
|
- SQL 注入防护
|
||||||
|
- XSS 攻击防护
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- 后端使用连接池(Druid)
|
||||||
|
- 前端使用路由懒加载
|
||||||
|
- 图片使用 WebP 格式
|
||||||
|
- 大列表使用虚拟滚动
|
||||||
|
|
||||||
|
## 常用工具类
|
||||||
|
- 后端:`com.core.common.utils.*`
|
||||||
|
- 前端:`@/utils/*`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
1. 修改数据库结构需要同步 SQL 脚本
|
||||||
|
2. 新增功能需要添加权限配置
|
||||||
|
3. 前端路由需要在权限系统中注册
|
||||||
|
4. 接口变更需要更新 Swagger 文档
|
||||||
|
5. 遵循现有代码风格,避免不必要的变化
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
- 后端端口:18080
|
||||||
|
- 前端端口:81
|
||||||
|
- API 前缀:`/openhis`
|
||||||
|
- Swagger UI:`/openhis/swagger-ui/index.html`
|
||||||
|
- Druid 监控:`/openhis/druid/login.html`
|
||||||
84
BUG428_ANALYSIS.md
Normal file
84
BUG428_ANALYSIS.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Bug #428 分析报告与修复记录
|
||||||
|
|
||||||
|
**标题**: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
|
||||||
|
**类型**: codeerror | **严重度**: 3 | **优先级**: 3
|
||||||
|
**提出人**: 陈显精(chenxj)
|
||||||
|
|
||||||
|
## 需求描述
|
||||||
|
|
||||||
|
医生站在为患者新增检查申请时,需实现三个联动功能:
|
||||||
|
1. **动作一**:展开右侧项目分类(如:彩超)后,下方自动加载后台维护的"检查方法"列表
|
||||||
|
2. **动作二**:勾选某个检查方法后,该项目自动填充到右侧顶部"已选择"列表
|
||||||
|
3. **动作三**:在"已选择"列表中点击展开图标,展示该套餐包含的收费明细
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 动作一(分类联动加载检查方法):✅ 已实现
|
||||||
|
- `handleCollapseChange`(第949行)→ `handleCategoryExpand`(第913行)→ `searchCheckMethod({ checkType: cat.typeName })`
|
||||||
|
- 代码路径完整,数据解析正确,Vue 响应式绑定正确
|
||||||
|
|
||||||
|
### 动作二(勾选方法后填充到"已选择"列表):❌ 存在根因缺陷
|
||||||
|
**根因位置**:`handleMethodSelect` 函数第1373行
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const targetItem = cat.items[0]; // ← 根因:硬编码假设分类下必有 items
|
||||||
|
if (!targetItem) {
|
||||||
|
console.warn('分类下没有检查项目,无法关联方法');
|
||||||
|
return; // ← 当分类下没有 items 时直接返回,不执行任何操作
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题链**:
|
||||||
|
1. 用户展开分类 → 检查方法列表加载成功(动作一 OK)
|
||||||
|
2. 用户勾选检查方法 → `handleMethodSelect(checked, method, cat)` 被调用
|
||||||
|
3. 代码使用 `cat.items[0]` 作为目标项目,但很多分类**没有 items(检查部位)**,只有 methods(检查方法)
|
||||||
|
4. 当 `cat.items` 为空数组时,`targetItem` 为 `undefined`,函数在第1377行直接 `return`
|
||||||
|
5. 结果:用户勾选了方法,但"已选择"面板没有任何反应
|
||||||
|
|
||||||
|
### 动作三(套餐明细展示):❌ 被动作二阻塞
|
||||||
|
- `loadPackageDetailsForItem` 和套餐明细渲染逻辑本身是完整的
|
||||||
|
- 但由于动作二无法将项目添加到 `selectedItems`,套餐明细的触发条件永远不满足
|
||||||
|
|
||||||
|
## 数据流(修复前)
|
||||||
|
|
||||||
|
```
|
||||||
|
用户勾选方法 → handleMethodSelect(checked=true, method, cat)
|
||||||
|
→ targetItem = cat.items[0] ← 根因:可能为 undefined
|
||||||
|
→ if (!targetItem) return; ← 直接退出,什么都不做
|
||||||
|
→ ❌ selectedItems 不变
|
||||||
|
→ ❌ 右侧"已选择"面板无反应
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据流(修复后)
|
||||||
|
|
||||||
|
```
|
||||||
|
用户勾选方法 → handleMethodSelect(checked=true, method, cat)
|
||||||
|
→ targetItem = cat.items[0]
|
||||||
|
→ if (!targetItem) {
|
||||||
|
targetItem = { ← 修复:以方法自身作为项目
|
||||||
|
id: method.id, name: method.name,
|
||||||
|
price: method.packagePrice || method.price || 0,
|
||||||
|
packageId: method.packageId, packageName: method.packageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
→ ✅ 正常创建 selectedItems 条目
|
||||||
|
→ ✅ 右侧"已选择"面板正确显示
|
||||||
|
→ ✅ 如有套餐 → loadPackageDetailsForItem → 动作三正常触发
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
**文件**:`openhis-ui-vue3/src/views/doctorstation/components/examination/examinationApplication.vue`
|
||||||
|
**改动**:`handleMethodSelect` 函数第1370-1378行
|
||||||
|
|
||||||
|
将硬编码的 `cat.items[0]` + 直接 return 改为降级策略:
|
||||||
|
- 当分类下有 items 时,使用 `cat.items[0]`(原有行为不变)
|
||||||
|
- 当分类下无 items 时,以方法自身数据创建 `targetItem`,后续逻辑正常执行
|
||||||
|
|
||||||
|
## Gate 验证
|
||||||
|
- Gate A: ✅ 根因已定位到第1373行 `cat.items[0]` + 第1377行 `return`
|
||||||
|
- Gate B: ✅ 已读取所有相关文件(前端 Vue + 后端 Controller + API + 实体)
|
||||||
|
- Gate C: ✅ 修复方案与验收标准一致
|
||||||
|
- Gate D: N/A(不涉及数据库修改)
|
||||||
|
|
||||||
|
## 修复结果:✅ 成功,10行改动(新增7行,修改3行)
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
# Bug #540 分析报告
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
【住院医生站-检查申请】详情页弹窗中"申请单描述"区域缺少临床必要信息显示
|
|
||||||
|
|
||||||
## 数据流分析
|
|
||||||
|
|
||||||
### 前端组件
|
|
||||||
- 入口: `src/views/inpatientDoctor/home/index.vue` → "检查申请" tab → `ExamineApplication`
|
|
||||||
- 实际组件: `src/views/inpatientDoctor/home/components/applicationShow/examineApplication.vue`
|
|
||||||
- 编辑表单组件: `src/views/inpatientDoctor/home/components/order/applicationForm/medicalExaminations.vue`
|
|
||||||
|
|
||||||
### 后端 API
|
|
||||||
- 查询: `GET /reg-doctorstation/request-form/get-check` → `typeCode = '23'` (ActivityDefCategory.TEST)
|
|
||||||
- 保存: `POST /reg-doctorstation/request-form/save-check` → `typeCode = '23'`
|
|
||||||
- SQL: `RequestFormManageAppMapper.xml` 的 `getRequestForm` 查询,SELECT `drf.desc_json`
|
|
||||||
- DTO: `RequestFormQueryDto` 有 `descJson` 字段 (String 类型)
|
|
||||||
|
|
||||||
### 数据库
|
|
||||||
- 表: `doc_request_form`,type_code = '23' 的记录 desc_json 均有数据
|
|
||||||
- descJson 包含: targetDepartment, urgencyLevel, symptom, sign, clinicalDiagnosis, otherDiagnosis, relatedResult, attention, examinationPurpose, medicalHistorySummary, allergyHistory, expectedExaminationTime 等
|
|
||||||
|
|
||||||
## 根因定位
|
|
||||||
|
|
||||||
对比检验申请 (testApplication.vue) 和检查申请 (examineApplication.vue) 的详情弹窗中"申请单描述"区域的渲染逻辑:
|
|
||||||
|
|
||||||
**testApplication.vue (检验申请) - 正确:**
|
|
||||||
```vue
|
|
||||||
<template v-for="(value, key) in descJsonData" :key="key">
|
|
||||||
<el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)">
|
|
||||||
{{ value || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
- 遍历 `descJsonData` 的所有 key,只要 key 在 labelMap 中就显示
|
|
||||||
- 空值显示为 '-'
|
|
||||||
|
|
||||||
**examineApplication.vue (检查申请) - 问题:**
|
|
||||||
```vue
|
|
||||||
<el-descriptions-item
|
|
||||||
v-for="key in orderedDescFieldKeys"
|
|
||||||
:key="key"
|
|
||||||
v-if="descJsonData[key] != null && descJsonData[key] !== ''"
|
|
||||||
:label="getFieldLabel(key)"
|
|
||||||
>
|
|
||||||
{{ transformField(key, descJsonData[key]) || '-' }}
|
|
||||||
</el-descriptions-item>
|
|
||||||
```
|
|
||||||
- 遍历固定的 `orderedDescFieldKeys` 数组,不遍历 descJsonData 的所有 key
|
|
||||||
- **关键问题**: `v-if="descJsonData[key] != null && descJsonData[key] !== ''"` 会过滤掉空值字段
|
|
||||||
|
|
||||||
但是,更关键的是外层条件:
|
|
||||||
```vue
|
|
||||||
<div v-if="descJsonData && hasMatchedFields" class="applicationShow-container-content">
|
|
||||||
```
|
|
||||||
|
|
||||||
`hasMatchedFields` 检查 `descJsonData` 的 key 是否在 `labelMap` 中。`labelMap` 包含所有需要显示的字段。
|
|
||||||
|
|
||||||
**实际根因**:通过对比 testApplication.vue 与 examineApplication.vue,发现两个组件在 "申请单描述" 区域的渲染方式不同。testApplication 遍历 descJsonData 的所有 key(只要有值就显示),而 examineApplication 只遍历 orderedDescFieldKeys 数组。
|
|
||||||
|
|
||||||
**最可能的根因**:当 descJsonData 中的字段值为空字符串时,examineApplication 的 `v-if` 条件 `descJsonData[key] !== ''` 会过滤掉该字段(整行不显示),而 testApplication 会显示该字段标签并填入 `-`。
|
|
||||||
|
|
||||||
对于 `targetDepartment` 字段,`recursionFun` 函数在科室列表中找不到对应 ID 时会返回空字符串 `''`,导致 `targetDepartment` 被过滤不显示。
|
|
||||||
|
|
||||||
**但核心问题是**:如果 descJsonData 存在但某些字段为空,这些字段会被完全隐藏而不是显示 `-`。用户期望看到的是字段标签+占位符 `-`,而不是整个字段不显示。
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
将 examineApplication.vue 中"申请单描述"区域的渲染方式改为与 testApplication.vue 一致:
|
|
||||||
1. 遍历 `descJsonData` 的所有 key(而非固定 orderedDescFieldKeys)
|
|
||||||
2. 使用 `isFieldMatched(key)` 过滤需要显示的字段
|
|
||||||
3. 空值显示为 `-`(而非完全隐藏)
|
|
||||||
|
|
||||||
同时保留 `orderedDescFieldKeys` 用于打印功能(已有代码使用)。
|
|
||||||
|
|
||||||
## 变更文件
|
|
||||||
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/examineApplication.vue`(前端模板修改)
|
|
||||||
|
|
||||||
修复结果:✅ 成功,5行改动(+5/-8)
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Bug #522 分析报告
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
[住院护士站-三测单] 体征录入点击保存后缺乏执行反馈且窗口异常自动关闭
|
|
||||||
|
|
||||||
## 涉及文件
|
|
||||||
- 前端: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/components/addTprDialog.vue`
|
|
||||||
- API: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/components/api.js`
|
|
||||||
- 父组件: `openhis-ui-vue3/src/views/inpatientNurse/tprChart/index.vue`
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
|
|
||||||
### 问题1:弹窗异常自动关闭 — 根因
|
|
||||||
|
|
||||||
在 `addTprDialog.vue` 模板中,保存按钮使用了 `:disabled="buttonDisabled"`(第50行和第108行),但 **`buttonDisabled` 变量在整个 script setup 中从未声明**。
|
|
||||||
|
|
||||||
在 Vue 3 `<script setup>` + Composition API 中,模板引用的变量必须在 script 中声明。未声明的变量会触发 `ReferenceError`,导致组件渲染失败或运行时异常。这个错误会破坏组件的响应式系统,使得 `dialogVisible` 的响应式绑定失效,从而导致弹窗在保存操作后异常关闭。
|
|
||||||
|
|
||||||
### 问题2:缺乏保存成功反馈 — 连带结果
|
|
||||||
|
|
||||||
虽然 `confirmCharge()` 函数在第1087行已有 `proxy.$modal.msgSuccess('保存成功')` 的调用,但由于 `buttonDisabled` 未声明引发的异常,导致代码执行路径被破坏,success 回调中的提示逻辑可能未能正常执行。
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
1. **在 `addTprDialog.vue` 的 script setup 中新增 `buttonDisabled` ref 声明**,初始值为 `false`
|
|
||||||
2. **在保存操作中添加 loading 状态**:点击保存后将按钮禁用,API 返回后恢复,防止重复提交的同时也保证了响应式状态的一致性
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
- [ ] 点击保存后弹窗保持开启状态
|
|
||||||
- [ ] 保存成功后弹出"保存成功"提示
|
|
||||||
- [ ] 左侧体征历史记录列表自动刷新
|
|
||||||
- [ ] 录入区域表单被清空,方便继续录入下一条
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Bug #539 分析报告
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
住院护士站点击后只有一个标签可见,缺少入出转管理、护理记录等功能模块。
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
|
|
||||||
### 数据库菜单结构
|
|
||||||
`hisdev.sys_menu` 中,住院护士站(menu_id=295)是**目录类型(M)**,没有 component 字段。
|
|
||||||
|
|
||||||
其下有多个子菜单(门户、入出转管理、护理记录、三测单等),都分配给了护士角色。
|
|
||||||
|
|
||||||
### 问题核心
|
|
||||||
1. 菜单 295(住院护士站)类型为 M(目录),点击后侧边栏展开为子菜单列表。
|
|
||||||
2. 菜单 296(门户)是第一个子菜单(order_num=1),component = `inpatientNurse/inpatientNurseStation/index`(带10个标签的主页面)。
|
|
||||||
3. 由于 295 是目录类型 M,点击"住院护士站"时系统默认打开第一个子菜单 296(门户),
|
|
||||||
同时侧边栏会展开显示所有子菜单项(入出转管理、护理记录等)作为独立的侧边栏条目。
|
|
||||||
4. **用户体验问题**:侧边栏展开后,"住院护士站"变成了一个可展开的目录,用户看到的是子菜单列表而非标签页导航。
|
|
||||||
门户(菜单296)加载了带标签的主页面,但侧边栏中额外的子菜单条目让用户困惑,以为"只有一个标签"。
|
|
||||||
|
|
||||||
### 结论
|
|
||||||
根本原因:菜单 295(住院护士站)为目录类型(M),应改为菜单类型(C)并设置 component。
|
|
||||||
改为 C 后,点击"住院护士站"直接加载 `inpatientNurseStation/index.vue`(带10个功能标签的主页面),
|
|
||||||
侧边栏不再展开子菜单,用户通过页面内的 el-tabs 切换各功能模块。
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
将菜单 295 的 menu_type 从 'M' 改为 'C',component 设置为 `inpatientNurse/inpatientNurseStation/index`。
|
|
||||||
|
|
||||||
## 修复结果
|
|
||||||
|
|
||||||
### 已执行操作(2026-05-18)
|
|
||||||
1. `UPDATE hisdev.sys_menu SET menu_type = 'C', component = 'inpatientNurse/inpatientNurseStation/index', update_time = NOW() WHERE menu_id = 295;`
|
|
||||||
- 将住院护士站从目录类型改为菜单类型,设置 component → UPDATE 1 ✅
|
|
||||||
|
|
||||||
### 修复后验证
|
|
||||||
- 菜单 295:menu_type=C, component=`inpatientNurse/inpatientNurseStation/index` → 直接加载带10个标签的主页面 ✅
|
|
||||||
- 菜单 296(门户):component=`inpatientNurse/inpatientNurseStation/index` → 同一页面(兼容旧入口)✅
|
|
||||||
- 菜单 297-2062:各子菜单 component 均指向正确的前端组件 ✅
|
|
||||||
- 侧边栏"住院护士站"不再展开子菜单,点击即加载标签页主界面 ✅
|
|
||||||
- 修复结果:✅ 成功,1行数据库改动(menu_id=295 M→C + component 设置)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# 分析报告 — Bug #469
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
检验申请列表的【操作】列仅显示固定的"打印"和"删除"按钮,未根据申请单状态动态切换操作权限。
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
文件 `openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue` 第97-104行:
|
|
||||||
- 操作列模板中固定渲染"打印"和"删除"按钮,没有任何状态判断逻辑
|
|
||||||
- 缺少"修改"和"撤回"按钮
|
|
||||||
|
|
||||||
## 状态机设计
|
|
||||||
| 状态 | 条件 | 允许的操作 |
|
|
||||||
|------|------|-----------|
|
|
||||||
| 待开立 | applyStatus == 0 | 修改、删除 |
|
|
||||||
| 已开立 | applyStatus == 1 && needExecute != true | 撤回 |
|
|
||||||
| 已执行 | applyStatus == 1 && needExecute == true | 无(仅打印) |
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
1. **前端 Vue**: 操作列改为 `v-if` 条件渲染按钮(修改/删除/撤回/打印)
|
|
||||||
2. **前端 API**: 新增撤回接口 `withdrawInspectionApplication(applyNo)`
|
|
||||||
3. **后端 Controller**: 新增 `POST /withdraw/{applyNo}` 端点
|
|
||||||
4. **后端 Service**: 新增 `withdrawInspectionLabApply` 方法,将 applyStatus 置回 0,needRefund/needExecute 置回 false
|
|
||||||
|
|
||||||
## 修复结果
|
|
||||||
✅ 成功,共14行改动(2个commit完成)
|
|
||||||
|
|
||||||
### 修复详情
|
|
||||||
1. **commit c643a78b** - 初始修复:将操作列从静态"打印/删除"改为基于状态的动态按钮(修改/删除/撤回/详情),10行改动
|
|
||||||
2. **commit f369ea41** - 跟进修复:将"详情"按钮包裹在 `<template v-else>` 中,避免对所有状态始终渲染,4行改动
|
|
||||||
|
|
||||||
### 状态机实现
|
|
||||||
| 状态 | 条件 | 显示按钮 |
|
|
||||||
|------|------|---------|
|
|
||||||
| 待签发 | billStatus == '0' | 修改 + 删除 |
|
|
||||||
| 已签发 | billStatus == '1' | 撤回 |
|
|
||||||
| 其他状态 | 已采证/已送检/报告已出/已作废 | 详情 |
|
|
||||||
|
|
||||||
### 涉及文件
|
|
||||||
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/testApplication.vue` - 前端操作列动态按钮
|
|
||||||
- `openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/api.js` - 前端API(deleteRequestForm, withdrawRequestForm)
|
|
||||||
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/regdoctorstation/controller/RequestFormManageController.java` - 后端Controller(/delete, /withdraw 端点)
|
|
||||||
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/regdoctorstation/appservice/impl/RequestFormManageAppServiceImpl.java` - 后端Service实现
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# 修复报告 — Bug #537
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
- **标题**: [住院医生工作站] 冗余功能显示:需在医生工作站页签中屏蔽"汇总发药申请"模块
|
|
||||||
- **问题**: 住院医生工作站的标签页菜单中可见"汇总发药申请"模块,该职能属于护士汇总提交领药单环节,医生不应可见
|
|
||||||
|
|
||||||
## 根因分析
|
|
||||||
"汇总发药申请"功能属于护士工作站,但错误地暴露在住院医生工作站界面中,存在以下问题:
|
|
||||||
1. `inpatientDoctor/home/index.vue` 中存在注释掉的 tab-pane(已屏蔽但仍残留死代码)
|
|
||||||
2. `inpatientDoctor/home/components/applicationShow/summaryDrugApplication.vue` 组件文件存在(引用了护士站的 MedicationSummary 组件)
|
|
||||||
3. `inpatientNurse/constants/navigation.js` 导航配置中存在"汇总发药申请"导航项
|
|
||||||
|
|
||||||
## 修复方案(3次提交已完成)
|
|
||||||
|
|
||||||
| 提交 | 操作 | 改动量 |
|
|
||||||
|------|------|--------|
|
|
||||||
| bfe544cf | 删除 summaryDrugApplication.vue 组件文件 | -20行 |
|
|
||||||
| 4809b357 | 移除 index.vue 中注释掉的 tab-pane 和引用 | -3行 |
|
|
||||||
| e6a61ea5 | 移除 navigation.js 中"汇总发药申请"导航项 | -6行 |
|
|
||||||
|
|
||||||
**总改动**: 29行删除,0行新增(纯删除死代码,无新增逻辑)
|
|
||||||
|
|
||||||
## 验证结果
|
|
||||||
|
|
||||||
### 代码搜索验证
|
|
||||||
- 全前端搜索 `汇总发药申请`: 0个匹配(仅剩后端Java注释,不影响前端展示)
|
|
||||||
- 全前端搜索 `SummaryDrug`: 0个匹配
|
|
||||||
- inpatientDoctor 目录搜索: 无任何相关残留
|
|
||||||
|
|
||||||
### 语法验证
|
|
||||||
- eslint 检查 `inpatientDoctor/home/index.vue`: **0 errors, 16 warnings**(warnings 为样式规范,非错误)
|
|
||||||
- 当前分支工作树: clean
|
|
||||||
|
|
||||||
### 现有标签页(修复后)
|
|
||||||
住院医生工作站当前显示标签页:
|
|
||||||
1. 住院病历
|
|
||||||
2. 诊断录入
|
|
||||||
3. 临床医嘱
|
|
||||||
4. 检验申请
|
|
||||||
5. 检查申请
|
|
||||||
6. 手术申请
|
|
||||||
7. 输血申请
|
|
||||||
8. 报告查询
|
|
||||||
|
|
||||||
**确认**: "汇总发药申请"标签页不存在于以上列表。
|
|
||||||
|
|
||||||
## 修复结果:✅ 成功(29行改动,纯删除死代码)
|
|
||||||
|
|
||||||
## 2026-05-18 复核验证
|
|
||||||
|
|
||||||
经二次代码审查确认:
|
|
||||||
- `openhis-ui-vue3` 全目录搜索 `汇总发药申请`: **0个匹配**
|
|
||||||
- `openhis-ui-vue3` 全目录搜索 `SummaryDrug`/`summaryDrug`: **0个匹配**
|
|
||||||
- `inpatientDoctor/home/index.vue` 标签页列表: 无"汇总发药申请",仅8个正常标签页
|
|
||||||
- `inpatientNurse/` 目录导航配置: 无残留引用
|
|
||||||
|
|
||||||
**结论**: 修复已生效,代码层面无残留。Bug在禅道中仍为active状态,需手动标记为resolved(API脚本的resolve_bug功能未实现)。
|
|
||||||
|
|
||||||
## 2026-05-18 最终复核
|
|
||||||
|
|
||||||
经再次验证确认:
|
|
||||||
- `inpatientDoctor/home/index.vue` 标签页列表: 仅8个正常标签页,无"汇总发药申请"
|
|
||||||
- `inpatientNurse/constants/navigation.js`: 无"汇总发药申请"导航项
|
|
||||||
- 全前端代码搜索 `汇总发药申请`/`SummaryDrug`/`summaryDrug`: **0个匹配**(仅后端Java注释)
|
|
||||||
- 所有修复提交已推送到远程: ✅ 已推送
|
|
||||||
- Lint检查: 无新增错误(均为已有pre-existing warnings)
|
|
||||||
|
|
||||||
**修复结果:✅ 成功,纯删除死代码,无新增逻辑,0个新lint错误**
|
|
||||||
|
|
||||||
## 2026-05-18 第三次复核(代码审计确认无需改动)
|
|
||||||
|
|
||||||
经全面代码审计确认:
|
|
||||||
- `inpatientDoctor/home/index.vue` 标签页列表: 仅8个正常标签页(住院病历、诊断录入、临床医嘱、检验申请、检查申请、手术申请、输血申请、报告查询),无"汇总发药申请"
|
|
||||||
- `inpatientNurse/constants/navigation.js`: 6个护士导航项,无"汇总发药申请"
|
|
||||||
- `openhis-ui-vue3` 全目录搜索 `汇总发药申请`: 仅1处API注释(`drug/inpatientMedicationDispensing/components/api.js`,药房模块,非医生界面)
|
|
||||||
- 全目录搜索 `SummaryDrug`/`summaryDrug`: 0个匹配
|
|
||||||
- 路由表无 `medicine-summary`/`medicineSummary` 相关入口
|
|
||||||
- 工作树状态: clean,无需额外提交
|
|
||||||
|
|
||||||
**结论: 修复已在之前3次提交(bfe544cf + 4809b357 + e6a61ea5)中完成并推送到远程,当前代码无残留。无需任何额外改动。**
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Bug #547 分析报告
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
在"系统管理-执行科室配置"页面,选择科室(如检验科)后添加新项目并保存,显示"与未知科室时间冲突"错误。
|
|
||||||
|
|
||||||
## 根因定位
|
|
||||||
|
|
||||||
**核心问题在 `OrganizationLocationAppServiceImpl.java:161-174`**
|
|
||||||
|
|
||||||
时间冲突检测的查询逻辑存在两个缺陷:
|
|
||||||
|
|
||||||
### 缺陷1:查询范围过窄
|
|
||||||
```java
|
|
||||||
// 只查同一科室 + 同一诊疗的记录
|
|
||||||
getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(), orgLoc.getActivityDefinitionId());
|
|
||||||
```
|
|
||||||
只查询**同一科室**的记录。如果同一诊疗项目在其他科室已有配置且时间重叠,不会被当前查询检测到。但系统本应阻止同一诊疗在多个科室同时段执行。
|
|
||||||
|
|
||||||
### 缺陷2:"未知科室"错误提示
|
|
||||||
当冲突记录关联的科室被软删除(`delete_flag='1'`)时,`organizationService.getById()` 受 `@TableLogic` 注解影响查不到该科室,返回 null,错误提示变成"与未知科室时间冲突"。
|
|
||||||
|
|
||||||
数据库验证发现确实存在软删除科室的组织位置记录(内科门诊、上海学校医院、信息科等,共9条)。
|
|
||||||
|
|
||||||
### 数据流
|
|
||||||
|
|
||||||
1. 前端选择科室 → 点击"添加新项目" → 填写诊疗和时间 → 点击"保存"
|
|
||||||
2. 后端 `addOrEditOrgLoc()` 接收请求
|
|
||||||
3. 查询现有冲突记录(**当前只查同科室**)
|
|
||||||
4. 对冲突记录检查时间重叠
|
|
||||||
5. 查找冲突科室名称 → 若科室被软删除则返回 null → "未知科室"
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
1. **修改冲突检测范围**:查询同一 `activityDefinitionId` 的所有记录(跨科室检测),而非仅限当前科室
|
|
||||||
2. **优雅处理"未知科室"**:当 `getById` 返回 null 时,使用 "已删除科室( ID )" 替代 "未知科室",提供更有用的信息
|
|
||||||
3. **新增 Service 方法**:`getOrgLocListByActivityDefinitionId(Long activityDefinitionId)` 用于按诊疗定义查询所有记录
|
|
||||||
|
|
||||||
## 涉及文件
|
|
||||||
- `openhis-server-new/openhis-application/src/main/java/com/openhis/web/basedatamanage/appservice/impl/OrganizationLocationAppServiceImpl.java`
|
|
||||||
- `openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/IOrganizationLocationService.java`
|
|
||||||
- `openhis-server-new/openhis-domain/src/main/java/com/openhis/administration/service/impl/OrganizationLocationServiceImpl.java`
|
|
||||||
2
his-repo
2
his-repo
Submodule his-repo updated: 515ed84118...414c204578
@@ -1,30 +0,0 @@
|
|||||||
# Bug #444 分析报告
|
|
||||||
|
|
||||||
## Bug 描述
|
|
||||||
生成临时医嘱界面,"已引用计费药品"列表未正常显示药品详细名称信息。具体表现为:
|
|
||||||
- 列表中出现了"小腿烧伤扩创交腿皮瓣修复术"(属于手术诊疗项目)
|
|
||||||
- 列表中出现了"心脏彩色多普勒超声"(属于检查/诊疗项目)
|
|
||||||
- 非药品类计费信息错误地混入"已引用计费药品"列表
|
|
||||||
|
|
||||||
## 根因定位
|
|
||||||
**文件**: `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
|
|
||||||
**行号**: 1580 (handleMedicalAdvice), 1864 (handleQuoteBilling), 1850 (handleTemporaryMedicalRefresh)
|
|
||||||
|
|
||||||
三处过滤逻辑均使用:
|
|
||||||
```javascript
|
|
||||||
if (item.adviceType !== 1) return false;
|
|
||||||
```
|
|
||||||
|
|
||||||
**问题1(主因)**: `adviceType` 字段命名兼容不完整。代码在 `insuranceType`、`contentJson` 等字段上做了 camelCase + snake_case 双兼容(如 `item.insuranceType || item.insurance_type`),但 `adviceType` 只检查了 camelCase。若后端返回 snake_case 数据(`advice_type`),`item.adviceType` 为 `undefined`,`undefined !== 1` 为 `true`,导致所有非药品项目全部放行。
|
|
||||||
|
|
||||||
**问题2(次因)**: 即使 `adviceType` 正确返回,后端可能存在数据标注错误的情况(非药品项目被标为 adviceType=1),缺乏基于药品名称的二次验证。
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
1. `adviceType` 检查增加 snake_case 回退:`const at = item.adviceType ?? item.advice_type; if (at !== 1) return false;`
|
|
||||||
2. 增加药品名称关键字二次过滤:排除名称中包含"术"、"检查"、"超声"、"多普勒"等关键词的非药品项目
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
1. "已引用计费药品"列表中只显示药品类项目
|
|
||||||
2. 不显示手术诊疗项目(如"小腿烧伤扩创交腿皮瓣修复术")
|
|
||||||
3. 不显示检查项目(如"心脏彩色多普勒超声")
|
|
||||||
4. 药品名称正常显示
|
|
||||||
@@ -132,22 +132,7 @@ temporaryAdvices.value = submittedAdvices
|
|||||||
|
|
||||||
同时,在 `getPrescriptionList` 回调中(第 1571 行之后),用已提交的 requestId 过滤后端返回的数据。
|
同时,在 `getPrescriptionList` 回调中(第 1571 行之后),用已提交的 requestId 过滤后端返回的数据。
|
||||||
|
|
||||||
## 修复结果
|
## 总结
|
||||||
|
|
||||||
### 实际根因
|
- **根因**:`handleMedicalAdvice` 每次打开都清空 `temporaryAdvices`,然后从后端重新拉取数据。但后端返回的新创建医嘱项可能没有 `requestId`,导致无法过滤。
|
||||||
`handleQuoteBilling` 函数中:
|
- **修复**:保留已提交(有 requestId)的医嘱数据,不清空;同时用这些 requestId 过滤后端返回的新数据。
|
||||||
1. **第1856行**:在调用 `getPrescriptionList` 之前先清空了 `temporaryAdvices.value = []`
|
|
||||||
2. **第1997-2019行(旧代码)**:ID 匹配过滤逻辑依赖已被清空的 `temporaryAdvices.value`,因此过滤形同虚设
|
|
||||||
3. 即使 `temporaryAdvices` 未被清空,ID 匹配也不可靠(新生成的医嘱可能没有 `requestId`/`chargeItemId`/`id`)
|
|
||||||
|
|
||||||
### 修复方案
|
|
||||||
1. 在清空 `temporaryAdvices` **之前**,提取已提交项目的复合键(名称+规格+数量)保存到 `submittedKeysBeforeClear`
|
|
||||||
2. 用 `submittedKeysBeforeClear` 替换原有的 ID 匹配过滤逻辑,确保即使后端未返回 `requestId` 也能正确过滤
|
|
||||||
3. 复合键匹配策略与 `handleTemporaryMedicalSubmit` 中使用的策略一致
|
|
||||||
|
|
||||||
### 修改文件
|
|
||||||
- `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
|
|
||||||
- 第1853-1864行:新增 `submittedKeysBeforeClear` 提取逻辑
|
|
||||||
- 第1997-2004行:替换 ID 匹配为复合键匹配
|
|
||||||
|
|
||||||
### 修复结果:✅ 成功,~20行改动(+20/-21)
|
|
||||||
|
|||||||
18
md/bug-analysis/bug469-analysis.md
Normal file
18
md/bug-analysis/bug469-analysis.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
### Bug #469 分析报告
|
||||||
|
|
||||||
|
**标题**: [住院医生工作站-检验申请] 完善【操作】列临床业务逻辑:支持按状态动态切换修改、删除、撤回等功能
|
||||||
|
|
||||||
|
**根因**: 操作列(`testApplication.vue` 第 108-122 行)模板中,"详情"按钮 `<el-button>` 位于 `v-if`/`v-else-if` 条件块之外,作为独立元素始终渲染。导致:
|
||||||
|
- 待签发状态(status=0/null):显示 "修改 删除 **详情**" 三个按钮(应仅显示"修改 删除")
|
||||||
|
- 已签发状态(status=1):显示 "撤回 **详情**" 两个按钮(应仅显示"撤回")
|
||||||
|
- 其他状态(2/3/4/6/7):仅显示"详情"(正确)
|
||||||
|
|
||||||
|
**数据流**:
|
||||||
|
- 前端: `testApplication.vue` → 操作列 template → 条件判断 `scope.row.status`
|
||||||
|
- 后端 SQL: `RequestFormManageAppMapper.xml` 中 `computed_status` CASE 表达式将 `status_enum` 映射为前端显示码(0=待签发, 1=已签发, 6=已出报告, 7=已作废)
|
||||||
|
- 后端删除: `RequestFormManageAppServiceImpl.deleteRequestForm` 校验 `RequestStatus.DRAFT` (status_enum=1)
|
||||||
|
- 后端撤回: `RequestFormManageAppServiceImpl.withdrawRequestForm` 校验 `RequestStatus.ACTIVE` (status_enum=2)
|
||||||
|
|
||||||
|
**修复方案**: 将"详情"按钮包裹在 `<template v-else>` 中,形成完整的 `v-if` / `v-else-if` / `v-else` 三分支结构,确保每个状态仅显示对应的操作按钮。
|
||||||
|
|
||||||
|
**修复结果:✅ 成功,4行改动**(1行删除,3行新增:`<template v-else>` + 按钮 + `</template>`)
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.core.framework.config;
|
package com.core.framework.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
@@ -35,9 +34,7 @@ public class ApplicationConfig {
|
|||||||
// 设置日期格式为 yyyy/M/d HH:mm:ss,支持多种格式反序列化
|
// 设置日期格式为 yyyy/M/d HH:mm:ss,支持多种格式反序列化
|
||||||
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
|
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
|
||||||
// 添加JavaTimeModule支持,用于LocalDateTime
|
// 添加JavaTimeModule支持,用于LocalDateTime
|
||||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
builder.modules(new JavaTimeModule());
|
||||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
|
||||||
builder.modules(javaTimeModule);
|
|
||||||
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
|
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.common.enums.SlotStatus;
|
import com.openhis.common.constant.CommonConstants;
|
||||||
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
||||||
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
|
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
|
||||||
import com.openhis.appointmentmanage.domain.SchedulePool;
|
import com.openhis.appointmentmanage.domain.SchedulePool;
|
||||||
@@ -502,8 +502,8 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
// 该排班下存在有效患者预约(号源槽:已预约/已锁定/已取号)则禁止删除;已退号、仅可用/已取消槽位不计入
|
// 该排班下存在有效患者预约(号源槽:已预约/已锁定/已取号)则禁止删除;已退号、仅可用/已取消槽位不计入
|
||||||
long appointmentCount = scheduleSlotService.count(new QueryWrapper<ScheduleSlot>()
|
long appointmentCount = scheduleSlotService.count(new QueryWrapper<ScheduleSlot>()
|
||||||
.in("pool_id", poolIds)
|
.in("pool_id", poolIds)
|
||||||
.in("status", SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue(),
|
.in("status", CommonConstants.SlotStatus.BOOKED, CommonConstants.SlotStatus.LOCKED,
|
||||||
SlotStatus.CHECKED_IN.getValue()));
|
CommonConstants.SlotStatus.CHECKED_IN));
|
||||||
if (appointmentCount > 0) {
|
if (appointmentCount > 0) {
|
||||||
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。");
|
return R.fail("该排班已有患者预约,禁止删除!如需取消请先处理患者退预约或使用'停诊'功能。");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.openhis.clinical.domain.Ticket;
|
|||||||
import com.openhis.clinical.service.ITicketService;
|
import com.openhis.clinical.service.ITicketService;
|
||||||
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
|
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
|
||||||
import com.openhis.web.appointmentmanage.dto.TicketDto;
|
import com.openhis.web.appointmentmanage.dto.TicketDto;
|
||||||
import com.openhis.common.enums.SlotStatus;
|
import com.openhis.common.constant.CommonConstants.SlotStatus;
|
||||||
import com.openhis.common.enums.OrderStatus;
|
import com.openhis.common.enums.OrderStatus;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -193,24 +193,25 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else {
|
} else {
|
||||||
SlotStatus status = SlotStatus.getByValue(raw.getSlotStatus());
|
Integer slotStatus = raw.getSlotStatus();
|
||||||
if (status != null) {
|
if (slotStatus != null) {
|
||||||
if (status == SlotStatus.LOCKED) {
|
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已取号");
|
||||||
|
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
|
||||||
|
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
|
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
|
dto.setStatus("系统取消");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("已锁定");
|
dto.setStatus("已预约");
|
||||||
}
|
}
|
||||||
} else if (status == SlotStatus.BOOKED) {
|
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
|
||||||
dto.setStatus("已退号");
|
|
||||||
} else {
|
|
||||||
dto.setStatus("已取号");
|
|
||||||
}
|
|
||||||
} else if (status == SlotStatus.CANCELLED) {
|
|
||||||
dto.setStatus("已停诊");
|
|
||||||
} else if (status == SlotStatus.RETURNED) {
|
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
|
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已停诊");
|
||||||
|
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已锁定");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("未预约");
|
dto.setStatus("未预约");
|
||||||
}
|
}
|
||||||
@@ -236,10 +237,6 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
/**
|
/**
|
||||||
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
|
* 统一状态入参,避免前端状态值大小写/中文/数字差异导致 SQL 条件失效后回全量数据
|
||||||
*/
|
*/
|
||||||
/**
|
|
||||||
* 规范前端传入的状态查询参数,映射到 SQL 的 slotStatusNormExpr 值。
|
|
||||||
* 数值映射: 0=待约 1=已约(签到后) 2=锁定(预约后) 3=已签到 4=已停诊 5=已退号
|
|
||||||
*/
|
|
||||||
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
|
private void normalizeQueryStatus(com.openhis.appointmentmanage.dto.TicketQueryDTO query) {
|
||||||
String rawStatus = query.getStatus();
|
String rawStatus = query.getStatus();
|
||||||
if (rawStatus == null) {
|
if (rawStatus == null) {
|
||||||
@@ -266,31 +263,28 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
case "已预约":
|
case "已预约":
|
||||||
query.setStatus("booked");
|
query.setStatus("booked");
|
||||||
break;
|
break;
|
||||||
case "locked":
|
|
||||||
case "2":
|
|
||||||
case "已锁定":
|
|
||||||
query.setStatus("locked");
|
|
||||||
break;
|
|
||||||
case "checked":
|
case "checked":
|
||||||
case "checkin":
|
case "checkin":
|
||||||
case "checkedin":
|
case "checkedin":
|
||||||
case "3":
|
case "2":
|
||||||
case "已取号":
|
case "已取号":
|
||||||
query.setStatus("checked");
|
query.setStatus("checked");
|
||||||
break;
|
break;
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
case "canceled":
|
case "canceled":
|
||||||
case "4":
|
case "3":
|
||||||
case "已停诊":
|
case "已停诊":
|
||||||
case "已取消":
|
case "已取消":
|
||||||
query.setStatus("cancelled");
|
query.setStatus("cancelled");
|
||||||
break;
|
break;
|
||||||
case "returned":
|
case "returned":
|
||||||
|
case "4":
|
||||||
case "5":
|
case "5":
|
||||||
case "已退号":
|
case "已退号":
|
||||||
query.setStatus("returned");
|
query.setStatus("returned");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
// 设置为 impossible 值,配合 mapper 的 otherwise 分支直接返回空
|
||||||
query.setStatus("__invalid__");
|
query.setStatus("__invalid__");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -373,25 +367,26 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
if (Boolean.TRUE.equals(raw.getIsStopped())) {
|
||||||
dto.setStatus("已停诊");
|
dto.setStatus("已停诊");
|
||||||
} else {
|
} else {
|
||||||
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已锁定...)
|
// 第二关:看独立的细分槽位状态 (0: 可用, 1: 已预约, 2: 已取消...)
|
||||||
SlotStatus status = SlotStatus.getByValue(raw.getSlotStatus());
|
Integer slotStatus = raw.getSlotStatus();
|
||||||
if (status != null) {
|
if (slotStatus != null) {
|
||||||
if (status == SlotStatus.LOCKED) {
|
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已取号");
|
||||||
|
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
|
||||||
|
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
|
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
||||||
|
dto.setStatus("系统取消");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("已锁定");
|
dto.setStatus("已预约");
|
||||||
}
|
}
|
||||||
} else if (status == SlotStatus.BOOKED) {
|
} else if (SlotStatus.RETURNED.equals(slotStatus)) {
|
||||||
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
|
|
||||||
dto.setStatus("已退号");
|
|
||||||
} else {
|
|
||||||
dto.setStatus("已取号");
|
|
||||||
}
|
|
||||||
} else if (status == SlotStatus.CANCELLED) {
|
|
||||||
dto.setStatus("已停诊");
|
|
||||||
} else if (status == SlotStatus.RETURNED) {
|
|
||||||
dto.setStatus("已退号");
|
dto.setStatus("已退号");
|
||||||
|
} else if (SlotStatus.CANCELLED.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已停诊");
|
||||||
|
} else if (SlotStatus.LOCKED.equals(slotStatus)) {
|
||||||
|
dto.setStatus("已锁定");
|
||||||
} else {
|
} else {
|
||||||
dto.setStatus("未预约");
|
dto.setStatus("未预约");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
|
|||||||
String activityName = activityDef != null ? activityDef.getName() : "";
|
String activityName = activityDef != null ? activityDef.getName() : "";
|
||||||
|
|
||||||
List<OrganizationLocation> organizationLocationList =
|
List<OrganizationLocation> organizationLocationList =
|
||||||
organizationLocationService.getOrgLocListByActivityDefinitionId(orgLoc.getActivityDefinitionId());
|
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getActivityDefinitionId());
|
||||||
organizationLocationList = (orgLoc.getId() != null)
|
organizationLocationList = (orgLoc.getId() != null)
|
||||||
? organizationLocationList.stream().filter(item -> !orgLoc.getId().equals(item.getId())).toList()
|
? organizationLocationList.stream().filter(item -> !orgLoc.getId().equals(item.getId())).toList()
|
||||||
: organizationLocationList;
|
: organizationLocationList;
|
||||||
@@ -169,7 +169,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
|
|||||||
if (DateTimeUtils.isOverlap(organizationLocation.getStartTime(), organizationLocation.getEndTime(),
|
if (DateTimeUtils.isOverlap(organizationLocation.getStartTime(), organizationLocation.getEndTime(),
|
||||||
orgLoc.getStartTime(), orgLoc.getEndTime())) {
|
orgLoc.getStartTime(), orgLoc.getEndTime())) {
|
||||||
Organization org = organizationService.getById(organizationLocation.getOrganizationId());
|
Organization org = organizationService.getById(organizationLocation.getOrganizationId());
|
||||||
String organizationName = org != null ? org.getName() : ("科室[" + organizationLocation.getOrganizationId() + "]已删除");
|
String organizationName = org != null ? org.getName() : "未知科室";
|
||||||
return R.fail("当前诊疗:" + activityName + CommonConstants.Common.DASH + orgLoc.getStartTime()
|
return R.fail("当前诊疗:" + activityName + CommonConstants.Common.DASH + orgLoc.getStartTime()
|
||||||
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "与" + organizationName + "时间冲突");
|
+ CommonConstants.Common.DASH + orgLoc.getEndTime() + "与" + organizationName + "时间冲突");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,4 @@ public class OrgLocQueryParam implements Serializable {
|
|||||||
/** 发放类别 */
|
/** 发放类别 */
|
||||||
private String distributionCategoryCode;
|
private String distributionCategoryCode;
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目编码 | 药品:1 耗材:2
|
|
||||||
*/
|
|
||||||
private String itemCode;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import com.openhis.administration.mapper.PatientMapper;
|
|||||||
import com.openhis.administration.service.*;
|
import com.openhis.administration.service.*;
|
||||||
import com.openhis.common.constant.CommonConstants;
|
import com.openhis.common.constant.CommonConstants;
|
||||||
import com.openhis.common.constant.PromptMsgConstant;
|
import com.openhis.common.constant.PromptMsgConstant;
|
||||||
import com.openhis.common.enums.SlotStatus;
|
|
||||||
import com.openhis.common.enums.*;
|
import com.openhis.common.enums.*;
|
||||||
import com.openhis.common.enums.ybenums.YbPayment;
|
import com.openhis.common.enums.ybenums.YbPayment;
|
||||||
import com.openhis.common.utils.EnumUtils;
|
import com.openhis.common.utils.EnumUtils;
|
||||||
@@ -644,7 +643,8 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
|
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
|
||||||
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
|
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
|
||||||
.set(Order::getCancelTime, new Date())
|
.set(Order::getCancelTime, new Date())
|
||||||
.set(Order::getCancelReason, "诊前退号")
|
.set(Order::getCancelReason,
|
||||||
|
StringUtils.isNotEmpty(reason) ? reason : "诊前退号")
|
||||||
.set(Order::getUpdateTime, new Date())
|
.set(Order::getUpdateTime, new Date())
|
||||||
.setSql("version = version + 1")
|
.setSql("version = version + 1")
|
||||||
.eq(Order::getId, appointmentOrder.getId())
|
.eq(Order::getId, appointmentOrder.getId())
|
||||||
@@ -660,27 +660,17 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
return appointmentOrder.getId();
|
return appointmentOrder.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只有已预约(1)的号源才能退号,对应签到后的 BOOKED 状态
|
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.AVAILABLE);
|
||||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
if (slotRows > 0) {
|
||||||
if (slot == null || !SlotStatus.BOOKED.getValue().equals(slot.getStatus())) {
|
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||||
log.warn("退号跳过:槽位非已预约状态, slotId={}, status={}", slotId,
|
if (poolId != null) {
|
||||||
slot != null ? slot.getStatus() : null);
|
schedulePoolMapper.refreshPoolStats(poolId);
|
||||||
return appointmentOrder.getId();
|
schedulePoolMapper.update(null,
|
||||||
}
|
new LambdaUpdateWrapper<SchedulePool>()
|
||||||
|
.setSql("version = version + 1")
|
||||||
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE.getValue());
|
.set(SchedulePool::getUpdateTime, new Date())
|
||||||
if (slotRows == 0) {
|
.eq(SchedulePool::getId, poolId));
|
||||||
log.warn("退号时更新槽位状态未影响任何行, slotId={}", slotId);
|
}
|
||||||
return appointmentOrder.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
|
||||||
if (poolId != null) {
|
|
||||||
schedulePoolMapper.update(null,
|
|
||||||
new LambdaUpdateWrapper<SchedulePool>()
|
|
||||||
.setSql("booked_num = booked_num - 1, version = version + 1")
|
|
||||||
.set(SchedulePool::getUpdateTime, new Date())
|
|
||||||
.eq(SchedulePool::getId, poolId));
|
|
||||||
}
|
}
|
||||||
return appointmentOrder.getId();
|
return appointmentOrder.getId();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -507,7 +507,6 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public R<?> deleteSurgery(Long id) {
|
public R<?> deleteSurgery(Long id) {
|
||||||
// 校验手术是否存在
|
// 校验手术是否存在
|
||||||
Surgery existSurgery = surgeryService.getById(id);
|
Surgery existSurgery = surgeryService.getById(id);
|
||||||
@@ -520,28 +519,6 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
|||||||
return R.fail("已完成的手术不能删除");
|
return R.fail("已完成的手术不能删除");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 级联删除关联数据
|
|
||||||
String surgeryNo = existSurgery.getSurgeryNo();
|
|
||||||
|
|
||||||
// 1. 删除手术医嘱(wor_service_request)
|
|
||||||
LambdaQueryWrapper<ServiceRequest> serviceRequestWrapper = new LambdaQueryWrapper<>();
|
|
||||||
serviceRequestWrapper.eq(ServiceRequest::getActivityId, id);
|
|
||||||
serviceRequestService.remove(serviceRequestWrapper);
|
|
||||||
log.info("删除手术关联的医嘱 - surgeryId: {}, surgeryNo: {}", id, surgeryNo);
|
|
||||||
|
|
||||||
// 2. 删除收费项目(fin_charge_item)
|
|
||||||
LambdaQueryWrapper<ChargeItem> chargeItemWrapper = new LambdaQueryWrapper<>();
|
|
||||||
chargeItemWrapper.eq(ChargeItem::getProductId, id)
|
|
||||||
.eq(ChargeItem::getProductTable, "cli_surgery");
|
|
||||||
chargeItemService.remove(chargeItemWrapper);
|
|
||||||
log.info("删除手术关联的收费项目 - surgeryId: {}, surgeryNo: {}", id, surgeryNo);
|
|
||||||
|
|
||||||
// 3. 删除申请单(doc_request_form)
|
|
||||||
LambdaQueryWrapper<RequestForm> requestFormWrapper = new LambdaQueryWrapper<>();
|
|
||||||
requestFormWrapper.eq(RequestForm::getPrescriptionNo, surgeryNo);
|
|
||||||
requestFormService.remove(requestFormWrapper);
|
|
||||||
log.info("删除手术关联的申请单 - surgeryId: {}, surgeryNo: {}", id, surgeryNo);
|
|
||||||
|
|
||||||
surgeryService.deleteSurgery(id);
|
surgeryService.deleteSurgery(id);
|
||||||
|
|
||||||
// 清除相关缓存
|
// 清除相关缓存
|
||||||
|
|||||||
@@ -215,9 +215,6 @@ public class SurgicalScheduleAppServiceImpl implements ISurgicalScheduleAppServi
|
|||||||
if (surgery != null) {
|
if (surgery != null) {
|
||||||
surgery.setStatusEnum(1); // 1 = 已排期
|
surgery.setStatusEnum(1); // 1 = 已排期
|
||||||
surgery.setUpdateTime(new Date());
|
surgery.setUpdateTime(new Date());
|
||||||
// Bug #558: 手术安排时同步写入手术室确认时间和确认人
|
|
||||||
surgery.setOperatingRoomConfirmTime(new Date());
|
|
||||||
surgery.setOperatingRoomConfirmUser(loginUser.getUsername());
|
|
||||||
|
|
||||||
// 填充缺失的申请科室和主刀医生名称
|
// 填充缺失的申请科室和主刀医生名称
|
||||||
fillSurgeryMissingNames(surgery);
|
fillSurgeryMissingNames(surgery);
|
||||||
|
|||||||
@@ -147,6 +147,6 @@ public interface IDoctorStationAdviceAppService {
|
|||||||
*/
|
*/
|
||||||
IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
IPage<SurgeryItemDto> getSurgeryPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
||||||
|
|
||||||
IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey, String categoryCode);
|
IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,21 +63,17 @@ public interface IDoctorStationEmrAppService {
|
|||||||
* 获取待写病历列表
|
* 获取待写病历列表
|
||||||
*
|
*
|
||||||
* @param doctorId 医生ID
|
* @param doctorId 医生ID
|
||||||
* @param pageNo 当前页码
|
* @return 待写病历列表
|
||||||
* @param pageSize 每页条数
|
|
||||||
* @param patientName 患者姓名(可选)
|
|
||||||
* @return 待写病历分页数据
|
|
||||||
*/
|
*/
|
||||||
R<?> getPendingEmrList(Long doctorId, Integer pageNo, Integer pageSize, String patientName);
|
R<?> getPendingEmrList(Long doctorId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取待写病历数量
|
* 获取待写病历数量
|
||||||
*
|
*
|
||||||
* @param doctorId 医生ID
|
* @param doctorId 医生ID
|
||||||
* @param patientName 患者姓名(可选)
|
|
||||||
* @return 待写病历数量
|
* @return 待写病历数量
|
||||||
*/
|
*/
|
||||||
R<?> getPendingEmrCount(Long doctorId, String patientName);
|
R<?> getPendingEmrCount(Long doctorId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查患者是否需要写病历
|
* 检查患者是否需要写病历
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ import com.openhis.medication.service.IMedicationDispenseService;
|
|||||||
import com.openhis.medication.service.IMedicationRequestService;
|
import com.openhis.medication.service.IMedicationRequestService;
|
||||||
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
|
||||||
import com.openhis.document.service.IRequestFormService;
|
|
||||||
import com.openhis.clinical.service.ISurgeryService;
|
|
||||||
import com.openhis.clinical.domain.Surgery;
|
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorStationInspectionLabApplyService;
|
import com.openhis.web.doctorstation.appservice.IDoctorStationInspectionLabApplyService;
|
||||||
import com.openhis.web.doctorstation.dto.*;
|
import com.openhis.web.doctorstation.dto.*;
|
||||||
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
|
import com.openhis.web.doctorstation.mapper.DoctorStationAdviceAppMapper;
|
||||||
@@ -48,16 +45,12 @@ import com.openhis.web.personalization.dto.ActivityDeviceDto;
|
|||||||
import com.openhis.workflow.domain.ActivityDefinition;
|
import com.openhis.workflow.domain.ActivityDefinition;
|
||||||
import com.openhis.workflow.domain.DeviceRequest;
|
import com.openhis.workflow.domain.DeviceRequest;
|
||||||
import com.openhis.workflow.domain.InventoryItem;
|
import com.openhis.workflow.domain.InventoryItem;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.*;
|
import com.openhis.workflow.service.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import com.openhis.document.domain.RequestForm;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -76,7 +69,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
private static final Pattern INSPECTION_APPLY_NO_JSON =
|
private static final Pattern INSPECTION_APPLY_NO_JSON =
|
||||||
Pattern.compile("\"applyNo\"\\s*:\\s*\"([^\"]+)\"");
|
Pattern.compile("\"applyNo\"\\s*:\\s*\"([^\"]+)\"");
|
||||||
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
AssignSeqUtil assignSeqUtil;
|
AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
@@ -140,20 +132,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
@Lazy
|
@Lazy
|
||||||
private IDoctorStationInspectionLabApplyService iDoctorStationInspectionLabApplyService;
|
private IDoctorStationInspectionLabApplyService iDoctorStationInspectionLabApplyService;
|
||||||
|
|
||||||
/**
|
|
||||||
* 与RequestFormManageAppServiceImpl存在循环依赖,需延迟注入;删除手术医嘱时级联作废手术申请单。
|
|
||||||
*/
|
|
||||||
@Resource
|
|
||||||
@Lazy
|
|
||||||
private IRequestFormService iRequestFormService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除手术医嘱时级联删除 cli_surgery 手术记录。
|
|
||||||
*/
|
|
||||||
@Resource
|
|
||||||
@Lazy
|
|
||||||
private ISurgeryService iSurgeryService;
|
|
||||||
|
|
||||||
// 缓存 key 前缀
|
// 缓存 key 前缀
|
||||||
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
|
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
|
||||||
// 缓存过期时间(小时)
|
// 缓存过期时间(小时)
|
||||||
@@ -581,11 +559,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (adviceSaveList != null && !adviceSaveList.isEmpty()) {
|
if (adviceSaveList != null && !adviceSaveList.isEmpty()) {
|
||||||
for (int i = 0; i < adviceSaveList.size(); i++) {
|
for (int i = 0; i < adviceSaveList.size(); i++) {
|
||||||
AdviceSaveDto dto = adviceSaveList.get(i);
|
AdviceSaveDto dto = adviceSaveList.get(i);
|
||||||
log.info("Request[{}]: requestId={}, dbOpType={}, adviceType={}, encounterId={}, patientId={}, categoryEnum={}, categoryEnum.class={}, categoryCode={}, categoryCode.class={}",
|
log.info("Request[{}]: requestId={}, dbOpType={}, adviceType={}, encounterId={}, patientId={}",
|
||||||
i, dto.getRequestId(), dto.getDbOpType(), dto.getAdviceType(),
|
i, dto.getRequestId(), dto.getDbOpType(), dto.getAdviceType(),
|
||||||
dto.getEncounterId(), dto.getPatientId(),
|
dto.getEncounterId(), dto.getPatientId());
|
||||||
dto.getCategoryEnum(), dto.getCategoryEnum() != null ? dto.getCategoryEnum().getClass().getName() : "NULL",
|
|
||||||
dto.getCategoryCode(), dto.getCategoryCode() != null ? dto.getCategoryCode().getClass().getName() : "NULL");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,27 +925,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
/**
|
/**
|
||||||
* 处理药品
|
* 处理药品
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 remark 合并到 contentJson 中,确保 Mapper 能从 content_json 提取 remark
|
|
||||||
*/
|
|
||||||
private String injectRemarkIntoContentJson(String contentJson, String remark) {
|
|
||||||
if (remark == null || remark.isEmpty() || contentJson == null || contentJson.isEmpty()) {
|
|
||||||
return contentJson;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode node = mapper.readTree(contentJson);
|
|
||||||
if (node instanceof ObjectNode) {
|
|
||||||
((ObjectNode) node).put("remark", remark);
|
|
||||||
return mapper.writeValueAsString(node);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to inject remark into contentJson: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
return contentJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> handMedication(List<AdviceSaveDto> medicineList, Date curDate, String adviceOpType,
|
private List<String> handMedication(List<AdviceSaveDto> medicineList, Date curDate, String adviceOpType,
|
||||||
Long organizationId, String signCode) {
|
Long organizationId, String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
@@ -1186,10 +1141,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (medicationRequest.getId() == null) {
|
if (medicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
medicationRequest.setContentJson(injectRemarkIntoContentJson(medicationRequest.getContentJson(), adviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(medicationRequest.getId().toString());
|
medRequestIdList.add(medicationRequest.getId().toString());
|
||||||
@@ -1611,7 +1562,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 🔧 BugFix #498: categoryEnum=22(检查) 走 ServiceRequest,不走 DeviceRequest
|
// 🔧 BugFix #498: categoryEnum=22(检查) 走 ServiceRequest,不走 DeviceRequest
|
||||||
// 检查申请单的诊疗定义ID存在 activityId,不在 adviceDefinitionId
|
// 检查申请单的诊疗定义ID存在 activityId,不在 adviceDefinitionId
|
||||||
// deviceDefId 对应耗材定义ID,不能用诊疗定义ID填充
|
// deviceDefId 对应耗材定义ID,不能用诊疗定义ID填充
|
||||||
if (Integer.valueOf(22).equals(adviceSaveDto.getCategoryEnum())) {
|
if (adviceSaveDto.getCategoryEnum() == 22) {
|
||||||
log.info("handDevice skip - 检查申请单(categoryEnum=22) 走 ServiceRequest 路径,跳过 DeviceRequest 保存");
|
log.info("handDevice skip - 检查申请单(categoryEnum=22) 走 ServiceRequest 路径,跳过 DeviceRequest 保存");
|
||||||
continue; // 跳过本次循环,不走耗材请求路径
|
continue; // 跳过本次循环,不走耗材请求路径
|
||||||
} else if (adviceSaveDto.getAdviceDefinitionId() != null) {
|
} else if (adviceSaveDto.getAdviceDefinitionId() != null) {
|
||||||
@@ -1650,10 +1601,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
|
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (adviceSaveDto.getRemark() != null && !adviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), adviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
// 处理耗材发放
|
// 处理耗材发放
|
||||||
@@ -1801,7 +1748,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
return StringUtils.isBlank(applyNo) ? null : applyNo;
|
return StringUtils.isBlank(applyNo) ? null : applyNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理诊疗
|
* 处理诊疗
|
||||||
*/
|
*/
|
||||||
@@ -1850,50 +1796,31 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 🔧 级联作废:在删除 ServiceRequest 之前,先读取所有待删除记录的级联信息
|
// 检验申请单在医嘱 contentJson 中写入 applyNo;从医嘱删除时需先级联作废检验单,避免检验页签仍显示孤儿申请
|
||||||
// 检验申请单:contentJson 中写入 applyNo;手术申请单:categoryEnum=24 + prescriptionNo
|
|
||||||
Map<String, List<Long>> labApplyNoToRequestIds = new LinkedHashMap<>();
|
Map<String, List<Long>> labApplyNoToRequestIds = new LinkedHashMap<>();
|
||||||
Map<String, List<Long>> surgeryPrescriptionNoToRequestIds = new LinkedHashMap<>();
|
|
||||||
// 收集待删除的 ServiceRequest(先查询再删除,避免级联逻辑因记录已删除而失效)
|
|
||||||
Map<Long, ServiceRequest> serviceRequestCache = new LinkedHashMap<>();
|
|
||||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||||
Long requestId = adviceSaveDto.getRequestId();
|
Long requestId = adviceSaveDto.getRequestId();
|
||||||
// 🔧 Bug #442: 跳过 requestId 为 null 的记录
|
// 🔧 Bug #442: 跳过 requestId 为 null 的记录,避免删除不存在的诊疗请求
|
||||||
if (requestId == null) {
|
if (requestId == null) {
|
||||||
log.warn("BugFix#442: handService - 跳过 requestId 为 null 的删除请求");
|
log.warn("BugFix#442: handService - 跳过 requestId 为 null 的删除请求");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ServiceRequest existing = iServiceRequestService.getById(requestId);
|
iServiceRequestService.removeById(requestId);// 删除诊疗
|
||||||
|
ServiceRequest existing = iServiceRequestService.getById(adviceSaveDto.getRequestId());
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
serviceRequestCache.put(requestId, existing);
|
|
||||||
log.info("【调试】handService 待删除医嘱: requestId={}, categoryEnum={}, prescriptionNo={}",
|
|
||||||
requestId, existing.getCategoryEnum(), existing.getPrescriptionNo());
|
|
||||||
// 检验申请单级联
|
|
||||||
String applyNo = extractInspectionApplyNoFromContentJson(existing.getContentJson());
|
String applyNo = extractInspectionApplyNoFromContentJson(existing.getContentJson());
|
||||||
if (StringUtils.isNotBlank(applyNo)) {
|
if (StringUtils.isNotBlank(applyNo)) {
|
||||||
labApplyNoToRequestIds.computeIfAbsent(applyNo, k -> new ArrayList<>())
|
labApplyNoToRequestIds.computeIfAbsent(applyNo, k -> new ArrayList<>())
|
||||||
.add(requestId);
|
.add(adviceSaveDto.getRequestId());
|
||||||
}
|
|
||||||
// 手术申请单级联(categoryEnum=24)
|
|
||||||
log.info("【调试】handService 判断手术条件: categoryEnum={}, prescriptionNo={}, isSurgery={}",
|
|
||||||
existing.getCategoryEnum(), existing.getPrescriptionNo(),
|
|
||||||
existing.getCategoryEnum() != null && existing.getCategoryEnum() == 24 && StringUtils.isNotBlank(existing.getPrescriptionNo()));
|
|
||||||
if (existing.getCategoryEnum() != null
|
|
||||||
&& existing.getCategoryEnum() == 24
|
|
||||||
&& StringUtils.isNotBlank(existing.getPrescriptionNo())) {
|
|
||||||
surgeryPrescriptionNoToRequestIds.computeIfAbsent(existing.getPrescriptionNo(), k -> new ArrayList<>())
|
|
||||||
.add(requestId);
|
|
||||||
log.info("【调试】handService 加入手术级联列表: prescriptionNo={}", existing.getPrescriptionNo());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 执行检验申请单级联作废
|
Set<Long> labCascadeSkippedRequestIds = new HashSet<>();
|
||||||
Set<Long> cascadeSkippedRequestIds = new HashSet<>();
|
|
||||||
for (Map.Entry<String, List<Long>> e : labApplyNoToRequestIds.entrySet()) {
|
for (Map.Entry<String, List<Long>> e : labApplyNoToRequestIds.entrySet()) {
|
||||||
R<?> delLab = iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(e.getKey());
|
R<?> delLab = iDoctorStationInspectionLabApplyService.deleteInspectionLabApply(e.getKey());
|
||||||
if (delLab != null && R.isSuccess(delLab)) {
|
if (delLab != null && R.isSuccess(delLab)) {
|
||||||
cascadeSkippedRequestIds.addAll(e.getValue());
|
labCascadeSkippedRequestIds.addAll(e.getValue());
|
||||||
log.info("handService - 级联作废检验申请单 applyNo={},已跳过重复删除的医嘱 requestIds={}",
|
log.info("handService - 级联作废检验申请单 applyNo={},已跳过重复删除的医嘱 requestIds={}",
|
||||||
e.getKey(), e.getValue());
|
e.getKey(), e.getValue());
|
||||||
} else {
|
} else {
|
||||||
@@ -1902,41 +1829,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
e.getKey(), msg);
|
e.getKey(), msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 🔧 手术申请单级联作废:删除手术医嘱时同步作废关联的手术申请单
|
|
||||||
for (Map.Entry<String, List<Long>> e : surgeryPrescriptionNoToRequestIds.entrySet()) {
|
|
||||||
String prescriptionNo = e.getKey();
|
|
||||||
try {
|
|
||||||
List<RequestForm> requestForms = iRequestFormService.list(
|
|
||||||
new LambdaQueryWrapper<RequestForm>()
|
|
||||||
.eq(RequestForm::getPrescriptionNo, prescriptionNo)
|
|
||||||
.in(RequestForm::getTypeCode, ActivityDefCategory.PROCEDURE.getCode().toString(), "SURGERY")
|
|
||||||
.and(w -> w.isNull(RequestForm::getDeleteFlag).or().eq(RequestForm::getDeleteFlag, "0")));
|
|
||||||
log.info("【调试】handService 查询手术申请单: prescriptionNo={}, 查到{}条", prescriptionNo, requestForms != null ? requestForms.size() : 0);
|
|
||||||
if (requestForms != null && !requestForms.isEmpty()) {
|
|
||||||
for (RequestForm requestForm : requestForms) {
|
|
||||||
iRequestFormService.removeById(requestForm.getId());
|
|
||||||
}
|
|
||||||
// 同步删除 cli_surgery 手术记录(prescriptionNo = surgeryNo)
|
|
||||||
Surgery surgery = iSurgeryService.getOne(
|
|
||||||
new LambdaQueryWrapper<Surgery>()
|
|
||||||
.eq(Surgery::getSurgeryNo, prescriptionNo)
|
|
||||||
.and(w -> w.isNull(Surgery::getDeleteFlag).or().eq(Surgery::getDeleteFlag, "0")));
|
|
||||||
if (surgery != null) {
|
|
||||||
iSurgeryService.removeById(surgery.getId());
|
|
||||||
log.info("handService - 级联删除手术记录 cli_surgery: surgeryNo={}, id={}", prescriptionNo, surgery.getId());
|
|
||||||
}
|
|
||||||
cascadeSkippedRequestIds.addAll(e.getValue());
|
|
||||||
log.info("handService - 级联作废手术申请单 prescriptionNo={}", prescriptionNo);
|
|
||||||
} else {
|
|
||||||
log.info("handService - 未找到手术申请单 prescriptionNo={}", prescriptionNo);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("handService - 级联作废手术申请单失败 prescriptionNo={} msg={}", prescriptionNo, ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 级联作废完成后,统一删除 ServiceRequest 及其子项、费用项
|
|
||||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||||
if (cascadeSkippedRequestIds.contains(adviceSaveDto.getRequestId())) {
|
if (labCascadeSkippedRequestIds.contains(adviceSaveDto.getRequestId())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Long requestId = adviceSaveDto.getRequestId();
|
Long requestId = adviceSaveDto.getRequestId();
|
||||||
@@ -1946,6 +1840,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
requestId));// 删除诊疗套餐对应的子项
|
requestId));// 删除诊疗套餐对应的子项
|
||||||
// 🔧 Bug Fix #219: 删除费用项
|
// 🔧 Bug Fix #219: 删除费用项
|
||||||
String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST;
|
String serviceTable = CommonConstants.TableName.WOR_SERVICE_REQUEST;
|
||||||
|
// 直接删除费用项
|
||||||
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
|
iChargeItemService.deleteByServiceTableAndId(serviceTable, requestId);
|
||||||
log.info("BugFix#219: 诊疗医嘱删除完成, requestId={}, serviceTable={}", requestId, serviceTable);
|
log.info("BugFix#219: 诊疗医嘱删除完成, requestId={}, serviceTable={}", requestId, serviceTable);
|
||||||
}
|
}
|
||||||
@@ -2065,9 +1960,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
serviceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 备注
|
|
||||||
serviceRequest.setRemark(adviceSaveDto.getRemark());
|
|
||||||
|
|
||||||
iServiceRequestService.saveOrUpdate(serviceRequest);
|
iServiceRequestService.saveOrUpdate(serviceRequest);
|
||||||
|
|
||||||
// 保存时保存诊疗费用项
|
// 保存时保存诊疗费用项
|
||||||
@@ -2227,6 +2119,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
CommonConstants.TableName.MED_MEDICATION_REQUEST, CommonConstants.TableName.WOR_DEVICE_REQUEST,
|
||||||
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
CommonConstants.TableName.WOR_SERVICE_REQUEST, practitionerId, Whether.NO.getCode(),
|
||||||
sourceEnum, sourceBillNo);
|
sourceEnum, sourceBillNo);
|
||||||
|
// 手术计费场景:sourceBillNo 不为空时,过滤掉药品(1),保留耗材(2)和诊疗(3/6)
|
||||||
|
if (sourceBillNo != null && !sourceBillNo.isEmpty()) {
|
||||||
|
requestBaseInfo.removeIf(dto -> dto.getAdviceType() != null
|
||||||
|
&& dto.getAdviceType() == 1);
|
||||||
|
}
|
||||||
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
for (RequestBaseDto requestBaseDto : requestBaseInfo) {
|
||||||
// 请求状态
|
// 请求状态
|
||||||
requestBaseDto
|
requestBaseDto
|
||||||
@@ -2240,10 +2137,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 收费状态
|
// 收费状态
|
||||||
requestBaseDto.setChargeStatus_enumText(
|
requestBaseDto.setChargeStatus_enumText(
|
||||||
EnumUtils.getInfoByValue(ChargeItemStatus.class, requestBaseDto.getChargeStatus()));
|
EnumUtils.getInfoByValue(ChargeItemStatus.class, requestBaseDto.getChargeStatus()));
|
||||||
// 单位字典翻译失败时回退使用原始值(如手术申请硬编码了中文单位名)
|
|
||||||
if (StringUtils.isNotBlank(requestBaseDto.getUnitCode()) && StringUtils.isBlank(requestBaseDto.getUnitCode_dictText())) {
|
|
||||||
requestBaseDto.setUnitCode_dictText(requestBaseDto.getUnitCode());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return R.ok(requestBaseInfo);
|
return R.ok(requestBaseInfo);
|
||||||
}
|
}
|
||||||
@@ -2586,17 +2479,21 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 MyBatis Plus 分页查询
|
// 使用直接 LIMIT 查询,不触发 MyBatis Plus 的 COUNT 开销
|
||||||
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getSurgeryPage(
|
List<SurgeryItemDto> records = doctorStationAdviceAppMapper.getSurgeryPage(
|
||||||
new Page<>(pageNo, pageSize),
|
new Page<>(pageNo, pageSize),
|
||||||
PublicationStatus.ACTIVE.getValue(),
|
PublicationStatus.ACTIVE.getValue(),
|
||||||
organizationId,
|
organizationId,
|
||||||
searchKey);
|
searchKey);
|
||||||
|
|
||||||
|
// 手动构造 Page 对象,total 设为 records.size()(前端 el-transfer 不需要精确的 total 总数)
|
||||||
|
IPage<SurgeryItemDto> result = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize);
|
||||||
|
result.setRecords(records);
|
||||||
|
result.setTotal(records.size());
|
||||||
log.info("getSurgeryPage 完成: {}ms, total={}, records={}", System.currentTimeMillis() - start, result.getTotal(), result.getRecords().size());
|
log.info("getSurgeryPage 完成: {}ms, total={}, records={}", System.currentTimeMillis() - start, result.getTotal(), result.getRecords().size());
|
||||||
|
|
||||||
// 无搜索时将结果写入缓存
|
// 无搜索时将结果写入缓存
|
||||||
if (useCache && result instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
|
if (useCache) {
|
||||||
redisCache.setCacheObject(cacheKey, result, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
|
redisCache.setCacheObject(cacheKey, result, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
|
||||||
log.info("缓存手术项目, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
|
log.info("缓存手术项目, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
|
||||||
}
|
}
|
||||||
@@ -2605,13 +2502,12 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey, String categoryCode) {
|
public IPage<SurgeryItemDto> getExaminationPage(Long organizationId, Integer pageNo, Integer pageSize, String searchKey) {
|
||||||
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getExaminationPage(
|
IPage<SurgeryItemDto> result = doctorStationAdviceAppMapper.getExaminationPage(
|
||||||
new Page<>(pageNo, pageSize),
|
new Page<>(pageNo, pageSize),
|
||||||
PublicationStatus.ACTIVE.getValue(),
|
PublicationStatus.ACTIVE.getValue(),
|
||||||
organizationId,
|
organizationId,
|
||||||
searchKey,
|
searchKey);
|
||||||
categoryCode);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import com.openhis.document.service.IEmrTemplateService;
|
|||||||
import com.openhis.web.doctorstation.appservice.IDoctorStationEmrAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorStationEmrAppService;
|
||||||
import com.openhis.web.doctorstation.dto.EmrTemplateDto;
|
import com.openhis.web.doctorstation.dto.EmrTemplateDto;
|
||||||
import com.openhis.web.doctorstation.dto.PatientEmrDto;
|
import com.openhis.web.doctorstation.dto.PatientEmrDto;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -42,7 +41,6 @@ import java.util.stream.Collectors;
|
|||||||
/**
|
/**
|
||||||
* 医生站-电子病历 应用实现类
|
* 医生站-电子病历 应用实现类
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Service
|
@Service
|
||||||
public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppService {
|
public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppService {
|
||||||
|
|
||||||
@@ -62,7 +60,13 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
IDocRecordService docRecordService;
|
IDocRecordService docRecordService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private com.openhis.web.doctorstation.mapper.DoctorStationEmrAppMapper doctorStationEmrAppMapper;
|
private EncounterMapper encounterMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PatientMapper patientMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private com.openhis.administration.mapper.EncounterParticipantMapper encounterParticipantMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加病人病历信息
|
* 添加病人病历信息
|
||||||
@@ -219,35 +223,52 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
* @return 待写病历列表
|
* @return 待写病历列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> getPendingEmrList(Long doctorId, Integer pageNo, Integer pageSize, String patientName) {
|
public R<?> getPendingEmrList(Long doctorId) {
|
||||||
List<Map<String, Object>> allRows = doctorStationEmrAppMapper.getPendingEmrList(doctorId, patientName);
|
// 由于Encounter实体中没有jzPractitionerUserId字段,我们需要通过关联查询来获取相关信息
|
||||||
int total = allRows.size();
|
// 使用医生工作站的mapper来查询相关数据
|
||||||
|
// 这里我们直接使用医生工作站的查询逻辑
|
||||||
|
|
||||||
// 分页截取
|
// 查询当前医生负责的、状态为"就诊中"但还没有写病历的患者
|
||||||
int fromIndex = (pageNo - 1) * pageSize;
|
// 需要通过EncounterParticipant表来关联医生信息
|
||||||
int toIndex = Math.min(fromIndex + pageSize, total);
|
List<Encounter> encounters = encounterMapper.selectList(
|
||||||
List<Map<String, Object>> pageRows;
|
new LambdaQueryWrapper<Encounter>()
|
||||||
if (fromIndex >= total) {
|
.eq(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())
|
||||||
pageRows = new ArrayList<>();
|
);
|
||||||
} else {
|
|
||||||
pageRows = allRows.subList(fromIndex, toIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算年龄列
|
// 过滤出由指定医生负责且还没有写病历的就诊记录
|
||||||
for (Map<String, Object> row : pageRows) {
|
List<Map<String, Object>> pendingEmrs = new ArrayList<>();
|
||||||
Object birthDate = row.get("birthDate");
|
for (Encounter encounter : encounters) {
|
||||||
if (birthDate instanceof Date) {
|
// 检查该就诊记录是否已经有病历
|
||||||
row.put("age", calculateAge((Date) birthDate));
|
Emr existingEmr = emrService.getOne(
|
||||||
} else {
|
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounter.getId())
|
||||||
row.put("age", null);
|
);
|
||||||
|
|
||||||
|
// 检查该就诊是否由指定医生负责
|
||||||
|
boolean isAssignedToDoctor = isEncounterAssignedToDoctor(encounter.getId(), doctorId);
|
||||||
|
|
||||||
|
if (existingEmr == null && isAssignedToDoctor) {
|
||||||
|
// 如果没有病历且由该医生负责,则添加到待写病历列表
|
||||||
|
Map<String, Object> pendingEmr = new java.util.HashMap<>();
|
||||||
|
|
||||||
|
// 获取患者信息
|
||||||
|
Patient patient = patientMapper.selectById(encounter.getPatientId());
|
||||||
|
|
||||||
|
pendingEmr.put("encounterId", encounter.getId());
|
||||||
|
pendingEmr.put("patientId", encounter.getPatientId());
|
||||||
|
pendingEmr.put("patientName", patient != null ? patient.getName() : "未知");
|
||||||
|
pendingEmr.put("gender", patient != null ? patient.getGenderEnum() : null);
|
||||||
|
// 使用出生日期计算年龄
|
||||||
|
pendingEmr.put("age", patient != null && patient.getBirthDate() != null ?
|
||||||
|
calculateAge(patient.getBirthDate()) : null);
|
||||||
|
// 使用创建时间作为挂号时间
|
||||||
|
pendingEmr.put("registerTime", encounter.getCreateTime());
|
||||||
|
pendingEmr.put("busNo", encounter.getBusNo()); // 病历号
|
||||||
|
|
||||||
|
pendingEmrs.add(pendingEmr);
|
||||||
}
|
}
|
||||||
row.remove("birthDate");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> result = new java.util.HashMap<>();
|
return R.ok(pendingEmrs);
|
||||||
result.put("rows", pageRows);
|
|
||||||
result.put("total", total);
|
|
||||||
return R.ok(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,9 +278,14 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
* @return 待写病历数量
|
* @return 待写病历数量
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> getPendingEmrCount(Long doctorId, String patientName) {
|
public R<?> getPendingEmrCount(Long doctorId) {
|
||||||
Long count = doctorStationEmrAppMapper.getPendingEmrCount(doctorId, patientName);
|
// 获取待写病历列表,然后返回数量
|
||||||
return R.ok(count != null ? count.intValue() : 0);
|
R<?> result = getPendingEmrList(doctorId);
|
||||||
|
if (result.getCode() == 200) {
|
||||||
|
List<?> pendingEmrs = (List<?>) result.getData();
|
||||||
|
return R.ok(pendingEmrs.size());
|
||||||
|
}
|
||||||
|
return R.ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -280,6 +306,24 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
return R.ok(needWrite);
|
return R.ok(needWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查就诊是否分配给指定医生
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 是否分配给指定医生
|
||||||
|
*/
|
||||||
|
private boolean isEncounterAssignedToDoctor(Long encounterId, Long doctorId) {
|
||||||
|
// 查询就诊参与者表,检查是否有指定医生的接诊记录
|
||||||
|
com.openhis.administration.domain.EncounterParticipant participant =
|
||||||
|
encounterParticipantMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.openhis.administration.domain.EncounterParticipant>()
|
||||||
|
.eq(com.openhis.administration.domain.EncounterParticipant::getEncounterId, encounterId)
|
||||||
|
.eq(com.openhis.administration.domain.EncounterParticipant::getPractitionerId, doctorId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return participant != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据出生日期计算年龄
|
* 根据出生日期计算年龄
|
||||||
|
|||||||
@@ -300,12 +300,16 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 获取 pool_id 和 slot_id:优先使用 encounter.orderId → order_main → adm_schedule_slot 链路
|
// 3. 获取 pool_id 和 slot_id:优先使用 triage_queue_item(挂号时录入的号源信息,为权威来源)
|
||||||
// (order_main.slot_id 为挂号时实际锁定的号源,是最权威的数据来源)
|
// 队列项不存在或值缺失时,回退使用 encounter → order_main → adm_schedule_slot 链路
|
||||||
// 当无 orderId 或订单无 slot_id 时,回退使用 triage_queue_item 的 poolId/slotId
|
|
||||||
Long divPoolId = null;
|
Long divPoolId = null;
|
||||||
Long divSlotId = null;
|
Long divSlotId = null;
|
||||||
if (encounter.getOrderId() != null) {
|
if (queueItem != null && queueItem.getPoolId() != null && queueItem.getSlotId() != null) {
|
||||||
|
divPoolId = queueItem.getPoolId();
|
||||||
|
divSlotId = queueItem.getSlotId();
|
||||||
|
}
|
||||||
|
// 队列项 poolId/slotId 缺失时,通过 encounter.orderId → order_main.slot_id → adm_schedule_slot.pool_id 回退获取
|
||||||
|
if ((divPoolId == null || divSlotId == null) && encounter.getOrderId() != null) {
|
||||||
try {
|
try {
|
||||||
Order order = iOrderService.getById(encounter.getOrderId());
|
Order order = iOrderService.getById(encounter.getOrderId());
|
||||||
if (order != null && order.getSlotId() != null) {
|
if (order != null && order.getSlotId() != null) {
|
||||||
@@ -316,16 +320,7 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("完诊获取div_log的pool_id/slot_id失败(order链路),encounterId={}", encounterId, e);
|
log.warn("回退获取完诊div_log的pool_id/slot_id失败,encounterId={}", encounterId, e);
|
||||||
}
|
|
||||||
}
|
|
||||||
// 订单链路无数据时,回退使用 triage_queue_item 的 poolId/slotId
|
|
||||||
if ((divPoolId == null || divSlotId == null) && queueItem != null) {
|
|
||||||
if (queueItem.getPoolId() != null) {
|
|
||||||
divPoolId = queueItem.getPoolId();
|
|
||||||
}
|
|
||||||
if (queueItem.getSlotId() != null) {
|
|
||||||
divSlotId = queueItem.getSlotId();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,10 +77,8 @@ public class DoctorStationAdviceController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping(value = "/save-advice")
|
@PostMapping(value = "/save-advice")
|
||||||
@RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试")
|
@RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试")
|
||||||
public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam,
|
public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam) {
|
||||||
@RequestParam(required = false, defaultValue = "1") String adviceOpType) {
|
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, AdviceOpType.SAVE_ADVICE.getCode());
|
||||||
// 🔧 Bug #445 修复:使用前端传入的 adviceOpType 参数(1=保存草稿,2=签发),而非硬编码
|
|
||||||
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, adviceOpType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,9 +226,8 @@ public class DoctorStationAdviceController {
|
|||||||
@RequestParam(value = "organizationId", required = false) Long organizationId,
|
@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(value = "pageSize", defaultValue = "500") Integer pageSize,
|
@RequestParam(value = "pageSize", defaultValue = "500") Integer pageSize,
|
||||||
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
|
@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
|
||||||
@RequestParam(value = "categoryCode", defaultValue = "23") String categoryCode) {
|
return R.ok(iDoctorStationAdviceAppService.getExaminationPage(organizationId, pageNo, pageSize, searchKey));
|
||||||
return R.ok(iDoctorStationAdviceAppService.getExaminationPage(organizationId, pageNo, pageSize, searchKey, categoryCode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,36 +26,34 @@ public class PendingEmrController {
|
|||||||
* 获取待写病历列表
|
* 获取待写病历列表
|
||||||
*
|
*
|
||||||
* @param doctorId 医生ID
|
* @param doctorId 医生ID
|
||||||
* @param pageNo 当前页码
|
* @return 待写病历列表
|
||||||
* @param pageSize 每页条数
|
|
||||||
* @param patientName 患者姓名(可选)
|
|
||||||
* @return 待写病历分页数据
|
|
||||||
*/
|
*/
|
||||||
@GetMapping("/pending-list")
|
@GetMapping("/pending-list")
|
||||||
public R<?> getPendingEmrList(@RequestParam(required = false) Long doctorId,
|
public R<?> getPendingEmrList(@RequestParam(required = false) Long doctorId) {
|
||||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
// 如果没有传递医生ID,则使用当前登录用户ID
|
||||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
|
||||||
@RequestParam(required = false) String patientName) {
|
|
||||||
if (doctorId == null) {
|
if (doctorId == null) {
|
||||||
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getPractitionerId();
|
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getUserId();
|
||||||
}
|
}
|
||||||
return iDoctorStationEmrAppService.getPendingEmrList(doctorId, pageNum, pageSize, patientName);
|
|
||||||
|
// 调用服务获取待写病历列表
|
||||||
|
return iDoctorStationEmrAppService.getPendingEmrList(doctorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取待写病历数量
|
* 获取待写病历数量
|
||||||
*
|
*
|
||||||
* @param doctorId 医生ID
|
* @param doctorId 医生ID
|
||||||
* @param patientName 患者姓名(可选)
|
|
||||||
* @return 待写病历数量
|
* @return 待写病历数量
|
||||||
*/
|
*/
|
||||||
@GetMapping("/pending-count")
|
@GetMapping("/pending-count")
|
||||||
public R<?> getPendingEmrCount(@RequestParam(required = false) Long doctorId,
|
public R<?> getPendingEmrCount(@RequestParam(required = false) Long doctorId) {
|
||||||
@RequestParam(required = false) String patientName) {
|
// 如果没有传递医生ID,则使用当前登录用户ID
|
||||||
if (doctorId == null) {
|
if (doctorId == null) {
|
||||||
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getPractitionerId();
|
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getUserId();
|
||||||
}
|
}
|
||||||
return iDoctorStationEmrAppService.getPendingEmrCount(doctorId, patientName);
|
|
||||||
|
// 调用服务获取待写病历数量
|
||||||
|
return iDoctorStationEmrAppService.getPendingEmrCount(doctorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -198,10 +198,8 @@ public class AdviceBaseDto {
|
|||||||
/**
|
/**
|
||||||
* 所属科室
|
* 所属科室
|
||||||
*/
|
*/
|
||||||
@Dict(dictTable = "adm_organization", dictCode = "id", dictText = "name")
|
|
||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long orgId;
|
private Long orgId;
|
||||||
private String orgId_dictText;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 所在位置
|
* 所在位置
|
||||||
@@ -250,9 +248,4 @@ public class AdviceBaseDto {
|
|||||||
* 是否缺少取药科室配置(仅药品类型使用)
|
* 是否缺少取药科室配置(仅药品类型使用)
|
||||||
*/
|
*/
|
||||||
private Boolean pharmacyConfigMissing;
|
private Boolean pharmacyConfigMissing;
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注(最长50字)
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,11 +270,6 @@ public class AdviceSaveDto {
|
|||||||
*/
|
*/
|
||||||
private String sourceBillNo;
|
private String sourceBillNo;
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注(最长50字)
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置默认值
|
* 设置默认值
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -96,11 +96,6 @@ public class DiagnosisQueryDto {
|
|||||||
*/
|
*/
|
||||||
private String diagnosisDoctor;
|
private String diagnosisDoctor;
|
||||||
|
|
||||||
/**
|
|
||||||
* 长效诊断标识
|
|
||||||
*/
|
|
||||||
private Integer longTermFlag;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已有传染病报卡(0-无,1-有)
|
* 是否已有传染病报卡(0-无,1-有)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -238,9 +238,4 @@ public class RequestBaseDto {
|
|||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long patientId;
|
private Long patientId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注(最长50字)
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ public class SurgeryItemDto {
|
|||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long orgId;
|
private Long orgId;
|
||||||
|
|
||||||
/** 所属科室名称 */
|
|
||||||
private String orgName;
|
|
||||||
|
|
||||||
/** 执行科室ID */
|
/** 执行科室ID */
|
||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long positionId;
|
private Long positionId;
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ public interface DoctorStationAdviceAppMapper {
|
|||||||
* @param searchKey 模糊查询关键字(可选)
|
* @param searchKey 模糊查询关键字(可选)
|
||||||
* @return 手术项目分页数据
|
* @return 手术项目分页数据
|
||||||
*/
|
*/
|
||||||
IPage<SurgeryItemDto> getSurgeryPage(@Param("page") Page<SurgeryItemDto> page,
|
List<SurgeryItemDto> getSurgeryPage(@Param("page") Page<SurgeryItemDto> page,
|
||||||
@Param("statusEnum") Integer statusEnum,
|
@Param("statusEnum") Integer statusEnum,
|
||||||
@Param("organizationId") Long organizationId,
|
@Param("organizationId") Long organizationId,
|
||||||
@Param("searchKey") String searchKey);
|
@Param("searchKey") String searchKey);
|
||||||
@@ -203,7 +203,6 @@ public interface DoctorStationAdviceAppMapper {
|
|||||||
IPage<SurgeryItemDto> getExaminationPage(@Param("page") Page<SurgeryItemDto> page,
|
IPage<SurgeryItemDto> getExaminationPage(@Param("page") Page<SurgeryItemDto> page,
|
||||||
@Param("statusEnum") Integer statusEnum,
|
@Param("statusEnum") Integer statusEnum,
|
||||||
@Param("organizationId") Long organizationId,
|
@Param("organizationId") Long organizationId,
|
||||||
@Param("searchKey") String searchKey,
|
@Param("searchKey") String searchKey);
|
||||||
@Param("categoryCode") String categoryCode);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
package com.openhis.web.doctorstation.mapper;
|
package com.openhis.web.doctorstation.mapper;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.Param;
|
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 医生站-电子病历 应用Mapper
|
* 医生站-电子病历 应用Mapper
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface DoctorStationEmrAppMapper {
|
public interface DoctorStationEmrAppMapper {
|
||||||
|
|
||||||
List<Map<String, Object>> getPendingEmrList(@Param("doctorId") Long doctorId,
|
|
||||||
@Param("patientName") String patientName);
|
|
||||||
|
|
||||||
Long getPendingEmrCount(@Param("doctorId") Long doctorId,
|
|
||||||
@Param("patientName") String patientName);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,24 +178,11 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
inpatientAdviceParam.setEncounterIds(null);
|
inpatientAdviceParam.setEncounterIds(null);
|
||||||
Integer exeStatus = inpatientAdviceParam.getExeStatus();
|
Integer exeStatus = inpatientAdviceParam.getExeStatus();
|
||||||
inpatientAdviceParam.setExeStatus(null);
|
inpatientAdviceParam.setExeStatus(null);
|
||||||
// 提取requestStatus手动处理,支持COMPLETED(3)和CHECK_VERIFIED(10)同时查询
|
// requestStatus由前端tab传入,通过QueryWrapper自动添加到SQL外层WHERE过滤
|
||||||
Integer requestStatus = inpatientAdviceParam.getRequestStatus();
|
|
||||||
inpatientAdviceParam.setRequestStatus(null);
|
|
||||||
// 构建查询条件
|
// 构建查询条件
|
||||||
QueryWrapper<InpatientAdviceParam> queryWrapper
|
QueryWrapper<InpatientAdviceParam> queryWrapper
|
||||||
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
|
||||||
|
|
||||||
// 手动拼接requestStatus条件:COMPLETED(3)时同时包含CHECK_VERIFIED(10)
|
|
||||||
// UNION查询外层列名为request_status(T1.status_enum AS request_status),不是status_enum
|
|
||||||
if (requestStatus != null) {
|
|
||||||
if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) {
|
|
||||||
queryWrapper.in("request_status",
|
|
||||||
RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue());
|
|
||||||
} else {
|
|
||||||
queryWrapper.eq("request_status", requestStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动拼接住院患者id条件
|
// 手动拼接住院患者id条件
|
||||||
if (encounterIds != null && !encounterIds.isEmpty()) {
|
if (encounterIds != null && !encounterIds.isEmpty()) {
|
||||||
List<Long> encounterIdList
|
List<Long> encounterIdList
|
||||||
@@ -328,29 +315,19 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
Date checkDate = new Date();
|
Date checkDate = new Date();
|
||||||
if (!serviceRequestList.isEmpty()) {
|
if (!serviceRequestList.isEmpty()) {
|
||||||
List<Long> serviceReqIds = serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList();
|
// 更新服务请求状态已完成
|
||||||
// 先查询服务请求,按 categoryEnum 分流:检查类(23)走 CHECK_VERIFIED,其余走 COMPLETED
|
serviceRequestService.updateCompleteRequestStatus(
|
||||||
List<ServiceRequest> allServiceRequests = serviceRequestService.listByIds(serviceReqIds);
|
serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate);
|
||||||
List<Long> checkReqIds = allServiceRequests.stream()
|
List<ServiceRequest> serviceRequests = serviceRequestService
|
||||||
.filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
|
.listByIds(serviceRequestList.stream().map(PerformInfoDto::getRequestId).collect(Collectors.toList()));
|
||||||
.map(ServiceRequest::getId).toList();
|
for (ServiceRequest serviceRequest : serviceRequests) {
|
||||||
List<Long> otherReqIds = allServiceRequests.stream()
|
// 判断医嘱类型
|
||||||
.filter(sr -> !ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
|
|
||||||
.map(ServiceRequest::getId).toList();
|
|
||||||
// 检查类 → 已校对(CHECK_VERIFIED=10)
|
|
||||||
if (!checkReqIds.isEmpty()) {
|
|
||||||
serviceRequestService.updateCheckVerifiedStatus(checkReqIds, practitionerId, checkDate);
|
|
||||||
}
|
|
||||||
// 其他类 → 已完成(COMPLETED=3)
|
|
||||||
if (!otherReqIds.isEmpty()) {
|
|
||||||
serviceRequestService.updateCompleteRequestStatus(otherReqIds, practitionerId, checkDate);
|
|
||||||
}
|
|
||||||
// 处理转科/出院等特殊医嘱
|
|
||||||
for (ServiceRequest serviceRequest : allServiceRequests) {
|
|
||||||
if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
|
if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
|
||||||
|
// 更新患者状态 待转科
|
||||||
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||||
EncounterZyStatus.PENDING_TRANSFER.getValue());
|
EncounterZyStatus.PENDING_TRANSFER.getValue());
|
||||||
} else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) {
|
} else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) {
|
||||||
|
// 更新患者状态 待出院
|
||||||
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
|
||||||
EncounterZyStatus.AWAITING_DISCHARGE.getValue());
|
EncounterZyStatus.AWAITING_DISCHARGE.getValue());
|
||||||
}
|
}
|
||||||
@@ -382,24 +359,6 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
medRequestList.add(item);
|
medRequestList.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 校验医嘱是否已执行,已执行的医嘱需要先取消执行后才能退回
|
|
||||||
List<Long> allRequestIds = performInfoList.stream().map(PerformInfoDto::getRequestId).toList();
|
|
||||||
List<Procedure> allProcedures = procedureService.list(
|
|
||||||
new LambdaQueryWrapper<Procedure>()
|
|
||||||
.in(Procedure::getRequestId, allRequestIds)
|
|
||||||
.eq(Procedure::getDeleteFlag, "0"));
|
|
||||||
Set<Long> executedIds = allProcedures.stream()
|
|
||||||
.filter(p -> EventStatus.COMPLETED.getValue().equals(p.getStatusEnum()))
|
|
||||||
.map(Procedure::getId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
Set<Long> cancelledRefundIds = allProcedures.stream()
|
|
||||||
.filter(p -> EventStatus.CANCEL.getValue().equals(p.getStatusEnum()) && p.getRefundId() != null)
|
|
||||||
.map(Procedure::getRefundId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
executedIds.removeAll(cancelledRefundIds);
|
|
||||||
if (!executedIds.isEmpty()) {
|
|
||||||
return R.fail("该医嘱已执行,请先取消执行后再退回");
|
|
||||||
}
|
|
||||||
// 校验药品医嘱是否已发药,已发药的医嘱不允许退回
|
// 校验药品医嘱是否已发药,已发药的医嘱不允许退回
|
||||||
if (!medRequestList.isEmpty()) {
|
if (!medRequestList.isEmpty()) {
|
||||||
List<Long> medReqIds = medRequestList.stream().map(PerformInfoDto::getRequestId).toList();
|
List<Long> medReqIds = medRequestList.stream().map(PerformInfoDto::getRequestId).toList();
|
||||||
@@ -465,15 +424,6 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
|
|||||||
List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList);
|
List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList);
|
||||||
// 处理诊疗执行
|
// 处理诊疗执行
|
||||||
this.exeActivity(actUseExeList, exeDate);
|
this.exeActivity(actUseExeList, exeDate);
|
||||||
// 检查类医嘱执行后,状态改为"待接收"(PENDING_RECEIVE=11)
|
|
||||||
List<Long> actReqIds = activityList.stream().map(AdviceExecuteDetailParam::getRequestId).toList();
|
|
||||||
List<ServiceRequest> executedReqs = serviceRequestService.listByIds(actReqIds);
|
|
||||||
List<Long> checkReqIds = executedReqs.stream()
|
|
||||||
.filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
|
|
||||||
.map(ServiceRequest::getId).toList();
|
|
||||||
if (!checkReqIds.isEmpty()) {
|
|
||||||
serviceRequestService.updatePendingReceiveStatus(checkReqIds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"}));
|
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"}));
|
||||||
|
|||||||
@@ -78,10 +78,12 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
|
|||||||
.map(notPerformedReason -> new DispenseInitDto.NotPerformedReasonOption(notPerformedReason.getValue(),
|
.map(notPerformedReason -> new DispenseInitDto.NotPerformedReasonOption(notPerformedReason.getValue(),
|
||||||
notPerformedReason.getInfo()))
|
notPerformedReason.getInfo()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 发药状态(汇总单:待配药→已提交,已发放→已发药)
|
// 发药状态
|
||||||
List<DispenseStatusOption> dispenseStatusOptions = new ArrayList<>();
|
List<DispenseStatusOption> dispenseStatusOptions = new ArrayList<>();
|
||||||
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.PREPARATION.getValue(), "已提交"));
|
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.PREPARATION.getValue(),
|
||||||
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.COMPLETED.getValue(), "已发药"));
|
DispenseStatus.PREPARATION.getInfo()));
|
||||||
|
dispenseStatusOptions.add(new DispenseStatusOption(DispenseStatus.COMPLETED.getValue(),
|
||||||
|
DispenseStatus.COMPLETED.getInfo()));
|
||||||
|
|
||||||
initDto.setNotPerformedReasonOptions(notPerformedReasonOptions).setDispenseStatusOptions(dispenseStatusOptions);
|
initDto.setNotPerformedReasonOptions(notPerformedReasonOptions).setDispenseStatusOptions(dispenseStatusOptions);
|
||||||
return R.ok(initDto);
|
return R.ok(initDto);
|
||||||
@@ -159,8 +161,8 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
|
|||||||
new Page<>(pageNo, pageSize), queryWrapper, DispenseStatus.COMPLETED.getValue(),
|
new Page<>(pageNo, pageSize), queryWrapper, DispenseStatus.COMPLETED.getValue(),
|
||||||
DispenseStatus.PREPARATION.getValue(), SupplyType.SUMMARY_DISPENSE.getValue());
|
DispenseStatus.PREPARATION.getValue(), SupplyType.SUMMARY_DISPENSE.getValue());
|
||||||
medicineSummaryFormPage.getRecords().forEach(e -> {
|
medicineSummaryFormPage.getRecords().forEach(e -> {
|
||||||
// 发药状态(汇总单展示文案)
|
// 发药状态
|
||||||
e.setStatusEnum_enumText(getSummaryFormStatusText(e.getStatusEnum()));
|
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(DispenseStatus.class, e.getStatusEnum()));
|
||||||
});
|
});
|
||||||
return R.ok(medicineSummaryFormPage);
|
return R.ok(medicineSummaryFormPage);
|
||||||
}
|
}
|
||||||
@@ -290,17 +292,4 @@ public class MedicineSummaryAppServiceImpl implements IMedicineSummaryAppService
|
|||||||
}
|
}
|
||||||
return R.ok(MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"取消"}));
|
return R.ok(MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"取消"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 汇总发药单状态展示文案(药品医嘱状态映射表:汇总申请→已提交,发药→已发药)
|
|
||||||
*/
|
|
||||||
private String getSummaryFormStatusText(Integer statusEnum) {
|
|
||||||
if (DispenseStatus.PREPARATION.getValue().equals(statusEnum)) {
|
|
||||||
return "已提交";
|
|
||||||
}
|
|
||||||
if (DispenseStatus.COMPLETED.getValue().equals(statusEnum)) {
|
|
||||||
return "已发药";
|
|
||||||
}
|
|
||||||
return EnumUtils.getInfoByValue(DispenseStatus.class, statusEnum);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -203,70 +202,62 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
|
|||||||
@Override
|
@Override
|
||||||
public R<?> addOrEditBatchTransferReceipt(List<ProductTransferDto> productTransferDtoList, Boolean flag) {
|
public R<?> addOrEditBatchTransferReceipt(List<ProductTransferDto> productTransferDtoList, Boolean flag) {
|
||||||
|
|
||||||
|
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
|
||||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
Date now = DateUtils.getNowDate();
|
for (ProductTransferDto dto : productTransferDtoList) {
|
||||||
|
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
|
||||||
|
return R.fail("调拨数量必须大于0");
|
||||||
|
}
|
||||||
|
// 查询该药品在源仓库的实时库存总量
|
||||||
|
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
|
||||||
|
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
|
||||||
|
java.math.BigDecimal actualStock = inventoryList.stream()
|
||||||
|
.map(InventoryItem::getQuantity)
|
||||||
|
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
||||||
|
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
|
||||||
|
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<String> idList = new ArrayList<>();
|
List<String> idList = new ArrayList<>();
|
||||||
if (flag) {
|
if (flag) {
|
||||||
// 批量保存按钮
|
// 批量保存按钮
|
||||||
|
// 单据号取得
|
||||||
List<String> busNoList = productTransferDtoList.stream().map(ProductTransferDto::getBusNo).toList();
|
List<String> busNoList = productTransferDtoList.stream().map(ProductTransferDto::getBusNo).toList();
|
||||||
// 保存前:获取旧记录用于恢复预划扣库存
|
// 请求id取得
|
||||||
List<SupplyRequest> oldRequestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
||||||
if (!oldRequestList.isEmpty()) {
|
if (!requestList.isEmpty()) {
|
||||||
// 恢复旧记录已预划扣的库存
|
List<Long> requestIdList = requestList.stream().map(SupplyRequest::getId).collect(Collectors.toList());
|
||||||
for (SupplyRequest oldReq : oldRequestList) {
|
// 单据信息删除
|
||||||
if (oldReq.getItemId() != null && oldReq.getLotNumber() != null
|
supplyRequestService.removeByIds(requestIdList);
|
||||||
&& oldReq.getSourceLocationId() != null && oldReq.getItemQuantity() != null) {
|
|
||||||
List<InventoryItem> invList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
oldReq.getItemId(), oldReq.getLotNumber(), oldReq.getSourceLocationId(), tenantId);
|
|
||||||
if (!invList.isEmpty()) {
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
invList.get(0).getId(),
|
|
||||||
invList.get(0).getQuantity().add(oldReq.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<Long> oldIdList = oldRequestList.stream().map(SupplyRequest::getId).collect(Collectors.toList());
|
|
||||||
supplyRequestService.removeByIds(oldIdList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验 + 预划扣新记录
|
|
||||||
for (ProductTransferDto dto : productTransferDtoList) {
|
|
||||||
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
return R.fail("调拨数量必须大于0");
|
|
||||||
}
|
|
||||||
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
|
|
||||||
BigDecimal actualStock = inventoryList.stream()
|
|
||||||
.map(InventoryItem::getQuantity)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
|
|
||||||
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + ")");
|
|
||||||
}
|
|
||||||
// 预划扣源仓库库存
|
|
||||||
if (!inventoryList.isEmpty()) {
|
|
||||||
InventoryItem inv = inventoryList.get(0);
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
inv.getId(), inv.getQuantity().subtract(dto.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成批量调拨单据
|
// 生成批量调拨单据
|
||||||
List<SupplyRequest> supplyRequestList = new ArrayList<>();
|
List<SupplyRequest> supplyRequestList = new ArrayList<>();
|
||||||
for (ProductTransferDto productTransferDto : productTransferDtoList) {
|
for (ProductTransferDto productTransferDto : productTransferDtoList) {
|
||||||
|
// 初始化单据信息
|
||||||
SupplyRequest supplyRequest = new SupplyRequest();
|
SupplyRequest supplyRequest = new SupplyRequest();
|
||||||
BeanUtils.copyProperties(productTransferDto, supplyRequest);
|
BeanUtils.copyProperties(productTransferDto, supplyRequest);
|
||||||
|
// 生成商品批量调拨单据
|
||||||
supplyRequest
|
supplyRequest
|
||||||
|
// id
|
||||||
.setId(null)
|
.setId(null)
|
||||||
|
// 单据分类:库存供应
|
||||||
.setCategoryEnum(SupplyCategory.STOCK_SUPPLY.getValue())
|
.setCategoryEnum(SupplyCategory.STOCK_SUPPLY.getValue())
|
||||||
|
// 单据类型:商品批量调拨
|
||||||
.setTypeEnum(SupplyType.PRODUCT_BATCH_TRANSFER.getValue())
|
.setTypeEnum(SupplyType.PRODUCT_BATCH_TRANSFER.getValue())
|
||||||
|
// 制单人
|
||||||
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
||||||
|
// 申请时间
|
||||||
.setApplyTime(DateUtils.getNowDate())
|
.setApplyTime(DateUtils.getNowDate())
|
||||||
|
// 源库存数量
|
||||||
.setTotalQuantity(productTransferDto.getTotalSourceQuantity());
|
.setTotalQuantity(productTransferDto.getTotalSourceQuantity());
|
||||||
supplyRequestList.add(supplyRequest);
|
supplyRequestList.add(supplyRequest);
|
||||||
}
|
}
|
||||||
supplyRequestService.saveOrUpdateBatch(supplyRequestList);
|
supplyRequestService.saveOrUpdateBatch(supplyRequestList);
|
||||||
|
// 请求id取得
|
||||||
List<SupplyRequest> supplyRequestIdList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
List<SupplyRequest> supplyRequestIdList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
||||||
|
// 返回请求id列表
|
||||||
List<Long> requestIdList = supplyRequestIdList.stream().map(SupplyRequest::getId).toList();
|
List<Long> requestIdList = supplyRequestIdList.stream().map(SupplyRequest::getId).toList();
|
||||||
for (Long list : requestIdList) {
|
for (Long list : requestIdList) {
|
||||||
idList.add(list.toString());
|
idList.add(list.toString());
|
||||||
@@ -274,58 +265,33 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
|
|||||||
} else {
|
} else {
|
||||||
// 单独保存按钮
|
// 单独保存按钮
|
||||||
for (ProductTransferDto productTransferDto : productTransferDtoList) {
|
for (ProductTransferDto productTransferDto : productTransferDtoList) {
|
||||||
// 更新已有记录:先恢复旧预划扣,再扣新的
|
// 初始化单据信息
|
||||||
if (productTransferDto.getId() != null) {
|
|
||||||
SupplyRequest oldReq = supplyRequestService.getById(productTransferDto.getId());
|
|
||||||
if (oldReq != null && oldReq.getItemId() != null && oldReq.getLotNumber() != null
|
|
||||||
&& oldReq.getSourceLocationId() != null && oldReq.getItemQuantity() != null) {
|
|
||||||
List<InventoryItem> invList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
oldReq.getItemId(), oldReq.getLotNumber(), oldReq.getSourceLocationId(), tenantId);
|
|
||||||
if (!invList.isEmpty()) {
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
invList.get(0).getId(),
|
|
||||||
invList.get(0).getQuantity().add(oldReq.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验 + 预划扣
|
|
||||||
if (productTransferDto.getItemQuantity() == null
|
|
||||||
|| productTransferDto.getItemQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
return R.fail("调拨数量必须大于0");
|
|
||||||
}
|
|
||||||
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
productTransferDto.getItemId(), productTransferDto.getLotNumber(),
|
|
||||||
productTransferDto.getSourceLocationId(), tenantId);
|
|
||||||
BigDecimal actualStock = inventoryList.stream()
|
|
||||||
.map(InventoryItem::getQuantity)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
if (productTransferDto.getItemQuantity().compareTo(actualStock) > 0) {
|
|
||||||
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + ")");
|
|
||||||
}
|
|
||||||
if (!inventoryList.isEmpty()) {
|
|
||||||
InventoryItem inv = inventoryList.get(0);
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
inv.getId(), inv.getQuantity().subtract(productTransferDto.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
|
|
||||||
SupplyRequest supplyRequest = new SupplyRequest();
|
SupplyRequest supplyRequest = new SupplyRequest();
|
||||||
BeanUtils.copyProperties(productTransferDto, supplyRequest);
|
BeanUtils.copyProperties(productTransferDto, supplyRequest);
|
||||||
|
|
||||||
supplyRequest.setTotalQuantity(productTransferDto.getTotalSourceQuantity());
|
supplyRequest.setTotalQuantity(productTransferDto.getTotalSourceQuantity());
|
||||||
|
|
||||||
if (productTransferDto.getId() != null) {
|
if (productTransferDto.getId() != null) {
|
||||||
|
// 更新单据信息
|
||||||
supplyRequestService.updateById(supplyRequest);
|
supplyRequestService.updateById(supplyRequest);
|
||||||
} else {
|
} else {
|
||||||
|
// 生成商品批量调拨单据
|
||||||
supplyRequest
|
supplyRequest
|
||||||
|
// 单据分类:库存供应
|
||||||
.setCategoryEnum(SupplyCategory.STOCK_SUPPLY.getValue())
|
.setCategoryEnum(SupplyCategory.STOCK_SUPPLY.getValue())
|
||||||
|
// 单据类型:商品批量调拨
|
||||||
.setTypeEnum(SupplyType.PRODUCT_BATCH_TRANSFER.getValue())
|
.setTypeEnum(SupplyType.PRODUCT_BATCH_TRANSFER.getValue())
|
||||||
|
// 制单人
|
||||||
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
||||||
|
// 申请时间
|
||||||
.setApplyTime(DateUtils.getNowDate());
|
.setApplyTime(DateUtils.getNowDate());
|
||||||
supplyRequestService.save(supplyRequest);
|
supplyRequestService.save(supplyRequest);
|
||||||
}
|
}
|
||||||
|
// 返回单据id
|
||||||
return R.ok(supplyRequest.getId().toString(), null);
|
return R.ok(supplyRequest.getId().toString(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 返回单据id
|
||||||
return R.ok(idList, null);
|
return R.ok(idList, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,63 +332,33 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
|
|||||||
@Override
|
@Override
|
||||||
public R<?> addOrEditTransferReceipt(List<ProductTransferDto> productTransferDtoList) {
|
public R<?> addOrEditTransferReceipt(List<ProductTransferDto> productTransferDtoList) {
|
||||||
|
|
||||||
|
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
|
||||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
Date now = DateUtils.getNowDate();
|
for (ProductTransferDto dto : productTransferDtoList) {
|
||||||
|
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
|
||||||
|
return R.fail("调拨数量必须大于0");
|
||||||
|
}
|
||||||
|
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
|
||||||
|
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
|
||||||
|
java.math.BigDecimal actualStock = inventoryList.stream()
|
||||||
|
.map(InventoryItem::getQuantity)
|
||||||
|
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
|
||||||
|
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
|
||||||
|
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<String> idList = new ArrayList<>();
|
List<String> idList = new ArrayList<>();
|
||||||
|
|
||||||
// 单据号取得
|
// 单据号取得
|
||||||
List<String> busNoList = productTransferDtoList.stream().map(ProductTransferDto::getBusNo).toList();
|
List<String> busNoList = productTransferDtoList.stream().map(ProductTransferDto::getBusNo).toList();
|
||||||
// 保存前:获取旧记录用于恢复预划扣库存
|
// 请求数据取得
|
||||||
List<SupplyRequest> oldRequestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNoList.get(0));
|
||||||
Map<String, BigDecimal> oldDeductionMap = new HashMap<>();
|
if (!requestList.isEmpty()) {
|
||||||
if (!oldRequestList.isEmpty()) {
|
// 请求id取得
|
||||||
for (SupplyRequest oldReq : oldRequestList) {
|
List<Long> requestIdList = requestList.stream().map(SupplyRequest::getId).collect(Collectors.toList());
|
||||||
if (oldReq.getItemId() != null && oldReq.getLotNumber() != null && oldReq.getSourceLocationId() != null
|
// 单据信息删除
|
||||||
&& oldReq.getItemQuantity() != null) {
|
supplyRequestService.removeByIds(requestIdList);
|
||||||
String key = oldReq.getItemId() + "_" + oldReq.getLotNumber() + "_" + oldReq.getSourceLocationId();
|
|
||||||
oldDeductionMap.merge(key, oldReq.getItemQuantity(), BigDecimal::add);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 恢复旧记录已预划扣的库存
|
|
||||||
for (Map.Entry<String, BigDecimal> entry : oldDeductionMap.entrySet()) {
|
|
||||||
String[] parts = entry.getKey().split("_");
|
|
||||||
Long itemId = Long.parseLong(parts[0]);
|
|
||||||
String lotNumber = parts[1];
|
|
||||||
Long sourceLocationId = Long.parseLong(parts[2]);
|
|
||||||
BigDecimal restoreQty = entry.getValue();
|
|
||||||
List<InventoryItem> invList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
itemId, lotNumber, sourceLocationId, tenantId);
|
|
||||||
if (!invList.isEmpty()) {
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
invList.get(0).getId(), invList.get(0).getQuantity().add(restoreQty), now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 删除旧记录
|
|
||||||
List<Long> oldIdList = oldRequestList.stream().map(SupplyRequest::getId).collect(Collectors.toList());
|
|
||||||
supplyRequestService.removeByIds(oldIdList);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验 + 预划扣新记录
|
|
||||||
Map<String, BigDecimal> newDeductionMap = new HashMap<>();
|
|
||||||
for (ProductTransferDto dto : productTransferDtoList) {
|
|
||||||
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
return R.fail("调拨数量必须大于0");
|
|
||||||
}
|
|
||||||
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
|
|
||||||
BigDecimal actualStock = inventoryList.stream()
|
|
||||||
.map(InventoryItem::getQuantity)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
|
|
||||||
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + ")");
|
|
||||||
}
|
|
||||||
// 预划扣:扣减源仓库库存
|
|
||||||
if (!inventoryList.isEmpty()) {
|
|
||||||
InventoryItem inv = inventoryList.get(0);
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
inv.getId(), inv.getQuantity().subtract(dto.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SupplyRequest> supplyRequestList = new ArrayList<>();
|
List<SupplyRequest> supplyRequestList = new ArrayList<>();
|
||||||
@@ -469,22 +405,6 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> deleteReceipt(List<Long> supplyRequestIds) {
|
public R<?> deleteReceipt(List<Long> supplyRequestIds) {
|
||||||
// 删除前恢复预划扣的库存
|
|
||||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
|
||||||
Date now = DateUtils.getNowDate();
|
|
||||||
for (Long reqId : supplyRequestIds) {
|
|
||||||
SupplyRequest sr = supplyRequestService.getById(reqId);
|
|
||||||
if (sr != null && sr.getItemId() != null && sr.getSourceLocationId() != null
|
|
||||||
&& sr.getItemQuantity() != null && sr.getLotNumber() != null) {
|
|
||||||
List<InventoryItem> invList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
sr.getItemId(), sr.getLotNumber(), sr.getSourceLocationId(), tenantId);
|
|
||||||
if (!invList.isEmpty()) {
|
|
||||||
InventoryItem inv = invList.get(0);
|
|
||||||
inventoryItemService.updateInventoryQuantity(
|
|
||||||
inv.getId(), inv.getQuantity().add(sr.getItemQuantity()), now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 删除单据
|
// 删除单据
|
||||||
boolean result = supplyRequestService.removeByIds(supplyRequestIds);
|
boolean result = supplyRequestService.removeByIds(supplyRequestIds);
|
||||||
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))
|
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))
|
||||||
|
|||||||
@@ -519,8 +519,58 @@ public class ReceiptApprovalAppServiceImpl implements IReceiptApprovalAppService
|
|||||||
// 暂时先取出全部的库存,循环查库存同一会有问题,后续优化
|
// 暂时先取出全部的库存,循环查库存同一会有问题,后续优化
|
||||||
List<InventoryItem> inventoryItems = inventoryItemService.selectAllInventory();
|
List<InventoryItem> inventoryItems = inventoryItemService.selectAllInventory();
|
||||||
for (SupplyItemDetailDto supplyItemDetailDto : supplyItemDetailList) {
|
for (SupplyItemDetailDto supplyItemDetailDto : supplyItemDetailList) {
|
||||||
// 🔧 源仓库库存已在保存时预划扣,审批通过时不再重复扣减
|
// 根据项目id,产品批号,源仓库id 查询源仓库库存表信息
|
||||||
outList.add(supplyItemDetailDto);
|
// List<InventoryItem> inventoryItemSourceList = inventoryItemService.selectInventoryByItemId(
|
||||||
|
// supplyItemDetailDto.getItemId(), supplyItemDetailDto.getLotNumber(),
|
||||||
|
// supplyItemDetailDto.getSourceLocationId(), SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
List<InventoryItem> filteredInventoryItems = inventoryItems.stream()
|
||||||
|
.filter(item -> item.getItemId().equals(supplyItemDetailDto.getItemId())
|
||||||
|
&& item.getLotNumber().equals(supplyItemDetailDto.getLotNumber())
|
||||||
|
&& item.getLocationId().equals(supplyItemDetailDto.getSourceLocationId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
InventoryItem inventoryItemSource = new InventoryItem();
|
||||||
|
if (!filteredInventoryItems.isEmpty()) {
|
||||||
|
inventoryItemSource = filteredInventoryItems.get(0);
|
||||||
|
// 最小数量(最小单位库存数量)
|
||||||
|
BigDecimal minQuantity = inventoryItemSource.getQuantity();
|
||||||
|
|
||||||
|
// // 计算调拨后库存数量,结果取小单位
|
||||||
|
// // 供应申请的物品计量单位与包装单位相同
|
||||||
|
// if (supplyItemDetailDto.getItemUnit().equals(supplyItemDetailDto.getUnitCode())) {
|
||||||
|
// if
|
||||||
|
// (minQuantity.compareTo(supplyItemDetailDto.getItemQuantity().multiply(supplyItemDetailDto.getPartPercent()))
|
||||||
|
// < 0) {
|
||||||
|
// // 库存数量不足
|
||||||
|
// throw new ServiceException("操作失败,库存数量不足");
|
||||||
|
// } else {
|
||||||
|
// // 源仓库库存-(调拨数量*拆零比)
|
||||||
|
// minQuantity = minQuantity.subtract(
|
||||||
|
// supplyItemDetailDto.getPartPercent().multiply(supplyItemDetailDto.getItemQuantity()));
|
||||||
|
// }
|
||||||
|
// } else if (supplyItemDetailDto.getItemUnit().equals(supplyItemDetailDto.getMinUnitCode())) {
|
||||||
|
// 直接扣减库存
|
||||||
|
if (minQuantity.compareTo(supplyItemDetailDto.getItemQuantity()) < 0) {
|
||||||
|
// 库存数量不足
|
||||||
|
throw new ServiceException("操作失败,库存数量不足");
|
||||||
|
} else {
|
||||||
|
// 供应申请的物品计量单位与最小单位相同
|
||||||
|
// 源仓库库存-调拨数量
|
||||||
|
minQuantity = minQuantity.subtract(supplyItemDetailDto.getItemQuantity());
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
// 更新源仓库库存数量
|
||||||
|
Boolean aBoolean
|
||||||
|
= inventoryItemService.updateInventoryQuantity(inventoryItemSource.getId(), minQuantity, now);
|
||||||
|
if (!aBoolean) {
|
||||||
|
throw new ServiceException("系统异常,请稍后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到出库列表
|
||||||
|
outList.add(supplyItemDetailDto);
|
||||||
|
} else {
|
||||||
|
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, null));
|
||||||
|
}
|
||||||
|
|
||||||
// 根据项目id,产品批号,目的仓库id 查询目的仓库库存表信息
|
// 根据项目id,产品批号,目的仓库id 查询目的仓库库存表信息
|
||||||
List<InventoryItem> inventoryItemPurposeList = inventoryItemService.selectInventoryByItemId(
|
List<InventoryItem> inventoryItemPurposeList = inventoryItemService.selectInventoryByItemId(
|
||||||
|
|||||||
@@ -256,9 +256,6 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> submitApproval(String busNo) {
|
public R<?> submitApproval(String busNo) {
|
||||||
// 提交审批前校验库存,防止超库存单据进入审批流
|
|
||||||
this.validateRequisitionStockByBusNo(busNo);
|
|
||||||
|
|
||||||
// 单据提交审核
|
// 单据提交审核
|
||||||
boolean result = supplyRequestService.submitApproval(busNo);
|
boolean result = supplyRequestService.submitApproval(busNo);
|
||||||
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))
|
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))
|
||||||
@@ -422,80 +419,4 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据单据号校验领用数量是否超过源仓库实际库存(提交审批前调用)
|
|
||||||
*
|
|
||||||
* @param busNo 单据号
|
|
||||||
*/
|
|
||||||
private void validateRequisitionStockByBusNo(String busNo) {
|
|
||||||
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
|
||||||
// 通过单据详情查询已保存的单据明细
|
|
||||||
R<List<IssueDetailDto>> detailResult = this.getDetail(busNo);
|
|
||||||
if (detailResult.getCode() != 200 || detailResult.getData() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<IssueDetailDto> detailList = detailResult.getData();
|
|
||||||
for (IssueDetailDto detail : detailList) {
|
|
||||||
Long itemId = detail.getItemId();
|
|
||||||
String lotNumber = detail.getLotNumber();
|
|
||||||
Long sourceLocationId = detail.getSourceLocationId();
|
|
||||||
BigDecimal reqQuantity = detail.getItemQuantity();
|
|
||||||
String itemTable = CommonConstants.TableName.MED_MEDICATION_DEFINITION;
|
|
||||||
// 根据药品类型判断表名
|
|
||||||
if (ItemType.DEVICE.getValue().equals(detail.getItemType())) {
|
|
||||||
itemTable = CommonConstants.TableName.ADM_DEVICE_DEFINITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询定义信息(拆零比、单位)
|
|
||||||
BigDecimal partPercent = BigDecimal.ONE;
|
|
||||||
String unitCode = detail.getUnitCode();
|
|
||||||
String minUnitCode = detail.getMinUnitCode();
|
|
||||||
|
|
||||||
if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(itemTable)) {
|
|
||||||
MedicationDefinition medDef = medicationDefinitionService.getById(itemId);
|
|
||||||
if (medDef != null) {
|
|
||||||
unitCode = medDef.getUnitCode();
|
|
||||||
minUnitCode = medDef.getMinUnitCode();
|
|
||||||
if (medDef.getPartPercent() != null) {
|
|
||||||
partPercent = medDef.getPartPercent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(itemTable)) {
|
|
||||||
DeviceDefinition devDef = deviceDefinitionService.getById(itemId);
|
|
||||||
if (devDef != null) {
|
|
||||||
unitCode = devDef.getUnitCode();
|
|
||||||
minUnitCode = devDef.getMinUnitCode();
|
|
||||||
if (devDef.getPartPercent() != null) {
|
|
||||||
partPercent = devDef.getPartPercent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算领用数量折合最小单位的值
|
|
||||||
BigDecimal reqQuantityInMinUnit;
|
|
||||||
if (unitCode != null && detail.getUnitCode().equals(unitCode)) {
|
|
||||||
reqQuantityInMinUnit = reqQuantity.multiply(partPercent);
|
|
||||||
} else {
|
|
||||||
reqQuantityInMinUnit = reqQuantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询源仓库实际库存
|
|
||||||
List<InventoryItem> inventoryItemList = inventoryItemService.selectInventoryByItemId(
|
|
||||||
itemId, lotNumber, sourceLocationId, tenantId);
|
|
||||||
|
|
||||||
// 累加总库存
|
|
||||||
BigDecimal totalStock = BigDecimal.ZERO;
|
|
||||||
for (InventoryItem inventoryItem : inventoryItemList) {
|
|
||||||
if (inventoryItem.getLocationId().equals(sourceLocationId)) {
|
|
||||||
totalStock = totalStock.add(inventoryItem.getQuantity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 比较领用数量与库存
|
|
||||||
if (reqQuantityInMinUnit.compareTo(totalStock) > 0) {
|
|
||||||
throw new ServiceException("提交失败,单据中存在领用数量超过库存的明细,请核对后重新保存");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class ProductTransferController {
|
|||||||
* @return 操作结果
|
* @return 操作结果
|
||||||
*/
|
*/
|
||||||
@PutMapping("/product-transfer-batch")
|
@PutMapping("/product-transfer-batch")
|
||||||
public R<?> addOrEditBatchTransferReceipt(@Validated @RequestBody List<ProductTransferDto> productTransferDtoList) {
|
public R<?> addOrEditBatchTransferReceipt(@RequestBody List<ProductTransferDto> productTransferDtoList) {
|
||||||
// 批量保存按钮
|
// 批量保存按钮
|
||||||
Boolean flag = true;
|
Boolean flag = true;
|
||||||
return productTransferAppService.addOrEditBatchTransferReceipt(productTransferDtoList, flag);
|
return productTransferAppService.addOrEditBatchTransferReceipt(productTransferDtoList, flag);
|
||||||
|
|||||||
@@ -133,13 +133,47 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
|||||||
@Override
|
@Override
|
||||||
public IPage<PatientBaseInfoDto> getPatientInfo(PatientBaseInfoDto patientBaseInfoDto, String searchKey,
|
public IPage<PatientBaseInfoDto> getPatientInfo(PatientBaseInfoDto patientBaseInfoDto, String searchKey,
|
||||||
Integer pageNo, Integer pageSize, HttpServletRequest request) {
|
Integer pageNo, Integer pageSize, HttpServletRequest request) {
|
||||||
// 构建基础查询条件
|
// 获取登录者信息
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
Long userId = loginUser.getUserId();
|
||||||
|
Integer tenantId = loginUser.getTenantId().intValue();
|
||||||
|
|
||||||
|
// 先构建基础查询条件
|
||||||
QueryWrapper<PatientBaseInfoDto> queryWrapper = HisQueryUtils.buildQueryWrapper(
|
QueryWrapper<PatientBaseInfoDto> queryWrapper = HisQueryUtils.buildQueryWrapper(
|
||||||
patientBaseInfoDto, searchKey, new HashSet<>(Arrays.asList(CommonConstants.FieldName.Name,
|
patientBaseInfoDto, searchKey, new HashSet<>(Arrays.asList(CommonConstants.FieldName.Name,
|
||||||
CommonConstants.FieldName.BusNo, CommonConstants.FieldName.PyStr, CommonConstants.FieldName.WbStr)),
|
CommonConstants.FieldName.BusNo, CommonConstants.FieldName.PyStr, CommonConstants.FieldName.WbStr)),
|
||||||
request);
|
request);
|
||||||
|
|
||||||
|
// 检查是否是精确ID查询(从门诊挂号页面跳转时使用)
|
||||||
|
boolean hasExactIdQuery = (patientBaseInfoDto.getId() != null);
|
||||||
|
|
||||||
|
// 只有非精确ID查询时,才添加医生患者过滤条件
|
||||||
|
if (!hasExactIdQuery) {
|
||||||
|
// 查询当前用户对应的医生信息
|
||||||
|
LambdaQueryWrapper<com.openhis.administration.domain.Practitioner> practitionerQuery = new LambdaQueryWrapper<>();
|
||||||
|
practitionerQuery.eq(com.openhis.administration.domain.Practitioner::getUserId, userId);
|
||||||
|
// 使用list()避免TooManyResultsException异常,然后取第一个记录
|
||||||
|
List<com.openhis.administration.domain.Practitioner> practitionerList = practitionerService.list(practitionerQuery);
|
||||||
|
com.openhis.administration.domain.Practitioner practitioner = practitionerList != null && !practitionerList.isEmpty() ? practitionerList.get(0) : null;
|
||||||
|
|
||||||
|
// 如果当前用户是医生,添加医生患者过滤条件
|
||||||
|
if (practitioner != null) {
|
||||||
|
// 查询该医生作为接诊医生(ADMITTER, code="1")和挂号医生(REGISTRATION_DOCTOR, code="12")的所有就诊记录的患者ID
|
||||||
|
List<Long> doctorPatientIds = patientManageMapper.getPatientIdsByPractitionerId(
|
||||||
|
practitioner.getId(),
|
||||||
|
Arrays.asList(ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode()),
|
||||||
|
tenantId);
|
||||||
|
|
||||||
|
if (doctorPatientIds != null && !doctorPatientIds.isEmpty()) {
|
||||||
|
// 添加患者ID过滤条件 - 注意:这里使用列名而不是表别名
|
||||||
|
queryWrapper.in("id", doctorPatientIds);
|
||||||
|
} else {
|
||||||
|
// 如果没有相关患者,返回空结果
|
||||||
|
queryWrapper.eq("id", -1); // 设置一个不存在的ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果不是医生,查询所有患者
|
||||||
|
}
|
||||||
|
|
||||||
IPage<PatientBaseInfoDto> patientInformationPage
|
IPage<PatientBaseInfoDto> patientInformationPage
|
||||||
= patientManageMapper.getPatientPage(new Page<>(pageNo, pageSize), queryWrapper);
|
= patientManageMapper.getPatientPage(new Page<>(pageNo, pageSize), queryWrapper);
|
||||||
@@ -235,7 +269,7 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
|||||||
// log.debug("添加病人信息,patientInfoDto:{}", patientBaseInfoDto);
|
// log.debug("添加病人信息,patientInfoDto:{}", patientBaseInfoDto);
|
||||||
// 如果患者没有输入身份证号则根据年龄自动生成
|
// 如果患者没有输入身份证号则根据年龄自动生成
|
||||||
String idCard = patientBaseInfoDto.getIdCard();
|
String idCard = patientBaseInfoDto.getIdCard();
|
||||||
if (idCard == null || idCard.length() < 6 || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {
|
if (idCard == null || CommonConstants.Common.AREA_CODE.equals(idCard.substring(0, 6))) {
|
||||||
if (patientBaseInfoDto.getAge() != null) {
|
if (patientBaseInfoDto.getAge() != null) {
|
||||||
idCard = IdCardUtil.generateIdByAge(patientBaseInfoDto.getAge());
|
idCard = IdCardUtil.generateIdByAge(patientBaseInfoDto.getAge());
|
||||||
patientBaseInfoDto.setIdCard(idCard);
|
patientBaseInfoDto.setIdCard(idCard);
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ public class PendingMedicationDetailsAppServiceImpl implements IPendingMedicatio
|
|||||||
Page<PendingMedicationPageDto> pendingMedicationPage = pendingMedicationDetailsMapper
|
Page<PendingMedicationPageDto> pendingMedicationPage = pendingMedicationDetailsMapper
|
||||||
.selectPendingMedicationDetailsPage(new Page<>(pageNo, pageSize), queryWrapper,
|
.selectPendingMedicationDetailsPage(new Page<>(pageNo, pageSize), queryWrapper,
|
||||||
DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.PREPARATION.getValue(),
|
DispenseStatus.IN_PROGRESS.getValue(), DispenseStatus.PREPARATION.getValue(),
|
||||||
DispenseStatus.PREPARED.getValue(), DispenseStatus.SUMMARIZED.getValue(),
|
DispenseStatus.PREPARED.getValue(), EncounterClass.AMB.getValue(), EncounterClass.IMP.getValue());
|
||||||
EncounterClass.AMB.getValue(), EncounterClass.IMP.getValue());
|
|
||||||
|
|
||||||
pendingMedicationPage.getRecords().forEach(e -> {
|
pendingMedicationPage.getRecords().forEach(e -> {
|
||||||
// 发药类型
|
// 发药类型
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ public interface PendingMedicationDetailsMapper {
|
|||||||
* @param inProgress 发药类型:待发药
|
* @param inProgress 发药类型:待发药
|
||||||
* @param preparation 发药类型:待配药
|
* @param preparation 发药类型:待配药
|
||||||
* @param prepared 发药类型:已配药
|
* @param prepared 发药类型:已配药
|
||||||
* @param summarized 发药类型:已汇总
|
|
||||||
* @param amb 门诊类型
|
* @param amb 门诊类型
|
||||||
* @param imp 住院类型
|
* @param imp 住院类型
|
||||||
* @return 待发药明细
|
* @return 待发药明细
|
||||||
@@ -33,7 +32,6 @@ public interface PendingMedicationDetailsMapper {
|
|||||||
@Param("inProgress") Integer inProgress,
|
@Param("inProgress") Integer inProgress,
|
||||||
@Param("preparation") Integer preparation,
|
@Param("preparation") Integer preparation,
|
||||||
@Param("prepared") Integer prepared,
|
@Param("prepared") Integer prepared,
|
||||||
@Param("summarized") Integer summarized,
|
|
||||||
@Param("amb") Integer amb,
|
@Param("amb") Integer amb,
|
||||||
@Param("imp") Integer imp);
|
@Param("imp") Integer imp);
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,12 @@ import com.openhis.web.regdoctorstation.dto.*;
|
|||||||
import com.openhis.web.regdoctorstation.mapper.AdviceManageAppMapper;
|
import com.openhis.web.regdoctorstation.mapper.AdviceManageAppMapper;
|
||||||
import com.openhis.web.regdoctorstation.utils.RegPrescriptionUtils;
|
import com.openhis.web.regdoctorstation.utils.RegPrescriptionUtils;
|
||||||
import com.openhis.workflow.domain.DeviceRequest;
|
import com.openhis.workflow.domain.DeviceRequest;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.IActivityDefinitionService;
|
import com.openhis.workflow.service.IActivityDefinitionService;
|
||||||
import com.openhis.workflow.domain.ActivityDefinition;
|
import com.openhis.workflow.domain.ActivityDefinition;
|
||||||
import com.openhis.workflow.service.IDeviceDispenseService;
|
import com.openhis.workflow.service.IDeviceDispenseService;
|
||||||
import com.openhis.workflow.service.IDeviceRequestService;
|
import com.openhis.workflow.service.IDeviceRequestService;
|
||||||
import com.openhis.workflow.service.IServiceRequestService;
|
import com.openhis.workflow.service.IServiceRequestService;
|
||||||
import com.openhis.document.domain.RequestForm;
|
|
||||||
import com.openhis.document.service.IRequestFormService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -90,9 +85,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
@Resource
|
@Resource
|
||||||
IDeviceDispenseService iDeviceDispenseService;
|
IDeviceDispenseService iDeviceDispenseService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
IRequestFormService iRequestFormService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询住院患者信息
|
* 查询住院患者信息
|
||||||
*
|
*
|
||||||
@@ -274,38 +266,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
|
|
||||||
log.info("开始处理删除操作,共 {} 条记录", deleteList.size());
|
log.info("开始处理删除操作,共 {} 条记录", deleteList.size());
|
||||||
|
|
||||||
// 🔧 手术申请单级联作废:删除手术医嘱(categoryEnum=24)时同步作废关联的手术申请单
|
|
||||||
Map<String, List<Long>> surgeryPrescriptionNoToRequestIds = new LinkedHashMap<>();
|
|
||||||
for (RegAdviceSaveDto adviceDto : deleteList) {
|
|
||||||
if (adviceDto.getRequestId() == null) continue;
|
|
||||||
ServiceRequest existing = iServiceRequestService.getById(adviceDto.getRequestId());
|
|
||||||
if (existing == null) continue;
|
|
||||||
if (existing.getCategoryEnum() != null
|
|
||||||
&& existing.getCategoryEnum() == 24
|
|
||||||
&& existing.getPrescriptionNo() != null && !existing.getPrescriptionNo().isEmpty()) {
|
|
||||||
surgeryPrescriptionNoToRequestIds.computeIfAbsent(existing.getPrescriptionNo(), k -> new ArrayList<>())
|
|
||||||
.add(adviceDto.getRequestId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Map.Entry<String, List<Long>> e : surgeryPrescriptionNoToRequestIds.entrySet()) {
|
|
||||||
String prescriptionNo = e.getKey();
|
|
||||||
try {
|
|
||||||
List<RequestForm> requestForms = iRequestFormService.list(
|
|
||||||
new LambdaQueryWrapper<RequestForm>()
|
|
||||||
.eq(RequestForm::getPrescriptionNo, prescriptionNo)
|
|
||||||
.eq(RequestForm::getTypeCode, ActivityDefCategory.PROCEDURE.getCode())
|
|
||||||
.and(w -> w.isNull(RequestForm::getDeleteFlag).or().eq(RequestForm::getDeleteFlag, "0")));
|
|
||||||
for (RequestForm requestForm : requestForms) {
|
|
||||||
iRequestFormService.removeById(requestForm.getId());
|
|
||||||
}
|
|
||||||
if (!requestForms.isEmpty()) {
|
|
||||||
log.info("级联作废手术申请单 prescriptionNo={}, 共{}条", prescriptionNo, requestForms.size());
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("级联作废手术申请单失败 prescriptionNo={}", prescriptionNo, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (RegAdviceSaveDto adviceDto : deleteList) {
|
for (RegAdviceSaveDto adviceDto : deleteList) {
|
||||||
Integer adviceType = adviceDto.getAdviceType();
|
Integer adviceType = adviceDto.getAdviceType();
|
||||||
Long requestId = adviceDto.getRequestId();
|
Long requestId = adviceDto.getRequestId();
|
||||||
@@ -354,27 +314,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
/**
|
/**
|
||||||
* 处理药品
|
* 处理药品
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 remark 合并到 contentJson 中,确保 Mapper 能从 content_json 提取 remark
|
|
||||||
*/
|
|
||||||
private String injectRemarkIntoContentJson(String contentJson, String remark) {
|
|
||||||
if (remark == null || remark.isEmpty() || contentJson == null || contentJson.isEmpty()) {
|
|
||||||
return contentJson;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
JsonNode node = mapper.readTree(contentJson);
|
|
||||||
if (node instanceof ObjectNode) {
|
|
||||||
((ObjectNode) node).put("remark", remark);
|
|
||||||
return mapper.writeValueAsString(node);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to inject remark into contentJson: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
return contentJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> handMedication(List<RegAdviceSaveDto> medicineList, Date startTime, Date authoredTime,
|
private List<String> handMedication(List<RegAdviceSaveDto> medicineList, Date startTime, Date authoredTime,
|
||||||
Date curDate, String adviceOpType, Long organizationId, String signCode) {
|
Date curDate, String adviceOpType, Long organizationId, String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
@@ -473,10 +412,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (longMedicationRequest.getId() == null) {
|
if (longMedicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
longMedicationRequest.setContentJson(injectRemarkIntoContentJson(longMedicationRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iMedicationRequestService.saveOrUpdate(longMedicationRequest);
|
iMedicationRequestService.saveOrUpdate(longMedicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(longMedicationRequest.getId().toString());
|
medRequestIdList.add(longMedicationRequest.getId().toString());
|
||||||
@@ -564,10 +499,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
if (tempMedicationRequest.getId() == null) {
|
if (tempMedicationRequest.getId() == null) {
|
||||||
firstTimeSave = true;
|
firstTimeSave = true;
|
||||||
}
|
}
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
tempMedicationRequest.setContentJson(injectRemarkIntoContentJson(tempMedicationRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iMedicationRequestService.saveOrUpdate(tempMedicationRequest);
|
iMedicationRequestService.saveOrUpdate(tempMedicationRequest);
|
||||||
if (firstTimeSave) {
|
if (firstTimeSave) {
|
||||||
medRequestIdList.add(tempMedicationRequest.getId().toString());
|
medRequestIdList.add(tempMedicationRequest.getId().toString());
|
||||||
@@ -671,7 +602,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
longServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
longServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
longServiceRequest.setRemark(regAdviceSaveDto.getRemark());
|
|
||||||
iServiceRequestService.saveOrUpdate(longServiceRequest);
|
iServiceRequestService.saveOrUpdate(longServiceRequest);
|
||||||
if (longServiceRequest.getId() != null) {
|
if (longServiceRequest.getId() != null) {
|
||||||
processedRequestIds.add(longServiceRequest.getId());
|
processedRequestIds.add(longServiceRequest.getId());
|
||||||
@@ -723,7 +653,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
tempServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
tempServiceRequest.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tempServiceRequest.setRemark(regAdviceSaveDto.getRemark());
|
|
||||||
iServiceRequestService.saveOrUpdate(tempServiceRequest);
|
iServiceRequestService.saveOrUpdate(tempServiceRequest);
|
||||||
if (tempServiceRequest.getId() != null) {
|
if (tempServiceRequest.getId() != null) {
|
||||||
processedRequestIds.add(tempServiceRequest.getId());
|
processedRequestIds.add(tempServiceRequest.getId());
|
||||||
@@ -855,10 +784,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
}
|
}
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,10 +823,6 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
|
||||||
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
}
|
}
|
||||||
// 确保 contentJson 包含 remark
|
|
||||||
if (regAdviceSaveDto.getRemark() != null && !regAdviceSaveDto.getRemark().isEmpty()) {
|
|
||||||
deviceRequest.setContentJson(injectRemarkIntoContentJson(deviceRequest.getContentJson(), regAdviceSaveDto.getRemark()));
|
|
||||||
}
|
|
||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
|
|
||||||
// 保存时,保存耗材费用项
|
// 保存时,保存耗材费用项
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.core.common.enums.DelFlag;
|
|
||||||
import com.core.common.exception.ServiceException;
|
import com.core.common.exception.ServiceException;
|
||||||
import com.core.common.utils.AssignSeqUtil;
|
import com.core.common.utils.AssignSeqUtil;
|
||||||
import com.core.common.utils.MessageUtils;
|
import com.core.common.utils.MessageUtils;
|
||||||
@@ -18,8 +17,6 @@ import com.openhis.common.constant.PromptMsgConstant;
|
|||||||
import com.openhis.common.enums.*;
|
import com.openhis.common.enums.*;
|
||||||
import com.openhis.document.domain.RequestForm;
|
import com.openhis.document.domain.RequestForm;
|
||||||
import com.openhis.document.service.IRequestFormService;
|
import com.openhis.document.service.IRequestFormService;
|
||||||
import com.openhis.lab.domain.Specimen;
|
|
||||||
import com.openhis.lab.service.ISpecimenService;
|
|
||||||
import com.openhis.web.doctorstation.dto.ActivityChildrenJsonParams;
|
import com.openhis.web.doctorstation.dto.ActivityChildrenJsonParams;
|
||||||
import com.openhis.web.doctorstation.utils.AdviceUtils;
|
import com.openhis.web.doctorstation.utils.AdviceUtils;
|
||||||
import com.openhis.web.regdoctorstation.appservice.IRequestFormManageAppService;
|
import com.openhis.web.regdoctorstation.appservice.IRequestFormManageAppService;
|
||||||
@@ -70,39 +67,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
@Resource
|
@Resource
|
||||||
IActivityDefinitionService iActivityDefinitionService;
|
IActivityDefinitionService iActivityDefinitionService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
ISpecimenService iSpecimenService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验当前用户是否有权操作该申请单(申请者本人或管理员)
|
|
||||||
*/
|
|
||||||
private R<?> validateRequestFormPermission(RequestForm requestForm) {
|
|
||||||
if (SecurityUtils.isAdmin(SecurityUtils.getUserId())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Long currentPractitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
|
||||||
Long requesterId = requestForm.getRequesterId();
|
|
||||||
if (currentPractitionerId == null || requesterId == null
|
|
||||||
|| !currentPractitionerId.equals(requesterId)) {
|
|
||||||
return R.fail("无操作权限,仅申请开立者或管理员可操作");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验关联医嘱是否已采证(存在已采集/已接收标本则不可撤回)
|
|
||||||
*/
|
|
||||||
private boolean hasCollectedSpecimen(List<Long> serviceRequestIds) {
|
|
||||||
if (serviceRequestIds == null || serviceRequestIds.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long count = iSpecimenService.count(
|
|
||||||
new LambdaQueryWrapper<Specimen>()
|
|
||||||
.in(Specimen::getServiceId, serviceRequestIds)
|
|
||||||
.ge(Specimen::getCollectionStatusEnum, SpecCollectStatus.COLLECTED.getValue()));
|
|
||||||
return count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存申请单
|
* 保存申请单
|
||||||
*
|
*
|
||||||
@@ -117,7 +81,16 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
Long requestFormId = requestFormSaveDto.getRequestFormId();
|
Long requestFormId = requestFormSaveDto.getRequestFormId();
|
||||||
boolean isEdit = requestFormId != null && requestFormId != 0L;
|
boolean isEdit = requestFormId != null && requestFormId != 0L;
|
||||||
|
|
||||||
// 🔧 手术/检查申请单:优先使用前端传入的positionId(用户手动选择的发往科室),跳过执行科室配置校验
|
// 诊疗执行科室配置校验(必须在任何数据库操作之前)
|
||||||
|
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
|
||||||
|
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
|
||||||
|
if (activityOrganizationConfig.isEmpty()) {
|
||||||
|
throw new ServiceException("请先配置当前时间段的执行科室");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个校验activityList中的项目是否都配置了执行科室,并收集positionId供后续使用
|
||||||
|
// 必须在任何数据库操作之前完成全部校验,避免部分保存后异常导致脏数据
|
||||||
|
// 🔧 Bug #516: 优先使用前端传入的positionId(用户手动选择的发往科室),仅在未选择时使用配置的执行科室
|
||||||
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
List<ActivitySaveDto> activityList = requestFormSaveDto.getActivityList();
|
||||||
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
|
// 缓存校验结果,避免主循环中重复查询和可能出现的数据不一致
|
||||||
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
|
java.util.Map<Long, Long> activityIdToPositionIdMap = new java.util.HashMap<>();
|
||||||
@@ -129,15 +102,14 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), frontendPositionId);
|
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), frontendPositionId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 前端未传入时,查询配置的执行科室(暂不校验,仅用于兼容无前端传入的场景)
|
// 前端未传入时,使用配置的执行科室
|
||||||
List<ActivityOrganizationConfigDto> activityOrganizationConfig =
|
|
||||||
requestFormManageAppMapper.getActivityOrganizationConfig(typeCode);
|
|
||||||
Long configPositionId = activityOrganizationConfig.stream()
|
Long configPositionId = activityOrganizationConfig.stream()
|
||||||
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
.filter(dto -> activitySaveDto.getAdviceDefinitionId().equals(dto.getActivityDefinitionId()))
|
||||||
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
.map(ActivityOrganizationConfigDto::getOrganizationId).findFirst().orElse(null);
|
||||||
if (configPositionId != null) {
|
if (configPositionId == null) {
|
||||||
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), configPositionId);
|
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||||
}
|
}
|
||||||
|
activityIdToPositionIdMap.put(activitySaveDto.getAdviceDefinitionId(), configPositionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,77 +176,73 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
ChargeItem chargeItem;
|
ChargeItem chargeItem;
|
||||||
log.info("保存申请单,typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
|
log.info("保存申请单,typeCode={}, activityListSize={}, encounterId={}", typeCode, activityList != null ? activityList.size() : 0, encounterId);
|
||||||
|
|
||||||
// 🔧 手术申请单:跳过普通医嘱生成,只由 isProcedure 块生成手术医嘱,避免重复
|
for (ActivitySaveDto activitySaveDto : activityList) {
|
||||||
boolean isSurgeryRequest = ActivityDefCategory.PROCEDURE.getCode().equals(typeCode);
|
serviceRequest = new ServiceRequest();
|
||||||
if (!isSurgeryRequest) {
|
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
|
||||||
for (ActivitySaveDto activitySaveDto : activityList) {
|
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||||
serviceRequest = new ServiceRequest();
|
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
|
serviceRequest.setPrescriptionNo(prescriptionNo);
|
||||||
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
||||||
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
serviceRequest.setQuantity(activitySaveDto.getQuantity()); // 请求数量
|
||||||
serviceRequest.setPrescriptionNo(prescriptionNo);
|
serviceRequest.setUnitCode(activitySaveDto.getUnitCode()); // 请求单位编码
|
||||||
serviceRequest.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue());// 治疗类型
|
serviceRequest.setCategoryEnum(categoryEnum); // 请求类型
|
||||||
serviceRequest.setQuantity(activitySaveDto.getQuantity()); // 请求数量
|
serviceRequest.setActivityId(activitySaveDto.getAdviceDefinitionId());// 诊疗定义id
|
||||||
serviceRequest.setUnitCode(activitySaveDto.getUnitCode()); // 请求单位编码
|
serviceRequest.setPatientId(patientId); // 患者
|
||||||
serviceRequest.setCategoryEnum(categoryEnum); // 请求类型
|
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
||||||
serviceRequest.setActivityId(activitySaveDto.getAdviceDefinitionId());// 诊疗定义id
|
serviceRequest.setEncounterId(encounterId); // 就诊id
|
||||||
serviceRequest.setPatientId(patientId); // 患者
|
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
||||||
serviceRequest.setRequesterId(practitionerId); // 开方医生
|
|
||||||
serviceRequest.setEncounterId(encounterId); // 就诊id
|
|
||||||
serviceRequest.setAuthoredTime(curDate); // 请求签发时间
|
|
||||||
|
|
||||||
Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
|
Long positionId = activityIdToPositionIdMap.get(activitySaveDto.getAdviceDefinitionId());
|
||||||
if (positionId == null) {
|
if (positionId == null) {
|
||||||
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
throw new ServiceException(activitySaveDto.getAdviceDefinitionName() + "未配置当前时间段的执行科室");
|
||||||
}
|
}
|
||||||
serviceRequest.setOrgId(positionId); // 执行科室
|
serviceRequest.setOrgId(positionId); // 执行科室
|
||||||
|
|
||||||
serviceRequest.setYbClassEnum(activitySaveDto.getYbClassEnum());// 类别医保编码
|
serviceRequest.setYbClassEnum(activitySaveDto.getYbClassEnum());// 类别医保编码
|
||||||
serviceRequest.setConditionId(activitySaveDto.getConditionId()); // 诊断id
|
serviceRequest.setConditionId(activitySaveDto.getConditionId()); // 诊断id
|
||||||
serviceRequest.setEncounterDiagnosisId(activitySaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
serviceRequest.setEncounterDiagnosisId(activitySaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
iServiceRequestService.save(serviceRequest);
|
iServiceRequestService.save(serviceRequest);
|
||||||
|
|
||||||
chargeItem = new ChargeItem();
|
chargeItem = new ChargeItem();
|
||||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
chargeItem.setPatientId(patientId); // 患者
|
chargeItem.setPatientId(patientId); // 患者
|
||||||
chargeItem.setContextEnum(activitySaveDto.getAdviceType()); // 类型
|
chargeItem.setContextEnum(activitySaveDto.getAdviceType()); // 类型
|
||||||
chargeItem.setEncounterId(encounterId); // 就诊id
|
chargeItem.setEncounterId(encounterId); // 就诊id
|
||||||
chargeItem.setDefinitionId(activitySaveDto.getDefinitionId()); // 费用定价ID
|
chargeItem.setDefinitionId(activitySaveDto.getDefinitionId()); // 费用定价ID
|
||||||
chargeItem.setDefDetailId(activitySaveDto.getDefinitionDetailId()); // 定价子表主键
|
chargeItem.setDefDetailId(activitySaveDto.getDefinitionDetailId()); // 定价子表主键
|
||||||
chargeItem.setEntererId(practitionerId);// 开立人ID
|
chargeItem.setEntererId(practitionerId);// 开立人ID
|
||||||
chargeItem.setEnteredDate(curDate); // 开立时间
|
chargeItem.setEnteredDate(curDate); // 开立时间
|
||||||
chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);// 医疗服务类型
|
chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);// 医疗服务类型
|
||||||
chargeItem.setServiceId(serviceRequest.getId()); // 医疗服务ID
|
chargeItem.setServiceId(serviceRequest.getId()); // 医疗服务ID
|
||||||
chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);// 产品所在表
|
chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);// 产品所在表
|
||||||
chargeItem.setProductId(activitySaveDto.getAdviceDefinitionId());// 收费项id
|
chargeItem.setProductId(activitySaveDto.getAdviceDefinitionId());// 收费项id
|
||||||
chargeItem.setAccountId(activitySaveDto.getAccountId());// 关联账户ID
|
chargeItem.setAccountId(activitySaveDto.getAccountId());// 关联账户ID
|
||||||
chargeItem.setRequestingOrgId(orgId); // 开立科室
|
chargeItem.setRequestingOrgId(orgId); // 开立科室
|
||||||
chargeItem.setConditionId(activitySaveDto.getConditionId()); // 诊断id
|
chargeItem.setConditionId(activitySaveDto.getConditionId()); // 诊断id
|
||||||
chargeItem.setEncounterDiagnosisId(activitySaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
chargeItem.setEncounterDiagnosisId(activitySaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
chargeItem.setQuantityValue(activitySaveDto.getQuantity()); // 数量
|
chargeItem.setQuantityValue(activitySaveDto.getQuantity()); // 数量
|
||||||
chargeItem.setQuantityUnit(activitySaveDto.getUnitCode()); // 单位
|
chargeItem.setQuantityUnit(activitySaveDto.getUnitCode()); // 单位
|
||||||
chargeItem.setUnitPrice(activitySaveDto.getUnitPrice()); // 单价
|
chargeItem.setUnitPrice(activitySaveDto.getUnitPrice()); // 单价
|
||||||
chargeItem.setTotalPrice(activitySaveDto.getTotalPrice()); // 总价
|
chargeItem.setTotalPrice(activitySaveDto.getTotalPrice()); // 总价
|
||||||
iChargeItemService.save(chargeItem);
|
iChargeItemService.save(chargeItem);
|
||||||
|
|
||||||
// 处理诊疗套餐的子项信息
|
// 处理诊疗套餐的子项信息
|
||||||
ActivityDefinition activityDefinition =
|
ActivityDefinition activityDefinition =
|
||||||
iActivityDefinitionService.getById(activitySaveDto.getAdviceDefinitionId());
|
iActivityDefinitionService.getById(activitySaveDto.getAdviceDefinitionId());
|
||||||
String childrenJson = activityDefinition.getChildrenJson();
|
String childrenJson = activityDefinition.getChildrenJson();
|
||||||
if (childrenJson != null) {
|
if (childrenJson != null) {
|
||||||
// 诊疗子项参数类
|
// 诊疗子项参数类
|
||||||
ActivityChildrenJsonParams activityChildrenJsonParams = new ActivityChildrenJsonParams();
|
ActivityChildrenJsonParams activityChildrenJsonParams = new ActivityChildrenJsonParams();
|
||||||
activityChildrenJsonParams.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue()); // 治疗类型
|
activityChildrenJsonParams.setTherapyEnum(TherapyTimeType.TEMPORARY.getValue()); // 治疗类型
|
||||||
activityChildrenJsonParams.setPatientId(serviceRequest.getPatientId()); // 患者
|
activityChildrenJsonParams.setPatientId(serviceRequest.getPatientId()); // 患者
|
||||||
activityChildrenJsonParams.setEncounterId(serviceRequest.getEncounterId()); // 就诊id
|
activityChildrenJsonParams.setEncounterId(serviceRequest.getEncounterId()); // 就诊id
|
||||||
activityChildrenJsonParams.setAccountId(chargeItem.getAccountId()); // 账户id
|
activityChildrenJsonParams.setAccountId(chargeItem.getAccountId()); // 账户id
|
||||||
activityChildrenJsonParams.setChargeItemId(chargeItem.getId()); // 费用项id
|
activityChildrenJsonParams.setChargeItemId(chargeItem.getId()); // 费用项id
|
||||||
activityChildrenJsonParams.setParentId(serviceRequest.getId());// 子项诊疗的父id
|
activityChildrenJsonParams.setParentId(serviceRequest.getId());// 子项诊疗的父id
|
||||||
activityChildrenJsonParams.setEncounterDiagnosisId(serviceRequest.getEncounterDiagnosisId());
|
activityChildrenJsonParams.setEncounterDiagnosisId(serviceRequest.getEncounterDiagnosisId());
|
||||||
adviceUtils.handleActivityChild(childrenJson, organizationId, activityChildrenJsonParams);
|
adviceUtils.handleActivityChild(childrenJson, organizationId, activityChildrenJsonParams);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,13 +326,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
} else if (surgeryName != null && !surgeryName.isEmpty()) {
|
} else if (surgeryName != null && !surgeryName.isEmpty()) {
|
||||||
contentMap.put("surgeryName", surgeryName);
|
contentMap.put("surgeryName", surgeryName);
|
||||||
}
|
}
|
||||||
// 🔧 手术申请单级联删除:contentJson 中记录 requestFormId 和 prescriptionNo,删除医嘱时可定位并作废申请单
|
|
||||||
if (requestForm.getId() != null) {
|
|
||||||
contentMap.put("requestFormId", String.valueOf(requestForm.getId()));
|
|
||||||
}
|
|
||||||
if (prescriptionNo != null && !prescriptionNo.isEmpty()) {
|
|
||||||
contentMap.put("prescriptionNo", prescriptionNo);
|
|
||||||
}
|
|
||||||
if (surgeryCode != null && !surgeryCode.isEmpty()) {
|
if (surgeryCode != null && !surgeryCode.isEmpty()) {
|
||||||
contentMap.put("surgeryCode", surgeryCode);
|
contentMap.put("surgeryCode", surgeryCode);
|
||||||
}
|
}
|
||||||
@@ -407,10 +368,9 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
surgeryChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
surgeryChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
||||||
surgeryChargeItem.setServiceId(surgeryServiceRequest.getId());
|
surgeryChargeItem.setServiceId(surgeryServiceRequest.getId());
|
||||||
surgeryChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
|
surgeryChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
|
||||||
// 优先从 activityList 获取 productId 和 definitionId
|
// 优先从 activityList 获取 productId
|
||||||
if (activityList != null && !activityList.isEmpty()) {
|
if (activityList != null && !activityList.isEmpty()) {
|
||||||
surgeryChargeItem.setProductId(activityList.get(0).getAdviceDefinitionId());
|
surgeryChargeItem.setProductId(activityList.get(0).getAdviceDefinitionId());
|
||||||
surgeryChargeItem.setDefinitionId(activityList.get(0).getDefinitionId());
|
|
||||||
surgeryChargeItem.setAccountId(activityList.get(0).getAccountId());
|
surgeryChargeItem.setAccountId(activityList.get(0).getAccountId());
|
||||||
}
|
}
|
||||||
surgeryChargeItem.setRequestingOrgId(orgId);
|
surgeryChargeItem.setRequestingOrgId(orgId);
|
||||||
@@ -449,10 +409,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
anesthesiaChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
anesthesiaChargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
||||||
anesthesiaChargeItem.setServiceId(surgeryServiceRequest.getId());
|
anesthesiaChargeItem.setServiceId(surgeryServiceRequest.getId());
|
||||||
anesthesiaChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
|
anesthesiaChargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
|
||||||
// 从 activityList 获取 definitionId
|
|
||||||
if (activityList != null && !activityList.isEmpty()) {
|
|
||||||
anesthesiaChargeItem.setDefinitionId(activityList.get(0).getDefinitionId());
|
|
||||||
}
|
|
||||||
anesthesiaChargeItem.setRequestingOrgId(orgId);
|
anesthesiaChargeItem.setRequestingOrgId(orgId);
|
||||||
anesthesiaChargeItem.setQuantityValue(BigDecimal.valueOf(1));
|
anesthesiaChargeItem.setQuantityValue(BigDecimal.valueOf(1));
|
||||||
anesthesiaChargeItem.setQuantityUnit("次");
|
anesthesiaChargeItem.setQuantityUnit("次");
|
||||||
@@ -469,18 +425,6 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
log.error("生成手术医嘱过程中发生异常", e);
|
log.error("生成手术医嘱过程中发生异常", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
// 将手术项目名称写入申请单name字段,确保医嘱删除后申请单仍保留正确名称
|
|
||||||
if (activityList != null && !activityList.isEmpty()) {
|
|
||||||
String surgeryDisplayName = activityList.stream()
|
|
||||||
.map(ActivitySaveDto::getAdviceDefinitionName)
|
|
||||||
.filter(name -> name != null && !name.isEmpty())
|
|
||||||
.distinct()
|
|
||||||
.collect(Collectors.joining("、"));
|
|
||||||
if (!surgeryDisplayName.isEmpty()) {
|
|
||||||
requestForm.setName(surgeryDisplayName);
|
|
||||||
iRequestFormService.updateById(requestForm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.info("不是手术申请单,跳过手术医嘱生成,typeCode={}", typeCode);
|
log.info("不是手术申请单,跳过手术医嘱生成,typeCode={}", typeCode);
|
||||||
}
|
}
|
||||||
@@ -564,17 +508,12 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
if (requestForm == null) {
|
if (requestForm == null) {
|
||||||
return R.fail("申请单不存在");
|
return R.fail("申请单不存在");
|
||||||
}
|
}
|
||||||
R<?> permissionResult = validateRequestFormPermission(requestForm);
|
|
||||||
if (permissionResult != null) {
|
|
||||||
return permissionResult;
|
|
||||||
}
|
|
||||||
String prescriptionNo = requestForm.getPrescriptionNo();
|
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||||
|
|
||||||
// 查询该申请单下所有 ServiceRequest(含子项)
|
// 查询该申请单下所有 ServiceRequest(含子项)
|
||||||
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||||
new LambdaQueryWrapper<ServiceRequest>()
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo)
|
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
|
||||||
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||||
return R.fail("未找到关联的诊疗医嘱");
|
return R.fail("未找到关联的诊疗医嘱");
|
||||||
}
|
}
|
||||||
@@ -604,7 +543,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
// 4. 删除申请单
|
// 4. 删除申请单
|
||||||
iRequestFormService.removeById(requestFormId);
|
iRequestFormService.removeById(requestFormId);
|
||||||
|
|
||||||
log.info("检验申请单删除成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
log.info("检查申请单删除成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||||
return R.ok("删除成功");
|
return R.ok("删除成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,47 +556,32 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
|
|||||||
if (requestForm == null) {
|
if (requestForm == null) {
|
||||||
return R.fail("申请单不存在");
|
return R.fail("申请单不存在");
|
||||||
}
|
}
|
||||||
R<?> permissionResult = validateRequestFormPermission(requestForm);
|
|
||||||
if (permissionResult != null) {
|
|
||||||
return permissionResult;
|
|
||||||
}
|
|
||||||
String prescriptionNo = requestForm.getPrescriptionNo();
|
String prescriptionNo = requestForm.getPrescriptionNo();
|
||||||
|
|
||||||
// 查询该申请单下所有 ServiceRequest
|
// 查询该申请单下所有 ServiceRequest
|
||||||
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
List<ServiceRequest> serviceRequests = iServiceRequestService.list(
|
||||||
new LambdaQueryWrapper<ServiceRequest>()
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo)
|
.eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
|
||||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
|
||||||
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
if (serviceRequests == null || serviceRequests.isEmpty()) {
|
||||||
return R.fail("未找到关联的诊疗医嘱");
|
return R.fail("未找到关联的诊疗医嘱");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验:只有已签发(status=2)的申请单可撤回
|
||||||
|
boolean allActive = serviceRequests.stream()
|
||||||
|
.allMatch(sr -> RequestStatus.ACTIVE.getValue().equals(sr.getStatusEnum()));
|
||||||
|
if (!allActive) {
|
||||||
|
return R.fail("只有已签发状态的申请单可撤回");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将所有 ServiceRequest 状态改回待签发(DRAFT=0)
|
||||||
List<Long> serviceRequestIds = serviceRequests.stream()
|
List<Long> serviceRequestIds = serviceRequests.stream()
|
||||||
.map(ServiceRequest::getId).collect(Collectors.toList());
|
.map(ServiceRequest::getId).collect(Collectors.toList());
|
||||||
|
iServiceRequestService.update(
|
||||||
// 校验:标本已采集则不可撤回
|
|
||||||
if (hasCollectedSpecimen(serviceRequestIds)) {
|
|
||||||
return R.fail("标本已采集,无法撤回");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验:任一ServiceRequest为ACTIVE(status=2)即可撤回,与SQL的EXISTS逻辑一致
|
|
||||||
boolean hasActive = serviceRequests.stream()
|
|
||||||
.anyMatch(sr -> RequestStatus.ACTIVE.getValue().equals(sr.getStatusEnum()));
|
|
||||||
if (!hasActive) {
|
|
||||||
return R.fail("只有已签发且未采证的申请单可撤回");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将所有已签发的 ServiceRequest 状态改回待签发,与申请单展示状态同步
|
|
||||||
boolean updated = iServiceRequestService.update(
|
|
||||||
new ServiceRequest().setStatusEnum(RequestStatus.DRAFT.getValue()),
|
new ServiceRequest().setStatusEnum(RequestStatus.DRAFT.getValue()),
|
||||||
new LambdaUpdateWrapper<ServiceRequest>()
|
new LambdaUpdateWrapper<ServiceRequest>()
|
||||||
.in(ServiceRequest::getId, serviceRequestIds)
|
.in(ServiceRequest::getId, serviceRequestIds));
|
||||||
.eq(ServiceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue()));
|
|
||||||
if (!updated) {
|
|
||||||
return R.fail("撤回失败,医嘱状态已变更,请刷新后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("检验申请单撤回成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
log.info("检查申请单撤回成功,requestFormId={}, prescriptionNo={}", requestFormId, prescriptionNo);
|
||||||
return R.ok("撤回成功");
|
return R.ok("撤回成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,7 +161,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
|
|||||||
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
|
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
|
||||||
// 对应的诊疗医嘱信息
|
// 对应的诊疗医嘱信息
|
||||||
activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null,
|
activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, null, null,
|
||||||
null, null, 1, 1, null, List.of(3), null, null).getRecords().get(0);
|
null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords().get(0);
|
||||||
// 逻辑1---------------------直接新增
|
// 逻辑1---------------------直接新增
|
||||||
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
||||||
longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
|
longServiceRequest.setOccurrenceStartTime(startTime); // 医嘱开始时间
|
||||||
@@ -209,7 +208,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
|
|||||||
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
|
adviceBaseDto.setAdviceDefinitionId(definitionId); // 医嘱定义id
|
||||||
// 对应的诊疗医嘱信息
|
// 对应的诊疗医嘱信息
|
||||||
activityAdviceBaseDto = iDoctorStationAdviceAppService
|
activityAdviceBaseDto = iDoctorStationAdviceAppService
|
||||||
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, null, List.of(3), null, null)
|
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
|
||||||
.getRecords().get(0);
|
.getRecords().get(0);
|
||||||
|
|
||||||
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
longServiceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
||||||
@@ -349,7 +348,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
|
|||||||
adviceBaseDto.setAdviceDefinitionId(transferOrganizationDefinitionId); // 医嘱定义id
|
adviceBaseDto.setAdviceDefinitionId(transferOrganizationDefinitionId); // 医嘱定义id
|
||||||
// 转科的医嘱信息
|
// 转科的医嘱信息
|
||||||
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService
|
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService
|
||||||
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, null, List.of(3), null, null)
|
.getAdviceBaseInfo(adviceBaseDto, null, null, null, null, 1, 1, Whether.NO.getValue(), List.of(3), null, null)
|
||||||
.getRecords().get(0);
|
.getRecords().get(0);
|
||||||
// 保存转科医嘱请求
|
// 保存转科医嘱请求
|
||||||
ServiceRequest serviceRequest = new ServiceRequest();
|
ServiceRequest serviceRequest = new ServiceRequest();
|
||||||
@@ -401,7 +400,7 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
|
|||||||
// 计划出院时间
|
// 计划出院时间
|
||||||
Date endTime = leaveHospitalParam.getEndTime();
|
Date endTime = leaveHospitalParam.getEndTime();
|
||||||
if (endTime == null) {
|
if (endTime == null) {
|
||||||
endTime = new Date();
|
endTime = endTime;
|
||||||
}
|
}
|
||||||
// 就诊id
|
// 就诊id
|
||||||
Long encounterId = leaveHospitalParam.getEncounterId();
|
Long encounterId = leaveHospitalParam.getEncounterId();
|
||||||
@@ -430,12 +429,9 @@ public class SpecialAdviceAppServiceImpl implements ISpecialAdviceAppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 出院的医嘱信息
|
// 出院的医嘱信息
|
||||||
List<AdviceBaseDto> adviceList = iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
|
AdviceBaseDto activityAdviceBaseDto = iDoctorStationAdviceAppService.getAdviceBaseInfo(null, null, null,
|
||||||
List.of(transferOrganizationDefinitionId), null, 1, 1, null, List.of(3), null, null).getRecords();
|
List.of(transferOrganizationDefinitionId), null, 1, 1, Whether.NO.getValue(), List.of(3), null, null).getRecords()
|
||||||
if (CollectionUtils.isEmpty(adviceList)) {
|
.get(0);
|
||||||
return R.fail("未找到出院医嘱定义数据,请确认诊疗目录中已配置出院医嘱");
|
|
||||||
}
|
|
||||||
AdviceBaseDto activityAdviceBaseDto = adviceList.get(0);
|
|
||||||
// 保存出院医嘱请求
|
// 保存出院医嘱请求
|
||||||
ServiceRequest serviceRequest = new ServiceRequest();
|
ServiceRequest serviceRequest = new ServiceRequest();
|
||||||
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());// 请求状态
|
||||||
|
|||||||
@@ -194,8 +194,8 @@ public class RequestFormManageController {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@PostMapping(value = "/delete")
|
@PostMapping(value = "/delete")
|
||||||
public R<?> deleteRequestForm(@RequestBody Map<String, Object> data) {
|
public R<?> deleteRequestForm(@RequestBody Map<String, Long> data) {
|
||||||
return iRequestFormManageAppService.deleteRequestForm(parseLong(data.get("requestFormId")));
|
return iRequestFormManageAppService.deleteRequestForm(data.get("requestFormId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,24 +205,7 @@ public class RequestFormManageController {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@PostMapping(value = "/withdraw")
|
@PostMapping(value = "/withdraw")
|
||||||
public R<?> withdrawRequestForm(@RequestBody Map<String, Object> data) {
|
public R<?> withdrawRequestForm(@RequestBody Map<String, Long> data) {
|
||||||
return iRequestFormManageAppService.withdrawRequestForm(parseLong(data.get("requestFormId")));
|
return iRequestFormManageAppService.withdrawRequestForm(data.get("requestFormId"));
|
||||||
}
|
|
||||||
|
|
||||||
private Long parseLong(Object value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value instanceof Long) {
|
|
||||||
return (Long) value;
|
|
||||||
}
|
|
||||||
if (value instanceof Number) {
|
|
||||||
return ((Number) value).longValue();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Long.parseLong(value.toString());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,4 @@ public class RegRequestBaseDto extends RequestBaseDto {
|
|||||||
private String doseUnitCode;
|
private String doseUnitCode;
|
||||||
private String doseUnitCode_dictText;
|
private String doseUnitCode_dictText;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注(最长50字)
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ import java.math.BigDecimal;
|
|||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
public class RequestFormDetailQueryDto {
|
public class RequestFormDetailQueryDto {
|
||||||
|
|
||||||
/**
|
|
||||||
* 诊疗活动定义ID(wor_service_request.activity_id,与开立检验时项目字典的 id / adviceDefinitionId 一致,用于编辑回显)
|
|
||||||
*/
|
|
||||||
private Long activityId;
|
|
||||||
|
|
||||||
/** 医嘱名称 */
|
/** 医嘱名称 */
|
||||||
private String adviceName;
|
private String adviceName;
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ public class HomeController {
|
|||||||
HomeStatisticsDto statisticsDto = homeStatisticsService.getHomeStatistics();
|
HomeStatisticsDto statisticsDto = homeStatisticsService.getHomeStatistics();
|
||||||
|
|
||||||
// 获取待写病历数量
|
// 获取待写病历数量
|
||||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
Long userId = SecurityUtils.getLoginUser().getUserId();
|
||||||
R<?> pendingEmrCount = doctorStationEmrAppService.getPendingEmrCount(practitionerId, null);
|
R<?> pendingEmrCount = doctorStationEmrAppService.getPendingEmrCount(userId);
|
||||||
|
|
||||||
// 将待写病历数量添加到统计数据中
|
// 将待写病历数量添加到统计数据中
|
||||||
statisticsDto.setPendingEmr((Integer) pendingEmrCount.getData());
|
statisticsDto.setPendingEmr((Integer) pendingEmrCount.getData());
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
|||||||
.eq(TriageQueueItem::getTenantId, tenantId)
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
.eq(TriageQueueItem::getQueueDate, qd)
|
.eq(TriageQueueItem::getQueueDate, qd)
|
||||||
.eq(TriageQueueItem::getDeleteFlag, "0")
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.ne(TriageQueueItem::getStatus, TriageQueueStatus.COMPLETED.getValue())
|
||||||
.orderByAsc(TriageQueueItem::getQueueOrder);
|
.orderByAsc(TriageQueueItem::getQueueOrder);
|
||||||
|
|
||||||
// 如果指定了科室,按科室过滤;否则查询所有科室(全科模式)
|
// 如果指定了科室,按科室过滤;否则查询所有科室(全科模式)
|
||||||
@@ -91,6 +92,14 @@ public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 双重保险:再次过滤掉 COMPLETED 状态的患者(防止数据库中有异常数据)
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
int beforeSize = list.size();
|
||||||
|
list = list.stream()
|
||||||
|
.filter(item -> !TriageQueueStatus.COMPLETED.getValue().equals(item.getStatus()))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
}
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
cs.apply_doctor_name AS apply_doctor_name,
|
cs.apply_doctor_name AS apply_doctor_name,
|
||||||
drf.create_time AS apply_time,
|
drf.create_time AS apply_time,
|
||||||
os.surgery_nature AS surgeryType,
|
os.surgery_nature AS surgeryType,
|
||||||
cs.incision_level AS incisionLevel,
|
cs.incision_level AS "incisionLevel",
|
||||||
os.fee_type AS feeType,
|
os.fee_type AS feeType,
|
||||||
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
|
||||||
FROM op_schedule os
|
FROM op_schedule os
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
<if test="searchKey != null and searchKey != ''">
|
<if test="searchKey != null and searchKey != ''">
|
||||||
AND (t1.name ILIKE '%' || '${searchKey}' || '%' OR t1.py_str ILIKE '%' || '${searchKey}' || '%')
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
|
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
|
||||||
AND t1.id IN
|
AND t1.id IN
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
WHERE t1.delete_flag = '0'
|
WHERE t1.delete_flag = '0'
|
||||||
AND t1.status_enum = #{statusEnum}
|
AND t1.status_enum = #{statusEnum}
|
||||||
<if test="searchKey != null and searchKey != ''">
|
<if test="searchKey != null and searchKey != ''">
|
||||||
AND (t1.name ILIKE '%' || '${searchKey}' || '%' OR t1.py_str ILIKE '%' || '${searchKey}' || '%')
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="categoryCode != null and categoryCode != ''">
|
<if test="categoryCode != null and categoryCode != ''">
|
||||||
AND t1.category_code = #{categoryCode}
|
AND t1.category_code = #{categoryCode}
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
AND T1.category_code != '手术' AND T1.category_code != '24'
|
AND T1.category_code != '手术' AND T1.category_code != '24'
|
||||||
</if>
|
</if>
|
||||||
<if test="searchKey != null and searchKey != ''">
|
<if test="searchKey != null and searchKey != ''">
|
||||||
AND (t1.name ILIKE '%' || '${searchKey}' || '%' OR t1.py_str ILIKE '%' || '${searchKey}' || '%')
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="categoryCode != null and categoryCode != ''">
|
<if test="categoryCode != null and categoryCode != ''">
|
||||||
AND t1.category_code = #{categoryCode}
|
AND t1.category_code = #{categoryCode}
|
||||||
@@ -516,7 +516,6 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'med_medication_definition' AS advice_table_name,
|
'med_medication_definition' AS advice_table_name,
|
||||||
T1.medication_id AS advice_definition_id
|
T1.medication_id AS advice_definition_id
|
||||||
, T1.content_json::jsonb ->> 'remark' AS remark
|
|
||||||
FROM med_medication_request AS T1
|
FROM med_medication_request AS T1
|
||||||
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
||||||
AND T2.delete_flag = '0'
|
AND T2.delete_flag = '0'
|
||||||
@@ -578,7 +577,6 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'med_medication_definition' AS advice_table_name,
|
'med_medication_definition' AS advice_table_name,
|
||||||
T3.ID AS advice_definition_id
|
T3.ID AS advice_definition_id
|
||||||
, T1.content_json::jsonb ->> 'remark' AS remark
|
|
||||||
FROM adm_charge_item AS T1
|
FROM adm_charge_item AS T1
|
||||||
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
INNER JOIN med_medication_request AS T2 ON T2.ID = T1.service_id AND T2.delete_flag = '0'
|
||||||
LEFT JOIN med_medication_definition AS T3 ON T3.ID = T2.medication_id AND T3.delete_flag = '0'
|
LEFT JOIN med_medication_definition AS T3 ON T3.ID = T2.medication_id AND T3.delete_flag = '0'
|
||||||
@@ -639,7 +637,6 @@
|
|||||||
CI.patient_id AS patient_id,
|
CI.patient_id AS patient_id,
|
||||||
'adm_device_definition' AS advice_table_name,
|
'adm_device_definition' AS advice_table_name,
|
||||||
CI.product_id AS advice_definition_id
|
CI.product_id AS advice_definition_id
|
||||||
, NULL AS remark
|
|
||||||
FROM adm_charge_item AS CI
|
FROM adm_charge_item AS CI
|
||||||
LEFT JOIN adm_charge_item_definition CID ON CID.id = CI.definition_id AND CID.delete_flag = '0'
|
LEFT JOIN adm_charge_item_definition CID ON CID.id = CI.definition_id AND CID.delete_flag = '0'
|
||||||
LEFT JOIN wor_device_request DR ON DR.id = CI.service_id AND DR.delete_flag = '0'
|
LEFT JOIN wor_device_request DR ON DR.id = CI.service_id AND DR.delete_flag = '0'
|
||||||
@@ -694,7 +691,6 @@
|
|||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'adm_device_definition' AS advice_table_name,
|
'adm_device_definition' AS advice_table_name,
|
||||||
T1.device_def_id AS advice_definition_id
|
T1.device_def_id AS advice_definition_id
|
||||||
, T1.content_json::jsonb ->> 'remark' AS remark
|
|
||||||
FROM wor_device_request AS T1
|
FROM wor_device_request AS T1
|
||||||
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
||||||
AND T2.delete_flag = '0'
|
AND T2.delete_flag = '0'
|
||||||
@@ -750,7 +746,6 @@
|
|||||||
T1.encounter_id AS encounter_id,
|
T1.encounter_id AS encounter_id,
|
||||||
T1.patient_id AS patient_id,
|
T1.patient_id AS patient_id,
|
||||||
'wor_activity_definition' AS advice_table_name,
|
'wor_activity_definition' AS advice_table_name,
|
||||||
, T1.remark AS remark
|
|
||||||
T1.activity_id AS advice_definition_id
|
T1.activity_id AS advice_definition_id
|
||||||
FROM wor_service_request AS T1
|
FROM wor_service_request AS T1
|
||||||
LEFT JOIN wor_activity_definition AS T2
|
LEFT JOIN wor_activity_definition AS T2
|
||||||
@@ -876,7 +871,6 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 手术项目专用分页查询:仅查手术 + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
<!-- 手术项目专用分页查询:仅查手术 + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
||||||
<!-- 使用 LIMIT/OFFSET 直接查询,避免 MyBatis Plus 分页插件的 COUNT 开销 -->
|
|
||||||
<select id="getSurgeryPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
<select id="getSurgeryPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
||||||
SELECT DISTINCT ON (t1.ID)
|
SELECT DISTINCT ON (t1.ID)
|
||||||
t1.ID AS advice_definition_id,
|
t1.ID AS advice_definition_id,
|
||||||
@@ -899,15 +893,15 @@
|
|||||||
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
</if>
|
</if>
|
||||||
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
ORDER BY t1.ID, t1.name ASC, t2.ID ASC
|
||||||
|
LIMIT #{page.size} OFFSET ${(page.current - 1) * page.size}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 检查/检验项目专用分页查询:仅查指定 category_code + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
<!-- 检查项目专用分页查询:仅查检查(23) + 定价,无库存/草稿库存/取药科室等无关逻辑 -->
|
||||||
<select id="getExaminationPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
<select id="getExaminationPage" resultType="com.openhis.web.doctorstation.dto.SurgeryItemDto">
|
||||||
SELECT DISTINCT ON (t1.ID)
|
SELECT DISTINCT ON (t1.ID)
|
||||||
t1.ID AS advice_definition_id,
|
t1.ID AS advice_definition_id,
|
||||||
t1.NAME AS advice_name,
|
t1.NAME AS advice_name,
|
||||||
t1.org_id AS org_id,
|
t1.org_id AS org_id,
|
||||||
t3.name AS org_name,
|
|
||||||
t1.org_id AS position_id,
|
t1.org_id AS position_id,
|
||||||
t2.ID AS charge_item_definition_id,
|
t2.ID AS charge_item_definition_id,
|
||||||
t2.price AS price,
|
t2.price AS price,
|
||||||
@@ -919,11 +913,8 @@
|
|||||||
AND t2.delete_flag = '0'
|
AND t2.delete_flag = '0'
|
||||||
AND t2.status_enum = #{statusEnum}
|
AND t2.status_enum = #{statusEnum}
|
||||||
AND t2.instance_table = 'wor_activity_definition'
|
AND t2.instance_table = 'wor_activity_definition'
|
||||||
LEFT JOIN adm_organization t3
|
|
||||||
ON t3.id = t1.org_id
|
|
||||||
AND t3.delete_flag = '0'
|
|
||||||
WHERE t1.delete_flag = '0'
|
WHERE t1.delete_flag = '0'
|
||||||
AND t1.category_code = #{categoryCode}
|
AND t1.category_code = '23'
|
||||||
<if test="searchKey != null and searchKey != ''">
|
<if test="searchKey != null and searchKey != ''">
|
||||||
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
|
||||||
</if>
|
</if>
|
||||||
|
|||||||
@@ -135,7 +135,6 @@
|
|||||||
T1.onset_date AS onsetDate,
|
T1.onset_date AS onsetDate,
|
||||||
T1.diagnosis_time AS diagnosisTime,
|
T1.diagnosis_time AS diagnosisTime,
|
||||||
T1.doctor AS diagnosisDoctor,
|
T1.doctor AS diagnosisDoctor,
|
||||||
T1.long_term_flag AS longTermFlag,
|
|
||||||
CASE WHEN EXISTS (
|
CASE WHEN EXISTS (
|
||||||
SELECT 1 FROM infectious_card T4
|
SELECT 1 FROM infectious_card T4
|
||||||
WHERE T4.diag_id = T2.id AND T4.delete_flag = '0' AND T4.status >= 1
|
WHERE T4.diag_id = T2.id AND T4.delete_flag = '0' AND T4.status >= 1
|
||||||
|
|||||||
@@ -4,38 +4,4 @@
|
|||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.openhis.web.doctorstation.mapper.DoctorStationEmrAppMapper">
|
<mapper namespace="com.openhis.web.doctorstation.mapper.DoctorStationEmrAppMapper">
|
||||||
|
|
||||||
<select id="getPendingEmrList" resultType="java.util.HashMap">
|
|
||||||
SELECT e.id AS "encounterId",
|
|
||||||
e.patient_id AS "patientId",
|
|
||||||
p.name AS "patientName",
|
|
||||||
p.gender_enum AS "gender",
|
|
||||||
p.birth_date AS "birthDate",
|
|
||||||
e.create_time AS "registerTime",
|
|
||||||
e.bus_no AS "busNo"
|
|
||||||
FROM adm_encounter e
|
|
||||||
INNER JOIN adm_encounter_participant ep ON e.id = ep.encounter_id AND ep.practitioner_id = #{doctorId}
|
|
||||||
LEFT JOIN adm_patient p ON e.patient_id = p.id
|
|
||||||
LEFT JOIN doc_emr emr ON e.id = emr.encounter_id
|
|
||||||
WHERE e.status_enum = 2
|
|
||||||
AND emr.id IS NULL
|
|
||||||
<if test="patientName != null and patientName != ''">
|
|
||||||
AND p.name LIKE CONCAT('%', #{patientName}, '%')
|
|
||||||
</if>
|
|
||||||
ORDER BY e.create_time DESC
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select id="getPendingEmrCount" resultType="java.lang.Long">
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM adm_encounter e
|
|
||||||
INNER JOIN adm_encounter_participant ep ON e.id = ep.encounter_id AND ep.practitioner_id = #{doctorId}
|
|
||||||
LEFT JOIN doc_emr emr ON e.id = emr.encounter_id
|
|
||||||
WHERE e.status_enum = 2
|
|
||||||
AND emr.id IS NULL
|
|
||||||
<if test="patientName != null and patientName != ''">
|
|
||||||
AND e.patient_id IN (
|
|
||||||
SELECT id FROM adm_patient WHERE name LIKE CONCAT('%', #{patientName}, '%')
|
|
||||||
)
|
|
||||||
</if>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
ON T5.medication_def_id = T6.id
|
ON T5.medication_def_id = T6.id
|
||||||
AND T5.delete_flag = '0'
|
AND T5.delete_flag = '0'
|
||||||
WHERE T1.delete_flag = '0'
|
WHERE T1.delete_flag = '0'
|
||||||
AND T1.status_enum IN (#{inProgress}, #{preparation}, #{prepared}, #{summarized})
|
AND T1.status_enum IN (#{inProgress}, #{preparation}, #{prepared})
|
||||||
ORDER BY T1.create_time DESC
|
ORDER BY T1.create_time DESC
|
||||||
) AS T7
|
) AS T7
|
||||||
${ew.customSqlSegment}
|
${ew.customSqlSegment}
|
||||||
|
|||||||
@@ -217,8 +217,7 @@
|
|||||||
T1.therapy_enum AS therapyEnum,
|
T1.therapy_enum AS therapyEnum,
|
||||||
T1.sort_number AS sort_number,
|
T1.sort_number AS sort_number,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.medication_id AS advice_definition_id,
|
T1.medication_id AS advice_definition_id
|
||||||
T1.content_json::jsonb ->> 'remark' AS remark
|
|
||||||
FROM med_medication_request AS T1
|
FROM med_medication_request AS T1
|
||||||
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
LEFT JOIN med_medication_definition AS T2 ON T2.ID = T1.medication_id
|
||||||
AND T2.delete_flag = '0'
|
AND T2.delete_flag = '0'
|
||||||
@@ -271,8 +270,7 @@
|
|||||||
2 AS therapyEnum,
|
2 AS therapyEnum,
|
||||||
99 AS sort_number,
|
99 AS sort_number,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.device_def_id AS advice_definition_id,
|
T1.device_def_id AS advice_definition_id
|
||||||
T1.content_json::jsonb ->> 'remark' AS remark
|
|
||||||
FROM wor_device_request AS T1
|
FROM wor_device_request AS T1
|
||||||
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
LEFT JOIN adm_device_definition AS T2 ON T2.ID = T1.device_def_id
|
||||||
AND T2.delete_flag = '0'
|
AND T2.delete_flag = '0'
|
||||||
@@ -322,8 +320,7 @@
|
|||||||
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
|
COALESCE(T1.therapy_enum, 2) AS therapyEnum,
|
||||||
99 AS sort_number,
|
99 AS sort_number,
|
||||||
T1.based_on_id AS based_on_id,
|
T1.based_on_id AS based_on_id,
|
||||||
T1.activity_id AS advice_definition_id,
|
T1.activity_id AS advice_definition_id
|
||||||
T1.remark AS remark
|
|
||||||
FROM wor_service_request AS T1
|
FROM wor_service_request AS T1
|
||||||
LEFT JOIN wor_activity_definition AS T2
|
LEFT JOIN wor_activity_definition AS T2
|
||||||
ON T2.ID = T1.activity_id
|
ON T2.ID = T1.activity_id
|
||||||
|
|||||||
@@ -35,36 +35,21 @@
|
|||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
||||||
AND ws.status_enum = 8
|
AND ws.status_enum = 8
|
||||||
) THEN 6
|
) THEN 6
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1 FROM wor_service_request ws
|
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
|
||||||
AND ws.status_enum = 5
|
|
||||||
) THEN 7
|
|
||||||
WHEN EXISTS (
|
WHEN EXISTS (
|
||||||
SELECT 1 FROM wor_service_request ws
|
SELECT 1 FROM wor_service_request ws
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
||||||
AND ws.status_enum = 3
|
AND ws.status_enum = 3
|
||||||
) THEN 5
|
) THEN 5
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1 FROM wor_service_request ws
|
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
|
||||||
AND ws.status_enum = 12
|
|
||||||
) THEN 4
|
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1 FROM wor_service_request ws
|
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
|
||||||
AND ws.status_enum = 11
|
|
||||||
) THEN 3
|
|
||||||
WHEN EXISTS (
|
|
||||||
SELECT 1 FROM wor_service_request ws
|
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
|
||||||
AND ws.status_enum = 10
|
|
||||||
) THEN 2
|
|
||||||
WHEN EXISTS (
|
WHEN EXISTS (
|
||||||
SELECT 1 FROM wor_service_request ws
|
SELECT 1 FROM wor_service_request ws
|
||||||
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
||||||
AND ws.status_enum = 2
|
AND ws.status_enum = 2
|
||||||
) THEN 1
|
) THEN 1
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1 FROM wor_service_request ws
|
||||||
|
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
|
||||||
|
AND ws.status_enum = 5
|
||||||
|
) THEN 7
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS computed_status
|
END AS computed_status
|
||||||
FROM doc_request_form AS drf
|
FROM doc_request_form AS drf
|
||||||
@@ -72,6 +57,8 @@
|
|||||||
AND ae.delete_flag = '0'
|
AND ae.delete_flag = '0'
|
||||||
LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id
|
LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id
|
||||||
AND ap.delete_flag = '0'
|
AND ap.delete_flag = '0'
|
||||||
|
LEFT JOIN wor_service_request AS wsr ON wsr.prescription_no = drf.prescription_no
|
||||||
|
AND wsr.delete_flag = '0'
|
||||||
WHERE drf.delete_flag = '0'
|
WHERE drf.delete_flag = '0'
|
||||||
AND drf.encounter_id = #{encounterId}
|
AND drf.encounter_id = #{encounterId}
|
||||||
AND drf.type_code = #{typeCode}
|
AND drf.type_code = #{typeCode}
|
||||||
|
|||||||
@@ -768,4 +768,36 @@ public class CommonConstants {
|
|||||||
Integer ACCOUNT_DEVICE_TYPE = 6;
|
Integer ACCOUNT_DEVICE_TYPE = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 号源槽位状态 (adm_schedule_slot.status)
|
||||||
|
*/
|
||||||
|
public interface SlotStatus {
|
||||||
|
/** 可用 / 待预约 */
|
||||||
|
Integer AVAILABLE = 0;
|
||||||
|
/** 已预约 */
|
||||||
|
Integer BOOKED = 1;
|
||||||
|
/** 已取消 / 已停诊 */
|
||||||
|
Integer CANCELLED = 2;
|
||||||
|
/** 已签到 / 已取号 */
|
||||||
|
Integer CHECKED_IN = 3;
|
||||||
|
/** 已锁定 */
|
||||||
|
Integer LOCKED = 4;
|
||||||
|
/** 已退号 */
|
||||||
|
Integer RETURNED = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预约订单状态 (order_main.status)
|
||||||
|
*/
|
||||||
|
public interface AppointmentOrderStatus {
|
||||||
|
/** 已预约 (待就诊) */
|
||||||
|
Integer BOOKED = 1;
|
||||||
|
/** 已取号 (已就诊) */
|
||||||
|
Integer CHECKED_IN = 2;
|
||||||
|
/** 已取消 */
|
||||||
|
Integer CANCELLED = 3;
|
||||||
|
/** 已退号 */
|
||||||
|
Integer RETURNED = 4;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,22 +57,7 @@ public enum RequestStatus implements HisEnumInterface {
|
|||||||
/**
|
/**
|
||||||
* 未知
|
* 未知
|
||||||
*/
|
*/
|
||||||
UNKNOWN(9, "unknown", "未知"),
|
UNKNOWN(9, "unknown", "未知");
|
||||||
|
|
||||||
/**
|
|
||||||
* 已校对(检查申请:护士校对通过)
|
|
||||||
*/
|
|
||||||
CHECK_VERIFIED(10, "check_verified", "已校对"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 待接收(检查申请:等待医技科室接单)
|
|
||||||
*/
|
|
||||||
PENDING_RECEIVE(11, "pending_receive", "待接收"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 已接收(检查申请:医技科室已接单)
|
|
||||||
*/
|
|
||||||
CHECK_RECEIVED(12, "check_received", "已接收");
|
|
||||||
|
|
||||||
@EnumValue
|
@EnumValue
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.openhis.common.enums;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 号源槽位状态 (adm_schedule_slot.status)
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 状态流转:
|
|
||||||
* 预约 → 0→2 (锁定), locked_num+1
|
|
||||||
* 取消预约 → 2→0 (释放), locked_num-1
|
|
||||||
* 签到 → 2→1 (已约), locked_num-1, booked_num+1
|
|
||||||
* 退号 → 1→0 (释放), booked_num-1
|
|
||||||
* 停诊 → 任意→4 (已取消)
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author system
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum SlotStatus implements HisEnumInterface {
|
|
||||||
|
|
||||||
/** 可用 / 待预约 */
|
|
||||||
AVAILABLE(0, "available", "可用"),
|
|
||||||
|
|
||||||
/** 已预约 */
|
|
||||||
BOOKED(1, "booked", "已预约"),
|
|
||||||
|
|
||||||
/** 已锁定 (约而不付:预约后锁定号源) */
|
|
||||||
LOCKED(2, "locked", "已锁定"),
|
|
||||||
|
|
||||||
/** 已签到 / 已取号 */
|
|
||||||
CHECKED_IN(3, "checked_in", "已签到"),
|
|
||||||
|
|
||||||
/** 已取消 / 已停诊 */
|
|
||||||
CANCELLED(4, "cancelled", "已取消"),
|
|
||||||
|
|
||||||
/** 已退号 */
|
|
||||||
RETURNED(5, "returned", "已退号");
|
|
||||||
|
|
||||||
private final Integer value;
|
|
||||||
private final String code;
|
|
||||||
private final String info;
|
|
||||||
|
|
||||||
public static SlotStatus getByValue(Integer value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (SlotStatus val : values()) {
|
|
||||||
if (val.getValue().equals(value)) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,14 +36,6 @@ public interface IOrganizationLocationService extends IService<OrganizationLocat
|
|||||||
* @param activityDefinitionId 诊疗定义id
|
* @param activityDefinitionId 诊疗定义id
|
||||||
* @return 诊疗的执行科室列表
|
* @return 诊疗的执行科室列表
|
||||||
*/
|
*/
|
||||||
List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long organizationId, Long activityDefinitionId);
|
List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long activityDefinitionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据诊疗定义id查询所有执行科室列表(跨科室)
|
|
||||||
*
|
|
||||||
* @param activityDefinitionId 诊疗定义id
|
|
||||||
* @return 执行科室列表
|
|
||||||
*/
|
|
||||||
List<OrganizationLocation> getOrgLocListByActivityDefinitionId(Long activityDefinitionId);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -53,25 +53,11 @@ public class OrganizationLocationServiceImpl extends ServiceImpl<OrganizationLoc
|
|||||||
/**
|
/**
|
||||||
* 查询诊疗的执行科室列表
|
* 查询诊疗的执行科室列表
|
||||||
*
|
*
|
||||||
* @param organizationId 机构id
|
|
||||||
* @param activityDefinitionId 诊疗定义id
|
* @param activityDefinitionId 诊疗定义id
|
||||||
* @return 诊疗的执行科室列表
|
* @return 诊疗的执行科室列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long organizationId, Long activityDefinitionId) {
|
public List<OrganizationLocation> getOrgLocListByOrgIdAndActivityDefinitionId(Long activityDefinitionId) {
|
||||||
return baseMapper.selectList(new LambdaQueryWrapper<OrganizationLocation>()
|
|
||||||
.eq(OrganizationLocation::getOrganizationId, organizationId)
|
|
||||||
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据诊疗定义id查询所有执行科室列表(跨科室)
|
|
||||||
*
|
|
||||||
* @param activityDefinitionId 诊疗定义id
|
|
||||||
* @return 执行科室列表
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<OrganizationLocation> getOrgLocListByActivityDefinitionId(Long activityDefinitionId) {
|
|
||||||
return baseMapper.selectList(new LambdaQueryWrapper<OrganizationLocation>()
|
return baseMapper.selectList(new LambdaQueryWrapper<OrganizationLocation>()
|
||||||
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
.eq(OrganizationLocation::getActivityDefinitionId, activityDefinitionId));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import org.springframework.stereotype.Repository;
|
|||||||
public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按号源池实时重算统计值。
|
* 按号源池实时重算统计值,避免并发场景下计数漂移。
|
||||||
*
|
*
|
||||||
* @param poolId 号源池ID
|
* 说明:available_num 在当前项目中可能为数据库生成列,因此这里仅维护
|
||||||
* @param bookedStatus 已约状态值,由 SlotStatus.BOOKED.getValue() 传入
|
* booked_num / locked_num,剩余号由数据库或查询逻辑计算。
|
||||||
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
|
||||||
*/
|
*/
|
||||||
@Update("""
|
@Update("""
|
||||||
UPDATE adm_schedule_pool p
|
UPDATE adm_schedule_pool p
|
||||||
@@ -24,22 +23,20 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
|||||||
FROM adm_schedule_slot s
|
FROM adm_schedule_slot s
|
||||||
WHERE s.pool_id = p.id
|
WHERE s.pool_id = p.id
|
||||||
AND s.delete_flag = '0'
|
AND s.delete_flag = '0'
|
||||||
AND s.status = #{bookedStatus}
|
AND s.status = 1
|
||||||
), 0),
|
), 0),
|
||||||
locked_num = COALESCE((
|
locked_num = COALESCE((
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM adm_schedule_slot s
|
FROM adm_schedule_slot s
|
||||||
WHERE s.pool_id = p.id
|
WHERE s.pool_id = p.id
|
||||||
AND s.delete_flag = '0'
|
AND s.delete_flag = '0'
|
||||||
AND s.status = #{lockedStatus}
|
AND s.status = 3
|
||||||
), 0),
|
), 0),
|
||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE p.id = #{poolId}
|
WHERE p.id = #{poolId}
|
||||||
AND p.delete_flag = '0'
|
AND p.delete_flag = '0'
|
||||||
""")
|
""")
|
||||||
int refreshPoolStats(@Param("poolId") Long poolId,
|
int refreshPoolStats(@Param("poolId") Long poolId);
|
||||||
@Param("bookedStatus") Integer bookedStatus,
|
|
||||||
@Param("lockedStatus") Integer lockedStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
||||||
|
|||||||
@@ -22,12 +22,9 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
|||||||
TicketSlotDTO selectTicketSlotById(@Param("id") Long id);
|
TicketSlotDTO selectTicketSlotById(@Param("id") Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 原子抢占槽位:仅当当前状态=0(待约)时,更新为目标锁定状态。
|
* 原子抢占槽位:仅当当前状态=0(可用)时,更新为1(已预约)。
|
||||||
*
|
|
||||||
* @param slotId 槽位ID
|
|
||||||
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
|
||||||
*/
|
*/
|
||||||
int lockSlotForBooking(@Param("slotId") Long slotId, @Param("lockedStatus") Integer lockedStatus);
|
int lockSlotForBooking(@Param("slotId") Long slotId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按主键更新槽位状态。
|
* 按主键更新槽位状态。
|
||||||
@@ -37,16 +34,12 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
|||||||
/**
|
/**
|
||||||
* 更新槽位状态并记录签到时间
|
* 更新槽位状态并记录签到时间
|
||||||
*
|
*
|
||||||
* @param slotId 槽位ID
|
* @param slotId 槽位ID
|
||||||
* @param status 目标状态,由 SlotStatus.BOOKED.getValue() 传入
|
* @param status 状态
|
||||||
* @param checkInTime 签到时间
|
* @param checkInTime 签到时间
|
||||||
* @param requiredStatus 前置状态,由 SlotStatus.LOCKED.getValue() 传入
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId,
|
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
|
||||||
@Param("status") Integer status,
|
|
||||||
@Param("checkInTime") Date checkInTime,
|
|
||||||
@Param("requiredStatus") Integer requiredStatus);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据槽位ID查询所属号源池ID。
|
* 根据槽位ID查询所属号源池ID。
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.openhis.clinical.service.impl;
|
package com.openhis.clinical.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
||||||
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
||||||
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
||||||
import com.openhis.appointmentmanage.domain.SchedulePool;
|
|
||||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||||
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
||||||
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
||||||
@@ -15,7 +13,7 @@ import com.openhis.clinical.domain.Ticket;
|
|||||||
import com.openhis.clinical.mapper.TicketMapper;
|
import com.openhis.clinical.mapper.TicketMapper;
|
||||||
import com.openhis.clinical.service.IOrderService;
|
import com.openhis.clinical.service.IOrderService;
|
||||||
import com.openhis.clinical.service.ITicketService;
|
import com.openhis.clinical.service.ITicketService;
|
||||||
import com.openhis.common.enums.SlotStatus;
|
import com.openhis.common.constant.CommonConstants.SlotStatus;
|
||||||
import com.openhis.common.enums.OrderStatus;
|
import com.openhis.common.enums.OrderStatus;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -179,7 +177,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
logger.error("安全拦截:号源底库核对失败,slotId: {}", slotId);
|
logger.error("安全拦截:号源底库核对失败,slotId: {}", slotId);
|
||||||
throw new RuntimeException("号源数据不存在");
|
throw new RuntimeException("号源数据不存在");
|
||||||
}
|
}
|
||||||
if (slot.getSlotStatus() != null && SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.AVAILABLE) {
|
if (slot.getSlotStatus() != null && !SlotStatus.AVAILABLE.equals(slot.getSlotStatus())) {
|
||||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||||
}
|
}
|
||||||
if (Boolean.TRUE.equals(slot.getIsStopped())) {
|
if (Boolean.TRUE.equals(slot.getIsStopped())) {
|
||||||
@@ -207,7 +205,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 原子抢占:避免并发下同一槽位被重复预约
|
// 原子抢占:避免并发下同一槽位被重复预约
|
||||||
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId, SlotStatus.LOCKED.getValue());
|
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId);
|
||||||
if (lockRows <= 0) {
|
if (lockRows <= 0) {
|
||||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||||
}
|
}
|
||||||
@@ -262,15 +260,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
throw new RuntimeException("预约成功但号源回填订单失败,请重试");
|
throw new RuntimeException("预约成功但号源回填订单失败,请重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 预约成功后 locked_num+1(原子递增替代全量 recount,避免并发计数漂移)
|
refreshPoolStatsBySlotId(slotId);
|
||||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
|
||||||
if (poolId != null) {
|
|
||||||
schedulePoolMapper.update(null,
|
|
||||||
new LambdaUpdateWrapper<SchedulePool>()
|
|
||||||
.setSql("locked_num = locked_num + 1, version = version + 1")
|
|
||||||
.set(SchedulePool::getUpdateTime, new Date())
|
|
||||||
.eq(SchedulePool::getId, poolId));
|
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,8 +277,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
if (slot == null) {
|
if (slot == null) {
|
||||||
throw new RuntimeException("号源槽位不存在");
|
throw new RuntimeException("号源槽位不存在");
|
||||||
}
|
}
|
||||||
// 只有锁定态(2)的号源可以取消预约
|
if (slot.getSlotStatus() == null || !SlotStatus.BOOKED.equals(slot.getSlotStatus())) {
|
||||||
if (slot.getSlotStatus() == null || SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.LOCKED) {
|
|
||||||
throw new RuntimeException("号源不可取消预约");
|
throw new RuntimeException("号源不可取消预约");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +292,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE.getValue());
|
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE);
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
refreshPoolStatsBySlotId(slotId);
|
refreshPoolStatsBySlotId(slotId);
|
||||||
}
|
}
|
||||||
@@ -329,14 +318,11 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
||||||
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
||||||
|
|
||||||
// 2. 只有锁定态(2)的号源才能签到,签到时 2→1(LOCKED→BOOKED)
|
// 2. 查询号源槽位信息
|
||||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||||
if (slot == null || !SlotStatus.LOCKED.getValue().equals(slot.getStatus())) {
|
|
||||||
throw new RuntimeException("号源状态异常,无法签到");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 更新号源槽位状态 2→1(LOCKED→BOOKED,已预约=已签到)
|
// 3. 更新号源槽位状态为已签到,记录签到时间
|
||||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.BOOKED.getValue(), new Date(), SlotStatus.LOCKED.getValue());
|
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN, new Date());
|
||||||
|
|
||||||
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
||||||
if (slot != null && slot.getPoolId() != null) {
|
if (slot != null && slot.getPoolId() != null) {
|
||||||
@@ -365,7 +351,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
|
orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
|
||||||
}
|
}
|
||||||
|
|
||||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED.getValue());
|
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
|
||||||
if (updated > 0) {
|
if (updated > 0) {
|
||||||
refreshPoolStatsBySlotId(slotId);
|
refreshPoolStatsBySlotId(slotId);
|
||||||
}
|
}
|
||||||
@@ -378,7 +364,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
|||||||
private void refreshPoolStatsBySlotId(Long slotId) {
|
private void refreshPoolStatsBySlotId(Long slotId) {
|
||||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||||
if (poolId != null) {
|
if (poolId != null) {
|
||||||
schedulePoolMapper.refreshPoolStats(poolId, SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue());
|
schedulePoolMapper.refreshPoolStats(poolId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,13 +79,11 @@ public class OpSchedule extends HisBaseEntity {
|
|||||||
private String surgerySite;
|
private String surgerySite;
|
||||||
|
|
||||||
/** 入院时间 */
|
/** 入院时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime admissionTime;
|
private LocalDateTime admissionTime;
|
||||||
|
|
||||||
/** 入手术室时间 */
|
/** 入手术室时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime entryTime;
|
private LocalDateTime entryTime;
|
||||||
|
|
||||||
/** 手术室编码 */
|
/** 手术室编码 */
|
||||||
@@ -144,23 +142,19 @@ public class OpSchedule extends HisBaseEntity {
|
|||||||
private String assistant3Code;
|
private String assistant3Code;
|
||||||
|
|
||||||
/** 手术开始时间 */
|
/** 手术开始时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime startTime;
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
/** 手术结束时间 */
|
/** 手术结束时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime endTime;
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
/** 麻醉开始时间 */
|
/** 麻醉开始时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime anesStart;
|
private LocalDateTime anesStart;
|
||||||
|
|
||||||
/** 麻醉结束时间 */
|
/** 麻醉结束时间 */
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private LocalDateTime anesEnd;
|
private LocalDateTime anesEnd;
|
||||||
|
|
||||||
/** 手术状态 */
|
/** 手术状态 */
|
||||||
|
|||||||
@@ -173,9 +173,4 @@ public class ServiceRequest extends HisBaseEntity {
|
|||||||
*/
|
*/
|
||||||
private Integer generateSourceEnum;
|
private Integer generateSourceEnum;
|
||||||
|
|
||||||
/**
|
|
||||||
* 备注(最长50字)
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,22 +39,6 @@ public interface IServiceRequestService extends IService<ServiceRequest> {
|
|||||||
*/
|
*/
|
||||||
void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
|
void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新检查申请状态:已校对(护士校对检查申请后状态为CHECK_VERIFIED,而非COMPLETED)
|
|
||||||
*
|
|
||||||
* @param serReqIdList 服务请求id列表
|
|
||||||
* @param practitionerId 校对人
|
|
||||||
* @param checkDate 校对时间
|
|
||||||
*/
|
|
||||||
void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新检查申请状态:待接收(护士执行检查申请后状态为PENDING_RECEIVE)
|
|
||||||
*
|
|
||||||
* @param serReqIdList 服务请求id列表
|
|
||||||
*/
|
|
||||||
void updatePendingReceiveStatus(List<Long> serReqIdList);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取执行过的诊疗数据
|
* 获取执行过的诊疗数据
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -66,31 +66,6 @@ public class ServiceRequestServiceImpl extends ServiceImpl<ServiceRequestMapper,
|
|||||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新检查申请状态:已校对(护士校对检查申请后状态为CHECK_VERIFIED,而非COMPLETED)
|
|
||||||
*
|
|
||||||
* @param serReqIdList 服务请求id列表
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate) {
|
|
||||||
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.CHECK_VERIFIED.getValue())
|
|
||||||
.setPerformerCheckId(SecurityUtils.getLoginUser().getPractitionerId()).setCheckTime(DateUtils.getNowDate()),
|
|
||||||
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
|
|
||||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新检查申请状态:待接收(护士执行检查申请后状态为PENDING_RECEIVE)
|
|
||||||
*
|
|
||||||
* @param serReqIdList 服务请求id列表
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void updatePendingReceiveStatus(List<Long> serReqIdList) {
|
|
||||||
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.PENDING_RECEIVE.getValue()),
|
|
||||||
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
|
|
||||||
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取执行过的诊疗数据
|
* 获取执行过的诊疗数据
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
|
<mapper namespace="com.openhis.appointmentmanage.mapper.ScheduleSlotMapper">
|
||||||
|
|
||||||
<!--
|
<!-- 统一状态值(兼容数字/英文字符串存储),输出 Integer,避免 resultType 映射 NumberFormatException -->
|
||||||
统一状态值映射: DB 数值 → 规范化输出
|
|
||||||
0=待约 1=已约(签到后) 2=锁定(预约后) 3=已签到 4=已停诊 5=已退号
|
|
||||||
-->
|
|
||||||
<sql id="slotStatusNormExpr">
|
<sql id="slotStatusNormExpr">
|
||||||
CASE
|
CASE
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
|
WHEN LOWER(CONCAT('', s.status)) IN ('0', 'unbooked', 'available') THEN 0
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('1', 'booked') THEN 1
|
WHEN LOWER(CONCAT('', s.status)) IN ('1', 'booked') THEN 1
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'locked') THEN 2
|
WHEN LOWER(CONCAT('', s.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
WHEN LOWER(CONCAT('', s.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
|
WHEN LOWER(CONCAT('', s.status)) IN ('4', 'locked') THEN 4
|
||||||
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
|
WHEN LOWER(CONCAT('', s.status)) IN ('5', 'returned') THEN 5
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END
|
END
|
||||||
@@ -34,9 +31,9 @@
|
|||||||
CASE
|
CASE
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
|
WHEN LOWER(CONCAT('', p.status)) IN ('0', 'unbooked', 'available') THEN 0
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('1', 'booked') THEN 1
|
WHEN LOWER(CONCAT('', p.status)) IN ('1', 'booked') THEN 1
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'locked') THEN 2
|
WHEN LOWER(CONCAT('', p.status)) IN ('2', 'cancelled', 'canceled', 'stopped') THEN 2
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
WHEN LOWER(CONCAT('', p.status)) IN ('3', 'checked', 'checked_in', 'checkin') THEN 3
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'cancelled', 'canceled', 'stopped') THEN 4
|
WHEN LOWER(CONCAT('', p.status)) IN ('4', 'locked') THEN 4
|
||||||
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
|
WHEN LOWER(CONCAT('', p.status)) IN ('5', 'returned') THEN 5
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END
|
END
|
||||||
@@ -152,11 +149,10 @@
|
|||||||
s.id = #{id}
|
s.id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 预约锁定: 0→#{lockedStatus} (AVAILABLE→LOCKED),由枚举传入 -->
|
|
||||||
<update id="lockSlotForBooking">
|
<update id="lockSlotForBooking">
|
||||||
UPDATE adm_schedule_slot
|
UPDATE adm_schedule_slot
|
||||||
SET
|
SET
|
||||||
status = #{lockedStatus},
|
status = 1,
|
||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
@@ -178,7 +174,6 @@
|
|||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<!-- 签到: #{requiredStatus}→#{status} (LOCKED→BOOKED),前置条件由枚举传入 -->
|
|
||||||
<update id="updateSlotStatusAndCheckInTime">
|
<update id="updateSlotStatusAndCheckInTime">
|
||||||
UPDATE adm_schedule_slot
|
UPDATE adm_schedule_slot
|
||||||
SET
|
SET
|
||||||
@@ -187,7 +182,6 @@
|
|||||||
update_time = NOW()
|
update_time = NOW()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
AND status = #{requiredStatus}
|
|
||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -208,7 +202,7 @@
|
|||||||
update_time = now()
|
update_time = now()
|
||||||
WHERE
|
WHERE
|
||||||
id = #{slotId}
|
id = #{slotId}
|
||||||
AND status = 2
|
AND status = 1
|
||||||
AND delete_flag = '0'
|
AND delete_flag = '0'
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
@@ -305,16 +299,15 @@
|
|||||||
<if test="query.phone != null and query.phone != ''">
|
<if test="query.phone != null and query.phone != ''">
|
||||||
AND o.phone LIKE CONCAT('%', #{query.phone}, '%')
|
AND o.phone LIKE CONCAT('%', #{query.phone}, '%')
|
||||||
</if>
|
</if>
|
||||||
<!-- 5. 时间过滤: 仅待约(0)受时间限制,已锁定(2)/已约(1)/已签到(3)/已退号(5)不受影响 -->
|
<!-- 5. 按系统时间过滤(Bug #398 #399 修复:仅未预约受时间过滤,已预约/已取号/已退号不受影响) -->
|
||||||
AND (
|
AND (
|
||||||
(<include refid="slotStatusNormExpr" /> = 0 AND (p.schedule_date > CURRENT_DATE OR (p.schedule_date = CURRENT_DATE AND (CAST(p.schedule_date AS TIMESTAMP) + CAST(s.expect_time AS TIME)) >= NOW())))
|
(<include refid="slotStatusNormExpr" /> = 0 AND (p.schedule_date > CURRENT_DATE OR (p.schedule_date = CURRENT_DATE AND (CAST(p.schedule_date AS TIMESTAMP) + CAST(s.expect_time AS TIME)) >= NOW())))
|
||||||
OR <include refid="slotStatusNormExpr" /> = 1
|
OR <include refid="slotStatusNormExpr" /> = 1
|
||||||
OR <include refid="slotStatusNormExpr" /> = 2
|
|
||||||
OR <include refid="slotStatusNormExpr" /> = 3
|
OR <include refid="slotStatusNormExpr" /> = 3
|
||||||
OR <include refid="slotStatusNormExpr" /> = 5
|
OR <include refid="slotStatusNormExpr" /> = 5
|
||||||
OR <include refid="orderStatusNormExpr" /> = 4
|
OR <include refid="orderStatusNormExpr" /> = 4
|
||||||
)
|
)
|
||||||
<!-- 6. 状态筛选: unbooked(0) locked(2) booked(2) checked(1) cancelled(4) returned(5) -->
|
<!-- 6. 状态过滤 -->
|
||||||
<if test="query.status != null and query.status != '' and query.status != 'all'">
|
<if test="query.status != null and query.status != '' and query.status != 'all'">
|
||||||
<choose>
|
<choose>
|
||||||
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
|
<when test="'unbooked'.equals(query.status) or '未预约'.equals(query.status)">
|
||||||
@@ -325,15 +318,7 @@
|
|||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
|
<when test="'booked'.equals(query.status) or '已预约'.equals(query.status)">
|
||||||
AND <include refid="slotStatusNormExpr" /> = 2
|
AND <include refid="slotStatusNormExpr" /> = 1
|
||||||
AND <include refid="orderStatusNormExpr" /> = 1
|
|
||||||
AND (
|
|
||||||
d.is_stopped IS NULL
|
|
||||||
OR d.is_stopped = FALSE
|
|
||||||
)
|
|
||||||
</when>
|
|
||||||
<when test="'locked'.equals(query.status) or '已锁定'.equals(query.status)">
|
|
||||||
AND <include refid="slotStatusNormExpr" /> = 2
|
|
||||||
AND <include refid="orderStatusNormExpr" /> = 1
|
AND <include refid="orderStatusNormExpr" /> = 1
|
||||||
AND (
|
AND (
|
||||||
d.is_stopped IS NULL
|
d.is_stopped IS NULL
|
||||||
@@ -341,7 +326,13 @@
|
|||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
<when test="'checked'.equals(query.status) or '已取号'.equals(query.status)">
|
||||||
AND <include refid="slotStatusNormExpr" /> = 1
|
AND (
|
||||||
|
<include refid="slotStatusNormExpr" /> = 3
|
||||||
|
OR (
|
||||||
|
<include refid="slotStatusNormExpr" /> = 1
|
||||||
|
AND <include refid="orderStatusNormExpr" /> = 2
|
||||||
|
)
|
||||||
|
)
|
||||||
AND (
|
AND (
|
||||||
d.is_stopped IS NULL
|
d.is_stopped IS NULL
|
||||||
OR d.is_stopped = FALSE
|
OR d.is_stopped = FALSE
|
||||||
@@ -349,7 +340,7 @@
|
|||||||
</when>
|
</when>
|
||||||
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
|
<when test="'cancelled'.equals(query.status) or '已停诊'.equals(query.status) or '已取消'.equals(query.status)">
|
||||||
AND (
|
AND (
|
||||||
<include refid="slotStatusNormExpr" /> = 4
|
<include refid="slotStatusNormExpr" /> = 2
|
||||||
OR d.is_stopped = TRUE
|
OR d.is_stopped = TRUE
|
||||||
)
|
)
|
||||||
</when>
|
</when>
|
||||||
|
|||||||
@@ -79,51 +79,6 @@ export const constantRoutes = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 住院护士站 — 快捷访问路由(与 sys_menu 中 menu_id=295 的动态路由并存,路径不同不冲突)
|
|
||||||
{
|
|
||||||
path: '/inpatientNurse',
|
|
||||||
component: Layout,
|
|
||||||
hidden: true,
|
|
||||||
redirect: '/inpatientNurse/inpatientNurseStation',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'inpatientNurseStation',
|
|
||||||
component: () => import('@/views/inpatientNurse/inpatientNurseStation/index.vue'),
|
|
||||||
name: 'InpatientNurseStation',
|
|
||||||
meta: {title: '住院护士站'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'medicalOrderExecution',
|
|
||||||
component: () => import('@/views/inpatientNurse/medicalOrderExecution/index.vue'),
|
|
||||||
name: 'MedicalOrderExecution',
|
|
||||||
meta: {title: '医嘱执行'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'medicalOrderProofread',
|
|
||||||
component: () => import('@/views/inpatientNurse/medicalOrderProofread/index.vue'),
|
|
||||||
name: 'MedicalOrderProofread',
|
|
||||||
meta: {title: '医嘱校对'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'medicineCollect',
|
|
||||||
component: () => import('@/views/inpatientNurse/medicineCollect/index.vue'),
|
|
||||||
name: 'MedicineCollect',
|
|
||||||
meta: {title: '领药管理'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'tprsheet',
|
|
||||||
component: () => import('@/views/inpatientNurse/tprsheet/index.vue'),
|
|
||||||
name: 'TprSheet',
|
|
||||||
meta: {title: '体温单'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'nursingRecord',
|
|
||||||
component: () => import('@/views/inpatientNurse/nursingRecord/index.vue'),
|
|
||||||
name: 'NursingRecord',
|
|
||||||
meta: {title: '护理记录'}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 添加套餐管理相关路由到公共路由,确保始终可用
|
// 添加套餐管理相关路由到公共路由,确保始终可用
|
||||||
{
|
{
|
||||||
path: '/maintainSystem/Inspection/PackageManagement',
|
path: '/maintainSystem/Inspection/PackageManagement',
|
||||||
|
|||||||
@@ -34,12 +34,6 @@ export const RequestStatus = {
|
|||||||
ENDED: 7,
|
ENDED: 7,
|
||||||
/** 未知 */
|
/** 未知 */
|
||||||
UNKNOWN: 9,
|
UNKNOWN: 9,
|
||||||
/** 已校对(检查申请:护士校对通过) */
|
|
||||||
CHECK_VERIFIED: 10,
|
|
||||||
/** 待接收(检查申请:等待医技科室接单) */
|
|
||||||
PENDING_RECEIVE: 11,
|
|
||||||
/** 已接收(检查申请:医技科室已接单) */
|
|
||||||
CHECK_RECEIVED: 12,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,9 +48,6 @@ export const RequestStatusDescriptions = {
|
|||||||
6: '停嘱',
|
6: '停嘱',
|
||||||
7: '不执行',
|
7: '不执行',
|
||||||
9: '未知',
|
9: '未知',
|
||||||
10: '已校对',
|
|
||||||
11: '待接收',
|
|
||||||
12: '已接收',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -181,12 +172,12 @@ export const SlotStatus = {
|
|||||||
AVAILABLE: 0,
|
AVAILABLE: 0,
|
||||||
/** 已预约 */
|
/** 已预约 */
|
||||||
BOOKED: 1,
|
BOOKED: 1,
|
||||||
/** 已锁定 */
|
/** 已取消 / 已停诊 */
|
||||||
LOCKED: 2,
|
CANCELLED: 2,
|
||||||
/** 已签到 / 已取号 */
|
/** 已签到 / 已取号 */
|
||||||
CHECKED_IN: 3,
|
CHECKED_IN: 3,
|
||||||
/** 已取消 / 已停诊 */
|
/** 已锁定 */
|
||||||
CANCELLED: 4,
|
LOCKED: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,10 +185,10 @@ export const SlotStatus = {
|
|||||||
*/
|
*/
|
||||||
export const SlotStatusDescriptions = {
|
export const SlotStatusDescriptions = {
|
||||||
0: '未预约',
|
0: '未预约',
|
||||||
1: '已取号',
|
1: '已预约',
|
||||||
2: '已锁定',
|
2: '已停诊',
|
||||||
3: '已取号',
|
3: '已取号',
|
||||||
4: '已停诊',
|
4: '已锁定',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,18 +220,3 @@ export function getSlotStatusDescription(value) {
|
|||||||
export function getSlotStatusClass(status) {
|
export function getSlotStatusClass(status) {
|
||||||
return SlotStatusClassMap[status] || 'status-unbooked';
|
return SlotStatusClassMap[status] || 'status-unbooked';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 诊疗项目分类代码(对应后端 ActivityDefCategory 枚举)
|
|
||||||
* wor_activity_definition.category_code 字段
|
|
||||||
*/
|
|
||||||
export const ActivityCategory = {
|
|
||||||
/** 治疗 */
|
|
||||||
TREATMENT: '21',
|
|
||||||
/** 检验 */
|
|
||||||
PROOF: '22',
|
|
||||||
/** 检查 */
|
|
||||||
TEST: '23',
|
|
||||||
/** 手术 */
|
|
||||||
PROCEDURE: '24',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
<select id="status-select" class="search-select" v-model="selectedStatus" @change="onSearch">
|
<select id="status-select" class="search-select" v-model="selectedStatus" @change="onSearch">
|
||||||
<option value="all">全部</option>
|
<option value="all">全部</option>
|
||||||
<option value="unbooked">未预约</option>
|
<option value="unbooked">未预约</option>
|
||||||
<option value="locked">已锁定</option>
|
|
||||||
<option value="booked">已预约</option>
|
<option value="booked">已预约</option>
|
||||||
<option value="checked">已取号</option>
|
<option value="checked">已取号</option>
|
||||||
<option value="cancelled">已停诊</option>
|
<option value="cancelled">已停诊</option>
|
||||||
@@ -254,7 +253,6 @@ import useUserStore from '@/store/modules/user';
|
|||||||
|
|
||||||
const STATUS_CLASS_MAP = {
|
const STATUS_CLASS_MAP = {
|
||||||
'未预约': 'status-unbooked',
|
'未预约': 'status-unbooked',
|
||||||
'已锁定': 'status-locked',
|
|
||||||
'已预约': 'status-booked',
|
'已预约': 'status-booked',
|
||||||
'已取号': 'status-checked',
|
'已取号': 'status-checked',
|
||||||
'已退号': 'status-returned',
|
'已退号': 'status-returned',
|
||||||
@@ -776,7 +774,6 @@ export default {
|
|||||||
// 🔧 BugFix#399: 确保已取号状态正确匹配
|
// 🔧 BugFix#399: 确保已取号状态正确匹配
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
unbooked: ['未预约'],
|
unbooked: ['未预约'],
|
||||||
locked: ['已锁定'],
|
|
||||||
booked: ['已预约'],
|
booked: ['已预约'],
|
||||||
checked: ['已取号', '已签到'],
|
checked: ['已取号', '已签到'],
|
||||||
cancelled: ['已停诊', '已取消'],
|
cancelled: ['已停诊', '已取消'],
|
||||||
|
|||||||
@@ -1685,7 +1685,7 @@ function loadCheckInPatientList() {
|
|||||||
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
|
const today = formatDateStr(new Date(), 'YYYY-MM-DD');
|
||||||
listTicket({
|
listTicket({
|
||||||
date: today,
|
date: today,
|
||||||
status: 'locked',
|
status: 'booked',
|
||||||
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
|
name: checkInSearchKey.value, // 支持姓名等模糊查询,后端需适配
|
||||||
page: checkInPage.value,
|
page: checkInPage.value,
|
||||||
limit: checkInLimit.value
|
limit: checkInLimit.value
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ export function getAdviceBaseInfo(queryParams) {
|
|||||||
/**
|
/**
|
||||||
* 保存处方(单条)
|
* 保存处方(单条)
|
||||||
* @param {Object} data - 处方数据
|
* @param {Object} data - 处方数据
|
||||||
* @param {String} adviceOpType - 医嘱操作类型:'1'=保存草稿(SAVE_ADVICE),'2'=签发(SIGN_ADVICE)
|
* @param {String} adviceOpType - 医嘱操作类型:'0'=保存草稿,'1'=签发医嘱
|
||||||
*/
|
*/
|
||||||
export function savePrescription(data, adviceOpType = '1') {
|
export function savePrescription(data, adviceOpType = '0') {
|
||||||
return request({
|
return request({
|
||||||
url: '/doctor-station/advice/save-advice',
|
url: '/doctor-station/advice/save-advice',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|||||||
@@ -461,10 +461,6 @@ watch(
|
|||||||
console.log(prescriptionList.value,"prescriptionList.value")
|
console.log(prescriptionList.value,"prescriptionList.value")
|
||||||
if(newValue&&newValue.length>0){
|
if(newValue&&newValue.length>0){
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
// 手术计费场景(generateSourceEnum=6)不限制 bizRequestFlag
|
|
||||||
if (isSurgeryChargeBillingContext()) {
|
|
||||||
return item.check && item.statusEnum == 1
|
|
||||||
}
|
|
||||||
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
})
|
})
|
||||||
console.log(saveList,"prescriptionList.value")
|
console.log(saveList,"prescriptionList.value")
|
||||||
@@ -877,32 +873,6 @@ function ensureOrgTreeLoaded() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 待签发且未收费(chargeStatus=5 为已收费) */
|
|
||||||
function isPendingUnsignedAndUnpaid(item) {
|
|
||||||
return item.statusEnum == 1 && item.chargeStatus != 5
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 门诊划价:仅允许操作本人开立(bizRequestFlag==1 或空)。
|
|
||||||
* 手术计费:列表接口需按库中 generate_source_enum 查询(当前多为 1),故子组件仍传 generateSourceEnum=1;
|
|
||||||
* 通过 patientInfo.generateSourceEnum===6(手术计费在 chargePatientInfo 中已写入)识别场景,删除时不卡 bizRequestFlag。
|
|
||||||
*/
|
|
||||||
function isSurgeryChargeBillingContext() {
|
|
||||||
const fromPatient = props.patientInfo?.generateSourceEnum
|
|
||||||
return fromPatient != null && Number(fromPatient) === 6
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBizRequestAllowedForDelete(item) {
|
|
||||||
if (isSurgeryChargeBillingContext()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const src = props.generateSourceEnum != null ? Number(props.generateSourceEnum) : NaN
|
|
||||||
if (src === 6) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return Number(item.bizRequestFlag) === 1 || !item.bizRequestFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
// 🔧 修复:使用 groupIndexList 而不是 check 属性
|
// 🔧 修复:使用 groupIndexList 而不是 check 属性
|
||||||
// 因为 watch 监听器会在数据更新时重置 check 为 false
|
// 因为 watch 监听器会在数据更新时重置 check 为 false
|
||||||
@@ -911,93 +881,80 @@ function handleDelete() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canDeleteRow = (item) =>
|
let deleteList = groupIndexList.value.map((index) => {
|
||||||
isPendingUnsignedAndUnpaid(item) && isBizRequestAllowedForDelete(item)
|
const item = prescriptionList.value[index];
|
||||||
|
// 只删除待签发且未收费的项目
|
||||||
|
if (item.statusEnum != 1 || item.chargeStatus == 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 🔧 Bug #442: 非本人创建的医嘱不允许删除(与签发/签退逻辑保持一致)
|
||||||
|
if (Number(item.bizRequestFlag) !== 1 && item.bizRequestFlag) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 🔧 Bug #442: 已保存的行必须有有效的 requestId,否则跳过(避免后端删除不存在的记录)
|
||||||
|
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
requestId: item.requestId,
|
||||||
|
dbOpType: '3',
|
||||||
|
adviceType: item.adviceType,
|
||||||
|
};
|
||||||
|
}).filter(item => item !== null); // 过滤掉已签发、已收费、非本人创建或无 requestId 的项目
|
||||||
|
|
||||||
const anySelectedDeletable = groupIndexList.value.some((index) =>
|
if (deleteList.length == 0) {
|
||||||
canDeleteRow(prescriptionList.value[index])
|
proxy.$modal.msgWarning('只能删除待签发且未收费的项目');
|
||||||
)
|
return;
|
||||||
if (!anySelectedDeletable) {
|
|
||||||
proxy.$modal.msgWarning(
|
|
||||||
'只能删除「待签发」且「未收费」的项目;门诊划价还需为本人开立。已签发、已收费或非本人开立项不可删。'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let deleteList = groupIndexList.value
|
|
||||||
.map((index) => {
|
|
||||||
const item = prescriptionList.value[index]
|
|
||||||
if (!canDeleteRow(item)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
if (item.requestId == null || item.requestId === undefined || item.requestId === '') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
requestId: item.requestId,
|
|
||||||
dbOpType: '3',
|
|
||||||
adviceType: item.adviceType,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((item) => item !== null)
|
|
||||||
|
|
||||||
// 删除逻辑:按索引从大到小排序,避免删除后索引变化
|
// 删除逻辑:按索引从大到小排序,避免删除后索引变化
|
||||||
const sortedIndexes = [...groupIndexList.value].sort((a, b) => b - a)
|
const sortedIndexes = groupIndexList.value.sort((a, b) => b - a);
|
||||||
let hasSavedItem = false
|
let hasSavedItem = false;
|
||||||
|
|
||||||
for (const index of sortedIndexes) {
|
for (const index of sortedIndexes) {
|
||||||
const item = prescriptionList.value[index]
|
const item = prescriptionList.value[index];
|
||||||
if (!canDeleteRow(item)) {
|
if (item.statusEnum != 1) {
|
||||||
continue
|
continue; // 跳过已签发的项目
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.requestId) {
|
if (!item.requestId) {
|
||||||
// 新增的行(未保存到数据库),直接删除
|
// 新增的行(未保存到数据库),直接删除
|
||||||
prescriptionList.value.splice(index, 1)
|
prescriptionList.value.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
hasSavedItem = true
|
hasSavedItem = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupAfterDelete = () => {
|
if (hasSavedItem) {
|
||||||
|
// 🔧 Bug #454: 删除前弹出确认提示,告知用户将级联删除关联检验申请单
|
||||||
|
const hasLabItem = deleteList.some(item => item.adviceType === 3);
|
||||||
|
const confirmMsg = hasLabItem
|
||||||
|
? '删除此医嘱将同时删除关联的检验申请单,是否确认删除?'
|
||||||
|
: '确认删除选中的医嘱项目吗?';
|
||||||
|
|
||||||
|
proxy.$modal.confirm(confirmMsg).then(() => {
|
||||||
|
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
proxy.$modal.msgSuccess('操作成功');
|
||||||
|
getListInfo(false);
|
||||||
|
expandOrder.value = [];
|
||||||
|
groupIndexList.value = [];
|
||||||
|
groupList.value = [];
|
||||||
|
isAdding.value = false;
|
||||||
|
adviceQueryParams.value.adviceType = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户取消删除
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 只有新增行,已经在前端删除完成
|
||||||
|
proxy.$modal.msgSuccess('操作成功');
|
||||||
expandOrder.value = [];
|
expandOrder.value = [];
|
||||||
groupIndexList.value = [];
|
groupIndexList.value = [];
|
||||||
groupList.value = [];
|
groupList.value = [];
|
||||||
isAdding.value = false;
|
isAdding.value = false;
|
||||||
adviceQueryParams.value.adviceType = undefined;
|
adviceQueryParams.value.adviceType = undefined;
|
||||||
};
|
|
||||||
|
|
||||||
if (hasSavedItem) {
|
|
||||||
if (deleteList.length === 0) {
|
|
||||||
proxy.$modal.msgWarning('没有可提交删除的已保存医嘱,请刷新后重试');
|
|
||||||
getListInfo(false);
|
|
||||||
cleanupAfterDelete();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bug #454: 删除前确认;检验医嘱提示级联删除申请单
|
|
||||||
const hasLabItem = deleteList.some((item) => item.adviceType === 3);
|
|
||||||
const confirmMsg = hasLabItem
|
|
||||||
? '删除此医嘱将同时删除关联的检验申请单,是否确认删除?'
|
|
||||||
: '确认删除选中的医嘱项目吗?';
|
|
||||||
|
|
||||||
proxy.$modal
|
|
||||||
.confirm(confirmMsg)
|
|
||||||
.then(() => {
|
|
||||||
savePrescription({ adviceSaveList: deleteList }).then((res) => {
|
|
||||||
if (res.code == 200) {
|
|
||||||
proxy.$modal.msgSuccess('操作成功');
|
|
||||||
getListInfo(false);
|
|
||||||
cleanupAfterDelete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// 用户取消删除
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
proxy.$modal.msgSuccess('操作成功');
|
|
||||||
cleanupAfterDelete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,9 +986,7 @@ function changeCheck(value,index,row){
|
|||||||
groupList.value.map(k=>{
|
groupList.value.map(k=>{
|
||||||
if(k.check){
|
if(k.check){
|
||||||
if(k.statusEnum == 1){//待签发
|
if(k.statusEnum == 1){//待签发
|
||||||
// 手术计费场景(generateSourceEnum=6)不限制 bizRequestFlag
|
if(Number(k.bizRequestFlag)==1||!k.bizRequestFlag){
|
||||||
const bizAllowed = isSurgeryChargeBillingContext() || Number(k.bizRequestFlag)==1||!k.bizRequestFlag
|
|
||||||
if(bizAllowed){
|
|
||||||
if(handleSaveDisabled.value&&!handleSingOutDisabled.value&&groupList.value.length>1){
|
if(handleSaveDisabled.value&&!handleSingOutDisabled.value&&groupList.value.length>1){
|
||||||
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
|
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
|
||||||
return
|
return
|
||||||
@@ -1046,9 +1001,7 @@ function changeCheck(value,index,row){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(k.statusEnum == 2){ //已签发
|
if(k.statusEnum == 2){ //已签发
|
||||||
// 手术计费场景(generateSourceEnum=6)不限制 bizRequestFlag
|
if(Number(k.bizRequestFlag)==1||!k.bizRequestFlag){
|
||||||
const bizAllowed = isSurgeryChargeBillingContext() || Number(k.bizRequestFlag)==1||!k.bizRequestFlag
|
|
||||||
if(bizAllowed){
|
|
||||||
if(!handleSaveDisabled.value&&handleSingOutDisabled.value&&groupList.value.length>1){
|
if(!handleSaveDisabled.value&&handleSingOutDisabled.value&&groupList.value.length>1){
|
||||||
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
|
proxy.$modal.msgWarning('请选择相同的状态的项目进行操作')
|
||||||
return
|
return
|
||||||
@@ -1075,28 +1028,28 @@ function handleSave() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let saveList = prescriptionList.value.filter((item) => {
|
let saveList = prescriptionList.value.filter((item) => {
|
||||||
// 手术计费场景(generateSourceEnum=6)不限制 bizRequestFlag,允许任何授权用户签发
|
|
||||||
// 门诊划价场景保留 bizRequestFlag 限制,只能操作本人开立的医嘱
|
|
||||||
if (isSurgeryChargeBillingContext()) {
|
|
||||||
return item.check && item.statusEnum == 1
|
|
||||||
}
|
|
||||||
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.check && item.statusEnum == 1&&(Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
});
|
});
|
||||||
// 无可签发项目时提前返回,避免后端报"医嘱列表为空"
|
// let saveList = prescriptionList.value
|
||||||
if (saveList.length == 0) {
|
// .filter((item) => {
|
||||||
proxy.$modal.msgWarning('当前无可签发处方');
|
// return item.check;
|
||||||
return;
|
// }).filter((item) => {
|
||||||
}
|
// return item.statusEnum == 1&&item.bizRequestFlag==1
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (saveList.length == 0) {
|
||||||
|
// proxy.$modal.msgWarning('当前无可签发处方');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
// 此处签发处方和单行保存处方传参相同,后台已经将传参存为JSON字符串,此处直接转换为JSON即可
|
// 此处签发处方和单行保存处方传参相同,后台已经将传参存为JSON字符串,此处直接转换为JSON即可
|
||||||
let list = saveList.map((item) => {
|
let list = saveList.map((item) => {
|
||||||
const parsedContent = item.contentJson ? JSON.parse(item.contentJson) : {};
|
const parsedContent = item.contentJson ? JSON.parse(item.contentJson) : {};
|
||||||
return {
|
return {
|
||||||
...parsedContent,
|
...parsedContent,
|
||||||
requestId: item.requestId,
|
requestId: item.requestId,
|
||||||
// 已有 requestId 的记录走 UPDATE 路径,新记录走 INSERT 路径
|
dbOpType: '1',
|
||||||
dbOpType: item.requestId ? '2' : '1',
|
|
||||||
groupId: item.groupId,
|
groupId: item.groupId,
|
||||||
// 补充顶层关键字段(这些可能不在 contentJson 中,需从 API 响应顶层提取)
|
// 🔧 Bug #443: 补充顶层关键字段(这些不在 contentJson 中,需从 API 响应顶层提取)
|
||||||
encounterId: item.encounterId,
|
encounterId: item.encounterId,
|
||||||
patientId: item.patientId,
|
patientId: item.patientId,
|
||||||
locationId: item.positionId,
|
locationId: item.positionId,
|
||||||
@@ -1104,14 +1057,6 @@ function handleSave() {
|
|||||||
adviceTableName: item.adviceTableName,
|
adviceTableName: item.adviceTableName,
|
||||||
adviceDefinitionId: item.adviceDefinitionId,
|
adviceDefinitionId: item.adviceDefinitionId,
|
||||||
chargeItemId: item.chargeItemId,
|
chargeItemId: item.chargeItemId,
|
||||||
// 补充数量、单位、批号等字段(后端 handDevice 需要这些字段)
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitCode: item.unitCode,
|
|
||||||
lotNumber: item.lotNumber,
|
|
||||||
categoryEnum: item.categoryEnum,
|
|
||||||
// 签发时显式设置手术计费关键字段,后端 generateSourceEnum 回退为默认值导致查询无法匹配
|
|
||||||
generateSourceEnum: props.generateSourceEnum ?? parsedContent.generateSourceEnum ?? item.generateSourceEnum,
|
|
||||||
sourceBillNo: props.sourceBillNo ?? parsedContent.sourceBillNo ?? item.sourceBillNo,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// 确保 organizationId 不为 undefined(手术计费场景下可能缺失 orgId)
|
// 确保 organizationId 不为 undefined(手术计费场景下可能缺失 orgId)
|
||||||
@@ -1173,11 +1118,8 @@ function handleSaveSign(row, index) {
|
|||||||
cleanRow.generateSourceEnum = 6; // 手术计费
|
cleanRow.generateSourceEnum = 6; // 手术计费
|
||||||
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
|
||||||
}
|
}
|
||||||
// 🔧 门诊计费场景:保存为草稿,让药品出现在临时医嘱弹窗"已引用计费药品(待生成医嘱)"中
|
console.log('cleanRow', cleanRow)
|
||||||
// 🔧 修复:后端 AdviceOpType 枚举:'1'=SAVE_ADVICE(草稿), '2'=SIGN_ADVICE(签发)
|
savePrescription({ adviceSaveList: [cleanRow] }, '1').then((res) => {
|
||||||
// 之前传 '0' 后端不识别,导致直接创建了已签发(statusEnum=2)的记录
|
|
||||||
const adviceOpType = props.patientInfo.sourceBillNo ? '1' : '1'
|
|
||||||
savePrescription({ adviceSaveList: [cleanRow] }, adviceOpType).then((res) => {
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
proxy.$modal.msgSuccess('保存成功');
|
proxy.$modal.msgSuccess('保存成功');
|
||||||
getListInfo(false);
|
getListInfo(false);
|
||||||
@@ -1201,10 +1143,6 @@ function handleSingOut() {
|
|||||||
return item.check;
|
return item.check;
|
||||||
})
|
})
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
// 手术计费场景(generateSourceEnum=6)不限制 bizRequestFlag
|
|
||||||
if (isSurgeryChargeBillingContext()) {
|
|
||||||
return item.statusEnum == 2 && item.chargeStatus != 5
|
|
||||||
}
|
|
||||||
return item.statusEnum == 2 && item.chargeStatus != 5 && (Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
return item.statusEnum == 2 && item.chargeStatus != 5 && (Number(item.bizRequestFlag)==1||!item.bizRequestFlag)
|
||||||
})
|
})
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-form
|
<el-form
|
||||||
v-show="showSearch"
|
|
||||||
ref="queryRef"
|
|
||||||
:model="queryParams"
|
:model="queryParams"
|
||||||
|
ref="queryRef"
|
||||||
:inline="true"
|
:inline="true"
|
||||||
|
v-show="showSearch"
|
||||||
label-width="90px"
|
label-width="90px"
|
||||||
>
|
>
|
||||||
<el-form-item label="查询日期">
|
<el-form-item label="查询日期:">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryTime"
|
v-model="queryTime"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
style="width: 300px"
|
style="width: 300px; margin-right: 20px"
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
@change="getValue"
|
@change="getValue"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="费用性质">
|
<el-form-item label="费用性质:">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="contractNo"
|
v-model="contractNo"
|
||||||
placeholder="费用性质"
|
placeholder="费用性质"
|
||||||
clearable
|
clearable
|
||||||
style="width: 160px"
|
|
||||||
@change="getValue"
|
@change="getValue"
|
||||||
|
style="width: 150px; margin-right: 30px"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in contractList"
|
v-for="item in contractList"
|
||||||
@@ -33,241 +33,228 @@
|
|||||||
:value="item.busNo"
|
:value="item.busNo"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-button type="primary" plain icon="Search" @click="getValue">查询</el-button>
|
||||||
|
<el-button type="primary" plain icon="Printer" @click="print">打印</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="search-buttons">
|
<!-- <el-form-item label="科室:" prop="sourceLocationId">
|
||||||
<el-button
|
<el-select
|
||||||
type="primary"
|
v-model="queryParams.sourceLocationId"
|
||||||
plain
|
placeholder=""
|
||||||
icon="Search"
|
clearable
|
||||||
@click="getValue"
|
style="width: 150px"
|
||||||
>
|
>
|
||||||
查询
|
<el-option
|
||||||
</el-button>
|
v-for="issueDepartment in issueDepartmentDto"
|
||||||
<el-button
|
:key="issueDepartment.id"
|
||||||
type="primary"
|
:label="issueDepartment.name"
|
||||||
plain
|
:value="issueDepartment.id"
|
||||||
icon="Printer"
|
/>
|
||||||
@click="print"
|
</el-select>
|
||||||
>
|
</el-form-item> -->
|
||||||
打印
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<div
|
<!-- <el-row :gutter="10" class="mb8">
|
||||||
v-loading="loading"
|
<el-col :span="1.5">
|
||||||
class="report-container"
|
<el-button type="primary" plain icon="Search" @click="getValue">查询</el-button>
|
||||||
>
|
</el-col>
|
||||||
<div class="report-title">
|
<el-col :span="1.5">
|
||||||
门诊收费日结单
|
<el-button type="warning" plain icon="CircleClose" @click="handleClear">重置</el-button>
|
||||||
</div>
|
</el-col>
|
||||||
|
</el-row> -->
|
||||||
<el-row :gutter="20" class="info-row">
|
<div v-loading="loading">
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-row
|
||||||
<div class="info-cell">
|
:gutter="10"
|
||||||
<span class="info-label">经办人姓名:</span>
|
outpatientNo="mb8"
|
||||||
<span class="info-value">{{ userStore.nickName || '全部' }}</span>
|
style="
|
||||||
</div>
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 20px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<!-- <el-col :span="3">
|
||||||
|
<span>经办人编号:</span>
|
||||||
|
</el-col> -->
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">经办人姓名:</span>
|
||||||
|
<span class="value"> {{ userStore.nickName }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="info-cell">
|
<span class="label">科室:</span>
|
||||||
<span class="info-label">科室:</span>
|
<span class="value">{{ userStore.orgName }}</span>
|
||||||
<span class="info-value">{{ userStore.orgName || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="4.5">
|
||||||
<div class="info-cell">
|
<span class="label">时间:</span>
|
||||||
<span class="info-label">机构:</span>
|
<span class="value"> {{ queryTime[0] + '~' + queryTime[1] }} </span>
|
||||||
<span class="info-value">{{ userStore.hospitalName || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="info-cell">
|
|
||||||
<span class="info-label">时间:</span>
|
|
||||||
<span class="info-value">{{ queryTime && queryTime.length === 2 ? queryTime[0] + ' ~ ' + queryTime[1] : '-' }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row
|
||||||
<el-divider />
|
:gutter="10"
|
||||||
|
outpatientNo="mb8"
|
||||||
<div class="section-title">收入汇总</div>
|
style="
|
||||||
<el-row :gutter="16" class="data-row">
|
margin: 20px 0;
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
display: flex;
|
||||||
<div class="data-cell">
|
align-items: center;
|
||||||
<span class="data-label">总收入:</span>
|
justify-content: flex-start;
|
||||||
<span class="data-value">{{ formatValue(reportValue.cashSum) }}</span>
|
padding: 0 20px;
|
||||||
</div>
|
"
|
||||||
|
>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">实际现金收入:</span>
|
||||||
|
<span class="value"> {{ formatValue(reportValue.cashSum) }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">现金:</span>
|
||||||
<span class="data-label">现金:</span>
|
<span class="value">{{ formatValue(reportValue.rmbCashSum) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.rmbCashSum) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">微信:</span>
|
||||||
<span class="data-label">微信:</span>
|
<span class="value">{{ formatValue(reportValue.vxCashSum) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.vxCashSum) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">支付宝:</span>
|
||||||
<span class="data-label">支付宝:</span>
|
<span class="value">{{ formatValue(reportValue.aliCashSum) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.aliCashSum) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row
|
||||||
<el-divider />
|
:gutter="10"
|
||||||
|
outpatientNo="mb8"
|
||||||
<div class="section-title">医保支付</div>
|
style="
|
||||||
<el-row :gutter="16" class="data-row">
|
margin: 20px 0;
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
display: flex;
|
||||||
<div class="data-cell">
|
align-items: center;
|
||||||
<span class="data-label">统筹支付:</span>
|
justify-content: flex-start;
|
||||||
<span class="data-value">{{ formatValue(reportValue.tcSum) }}</span>
|
padding: 0 20px;
|
||||||
</div>
|
"
|
||||||
|
>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">统筹支付:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.tcSum) }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">账户支付:</span>
|
||||||
<span class="data-label">账户支付:</span>
|
<span class="value">{{ formatValue(reportValue.zhSum) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.zhSum) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">基金支付总额:</span>
|
||||||
<span class="data-label">基金支付总额:</span>
|
<span class="value">{{ formatValue(reportValue.fundSum) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.fundSum) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<!-- <el-col :span="3">
|
||||||
<div class="data-cell">
|
<span>医保人次:{{ reportValue.aliCashSum }}</span>
|
||||||
<span class="data-label">医保统筹+账户:</span>
|
</el-col> -->
|
||||||
<span class="data-value">{{ formatValue(Number(reportValue.zhSum || 0) + Number(reportValue.fundSum || 0)) }}</span>
|
</el-row>
|
||||||
</div>
|
<el-row
|
||||||
|
:gutter="10"
|
||||||
|
outpatientNo="mb8"
|
||||||
|
style="
|
||||||
|
margin: 20px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 20px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">诊查费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">检查费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.CHECK_FEE) }}</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">化验费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">治疗费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row
|
||||||
<el-divider />
|
:gutter="10"
|
||||||
|
outpatientNo="mb8"
|
||||||
<div class="section-title">费用明细</div>
|
style="
|
||||||
<el-row :gutter="16" class="data-row">
|
margin: 20px 0;
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
display: flex;
|
||||||
<div class="data-cell">
|
align-items: center;
|
||||||
<span class="data-label">诊查费:</span>
|
justify-content: flex-start;
|
||||||
<span class="data-value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span>
|
padding: 0 20px;
|
||||||
</div>
|
"
|
||||||
|
>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">西药费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">中药饮片费:</span>
|
||||||
<span class="data-label">检查费:</span>
|
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.CHECK_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">中成药费:</span>
|
||||||
<span class="data-label">化验费:</span>
|
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">卫生材料费:</span>
|
||||||
<span class="data-label">治疗费:</span>
|
<span class="value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="16" class="data-row">
|
<el-row
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
:gutter="10"
|
||||||
<div class="data-cell">
|
outpatientNo="mb8"
|
||||||
<span class="data-label">西药费:</span>
|
style="
|
||||||
<span class="data-value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span>
|
margin: 20px 0;
|
||||||
</div>
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 20px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-col :span="3">
|
||||||
|
<span class="label">诊疗费:</span>
|
||||||
|
<span class="value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">挂号费:</span>
|
||||||
<span class="data-label">中药饮片费:</span>
|
<span class="value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
<el-col :span="3">
|
||||||
<div class="data-cell">
|
<span class="label">其他费用:</span>
|
||||||
<span class="data-label">中成药费:</span>
|
<span class="value">{{ formatValue(reportValue.OTHER_FEE) }}</span>
|
||||||
<span class="data-value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell">
|
|
||||||
<span class="data-label">卫生材料费:</span>
|
|
||||||
<span class="data-value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row :gutter="16" class="data-row">
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell">
|
|
||||||
<span class="data-label">普通挂号费:</span>
|
|
||||||
<span class="data-value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell">
|
|
||||||
<span class="data-label">挂号费:</span>
|
|
||||||
<span class="data-value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell">
|
|
||||||
<span class="data-label">其他费用:</span>
|
|
||||||
<span class="data-value">{{ formatValue(reportValue.OTHER_FEE) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell">
|
|
||||||
<span class="data-label">退费金额:</span>
|
|
||||||
<span class="data-value">{{ formatValue(reportValue.returnFee) }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row :gutter="16" class="data-row summary-row">
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell summary-cell">
|
|
||||||
<span class="data-label summary-label">费用总额:</span>
|
|
||||||
<span class="data-value value-highlight">{{ totalFeeAmount }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :xs="24" :sm="12" :md="6">
|
|
||||||
<div class="data-cell summary-cell">
|
|
||||||
<span class="data-label summary-label">医保报销:</span>
|
|
||||||
<span class="data-value value-highlight">{{ insuranceReimbursement }}</span>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<!-- <el-col :span="3">
|
||||||
|
<span>现金:</span>
|
||||||
|
</el-col> -->
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="DayEnd">
|
<script setup name="dayEnd">
|
||||||
import { ref, reactive, toRefs, getCurrentInstance, computed } from 'vue';
|
import {getContractList, getRreportReturnIssue, getTotal} from './component/api';
|
||||||
import Decimal from 'decimal.js';
|
|
||||||
import { getTotal, getContractList, getRreportReturnIssue } from './component/api';
|
|
||||||
import useUserStore from '@/store/modules/user';
|
import useUserStore from '@/store/modules/user';
|
||||||
import { formatDateStr } from '@/utils/index';
|
import {formatDateStr} from '@/utils/index';
|
||||||
|
import Decimal from 'decimal.js';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
// import Dialog from "./components/Dialog";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
const purchaseinventoryRef = ref(null); // 初始化 ref
|
||||||
const purchaseinventoryList = ref([]);
|
const purchaseinventoryList = ref([]);
|
||||||
const reportValue = ref({});
|
const open = ref(false);
|
||||||
const total = ref(0);
|
const loading = ref(true);
|
||||||
const loading = ref(false);
|
|
||||||
const showSearch = ref(true);
|
const showSearch = ref(true);
|
||||||
const ids = ref([]);
|
const ids = ref([]);
|
||||||
const single = ref(true);
|
const single = ref(true);
|
||||||
const multiple = ref(true);
|
const multiple = ref(true);
|
||||||
const occurrenceTime = ref([]);
|
const total = ref(0);
|
||||||
const contractList = ref([]);
|
const title = ref('');
|
||||||
|
const contractList = ref(undefined);
|
||||||
|
const reportValue = ref({});
|
||||||
const queryTime = ref([
|
const queryTime = ref([
|
||||||
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
||||||
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
||||||
@@ -313,6 +300,10 @@ function getContract() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPharmacyCabinetLists() {
|
function getPharmacyCabinetLists() {
|
||||||
|
// occurrenceTime.value =
|
||||||
|
// getDepartmentList().then((response) => {
|
||||||
|
// issueDepartmentDto.value = response.data
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
/** 查询调拨管理项目列表 */
|
/** 查询调拨管理项目列表 */
|
||||||
function getList() {
|
function getList() {
|
||||||
@@ -340,6 +331,7 @@ function handleQuery() {
|
|||||||
|
|
||||||
/** 清空条件按钮操作 */
|
/** 清空条件按钮操作 */
|
||||||
function handleClear() {
|
function handleClear() {
|
||||||
|
// 清空查询条件
|
||||||
queryParams.value.approvalTimeSTime = '';
|
queryParams.value.approvalTimeSTime = '';
|
||||||
queryParams.value.approvalTimeETime = '';
|
queryParams.value.approvalTimeETime = '';
|
||||||
occurrenceTime.value = '';
|
occurrenceTime.value = '';
|
||||||
@@ -356,11 +348,12 @@ function handleSelectionChange(selection) {
|
|||||||
|
|
||||||
/** 打印门诊日结 */
|
/** 打印门诊日结 */
|
||||||
async function print() {
|
async function print() {
|
||||||
|
// const selectedRows = proxy.$refs['tableRef'].getSelectionRows();
|
||||||
console.log(reportValue.value, '==reportValue.value==');
|
console.log(reportValue.value, '==reportValue.value==');
|
||||||
const result = {
|
const result = {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
...reportValue.value,
|
...reportValue.value, // 将 reportValue.value 中的所有属性展开到 result 中
|
||||||
nickName: userStore.nickName,
|
nickName: userStore.nickName,
|
||||||
orgName: userStore.orgName,
|
orgName: userStore.orgName,
|
||||||
fixmedinsName: userStore.hospitalName,
|
fixmedinsName: userStore.hospitalName,
|
||||||
@@ -381,12 +374,14 @@ async function print() {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
console.log(result, '==result.data==');
|
console.log(result, '==result.data==');
|
||||||
|
// 将对象转换为 JSON 字符串
|
||||||
let jsonString = JSON.stringify(result, null, 2);
|
let jsonString = JSON.stringify(result, null, 2);
|
||||||
console.log(jsonString, 'jsonstring');
|
console.log(jsonString, 'jsonstring');
|
||||||
await CefSharp.BindObjectAsync('boundAsync');
|
await CefSharp.BindObjectAsync('boundAsync');
|
||||||
await boundAsync
|
await boundAsync
|
||||||
.printReport(getPrintFileName(contractNo.value), jsonString)
|
.printReport(getPrintFileName(contractNo.value), jsonString)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
//返回结果是jsonString,可判断其调用是否成功
|
||||||
console.log(response, 'response');
|
console.log(response, 'response');
|
||||||
var res = JSON.parse(response);
|
var res = JSON.parse(response);
|
||||||
if (!res.IsSuccess) {
|
if (!res.IsSuccess) {
|
||||||
@@ -402,9 +397,9 @@ function getPrintFileName(value) {
|
|||||||
switch (value) {
|
switch (value) {
|
||||||
case '0000':
|
case '0000':
|
||||||
return '门诊日结单(按登录角色查询)自费.grf';
|
return '门诊日结单(按登录角色查询)自费.grf';
|
||||||
case '229900':
|
case '229900': // 省医保
|
||||||
return '门诊日结单(按登录角色查询)省医保.grf';
|
return '门诊日结单(按登录角色查询)省医保.grf';
|
||||||
case '220100':
|
case '220100': // 市医保
|
||||||
return '门诊日结单(按登录角色查询)市医保.grf';
|
return '门诊日结单(按登录角色查询)市医保.grf';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,173 +408,28 @@ function formatValue(value) {
|
|||||||
return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元';
|
return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性:费用总额
|
|
||||||
const totalFeeAmount = computed(() => {
|
|
||||||
const v = reportValue.value;
|
|
||||||
const sum =
|
|
||||||
Number(v.DIAGNOSTIC_FEE || 0) +
|
|
||||||
Number(v.CHECK_FEE || 0) +
|
|
||||||
Number(v.DIAGNOSTIC_TEST_FEE || 0) +
|
|
||||||
Number(v.MEDICAL_EXPENSE_FEE || 0) +
|
|
||||||
Number(v.WEST_MEDICINE || 0) +
|
|
||||||
Number(v.CHINESE_MEDICINE_SLICES_FEE || 0) +
|
|
||||||
Number(v.CHINESE_MEDICINE_FEE || 0) +
|
|
||||||
Number(v.GENERAL_CONSULTATION_FEE || 0) +
|
|
||||||
Number(v.REGISTRATION_FEE || 0) +
|
|
||||||
Number(v.OTHER_FEE || 0) +
|
|
||||||
Number(v.SANITARY_MATERIALS_FEE || 0);
|
|
||||||
return formatValue(sum);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 计算属性:医保报销(统筹+账户)
|
|
||||||
const insuranceReimbursement = computed(() => {
|
|
||||||
const v = reportValue.value;
|
|
||||||
const sum = Number(v.tcSum || 0) + Number(v.zhSum || 0);
|
|
||||||
return formatValue(sum);
|
|
||||||
});
|
|
||||||
|
|
||||||
getList();
|
getList();
|
||||||
getPharmacyCabinetLists();
|
getPharmacyCabinetLists();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-container {
|
.custom-tree-node {
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 16px auto;
|
|
||||||
padding: 24px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
padding: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-cell {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 0;
|
|
||||||
min-height: 32px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.title {
|
||||||
color: #909399;
|
font-weight: bold;
|
||||||
font-size: 13px;
|
font-size: large;
|
||||||
white-space: nowrap;
|
margin-bottom: 10px;
|
||||||
min-width: 80px;
|
|
||||||
}
|
}
|
||||||
|
.label {
|
||||||
.info-value {
|
display: inline-block;
|
||||||
color: #303133;
|
width: 120px !important;
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
|
.value {
|
||||||
.data-row {
|
float: right;
|
||||||
padding: 4px 0;
|
|
||||||
}
|
}
|
||||||
|
.el-col {
|
||||||
.data-cell {
|
margin-right: 50px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 12px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 4px;
|
|
||||||
min-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-label {
|
|
||||||
color: #606266;
|
|
||||||
font-size: 13px;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 100px;
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-value {
|
|
||||||
color: #303133;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value-highlight {
|
|
||||||
color: #409eff;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-row {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-cell {
|
|
||||||
background: #ecf5ff;
|
|
||||||
border: 1px solid #d9ecff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #409eff;
|
|
||||||
padding: 8px 0 8px 12px;
|
|
||||||
margin: 8px 0 4px;
|
|
||||||
border-left: 3px solid #409eff;
|
|
||||||
background: linear-gradient(90deg, rgba(64, 158, 255, 0.05) 0%, transparent 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-buttons {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-buttons .el-form-item__content {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-divider--horizontal) {
|
|
||||||
margin: 12px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form--inline .el-form-item {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.data-label {
|
|
||||||
min-width: 80px;
|
|
||||||
text-align: left;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-value {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container">
|
|
||||||
<el-form
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryRef"
|
|
||||||
:inline="true"
|
|
||||||
v-show="showSearch"
|
|
||||||
label-width="90px"
|
|
||||||
>
|
|
||||||
<el-form-item label="查询日期:">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="queryTime"
|
|
||||||
type="daterange"
|
|
||||||
start-placeholder="开始日期"
|
|
||||||
end-placeholder="结束日期"
|
|
||||||
style="width: 300px; margin-right: 20px"
|
|
||||||
@change="getValue"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="费用性质:">
|
|
||||||
<el-select
|
|
||||||
v-model="contractNo"
|
|
||||||
placeholder="费用性质"
|
|
||||||
clearable
|
|
||||||
@change="getValue"
|
|
||||||
style="width: 150px; margin-right: 30px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in contractList"
|
|
||||||
:key="item.busNo"
|
|
||||||
:label="item.contractName"
|
|
||||||
:value="item.busNo"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-button type="primary" plain icon="Search" @click="getValue">查询</el-button>
|
|
||||||
<el-button type="primary" plain icon="Printer" @click="print">打印</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<div v-loading="loading" style="width: 1300px">
|
|
||||||
<div style="text-align: center">
|
|
||||||
<h2>门诊收费日结单</h2>
|
|
||||||
</div>
|
|
||||||
<el-row
|
|
||||||
:gutter="5"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="4">
|
|
||||||
<span class="label">经办人姓名:</span>
|
|
||||||
<span class="value">{{ userStore.nickName }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="4">
|
|
||||||
<span class="label">科室:</span>
|
|
||||||
<span class="value">{{ userStore.orgName }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="7">
|
|
||||||
<span class="label">时间:</span>
|
|
||||||
<span class="value">{{ queryTime[0] + '~' + queryTime[1] }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<el-row
|
|
||||||
:gutter="10"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">总收入:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.cashSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">现金:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.rmbCashSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">微信:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.vxCashSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">支付宝:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.aliCashSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<el-row
|
|
||||||
:gutter="10"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">统筹支付:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.tcSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">账户支付:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.zhSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">基金支付总额:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.fundSum) }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<el-row
|
|
||||||
:gutter="10"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">诊查费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">检查费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.CHECK_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">化验费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">治疗费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row
|
|
||||||
:gutter="10"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">西药费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">中药饮片费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">中成药费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">卫生材料费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row
|
|
||||||
:gutter="10"
|
|
||||||
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
|
|
||||||
>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">诊疗费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">挂号费:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="5">
|
|
||||||
<span class="label">其他费用:</span>
|
|
||||||
<span class="value">{{ formatValue(reportValue.OTHER_FEE) }}</span>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup name="dayEnd">
|
|
||||||
import {getContractList, getRreportReturnIssue, getTotal} from './component/api';
|
|
||||||
import useUserStore from '@/store/modules/user';
|
|
||||||
import {formatDateStr} from '@/utils/index';
|
|
||||||
import Decimal from 'decimal.js';
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { proxy } = getCurrentInstance();
|
|
||||||
|
|
||||||
const purchaseinventoryRef = ref(null);
|
|
||||||
const purchaseinventoryList = ref([]);
|
|
||||||
const open = ref(false);
|
|
||||||
const loading = ref(true);
|
|
||||||
const showSearch = ref(true);
|
|
||||||
const ids = ref([]);
|
|
||||||
const single = ref(true);
|
|
||||||
const multiple = ref(true);
|
|
||||||
const total = ref(0);
|
|
||||||
const title = ref('');
|
|
||||||
const contractList = ref(undefined);
|
|
||||||
const reportValue = ref({});
|
|
||||||
const queryTime = ref([
|
|
||||||
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
|
||||||
formatDateStr(new Date(), 'YYYY-MM-DD'),
|
|
||||||
]);
|
|
||||||
const contractNo = ref('0000');
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
queryParams: {
|
|
||||||
form: {},
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
searchKey: undefined,
|
|
||||||
purposeLocationId: undefined,
|
|
||||||
sourceLocationId: undefined,
|
|
||||||
supplierId: undefined,
|
|
||||||
approvalTimeSTime: undefined,
|
|
||||||
approvalTimeETime: undefined,
|
|
||||||
},
|
|
||||||
rules: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
getValue();
|
|
||||||
function getValue() {
|
|
||||||
loading.value = true;
|
|
||||||
getTotal({
|
|
||||||
contractNo: contractNo.value,
|
|
||||||
startTime: queryTime.value[0] + ' 00:00:00',
|
|
||||||
endTime: queryTime.value[1] + ' 23:59:59',
|
|
||||||
entererId: userStore.practitionerId,
|
|
||||||
}).then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
reportValue.value = res.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getContract();
|
|
||||||
function getContract() {
|
|
||||||
getContractList().then((response) => {
|
|
||||||
contractList.value = response.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPharmacyCabinetLists() {
|
|
||||||
}
|
|
||||||
/** 查询调拨管理项目列表 */
|
|
||||||
function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
getRreportReturnIssue(queryParams.value).then((res) => {
|
|
||||||
loading.value = false;
|
|
||||||
purchaseinventoryList.value = res.data.records;
|
|
||||||
total.value = res.data.total;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.approvalTimeSTime =
|
|
||||||
occurrenceTime.value && occurrenceTime.value.length == 2
|
|
||||||
? occurrenceTime.value[0] + ' 00:00:00'
|
|
||||||
: '';
|
|
||||||
queryParams.value.approvalTimeETime =
|
|
||||||
occurrenceTime.value && occurrenceTime.value.length == 2
|
|
||||||
? occurrenceTime.value[1] + ' 23:59:59'
|
|
||||||
: '';
|
|
||||||
queryParams.value.pageNo = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 清空条件按钮操作 */
|
|
||||||
function handleClear() {
|
|
||||||
queryParams.value.approvalTimeSTime = '';
|
|
||||||
queryParams.value.approvalTimeETime = '';
|
|
||||||
occurrenceTime.value = '';
|
|
||||||
proxy.resetForm('queryRef');
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 选择条数 */
|
|
||||||
function handleSelectionChange(selection) {
|
|
||||||
ids.value = selection.map((item) => item.id);
|
|
||||||
single.value = selection.length != 1;
|
|
||||||
multiple.value = !selection.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 打印门诊日结 */
|
|
||||||
async function print() {
|
|
||||||
console.log(reportValue.value, '==reportValue.value==');
|
|
||||||
const result = {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
...reportValue.value,
|
|
||||||
nickName: userStore.nickName,
|
|
||||||
orgName: userStore.orgName,
|
|
||||||
fixmedinsName: userStore.hospitalName,
|
|
||||||
queryTime: queryTime.value[0] + '~' + queryTime.value[1],
|
|
||||||
zfAmount: new Decimal(reportValue.value.zhSum || 0).add(reportValue.value.fundSum || 0),
|
|
||||||
feeAmount: new Decimal(reportValue.value.DIAGNOSTIC_FEE || 0)
|
|
||||||
.add(reportValue.value.CHECK_FEE || 0)
|
|
||||||
.add(reportValue.value.DIAGNOSTIC_TEST_FEE || 0)
|
|
||||||
.add(reportValue.value.MEDICAL_EXPENSE_FEE || 0)
|
|
||||||
.add(reportValue.value.WEST_MEDICINE || 0)
|
|
||||||
.add(reportValue.value.CHINESE_MEDICINE_SLICES_FEE || 0)
|
|
||||||
.add(reportValue.value.CHINESE_MEDICINE_FEE || 0)
|
|
||||||
.add(reportValue.value.GENERAL_CONSULTATION_FEE || 0)
|
|
||||||
.add(reportValue.value.REGISTRATION_FEE || 0)
|
|
||||||
.add(reportValue.value.OTHER_FEE || 0)
|
|
||||||
.add(reportValue.value.SANITARY_MATERIALS_FEE || 0),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
console.log(result, '==result.data==');
|
|
||||||
let jsonString = JSON.stringify(result, null, 2);
|
|
||||||
console.log(jsonString, 'jsonstring');
|
|
||||||
await CefSharp.BindObjectAsync('boundAsync');
|
|
||||||
await boundAsync
|
|
||||||
.printReport(getPrintFileName(contractNo.value), jsonString)
|
|
||||||
.then((response) => {
|
|
||||||
console.log(response, 'response');
|
|
||||||
var res = JSON.parse(response);
|
|
||||||
if (!res.IsSuccess) {
|
|
||||||
proxy.$modal.msgError('调用打印插件失败:' + res.ErrorMessage);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
proxy.$modal.msgError('调用打印插件失败:' + error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrintFileName(value) {
|
|
||||||
switch (value) {
|
|
||||||
case '0000':
|
|
||||||
return '门诊日结单(按登录角色查询)自费.grf';
|
|
||||||
case '229900':
|
|
||||||
return '门诊日结单(按登录角色查询)省医保.grf';
|
|
||||||
case '220100':
|
|
||||||
return '门诊日结单(按登录角色查询)市医保.grf';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatValue(value) {
|
|
||||||
return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元';
|
|
||||||
}
|
|
||||||
|
|
||||||
getList();
|
|
||||||
getPharmacyCabinetLists();
|
|
||||||
</script>
|
|
||||||
<style scoped>
|
|
||||||
.label {
|
|
||||||
display: inline-block;
|
|
||||||
width: 120px !important;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.el-col {
|
|
||||||
margin-right: 50px;
|
|
||||||
}
|
|
||||||
.divider {
|
|
||||||
height: 3px;
|
|
||||||
background-color: #000;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -354,11 +354,6 @@ async function getList() {
|
|||||||
if (!item.classification) {
|
if (!item.classification) {
|
||||||
item.classification = '西医';
|
item.classification = '西医';
|
||||||
}
|
}
|
||||||
// 转换 longTermFlag 为字符串,以匹配 useDict 返回的字典值类型(字符串)
|
|
||||||
// 避免 el-select 因类型不匹配(整数 1 vs 字符串 "1")导致下拉框清空
|
|
||||||
if (item.longTermFlag != null) {
|
|
||||||
item.longTermFlag = String(item.longTermFlag);
|
|
||||||
}
|
|
||||||
// 如果ybNo(诊断编码)符合传染病编码格式,添加到selectedDiseases
|
// 如果ybNo(诊断编码)符合传染病编码格式,添加到selectedDiseases
|
||||||
if (item.ybNo && /^(01|02|03)/.test(item.ybNo)) {
|
if (item.ybNo && /^(01|02|03)/.test(item.ybNo)) {
|
||||||
item.selectedDiseases = [item.ybNo];
|
item.selectedDiseases = [item.ybNo];
|
||||||
@@ -749,26 +744,22 @@ function handleInfectiousDiseaseReport() {
|
|||||||
'手足口病': '0311',
|
'手足口病': '0311',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取所有命中传染病映射的诊断,但跳过已有已提交报卡的诊断
|
// 获取所有诊断名称对应的报卡编码,但跳过已有已提交报卡的诊断
|
||||||
const infectiousDiagnoses = form.value.diagnosisList
|
const allSelectedDiseases = form.value.diagnosisList
|
||||||
.map(d => ({
|
.filter(d => d.name && d.hasInfectiousReport !== 1)
|
||||||
diagnosis: d,
|
.map(d => diseaseNameToCode[d.name] || null)
|
||||||
diseaseCode: d.name && d.hasInfectiousReport !== 1 ? diseaseNameToCode[d.name] : null
|
.filter(code => code);
|
||||||
}))
|
|
||||||
.filter(item => item.diseaseCode);
|
|
||||||
|
|
||||||
const allSelectedDiseases = infectiousDiagnoses.map(item => item.diseaseCode);
|
|
||||||
|
|
||||||
if (allSelectedDiseases.length === 0) {
|
if (allSelectedDiseases.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先使用命中传染病映射的主诊断,否则使用第一条命中的传染病诊断
|
// 优先使用主诊断(同样跳过已有报卡的)
|
||||||
const mainInfectiousDiagnosis = infectiousDiagnoses.find(item => item.diagnosis.maindiseFlag === 1)?.diagnosis;
|
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1 && d.hasInfectiousReport !== 1);
|
||||||
const firstInfectiousDiagnosis = infectiousDiagnoses[0].diagnosis;
|
const firstDiagnosis = form.value.diagnosisList.find(d => d.hasInfectiousReport !== 1) || form.value.diagnosisList[0];
|
||||||
|
|
||||||
const diagnosisToShow = {
|
const diagnosisToShow = {
|
||||||
...(mainInfectiousDiagnosis || firstInfectiousDiagnosis),
|
...(mainDiagnosis || firstDiagnosis),
|
||||||
selectedDiseases: allSelectedDiseases
|
selectedDiseases: allSelectedDiseases
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -548,8 +548,6 @@ const dialogReadOnly = ref(false);
|
|||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
// 保存按钮加载状态,防止重复提交
|
// 保存按钮加载状态,防止重复提交
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
// 数据加载中标志,防止 showReport 加载已有数据时 watch 清空分型字段
|
|
||||||
const loadingData = ref(false);
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
@@ -942,8 +940,7 @@ const showSubtypeSelect = computed(() => {
|
|||||||
// 监听疾病选择变化,自动清空分型选择
|
// 监听疾病选择变化,自动清空分型选择
|
||||||
watch(() => [form.value.selectedClassA, form.value.selectedClassB, form.value.selectedClassC], (newVal, oldVal) => {
|
watch(() => [form.value.selectedClassA, form.value.selectedClassB, form.value.selectedClassC], (newVal, oldVal) => {
|
||||||
// 如果疾病选择发生变化,清空分型选择
|
// 如果疾病选择发生变化,清空分型选择
|
||||||
// 数据加载中时不清空,避免 showReport 加载已有数据时被错误清空
|
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
||||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal) && !loadingData.value) {
|
|
||||||
form.value.diseaseType = '';
|
form.value.diseaseType = '';
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
@@ -1095,9 +1092,6 @@ function showReport(reportData = {}, readOnly = true) {
|
|||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
dialogReadOnly.value = readOnly;
|
dialogReadOnly.value = readOnly;
|
||||||
|
|
||||||
// 标记数据加载中,防止 watch 清空 diseaseType 分型字段
|
|
||||||
loadingData.value = true;
|
|
||||||
|
|
||||||
resetAddressSelector();
|
resetAddressSelector();
|
||||||
initProvinceOptions();
|
initProvinceOptions();
|
||||||
|
|
||||||
@@ -1155,9 +1149,6 @@ function showReport(reportData = {}, readOnly = true) {
|
|||||||
form.value.addressCounty,
|
form.value.addressCounty,
|
||||||
form.value.addressTown
|
form.value.addressTown
|
||||||
);
|
);
|
||||||
|
|
||||||
// 数据加载完成,恢复 watch 监听
|
|
||||||
loadingData.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1442,7 +1433,7 @@ async function buildSubmitData() {
|
|||||||
const submitData = {
|
const submitData = {
|
||||||
cardNo: formData.cardNo,
|
cardNo: formData.cardNo,
|
||||||
visitId: props.patientInfo?.encounterId || formData.encounterId || null,
|
visitId: props.patientInfo?.encounterId || formData.encounterId || null,
|
||||||
diagId: formData.diagnosisId || null,
|
diagId: formData.diagnosisId ? Number(formData.diagnosisId) : null,
|
||||||
patId: formData.patientId || null,
|
patId: formData.patientId || null,
|
||||||
idType: 1, // 默认身份证
|
idType: 1, // 默认身份证
|
||||||
idNo: formData.idNo,
|
idNo: formData.idNo,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -179,8 +179,8 @@
|
|||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择执行时间"
|
placeholder="选择执行时间"
|
||||||
size="small"
|
size="small"
|
||||||
format="YYYY-MM-DD HH:mm"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
value-format="YYYY-MM-DD HH:mm"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -445,6 +445,7 @@
|
|||||||
>
|
>
|
||||||
<el-table-column label="项目名称" prop="itemName" min-width="180">
|
<el-table-column label="项目名称" prop="itemName" min-width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
|
||||||
<span :style="{ fontWeight: scope.row.isPackage ? 'bold' : 'normal' }">
|
<span :style="{ fontWeight: scope.row.isPackage ? 'bold' : 'normal' }">
|
||||||
{{ scope.row.itemName }}
|
{{ scope.row.itemName }}
|
||||||
</span>
|
</span>
|
||||||
@@ -562,6 +563,7 @@
|
|||||||
@change="toggleInspectionItem(item)"
|
@change="toggleInspectionItem(item)"
|
||||||
@click.stop
|
@click.stop
|
||||||
/>
|
/>
|
||||||
|
<el-tag v-if="item.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
|
||||||
<span class="item-itemName">{{ item.itemName }}</span>
|
<span class="item-itemName">{{ item.itemName }}</span>
|
||||||
<span class="item-price">¥{{ item.itemPrice }}/{{ item.unit || "次" }}</span>
|
<span class="item-price">¥{{ item.itemPrice }}/{{ item.unit || "次" }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -612,6 +614,7 @@
|
|||||||
<template v-if="item.isPackage">{{ item.expanded ? '▼' : '▶' }}</template>
|
<template v-if="item.isPackage">{{ item.expanded ? '▼' : '▶' }}</template>
|
||||||
<template v-else>•</template>
|
<template v-else>•</template>
|
||||||
</span>
|
</span>
|
||||||
|
<el-tag v-if="item.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
|
||||||
<span class="item-itemName">{{ item.itemName }}</span>
|
<span class="item-itemName">{{ item.itemName }}</span>
|
||||||
<span class="item-price">¥{{ item.itemPrice }}/{{ item.unit || "次" }}</span>
|
<span class="item-price">¥{{ item.itemPrice }}/{{ item.unit || "次" }}</span>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -872,30 +875,6 @@ let applyTimeTimer = null
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { id: userId, name: userName, nickName: userNickName } = storeToRefs(userStore)
|
const { id: userId, name: userName, nickName: userNickName } = storeToRefs(userStore)
|
||||||
|
|
||||||
/** 执行时间默认值:当前系统时间,精确到分钟 */
|
|
||||||
const getDefaultExecuteTime = () => {
|
|
||||||
const d = new Date()
|
|
||||||
const year = d.getFullYear()
|
|
||||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(d.getDate()).padStart(2, '0')
|
|
||||||
const hours = String(d.getHours()).padStart(2, '0')
|
|
||||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将后端时间规范为 YYYY-MM-DD HH:mm */
|
|
||||||
const normalizeExecuteTime = (value) => {
|
|
||||||
if (!value) return getDefaultExecuteTime()
|
|
||||||
const d = new Date(String(value).replace(/-/g, '/'))
|
|
||||||
if (Number.isNaN(d.getTime())) return getDefaultExecuteTime()
|
|
||||||
const year = d.getFullYear()
|
|
||||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(d.getDate()).padStart(2, '0')
|
|
||||||
const hours = String(d.getHours()).padStart(2, '0')
|
|
||||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改 initData 函数
|
// 修改 initData 函数
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
// 先初始化患者信息(如果有)
|
// 先初始化患者信息(如果有)
|
||||||
@@ -918,13 +897,6 @@ const initData = async () => {
|
|||||||
formData.applyNo = '自动生成'
|
formData.applyNo = '自动生成'
|
||||||
// 申请日期实时更新(启动定时器)
|
// 申请日期实时更新(启动定时器)
|
||||||
startApplyTimeTimer()
|
startApplyTimeTimer()
|
||||||
// 执行时间默认当前系统时间(精确到分钟)
|
|
||||||
if (!formData.executeTime) {
|
|
||||||
formData.executeTime = getDefaultExecuteTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行时间默认填充当前系统时间
|
|
||||||
formData.executeTime = formatDateTime(new Date())
|
|
||||||
|
|
||||||
// 获取主诊断信息
|
// 获取主诊断信息
|
||||||
try {
|
try {
|
||||||
@@ -1003,7 +975,7 @@ const formData = reactive({
|
|||||||
applyDeptCode: '',
|
applyDeptCode: '',
|
||||||
specimenName: '血液',
|
specimenName: '血液',
|
||||||
encounterId: '',
|
encounterId: '',
|
||||||
executeTime: getDefaultExecuteTime(),
|
executeTime: null,
|
||||||
applicationType: 0
|
applicationType: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1213,9 +1185,9 @@ const loadCategoryItems = async (categoryKey, loadMore = false) => {
|
|||||||
|
|
||||||
// 映射数据格式(从检验项目维护页面的数据结构映射)
|
// 映射数据格式(从检验项目维护页面的数据结构映射)
|
||||||
const mappedItems = records.map(item => {
|
const mappedItems = records.map(item => {
|
||||||
// 套餐项目处理:需同时满足 feePackageId 有效且 packageName 非空
|
// 套餐项目处理:套餐项目使用套餐金额,普通项目使用零售价
|
||||||
// BugFix#556: 增加 packageName 联合判断,避免普通项目因 feePackageId 有值被误标为套餐
|
// BugFix#404: 增加对空字符串的判断,避免空字符串被误认为有效套餐ID
|
||||||
const isPackage = item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName
|
const isPackage = item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null'
|
||||||
const itemPrice = isPackage
|
const itemPrice = isPackage
|
||||||
? (parseFloat(item.packageAmount || 0) || parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
|
? (parseFloat(item.packageAmount || 0) || parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
|
||||||
: (parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
|
: (parseFloat(item.retailPrice || 0) || parseFloat(item.price || 0))
|
||||||
@@ -1575,7 +1547,7 @@ const resetForm = async () => {
|
|||||||
visitNo: '',
|
visitNo: '',
|
||||||
specimenName: '血液',
|
specimenName: '血液',
|
||||||
encounterId: props.patientInfo.encounterId || '',
|
encounterId: props.patientInfo.encounterId || '',
|
||||||
executeTime: getDefaultExecuteTime(),
|
executeTime: null,
|
||||||
applicationType: 0,
|
applicationType: 0,
|
||||||
})
|
})
|
||||||
selectedInspectionItems.value = []
|
selectedInspectionItems.value = []
|
||||||
@@ -2013,7 +1985,7 @@ const loadApplicationToForm = async (row) => {
|
|||||||
visitNo: detail.visitNo,
|
visitNo: detail.visitNo,
|
||||||
specimenName: detail.specimenName,
|
specimenName: detail.specimenName,
|
||||||
encounterId: detail.encounterId,
|
encounterId: detail.encounterId,
|
||||||
executeTime: normalizeExecuteTime(detail.executeTime),
|
executeTime: detail.executeTime || null,
|
||||||
applicationType: detail.applicationType ?? 0
|
applicationType: detail.applicationType ?? 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -89,14 +89,8 @@ const getList = async () => {
|
|||||||
const response = await listPendingEmr(queryParams)
|
const response = await listPendingEmr(queryParams)
|
||||||
// 根据后端返回的数据结构调整
|
// 根据后端返回的数据结构调整
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
const data = response.data
|
emrList.value = response.data || []
|
||||||
if (data && data.rows !== undefined) {
|
total.value = Array.isArray(response.data) ? response.data.length : 0
|
||||||
emrList.value = data.rows || []
|
|
||||||
total.value = data.total || 0
|
|
||||||
} else {
|
|
||||||
emrList.value = Array.isArray(data) ? data : []
|
|
||||||
total.value = emrList.value.length
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(response.msg || '获取待写病历列表失败')
|
ElMessage.error(response.msg || '获取待写病历列表失败')
|
||||||
emrList.value = []
|
emrList.value = []
|
||||||
|
|||||||
@@ -18,12 +18,16 @@
|
|||||||
<!-- <el-table-column label="组套类型" align="center" prop="typeEnum_enumText" /> -->
|
<!-- <el-table-column label="组套类型" align="center" prop="typeEnum_enumText" /> -->
|
||||||
<el-table-column label="单次剂量" align="center" prop="rangeCode_dictText">
|
<el-table-column label="单次剂量" align="center" prop="rangeCode_dictText">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatHistoryDose(scope.row) }}
|
{{
|
||||||
|
scope.row.dose
|
||||||
|
? formatNumber(scope.row.dose) + ' ' + scope.row.doseUnitCode_dictText
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="总量" align="center" prop="rangeCode_dictText">
|
<el-table-column label="总量" align="center" prop="rangeCode_dictText">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatHistoryTotalQuantity(scope.row) }}
|
{{ scope.row.quantity ? scope.row.quantity + ' ' + scope.row.unitCode_dictText : '' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="频次/用法" align="center" prop="rangeCode_dictText" width="200">
|
<el-table-column label="频次/用法" align="center" prop="rangeCode_dictText" width="200">
|
||||||
@@ -86,31 +90,6 @@ const queryParams = ref({
|
|||||||
typeEnum: 1,
|
typeEnum: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
function formatHistoryTotalQuantity(row) {
|
|
||||||
if (!row || row.quantity == null || row.quantity === '') return '';
|
|
||||||
const fromDict = row.unitCode_dictText ?? row.minUnitCode_dictText ?? row.unitCodeName;
|
|
||||||
let u =
|
|
||||||
fromDict != null && String(fromDict).trim() !== ''
|
|
||||||
&& String(fromDict).toLowerCase() !== 'null'
|
|
||||||
? String(fromDict).trim()
|
|
||||||
: '';
|
|
||||||
if (!u) {
|
|
||||||
const t = Number(row.adviceType);
|
|
||||||
if (t === 3 || t === 6 || t === 23 || t === 5) u = '次';
|
|
||||||
else if (t === 4) u = '个';
|
|
||||||
}
|
|
||||||
return u ? `${row.quantity} ${u}` : String(row.quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatHistoryDose(row) {
|
|
||||||
if (!row?.dose) return '';
|
|
||||||
const du = row.doseUnitCode_dictText;
|
|
||||||
if (du != null && String(du).trim() !== '' && String(du).toLowerCase() !== 'null') {
|
|
||||||
return formatNumber(row.dose) + ' ' + du;
|
|
||||||
}
|
|
||||||
return formatNumber(row.dose);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOpen() {
|
function handleOpen() {
|
||||||
drawer.value = true;
|
drawer.value = true;
|
||||||
getList();
|
getList();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<span>{{ index + 1 + '. ' }}</span>
|
<span>{{ index + 1 + '. ' }}</span>
|
||||||
<span>{{ medItem.adviceName }}</span>
|
<span>{{ medItem.adviceName }}</span>
|
||||||
<span>{{ '(' + medItem.volume + ')' }}</span>
|
<span>{{ '(' + medItem.volume + ')' }}</span>
|
||||||
<span>{{ formatPrintLineQuantity(medItem) }}</span>
|
<span>{{ medItem.quantity + ' ' + medItem.unitCode_dictText }}</span>
|
||||||
<span>{{ '批次号:' + medItem.lotNumber }}</span>
|
<span>{{ '批次号:' + medItem.lotNumber }}</span>
|
||||||
<div>
|
<div>
|
||||||
<span>用法用量:</span>
|
<span>用法用量:</span>
|
||||||
@@ -161,22 +161,6 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
//合计
|
//合计
|
||||||
function formatPrintLineQuantity(row) {
|
|
||||||
if (row == null || row.quantity == null || row.quantity === '') return '';
|
|
||||||
const fromDict = row.unitCode_dictText ?? row.minUnitCode_dictText ?? row.unitCodeName;
|
|
||||||
let u =
|
|
||||||
fromDict != null && String(fromDict).trim() !== ''
|
|
||||||
&& String(fromDict).toLowerCase() !== 'null'
|
|
||||||
? String(fromDict).trim()
|
|
||||||
: '';
|
|
||||||
if (!u) {
|
|
||||||
const t = Number(row.adviceType);
|
|
||||||
if (t === 3 || t === 6 || t === 23 || t === 5) u = '次';
|
|
||||||
else if (t === 4) u = '个';
|
|
||||||
}
|
|
||||||
return u ? `${row.quantity} ${u}` : String(row.quantity);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTotalPrice(item) {
|
function getTotalPrice(item) {
|
||||||
let totalPrice = new Decimal(0);
|
let totalPrice = new Decimal(0);
|
||||||
item.prescriptionInfoDetailList.forEach((medItem) => {
|
item.prescriptionInfoDetailList.forEach((medItem) => {
|
||||||
|
|||||||
@@ -686,15 +686,7 @@
|
|||||||
<span style="margin-left: 4px">{{ scope.row.doseUnitCode_dictText }}</span>
|
<span style="margin-left: 4px">{{ scope.row.doseUnitCode_dictText }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{
|
{{ scope.row.dose ? scope.row.dose + ' ' + scope.row.doseUnitCode_dictText : '' }}
|
||||||
scope.row.dose
|
|
||||||
? scope.row.dose +
|
|
||||||
(scope.row.doseUnitCode_dictText &&
|
|
||||||
String(scope.row.doseUnitCode_dictText).toLowerCase() !== 'null'
|
|
||||||
? ' ' + scope.row.doseUnitCode_dictText
|
|
||||||
: '')
|
|
||||||
: ''
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -711,10 +703,10 @@
|
|||||||
@change="calculateTotalPrice(scope.row, scope.$index)"
|
@change="calculateTotalPrice(scope.row, scope.$index)"
|
||||||
@input="calculateTotalPrice(scope.row, scope.$index)"
|
@input="calculateTotalPrice(scope.row, scope.$index)"
|
||||||
/>
|
/>
|
||||||
<span style="margin-left: 4px">{{ resolveTotalQuantityUnit(scope.row) }}</span>
|
<span style="margin-left: 4px">{{ scope.row.unitCode_dictText }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ formatTotalQuantityWithUnit(scope.row) }}
|
{{ scope.row.quantity ? scope.row.quantity + ' ' + scope.row.unitCode_dictText : '' }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -925,39 +917,6 @@ const unitMap = ref({
|
|||||||
minUnit: 'minUnit',
|
minUnit: 'minUnit',
|
||||||
unit: 'unit',
|
unit: 'unit',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 解析总量单位文案(无字典时:诊疗/手术/检查默认「次」,耗材默认「个」) */
|
|
||||||
const resolveTotalQuantityUnit = (row) => {
|
|
||||||
if (row == null) return '';
|
|
||||||
const fromDict =
|
|
||||||
row.unitCode_dictText ?? row.minUnitCode_dictText ?? row.unitCodeName;
|
|
||||||
let unitStr =
|
|
||||||
fromDict != null && String(fromDict).trim() !== ''
|
|
||||||
&& String(fromDict).toLowerCase() !== 'null'
|
|
||||||
? String(fromDict).trim()
|
|
||||||
: '';
|
|
||||||
if (!unitStr) {
|
|
||||||
const t = Number(row.adviceType);
|
|
||||||
// drord_doctor_type: 3=诊疗 4=耗材 5=会诊 6=手术;23=检查(特殊)
|
|
||||||
// 注意:2=中成药(药品),不可用「次」作为默认单位
|
|
||||||
if (t === 3 || t === 6 || t === 23 || t === 5) {
|
|
||||||
unitStr = '次';
|
|
||||||
} else if (t === 4) {
|
|
||||||
unitStr = '个';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unitStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 总量列展示:避免 unitCode_dictText 为空时显示「1 null」 */
|
|
||||||
const formatTotalQuantityWithUnit = (row) => {
|
|
||||||
if (row == null) return '';
|
|
||||||
const q = row.quantity;
|
|
||||||
if (q === undefined || q === null || q === '') return '';
|
|
||||||
const unitStr = resolveTotalQuantityUnit(row);
|
|
||||||
return unitStr ? `${q} ${unitStr}` : String(q);
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonDisabled = computed(() => {
|
const buttonDisabled = computed(() => {
|
||||||
return !props.patientInfo;
|
return !props.patientInfo;
|
||||||
});
|
});
|
||||||
@@ -2755,8 +2714,7 @@ function handleEmrTreatment() {
|
|||||||
treatment += '诊疗[' + (index + 1) + ']' + ' ';
|
treatment += '诊疗[' + (index + 1) + ']' + ' ';
|
||||||
treatment += item.adviceName + ' ';
|
treatment += item.adviceName + ' ';
|
||||||
if (item.quantity) {
|
if (item.quantity) {
|
||||||
const u = resolveTotalQuantityUnit(item);
|
treatment += '数量:' + item.quantity + item.unitCode_dictText + ' ';
|
||||||
treatment += '数量:' + item.quantity + (u ? ' ' + u : '') + ' ';
|
|
||||||
}
|
}
|
||||||
treatment += '频次:' + item.rateCode_dictText + ' ';
|
treatment += '频次:' + item.rateCode_dictText + ' ';
|
||||||
if (item.methodCode_dictText) {
|
if (item.methodCode_dictText) {
|
||||||
@@ -3843,8 +3801,6 @@ function handleSaveHistory(value) {
|
|||||||
uniqueKey: undefined,
|
uniqueKey: undefined,
|
||||||
dbOpType: value.requestId ? '2' : '1',
|
dbOpType: value.requestId ? '2' : '1',
|
||||||
minUnitQuantity: value.quantity * value.partPercent,
|
minUnitQuantity: value.quantity * value.partPercent,
|
||||||
// 🔧 修复:确保 categoryEnum 被传递(耗材必填字段),避免后端 NPE
|
|
||||||
categoryEnum: value.categoryEnum || value.categoryCode,
|
|
||||||
conditionId: conditionId.value,
|
conditionId: conditionId.value,
|
||||||
conditionDefinitionId: conditionDefinitionId.value,
|
conditionDefinitionId: conditionDefinitionId.value,
|
||||||
encounterDiagnosisId: encounterDiagnosisId.value,
|
encounterDiagnosisId: encounterDiagnosisId.value,
|
||||||
|
|||||||
@@ -856,7 +856,6 @@ function handleDelete(row) {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
getList()
|
getList()
|
||||||
proxy.$modal.msgSuccess('删除成功')
|
proxy.$modal.msgSuccess('删除成功')
|
||||||
emit('saved') // 通知父组件刷新医嘱列表
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('删除手术失败:', error)
|
console.error('删除手术失败:', error)
|
||||||
proxy.$modal.msgError('删除失败')
|
proxy.$modal.msgError('删除失败')
|
||||||
@@ -868,7 +867,6 @@ function handleDelete(row) {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
getList()
|
getList()
|
||||||
proxy.$modal.msgSuccess('手术已取消')
|
proxy.$modal.msgSuccess('手术已取消')
|
||||||
emit('saved') // 通知父组件刷新医嘱列表
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('取消手术失败:', error)
|
console.error('取消手术失败:', error)
|
||||||
proxy.$modal.msgError('取消失败')
|
proxy.$modal.msgError('取消失败')
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user