Compare commits

..

16 Commits

Author SHA1 Message Date
3310a1de9e Fix Bug #559: 组套添加医嘱后新增医嘱置顶 — 根因:handleSaveGroup 只做了 unshift 但未显式排序,导致已有 requestTime 的旧数据与无 requestTime 的新数据混排时顺序不稳定;修复:unshift 后增加按 requestTime 降序排序,确保无 requestTime 的新增组套医嘱始终显示在列表最上方
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-20 12:06:26 +08:00
bbeddc3077 Fix Bug #559: 根因+修复方案摘要 2026-05-20 11:07:08 +08:00
Ranyunqiao
3f4ada958c bug 555 558 2026-05-20 11:07:08 +08:00
377bd55c03 Fix Bug #559: 根因+修复方案摘要 2026-05-20 11:02:27 +08:00
30526ee4fc Fix Bug #547: 执行科室配置保存时时间冲突检测范围错误 — 根因:addOrEditOrgLoc 方法使用 getOrgLocListByActivityDefinitionId 跨科室查询同一诊疗的所有配置,导致不同科室间的正常时间重叠被误判为冲突;修复:改为 getOrgLocListByOrgIdAndActivityDefinitionId(orgId, activityDefId) 限定同科室范围;同时优化软删除科室处理,当冲突记录关联的科室已被删除时,使用"科室[ID]已删除"替代静默跳过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 11:02:27 +08:00
fa0cd2b41d Fix Bug #559: 住院医生站-临床医嘱 组套功能添加医嘱后新增医嘱置顶显示
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:15:25 +08:00
1f7aed4a8e Fix Bug #556: 就诊卡号改用busNo映射、执行时间默认当前时间、套餐标识增加packageName联合判断
根因:
1. medicalrecordNumber 绑定到 identifierNo(身份证号)而非 busNo(就诊卡号),导致字段为空
2. executeTime 初始化为 null 且未在 initData/resetForm 中设置默认值
3. loadApplicationToForm 中 isPackage 判断仅用 feePackageId != null,缺少 packageName 联合判断,
   导致 feePackageId 非空但非套餐的项目被误标为"套餐"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 10:10:12 +08:00
9f0e69177c Fix Bug #557: 根因+修复方案摘要 2026-05-20 10:10:12 +08:00
wangjian963
e5b85c733d 549【住院医生站-临床医嘱-检验】打开“检验申请单”弹窗获取项目列表响应极其缓慢
546 【患者管理】-【患者列表】-【新增患者】,新增患者,保存成功,但没有数据
536 [门诊手术安排]“手术申请查询”弹窗底部,分页组件与界底部元素重叠,影响操作。
2026-05-20 10:10:12 +08:00
75970f07b1 Fix Bug #557: ApplicationConfig 全局 Jackson LocalDateTime 反序列化器缺失 — 根因:JavaTimeModule 仅注册了 LocalDateTimeSerializer,未注册 LocalDateTimeDeserializer,导致默认反序列化器期望 ISO-8601 格式(T 分隔符),与前端 el-date-picker 空格分隔格式(YYYY-MM-DD HH:mm:ss)不匹配;修复:新增 LocalDateTimeDeserializer(pattern="yyyy-MM-dd HH:mm:ss")
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:42:13 +08:00
103121d832 Fix Bug #556: 根因+修复方案摘要 2026-05-20 09:42:13 +08:00
c39f0175bb Merge branch 'zhaoyun' of http://192.168.110.253:3000/wangyizhe/his into zhaoyun
# Conflicts:
#	openhis-server-new/openhis-domain/src/main/java/com/openhis/surgicalschedule/domain/OpSchedule.java
2026-05-20 09:40:03 +08:00
1767710754 Fix Bug #556: 根因+修复方案摘要
根因:
1. 就诊卡号字段映射错误:三处 (initData/resetForm/handleSave) 使用 identifierNo(身份证号)
   而非 visitNo/busNo(就诊卡号),参照 examinationApplication.vue 第1167行注释确认
2. 执行时间未设置默认值:initData 和 resetForm 中 executeTime 初始化为 null
3. 套餐判断条件过松:loadApplicationToForm 中 feePackageId != null 即判定为套餐,
   普通项目因 feePackageId 有值被误标

修复:
1. 三处 medicalrecordNumber 改为 props.patientInfo.visitNo || props.patientInfo.busNo
2. initData 和 resetForm 中 executeTime 默认填充 formatDateTime(new Date())
3. isPackage 增加 packageName 联合判断,与 loadCategoryItems 逻辑一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:39:11 +08:00
ca8b547062 Fix Bug #557: 根因+修复方案摘要
根因:OpSchedule.java 中 6 个时间字段的 @JsonFormat 使用 ISO 格式
(yyyy-MM-dd'T'HH:mm:ss),而前端 el-date-picker 通过 value-format 发送的
是空格分隔格式 (yyyy-MM-dd HH:mm:ss),导致编辑手术安排时 Jackson 反序列化
失败,抛出日期格式解析错误。

修复:将 admissionTime、entryTime、startTime、endTime、anesStart、anesEnd
共6个字段 @JsonFormat 格式从 'T' 分隔改为空格分隔,与 OpCreateScheduleDto
及前端 value-format 保持一致。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:35:28 +08:00
7fa4871977 Fix Bug #556: 修复结果记录
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:25:04 +08:00
a7b09a0248 Fix Bug #556: 就诊卡号/执行时间自动回显 + 去除冗余"套餐"文字
- 执行时间默认填充: initData() 和 resetForm() 中设置 executeTime = formatDateTime(new Date())
- isPackage判定统一: loadApplicationToForm() 中的 isPackage 判断与 loadCategoryItems() 保持一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:24:42 +08:00
8 changed files with 87 additions and 85 deletions

View File

@@ -25,3 +25,11 @@
1. `initData()`: Add `formData.executeTime = formatDateTime(new Date())` after line 899 1. `initData()`: Add `formData.executeTime = formatDateTime(new Date())` after line 899
2. `resetForm()`: Change `executeTime: null` to `executeTime: formatDateTime(new Date())` at line 1550 2. `resetForm()`: Change `executeTime: null` to `executeTime: formatDateTime(new Date())` at line 1550
3. `loadApplicationToForm()`: Fix `isPackage` logic at line 2000 3. `loadApplicationToForm()`: Fix `isPackage` logic at line 2000
修复结果:✅ 成功5行改动
### 修改内容
1. `initData()` (line ~898): 新增 `formData.executeTime = formatDateTime(new Date())` — 新增检验申请单时执行时间自动填充当前时间
2. `resetForm()` (line ~1552): `executeTime: null``executeTime: formatDateTime(new Date())` — 重置表单/新增时执行时间默认当前时间
3. `loadApplicationToForm()` (line ~2002): `isPackage` 判定从 `item.feePackageId != null || item.itemName?.includes('套餐')` 改为 `item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName` — 与 `loadCategoryItems()` 保持一致,只有真正的套餐项目才显示"套餐"标签

View File

@@ -1,44 +0,0 @@
# Bug #444 分析报告
## Bug 描述
【手术管理-门诊手术安排】生成临时医嘱界面,"已引用计费药品"列表未正常显示药品详细名称信息,且错误地带出了非药品类的计费信息(如手术诊疗项目"小腿烧伤扩创交腿皮瓣修复术"、检查项目"心脏彩色多普勒超声")。
## 根因分析
### 数据流
1. 用户点击"医嘱"按钮 → `handleMedicalAdvice()` → 调用 `getPrescriptionList()` 获取计费数据
2. 用户对数据进行过滤后展示在"已引用计费药品"列表
3. 用户点击"引用计费"按钮 → `handleQuoteBilling()` → 再次调用 `getPrescriptionList()` 获取最新计费数据
### 根因定位
**`handleQuoteBilling()` 方法index.vue:1866-1877缺少非药品关键词过滤逻辑。**
`handleMedicalAdvice()` 中有两层过滤:
1. `adviceType !== 1` 过滤(只保留药品类型)
2. **关键词排除过滤**(排除名称中包含"术"、"超声"、"检查"等非药品关键词的项目)
`handleQuoteBilling()` 中只有第一层过滤(`adviceType !== 1`**缺少关键词排除过滤**。
当后端返回的计费数据中某些非药品项目被错误标注为 `adviceType=1` 时:
- `handleMedicalAdvice()` 能通过关键词过滤排除这些项目
- `handleQuoteBilling()` 无法排除,导致非药品项目出现在"已引用计费药品"列表中
### 代码对比
| 过滤条件 | handleMedicalAdvice (L1576-1597) | handleQuoteBilling (L1866-1877) |
|---------|:---:|:---:|
| encounterId 匹配 | ✓ | ✓ |
| adviceType === 1 | ✓ | ✓ |
| 名称非空 | ✓ | ✓ |
| **关键词排除** | ✓ **有** | ✗ **缺失** |
| requestId 过滤 | ✓ | ✓ |
## 修复方案
`handleQuoteBilling()` 方法的过滤逻辑中,添加与 `handleMedicalAdvice()` 一致的关键词排除逻辑:
```javascript
// 🔧 修复 Bug #444: 排除名称中包含手术/检查/诊疗关键词的非药品项目
const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影'];
if (excludedKeywords.some(kw => medicineName.includes(kw))) return false;
```

View File

@@ -5,39 +5,37 @@
## 根因定位 ## 根因定位
**核心问题在 `OrganizationLocationAppServiceImpl.java:161-162`** **核心问题在 `OrganizationLocationAppServiceImpl.java:161-174`**
时间冲突检测使用了 `getOrgLocListByActivityDefinitionId()` 跨科室查询同一诊疗定义下的**所有**科室配置记录。 时间冲突检测的查询逻辑存在两个缺陷:
### 缺陷:跨科室误报冲突
### 缺陷1查询范围过窄
```java ```java
// 修复前:查询同一诊疗的所有科室配置(跨科室) // 只查同一科室 + 同一诊疗的记录
organizationLocationService.getOrgLocListByActivityDefinitionId(orgLoc.getActivityDefinitionId()); getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(), orgLoc.getActivityDefinitionId());
``` ```
只查询**同一科室**的记录。如果同一诊疗项目在其他科室已有配置且时间重叠,不会被当前查询检测到。但系统本应阻止同一诊疗在多个科室同时段执行。
该查询返回**所有科室**中同一诊疗项目的配置记录。当其他科室(非当前操作科室)已配置了相同诊疗且时间重叠时,会被误判为冲突。 ### 缺陷2"未知科室"错误提示
"执行科室配置"的业务语义是:为某个科室配置它可执行的诊疗项目及时段。不同科室配置同一诊疗的不同时段是完全合理的(如检验科 08:00-12:00放射科 14:00-18:00。跨科室时间重叠不应视为冲突。
### 附带缺陷:"未知科室"错误提示
当冲突记录关联的科室被软删除(`delete_flag='1'`)时,`organizationService.getById()``@TableLogic` 注解影响查不到该科室,返回 null错误提示变成"与未知科室时间冲突"。 当冲突记录关联的科室被软删除(`delete_flag='1'`)时,`organizationService.getById()``@TableLogic` 注解影响查不到该科室,返回 null错误提示变成"与未知科室时间冲突"。
数据库验证发现确实存在软删除科室的组织位置记录内科门诊、上海学校医院、信息科等共9条
### 数据流
1. 前端选择科室 → 点击"添加新项目" → 填写诊疗和时间 → 点击"保存"
2. 后端 `addOrEditOrgLoc()` 接收请求
3. 查询现有冲突记录(**当前只查同科室**
4. 对冲突记录检查时间重叠
5. 查找冲突科室名称 → 若科室被软删除则返回 null → "未知科室"
## 修复方案 ## 修复方案
**修改冲突检测范围**`getOrgLocListByActivityDefinitionId` 改为 `getOrgLocListByOrgIdAndActivityDefinitionId`,仅检测**同一科室内**的时间冲突。 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`
**改动行数**: +3/-1`OrganizationLocationAppServiceImpl.java`
**变更内容**:
```diff
-organizationLocationService.getOrgLocListByActivityDefinitionId(orgLoc.getActivityDefinitionId());
+organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(),
+ orgLoc.getActivityDefinitionId());
```
编译验证通过。

36
bug556-analysis.md Normal file
View File

@@ -0,0 +1,36 @@
# Bug #556 分析报告
## Bug 描述
【门诊医生站-检验】新增检验申请单时就诊卡号/执行时间未自动回显,且项目列表冗余显示"套餐"文字
## 根因分析
### 问题1就诊卡号字段为空
**根因**`formData.medicalrecordNumber` 绑定到前端"就诊卡号"字段,但数据来源映射错误。
- `initData()` 第886行`formData.medicalrecordNumber = props.patientInfo.identifierNo || ''`
- `resetForm()` 第1526行`medicalrecordNumber: props.patientInfo.identifierNo || ''`
`identifierNo` 是身份证号/标识号字段,通常为空。实际的就诊卡号/业务编号是 `props.patientInfo.busNo`。代码中 `formData.visitNo` 已经正确映射了 `busNo`,但 `medicalrecordNumber` 映射到了错误的字段。
### 问题2执行时间未自动填充
**根因**`formData.executeTime` 初始值为 `null`第978行`initData()``resetForm()` 中均未赋予默认值。
- 对比:`applyTime``startApplyTimeTimer()` 实时更新定时器第1489行`executeTime` 没有类似初始化。
- 用户期望:新增申请单时执行时间应默认填充当前系统时间。
### 问题3项目列表冗余显示"套餐"文字
**根因**`loadApplicationToForm()` 第2000行的 `isPackage` 判断条件过于宽松。
- 第2000行`const isPackage = item.feePackageId != null || item.itemName?.includes('套餐')`
- 对比:`loadCategoryItems()` 第1190行已经做了正确修复`item.feePackageId != null && ... && item.packageName`
- 差异第2000行只用 `feePackageId != null` 判断,缺少 `packageName` 联合判断。如果后端返回的非套餐项目 `feePackageId` 有值但 `packageName` 为空,仍会被误标为套餐。
## 修复方案
### 修复1就诊卡号映射
`medicalrecordNumber` 的数据源从 `props.patientInfo.identifierNo` 改为 `props.patientInfo.busNo`
涉及位置:`initData()` 第886行、`resetForm()` 第1526行
### 修复2执行时间默认值
`initData()``resetForm()` 中,将 `executeTime` 设置为当前时间。使用 `formatDateTime(new Date())` 格式化为 `YYYY-MM-DD HH:mm:ss`
### 修复3套餐标识判断
`loadApplicationToForm()` 第2000行的 `isPackage` 判断条件与 `loadCategoryItems()` 第1190行保持一致增加 `item.packageName` 联合判断。

View File

@@ -158,10 +158,8 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
? activityDefinitionMapper.selectById(activityDefinitionId) : null; ? activityDefinitionMapper.selectById(activityDefinitionId) : null;
String activityName = activityDef != null ? activityDef.getName() : ""; String activityName = activityDef != null ? activityDef.getName() : "";
// Only check for time conflicts within the same department
List<OrganizationLocation> organizationLocationList = List<OrganizationLocation> organizationLocationList =
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getOrganizationId(), organizationLocationService.getOrgLocListByActivityDefinitionId(orgLoc.getActivityDefinitionId());
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;

View File

@@ -883,7 +883,7 @@ const initData = async () => {
formData.visitNo = props.patientInfo.busNo || '' formData.visitNo = props.patientInfo.busNo || ''
formData.patientId = props.patientInfo.patientId || '' formData.patientId = props.patientInfo.patientId || ''
formData.patientName = props.patientInfo.patientName || '' formData.patientName = props.patientInfo.patientName || ''
formData.medicalrecordNumber = props.patientInfo.identifierNo || '' formData.medicalrecordNumber = props.patientInfo.visitNo || props.patientInfo.busNo || ''
formData.applyDepartment = props.patientInfo.organizationName || '' formData.applyDepartment = props.patientInfo.organizationName || ''
formData.applyDocName = userNickName.value || userName.value || '' formData.applyDocName = userNickName.value || userName.value || ''
formData.applyDocCode = userId.value || '' formData.applyDocCode = userId.value || ''
@@ -895,6 +895,8 @@ const initData = async () => {
// 申请单号在保存时由后端生成,此处显示"自动生成" // 申请单号在保存时由后端生成,此处显示"自动生成"
formData.applyNo = '自动生成' formData.applyNo = '自动生成'
// 执行时间默认填充当前系统时间
formData.executeTime = formatDateTime(new Date())
// 申请日期实时更新(启动定时器) // 申请日期实时更新(启动定时器)
startApplyTimeTimer() startApplyTimeTimer()
@@ -1526,7 +1528,7 @@ const resetForm = async () => {
applicationId: null, applicationId: null,
applyOrganizationId: props.patientInfo.orgId || '', applyOrganizationId: props.patientInfo.orgId || '',
patientName: props.patientInfo.patientName || '', patientName: props.patientInfo.patientName || '',
medicalrecordNumber: props.patientInfo.identifierNo || '', medicalrecordNumber: props.patientInfo.visitNo || props.patientInfo.busNo || '',
natureofCost: 'self', natureofCost: 'self',
applyTime: '', // 申请日期由定时器实时更新 applyTime: '', // 申请日期由定时器实时更新
applyDepartment: props.patientInfo.organizationName || '', applyDepartment: props.patientInfo.organizationName || '',
@@ -1550,7 +1552,7 @@ const resetForm = async () => {
visitNo: '', visitNo: '',
specimenName: '血液', specimenName: '血液',
encounterId: props.patientInfo.encounterId || '', encounterId: props.patientInfo.encounterId || '',
executeTime: null, executeTime: formatDateTime(new Date()),
applicationType: 0, applicationType: 0,
}) })
selectedInspectionItems.value = [] selectedInspectionItems.value = []
@@ -1600,7 +1602,7 @@ const handleSave = () => {
// 修复【#406】保存前尝试从 props 同步患者信息,避免因加载时序导致信息缺失 // 修复【#406】保存前尝试从 props 同步患者信息,避免因加载时序导致信息缺失
if ((!formData.patientName?.trim() || !formData.medicalrecordNumber?.trim()) && props.patientInfo && props.patientInfo.encounterId) { if ((!formData.patientName?.trim() || !formData.medicalrecordNumber?.trim()) && props.patientInfo && props.patientInfo.encounterId) {
formData.patientName = props.patientInfo.patientName || '' formData.patientName = props.patientInfo.patientName || ''
formData.medicalrecordNumber = props.patientInfo.identifierNo || '' formData.medicalrecordNumber = props.patientInfo.visitNo || props.patientInfo.busNo || ''
formData.encounterId = props.patientInfo.encounterId || '' formData.encounterId = props.patientInfo.encounterId || ''
formData.visitNo = props.patientInfo.busNo || '' formData.visitNo = props.patientInfo.busNo || ''
formData.patientId = props.patientInfo.patientId || '' formData.patientId = props.patientInfo.patientId || ''
@@ -2000,7 +2002,7 @@ const loadApplicationToForm = async (row) => {
// Bug #387修复: 套餐项目默认展开,并自动加载明细 // Bug #387修复: 套餐项目默认展开,并自动加载明细
selectedInspectionItems.value = detail.labApplyItemList.map(item => { selectedInspectionItems.value = detail.labApplyItemList.map(item => {
const itemId = item.activityId || item.itemId || item.id || Math.random().toString(36).substring(2, 11) const itemId = item.activityId || item.itemId || item.id || Math.random().toString(36).substring(2, 11)
const isPackage = item.feePackageId != null || item.itemName?.includes('套餐') const isPackage = item.feePackageId != null && item.feePackageId !== '' && item.feePackageId !== 'null' && item.packageName
return { return {
itemId: itemId, itemId: itemId,

View File

@@ -1671,6 +1671,13 @@ function handleSaveGroup(orderGroupList) {
if (newRows.length > 0) { if (newRows.length > 0) {
prescriptionList.value.splice(originalLength); // 移除循环中追加到末尾的临时行 prescriptionList.value.splice(originalLength); // 移除循环中追加到末尾的临时行
prescriptionList.value.unshift(...newRows); prescriptionList.value.unshift(...newRows);
// 排序:确保没有 requestTime 的新行始终排在最前面
prescriptionList.value.sort((a, b) => {
if (!a.requestTime && !b.requestTime) return 0;
if (!a.requestTime) return -1;
if (!b.requestTime) return 1;
return new Date(b.requestTime) - new Date(a.requestTime);
});
proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`); proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`);
} }
} }

View File

@@ -1818,14 +1818,11 @@ function handleQuoteBilling() {
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 🔧 修复 Bug #444: 统一过滤逻辑,与 handleMedicalAdvice 保持一致 // 🔧 修复 Bug #445: 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6)
// 1. 使用 Number() + snake_case 回退,避免类型转换导致过滤失效 // 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
// 2. 增加关键词二次过滤,排除手术/检查/诊疗等非药品项目 // 先提取已签发项目(statusEnum=2)填充已生成列表
const filteredItems = res.data.filter(item => { const activeItems = res.data.filter(item => {
// 匹配 encounterId
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false; if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6)
// 🔧 修复 Bug #444: 使用 Number() 显式转换,增加 snake_case 回退
const at = Number(item.adviceType ?? item.advice_type); const at = Number(item.adviceType ?? item.advice_type);
if (at !== 1 && at !== 2) return false; if (at !== 1 && at !== 2) return false;
if (item.statusEnum !== 2) return false; if (item.statusEnum !== 2) return false;