35 Commits

Author SHA1 Message Date
257ea42db7 fix(hospitalization): 优化住院登记表单的默认值设置逻辑 bug#178
- 引入watch监听诊断类别字典变化,动态设置默认值
- 移除硬编码的medTypeCode初始值'21',改为从字典动态获取
- 修复科室选择逻辑,支持当前医生科室不在住院科室列表时的显示
- 为诊断类别添加验证逻辑,确保主诊断的medTypeCode在字典选项中
- 解决已选科室不在过滤列表中时无法正确显示的问题
- 添加科室树形结构递归查找功能,支持临时添加医生科室到选项列表
2026-03-17 19:22:07 +08:00
872a5308ef Merge remote-tracking branch 'origin/develop' into develop 2026-03-17 17:29:22 +08:00
32f7077fc1 fix(datadictionary): 修复查询条件中的字段别名问题 BUG#183
- 修正DiagTreatMAppServiceImpl中searchKey字段添加表别名T1前缀
- 移除ActivityDefinitionManageMapper.xml中不必要的字段名替换逻辑
- 确保查询条件正确使用表别名避免字段冲突
- 保持租户ID和状态枚举的别名替换功能正常工作
2026-03-17 17:28:56 +08:00
4f917b0121 199 检验项目设置-》检验项目:费用套餐字段下拉选项没有取值于套餐管理的数据 2026-03-17 13:55:21 +08:00
8a7d2abb4a feat(medicalOrderSet): 优化医嘱组套功能实现
- 实现医嘱基础列表的分页功能,添加loading状态和缓存机制
- 添加防抖处理和组织机构ID参数支持,优化性能表现
- 实现医嘱组套的完整编辑功能,包括增删改查操作界面
- 添加医嘱组套预览、应用和管理功能模块
- 实现西医组套筛选功能,确保tcmFlag参数正确传递
- 优化医嘱组套数据结构,完善明细项信息处理逻辑
- 添加表单验证和错误处理,提升用户体验和系统稳定性
- 重构代码结构,采用响应式设计提高可维护性
2026-03-17 09:57:21 +08:00
03939fb232 feat(basicmanage): 新增医嘱组套对话框组件和相关API
- 实现 MedicalOrderSetDialog.vue 组件,支持医嘱组套的新增、编辑功能
- 添加中药医嘱基础列表组件 tcmMedicineList.vue,支持键盘导航选择
- 创建医嘱组套相关API接口文件,包含个人、科室、全院组套的增删改查功能
- 实现医嘱组套的组合拆组功能,支持批量操作
- 集成分页、搜索、缓存等优化功能提升用户体验
- 添加表单验证和数据校验机制确保数据完整性
2026-03-17 09:35:07 +08:00
wangjian963
16f6fbb8cf 修改地区联级选择器,修改报告卡的样式。 2026-03-16 16:17:08 +08:00
28f85ceb05 revert 0bf29a53a4
revert 去除变片编号生成器,修改地区选择器,修改报告卡的样式。
2026-03-16 15:48:17 +08:00
wangjian963
b43688e483 Merge remote-tracking branch 'origin/develop' into develop 2026-03-16 14:40:39 +08:00
wangjian963
0bf29a53a4 去除变片编号生成器,修改地区选择器,修改报告卡的样式。 2026-03-16 14:40:35 +08:00
Ranyunqiao
0c70b224f9 185 检查项目设置-》检查方法:【编辑】检查类型已选中“心电图”【保存】报错“检查类型不能为空”
186
检查项目设置-》检查部位:曝光次数和费用套餐的值错位了
2026-03-16 11:49:57 +08:00
wangjian963
449209a79b 检验页面-临床诊断自动获取当前患者的主诊断。 2026-03-16 11:33:35 +08:00
Ranyunqiao
d6663c9667 184 检查项目设置-》检查类型:检查类型的字段下拉选项内容取值位置不对 2026-03-16 10:59:51 +08:00
Ranyunqiao
53d29cbe14 168 入科分配床位填写的住院医生、主治医生、责任护士字段的内容双击查看未显示。 2026-03-16 10:16:49 +08:00
liuliu
e16a70dd50 146 收费工作站-》门诊收费:门诊划价费用在门诊收费点击【确认收费】按钮报错。 2026-03-16 10:08:44 +08:00
Ranyunqiao
bba63d2f1b 147 门诊医生站》医嘱TAB页面:一次性静脉采血器为耗材系统且自动转成中成药类型,点击【保存】报错。 2026-03-16 10:05:55 +08:00
wangjian963
f3d011951b 78 增加门诊医生开立检验申请单--对搜索项目区实现懒加载,以及动态搜索。 2026-03-13 18:34:14 +08:00
7dc76d7b59 fix(advice): 修复禅道 Bug #147 - 添加耗材处理逻辑
- 在 saveRegAdvice 方法中添加耗材列表过滤
- 添加 handDevice 方法调用处理耗材请求
- 实现完整的 handDevice 方法处理耗材的保存、签发和删除操作
- 添加必要的导入:ActivityDefinition、DeviceRequest、IDeviceRequestService、IDeviceDispenseService
2026-03-13 12:10:24 +08:00
b2dec2667a fix(document): 修复文书定义树形列表查询逻辑
- 添加了对organizationId和useRanges参数的空值检查和日志警告
- 在SQL查询中增加了isValid字段过滤条件
- 添加了对primaryMenuEnum参数的条件查询支持
- 增加了详细的请求参数和查询结果日志记录
- 优化了参数传递的一致性,使用变量替代直接访问对象属性
2026-03-13 12:10:24 +08:00
HuangXinQuan
879d31b51d 165 药房管理-》门诊发药:字段内容显示问题 2026-03-13 11:20:23 +08:00
HuangXinQuan
473a5f7f06 149 门诊管理-》门诊输液查询不到患者已收费注射类的药品信息 2026-03-13 09:44:32 +08:00
2eec988c56 Merge remote-tracking branch 'origin/develop' into develop 2026-03-13 08:59:35 +08:00
8820048d55 feat(emr): 添加住院病历菜单类型枚举
- 新增 primaryMenuEnum 字段用于标识住院病历类型
- 设置默认值为 1 对应住院病历文档类型枚举
2026-03-13 08:59:10 +08:00
6af7720470 feat(diagnosis): 完善诊断模块功能并优化病历数据获取
- 添加isSaving状态控制保存过程
- 监听患者信息变化自动获取病历详情和诊断列表
- 增强getDetail方法添加错误处理和日志输出
- 重构handleAddDiagnosis方法分离验证逻辑到独立函数
- 优化病历详情获取接口同时查询门诊和住院病历数据
- 添加文档定义树形列表按使用范围筛选功能
- 修复历史病历数据加载错误处理机制
2026-03-12 23:21:34 +08:00
wangjian963
5f134945ab 1. 在ActivityDefinition实体类及相关DTO中添加inspectionTypeId字段
2. 新增检验类型分页查询接口及前端API调用
3. 优化检验申请模块的前后端交互逻辑
4.完成修改78 增加门诊医生开立检验申请单立检验申请单的检验项目写死的问题
5.对检验目录设置的查询,更新和保存进行修改完善。
6.对检验项目设置的页面使用vue3+elementui进行修改。
2026-03-12 18:58:54 +08:00
bc12cc1b08 fix(charge): 修复用法绑定耗材门诊收费未显示问题 (Bug #145) 2026-03-12 18:05:17 +08:00
17b8ea7192 fix(nurse-station): 修复住院护士站门户护理级别筛选功能失效问题 (Bug #172) 2026-03-12 17:38:31 +08:00
Ranyunqiao
2bfdd686c7 栈溢出 2026-03-12 17:31:50 +08:00
Ranyunqiao
066cfaba46 168 入科分配床位填写的住院医生、主治医生、责任护士字段的内容双击查看未显示。 2026-03-12 16:58:39 +08:00
e8850e85fc feat(transfer): add warehouse type support and lab specimen tables 2026-03-12 16:31:31 +08:00
d083a3123a fix: Bug #177 修复新增医嘱报错 - category_code 类型转换错误
问题原因:
SQL查询中尝试将 wor_activity_definition.category_code(中文值如'检验'、'检查')
直接转换为 INTEGER 类型,导致 PostgreSQL 类型转换错误。

修复方案:
使用 CASE WHEN 语句将中文 category_code 映射为对应的整数值:
- 检验 -> 1
- 检查 -> 2
- 护理 -> 3
- 手术 -> 4
- 其他 -> 5

这与 ActivityType 枚举定义保持一致。
2026-03-12 15:53:06 +08:00
96c1927f8d fix: Bug #177 门诊医生站耗材医嘱保存提示未匹配库存信息
问题原因:
1. 前端查询耗材列表时未设置 adviceTableName 字段
2. 后端库存校验时严格要求 adviceTableName 匹配,导致耗材无法匹配库存

修复方案:
1. 前端(adviceBaseList.vue): 添加 adviceTableName = 'adm_device_definition' 字段
2. 后端(AdviceUtils.java): 添加容错处理,当 adviceTableName 为空时跳过该项匹配

双保险策略确保问题彻底解决。
2026-03-12 15:44:03 +08:00
bdac9d0709 feat: 护理记录患者列表恢复按科室过滤 (Bug #175)
- 恢复orgId过滤,只显示当前用户科室的在院患者
- 配合后端SQL修复,从就诊表获取科室ID
- 李光明等同科室患者现在可以正常显示
2026-03-12 15:18:42 +08:00
8faba1ea21 fix: 护理记录患者列表科室ID从就诊表获取 (Bug #175)
- 将org_id来源从adm_patient改为adm_encounter
- adm_patient.organization_id通常为空
- adm_encounter.organization_id才是入院科室
- 修复按科室过滤时查不到患者的问题
2026-03-12 15:13:37 +08:00
dd9b77f6bb fix: 护理记录患者列表显示所有在院患者 (Bug #175)
- 移除orgId过滤,不再按科室限制患者显示
- 修复李光明等患者无法显示的问题
- 现在显示所有在院患者,不按科室/病区过滤
2026-03-12 14:57:27 +08:00
94 changed files with 7924 additions and 2734 deletions

View File

@@ -59,8 +59,10 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
return R.fail("检查方法的检查类型不能为空!");
}
//2.保存
boolean save = checkMethodService.save(checkMethod);
return R.ok(save);
checkMethodService.save(checkMethod);
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("id", checkMethod.getId());
return R.ok(result);
}
@Override

View File

@@ -193,9 +193,16 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
DiagnosisTreatmentSelParam.setYbType(null); // 临时移除防止HisQueryUtils处理
}
// 临时保存inspectionTypeId值手动添加带表别名的条件
Long inspectionTypeIdValue = null;
if (DiagnosisTreatmentSelParam != null && DiagnosisTreatmentSelParam.getInspectionTypeId() != null) {
inspectionTypeIdValue = DiagnosisTreatmentSelParam.getInspectionTypeId();
DiagnosisTreatmentSelParam.setInspectionTypeId(null); // 临时移除防止HisQueryUtils处理
}
// 构建查询条件
QueryWrapper<DiagnosisTreatmentDto> queryWrapper = HisQueryUtils.buildQueryWrapper(DiagnosisTreatmentSelParam,
searchKey, new HashSet<>(Arrays.asList("bus_no", "name", "py_str", "wb_str")), request);
searchKey, new HashSet<>(Arrays.asList("T1.bus_no", "T1.name", "T1.py_str", "T1.wb_str")), request);
// 如果需要按医保类型过滤如挂号费类型13添加带表别名的条件
if (ybTypeValue != null) {
@@ -204,6 +211,13 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
DiagnosisTreatmentSelParam.setYbType(ybTypeValue);
}
// 如果需要按检验类型过滤,添加带表别名的条件
if (inspectionTypeIdValue != null) {
queryWrapper.eq("T1.inspection_type_id", inspectionTypeIdValue);
// 恢复参数对象中的值
DiagnosisTreatmentSelParam.setInspectionTypeId(inspectionTypeIdValue);
}
// 分页查询
IPage<DiagnosisTreatmentDto> diseaseTreatmentPage
= activityDefinitionManageMapper.getDiseaseTreatmentPage(new Page<DiagnosisTreatmentDto>(pageNo, pageSize), queryWrapper);
@@ -336,6 +350,8 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
// 显式设置新增的字段
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
// 显式设置检验类型ID
activityDefinition.setInspectionTypeId(diagnosisTreatmentUpDto.getInspectionTypeId());
// 显式设置划价标记(避免前端字段/类型差异导致 copyProperties 后仍为默认值)
activityDefinition.setPricingFlag(diagnosisTreatmentUpDto.getPricingFlag());
@@ -483,6 +499,8 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
// 显式设置新增的字段
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
// 显式设置检验类型ID
activityDefinition.setInspectionTypeId(diagnosisTreatmentUpDto.getInspectionTypeId());
// 如果前端没有传入编码则使用10位数基础采番
if (StringUtils.isEmpty(activityDefinition.getBusNo())) {

View File

@@ -1,6 +1,9 @@
package com.openhis.web.datadictionary.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.openhis.lab.domain.InspectionType;
import com.openhis.lab.service.IInspectionTypeService;
import com.openhis.web.datadictionary.appservice.IDiagTreatMAppService;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentSelParam;
import com.openhis.web.datadictionary.dto.DiagnosisTreatmentUpDto;
@@ -30,6 +33,9 @@ public class DiagnosisTreatmentController {
@Resource
private IDiagTreatMAppService diagTreatMAppService;
@Resource
private IInspectionTypeService inspectionTypeService;
/**
* 诊疗目录初期查询
*
@@ -188,4 +194,17 @@ public class DiagnosisTreatmentController {
public R<?> updatePricingFlag(@RequestBody List<Long> ids, @RequestParam Integer pricingFlag) {
return diagTreatMAppService.updatePricingFlag(ids, pricingFlag);
}
/**
* 获取检验类型列表(用于下拉框)
*
* @return 检验类型列表
*/
@GetMapping("/inspection-types")
public R<?> getInspectionTypes() {
LambdaQueryWrapper<InspectionType> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(InspectionType::getValidFlag, 1)
.orderByAsc(InspectionType::getSortOrder);
return R.ok(inspectionTypeService.list(queryWrapper));
}
}

View File

@@ -130,4 +130,13 @@ public class DiagnosisTreatmentDto {
/** 服务范围 */
private String serviceRange;
/** 检验类型ID */
@Dict(dictTable = "inspection_type", dictCode = "id", dictText = "name", deleteFlag = "valid_flag")
@JsonSerialize(using = ToStringSerializer.class)
private Long inspectionTypeId;
private String inspectionTypeId_dictText;
/** 检验类型名称(用于前端 testType 字段) */
private String testType;
}

View File

@@ -30,4 +30,7 @@ public class DiagnosisTreatmentSelParam {
/** 状态 */
private Integer statusEnum;
/** 检验类型ID */
private Long inspectionTypeId;
}

View File

@@ -128,4 +128,8 @@ public class DiagnosisTreatmentUpDto {
/** 服务范围 */
private String serviceRange;
/** 检验类型ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long inspectionTypeId;
}

View File

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.core.common.core.redis.RedisCache;
import com.core.common.enums.TenantOptionDict;
import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil;
@@ -14,6 +15,8 @@ import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils;
import com.core.web.util.TenantOptionUtil;
import com.openhis.administration.domain.Account;
import com.openhis.administration.service.IAccountService;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants;
@@ -72,6 +75,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource
IChargeItemService iChargeItemService;
@Resource
IAccountService iAccountService;
// @Resource
// IOrganizationLocationService iOrganizationLocationService;
@Resource
@@ -94,6 +100,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
@Resource
DoctorStationSendApplyUtil doctorStationSendApplyUtil;
@Resource
RedisCache redisCache;
// 缓存 key 前缀
private static final String ADVICE_BASE_INFO_CACHE_PREFIX = "advice:base:info:";
// 缓存过期时间(小时)
private static final long CACHE_EXPIRE_HOURS = 24;
/**
* 查询医嘱信息
@@ -127,14 +141,40 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
String safePageNo = pageNo != null ? pageNo.toString() : "";
String safePageSize = pageSize != null ? pageSize.toString() : "";
log.info("从数据库查询医嘱基础信息");
// 设置默认科室:仅当前端/调用方未传 organizationId 时才回退到登录人科室
// 否则会导致门诊划价等场景(按患者挂号科室查询)返回空
if (organizationId == null) {
organizationId = SecurityUtils.getLoginUser().getOrgId();
}
// 只有在没有搜索关键字时才尝试使用缓存
boolean useCache = (searchKey == null || searchKey.trim().isEmpty())
&& (adviceDefinitionIdParamList == null || adviceDefinitionIdParamList.isEmpty());
String cacheKey = null;
if (useCache) {
// 生成缓存 key无搜索关键字时按科室缓存
cacheKey = ADVICE_BASE_INFO_CACHE_PREFIX + organizationId + ":" + safeAdviceTypesStr + ":" + safePageNo + ":" + safePageSize;
// 先清除可能存在的无效缓存JSONObject类型
if (redisCache.hasKey(cacheKey)) {
Object cachedObj = redisCache.getCacheObject(cacheKey);
if (cachedObj instanceof com.alibaba.fastjson2.JSONObject) {
redisCache.deleteObject(cacheKey);
log.info("清除无效缓存, key: {}", cacheKey);
} else if (cachedObj instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
log.info("从缓存获取医嘱基础信息, key: {}, records: {}", cacheKey, ((IPage<?>)cachedObj).getRecords().size());
return (IPage<AdviceBaseDto>) cachedObj;
}
}
log.info("缓存未命中,准备查询数据库, key: {}", cacheKey);
} else {
log.info("不使用缓存条件: searchKey={}, adviceDefinitionIdParamList={}", searchKey, adviceDefinitionIdParamList);
}
log.info("从数据库查询医嘱基础信息");
// 医嘱定价来源
String orderPricingSource = TenantOptionUtil.getOptionContent(TenantOptionDict.ORDER_PRICING_SOURCE);
if (StringUtils.isEmpty(orderPricingSource) && StringUtils.isEmpty(orderPricing)) {
@@ -155,7 +195,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList,
adviceTypes,
adviceTypes, searchKey,
queryWrapper);
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
@@ -385,6 +425,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
}
}
// 缓存结果(只有无搜索关键字时才缓存)
if (useCache && cacheKey != null && adviceBaseInfo != null) {
// 确保是 IPage 类型再缓存,避免缓存无效数据
if (adviceBaseInfo instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
redisCache.setCacheObject(cacheKey, adviceBaseInfo, (int) CACHE_EXPIRE_HOURS, java.util.concurrent.TimeUnit.HOURS);
log.info("缓存医嘱基础信息, key: {}, 过期时间: {} 小时", cacheKey, CACHE_EXPIRE_HOURS);
}
}
return adviceBaseInfo;
}
@@ -441,9 +490,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
}
// 🔧 Bug Fix: 跳过耗材的库存校验(耗材的库存校验逻辑不同)
List<AdviceSaveDto> needCheckList = adviceSaveList.stream()
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType()))
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())
&& !ItemType.DEVICE.getValue().equals(e.getAdviceType())) // 排除耗材
.collect(Collectors.toList());
// 校验库存
String tipRes = adviceUtils.checkInventory(needCheckList);
@@ -778,7 +829,27 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
chargeItem.setProductTable(adviceSaveDto.getAdviceTableName());// 产品所在表
chargeItem.setProductId(adviceSaveDto.getAdviceDefinitionId());// 收费项id
chargeItem.setAccountId(adviceSaveDto.getAccountId());// 关联账户ID
// 🔧 Bug Fix: 如果accountId为null从就诊中获取账户ID如果没有则自动创建
Long accountId = adviceSaveDto.getAccountId();
if (accountId == null) {
// 尝试从患者就诊中获取默认账户ID自费账户
Account selfAccount = iAccountService.getSelfAccount(adviceSaveDto.getEncounterId());
if (selfAccount != null) {
accountId = selfAccount.getId();
} else {
// 自动创建自费账户
Account newAccount = new Account();
newAccount.setPatientId(adviceSaveDto.getPatientId());
newAccount.setEncounterId(adviceSaveDto.getEncounterId());
newAccount.setContractNo(CommonConstants.BusinessName.DEFAULT_CONTRACT_NO);
newAccount.setTypeCode(AccountType.PERSONAL_CASH_ACCOUNT.getCode());
newAccount.setBalanceAmount(BigDecimal.ZERO);
newAccount.setStatusEnum(AccountStatus.ACTIVE.getValue());
newAccount.setEncounterFlag(Whether.YES.getValue());
accountId = iAccountService.saveAccountByRegister(newAccount);
}
}
chargeItem.setAccountId(accountId);// 关联账户ID
chargeItem.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
chargeItem.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
chargeItem.setDispenseId(dispenseId); // 发放ID

View File

@@ -353,8 +353,6 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
encounterDiagnosis.setLongTermFlag(saveDiagnosisChildParam.getLongTermFlag());
encounterDiagnosis.setOnsetDate(saveDiagnosisChildParam.getOnsetDate());
encounterDiagnosis.setDiagnosisTime(saveDiagnosisChildParam.getDiagnosisTime());
encounterDiagnosis.setOnsetDate(saveDiagnosisChildParam.getOnsetDate());
encounterDiagnosis.setDiagnosisTime(saveDiagnosisChildParam.getDiagnosisTime());
if(encounterDiagnosis.getCreateBy() == null){
encounterDiagnosis.setCreateBy(username);
}
@@ -383,8 +381,6 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
encounterDiagnosis.setLongTermFlag(saveDiagnosisChildParam.getLongTermFlag());
encounterDiagnosis.setOnsetDate(saveDiagnosisChildParam.getOnsetDate());
encounterDiagnosis.setDiagnosisTime(saveDiagnosisChildParam.getDiagnosisTime());
encounterDiagnosis.setOnsetDate(saveDiagnosisChildParam.getOnsetDate());
encounterDiagnosis.setDiagnosisTime(saveDiagnosisChildParam.getDiagnosisTime());
if(encounterDiagnosis.getCreateBy() == null){
encounterDiagnosis.setCreateBy(username);
}

View File

@@ -16,10 +16,12 @@ import java.util.Date;
import java.sql.Timestamp;
import com.openhis.common.enums.BindingType;
import com.openhis.common.enums.EncounterStatus;
import com.openhis.document.domain.DocRecord;
import com.openhis.document.domain.Emr;
import com.openhis.document.domain.EmrDetail;
import com.openhis.document.domain.EmrDict;
import com.openhis.document.domain.EmrTemplate;
import com.openhis.document.service.IDocRecordService;
import com.openhis.document.service.IEmrDetailService;
import com.openhis.document.service.IEmrDictService;
import com.openhis.document.service.IEmrService;
@@ -54,6 +56,9 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
@Resource
IEmrDictService emrDictService;
@Resource
IDocRecordService docRecordService;
@Resource
private EncounterMapper encounterMapper;
@@ -128,16 +133,35 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
/**
* 获取病历详情
* 同时检查门诊病历(emr表)和住院病历(doc_record表)
*
* @param encounterId 就诊id
* @return 病历详情
*/
@Override
public R<?> getEmrDetail(Long encounterId) {
// 先查询门诊病历(emr表)
Emr emrDetail = emrService.getOne(new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId));
if (emrDetail != null) {
return R.ok(emrDetail);
}
// 如果门诊病历为空,检查住院病历(doc_record表)
DocRecord docRecord = docRecordService.getOne(
new LambdaQueryWrapper<DocRecord>()
.eq(DocRecord::getEncounterId, encounterId)
.orderByDesc(DocRecord::getCreateTime)
.last("LIMIT 1")
);
if (docRecord != null) {
// 住院病历存在,也返回数据
return R.ok(docRecord);
}
// 都没有病历
return R.ok(null);
}
/**
* 保存病历模板
*

View File

@@ -4,6 +4,7 @@
package com.openhis.web.doctorstation.controller;
import com.core.common.core.domain.R;
import com.core.common.utils.SecurityUtils;
import com.openhis.common.enums.AdviceOpType;
import com.openhis.common.enums.Whether;
import com.openhis.web.doctorstation.appservice.IDoctorStationAdviceAppService;
@@ -109,9 +110,13 @@ public class DoctorStationChineseMedicalController {
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "locationId", required = false) Long locationId,
@RequestParam(value = "adviceDefinitionIdParamList", required = false) List<Long> adviceDefinitionIdParamList,
@RequestParam(value = "organizationId") Long organizationId,
@RequestParam(value = "organizationId", required = false) Long organizationId,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize) {
// 从当前登录用户获取科室 ID 作为默认值
if (organizationId == null) {
organizationId = SecurityUtils.getLoginUser().getOrgId();
}
return R.ok(iDoctorStationChineseMedicalAppService.getTcmAdviceBaseInfo(adviceBaseDto, searchKey, locationId,
adviceDefinitionIdParamList, organizationId, pageNo, pageSize, Whether.NO.getValue()));
}

View File

@@ -8,6 +8,8 @@ import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;
/**
* 医嘱保存 dto
@@ -80,6 +82,7 @@ public class AdviceSaveDto {
private BigDecimal unitPrice;
/** 总价 */
@JsonSetter(nulls = Nulls.AS_EMPTY)
private BigDecimal totalPrice;
/** 费用定价主表ID */
@@ -256,4 +259,16 @@ public class AdviceSaveDto {
this.founderOrgId = SecurityUtils.getLoginUser().getOrgId(); // 开方人科室
}
/**
* 🔧 Bug Fix: Custom setter for totalPrice to handle NaN and invalid values
* Prevents JSON parse errors when frontend sends NaN or invalid BigDecimal values
*/
public void setTotalPrice(BigDecimal totalPrice) {
if (totalPrice == null || totalPrice.compareTo(BigDecimal.ZERO) < 0) {
this.totalPrice = BigDecimal.ZERO;
} else {
this.totalPrice = totalPrice;
}
}
}

View File

@@ -37,6 +37,7 @@ public interface DoctorStationAdviceAppMapper {
@Param("activityTableName") String activityTableName, @Param("pricingFlag") Integer pricingFlag,
@Param("adviceDefinitionIdParamList") List<Long> adviceDefinitionIdParamList,
@Param("adviceTypes") List<Integer> adviceTypes,
@Param("searchKey") String searchKey,
@Param(Constants.WRAPPER) QueryWrapper<AdviceBaseDto> queryWrapper);
/**
@@ -174,4 +175,12 @@ public interface DoctorStationAdviceAppMapper {
List<ProofAndTestResultDto> getProofAndTestResult(@Param("encounterId") Long encounterId,
@Param("status") Integer status, @Param("typeEnum") Integer typeEnum);
/**
* 获取就诊的默认账户ID
*
* @param encounterId 就诊ID
* @return 默认账户ID如果没有则返回null
*/
Long getDefaultAccountId(@Param("encounterId") Long encounterId);
}

View File

@@ -98,11 +98,14 @@ public class AdviceUtils {
for (AdviceInventoryDto inventoryDto : adviceInventory) {
// 匹配条件adviceDefinitionId, adviceTableName, locationId, lotNumber 同时相等
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
// 🔧 Bug #177 修复:添加容错处理,如果 adviceTableName 为空则跳过该项匹配
boolean lotNumberMatch = StringUtils.isEmpty(saveDto.getLotNumber())
|| saveDto.getLotNumber().equals(inventoryDto.getLotNumber());
boolean tableNameMatch = StringUtils.isEmpty(saveDto.getAdviceTableName())
|| inventoryDto.getItemTable().equals(saveDto.getAdviceTableName());
// if (saveDto.)
if (inventoryDto.getItemId().equals(saveDto.getAdviceDefinitionId())
&& inventoryDto.getItemTable().equals(saveDto.getAdviceTableName())
&& tableNameMatch
&& inventoryDto.getLocationId().equals(saveDto.getLocationId()) && lotNumberMatch) {
matched = true;
// 检查库存是否充足

View File

@@ -245,15 +245,37 @@ public class DocDefinitionAppServiceImpl implements IDocDefinitionAppService {
public R<?> getTreeList(DocDefinitonParam param) {
// 1. 获取当前登录用户的医院ID避免跨医院查询
Long hospitalId = SecurityUtils.getLoginUser().getHospitalId();
Long organizationId = param.getOrganizationId();
List<Integer> useRanges = param.getUseRanges();
log.info("获取文书定义树形列表 - 请求参数: hospitalId={}, organizationId={}, useRanges={}, name={}, primaryMenuEnum={}",
hospitalId, organizationId, useRanges, param.getName(), param.getPrimaryMenuEnum());
if (hospitalId == null) {
log.warn("当前登录用户未关联医院ID将使用默认值");
// 设置默认医院ID为1或其他合适的默认值
hospitalId = 1L;
}
if (organizationId == null || organizationId == 0) {
log.warn("organizationId为空或0将跳过医院过滤和使用范围过滤");
}
if (useRanges == null || useRanges.isEmpty()) {
log.warn("useRanges为空可能返回所有使用范围的文书");
}
// 2. 数据库查询文书定义列表
List<DocDefinitionDto> docList = docDefinitionAppMapper.getDefinationList(param.getUseRanges(),
param.getOrganizationId(), hospitalId, param.getName(), param.getPrimaryMenuEnum());
List<DocDefinitionDto> docList = docDefinitionAppMapper.getDefinationList(useRanges,
organizationId, hospitalId, param.getName(), param.getPrimaryMenuEnum());
log.info("获取文书定义树形列表 - 查询结果: 记录数={}", docList != null ? docList.size() : 0);
if (docList != null && !docList.isEmpty()) {
for (DocDefinitionDto doc : docList) {
log.debug("文书: id={}, name={}, useRangeEnum={}, hospitalId={}",
doc.getId(), doc.getName(), doc.getUseRangeEnum(), doc.getHospitalId());
}
}
// 3. 构建树形结构(空列表时返回空树,避免空指针)
List<DirectoryNode> treeNodes = new ArrayList<>();

View File

@@ -106,7 +106,9 @@ public class DocDefinitionController {
* @return
*/
@GetMapping("/treeList")
public R<?> getTreeList(DocDefinitonParam docDefinitonParam) {
public R<?> getTreeList(DocDefinitonParam docDefinitonParam,
@RequestParam(value = "useRanges", required = false) List<Integer> useRanges) {
docDefinitonParam.setUseRanges(useRanges);
return iDocDefinitionAppService.getTreeList(docDefinitonParam);
}

View File

@@ -14,10 +14,13 @@ import com.core.common.utils.SecurityUtils;
import com.core.common.utils.StringUtils;
import com.openhis.administration.domain.Encounter;
import com.openhis.administration.domain.EncounterLocation;
import com.openhis.administration.domain.EncounterParticipant;
import com.openhis.administration.domain.Practitioner;
import com.openhis.administration.service.IEncounterLocationService;
import com.openhis.administration.service.IEncounterParticipantService;
import com.openhis.administration.service.IEncounterService;
import com.openhis.administration.service.ILocationService;
import com.openhis.administration.service.IPractitionerService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils;
@@ -40,8 +43,10 @@ import com.openhis.web.inhospitalnursestation.mapper.ATDManageAppMapper;
import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IServiceRequestService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
@@ -53,6 +58,7 @@ import java.util.stream.Stream;
* @author zwh
* @date 2025-07-28
*/
@Slf4j
@Service
public class ATDManageAppServiceImpl implements IATDManageAppService {
@@ -92,6 +98,9 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
@Resource
private IServiceRequestService serviceRequestService;
@Resource
private IPractitionerService practitionerService;
/**
* 入出转管理页面初始化
*
@@ -212,12 +221,131 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
*/
@Override
public R<?> getAdmissionPatientInfo(Long encounterId) {
// 查询住院患者详细信息
log.info("========== 查询患者信息 - encounterId: {} ==========", encounterId);
Integer active = EncounterActivityStatus.ACTIVE.getValue();
String primaryNurse = ParticipantType.PRIMARY_NURSE.getCode();
String attendingDoctor = ParticipantType.ATTENDING_DOCTOR.getCode();
String admittingDoctor = ParticipantType.ADMITTING_DOCTOR.getCode();
String chiefDoctor = ParticipantType.CHIEF_DOCTOR.getCode();
log.info("查询参数 - encounterId: {}, active: {} (type: {}), primaryNurse: {} (type: {}), attendingDoctor: {} (type: {}), admittingDoctor: {} (type: {}), chiefDoctor: {} (type: {})",
encounterId, active, active.getClass().getSimpleName(),
primaryNurse, primaryNurse.getClass().getSimpleName(),
attendingDoctor, attendingDoctor.getClass().getSimpleName(),
admittingDoctor, admittingDoctor.getClass().getSimpleName(),
chiefDoctor, chiefDoctor.getClass().getSimpleName());
// 先通过简单的 SQL 查询患者基本信息
AdmissionPatientInfoDto admissionPatientInfoDto
= atdManageAppMapper.selectAdmissionPatientInfo(encounterId, EncounterActivityStatus.ACTIVE.getValue(),
= atdManageAppMapper.selectAdmissionPatientInfo(encounterId, active,
LocationForm.WARD.getValue(), LocationForm.HOUSE.getValue(), LocationForm.BED.getValue(),
ParticipantType.PRIMARY_NURSE.getCode(), ParticipantType.ATTENDING_DOCTOR.getCode(),
ParticipantType.ADMITTING_DOCTOR.getCode(), ParticipantType.CHIEF_DOCTOR.getCode());
primaryNurse, attendingDoctor, admittingDoctor, chiefDoctor);
// 从 EncounterParticipant 表手动获取医生护士信息
List<EncounterParticipant> participantList = encounterParticipantService.getEncounterParticipantList(encounterId);
log.info("从 EncounterParticipant 获取到 {} 条记录", participantList.size());
// 调试:打印所有 participant 的详细信息
for (EncounterParticipant ep : participantList) {
log.info("调试 - typeCode: '{}', practitionerId: {}, statusEnum: {}, deleteFlag: '{}'",
ep.getTypeCode(), ep.getPractitionerId(), ep.getStatusEnum(), ep.getDeleteFlag());
}
// 查找各个角色 - 使用 Integer 类型进行比较
Integer primaryNurseValue = ParticipantType.PRIMARY_NURSE.getValue();
Integer attendingDoctorValue = ParticipantType.ATTENDING_DOCTOR.getValue();
Integer admittingDoctorValue = ParticipantType.ADMITTING_DOCTOR.getValue();
Integer chiefDoctorValue = ParticipantType.CHIEF_DOCTOR.getValue();
log.info("枚举值 - primaryNurse: {} ({})", primaryNurseValue, primaryNurse);
log.info("枚举值 - attendingDoctor: {} ({})", attendingDoctorValue, attendingDoctor);
log.info("枚举值 - admittingDoctor: {} ({})", admittingDoctorValue, admittingDoctor);
log.info("枚举值 - chiefDoctor: {} ({})", chiefDoctorValue, chiefDoctor);
// 查找各个角色
Long primaryNurseId = null;
String primaryNurseName = null;
Long attendingDoctorId = null;
String attendingDoctorName = null;
Long admittingDoctorId = null;
String admittingDoctorName = null;
Long chiefDoctorId = null;
String chiefDoctorName = null;
for (EncounterParticipant ep : participantList) {
if (ep.getStatusEnum() == null || !ep.getStatusEnum().equals(active)) {
log.info("跳过 - statusEnum: {} vs active: {}", ep.getStatusEnum(), active);
continue;
}
if (ep.getTypeCode() == null) {
log.info("跳过 - typeCode 为空");
continue;
}
// 尝试将 typeCode 转换为 Integer 进行比较
Integer typeCodeValue = null;
try {
typeCodeValue = Integer.parseInt(ep.getTypeCode().trim());
} catch (NumberFormatException e) {
log.warn("typeCode 无法转换为 Integer: {}", ep.getTypeCode());
}
log.info("检查 typeCode: {} (Integer: {}) vs primaryNurseValue: {}, attendingDoctorValue: {}, admittingDoctorValue: {}, chiefDoctorValue: {}",
ep.getTypeCode(), typeCodeValue, primaryNurseValue, attendingDoctorValue, admittingDoctorValue, chiefDoctorValue);
// 根据 typeCode 的 Integer 值匹配并获取 practitioner 名称
if (typeCodeValue != null && typeCodeValue.equals(primaryNurseValue)) {
primaryNurseId = ep.getPractitionerId();
// 查询 practitioner 名称
if (primaryNurseId != null) {
Practitioner practitioner = practitionerService.getById(primaryNurseId);
if (practitioner != null) {
primaryNurseName = practitioner.getName();
}
}
log.info("找到责任护士 - id: {}, name: {}", primaryNurseId, primaryNurseName);
} else if (typeCodeValue != null && typeCodeValue.equals(attendingDoctorValue)) {
attendingDoctorId = ep.getPractitionerId();
if (attendingDoctorId != null) {
Practitioner practitioner = practitionerService.getById(attendingDoctorId);
if (practitioner != null) {
attendingDoctorName = practitioner.getName();
}
}
log.info("找到主治医生 - id: {}, name: {}", attendingDoctorId, attendingDoctorName);
} else if (typeCodeValue != null && typeCodeValue.equals(admittingDoctorValue)) {
admittingDoctorId = ep.getPractitionerId();
if (admittingDoctorId != null) {
Practitioner practitioner = practitionerService.getById(admittingDoctorId);
if (practitioner != null) {
admittingDoctorName = practitioner.getName();
}
}
log.info("找到入院医生 - id: {}, name: {}", admittingDoctorId, admittingDoctorName);
} else if (typeCodeValue != null && typeCodeValue.equals(chiefDoctorValue)) {
chiefDoctorId = ep.getPractitionerId();
if (chiefDoctorId != null) {
Practitioner practitioner = practitionerService.getById(chiefDoctorId);
if (practitioner != null) {
chiefDoctorName = practitioner.getName();
}
}
log.info("找到主任医生 - id: {}, name: {}", chiefDoctorId, chiefDoctorName);
} else {
log.info("未匹配到任何角色 typeCode: {} (value: {})", ep.getTypeCode(), typeCodeValue);
}
}
// 手动设置医生护士信息到 DTO
if (admissionPatientInfoDto != null) {
admissionPatientInfoDto.setPrimaryNurseId(primaryNurseId);
admissionPatientInfoDto.setPrimaryNurseName(primaryNurseName);
admissionPatientInfoDto.setAttendingDoctorId(attendingDoctorId);
admissionPatientInfoDto.setAttendingDoctorName(attendingDoctorName);
admissionPatientInfoDto.setAdmittingDoctorId(admittingDoctorId);
admissionPatientInfoDto.setAdmittingDoctorName(admittingDoctorName);
admissionPatientInfoDto.setChiefDoctorId(chiefDoctorId);
admissionPatientInfoDto.setChiefDoctorName(chiefDoctorName);
// 年龄
if (admissionPatientInfoDto.getBirthDate() != null) {
admissionPatientInfoDto.setAge(AgeCalculatorUtil.getAge(admissionPatientInfoDto.getBirthDate()));
@@ -230,6 +358,13 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
EnumUtils.getInfoByValue(PriorityLevel.class, admissionPatientInfoDto.getPriorityEnum()));
// 获取入院体征
getAdmissionSigns(encounterId, admissionPatientInfoDto);
}
log.info("查询结果 - admittingDoctorId: {}, attendingDoctorId: {}, chiefDoctorId: {}, primaryNurseId: {}",
admissionPatientInfoDto != null ? admissionPatientInfoDto.getAdmittingDoctorId() : "null",
admissionPatientInfoDto != null ? admissionPatientInfoDto.getAttendingDoctorId() : "null",
admissionPatientInfoDto != null ? admissionPatientInfoDto.getChiefDoctorId() : "null",
admissionPatientInfoDto != null ? admissionPatientInfoDto.getPrimaryNurseId() : "null");
return R.ok(admissionPatientInfoDto);
}
@@ -283,7 +418,9 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> admissionBedAssignment(AdmissionPatientInfoDto admissionPatientInfoDto) {
log.info("===== admissionBedAssignment 开始 =====");
// 住院id
Long encounterId = admissionPatientInfoDto.getEncounterId();
@@ -297,35 +434,58 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
Long targetBedId = admissionPatientInfoDto.getTargetBedId();
// 进入编辑患者信息,不更换床位
if (Whether.YES.getCode().equals(admissionPatientInfoDto.getEditFlag())) {
if ("1".equals(admissionPatientInfoDto.getEditFlag())) {
log.info("编辑模式 - encounterId: {}, admittingDoctorId: {}, attendingDoctorId: {}, chiefDoctorId: {}, primaryNurseId: {}",
encounterId, admissionPatientInfoDto.getAdmittingDoctorId(), admissionPatientInfoDto.getAttendingDoctorId(),
admissionPatientInfoDto.getChiefDoctorId(), admissionPatientInfoDto.getPrimaryNurseId());
// 更新患者病情(如果提供了)
if (admissionPatientInfoDto.getPriorityEnum() != null) {
// 更新患者病情
encounterService.updatePriorityEnumById(encounterId, admissionPatientInfoDto.getPriorityEnum());
}
// 将之前的住院参与者更新为已完成(如果存在的话)
encounterParticipantService.updateEncounterParticipantsStatus(encounterId);
// 更新住院参与者
// 住院医生
// 住院医生(必须填写)
if (admissionPatientInfoDto.getAdmittingDoctorId() != null) {
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
admissionPatientInfoDto.getAdmittingDoctorId(), ParticipantType.ADMITTING_DOCTOR.getCode());
// 责任护士
}
// 责任护士(必须填写)
if (admissionPatientInfoDto.getPrimaryNurseId() != null) {
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
admissionPatientInfoDto.getPrimaryNurseId(), ParticipantType.PRIMARY_NURSE.getCode());
}
// 主治医生(可选)
if (admissionPatientInfoDto.getAttendingDoctorId() != null) {
// 主治医生
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
admissionPatientInfoDto.getAttendingDoctorId(), ParticipantType.ATTENDING_DOCTOR.getCode());
}
// 主任医生(可选)
if (admissionPatientInfoDto.getChiefDoctorId() != null) {
// 主任医生
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
admissionPatientInfoDto.getChiefDoctorId(), ParticipantType.CHIEF_DOCTOR.getCode());
}
// 保存后立即查询确认数据
List<EncounterParticipant> savedParticipants = encounterParticipantService.getEncounterParticipantList(encounterId);
log.info("保存后查询参与者 - encounterId: {}, 数量: {}", encounterId, savedParticipants.size());
for (EncounterParticipant ep : savedParticipants) {
log.info("参与者详情 - typeCode: {}, practitionerId: {}, statusEnum: {}",
ep.getTypeCode(), ep.getPractitionerId(), ep.getStatusEnum());
}
// 更新入院体征
// 更新入院体征(在事务外执行,避免影响参与者数据保存)
try {
saveOrUpdateAdmissionSigns(encounterId, admissionPatientInfoDto, startTime);
} catch (Exception e) {
log.error("保存入院体征失败,但不影响参与者数据", e);
}
return R.ok("患者信息更新成功");
}
// 新分配床位时,校验床位信息
if (targetBedId == null || admissionPatientInfoDto.getTargetHouseId() == null) {
return R.fail("床位分配失败,请选择有效的床位");
}
// 判断患者是否已经在床
if (oldBedId != null) {
// 判断目标床位是否已经被占用
@@ -421,8 +581,12 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
if (result == 0) {
return R.fail("床位分配失败,请联系管理员");
}
// 保存入院体征
// 保存入院体征(在事务外执行,避免影响参与者数据保存)
try {
saveOrUpdateAdmissionSigns(encounterId, admissionPatientInfoDto, startTime);
} catch (Exception e) {
log.error("保存入院体征失败,但不影响参与者数据", e);
}
return R.ok("床位分配成功");
}

View File

@@ -69,7 +69,8 @@ public class PatientHomeAppServiceImpl implements IPatientHomeAppService {
// 分页查询,查询患者信息
IPage<PatientHomeDto> patientHomeInfoPage = patientHomeAppMapper.getPage(new Page<>(pageNo, pageSize),
patientHomeSearchParam.getStatusEnum(), patientHomeSearchParam.getPatientId(), searchKey, queryWrapper);
patientHomeSearchParam.getStatusEnum(), patientHomeSearchParam.getPatientId(), searchKey,
patientHomeSearchParam.getNursingLevelList(), queryWrapper);
patientHomeInfoPage.getRecords().forEach(e -> {
// 护理级别

View File

@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* 患者首页 应用实现
@@ -36,7 +37,11 @@ public class PatientHomeController {
public R<?> getPatientInfoInit(PatientHomeSearchParam patientHomeSearchParam,
@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest request) {
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "nursingLevelList", required = false) List<Integer> nursingLevelList,
HttpServletRequest request) {
// 将护理级别列表设置到查询参数中
patientHomeSearchParam.setNursingLevelList(nursingLevelList);
return patientHomeAppService.getPatientInfoInit(patientHomeSearchParam, searchKey, pageNo, pageSize, request);
}

View File

@@ -3,6 +3,8 @@ package com.openhis.web.inpatientmanage.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 患者首页查询参数
*
@@ -19,4 +21,7 @@ public class PatientHomeSearchParam {
/** 患者ID */
private Long patientId;
/** 护理级别列表(多选) */
private List<Integer> nursingLevelList;
}

View File

@@ -27,10 +27,13 @@ public interface PatientHomeAppMapper {
* @param statusEnum 状态编码
* @param patientId 患者ID
* @param searchKey 查询条件
* @param nursingLevelList 护理级别列表
* @param queryWrapper 查询wrapper
* @return 住院登记信息
*/
IPage<PatientHomeDto> getPage(@Param("page") Page<PatientHomeDto> page, @Param("statusEnum") Integer statusEnum,
@Param("patientId") Long patientId, @Param("searchKey") String searchKey,
@Param("nursingLevelList") List<Integer> nursingLevelList,
@Param(Constants.WRAPPER) QueryWrapper<PatientHomeDto> queryWrapper);
/**

View File

@@ -1,6 +1,7 @@
package com.openhis.web.lab.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.controller.BaseController;
import com.core.common.core.domain.AjaxResult;
import com.openhis.lab.domain.InspectionType;
@@ -29,7 +30,25 @@ public class InspectionTypeController extends BaseController {
private final TransactionTemplate transactionTemplate;
/**
* 获取检验类型列表
* 分页获取检验类型列表
*
* @param pageNo 页码
* @param pageSize 每页数量
* @param searchKey 搜索关键词
*/
@GetMapping("/page")
public AjaxResult getPage(
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "100") Integer pageSize,
@RequestParam(value = "searchKey", required = false) String searchKey) {
log.info("【检验类型】分页查询请求 - pageNo: {}, pageSize: {}, searchKey: {}", pageNo, pageSize, searchKey);
IPage<InspectionType> result = inspectionTypeService.getPage(pageNo, pageSize, searchKey);
log.info("【检验类型】分页查询完成 - 总记录数: {}, 当前页记录数: {}", result.getTotal(), result.getRecords().size());
return AjaxResult.success(result);
}
/**
* 获取检验类型列表(不分页,兼容旧接口)
*/
@GetMapping("/list")
public AjaxResult list(InspectionType inspectionType) {

View File

@@ -62,10 +62,10 @@ public class OutpatientInfusionAppServiceImpl implements IOutpatientInfusionAppS
OutpatientStationInitDto initDto = new OutpatientStationInitDto();
// 执行状态
List<OutpatientStationInitDto.ServiceStatus> serviceStatusOptions = new ArrayList<>();
serviceStatusOptions.add(new OutpatientStationInitDto.ServiceStatus(RequestStatus.ACTIVE.getValue(),
"待执行"));
serviceStatusOptions.add(new OutpatientStationInitDto.ServiceStatus(RequestStatus.COMPLETED.getValue(),
RequestStatus.COMPLETED.getInfo()));
// serviceStatusOptions.add(new OutpatientStationInitDto.ServiceStatus(RequestStatus.IN_PROGRESS.getValue(),
// RequestStatus.IN_PROGRESS.getInfo()));
serviceStatusOptions.add(new OutpatientStationInitDto.ServiceStatus(RequestStatus.CANCELLED.getValue(),
RequestStatus.CANCELLED.getInfo()));
initDto.setServiceStatusOptions(serviceStatusOptions);

View File

@@ -199,13 +199,19 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
// 此次chargeItem的就诊诊断id集合
List<Long> diagIdList
= chargeItemList.stream().map(ChargeItem::getEncounterDiagnosisId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
if (diagIdList.isEmpty()) {
throw new ServiceException("收费项的就诊诊断查询为空");
}
// 此次就诊的医疗类型列表
List<EncounterDiagnosis> diagList = iEncounterDiagnosisService.getDiagnosisList(diagIdList);
List<EncounterDiagnosis> diagList;
if (diagIdList.isEmpty()) {
// 收费项未关联就诊诊断(如挂号费、自动诊疗费等),则直接从就诊维度查询诊断
diagList = iEncounterDiagnosisService.getDiagnosisList(prePaymentDto.getEncounterId());
if (diagList.isEmpty()) {
throw new ServiceException("就诊诊断查询为空错误信息EncounterDiagnosis");
throw new ServiceException("当前就诊暂无诊断记录,请先由医生录入诊断后再进行收费");
}
} else {
diagList = iEncounterDiagnosisService.getDiagnosisList(diagIdList);
if (diagList.isEmpty()) {
throw new ServiceException("就诊诊断查询为空");
}
}
List<String> medTypeList
= diagList.stream().map(EncounterDiagnosis::getMedTypeCode).distinct().collect(Collectors.toList());
@@ -231,10 +237,20 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
if (distinctAccountIdList.isEmpty()) {
throw new ServiceException("未找到有效的账户信息");
}
// 只按账户ID查询不再追加 encounter_id 条件
// 原因:收费项上的 accountId 已能唯一定位账户,额外的 encounter_id 条件
// 在挂号费等场景下可能因数据不一致导致查不到,引发误报
List<Account> accountList = iAccountService.list(new LambdaQueryWrapper<Account>()
.in(Account::getId, distinctAccountIdList).eq(Account::getEncounterId, prePaymentDto.getEncounterId()));
.in(Account::getId, distinctAccountIdList));
if (accountList.size() != distinctAccountIdList.size()) {
throw new ServiceException("未查询到账户信息");
// 部分账户查不到时,记录警告日志,并校验是否每个收费项都能找到对应账户
Set<Long> foundAccountIds = accountList.stream().map(Account::getId).collect(Collectors.toSet());
List<Long> missingAccountIds = distinctAccountIdList.stream()
.filter(id -> !foundAccountIds.contains(id)).collect(Collectors.toList());
if (accountList.isEmpty()) {
throw new ServiceException("未查询到任何账户信息encounterId" + prePaymentDto.getEncounterId()
+ "期望accountId列表" + distinctAccountIdList);
}
}
// 账户id对应的账单列表
@@ -622,6 +638,37 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
});
if (!medicationRequestIdList.isEmpty()) {
// 获取药品请求信息,为输液类药品生成服务请求
List<MedicationRequest> medicationRequests = medicationRequestService.listByIds(medicationRequestIdList);
// 为输液类药品生成 wor_service_request 记录
for (MedicationRequest medReq : medicationRequests) {
if (medReq.getInfusionFlag() != null && medReq.getInfusionFlag() == 1) {
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setBasedOnId(medReq.getId())
.setBasedOnTable(CommonConstants.TableName.MED_MEDICATION_REQUEST)
.setEncounterId(medReq.getEncounterId())
.setPatientId(medReq.getPatientId())
.setActivityId(medReq.getMedicationId())
.setStatusEnum(RequestStatus.ACTIVE.getValue()) // 状态设为已发送 (2),这样门诊输液页面才能查到
.setGroupId(medReq.getGroupId())
.setOrgId(medReq.getOrgId())
.setRequesterId(medReq.getPractitionerId())
.setAuthoredTime(new Date())
.setEncounterDiagnosisId(medReq.getEncounterDiagnosisId())
.setConditionId(medReq.getConditionId())
.setQuantity(medReq.getQuantity())
.setUnitCode(medReq.getUnitCode())
.setPriorityEnum(medReq.getPriorityEnum())
.setPerformFlag(Whether.NO.getValue())
.setIntentEnum(medReq.getIntentEnum())
.setCategoryEnum(medReq.getCategoryEnum())
.setYbClassEnum(medReq.getYbClassEnum())
.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
serviceRequestService.save(serviceRequest);
}
}
// 更新请求状态为已完成
medicationRequestService.updateCompletedStatusBatch(medicationRequestIdList, null, null);
// 更新药品发放状态为待配药
@@ -2125,14 +2172,18 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
piModel.setBusNo(chargeItem.getBusNo());
EncounterDiagnosis encounterDiagnosis;
if (chargeItem.getEncounterDiagnosisId() == null) {
// 收费项未关联就诊诊断(如挂号费、自动诊疗费等),取诊断列表第一条作为兜底
encounterDiagnosis = diagList.get(0);
} else {
Optional<EncounterDiagnosis> optional
= diagList.stream().filter(e -> e.getId().equals(chargeItem.getEncounterDiagnosisId())).findFirst();
if (optional.isPresent()) {
encounterDiagnosis = optional.get();
} else {
throw new ServiceException(
"诊断信息与收费项的诊断信息未对应,错误信息:费用项" + chargeItem.getBusNo() + chargeItem.getEncounterDiagnosisId());
// 收费项关联的诊断ID在diagList中找不到也取第一条兜底
encounterDiagnosis = diagList.get(0);
}
}
piModel.setMedType(encounterDiagnosis.getMedTypeCode());
piModel.setTotalPrice(chargeItem.getTotalPrice());

View File

@@ -30,9 +30,10 @@ public interface IOrdersGroupPackageAppService {
*
* @param packageTypeEnum 类型枚举
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey);
List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey, Integer tcmFlag);
/**
* 查询组合套餐明细
@@ -54,8 +55,9 @@ public interface IOrdersGroupPackageAppService {
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId);
OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId, Integer tcmFlag);
}

View File

@@ -1,8 +1,16 @@
package com.openhis.web.personalization.appservice.impl;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.core.common.exception.ServiceException;
import com.core.common.utils.MessageUtils;
import com.core.common.utils.SecurityUtils;
import com.openhis.common.constant.PromptMsgConstant;
@@ -18,13 +26,8 @@ import com.openhis.web.doctorstation.dto.AdviceBaseDto;
import com.openhis.web.personalization.appservice.IOrdersGroupPackageAppService;
import com.openhis.web.personalization.dto.*;
import com.openhis.web.personalization.mapper.OrdersGroupPackageAppMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.core.framework.datasource.DynamicDataSourceContextHolder.log;
/**
* 组合套餐 实现类
@@ -61,10 +64,12 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
if (BindingType.PERSONAL.getValue().equals(packageTypeEnum)
&& ordersGroupPackageSaveDto.getPractitionerId() == null) {
throw new ServiceException("个人组套需选择人员");
// throw new ServiceException("个人组套需选择人员");
ordersGroupPackageSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
} else if (BindingType.ORGANIZATION.getValue().equals(packageTypeEnum)
&& ordersGroupPackageSaveDto.getOrganizationId() == null) {
throw new ServiceException("科室组套需选择科室");
// throw new ServiceException("科室组套需选择科室");
ordersGroupPackageSaveDto.setOrganizationId(SecurityUtils.getLoginUser().getOrgId());
}
// 保存主表
OrdersGroupPackage ordersGroupPackage = new OrdersGroupPackage();
@@ -73,6 +78,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
ordersGroupPackage.setPackageTypeEnum(packageTypeEnum);// 组套包类型
ordersGroupPackage.setOrganizationId(ordersGroupPackageSaveDto.getOrganizationId()); // 科室id
ordersGroupPackage.setPractitionerId(ordersGroupPackageSaveDto.getPractitionerId()); // 参与者id
ordersGroupPackage.setTcmFlag(ordersGroupPackageSaveDto.getTcmFlag()); // 中医标识
ordersGroupPackageService.saveOrUpdate(ordersGroupPackage);
// 编辑场景时,先删除原有的明细再新增
@@ -96,6 +102,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
ordersGroupPackageDetail.setMethodCode(ordersGroupPackageDetailSaveDto.getMethodCode()); // 给药途径
ordersGroupPackageDetail.setDoseQuantity(ordersGroupPackageDetailSaveDto.getDoseQuantity()); // 小单位单次剂量
ordersGroupPackageDetail.setGroupId(ordersGroupPackageDetailSaveDto.getGroupId()); // 组号
ordersGroupPackageDetail.setTherapyEnum(ordersGroupPackageDetailSaveDto.getTherapyEnum()); // 治疗类型
ordersGroupPackageDetailService.save(ordersGroupPackageDetail);
}
@@ -107,12 +114,14 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
*
* @param packageTypeEnum 类型枚举
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识
* @return 组合套餐
*/
@Override
public List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey) {
public List<OrdersGroupPackageQueryDto> getGroupPackage(Integer packageTypeEnum, String searchKey,
Integer tcmFlag) {
List<OrdersGroupPackageQueryDto> groupPackage =
ordersGroupPackageAppMapper.getGroupPackage(packageTypeEnum, null, null, searchKey);
ordersGroupPackageAppMapper.getGroupPackage(packageTypeEnum, null, null, searchKey, tcmFlag);
for (OrdersGroupPackageQueryDto ordersGroupPackageQueryDto : groupPackage) {
ordersGroupPackageQueryDto.setPackageTypeEnum_enumText(
EnumUtils.getInfoByValue(BindingType.class, ordersGroupPackageQueryDto.getPackageTypeEnum()));
@@ -151,19 +160,23 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识
* @return 组合套餐
*/
@Override
public OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId) {
public OrdersGroupPackageUseDto getGroupPackageForOrder(Long organizationId, Integer tcmFlag) {
OrdersGroupPackageUseDto ordersGroupPackageUseDto = new OrdersGroupPackageUseDto();
// 当前参参与者id
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
// 当前登录账号的科室id
Long orgId = SecurityUtils.getLoginUser().getOrgId();
log.info("[getGroupPackageForOrder] 当前登录用户 practitionerId: {}, orgId: {}", practitionerId, orgId);
// 个人组套
List<OrdersGroupPackageQueryDto> personalGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.PERSONAL.getValue(), null, practitionerId, null);
List<OrdersGroupPackageQueryDto> personalGroupPackage = ordersGroupPackageAppMapper
.getGroupPackage(BindingType.PERSONAL.getValue(), null, practitionerId, null, tcmFlag);
log.info("[getGroupPackageForOrder] 个人组套查询结果数: {}", personalGroupPackage.size());
if (!personalGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> personalList = personalGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();
@@ -208,10 +221,13 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
});
}
ordersGroupPackageUseDto.setPersonalList(personalList);
log.info("[getGroupPackageForOrder] 个人组套已设置,数量: {}", personalList.size());
} else {
log.info("[getGroupPackageForOrder] 个人组套查询结果为空");
}
// 科室组套
List<OrdersGroupPackageQueryDto> organizationGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.ORGANIZATION.getValue(), orgId, null, null);
List<OrdersGroupPackageQueryDto> organizationGroupPackage = ordersGroupPackageAppMapper
.getGroupPackage(BindingType.ORGANIZATION.getValue(), orgId, null, null, tcmFlag);
if (!organizationGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> organizationList = organizationGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();
@@ -260,7 +276,7 @@ public class OrdersGroupPackageAppServiceImpl implements IOrdersGroupPackageAppS
// 全院组套
List<OrdersGroupPackageQueryDto> hospitalGroupPackage =
ordersGroupPackageAppMapper.getGroupPackage(BindingType.HOSPITAL.getValue(), null, null, null);
ordersGroupPackageAppMapper.getGroupPackage(BindingType.HOSPITAL.getValue(), null, null, null, tcmFlag);
if (!hospitalGroupPackage.isEmpty()) {
List<OrdersGroupPackageDto> hospitalList = hospitalGroupPackage.stream().map(queryDto -> {
OrdersGroupPackageDto dto = new OrdersGroupPackageDto();

View File

@@ -65,33 +65,39 @@ public class OrdersGroupPackageController {
* 查询个人组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 个人组套
*/
@GetMapping(value = "/get-personal")
public R<?> getPersonal(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.PERSONAL.getValue(), searchKey));
public R<?> getPersonal(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.PERSONAL.getValue(), searchKey, tcmFlag));
}
/**
* 查询科室组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 科室组套
*/
@GetMapping(value = "/get-organization")
public R<?> getOrganization(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.ORGANIZATION.getValue(), searchKey));
public R<?> getOrganization(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.ORGANIZATION.getValue(), searchKey, tcmFlag));
}
/**
* 查询全院组套
*
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 全院组套
*/
@GetMapping(value = "/get-hospital")
public R<?> getHospital(@RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.HOSPITAL.getValue(), searchKey));
public R<?> getHospital(@RequestParam(value = "searchKey", defaultValue = "") String searchKey,
@RequestParam(value = "tcmFlag", defaultValue = "0") Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackage(BindingType.HOSPITAL.getValue(), searchKey, tcmFlag));
}
/**
@@ -120,12 +126,13 @@ public class OrdersGroupPackageController {
* 查询组合套餐,供开立医嘱使用
*
* @param organizationId 患者挂号对应的科室id
* @param tcmFlag 中医标识0-西医 1-中医,不传则不过滤
* @return 组合套餐
*/
@GetMapping(value = "/group-package-for-order")
public R<?>
getGroupPackageForOrder(@RequestParam(value = "organizationId", defaultValue = "0") Long organizationId) {
return R.ok(ordersGroupPackageAppService.getGroupPackageForOrder(organizationId));
public R<?> getGroupPackageForOrder(@RequestParam(value = "organizationId", required = false) Long organizationId,
@RequestParam(value = "tcmFlag", required = false) Integer tcmFlag) {
return R.ok(ordersGroupPackageAppService.getGroupPackageForOrder(organizationId, tcmFlag));
}
}

View File

@@ -63,4 +63,9 @@ public class OrdersGroupPackageDetailSaveDto {
*/
private Long groupId;
/**
* 治疗类型1-长期 2-临时
*/
private Integer therapyEnum;
}

View File

@@ -48,6 +48,11 @@ public class OrdersGroupPackageSaveDto {
@JsonSerialize(using = ToStringSerializer.class)
private Long practitionerId;
/**
* 中医标识0-西医 1-中医
*/
private Integer tcmFlag;
/**
* 明细集合
*/

View File

@@ -26,11 +26,12 @@ public interface OrdersGroupPackageAppMapper {
* @param organizationId 科室id
* @param practitionerId 参与者id
* @param searchKey 模糊查询关键字
* @param tcmFlag 中医标识0-西医 1-中医
* @return 组合套餐
*/
List<OrdersGroupPackageQueryDto> getGroupPackage(@Param("packageTypeEnum") Integer packageTypeEnum,
@Param("organizationId") Long organizationId, @Param("practitionerId") Long practitionerId,
@Param("searchKey") String searchKey);
@Param("searchKey") String searchKey, @Param("tcmFlag") Integer tcmFlag);
/**
* 查询组合套餐明细

View File

@@ -236,6 +236,11 @@ public class WesternMedicineDispenseAppServiceImpl implements IWesternMedicineDi
EnumUtils.getInfoByValue(DispenseStatus.class, medicineDispenseOrder.getStatusEnum()));
// 设置所在表名
medicineDispenseOrder.setItemTable(CommonConstants.TableName.MED_MEDICATION_DEFINITION);
// 处方类型(发药类型:门诊/住院等)
if (medicineDispenseOrder.getDispenseEnum() != null) {
medicineDispenseOrder.setDispenseEnum_enumText(
EnumUtils.getInfoByValue(EncounterClass.class, medicineDispenseOrder.getDispenseEnum()));
}
});
return R.ok(medicineDispenseOrderPage);
}

View File

@@ -271,6 +271,12 @@ public class ItemDispenseOrderDto {
private String medTypeCode;
private String medTypeCode_dictText;
/**
* 发药类型(处方类型:门诊/住院等)
*/
private Integer dispenseEnum;
private String dispenseEnum_enumText;
/**
* 输液标志
*/

View File

@@ -29,9 +29,12 @@ import com.openhis.web.regdoctorstation.appservice.IAdviceManageAppService;
import com.openhis.web.regdoctorstation.dto.*;
import com.openhis.web.regdoctorstation.mapper.AdviceManageAppMapper;
import com.openhis.web.regdoctorstation.utils.RegPrescriptionUtils;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.workflow.domain.DeviceRequest;
import com.openhis.workflow.domain.ServiceRequest;
import com.openhis.workflow.service.IActivityDefinitionService;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -76,6 +79,12 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
@Resource
IActivityDefinitionService iActivityDefinitionService;
@Resource
IDeviceRequestService iDeviceRequestService;
@Resource
IDeviceDispenseService iDeviceDispenseService;
/**
* 查询住院患者信息
*
@@ -174,6 +183,9 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
// 诊疗活动
List<RegAdviceSaveDto> activityList = regAdviceSaveList.stream()
.filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 耗材 🔧 Bug #147 修复
List<RegAdviceSaveDto> deviceList = regAdviceSaveList.stream()
.filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
// 保存时,校验临时医嘱库存
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
@@ -210,6 +222,11 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
*/
this.handService(activityList, startTime, authoredTime, curDate, adviceOpType, organizationId, signCode);
/**
* 🔧 Bug #147 修复:处理耗材请求
*/
this.handDevice(deviceList, startTime, authoredTime, curDate, adviceOpType, organizationId, signCode);
// 签发时,把草稿状态的账单更新为待收费
if (AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType) && !regAdviceSaveList.isEmpty()) {
// 签发的医嘱id集合
@@ -594,6 +611,150 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
}
/**
* 🔧 Bug #147 修复:处理耗材
*/
private void handDevice(List<RegAdviceSaveDto> deviceList, Date startTime, Date authoredTime, Date curDate,
String adviceOpType, Long organizationId, String signCode) {
// 当前登录账号的科室id
Long orgId = SecurityUtils.getLoginUser().getOrgId();
// 获取当前登录用户的tenantId
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
// 保存操作
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
// 签发操作
boolean is_sign = AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType);
// 删除
List<RegAdviceSaveDto> deleteList = deviceList.stream()
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
for (RegAdviceSaveDto regAdviceSaveDto : deleteList) {
iDeviceRequestService.removeById(regAdviceSaveDto.getRequestId());
// 删除已经产生的耗材发放信息
iDeviceDispenseService.deleteDeviceDispense(regAdviceSaveDto.getRequestId());
// 删除费用项
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_DEVICE_REQUEST,
regAdviceSaveDto.getRequestId());
}
// 声明耗材请求
DeviceRequest deviceRequest;
// 声明费用项
ChargeItem chargeItem;
// 新增 + 修改 (长期医嘱)
List<RegAdviceSaveDto> longInsertOrUpdateList = deviceList.stream().filter(e -> TherapyTimeType.LONG_TERM
.getValue().equals(e.getTherapyEnum())
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList());
for (RegAdviceSaveDto regAdviceSaveDto : longInsertOrUpdateList) {
deviceRequest = new DeviceRequest();
deviceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
deviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
if (is_sign) {
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
}
// 保存时处理的字段属性
if (is_save) {
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
deviceRequest.setQuantity(regAdviceSaveDto.getQuantity()); // 请求数量
deviceRequest.setUnitCode(regAdviceSaveDto.getUnitCode()); // 请求单位编码
deviceRequest.setLotNumber(regAdviceSaveDto.getLotNumber()); // 产品批号
deviceRequest.setCategoryEnum(regAdviceSaveDto.getCategoryEnum()); // 请求类型
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());// 耗材定义id
deviceRequest.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(regAdviceSaveDto.getFounderOrgId()); // 开方人科室
deviceRequest.setReqAuthoredTime(startTime); // 医嘱开始时间
deviceRequest.setPerformLocation(regAdviceSaveDto.getLocationId()); // 发放科室
deviceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(regAdviceSaveDto.getPackageId()); // 组套id
deviceRequest.setContentJson(regAdviceSaveDto.getContentJson()); // 请求内容json
deviceRequest.setYbClassEnum(regAdviceSaveDto.getYbClassEnum());// 类别医保编码
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
}
iDeviceRequestService.saveOrUpdate(deviceRequest);
}
// 新增 + 修改 (临时医嘱)
List<RegAdviceSaveDto> tempInsertOrUpdateList = deviceList.stream().filter(e -> TherapyTimeType.TEMPORARY
.getValue().equals(e.getTherapyEnum())
&& (DbOpType.INSERT.getCode().equals(e.getDbOpType()) || DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
.collect(Collectors.toList());
for (RegAdviceSaveDto regAdviceSaveDto : tempInsertOrUpdateList) {
deviceRequest = new DeviceRequest();
deviceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
deviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
if (is_sign) {
deviceRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
}
// 保存时处理的字段属性
if (is_save) {
deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), 4));
deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
deviceRequest.setQuantity(regAdviceSaveDto.getQuantity()); // 请求数量
deviceRequest.setUnitCode(regAdviceSaveDto.getUnitCode()); // 请求单位编码
deviceRequest.setLotNumber(regAdviceSaveDto.getLotNumber()); // 产品批号
deviceRequest.setCategoryEnum(regAdviceSaveDto.getCategoryEnum()); // 请求类型
deviceRequest.setDeviceDefId(regAdviceSaveDto.getAdviceDefinitionId());// 耗材定义id
deviceRequest.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // 开方医生
deviceRequest.setOrgId(regAdviceSaveDto.getFounderOrgId()); // 开方人科室
deviceRequest.setReqAuthoredTime(startTime); // 医嘱开始时间
deviceRequest.setPerformLocation(regAdviceSaveDto.getLocationId()); // 发放科室
deviceRequest.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
deviceRequest.setPackageId(regAdviceSaveDto.getPackageId()); // 组套id
deviceRequest.setContentJson(regAdviceSaveDto.getContentJson()); // 请求内容json
deviceRequest.setYbClassEnum(regAdviceSaveDto.getYbClassEnum());// 类别医保编码
deviceRequest.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
deviceRequest.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
}
iDeviceRequestService.saveOrUpdate(deviceRequest);
// 保存时,保存耗材费用项
if (is_save) {
// 处理耗材发放
Long dispenseId = iDeviceDispenseService.handleDeviceDispense(deviceRequest,
regAdviceSaveDto.getDbOpType());
// 保存耗材费用项
chargeItem = new ChargeItem();
chargeItem.setId(regAdviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
chargeItem.setPatientId(regAdviceSaveDto.getPatientId()); // 患者
chargeItem.setContextEnum(regAdviceSaveDto.getAdviceType()); // 类型
chargeItem.setEncounterId(regAdviceSaveDto.getEncounterId()); // 就诊id
chargeItem.setDefinitionId(regAdviceSaveDto.getDefinitionId()); // 费用定价ID
chargeItem.setDefDetailId(regAdviceSaveDto.getDefinitionDetailId()); // 定价子表主键
chargeItem.setEntererId(regAdviceSaveDto.getPractitionerId());// 开立人ID
chargeItem.setEnteredDate(curDate); // 开立时间
chargeItem.setServiceTable(CommonConstants.TableName.WOR_DEVICE_REQUEST);// 医疗服务类型
chargeItem.setServiceId(deviceRequest.getId()); // 医疗服务ID
chargeItem.setProductTable(regAdviceSaveDto.getAdviceTableName());// 产品所在表
chargeItem.setProductId(regAdviceSaveDto.getAdviceDefinitionId());// 收费项id
chargeItem.setAccountId(regAdviceSaveDto.getAccountId());// 关联账户ID
chargeItem.setRequestingOrgId(orgId); // 开立科室
chargeItem.setConditionId(regAdviceSaveDto.getConditionId()); // 诊断id
chargeItem.setEncounterDiagnosisId(regAdviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
chargeItem.setDispenseId(dispenseId); // 发放ID
chargeItem.setQuantityValue(regAdviceSaveDto.getQuantity()); // 数量
chargeItem.setQuantityUnit(regAdviceSaveDto.getUnitCode()); // 单位
chargeItem.setUnitPrice(regAdviceSaveDto.getUnitPrice()); // 单价
chargeItem.setTotalPrice(regAdviceSaveDto.getTotalPrice()); // 总价
iChargeItemService.saveOrUpdate(chargeItem);
}
}
}
/**
* 查询住院医嘱请求数据
*

View File

@@ -230,7 +230,7 @@
WHERE T1.purpose_location_id = #{locationId}
AND T1.status_enum = #{statusEnum}
AND T1.type_enum = #{typeEnum}
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -292,7 +292,7 @@
WHERE T1.purpose_location_id = #{locationId}
AND T1.status_enum = #{statusEnum}
AND T1.type_enum = #{typeEnum}
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -442,7 +442,7 @@
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') = #{startTime}
</if>
<if test="endTime != null">
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
</if>
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
@@ -515,7 +515,7 @@
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') = #{startTime}
</if>
<if test="endTime != null">
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
</if>
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'

View File

@@ -419,7 +419,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -499,7 +499,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -229,7 +229,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -280,7 +280,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -384,7 +384,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -478,7 +478,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -268,7 +268,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -343,7 +343,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -266,7 +266,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{medicationTableName}
AND T1.delete_flag = '0'
UNION
@@ -341,7 +341,7 @@
<foreach collection="typeEnum" item="item" separator="," open="(" close=")">
#{item}
</foreach>
AND TO_CHAR(T1.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.item_table = #{deviceTableName}
AND T1.delete_flag = '0'
</select>

View File

@@ -30,16 +30,22 @@
T1.pricing_flag,
T1.sort_order,
T1.service_range,
T1.inspection_type_id,
T2.type_code as item_type_code,
T2.yb_type,
T2.price_code,
T2.price as retail_price,
T4.amount as maximum_retail_price
T4.amount as maximum_retail_price,
T3.name as test_type
FROM wor_activity_definition T1
/* 只JOIN必要的价格表使用INNER JOIN避免笛卡尔积 */
INNER JOIN adm_charge_item_definition T2
ON T1.id = T2.instance_id
AND T2.instance_table = 'wor_activity_definition'
/* 检验类型关联 */
LEFT JOIN inspection_type T3
ON T1.inspection_type_id = T3.id
AND T3.valid_flag = 1
/* 最高零售价使用LEFT JOIN因为可能不存在 */
LEFT JOIN adm_charge_item_def_detail T4
ON T4.definition_id = T2.id
@@ -97,9 +103,13 @@
T1.children_json,
T1.pricing_flag,
T1.sort_order,
T1.service_range
T1.service_range,
T1.inspection_type_id,
T3.name as test_type
FROM wor_activity_definition T1
LEFT JOIN adm_charge_item_definition T2 ON T1.id = T2.instance_id
/* 检验类型关联 */
LEFT JOIN inspection_type T3 ON T1.inspection_type_id = T3.id AND T3.valid_flag = 1
<where>
T1.delete_flag = '0'
AND T2.instance_table = 'wor_activity_definition'

View File

@@ -81,7 +81,7 @@
CAST(T2.dose AS TEXT) AS dose,
T2.dose_unit_code AS dose_unit_code,
T3.NAME AS supplier,
T3.id AS supplier_id,
t3.id AS supplier_id,
T1.manufacturer_text AS manufacturer,
T5.id AS charge_item_definition_id,
T5.instance_table AS advice_table_name,
@@ -91,24 +91,21 @@
T1.dosage_instruction AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM med_medication_definition AS t1
INNER JOIN med_medication AS T2 ON T2.medication_def_id = T1.ID
INNER JOIN med_medication AS T2 ON T2.medication_def_id = t1.ID
AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum}
LEFT JOIN adm_supplier AS T3
ON T3.ID = T1.supply_id
AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T5 ON T5.instance_id = T1.ID
AND T5.delete_flag = '0' AND T5.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T6
ON T6.distribution_category_code = T1.category_code
AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND
(CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE T1.delete_flag = '0'
LEFT JOIN adm_supplier AS T3 ON T3.ID = t1.supply_id AND T3.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T5 ON T5.instance_id = t1.ID AND T5.delete_flag = '0' AND T5.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T6 ON T6.distribution_category_code = t1.category_code AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum}
<if test="pricingFlag ==1">
AND 1 = 2
</if>
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
@@ -122,7 +119,7 @@
(SELECT
DISTINCT ON (T1.ID)
T1.tenant_id,
2 AS advice_type,
4 AS advice_type,
T1.bus_no AS bus_no,
T1.category_code AS category_code,
'' AS pharmacology_category_code,
@@ -151,7 +148,7 @@
'' AS dose,
'' AS dose_unit_code,
T2.NAME AS supplier,
T2.id AS supplier_id,
t2.id AS supplier_id,
T1.manufacturer_text AS manufacturer,
T4.id AS charge_item_definition_id,
T4.instance_table AS advice_table_name,
@@ -161,17 +158,16 @@
'' AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM adm_device_definition AS T1
LEFT JOIN adm_supplier AS T2
ON T2.ID = T1.supply_id
AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T4 ON T4.instance_id = T1.ID
AND T4.delete_flag = '0' AND T4.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T5 ON T5.distribution_category_code = T1.category_code
AND T5.delete_flag = '0' AND T5.item_code = '2' AND T5.organization_id = #{organizationId} AND
(CURRENT_TIME :: time (6) BETWEEN T5.start_time AND T5.end_time)
WHERE T1.delete_flag = '0'
LEFT JOIN adm_supplier AS T2 ON T2.ID = t1.supply_id AND T2.delete_flag = '0'
LEFT JOIN adm_charge_item_definition AS T4 ON T4.instance_id = t1.ID AND T4.delete_flag = '0' AND T4.status_enum = #{statusEnum}
LEFT JOIN adm_organization_location AS T5 ON T5.distribution_category_code = t1.category_code AND T5.delete_flag = '0' AND T5.item_code = '2' AND T5.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T5.start_time AND T5.end_time)
WHERE t1.delete_flag = '0'
AND t1.status_enum = #{statusEnum}
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
@@ -204,8 +200,16 @@
T1.yb_no AS yb_no,
'' AS product_name,
-- 前端"类型"列显示目录类别category_code
-- 将category_code转换为整数,用于字典转换字典转换框架会自动填充activityType_dictText
CAST(T1.category_code AS INTEGER) AS activity_type,
-- 🔧 Bug #177 修复:将category_code(中文)转换为对应的整数,用于字典转换
-- 检验->1, 检查->2, 护理->3, 手术->4, 其他->5
CASE T1.category_code
WHEN '检验' THEN 1
WHEN '检查' THEN 2
WHEN '护理' THEN 3
WHEN '手术' THEN 4
WHEN '其他' THEN 5
ELSE 0
END AS activity_type,
NULL AS activity_type_dictText,
-- 前端"包装单位"列显示使用单位permitted_unit_code
T1.permitted_unit_code AS unit_code,
@@ -228,19 +232,18 @@
'' AS dosage_instruction,
T1.chrgitm_lv as chrgitm_lv
FROM wor_activity_definition AS T1
LEFT JOIN adm_charge_item_definition AS T2
ON T2.instance_id = T1.ID
AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum}
AND T2.instance_table = #{activityTableName}
LEFT JOIN adm_organization_location AS T3 ON T3.activity_definition_id = T1.ID
AND T3.delete_flag = '0' AND (CURRENT_TIME :: time (6) BETWEEN T3.start_time AND T3.end_time)
LEFT JOIN adm_charge_item_definition AS T2 ON T2.instance_id = t1.ID AND T2.delete_flag = '0' AND T2.status_enum = #{statusEnum} AND T2.instance_table = #{activityTableName}
LEFT JOIN adm_organization_location AS T3 ON T3.activity_definition_id = t1.ID AND T3.delete_flag = '0' AND (CURRENT_TIME :: time (6) BETWEEN T3.start_time AND T3.end_time)
<if test="organizationId != null">
AND T3.organization_id = #{organizationId}
</if>
WHERE T1.delete_flag = '0'
AND (T1.pricing_flag = 1 OR T1.pricing_flag IS NULL)
WHERE t1.delete_flag = '0'
AND (t1.pricing_flag = 1 OR t1.pricing_flag IS NULL)
<if test="searchKey != null and searchKey != ''">
AND (t1.name ILIKE '%' || #{searchKey} || '%' OR t1.py_str ILIKE '%' || #{searchKey} || '%')
</if>
<if test="adviceDefinitionIdParamList != null and !adviceDefinitionIdParamList.isEmpty()">
AND T1.id IN
AND t1.id IN
<foreach collection="adviceDefinitionIdParamList" item="itemId" open="(" separator="," close=")">
#{itemId}
</foreach>
@@ -675,4 +678,13 @@
AND wad.ID = wsr.activity_id )
</select>
<!-- 获取就诊的默认账户ID -->
<select id="getDefaultAccountId" resultType="java.lang.Long">
SELECT aa.id
FROM adm_account aa
WHERE aa.delete_flag = '0'
AND aa.encounter_id = #{encounterId}
LIMIT 1
</select>
</mapper>

View File

@@ -30,6 +30,7 @@
AND ddo.delete_flag = '0'
WHERE dd.delete_flag = '0'
AND dd.is_valid = 0
<!-- 关键:医院 + 科室联合可见 -->
<if test="organizationId != null and organizationId != 0">
@@ -80,6 +81,10 @@
AND dd.name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="primaryMenuEnum != null">
AND dd.primary_menu_enum = #{primaryMenuEnum}
</if>
GROUP BY dd.id, dd.primary_menu_enum, dd.sub_menu
ORDER BY dd.display_order

View File

@@ -290,46 +290,46 @@
) AS ward ON ward.encounter_id = ae.id
LEFT JOIN (
SELECT aep.encounter_id,
pra.id AS practitioner_id,
aep.practitioner_id AS practitioner_id,
pra."name" AS practitioner_name
FROM adm_encounter_participant aep
LEFT JOIN adm_practitioner pra ON aep.practitioner_id = pra.id AND pra.delete_flag = '0'
WHERE aep.status_enum = #{active}
AND aep.delete_flag = '0'
AND aep.type_code = #{primaryNurse}
AND aep.type_code::text = CAST(#{primaryNurse} AS TEXT)
LIMIT 1
) AS primaryNurse ON primaryNurse.encounter_id = ae.id
LEFT JOIN (
SELECT aep.encounter_id,
pra.id AS practitioner_id,
aep.practitioner_id AS practitioner_id,
pra."name" AS practitioner_name
FROM adm_encounter_participant aep
LEFT JOIN adm_practitioner pra ON aep.practitioner_id = pra.id AND pra.delete_flag = '0'
WHERE aep.status_enum = #{active}
AND aep.delete_flag = '0'
AND aep.type_code = #{attendingDoctor}
AND aep.type_code::text = CAST(#{attendingDoctor} AS TEXT)
LIMIT 1
) AS attendingDoctor ON attendingDoctor.encounter_id = ae.id
LEFT JOIN (
SELECT aep.encounter_id,
pra.id AS practitioner_id,
aep.practitioner_id AS practitioner_id,
pra."name" AS practitioner_name
FROM adm_encounter_participant aep
LEFT JOIN adm_practitioner pra ON aep.practitioner_id = pra.id AND pra.delete_flag = '0'
WHERE aep.status_enum = #{active}
AND aep.delete_flag = '0'
AND aep.type_code = #{admittingDoctor}
AND aep.type_code::text = CAST(#{admittingDoctor} AS TEXT)
LIMIT 1
) AS admittingDoctor ON admittingDoctor.encounter_id = ae.id
LEFT JOIN (
SELECT aep.encounter_id,
pra.id AS practitioner_id,
aep.practitioner_id AS practitioner_id,
pra."name" AS practitioner_name
FROM adm_encounter_participant aep
LEFT JOIN adm_practitioner pra ON aep.practitioner_id = pra.id AND pra.delete_flag = '0'
WHERE aep.status_enum = #{active}
AND aep.delete_flag = '0'
AND aep.type_code = #{chiefDoctor}
AND aep.type_code::text = CAST(#{chiefDoctor} AS TEXT)
LIMIT 1
) AS chiefDoctor ON chiefDoctor.encounter_id = ae.id
LEFT JOIN adm_organization ao
@@ -575,7 +575,7 @@
ON ae.id = aep.encounter_id
AND aep.delete_flag = '0'
AND aep.status_enum = #{active}
AND aep.type_code = #{admittingDoctor}
AND aep.type_code::text = #{admittingDoctor}::text
LEFT JOIN adm_practitioner pra
ON aep.practitioner_id = pra.id
AND pra.delete_flag = '0'
@@ -709,7 +709,7 @@
ON ae.id = aep.encounter_id
AND aep.delete_flag = '0'
AND aep.status_enum = #{active}
AND aep.type_code = #{admittingDoctor}
AND aep.type_code::text = #{admittingDoctor}::text
LEFT JOIN adm_practitioner pra
ON aep.practitioner_id = pra.id
AND pra.delete_flag = '0'

View File

@@ -21,7 +21,7 @@
T1.bus_no AS patient_bus_no, --病历号
T1.birth_date, --患者出生日期
T1.gender_enum, --患者性别
T1.organization_id AS org_id, --科室ID
T2.organization_id AS org_id, --科室ID(从就诊表取)
T2.id AS encounter_id, --就诊ID
T2.start_time AS admissionDate, --入院日期
T3.location_id AS ward_location_id, --病区
@@ -83,7 +83,7 @@
T1.name AS patient_name, --患者姓名
T1.birth_date, --患者出生日期
T1.gender_enum, --患者性别
T1.organization_id AS org_id, --科室ID
T2.organization_id AS org_id, --科室ID(从就诊表取)
T2.id AS encounter_id, --就诊ID
T2.start_time AS admissionDate, --入院日期
T3.location_id AS ward_location_id, --病区

View File

@@ -242,6 +242,14 @@
)
)
</if>
-- 护理级别筛选(多选)
<if test="nursingLevelList != null and nursingLevelList.size() > 0">
AND T2.priority_enum IN
<foreach collection="nursingLevelList" item="level" open="(" separator="," close=")">
#{level}
</foreach>
</if>
) patient_base
) ranked_result
WHERE rn = 1

View File

@@ -34,14 +34,16 @@
ON e.id = sr.encounter_id
AND sr.refund_service_id IS NULL
AND sr.delete_flag = '0'
INNER JOIN med_medication_request mmr
ON mmr.id = sr.based_on_id
AND mmr.delete_flag = '0'
AND mmr.infusion_flag = 1
LEFT JOIN adm_patient pt
ON pt.id = sr.patient_id
AND pt.delete_flag = '0'
LEFT JOIN wor_activity_definition ad
ON ad.id = sr.activity_id
AND ad.delete_flag = '0'
WHERE e.delete_flag = '0'
AND sr.status_enum IN (#{inProgress}, #{completed}, #{cancelled})
AND sr.based_on_table = #{medMedicationRequest}
GROUP BY sr.status_enum,
sr.encounter_id,
e.bus_no,
@@ -106,7 +108,7 @@
mmr.performer_id,
dis.status_enum AS dispense_status,
mmd."name" AS medication_name,
ad."name" AS service_name,
mmd."name" AS service_name,
ap."name" AS practitioner_name,
o."name" AS dept_name,
-- 新增子查询:查询配药人
@@ -128,15 +130,12 @@
AND wsr.delete_flag = '0'
AND wsr.refund_service_id IS NULL
LEFT JOIN med_medication_request mmr
ON mmr.group_id = wsr.group_id
ON mmr.id = wsr.based_on_id
AND mmr.delete_flag = '0'
AND mmr.infusion_flag = 1
LEFT JOIN med_medication_dispense dis
ON dis.med_req_id = mmr.id
AND dis.delete_flag = '0'
LEFT JOIN wor_activity_definition ad
ON ad.id = wsr.activity_id
AND ad.delete_flag = '0'
LEFT JOIN med_medication_definition mmd
ON mmr.medication_id = mmd.id
AND mmd.delete_flag = '0'
@@ -147,7 +146,6 @@
ON o.id = wsr.org_id
AND o.delete_flag = '0'
WHERE ae.id = #{encounterId}
AND ad.category_code = '21'
AND wsr.based_on_table = #{medMedicationRequest}
AND ae.delete_flag = '0') AS pr
${ew.customSqlSegment}
@@ -173,13 +171,16 @@
wsr.occurrence_end_time,
wsr.id AS service_id,
wsr.tenant_id,
ad."name" AS service_name,
mmd."name" AS service_name,
ap."name" AS performer_name,
al."name" AS org_name
FROM wor_service_request wsr
LEFT JOIN wor_activity_definition ad
ON ad.id = wsr.activity_id
AND ad.delete_flag = '0'
LEFT JOIN med_medication_request mmr
ON wsr.based_on_id = mmr.id
AND mmr.delete_flag = '0'
LEFT JOIN med_medication_definition mmd
ON mmr.medication_id = mmd.id
AND mmd.delete_flag = '0'
LEFT JOIN adm_practitioner ap
ON wsr.performer_id = ap.id
AND ap.delete_flag = '0'

View File

@@ -28,6 +28,10 @@
<if test="practitionerId != null">
AND togp.practitioner_id = #{practitionerId}
</if>
<if test="tcmFlag != null">
AND togp.tcm_flag = #{tcmFlag}
</if>
ORDER BY togp.create_time DESC
</select>
<select id="getGroupPackageDetail"

View File

@@ -41,6 +41,7 @@
<result property="merchandiseName" column="merchandise_name"/>
<result property="traceNo" column="trace_no"/>
<result property="partAttributeEnum" column="part_attribute_enum"/>
<result property="dispenseEnum" column="dispense_enum"/>
<collection property="inventoryDetailList" ofType="com.openhis.web.pharmacymanage.dto.InventoryDetailDto">
<result property="inventoryId" column="inventory_id"/>
<result property="maxUnitCode" column="max_unit_code"/>
@@ -169,7 +170,8 @@
ii.inventory_quantity,
ii.inventory_lot_number,
ii.expiration_date,
ii.med_type_code
ii.med_type_code,
ii.dispense_enum
FROM ( SELECT T8."name" AS department_name,
T9.id AS doctor_id,
T9."name" AS doctor_name,
@@ -188,7 +190,7 @@
T1.status_enum,
T2.rate_code,
T1.location_id,
T1.method_code,
T2.method_code,
T1.lot_number,
T2.dose_unit_code,
T2.dispense_per_quantity,
@@ -216,7 +218,8 @@
T14.quantity AS inventory_quantity,
T14.lot_number AS inventory_lot_number,
T14.expiration_date,
T15.med_type_code
T15.med_type_code,
T1.dispense_enum
FROM med_medication_dispense AS T1
LEFT JOIN med_medication_request AS T2
ON T1.med_req_id = T2.id
@@ -252,7 +255,7 @@
ON T1.location_id = T13.id
AND T13.delete_flag = '0'
LEFT JOIN wor_inventory_item AS T14
ON T2.medication_id = T14.item_id
ON T1.medication_id = T14.item_id
AND T1.location_id = T14.location_id
AND T14.delete_flag = '0'
LEFT JOIN adm_encounter_diagnosis AS T15

View File

@@ -57,7 +57,7 @@
WHERE T1.status_enum = #{completed} --'3'
AND T2.status_enum = #{eventStatus} --'4'
AND T1.delete_flag = '0'
AND TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.refund_medicine_id IS NULL
AND T17.refund_medicine_id IS NULL
@@ -89,7 +89,7 @@
WHERE T4.status_enum = #{completed} --'3'
AND T5.status_enum = #{eventStatus} --'4'
AND T4.delete_flag = '0'
AND TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
AND T4.refund_device_id IS NULL
AND T17.refund_device_id IS NULL
) AS q1
@@ -146,7 +146,7 @@
AND T9.item_table = #{medDefTableName} --'med_medication_definition'
AND T12.status_enum = #{eventStatus}--'4'
AND T9.delete_flag = '0'
AND TO_CHAR(T12.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T12.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
UNION ALL
@@ -173,7 +173,7 @@
AND T13.item_table = #{devDefTableName} --'adm_device_definition'
AND T15.status_enum = #{eventStatus}--'4'
AND T13.delete_flag = '0'
AND TO_CHAR(T15.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T15.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
) AS q2
GROUP BY
name,
@@ -219,8 +219,7 @@
ON T2.med_req_id = T1.id
AND T2.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_charge_item T8
ON T8.service_table = #{medReqTableName} --'med_medication_request'
AND T8.service_id = T1.id
@@ -251,8 +250,7 @@
ON T5.device_req_id = T4.id
AND T5.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_charge_item T9
ON T9.service_table = #{devReqTableName} --'wor_device_request'
AND T9.service_id = T4.id
@@ -289,8 +287,7 @@
ON T12.request_id = T9.id
AND T12.delete_flag = '0'
/* 时间区间 */
AND
TO_CHAR(T12.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{dispenseDateSTime} AND #{dispenseDateETime}
AND T12.occurrence_time BETWEEN TO_TIMESTAMP(#{dispenseDateSTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{dispenseDateETime}, 'yyyy-MM-dd HH24:mi:ss')
LEFT JOIN adm_organization T16
ON T9.source_location_id = T16.id
AND T16.delete_flag = '0'

View File

@@ -24,7 +24,7 @@
AND T1.type_enum = #{typeEnum} -- 1:采购入库
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -40,7 +40,7 @@
AND T1.type_enum IN (#{productTransfer} ,#{productBatchTransfer} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -56,7 +56,7 @@
AND T1.type_enum IN (#{productTransfer} ,#{productBatchTransfer} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -72,7 +72,7 @@
AND T1.type_enum IN (#{productStocktaking} ,#{productBatchStocktaking} )
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -88,7 +88,7 @@
AND T1.type_enum = #{lossReportForm}
AND T1.status_enum = #{agree}--同意
AND T2.status_enum = #{completed}--已完成
AND TO_CHAR(T2.occurrence_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.occurrence_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
@@ -114,7 +114,7 @@
AND T1.status_enum = #{completed}--请求状态:已完成
AND T2.status_enum = #{agree}--请求状态:已发药
AND T1.delete_flag = '0'
AND TO_CHAR(T2.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T2.dispense_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
GROUP BY
T3.name,
T7.total_volume,
@@ -139,7 +139,7 @@
AND T4.status_enum = #{completed}--请求状态:已完成
AND T5.status_enum = #{agree}--请求状态:已发药
AND T4.delete_flag = '0'
AND TO_CHAR(T5.dispense_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T5.dispense_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
GROUP BY
T6.name,
T6.size,

View File

@@ -75,7 +75,7 @@
ON T1.encounter_id = T3.id
AND T3.delete_flag = '0'
WHERE T1.status_enum = #{billed}
AND TO_CHAR(T1.update_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.update_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>
<select id="getTreatmentStat"
@@ -93,7 +93,7 @@
-- AS discharged_count--出院人次数
FROM adm_encounter T1
WHERE T1.class_enum = #{amb}
AND TO_CHAR(T1.reception_time, 'yyyy-MM-dd HH24:mi:ss') BETWEEN #{startTime} AND #{endTime}
AND T1.reception_time BETWEEN TO_TIMESTAMP(#{startTime}, 'yyyy-MM-dd HH24:mi:ss') AND TO_TIMESTAMP(#{endTime}, 'yyyy-MM-dd HH24:mi:ss')
AND T1.delete_flag = '0'
</select>

View File

@@ -10,6 +10,7 @@ import com.openhis.administration.mapper.EncounterParticipantMapper;
import com.openhis.administration.service.IEncounterParticipantService;
import com.openhis.common.enums.EncounterActivityStatus;
import com.openhis.common.enums.ParticipantType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@@ -22,6 +23,7 @@ import java.util.List;
* @author system
* @date 2025-02-20
*/
@Slf4j
@Service
public class EncounterParticipantServiceImpl extends ServiceImpl<EncounterParticipantMapper, EncounterParticipant>
implements IEncounterParticipantService {
@@ -57,10 +59,16 @@ public class EncounterParticipantServiceImpl extends ServiceImpl<EncounterPartic
*/
@Override
public void creatEncounterParticipants(Long encounterId, Date startTime, Long practitionerId, String typeCode) {
log.info("创建参与者 - encounterId: {}, startTime: {}, practitionerId: {}, typeCode: {} (type: {}), status: {}",
encounterId, startTime, practitionerId, typeCode, typeCode != null ? typeCode.getClass().getSimpleName() : "null", EncounterActivityStatus.ACTIVE.getValue());
EncounterParticipant encounterParticipant = new EncounterParticipant();
encounterParticipant.setEncounterId(encounterId).setStartTime(startTime).setPractitionerId(practitionerId)
.setTypeCode(typeCode);
baseMapper.insert(encounterParticipant);
encounterParticipant.setEncounterId(encounterId)
.setStartTime(startTime)
.setPractitionerId(practitionerId)
.setTypeCode(typeCode)
.setStatusEnum(EncounterActivityStatus.ACTIVE.getValue());
int result = baseMapper.insert(encounterParticipant);
log.info("创建参与者结果 - encounterId: {}, typeCode: {}, result: {}, insertId: {}", encounterId, typeCode, result, encounterParticipant.getId());
}
/**
@@ -75,7 +83,9 @@ public class EncounterParticipantServiceImpl extends ServiceImpl<EncounterPartic
return baseMapper.update(null,
new LambdaUpdateWrapper<EncounterParticipant>()
.set(EncounterParticipant::getStatusEnum, EncounterActivityStatus.COMPLETED.getValue())
.eq(EncounterParticipant::getEncounterId, encounterId).in(EncounterParticipant::getTypeCode,
.eq(EncounterParticipant::getEncounterId, encounterId)
.eq(EncounterParticipant::getStatusEnum, EncounterActivityStatus.ACTIVE.getValue())
.in(EncounterParticipant::getTypeCode,
ParticipantType.ATTENDING_DOCTOR.getCode(), ParticipantType.CHIEF_DOCTOR.getCode(),
ParticipantType.PRIMARY_NURSE.getCode(), ParticipantType.ADMITTING_DOCTOR.getCode()));
}

View File

@@ -1,5 +1,7 @@
package com.openhis.lab.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.openhis.lab.domain.InspectionType;
@@ -11,4 +13,13 @@ import com.openhis.lab.domain.InspectionType;
*/
public interface IInspectionTypeService extends IService<InspectionType> {
/**
* 分页查询检验类型列表
*
* @param pageNo 页码
* @param pageSize 每页数量
* @param searchKey 搜索关键词(可选)
* @return 分页结果
*/
IPage<InspectionType> getPage(Integer pageNo, Integer pageSize, String searchKey);
}

View File

@@ -1,10 +1,14 @@
package com.openhis.lab.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.openhis.lab.domain.InspectionType;
import com.openhis.lab.mapper.InspectionTypeMapper;
import com.openhis.lab.service.IInspectionTypeService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* 检验类型Service实现类
@@ -15,4 +19,22 @@ import org.springframework.stereotype.Service;
@Service
public class InspectionTypeServiceImpl extends ServiceImpl<InspectionTypeMapper, InspectionType> implements IInspectionTypeService {
@Override
public IPage<InspectionType> getPage(Integer pageNo, Integer pageSize, String searchKey) {
Page<InspectionType> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<InspectionType> queryWrapper = new LambdaQueryWrapper<>();
// 搜索关键词(按编码或名称模糊查询)
if (StringUtils.hasText(searchKey)) {
queryWrapper.and(wrapper -> wrapper
.like(InspectionType::getCode, searchKey)
.or()
.like(InspectionType::getName, searchKey));
}
// 按排序字段升序排列
queryWrapper.orderByAsc(InspectionType::getSortOrder);
return this.page(page, queryWrapper);
}
}

View File

@@ -38,4 +38,9 @@ public class OrdersGroupPackage extends HisBaseEntity {
*/
private Long practitionerId;
/**
* 中医标识0-西医 1-中医
*/
private Integer tcmFlag;
}

View File

@@ -66,4 +66,10 @@ public class OrdersGroupPackageDetail extends HisBaseEntity {
* 组号
*/
private Long groupId;
/**
* 治疗类型1-长期 2-临时
*/
private Integer therapyEnum;
}

View File

@@ -89,4 +89,7 @@ public class ActivityDefinition extends HisBaseEntity {
/** 服务范围 */
private String serviceRange;
/** 检验类型ID关联 inspection_type 表) */
private Long inspectionTypeId;
}

View File

@@ -1,6 +1,6 @@
{
"name": "openhis",
"version": "3.8.8",
"version": "3.8.9",
"description": "OpenHIS管理系统",
"author": "OpenHIS",
"license": "MIT",

View File

@@ -3,8 +3,9 @@
* 从字典动态获取常量值,避免硬编码
*
* 使用方式:
* import { DIAG_TYPE } from '@/utils/medicalConstants';
* import { DIAG_TYPE, RequestStatus } from '@/utils/medicalConstants';
* medTypeCode: DIAG_TYPE.WESTERN_MEDICINE
* serviceStatus: RequestStatus.ACTIVE
*/
import { getDicts } from '@/api/system/dict/data';
@@ -12,6 +13,52 @@ import { getDicts } from '@/api/system/dict/data';
// 诊断类型字典缓存
let diagTypeCache = null;
/**
* 请求状态枚举(与后端 RequestStatus.java 保持一致)
* 用于服务申请、医嘱执行等状态管理
*/
export const RequestStatus = {
/** 待发送 */
DRAFT: 1,
/** 已发送/待执行 */
ACTIVE: 2,
/** 已完成 */
COMPLETED: 3,
/** 暂停 */
ON_HOLD: 4,
/** 取消/待退 */
CANCELLED: 5,
/** 停嘱 */
STOPPED: 6,
/** 不执行 */
ENDED: 7,
/** 未知 */
UNKNOWN: 9,
};
/**
* 请求状态枚举的说明信息
*/
export const RequestStatusDescriptions = {
1: '待发送',
2: '已发送/待执行',
3: '已完成',
4: '暂停',
5: '取消/待退',
6: '停嘱',
7: '不执行',
9: '未知',
};
/**
* 获取请求状态的说明
* @param {number} value - 请求状态值
* @returns {string} - 说明信息
*/
export function getRequestStatusDescription(value) {
return RequestStatusDescriptions[value] || '未知状态';
}
/**
* 获取诊断类型字典(异步初始化)
*/

View File

@@ -241,6 +241,15 @@ export function tansParams(params) {
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
// 处理数组类型
if (Array.isArray(value)) {
for (const item of value) {
if (item !== null && item !== "" && typeof (item) !== 'undefined') {
result += part + encodeURIComponent(item) + "&";
}
}
} else {
// 处理对象类型
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
@@ -248,6 +257,7 @@ export function tansParams(params) {
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}

View File

@@ -0,0 +1,885 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1300px" @close="handleDialogClose">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<div class="dialog-top-row">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" style="width: 220px" />
</el-form-item>
<el-form-item label="使用范围" v-if="showRangeSelector && !isEdit">
<el-select
v-model="rangeSelectValue"
placeholder="个人/科室/全院"
style="width: 220px"
@change="handleRangeChange"
>
<el-option label="个人" value="personal" />
<el-option label="科室" value="department" />
<el-option label="全院" value="hospital" />
</el-select>
</el-form-item>
<el-form-item
label="参与者"
prop="practitionerId"
v-if="currentTab === 'personal' && !isDoctorStation && !showRangeSelector"
>
<el-select
v-model="formData.practitionerId"
placeholder="请选择参与者"
clearable
style="width: 220px"
>
<el-option
v-for="item in participantListOptions"
:key="item.practitionerId"
:label="item.practitionerName"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" prop="organizationId" v-if="currentTab === 'department'">
<el-tree-select
clearable
v-model="formData.organizationId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
default-expand-all
placeholder="请选择科室"
style="width: 220px"
:render-after-expand="false"
@change="handleOrgChange"
/>
</el-form-item>
</div>
</el-form>
<div style="margin-bottom: 10px">
<el-button type="primary" @click="handleAddRow">新增</el-button>
<el-button @click="handleCombineGroup">组合</el-button>
<el-button @click="handleSplitGroup">拆组</el-button>
</div>
<el-table
max-height="650"
ref="prescriptionRef"
:data="prescriptionList"
row-key="uniqueKey"
border
@cell-click="clickRow"
@selection-change="handleSelectionChange"
:expand-row-keys="expandOrder"
>
<el-table-column type="selection" width="55" :selectable="isRowSelectable" />
<el-table-column label="组" align="center" width="60">
<template #default="scope">
<span v-if="scope.row.groupId">{{ getGroupIcon(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="类型" align="center" width="120">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-radio-group v-model="scope.row.therapyEnum" size="small">
<el-radio-button label="1">长期</el-radio-button>
<el-radio-button label="2">临时</el-radio-button>
</el-radio-group>
</template>
<span v-else>
{{
scope.row.therapyEnum == '1' ? '长期' : scope.row.therapyEnum == '2' ? '临时' : '-'
}}
</span>
</template>
</el-table-column>
<el-table-column label="医嘱" align="center" prop="productName" width="300">
<template #default="scope">
<template v-if="getRowDisabled(scope.row)">
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="scope.row.showPopover"
:width="1200"
>
<adviceBaseList
ref="adviceTableRef"
:popoverVisible="scope.row.showPopover"
:adviceQueryParams="adviceQueryParams"
@selectAdviceBase="(row) => selectAdviceBase(scope.row.uniqueKey, row)"
/>
<template #reference>
<el-input
:ref="'adviceRef' + scope.$index"
style="width: 100%"
v-model="scope.row.adviceName"
placeholder="请选择项目"
@input="(value) => handleInput(value, scope.row, scope.$index)"
@click="handleFocus(scope.row, scope.$index)"
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
@keydown="
(e) => {
if (!scope.row.showPopover) return;
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
e.preventDefault();
adviceTableRef.handleKeyDown(e);
}
}
"
@blur="handleBlur(scope.row)"
/>
</template>
</el-popover>
</template>
<span v-else>{{ scope.row.adviceName }}</span>
</template>
</el-table-column>
<el-table-column label="单次剂量" align="center" width="250" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<!-- 新增/未保存行统一按数量 + 单位 = 剂量 + 单位展示 -->
<template v-if="!scope.row.groupPackageId">
<el-input
style="width: 70px; margin-right: 10px"
v-model="scope.row.doseQuantity"
@input="
(value) => {
scope.row.dose = value * scope.row.unitConversionRatio;
}
"
/>
<span>
{{
scope.row.minUnitCode_dictText ||
scope.row.unitCodeName ||
scope.row.unitCode_dictText ||
''
}}
</span>
<span>{{ ' = ' }}</span>
<el-input
style="width: 70px; margin-right: 10px"
v-model="scope.row.dose"
@input="
(value) => {
scope.row.doseQuantity = value / scope.row.unitConversionRatio;
}
"
/>
<span>
{{
scope.row.doseUnitCode_dictText ||
scope.row.unitCodeName ||
scope.row.unitCode_dictText ||
''
}}
</span>
</template>
<span v-else>{{ scope.row.dose }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="给药途径" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-select v-model="scope.row.methodCode" placeholder="给药途径" clearable filterable>
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
<span v-else>{{ scope.row.methodCode }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="用药频次" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-select
v-model="scope.row.rateCode"
placeholder="频次"
style="width: 120px"
filterable
>
<el-option
v-for="dict in getRateOptions(scope.row)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
<span v-else>{{ scope.row.rateCode_dictText }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="用药天数" align="center" width="100" prop="sortNumber">
<template #default="scope">
<template v-if="scope.row.adviceType == 1">
<template v-if="!scope.row.groupPackageId">
<el-input
v-model="scope.row.dispensePerDuration"
@change="handleQuantityChange(scope.row)"
/>
</template>
<span v-else>{{ scope.row.dispensePerDuration }}</span>
</template>
<span v-else>{{ '-' }}</span>
</template>
</el-table-column>
<el-table-column label="总量/执行次数" align="center" width="150" prop="sortNumber">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-input
v-model="scope.row.sortNumber"
type="number"
min="1"
@change="handleQuantityChange(scope.row)"
/>
</template>
<span v-else>{{ scope.row.sortNumber }}</span>
</template>
</el-table-column>
<el-table-column label="单位" align="center" width="120" prop="unitCode">
<template #default="scope">
<template v-if="!scope.row.groupPackageId">
<el-select
v-model="scope.row.selectUnitCode"
placeholder="请选择单位"
@change="handleUnitChange(scope.row)"
>
<el-option
v-if="scope.row.minUnitCode"
:key="scope.row.minUnitCode"
:label="scope.row.minUnitCode_dictText || scope.row.minUnitCode"
:value="scope.row.minUnitCode"
/>
<el-option
v-if="scope.row.unitCode"
:key="scope.row.unitCode"
:label="scope.row.unitCode_dictText || scope.row.unitCode"
:value="scope.row.unitCode"
/>
</el-select>
</template>
<span>{{ scope.row.unitCodeName }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80">
<template #default="scope">
<el-button
type="danger"
icon="Delete"
circle
size="small"
@click="handleDeleteRow(scope.$index, scope.row)"
/>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" :loading="submitLoading"> </el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import adviceBaseList from './adviceBaseList';
import {
queryParticipantList,
queryGroupDetail,
getOrgTree,
} from './api.js';
import { saveOrderGroup } from '@/views/doctorstation/components/api.js';
import useUserStore from '@/store/modules/user';
import { ElMessage } from 'element-plus';
const props = defineProps({
// 是否在“医生站/住院医生站”场景复用个人不选人practitionerId 置空),科室可选科室
isDoctorStation: {
type: Boolean,
default: false,
},
method_code: {
type: Array,
default: () => [],
},
rate_code: {
type: Array,
default: () => [],
},
// 顶部是否显示“个人/科室/全院”切换(用于住院医生站顶部【组套】按钮场景)
showRangeSelector: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['saved']);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const dialogVisible = ref(false);
const dialogTitle = ref('');
const currentTab = ref('personal'); // personal | department | hospital
const isEdit = ref(false);
const submitLoading = ref(false);
const rangeSelectValue = ref('personal');
const participantListOptions = ref([]);
const organization = ref([]);
const adviceQueryParams = reactive({});
const formData = reactive({
groupPackageId: undefined,
name: '',
practitionerId: undefined,
organizationId: undefined,
});
const prescriptionList = ref([]);
const expandOrder = ref([]);
const nextId = ref(1);
const rowIndex = ref(0);
const selectedRows = ref([]);
const groupIndex = ref(1);
const formRef = ref();
const prescriptionRef = ref();
const adviceTableRef = ref();
const formRules = computed(() => {
const rules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
};
// 独立页面:个人必须选参与者;科室必须选科室
if (!props.isDoctorStation && currentTab.value === 'personal') {
rules.practitionerId = [{ required: true, message: '请选择参与者', trigger: 'change' }];
}
if (currentTab.value === 'department') {
rules.organizationId = [{ required: true, message: '请选择科室', trigger: 'change' }];
}
return rules;
});
onMounted(() => {
queryParticipantList().then((res) => {
participantListOptions.value = res.data || [];
});
getOrgTree().then((res) => {
organization.value = res.data?.records || [];
});
});
function openAdd(tab) {
currentTab.value = tab;
isEdit.value = false;
dialogTitle.value = '新增医嘱';
dialogVisible.value = true;
if (props.showRangeSelector) rangeSelectValue.value = tab;
prescriptionList.value = [];
expandOrder.value = [];
nextId.value = 1;
rowIndex.value = 0;
selectedRows.value = [];
groupIndex.value = 1;
formData.groupPackageId = undefined;
formData.name = '';
formData.practitionerId = undefined;
formData.organizationId = undefined;
Object.keys(adviceQueryParams).forEach((key) => {
delete adviceQueryParams[key];
});
prescriptionRef.value?.clearSelection?.();
if (tab === 'personal') {
// 医生站场景:个人组套需要关联当前医生才能查询到
formData.practitionerId = userStore.practitionerId;
} else if (tab === 'department') {
formData.organizationId = userStore.orgId;
}
addEmptyRow();
}
function handleRangeChange(tab) {
openAdd(tab);
}
/**
* 从外部选中的医嘱直接生成组套明细
* @param tab 使用范围personal / department / hospital
* @param rows 选中的处方行列表
*/
function openFromSelection(tab, rows = []) {
currentTab.value = tab;
isEdit.value = false;
dialogTitle.value = '另存组套';
dialogVisible.value = true;
if (props.showRangeSelector) rangeSelectValue.value = tab;
// 重置表单
formData.groupPackageId = undefined;
formData.name = '';
formData.practitionerId = undefined;
formData.organizationId = undefined;
if (tab === 'personal') {
// 医生站场景:个人组套需要关联当前医生才能查询到
formData.practitionerId = userStore.practitionerId;
} else if (tab === 'department') {
formData.organizationId = userStore.orgId;
}
const validRows = (rows || []).filter((i) => i.adviceDefinitionId);
if (validRows.length === 0) {
proxy.$modal.msgWarning('所选医嘱中没有有效的医嘱项,请先选择医嘱后再另存组套');
dialogVisible.value = false;
return;
}
prescriptionList.value = validRows.map((row, index) => {
let therapyEnum = row.therapyEnum;
if (!therapyEnum && row.contentJson) {
try {
const content = JSON.parse(row.contentJson);
therapyEnum = content.therapyEnum;
} catch (e) {}
}
therapyEnum = therapyEnum != null ? String(therapyEnum) : '1';
return {
uniqueKey: index + 1,
showPopover: false,
check: false,
isEdit: false,
statusEnum: 1,
adviceDefinitionId: row.adviceDefinitionId,
adviceTableName: row.adviceTableName || 'advice_definition',
sortNumber: row.quantity ?? row.sortNumber ?? 1,
selectUnitCode: row.selectUnitCode || row.unitCode,
adviceName: row.adviceName || row.productName,
unitCodeName: row.unitCodeName || row.unitCode_dictText || '',
methodCode: row.methodCode,
rateCode: row.rateCode,
dispensePerDuration: row.dispensePerDuration,
dose: row.dose,
doseQuantity: row.doseQuantity,
minUnitCode: row.minUnitCode,
minUnitCode_dictText: row.minUnitCode_dictText,
unitCode: row.unitCode,
unitCode_dictText: row.unitCode_dictText,
groupId: row.groupId,
groupOrder: row.groupOrder,
therapyEnum: therapyEnum,
adviceType: row.adviceType,
};
});
expandOrder.value = [];
nextId.value = prescriptionList.value.length + 1;
}
function openEdit(tab, row) {
currentTab.value = tab;
isEdit.value = true;
dialogTitle.value = '编辑医嘱';
dialogVisible.value = true;
formData.groupPackageId = row.groupPackageId;
formData.name = row.name;
formData.practitionerId = row.practitionerId;
formData.organizationId = row.organizationId;
queryGroupDetail({ groupPackageId: row.groupPackageId }).then((res) => {
const detailList = res.data || [];
prescriptionList.value = detailList.map((item, index) => {
const therapyEnum = item.therapyEnum != null ? String(item.therapyEnum) : '1'; // 统一转成字符串
return {
uniqueKey: index + 1,
showPopover: false,
check: false,
isEdit: false,
statusEnum: 1,
groupPackageId: item.groupPackageId,
adviceDefinitionId: item.orderDefinitionId,
adviceTableName: item.orderDefinitionTable,
sortNumber: item.quantity,
selectUnitCode: item.unitCode,
adviceName: item.orderDefinitionName,
unitCodeName: item.unitCodeName,
groupId: item.groupId,
groupOrder: item.groupOrder,
therapyEnum, // 长期/临时类型1-长期2-临时
// 回显单次剂量/给药途径/用药频次/天数等字段
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
// 医嘱类型(药品=1没有则按表名推断药品表 -> 药品)
adviceType:
item.adviceType !== undefined
? item.adviceType
: item.orderDefinitionTable === 'med_medication_definition'
? 1
: undefined,
};
});
nextId.value = prescriptionList.value.length + 1;
addEmptyRow();
});
}
function addEmptyRow() {
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
therapyEnum: '1', // 默认长期
});
}
function handleAddRow() {
addEmptyRow();
}
function handleSelectionChange(selection) {
selectedRows.value = selection || [];
}
function handleCombineGroup() {
const rows = selectedRows.value || [];
if (rows.length <= 1) {
proxy.$modal.msgWarning('至少选择两项');
return;
}
// 必须都已选择医嘱
if (rows.some((r) => !r.adviceDefinitionId)) {
proxy.$modal.msgWarning('请先完成医嘱选择');
return;
}
// 相同分组用法需要相同
const methodSet = new Set();
rows.forEach((r) => {
if (r.methodCode != null) methodSet.add(r.methodCode);
});
if (methodSet.size > 1) {
proxy.$modal.msgWarning('同一分组药品用法必须相同');
return;
}
// 相同分组频次需要相同
const rateSet = new Set();
rows.forEach((r) => {
if (r.rateCode != null) rateSet.add(r.rateCode);
});
if (rateSet.size > 1) {
proxy.$modal.msgWarning('同一分组药品频次必须相同');
return;
}
const timestamp = Date.now().toString();
const gid = timestamp + groupIndex.value;
const newList = [...prescriptionList.value];
rows.forEach((item, selectIndex) => {
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
if (idx !== -1) {
newList[idx].groupId = gid;
newList[idx].groupOrder = selectIndex + 1;
}
});
groupIndex.value += 1;
prescriptionList.value = newList;
prescriptionRef.value?.clearSelection?.();
proxy.$modal.msgSuccess('组套成功');
}
function isRowSelectable(row) {
return !!row.adviceDefinitionId;
}
function getGroupIcon(row) {
if (!row.groupId) return '';
const sameGroup = prescriptionList.value
.filter((item) => item.groupId === row.groupId)
.sort((a, b) => {
const aOrder = a.groupOrder ?? 0;
const bOrder = b.groupOrder ?? 0;
return aOrder - bOrder;
});
if (sameGroup.length === 0) return '';
const index = sameGroup.findIndex((item) => item.uniqueKey === row.uniqueKey);
if (index === 0) {
return '┏';
}
if (index === sameGroup.length - 1) {
return '┗';
}
return '┃';
}
function getRateOptions(row) {
const all = props.rate_code || [];
if (row?.therapyEnum == '2') {
return all.filter(
(dict) => dict.value === 'ST' || (dict.label && dict.label.indexOf('立即') !== -1)
);
}
return all;
}
function handleSplitGroup() {
const rows = selectedRows.value || [];
if (rows.length < 1) {
proxy.$modal.msgWarning('至少选择一项');
return;
}
if (rows.some((r) => !r.groupId)) {
proxy.$modal.msgWarning('包含非组合数据无法拆组');
return;
}
const newList = [...prescriptionList.value];
rows.forEach((item) => {
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
if (idx !== -1) {
newList[idx].groupId = undefined;
newList[idx].groupOrder = undefined;
}
});
prescriptionList.value = newList;
prescriptionRef.value?.clearSelection?.();
proxy.$modal.msgSuccess('拆组成功');
}
function submitForm() {
formRef.value.validate((valid) => {
if (!valid) return;
// 验证至少选择了一条医嘱
const detailList = prescriptionList.value.filter((item) => item.adviceDefinitionId);
if (detailList.length === 0) {
proxy?.$modal?.msgWarning?.('请至少选择一条医嘱');
return;
}
submitLoading.value = true;
setTimeout(() => {
console.log('[submitForm] 当前tab:', currentTab.value);
const params = { ...formData };
console.log('[submitForm] formData:', formData);
// 添加 rangeCode 字段,用于后端判断组套类型
if (currentTab.value === 'personal') {
params.rangeCode = 1;
} else if (currentTab.value === 'department') {
params.rangeCode = 2;
} else {
params.rangeCode = 3;
}
console.log('[submitForm] 设置后的rangeCode:', params.rangeCode);
// 医生站场景:个人组套需要设置 practitionerId 为当前医生,否则查询时查不到
console.log('[submitForm] isDoctorStation:', props.isDoctorStation, 'currentTab:', currentTab.value);
console.log('[submitForm] userStore.practitionerId:', userStore.practitionerId);
if (props.isDoctorStation && currentTab.value === 'personal') {
params.practitionerId = userStore.practitionerId;
console.log('[submitForm] 已设置 practitionerId:', params.practitionerId);
} else {
console.log('[submitForm] 未设置 practitionerId, 当前值:', params.practitionerId);
}
params.detailList = detailList.map((item) => ({
orderDefinitionId: item.adviceDefinitionId,
orderDefinitionTable: item.adviceTableName,
quantity: item.sortNumber,
unitCode: item.selectUnitCode,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
groupId: item.groupId,
groupOrder: item.groupOrder,
therapyEnum: item.therapyEnum || '1',
}));
console.log('[submitForm] 准备保存组套, params:', params);
// 使用 saveOrderGroup它会根据 rangeCode 自动选择正确的API
saveOrderGroup(params)
.then((res) => {
console.log('[submitForm] 保存API返回:', res);
if (res.code === 200) {
ElMessage.success(res.message || '保存成功');
console.log('[submitForm] 准备触发 saved 事件');
emit('saved');
console.log('[submitForm] saved 事件已触发');
dialogVisible.value = false;
prescriptionList.value = [];
} else {
proxy?.$modal?.msgError?.(res.message || '保存失败');
}
})
.catch((err) => {
console.error('保存失败:', err);
proxy?.$modal?.msgError?.(err?.message || '保存失败,请检查网络或联系管理员');
})
.finally(() => {
submitLoading.value = false;
});
}, 200);
});
}
function selectAdviceBase(key, row) {
const currentRow = prescriptionList.value[rowIndex.value];
const preservedTherapyEnum = currentRow?.therapyEnum || row.therapyEnum || '1';
prescriptionList.value[rowIndex.value] = {
...prescriptionList.value[rowIndex.value],
...JSON.parse(JSON.stringify(row)),
};
prescriptionList.value[rowIndex.value].dose = undefined;
prescriptionList.value[rowIndex.value].doseQuantity = undefined;
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode;
prescriptionList.value[rowIndex.value].unitCode =
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
prescriptionList.value[rowIndex.value].isEdit = false;
prescriptionList.value[rowIndex.value].definitionId = JSON.parse(
JSON.stringify(row)
).chargeItemDefinitionId;
prescriptionList.value[rowIndex.value].therapyEnum = preservedTherapyEnum;
addEmptyRow();
expandOrder.value = [key];
}
function handleFocus(row, index) {
rowIndex.value = index;
row.showPopover = true;
adviceQueryParams.searchKey = row.adviceName || '';
// 设置 organizationId否则 adviceBaseList 会跳过 API 调用
adviceQueryParams.organizationId = userStore.orgId;
}
function handleInput(value, row, index) {
adviceQueryParams.searchKey = value || '';
handleFocus(row, index);
}
function handleBlur(row) {
row.showPopover = false;
}
function clickRow() {}
function getRowDisabled(row) {
return row.isEdit;
}
function handleQuantityChange(row) {
if (row.sortNumber && row.sortNumber > 0) row.sortNumber = parseInt(row.sortNumber);
else row.sortNumber = 1;
prescriptionList.value = [...prescriptionList.value];
}
function handleUnitChange(row) {
prescriptionList.value = [...prescriptionList.value];
}
function handleDeleteRow(index) {
if (prescriptionList.value.length <= 1) {
proxy.$modal.msgWarning('至少保留一行');
return;
}
proxy.$modal
.confirm('确定要删除该行吗?')
.then(() => {
prescriptionList.value.splice(index, 1);
proxy.$modal.msgSuccess('删除成功');
})
.catch(() => {});
}
function isLeafNode(node) {
return !node.children || node.children.length === 0;
}
function handleDialogClose() {
formRef.value?.resetFields?.();
prescriptionList.value = [];
}
function handleOrgChange(value) {
if (!value) return;
const findNode = (nodes) => {
for (let node of nodes) {
if (node.id === value) return node;
if (node.children && node.children.length > 0) {
const found = findNode(node.children);
if (found) return found;
}
}
return null;
};
const selectedNode = findNode(organization.value);
if (selectedNode && !isLeafNode(selectedNode)) {
proxy.$modal.msgWarning('只能选择末级科室');
formData.organizationId = null;
}
}
defineExpose({
openAdd,
openEdit,
openFromSelection,
});
</script>
<style scoped>
.dialog-top-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.dialog-top-row :deep(.el-form-item) {
margin-bottom: 0;
}
</style>

View File

@@ -1,12 +1,13 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
<el-table
ref="adviceBaseRef"
height="400"
height="350"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="patientId"
v-loading="loading"
@cell-click="clickRow"
>
<el-table-column label="名称" align="center" prop="adviceName" />
@@ -21,13 +22,23 @@
<el-table-column label="注射药品" align="center" prop="injectFlag_enumText" />
<el-table-column label="皮试" align="center" prop="skinTestFlag_enumText" />
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
:page-size="20"
:total="total"
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import {nextTick} from 'vue';
import {getAdviceBaseInfo} from './api';
import {throttle} from 'lodash-es';
import {debounce} from 'lodash-es';
const props = defineProps({
adviceQueryParams: {
@@ -41,33 +52,85 @@ const props = defineProps({
});
const emit = defineEmits(['selectAdviceBase']);
const total = ref(0);
const loading = ref(false);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0); // 当前选中行索引
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 100,
pageSize: 20,
pageNum: 1,
adviceTypes: '1,2,3',
organizationId: null,
});
const adviceBaseList = ref([]);
// 节流函数
const throttledGetList = throttle(
// 前端缓存 - 使用 sessionStorage 持久化缓存
const CACHE_PREFIX = 'advice_cache_';
const CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
function getCacheKey(orgId, pageNum) {
return CACHE_PREFIX + orgId + '_' + pageNum;
}
function getFromCache(orgId, pageNum) {
try {
const key = getCacheKey(orgId, pageNum);
const cachedData = sessionStorage.getItem(key);
if (!cachedData) return null;
const cacheData = JSON.parse(cachedData);
if (Date.now() - cacheData.timestamp > CACHE_EXPIRE) {
sessionStorage.removeItem(key);
return null;
}
console.log('从前端缓存获取:', key);
return cacheData;
} catch (e) {
console.error('读取缓存失败:', e);
return null;
}
}
function setToCache(orgId, pageNum, data, total) {
try {
const key = getCacheKey(orgId, pageNum);
const cacheData = {
data: data,
total: total,
timestamp: Date.now()
};
sessionStorage.setItem(key, JSON.stringify(cacheData));
console.log('写入前端缓存:', key);
} catch (e) {
console.error('写入缓存失败:', e);
}
}
// 防抖函数 - 避免重复请求
const debouncedGetList = debounce(
() => {
// 只有当 organizationId 有效时才请求
if (!queryParams.value.organizationId) {
console.log('organizationId 无效,跳过请求');
return;
}
getList();
},
300,
{ leading: true, trailing: true }
{ leading: false, trailing: true }
);
// 监听adviceQueryParams变化
watch(
() => props.adviceQueryParams,
(newValue) => {
console.log('adviceQueryParams 变化:', newValue);
queryParams.value.searchKey = newValue.searchKey;
queryParams.value.adviceType = newValue.adviceType;
console.log(queryParams.value);
throttledGetList();
queryParams.value.organizationId = newValue.organizationId;
console.log('更新后的queryParams:', queryParams.value);
debouncedGetList();
},
{ deep: true }
);
@@ -77,26 +140,77 @@ watch(
() => props.adviceQueryParams?.searchKey,
(newVal) => {
queryParams.value.searchKey = newVal;
throttledGetList();
debouncedGetList();
}
);
getList();
// 监听organizationId变化
watch(
() => props.adviceQueryParams?.organizationId,
(newVal) => {
console.log('watch organizationId 变化:', newVal);
queryParams.value.organizationId = newVal;
// 当organizationId有效时重新获取数据
if (newVal) {
debouncedGetList();
}
}
);
// 不再页面加载时立即请求,等待用户点击时再请求
function getList() {
// 确保有 organizationId 才调用API
if (!queryParams.value.organizationId) {
console.log('organizationId 为空跳过API调用');
return;
}
const orgId = queryParams.value.organizationId;
const pageNum = queryParams.value.pageNum;
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
if (pageNum === 1 && !queryParams.value.searchKey) {
const cached = getFromCache(orgId, pageNum);
if (cached) {
adviceBaseList.value = cached.data;
total.value = cached.total;
return;
}
}
// 显示 loading
loading.value = true;
// queryParams.value.organizationId = '1922545444781481985';
getAdviceBaseInfo(queryParams.value).then((res) => {
adviceBaseList.value = res.data.records;
console.log(adviceBaseList.value)
total.value = res.data.total;
adviceBaseList.value = res.data.records || [];
console.log('获取到医嘱数据:', adviceBaseList.value)
total.value = res.data.total || 0;
// 缓存第一页数据
if (pageNum === 1 && !queryParams.value.searchKey) {
setToCache(orgId, pageNum, adviceBaseList.value, total.value);
}
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0) {
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
})
.finally(() => {
loading.value = false;
});
}
// 当前页改变(分页)
function handlePageChange(page) {
queryParams.value.pageNum = page;
getList();
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
@@ -155,7 +269,17 @@ defineExpose({
</script>
<style scoped>
.table-container {
display: flex;
flex-direction: column;
}
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 10px 0;
background: #fff;
}
.popover-table-wrapper:focus {
outline: 2px solid #409eff; /* 聚焦时的高亮效果 */
outline: 2px solid #409eff;
}
</style>

View File

@@ -401,10 +401,10 @@ const { method_code, rate_code, distribution_category_code } = proxy.useDict(
'rate_code',
'distribution_category_code'
);
// 查询参数
const personalQuery = reactive({ searchKey: '' });
const departmentQuery = reactive({ searchKey: '' });
const hospitalQuery = reactive({ searchKey: '' });
// 查询参数 - 西医组套 tcmFlag = 0
const personalQuery = reactive({ searchKey: '', tcmFlag: 0 });
const departmentQuery = reactive({ searchKey: '', tcmFlag: 0 });
const hospitalQuery = reactive({ searchKey: '', tcmFlag: 0 });
// 加载状态
const loading = reactive({
@@ -462,18 +462,15 @@ function getInit() {
});
}
// 获取所有数据
function fetchAllData(params = {}) {
getPersonalListData(params);
getDepartmentListData(params);
getHospitalListData(params);
// 获取所有数据(不传参,保留查询框已有的参数)
function fetchAllData() {
getPersonalListData();
getDepartmentListData();
getHospitalListData();
}
// 获取个人医嘱列表
function getPersonalListData(params = {}) {
// 合并查询参数
Object.assign(personalQuery, params);
function getPersonalListData() {
loading.personal = true;
getPersonalList(personalQuery)
.then((response) => {
@@ -488,10 +485,7 @@ function getPersonalListData(params = {}) {
}
// 获取科室医嘱列表
function getDepartmentListData(params = {}) {
// 合并查询参数
Object.assign(departmentQuery, params);
function getDepartmentListData() {
loading.department = true;
getDeptList(departmentQuery)
.then((response) => {
@@ -506,10 +500,7 @@ function getDepartmentListData(params = {}) {
}
// 获取全院医嘱列表
function getHospitalListData(params = {}) {
// 合并查询参数
Object.assign(hospitalQuery, params);
function getHospitalListData() {
loading.hospital = true;
getAllList(hospitalQuery)
.then((response) => {
@@ -627,10 +618,11 @@ function submitForm() {
if (valid) {
submitLoading.value = true;
// 模拟提交操作这里应该调用相应的API
setTimeout(() => {
// 提交数据
console.log('提交表单数据:', formData);
let params = { ...formData };
// 添加 tcmFlag西医组套为 0
params.tcmFlag = 0;
// 过滤掉空列表项没有adviceDefinitionId的项
params.detailList = prescriptionList.value
.filter((item) => item.adviceDefinitionId) // 过滤掉空列表项
@@ -647,49 +639,70 @@ function submitForm() {
doseQuantity: item.doseQuantity,
};
});
console.log('保存参数:', params);
// 编辑模式
switch (currentTab.value) {
case 'personal':
savePersonal(params).then((res) => {
console.log('保存个人组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存个人组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'department':
saveDepartment(params).then((res) => {
console.log('保存科室组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存科室组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'hospital':
saveAll(params).then((res) => {
console.log('保存全院组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.message);
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存全院组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
}
}, 500);
}
});
}
@@ -728,6 +741,10 @@ function handleFocus(row, index) {
row.showPopover = true;
// 将输入框的值传递给adviceBaseList组件作为查询条件
adviceQueryParams.searchKey = row.adviceName || '';
// 获取当前登录用户的科室ID
const userStore = useUserStore();
adviceQueryParams.organizationId = userStore.orgId;
console.log('handleFocus - organizationId:', userStore.orgId, 'userStore:', userStore);
}
// 处理输入事件

View File

@@ -0,0 +1,129 @@
import request from '@/utils/request'
/**
* 获取个人组套
* @param {*} queryParams
*/
export function getPersonalList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-personal',
method: 'get',
params: queryParams
})
}
/**
* 获取科室组套
* @param {*} queryParams
*/
export function getDeptList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-organization',
method: 'get',
params: queryParams
})
}
/**
* 获取全院组套
* @param {*} queryParams
*/
export function getAllList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-hospital',
method: 'get',
params: queryParams
})
}
/**
* 保存个人组套
* @param {*} data
*/
export function savePersonal(data) {
return request({
url: '/personalization/orders-group-package/save-personal',
method: 'post',
data: data
})
}
/**
* 保存科室组套
* @param {*} data
*/
export function saveDepartment(data) {
return request({
url: '/personalization/orders-group-package/save-organization',
method: 'post',
data: data
})
}
/**
* 保存全院组套
* @param {*} data
*/
export function saveAll(data) {
return request({
url: '/personalization/orders-group-package/save-hospital',
method: 'post',
data: data
})
}
/**
* 查询组套明细
* @param {*} data
*/
export function queryGroupDetail(params) {
return request({
url: '/personalization/orders-group-package/get-group-package-detail',
method: 'get',
params: params
})
}
/**
* 删除组套
* @param {*} data
*/
export function deleteGroup(data) {
return request({
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
method: 'delete'
})
}
/**
* 查询参与者下拉列表
* @param {*} data
*/
export function queryParticipantList(params) {
return request({
url: '/app-common/practitioner-list',
method: 'get',
params: params
})
}
/**
* 获取科室列表
*/
export function getOrgTree() {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
})
}
/**
* 获取中药列表
*/
export function getTcmMedicine(params) {
return request({
url: '/doctor-station/chinese-medical/tcm-advice-base-info',
method: 'get',
params: params,
});
}

View File

@@ -0,0 +1,269 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
<el-table
ref="adviceBaseRef"
height="350"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceCode"
v-loading="loading"
@cell-click="clickRow"
>
<el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center" prop="categoryCodeText" />
<el-table-column label="医保等级" align="center" prop="chrgitmLv_dictText" />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="库存数量" align="center">
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
:page-size="20"
:total="total"
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { getTcmMedicine } from './api';
import { debounce } from 'lodash-es';
const props = defineProps({
searchKey: {
type: String,
default: '',
},
organizationId: {
type: String,
default: '',
},
});
const emit = defineEmits(['selectMedicine']);
const total = ref(0);
const loading = ref(false);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0);
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 20,
pageNum: 1,
searchKey: '',
organizationId: props.organizationId,
});
const adviceBaseList = ref([]);
// 前端缓存 - 使用 sessionStorage 持久化缓存
const TCM_CACHE_PREFIX = 'tcm_advice_cache_';
const TCM_CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
function getTcmCacheKey(orgId, pageNum, searchKey) {
return TCM_CACHE_PREFIX + orgId + '_' + pageNum + '_' + (searchKey || 'none');
}
function getFromTcmCache(orgId, pageNum, searchKey) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cachedData = sessionStorage.getItem(key);
if (!cachedData) return null;
const cacheData = JSON.parse(cachedData);
if (Date.now() - cacheData.timestamp > TCM_CACHE_EXPIRE) {
sessionStorage.removeItem(key);
return null;
}
console.log('从TCM前端缓存获取:', key);
return cacheData;
} catch (e) {
console.error('读取TCM缓存失败:', e);
return null;
}
}
function setToTcmCache(orgId, pageNum, searchKey, data, total) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cacheData = {
data: data,
total: total,
timestamp: Date.now()
};
sessionStorage.setItem(key, JSON.stringify(cacheData));
console.log('写入TCM前端缓存:', key);
} catch (e) {
console.error('写入TCM缓存失败:', e);
}
}
// 防抖函数 - 避免重复请求
const debouncedGetList = debounce(
() => {
// 只有当 organizationId 有效时才请求
if (!queryParams.value.organizationId) {
console.log('organizationId 无效,跳过请求');
return;
}
getList();
},
300,
{ leading: false, trailing: true }
);
// 监听搜索关键字变化
watch(
() => props.searchKey,
(newValue) => {
queryParams.value.searchKey = newValue || '';
debouncedGetList();
},
{ deep: true }
);
// 监听 organizationId 变化
watch(
() => props.organizationId,
(newValue) => {
queryParams.value.organizationId = newValue;
// organizationId 变化时重新加载
if (newValue) {
debouncedGetList();
}
},
{ deep: true }
);
// 不再页面加载时立即请求,等待用户点击时再请求
function getList() {
const orgId = queryParams.value.organizationId;
const pageNum = queryParams.value.pageNum;
const searchKey = queryParams.value.searchKey;
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
if (pageNum === 1 && !searchKey) {
const cached = getFromTcmCache(orgId, pageNum, searchKey);
if (cached) {
adviceBaseList.value = cached.data;
total.value = cached.total;
return;
}
}
// 显示 loading
loading.value = true;
getTcmMedicine(queryParams.value).then((res) => {
adviceBaseList.value = res.data.records || [];
total.value = res.data.total || 0;
// 缓存第一页数据
if (pageNum === 1 && !searchKey) {
setToTcmCache(orgId, pageNum, searchKey, adviceBaseList.value, total.value);
}
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
})
.finally(() => {
loading.value = false;
});
}
// 当前页改变
function handlePageChange(page) {
queryParams.value.pageNum = page;
getList();
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp':
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown':
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter':
event.preventDefault();
if (currentSelectRow.value) {
emit('selectMedicine', currentSelectRow.value);
}
break;
}
};
function handleQuantity(row) {
if (row.inventoryList) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
}
return 0;
}
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引
const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
function clickRow(row) {
emit('selectMedicine', row);
}
defineExpose({
handleKeyDown,
});
</script>
<style scoped>
.table-container {
display: flex;
flex-direction: column;
}
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 10px 0;
background: #fff;
}
.popover-table-wrapper:focus {
outline: 2px solid #409eff;
}
</style>

View File

@@ -0,0 +1,820 @@
<template>
<div class="app-container tcm-medical-order-set">
<el-row :gutter="20" class="full-height">
<!-- 左侧树形结构 -->
<el-col :span="5" class="left-panel">
<el-card shadow="never" class="tree-card panel-shadow">
<template #header>
<span>中医组套</span>
</template>
<el-tree
:data="treeData"
node-key="id"
:props="treeProps"
default-expand-all
highlight-current
@node-click="(data, node) => handleTreeNodeClick(data, node)"
/>
</el-card>
</el-col>
<!-- 右侧操作区域 -->
<el-col :span="19" class="right-panel">
<!-- 上方表单:组套公共字段 -->
<el-card shadow="never" class="form-card panel-shadow">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
<el-button type="primary" plain @click="clearForm">清空 </el-button>
<el-button type="primary" @click="createNew">新建 </el-button>
<el-button type="primary" @click="saveCommonForm">保存 </el-button>
</div>
<div class="common-bar">
<el-form :model="commonForm" :inline="true" class="demo-form-inline">
<el-form-item label="组套名称">
<el-input v-model="commonForm.name" placeholder="组套名称" clearable />
</el-form-item>
<el-form-item label="使用范围">
<el-select
v-model="commonForm.useRange"
placeholder="个人/科室/全院"
clearable
@change="handleRangeChange"
>
<el-option label="个人" value="personal" />
<el-option label="科室" value="department" />
<el-option label="全院" value="hospital" />
</el-select>
</el-form-item>
<el-form-item label="使用人" v-if="commonForm.useRange === 'personal'">
<el-select
v-model="commonForm.practitionerId"
placeholder="请选择使用人"
clearable
style="width: 200px"
>
<el-option
v-for="item in participantListOptions"
:key="item.practitionerId"
:label="item.practitionerName"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" v-if="commonForm.useRange === 'department'">
<el-tree-select
clearable
v-model="commonForm.organizationId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
default-expand-all
placeholder="请选择科室"
style="width: 200px"
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="用法">
<el-select v-model="commonForm.method" placeholder="用法" clearable>
<el-option
v-for="item in method_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="commonForm.frequency" placeholder="频次" clearable>
<el-option
v-for="item in rate_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 下方:中药明细卡片列表 -->
<el-card shadow="never" class="detail-card panel-shadow" style="height: 100%">
<div class="drug-card-list">
<!-- 已有药品卡片 -->
<el-card
v-for="(item, index) in drugList"
:key="item.id"
class="drug-card"
shadow="never"
>
<template v-if="!item.isEditing">
<div class="drug-card-body">
<div class="drug-top">
<div class="drug-view-name">{{ item.drugName || '-' }}</div>
</div>
<div class="drug-divider"></div>
<div class="drug-bottom" style="margin-top: 10px">
<div class="drug-bottom-left">
<span class="drug-view-qty">{{ item.quantity }} {{ item.unit }}</span>
</div>
<div class="drug-view-actions">
<el-button
type="primary"
text
:icon="Edit"
@click.stop="editDrug(item)"
title="编辑"
/>
<el-button
v-if="drugList.length > 1"
type="danger"
text
:icon="Delete"
@click.stop="removeDrug(index)"
title="删除"
/>
</div>
</div>
</div>
</template>
<template v-else>
<div class="drug-card-body">
<div class="drug-top">
<el-form :model="item" class="drug-card-form">
<el-form-item>
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="item.showPopover"
:width="800"
>
<tcmMedicineList
:ref="(el) => setMedicineTableRef(el, item)"
:searchKey="item.searchKey"
:organizationId="commonForm.organizationId"
@selectMedicine="(row) => handleSelectMedicine(row, item)"
/>
<template #reference>
<el-input
v-model="item.drugName"
placeholder="请选择中药"
clearable
@input="(val) => handleSearchKeyChange(val, item)"
@click="() => handleClick(item)"
@keydown="(e) => handleKeyDown(e, item)"
@blur="() => handleBlur(item)"
/>
</template>
</el-popover>
</el-form-item>
</el-form>
</div>
<div class="drug-bottom">
<div class="drug-bottom-left">
<div class="quantity-wrap">
<el-input v-model="item.quantity" />
<el-input value="g" style="width: 80px" disabled />
</div>
</div>
</div>
<div class="drug-edit-actions">
<el-button type="primary" link @click.stop="saveDrug(item)"> 保存 </el-button>
<el-button type="danger" link @click.stop="removeDrug(index)"> 删除 </el-button>
</div>
</div>
</template>
</el-card>
<!-- 始终存在的新增卡片 -->
<el-card class="drug-card add-card" shadow="never" @click="addDrug">
<div class="add-card-content">
<el-icon class="add-icon">
<Plus />
</el-icon>
<span>新增</span>
</div>
</el-card>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="TcmMedicalOrderSet">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage } from 'element-plus';
import {
getPersonalList,
getDeptList,
getAllList,
queryGroupDetail,
queryParticipantList,
getOrgTree,
savePersonal,
saveDepartment,
saveAll,
} from './components/api';
import TcmMedicineList from './components/tcmMedicineList.vue';
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance();
// 字典数据
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
// 左侧树形结构数据
const treeData = ref([]);
const treeProps = {
children: 'children',
label: 'label',
value: 'value',
};
// 加载左侧树数据
function loadTreeData() {
Promise.all([
getPersonalList({ tcmFlag: 1 }),
getDeptList({ tcmFlag: 1 }),
getAllList({ tcmFlag: 1 }),
])
.then(([personalRes, deptRes, allRes]) => {
const personalList = personalRes?.data || [];
const deptList = deptRes?.data || [];
const allList = allRes?.data || [];
treeData.value = [
{
id: 'personal',
label: '个人',
children: personalList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: item.practitionerId || '',
organizationId: '',
})),
},
{
id: 'department',
label: '科室',
children: deptList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: item.organizationId || '',
})),
},
{
id: 'hospital',
label: '全院',
children: allList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: '',
})),
},
];
})
.catch(() => {
ElMessage.error('加载组套树失败');
});
}
onMounted(() => {
loadTreeData();
loadParticipantList();
loadOrganization();
});
// 使用人列表
const participantListOptions = ref([]);
// 科室树形数据
const organization = ref([]);
// 加载使用人列表
function loadParticipantList() {
queryParticipantList().then((res) => {
participantListOptions.value = res.data || [];
});
}
// 加载科室树
function loadOrganization() {
getOrgTree().then((res) => {
organization.value = res.data.records || [];
});
}
// 使用范围变更
function handleRangeChange() {
commonForm.practitionerId = '';
commonForm.organizationId = '';
}
// 上方公共字段表单
const commonForm = reactive({
name: '',
useRange: '',
practitionerId: '',
organizationId: '',
method: '',
frequency: '',
});
// 下方药品卡片列表
const drugList = ref([]);
const nextDrugId = ref(1);
const currentSetName = ref('');
// 动态设置表格组件引用
function setMedicineTableRef(el, item) {
if (el) {
item.tableRef = el;
}
}
function createEmptyDrug() {
return {
id: nextDrugId.value++,
drugCode: '',
drugName: '',
orderDefinitionId: '',
quantity: 1,
unitCode: '',
unit: 'g',
isEditing: true,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: '',
methodCode: '',
};
}
// 点击树节点
function handleTreeNodeClick(data, node) {
if (data.children != undefined) {
return;
}
// 从父节点获取使用范围personal/department/hospital
const useRange = node.parent?.data?.id || '';
// 组套名称从树节点获取
commonForm.name = data.label || '';
commonForm.useRange = useRange;
// 从树节点数据中获取使用人/科室
commonForm.practitionerId = data.practitionerId || '';
commonForm.organizationId = data.organizationId || '';
// 调用查询接口回显数据
queryGroupDetail({ groupPackageId: data.value })
.then((res) => {
const detailList = res.data || [];
if (detailList.length === 0) {
drugList.value = [createEmptyDrug()];
return;
}
// 取第一个药品的频次和用法赋值到表单
const firstDrug = detailList[0];
commonForm.method = firstDrug.methodCode || '';
commonForm.frequency = firstDrug.rateCode || '';
// 回显药品列表
drugList.value = detailList.map((detail, index) => ({
id: index,
drugCode: '',
drugName: detail.orderDefinitionName || '',
orderDefinitionId: detail.orderDefinitionId,
quantity: detail.quantity,
unitCode: detail.unitCode,
unit: detail.unitCode_dictText || 'g',
isEditing: false,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: detail.rateCode,
methodCode: detail.methodCode,
}));
currentSetName.value = data.label;
})
.catch(() => {
ElMessage.error('加载组套详情失败');
});
}
// 新增药品
function addDrug() {
// 如果有药品列表,检查上一个药品是否未保存
if (drugList.value.length > 0) {
const last = drugList.value[drugList.value.length - 1];
// 如果上一个药品未选择药品,不允许添加下一个
if (!last.orderDefinitionId) {
ElMessage.warning('请先选择上一个药品');
return;
}
// 如果上一个药品已有药品ID且处于编辑状态自动保存
if (last.isEditing) {
const msg = validateDrug(last);
if (!msg) {
last.isEditing = false;
}
}
}
drugList.value.push(createEmptyDrug());
}
// 删除药品
function removeDrug(index) {
if (drugList.value.length <= 1) {
ElMessage.warning('至少保留一条药品信息');
return;
}
drugList.value.splice(index, 1);
}
function validateDrug(item) {
if (!item.orderDefinitionId) return '请选择中药';
if (!item.quantity || Number(item.quantity) <= 0) return '请填写数量';
return '';
}
function saveDrug(item) {
const msg = validateDrug(item);
if (msg) {
ElMessage.warning(msg);
return false;
}
item.isEditing = false;
return true;
}
function editDrug(item) {
item.isEditing = true;
}
// 处理搜索关键字变化
function handleSearchKeyChange(value, item) {
item.searchKey = value;
}
// 处理点击输入框
function handleClick(item) {
item.showPopover = true;
}
// 处理失焦
function handleBlur(item) {
item.showPopover = false;
item.searchKey = '';
}
// 处理键盘事件(上下键和回车)
function handleKeyDown(event, item) {
if (!item.showPopover) return;
const key = event.key;
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(key)) {
event.preventDefault();
if (item.tableRef) {
item.tableRef.handleKeyDown(event);
}
}
}
// 选择中药时,回填信息
function handleSelectMedicine(row, item) {
item.drugCode = row.adviceCode;
item.drugName = row.adviceName;
item.orderDefinitionId = row.adviceDefinitionId;
item.unitCode = row.unitCode;
item.unit = row.unitCode_dictText || 'g';
item.showPopover = false;
item.searchKey = '';
}
// 清空表单
function clearForm() {
commonForm.name = '';
commonForm.useRange = '';
commonForm.practitionerId = '';
commonForm.organizationId = '';
commonForm.method = '';
commonForm.frequency = '';
drugList.value = [];
}
// 新建组套
function createNew() {
clearForm();
commonForm.useRange = 'personal';
drugList.value = [createEmptyDrug()];
}
// 保存组套
function saveCommonForm() {
// 校验组套名称
if (!commonForm.name) {
ElMessage.warning('请填写组套名称');
return;
}
// 校验使用范围
if (!commonForm.useRange) {
ElMessage.warning('请选择使用范围');
return;
}
// 校验个人时选择使用人
if (commonForm.useRange === 'personal' && !commonForm.practitionerId) {
ElMessage.warning('请选择使用人');
return;
}
// 校验科室时选择科室
if (commonForm.useRange === 'department' && !commonForm.organizationId) {
ElMessage.warning('请选择科室');
return;
}
// 校验药品列表
if (drugList.value.length === 0) {
ElMessage.warning('请添加药品');
return;
}
// 校验每个药品
for (let i = 0; i < drugList.value.length; i++) {
const item = drugList.value[i];
if (!item.orderDefinitionId) {
ElMessage.warning(`请选择第${i + 1}个药品`);
return;
}
if (!item.quantity || Number(item.quantity) <= 0) {
ElMessage.warning(`请填写第${i + 1}个药品的数量`);
return;
}
}
// 生成组套ID时间戳
const groupId = Date.now();
// 构建详情列表
const detailList = drugList.value.map((item) => ({
orderDefinitionId: item.orderDefinitionId,
orderDefinitionTable: 'order_definition',
quantity: item.quantity,
unitCode: item.unitCode,
dose: item.quantity,
rateCode: commonForm.frequency,
dispensePerDuration: 7,
methodCode: commonForm.method,
doseQuantity: item.quantity,
groupId: groupId,
}));
// 构建请求数据
const data = {
name: commonForm.name,
tcmFlag: 1,
detailList: detailList,
};
// 根据使用范围添加不同字段
if (commonForm.useRange === 'personal') {
data.practitionerId = commonForm.practitionerId;
} else if (commonForm.useRange === 'department') {
data.organizationId = commonForm.organizationId;
}
// 全院不传 practitionerId 和 organizationId
// 调用对应接口
let saveApi;
if (commonForm.useRange === 'personal') {
saveApi = savePersonal;
} else if (commonForm.useRange === 'department') {
saveApi = saveDepartment;
} else {
saveApi = saveAll;
}
saveApi(data)
.then(() => {
ElMessage.success('保存成功');
loadTreeData();
})
.catch((err) => {
ElMessage.error('保存失败:' + (err.message || '未知错误'));
});
}
</script>
<style scoped>
.tcm-medical-order-set {
height: 90vh;
}
.full-height {
height: 100%;
}
.left-panel,
.right-panel {
height: 100%;
}
.tree-card {
height: 100%;
}
.right-panel {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-card,
.detail-card {
width: 100%;
}
.panel-shadow {
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.1);
}
.common-form {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
}
.common-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
.common-item {
width: 300px;
margin-bottom: 0;
}
.common-item :deep(.el-input__wrapper),
.common-item :deep(.el-select__wrapper) {
height: 40px;
}
.common-item :deep(.el-input__inner) {
height: 40px;
}
.drug-card-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.drug-card {
width: 220px;
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.12);
height: 120px;
}
.drug-card :deep(.el-card__body) {
padding: 10px 10px 8px;
}
.drug-card-body {
display: flex;
flex-direction: column;
height: 120px;
}
.drug-top {
flex: 0 0 auto;
}
.drug-divider {
margin: 6px 0;
border-top: 1px dashed var(--el-border-color);
}
.drug-bottom {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.drug-bottom-left {
flex: 1 1 auto;
min-width: 0;
}
.drug-card-form {
margin-top: 0;
}
.compact-item {
margin-bottom: 8px;
}
.compact-item :deep(.el-form-item__label) {
padding: 0 0 4px;
line-height: 1.2;
}
.compact-item :deep(.el-form-item__content) {
line-height: 1.2;
}
.quantity-wrap {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
}
.unit-select {
width: 80px;
}
.drug-select {
flex: 1;
min-width: 0;
}
.drug-edit-actions {
display: flex;
justify-content: flex-end;
gap: 0px;
padding-top: 2px;
flex-shrink: 0;
}
.drug-view-name {
font-weight: 600;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.drug-view-qty {
font-size: 13px;
color: var(--el-text-color-regular);
flex: none;
}
.drug-view-actions {
display: flex;
justify-content: flex-end;
gap: 6px;
flex-shrink: 0;
}
.drug-view-actions :deep(.el-button),
.drug-edit-actions :deep(.el-button) {
padding: 4px;
min-width: 0;
}
.add-card {
border-style: dashed;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-color-primary);
box-shadow: 0 3px 14px rgba(64, 158, 255, 0.18);
}
.add-card-content {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.add-icon {
font-size: 18px;
}
.empty-tip {
margin-top: 80px;
}
.demo-form-inline .el-input {
--el-input-width: 200px;
}
.demo-form-inline .el-select {
--el-select-width: 200px;
}
.el-form-item--default {
margin-bottom: 10px;
}
</style>

View File

@@ -175,6 +175,7 @@ import {
} from './component/api';
import AdviceListDialog from './component/adviceListDialog.vue';
import {formatDate, formatDateStr} from '@/utils/index';
import { RequestStatus } from '@/utils/medicalConstants';
const showSearch = ref(true);
const total = ref(1);
@@ -210,7 +211,7 @@ const data = reactive({
pageNo: 1,
pageSize: 10,
searchKey: undefined,
serviceStatus: 3, // 默认值为已完成 (对应 RequestStatus.COMPLETED)
serviceStatus: RequestStatus.ACTIVE, // 默认值为待执行
},
});
const { queryParams } = toRefs(data);

View File

@@ -1,33 +1,14 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper">
<!-- 医保等级测试区域已隐藏 -->
<!--
<div style="margin-bottom: 20px; padding: 10px; border: 1px solid #ccc; background: #f5f5f5;">
<h3>医保等级测试</h3>
<div>
<div>1 -> {{ getMedicalInsuranceLevel({chrgitmLv: '1'}) }}</div>
<div>2 -> {{ getMedicalInsuranceLevel({chrgitmLv: '2'}) }}</div>
<div>3 -> {{ getMedicalInsuranceLevel({chrgitmLv: '3'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '甲'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '乙'}) }}</div>
<div> -> {{ getMedicalInsuranceLevel({chrgitmLv: '自'}) }}</div>
<div>甲类 -> {{ getMedicalInsuranceLevel({chrgitmLv: '甲类'}) }}</div>
<div>乙类 -> {{ getMedicalInsuranceLevel({chrgitmLv: '乙类'}) }}</div>
<div>自费 -> {{ getMedicalInsuranceLevel({chrgitmLv: '自费'}) }}</div>
</div>
</div>
-->
<!-- 使用Element Plus的虚拟滚动表格 -->
<el-table
ref="adviceBaseRef"
v-loading="loading"
height="400"
:data="filteredAdviceBaseList"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceDefinitionId"
@cell-click="clickRow"
v-loading="loading"
>
<el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" />
@@ -85,50 +66,9 @@
</template>
<script setup>
import { getCurrentInstance, nextTick, onMounted, ref, computed, watch } from 'vue';
import { nextTick, ref, watch, computed } from 'vue';
import { getAdviceBaseInfo, getDeviceList, getConsultationActivities } from './api';
import { throttle, debounce } from 'lodash-es';
const { proxy } = getCurrentInstance();
// 使用系统统一的数据字典
const { med_category_code, chrgitm_lv } = proxy.useDict('med_category_code', 'chrgitm_lv');
// 医保等级映射(作为后备使用)
const medicalInsuranceLevelMap = {
'1': '甲类',
'2': '乙类',
'3': '自费'
};
// 获取药品分类名称 - 使用系统统一的数据字典
function getCategoryName(row) {
if (!row) return '-';
if (row.adviceType === 1) { // 药品类型
// 优先使用系统统一的药品分类数据字典
if (med_category_code.value && med_category_code.value.length > 0) {
const found = med_category_code.value.find(item => String(item.value) === String(row.categoryCode));
if (found) {
return found.label;
}
}
// 兼容处理:对于中草药可能的多种编码形式
const herbCodes = ['3', '03', '4', 3, 4];
if (herbCodes.includes(row.categoryCode)) {
return '中草药';
}
return '-';
} else if (row.adviceType === 2) { // 耗材类型
return '耗材';
} else if (row.adviceType === 3) { // 诊疗类型
// 对于诊疗类型activityType 就是 category_code 的整数值,使用 activityType_dictText 显示
return row.activityType_dictText || row.categoryCode_dictText || row.categoryCode || '诊疗';
}
// 其他情况使用 categoryCode_dictText
return row.categoryCode_dictText || '-';
}
import { throttle } from 'lodash-es';
// 获取医保等级 - 简单直接的实现
function getMedicalInsuranceLevel(record) {
@@ -162,148 +102,121 @@ const props = defineProps({
type: Object,
required: true,
},
// 预加载的数据
preloadedData: {
type: Array,
default: () => []
},
// 预加载是否完成
preloadedLoaded: {
type: Boolean,
default: false
},
});
const emit = defineEmits(['selectAdviceBase']);
// 使用缓存机制
const searchCache = new Map();
const allAdviceData = ref([]); // 预加载数据
const isDataLoaded = ref(false); // 数据是否已加载
const adviceBaseList = ref([]);
const loading = ref(false);
const currentSelectRow = ref({});
const currentIndex = ref(0);
const tableWrapper = ref();
const loading = ref(false);
const isRequestInProgress = ref(false); // 请求进行中的标志
const adviceBaseRef = ref();
// 计算属性:根据搜索条件过滤数据
const filteredAdviceBaseList = computed(() => {
let result = [];
if (!props.adviceQueryParams.searchKey) {
result = adviceBaseList.value.slice(0, 50); // 返回前50个常用项目
} else {
const searchKey = props.adviceQueryParams.searchKey.toLowerCase();
result = adviceBaseList.value.filter(item =>
item.adviceName.toLowerCase().includes(searchKey) ||
item.py_str?.toLowerCase().includes(searchKey) ||
item.wb_str?.toLowerCase().includes(searchKey)
).slice(0, 100); // 限制返回数量
}
// 过滤无库存的药品(只针对药品类型 adviceType === 1
// Bug #129 修复确保库存为0的药品不被检索出来
result = result.filter(item => {
if (item.adviceType === 1) {
// 检查是否有库存
if (item.inventoryList && item.inventoryList.length > 0) {
// 计算总库存数量,确保转换为数字进行正确计算
const totalQuantity = item.inventoryList.reduce((sum, inv) => {
const qty = inv.quantity !== undefined && inv.quantity !== null
? (typeof inv.quantity === 'number' ? inv.quantity : Number(inv.quantity) || 0)
: 0;
return sum + qty;
}, 0);
return totalQuantity > 0;
}
return false; // 无库存列表或库存为空,视为无库存
}
return true; // 非药品类型不过滤
});
return result;
// 查询参数 - 与后端API参数名一致
const queryParams = ref({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3',
});
// 预加载数据
async function preloadData() {
if (isDataLoaded.value) return;
try {
const queryParams = {
pageSize: 10000, // 加载更多数据用于本地搜索
pageNum: 1,
adviceTypes: '1,2,3',
organizationId: props.patientInfo.orgId
};
const res = await getAdviceBaseInfo(queryParams);
allAdviceData.value = res.data?.records || [];
isDataLoaded.value = true;
} catch (error) {
console.error('预加载数据失败:', error);
}
}
// 节流函数 - 增强防抖机制
// 节流函数 - 与V1.3一致300ms首次立即响应
const throttledGetList = throttle(
async () => {
// 只有在没有进行中的请求时才执行
if (!isRequestInProgress.value) {
await getList();
}
() => {
getList();
},
500, // 增加到500ms减少频繁请求
{ leading: false, trailing: true }
300,
{ leading: true, trailing: true }
);
// 监听参数变化 - 使用防抖优化
// 监听参数变化 - 优化:优先使用预加载数据
watch(
() => props.adviceQueryParams,
(newValue) => {
// 只有在搜索关键词长度达到一定要求时才触发搜索
if (newValue.searchKey && newValue.searchKey.length < 2) {
adviceBaseList.value = [];
return;
queryParams.value.searchKey = newValue.searchKey;
if (newValue.adviceType) {
queryParams.value.adviceTypes = [newValue.adviceType].join(',');
} else {
queryParams.value.adviceTypes = '1,2,3';
}
throttledGetList();
},
{ deep: true }
);
// 获取数据
async function getList() {
// 防止重复请求
if (isRequestInProgress.value) {
return; // 如果请求正在进行中,直接返回
// 获取数据 - 优化:优先使用预加载数据
function getList() {
const searchKey = queryParams.value.searchKey || '';
const adviceTypes = queryParams.value.adviceTypes || '1,2,3';
// 判断是否可以使用预加载数据
// 条件:预加载数据已就绪,且是普通药品/诊疗类型(adviceTypes包含1,2,3),且没有搜索关键词或搜索关键词很短
const canUsePreloaded = props.preloadedLoaded &&
props.preloadedData &&
props.preloadedData.length > 0 &&
(adviceTypes === '1,2,3' || adviceTypes === '1' || adviceTypes === '2' || adviceTypes === '3');
if (canUsePreloaded) {
// 使用预加载数据
console.log('[adviceBaseList] 使用预加载数据,共', props.preloadedData.length, '条');
let filteredData = [...props.preloadedData]; // 创建副本避免修改原数据
// 根据 adviceTypes 过滤
if (adviceTypes !== '1,2,3') {
const types = adviceTypes.split(',').map(t => parseInt(t));
filteredData = filteredData.filter(item => types.includes(item.adviceType));
}
// 设置请求进行中的标志
isRequestInProgress.value = true;
loading.value = true; // 显示加载状态
// 根据搜索关键词过滤
if (searchKey && searchKey.length >= 1) {
const lowerSearchKey = searchKey.toLowerCase();
filteredData = filteredData.filter(item =>
(item.adviceName && item.adviceName.toLowerCase().includes(lowerSearchKey)) ||
(item.adviceDefinitionId && item.adviceDefinitionId.toString().includes(searchKey))
);
}
try {
// 生成缓存键
const cacheKey = `${props.adviceQueryParams.searchKey}_${props.adviceQueryParams.adviceTypes}_${props.adviceQueryParams.categoryCode}_${props.patientInfo.orgId}`;
adviceBaseList.value = filteredData;
nextTick(() => {
currentIndex.value = 0;
});
// 检查缓存
if (searchCache.has(cacheKey)) {
const cachedData = searchCache.get(cacheKey);
if (Date.now() - cachedData.timestamp < 300000) { // 5分钟有效期
adviceBaseList.value = cachedData.data;
// 如果有搜索关键词,还需要后台请求更精确的结果
if (searchKey && searchKey.length >= 2) {
fetchFromApi(searchKey);
}
return;
}
}
const queryParams = {
pageSize: 1000, // 增加页面大小以适应虚拟滚动
pageNum: 1,
adviceTypes: props.adviceQueryParams.adviceTypes || '1,2,3',
searchKey: props.adviceQueryParams.searchKey || '',
categoryCode: props.adviceQueryParams.categoryCode || '',
organizationId: props.patientInfo.orgId
};
// 不能使用预加载数据直接请求API
fetchFromApi(searchKey);
}
const isConsumables = queryParams.adviceTypes === '2' || queryParams.adviceTypes === 2;
const isConsultation = queryParams.adviceTypes === '5' || queryParams.adviceTypes === 5;
// 从API获取数据
function fetchFromApi(searchKey) {
loading.value = true;
queryParams.value.organizationId = props.patientInfo.orgId;
const isConsumables = queryParams.value.adviceTypes === '2';
const isConsultation = queryParams.value.adviceTypes === '5';
if (isConsultation) {
// 会诊类型:调用会诊项目接口
const res = await getConsultationActivities();
// 会诊类型
getConsultationActivities().then((res) => {
if (res.data && Array.isArray(res.data)) {
const result = res.data.map((item) => ({
adviceBaseList.value = res.data.map((item) => ({
adviceName: item.name || item.activityName,
adviceType: 5, // 会诊类型
unitCode: '111', // 次
adviceType: 5,
unitCode: '111',
unitCode_dictText: '次',
minUnitCode: '111',
minUnitCode_dictText: '次',
@@ -322,34 +235,32 @@ async function getList() {
injectFlag_enumText: '否',
skinTestFlag: 0,
skinTestFlag_enumText: '否',
categoryCode: 31, // 会诊的category_enum
categoryCode: 31,
unitPrice: item.price || 0,
...item,
}));
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
nextTick(() => {
currentIndex.value = 0;
});
adviceBaseList.value = result;
} else {
adviceBaseList.value = [];
}
} else if (isConsumables) {
const deviceQueryParams = {
pageNo: queryParams.pageNum || 1,
pageSize: queryParams.pageSize || 1000,
searchKey: queryParams.searchKey || '',
statusEnum: 2,
};
}).finally(() => {
loading.value = false;
});
return;
}
const res = await getDeviceList(deviceQueryParams);
if (isConsumables) {
// 耗材类型
getDeviceList({
pageNo: 1,
pageSize: 100,
searchKey: searchKey || '',
statusEnum: 2,
}).then((res) => {
if (res.data && res.data.records) {
const result = res.data.records.map((item) => ({
adviceBaseList.value = res.data.records.map((item) => ({
adviceName: item.name || item.busNo,
adviceType: 4, // 🔧 Bug #147 修复前端耗材类型值为4保存时会转换为后端类型值2
adviceType: 4,
unitCode: item.unitCode || '',
unitCode_dictText: item.unitCode_dictText || '',
minUnitCode: item.minUnitCode || item.unitCode || '',
@@ -359,6 +270,7 @@ async function getList() {
priceList: item.price ? [{ price: item.price }] : (item.retailPrice ? [{ price: item.retailPrice }] : []),
inventoryList: [],
adviceDefinitionId: item.id,
adviceTableName: 'adm_device_definition',
chargeItemDefinitionId: item.id,
positionId: item.locationId,
positionName: item.locationId_dictText || '',
@@ -374,98 +286,71 @@ async function getList() {
deviceName: item.name,
...item,
}));
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
nextTick(() => {
currentIndex.value = 0;
});
adviceBaseList.value = result;
} else {
adviceBaseList.value = [];
}
} else {
const res = await getAdviceBaseInfo(queryParams);
let result = res.data?.records || [];
// 缓存结果
searchCache.set(cacheKey, {
data: result,
timestamp: Date.now()
});
adviceBaseList.value = result;
}
} catch (error) {
console.error('获取数据失败:', error);
adviceBaseList.value = [];
} finally {
// 无论成功或失败,都要清除请求标志和加载状态
isRequestInProgress.value = false;
}).finally(() => {
loading.value = false;
});
return;
}
// 普通药品/诊疗 - 与V1.3一致的处理方式
getAdviceBaseInfo(queryParams.value).then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
// 与V1.3一致:在获取数据后直接过滤无库存的药品
adviceBaseList.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
return handleQuantity(item) != 0;
} else {
return true;
}
});
nextTick(() => {
currentIndex.value = 0;
});
}
}).finally(() => {
loading.value = false;
});
}
// 格式化剂量显示
function formatDose(item) {
if (!item.dose || isNaN(parseFloat(item.dose))) {
return '-';
}
const num = parseFloat(item.dose);
if (num.toFixed(2) === '0.00') {
return '-';
}
return num.toFixed(2) + (item.doseUnitCode_dictText || '');
}
// 从priceList列表中获取价格
// 从priceList列表中获取价格 - 与V1.3一致
function getPriceFromInventory(row) {
if (row.priceList && row.priceList.length > 0) {
const price = row.priceList[0].price;
// 检查价格是否为有效数字
if (price !== undefined && price !== null && !isNaN(price) && isFinite(price)) {
const price = row.priceList[0].price || 0;
return Number(price).toFixed(2) + ' 元';
}
// 如果价格无效,尝试从其他可能的字段获取价格
if (row.totalPrice !== undefined && row.totalPrice !== null && !isNaN(row.totalPrice) && isFinite(row.totalPrice)) {
return Number(row.totalPrice).toFixed(2) + ' 元';
}
if (row.price !== undefined && row.price !== null && !isNaN(row.price) && isFinite(row.price)) {
return Number(row.price).toFixed(2) + ' 元';
}
}
return '-';
}
function handleQuantity(row) {
if (row.inventoryList && row.inventoryList.length > 0) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
return totalQuantity.toString() + row.minUnitCode_dictText;
}
return '0';
return 0;
}
// 处理键盘事件
// 处理键盘事件 - 与V1.3一致
const handleKeyDown = (event) => {
const key = event.key;
const data = filteredAdviceBaseList.value;
if (data.length === 0) return;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp': // 上箭头
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown': // 下箭头
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
currentSelectRow.value = data[currentIndex.value];
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter': // 回车键
@@ -477,9 +362,19 @@ const handleKeyDown = (event) => {
}
};
// 当前行变化时更新索引
// 设置选中行(带滚动)- 与V1.3一致
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引 - 与V1.3一致
const handleCurrentChange = (currentRow) => {
currentIndex.value = filteredAdviceBaseList.value.findIndex((item) => item.adviceDefinitionId === currentRow.adviceDefinitionId);
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
@@ -488,12 +383,6 @@ function clickRow(row) {
emit('selectAdviceBase', row);
}
// 初始化
onMounted(() => {
// preloadData(); // 预加载数据
getList(); // 获取初始数据
});
defineExpose({
handleKeyDown,
});

View File

@@ -780,6 +780,48 @@ export function getOrderGroup(data) {
});
}
/**
* 保存组套
* @param {Object} data - 组套数据,包含 rangeCode 字段
*/
export function saveOrderGroup(data) {
// 根据使用范围选择不同的接口
let url = '/personalization/orders-group-package/save-personal';
console.log('[saveOrderGroup] data.rangeCode:', data?.rangeCode);
if (data.rangeCode === 2) {
url = '/personalization/orders-group-package/save-organization';
} else if (data.rangeCode === 3) {
url = '/personalization/orders-group-package/save-hospital';
}
console.log('[saveOrderGroup] 使用的URL:', url);
return request({
url: url,
method: 'post',
data: data,
});
}
/**
* 删除组套
*/
export function deleteGroup(data) {
return request({
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
method: 'delete',
});
}
/**
* 查询组套详情
*/
export function queryGroupDetail(data) {
return request({
url: '/personalization/orders-group-package/get-detail',
method: 'get',
params: data,
});
}
/**
* 查询项目绑定信息
*/
@@ -949,7 +991,23 @@ export function deleteInspectionApplication(applyNo) {
}
/**
* 获取检验类型列表(分类)
* 分页获取检验类型列表(分类)
* @param {Object} queryParams - 查询参数
* @param {number} queryParams.pageNo - 页码
* @param {number} queryParams.pageSize - 每页数量
* @param {string} queryParams.searchKey - 搜索关键词
*/
export function getInspectionTypePage(queryParams) {
return request({
url: '/system/inspection-type/page',
method: 'get',
params: queryParams,
});
}
/**
* 获取检验类型列表(不分页,兼容旧接口)
* @deprecated 建议使用 getInspectionTypePage 分页接口
*/
export function getInspectionTypeList() {
return request({
@@ -965,6 +1023,7 @@ export function getInspectionTypeList() {
* @param {number} queryParams.pageNo - 页码
* @param {number} queryParams.pageSize - 每页数量
* @param {string} queryParams.categoryCode - 目录类别编码(检验)
* @param {string} queryParams.inspectionTypeId - 检验类型ID列表多个用逗号分隔
*/
export function getInspectionItemList(queryParams) {
return request({

View File

@@ -15,8 +15,8 @@
<el-input
v-model="form.cardNo"
class="card-number-input"
placeholder="系统自动生成"
disabled
placeholder="单位自编,与网络直报一致"
maxlength="12"
/>
</el-space>
</el-card>
@@ -32,7 +32,7 @@
</el-col>
<el-col :span="8" class="form-item">
<span :class="['form-label', isChildPatient ? 'required' : '']">家长姓名</span>
<el-input v-model="form.parentName" class="underline-input" :placeholder="isChildPatient ? '14岁以下必填' : ''" />
<el-input v-model="form.parentName" class="underline-input" :placeholder="isChildPatient ? '14岁必填' : ''" />
</el-col>
<el-col :span="8" class="form-item">
<span class="form-label required">身份证号</span>
@@ -98,23 +98,29 @@
<el-row :gutter="16" class="form-row">
<el-col :span="24" class="form-item full-width">
<span class="form-label required">现住地址</span>
<div class="address-inputs region-selects-wrapper">
<el-cascader
v-model="addressCodes"
:options="addressOptions"
:props="addressCascaderProps"
placeholder="请选择省/市/区县/街道"
clearable
@change="handleAddressChange"
/>
<div class="address-selects">
<el-select v-model="provinceCode" placeholder="省" clearable @change="handleProvinceChange">
<el-option v-for="item in provinceOptions" :key="item.code" :label="item.name" :value="item.code" />
</el-select>
<el-select v-model="cityCode" placeholder="市" clearable @change="handleCityChange">
<el-option v-for="item in cityOptions" :key="item.code" :label="item.name" :value="item.code" />
</el-select>
<el-select v-model="countyCode" placeholder="区县" clearable @change="handleCountyChange">
<el-option v-for="item in countyOptions" :key="item.code" :label="item.name" :value="item.code" />
</el-select>
</div>
</el-col>
</el-row>
<el-row :gutter="16" class="form-row">
<el-col :span="12" class="form-item">
<el-col :span="8" class="form-item">
<el-select v-model="townCode" placeholder="街道" clearable @change="handleTownChange" class="underline-select">
<el-option v-for="item in townOptions" :key="item.code" :label="item.name" :value="item.code" />
</el-select>
</el-col>
<el-col :span="8" class="form-item">
<el-input v-model="form.addressVillage" class="underline-input" placeholder="村(居)" />
</el-col>
<el-col :span="12" class="form-item">
<el-col :span="8" class="form-item">
<el-input v-model="form.addressHouse" class="underline-input" placeholder="门牌号" />
</el-col>
</el-row>
@@ -135,26 +141,27 @@
</el-row>
<!-- 职业 -->
<!-- 职业病例分类 -->
<el-row :gutter="16" class="form-row">
<el-col :span="24" class="form-item">
<el-col :span="8" class="form-item">
<span class="form-label required">职业</span>
<el-select v-model="form.occupation" placeholder="请选择职业" class="full-select">
<el-option v-for="item in occupationList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-col>
</el-row>
<!-- 病例分类 -->
<el-row :gutter="16" class="form-row">
<el-col :span="24" class="form-item full-width">
<el-col :span="16" class="form-item">
<span class="form-label required">病例分类</span>
<el-radio-group v-model="form.caseClass">
<el-radio label="1">疑似病例</el-radio>
<el-radio label="2">临床诊断病例</el-radio>
<el-radio label="3">确诊病例</el-radio>
<el-radio label="4">病原携带者</el-radio>
<el-radio label="5">阳性检测结果</el-radio>
</el-radio-group>
<div class="case-class-group">
<div class="case-class-row">
<el-radio v-model="form.caseClass" label="1">疑似病例</el-radio>
<el-radio v-model="form.caseClass" label="2">临床诊断病例</el-radio>
<el-radio v-model="form.caseClass" label="3">确诊病例</el-radio>
</div>
<div class="case-class-row">
<el-radio v-model="form.caseClass" label="4">病原携带者</el-radio>
<el-radio v-model="form.caseClass" label="5">阳性检测结果(采供血机构填写)</el-radio>
</div>
</div>
</el-col>
</el-row>
@@ -327,7 +334,7 @@
:model-value="form.selectedClassB === '0221'"
@update:model-value="(checked) => handleClassBCheckbox('0221', checked)"
label="0221"
><EFBFBD><EFBFBD><EFBFBD><EFBFBD>病</el-checkbox>
>病</el-checkbox>
<el-checkbox
:model-value="form.selectedClassB === '0222'"
@update:model-value="(checked) => handleClassBCheckbox('0222', checked)"
@@ -348,6 +355,21 @@
@update:model-value="(checked) => handleClassBCheckbox('0225', checked)"
label="0225"
>疟疾</el-checkbox>
<el-checkbox
:model-value="form.selectedClassB === '0226'"
@update:model-value="(checked) => handleClassBCheckbox('0226', checked)"
label="0226"
>新型冠状病毒肺炎</el-checkbox>
<el-checkbox
:model-value="form.selectedClassB === '0227'"
@update:model-value="(checked) => handleClassBCheckbox('0227', checked)"
label="0227"
>甲型H1N1流感</el-checkbox>
<el-checkbox
:model-value="form.selectedClassB === '0228'"
@update:model-value="(checked) => handleClassBCheckbox('0228', checked)"
label="0228"
>人感染H7N9禽流感</el-checkbox>
</div>
</el-card>
@@ -404,7 +426,8 @@
:model-value="form.selectedClassC === '0310'"
@update:model-value="(checked) => handleClassCCheckbox('0310', checked)"
label="0310"
>其它感染性腹泻病</el-checkbox>
class="wide-checkbox"
>除霍乱/菌痢/伤寒副伤寒以外的感染性腹泻病</el-checkbox>
<el-checkbox
:model-value="form.selectedClassC === '0311'"
@update:model-value="(checked) => handleClassCCheckbox('0311', checked)"
@@ -495,7 +518,7 @@
<script setup>
import { ref, computed, getCurrentInstance, watch } from 'vue';
import pcas from 'china-division/dist/pcas-code.json';
import { saveInfectiousDiseaseReport, getNextCardNo } from '../api';
import { saveInfectiousDiseaseReport } from '../api';
import useUserStore from '@/store/modules/user';
import { useDict } from '@/utils/dict';
@@ -543,17 +566,151 @@ const props = defineProps({
const emit = defineEmits(['close', 'success']);
// 地址级联选择器配置
const addressOptions = ref(pcas);
const addressCodes = ref([]);
// 地址选择器数据
const addressData = ref(pcas);
const provinceCode = ref('');
const cityCode = ref('');
const countyCode = ref('');
const townCode = ref('');
const provinceOptions = ref([]);
const cityOptions = ref([]);
const countyOptions = ref([]);
const townOptions = ref([]);
// 直辖市列表(这些城市的省级和市级相同)
const municipalities = ['北京市', '天津市', '上海市', '重庆市'];
const addressCascaderProps = {
checkStrictly: true,
value: 'code',
label: 'name',
children: 'children'
};
// 初始化省级选项
function initProvinceOptions() {
provinceOptions.value = addressData.value.map(item => ({
code: item.code,
name: item.name
}));
}
// 省级变化处理
function handleProvinceChange(code) {
cityCode.value = '';
countyCode.value = '';
townCode.value = '';
cityOptions.value = [];
countyOptions.value = [];
townOptions.value = [];
form.value.addressProv = '';
form.value.addressCity = '';
form.value.addressCounty = '';
form.value.addressTown = '';
if (code) {
const province = addressData.value.find(item => item.code === code);
if (province) {
form.value.addressProv = province.name;
// 直辖市特殊处理:市级选项与省级相同
if (municipalities.includes(province.name)) {
cityOptions.value = [{ code: province.code, name: province.name }];
cityCode.value = province.code;
form.value.addressCity = province.name;
// 直辖市的区县是省级的children
if (province.children && province.children.length > 0) {
countyOptions.value = province.children[0].children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
} else {
cityOptions.value = province.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
}
}
}
// 市级变化处理
function handleCityChange(code) {
countyCode.value = '';
townCode.value = '';
countyOptions.value = [];
townOptions.value = [];
form.value.addressCity = '';
form.value.addressCounty = '';
form.value.addressTown = '';
if (code) {
const province = addressData.value.find(item => item.code === provinceCode.value);
if (province) {
// 直辖市处理
if (municipalities.includes(province.name)) {
const county = province.children?.[0]?.children?.find(item => item.code === code);
if (county) {
form.value.addressCounty = county.name;
townOptions.value = county.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
} else {
const city = province.children?.find(item => item.code === code);
if (city) {
form.value.addressCity = city.name;
countyOptions.value = city.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
}
}
}
}
// 区县变化处理
function handleCountyChange(code) {
townCode.value = '';
townOptions.value = [];
form.value.addressCounty = '';
form.value.addressTown = '';
if (code) {
const province = addressData.value.find(item => item.code === provinceCode.value);
if (province) {
if (municipalities.includes(province.name)) {
// 直辖市
const county = province.children?.[0]?.children?.find(item => item.code === code);
if (county) {
form.value.addressCounty = county.name;
townOptions.value = county.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
} else {
const city = province.children?.find(item => item.code === cityCode.value);
if (city) {
const county = city.children?.find(item => item.code === code);
if (county) {
form.value.addressCounty = county.name;
townOptions.value = county.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
}
}
}
}
}
}
// 街道变化处理
function handleTownChange(code) {
form.value.addressTown = '';
if (code) {
const town = townOptions.value.find(item => item.code === code);
if (town) {
form.value.addressTown = town.name;
}
}
}
const form = ref({
cardNo: '',
@@ -601,65 +758,104 @@ const form = ref({
diagnosisId: '',
});
// 根据地址名称数组查找对应的代码数组(用于初始化地址选择器回显)
function findCodesByNames(names) {
const codes = [];
let currentNodes = addressOptions.value;
// 根据地址名称初始化下拉选择器(用于回显)
function initAddressByName(provName, cityName, countyName, townName) {
if (!provName) return;
for (const name of names) {
if (!name) break;
const node = currentNodes?.find(n => n.name === name);
if (node) {
codes.push(node.code);
currentNodes = node.children;
} else {
break;
}
}
return codes;
}
// 初始化省级选项
initProvinceOptions();
// 地址级联选择器变化处理
function handleAddressChange(values) {
if (!values || values.length === 0) {
form.value.addressProv = '';
form.value.addressCity = '';
form.value.addressCounty = '';
form.value.addressTown = '';
return;
}
// 查找省份
const province = addressData.value.find(item => item.name === provName);
if (!province) return;
// 根据选中的代码获取对应的名称
const names = [];
let currentNodes = addressOptions.value;
provinceCode.value = province.code;
form.value.addressProv = province.name;
for (const code of values) {
const node = currentNodes?.find(n => n.code === code);
if (node) {
names.push(node.name);
currentNodes = node.children;
}
}
// 处理直辖市情况:如果是直辖市,市级和省级相同
const provName = names[0] || '';
let cityName = names[1] || '';
const countyName = names[2] || '';
const townName = names[3] || '';
// 如果是直辖市,市级应该与省级相同
// 直辖市处理
if (municipalities.includes(provName)) {
// 直辖市:省级=市级
form.value.addressProv = provName;
form.value.addressCity = provName; // 直辖市的市级与省级相同
form.value.addressCounty = cityName; // 原来的市级变成区县
form.value.addressTown = countyName; // 原来的区县变成街道
cityOptions.value = [{ code: province.code, name: province.name }];
cityCode.value = province.code;
form.value.addressCity = province.name;
// 直辖市的区县
if (province.children && province.children.length > 0) {
countyOptions.value = province.children[0].children?.map(item => ({
code: item.code,
name: item.name
})) || [];
if (countyName) {
const county = countyOptions.value.find(item => item.name === countyName);
if (county) {
countyCode.value = county.code;
form.value.addressCounty = county.name;
// 获取原始区县数据以查找街道
const countyData = province.children[0].children?.find(item => item.code === county.code);
if (countyData && countyData.children) {
townOptions.value = countyData.children.map(item => ({
code: item.code,
name: item.name
}));
if (townName) {
const town = townOptions.value.find(item => item.name === townName);
if (town) {
townCode.value = town.code;
form.value.addressTown = town.name;
}
}
}
}
}
}
} else {
// 非直辖市:正常处理
form.value.addressProv = provName;
form.value.addressCity = cityName;
form.value.addressCounty = countyName;
form.value.addressTown = townName;
// 非直辖市处理
cityOptions.value = province.children?.map(item => ({
code: item.code,
name: item.name
})) || [];
if (cityName) {
const city = cityOptions.value.find(item => item.name === cityName);
if (city) {
cityCode.value = city.code;
form.value.addressCity = city.name;
const cityData = province.children?.find(item => item.code === city.code);
if (cityData && cityData.children) {
countyOptions.value = cityData.children.map(item => ({
code: item.code,
name: item.name
}));
if (countyName) {
const county = countyOptions.value.find(item => item.name === countyName);
if (county) {
countyCode.value = county.code;
form.value.addressCounty = county.name;
const countyData = cityData.children.find(item => item.code === county.code);
if (countyData && countyData.children) {
townOptions.value = countyData.children.map(item => ({
code: item.code,
name: item.name
}));
if (townName) {
const town = townOptions.value.find(item => item.name === townName);
if (town) {
townCode.value = town.code;
form.value.addressTown = town.name;
}
}
}
}
}
}
}
}
}
}
@@ -920,6 +1116,19 @@ function calculateAge() {
*/
function show(diagnosisData) {
dialogVisible.value = true;
// 重置地址选择器状态
provinceCode.value = '';
cityCode.value = '';
countyCode.value = '';
townCode.value = '';
cityOptions.value = [];
countyOptions.value = [];
townOptions.value = [];
// 初始化省级地址选项
initProvinceOptions();
const patientInfo = props.patientInfo || {};
const birthInfo = parseBirthDate(patientInfo.birthDate || patientInfo.birthday);
@@ -1019,37 +1228,16 @@ function show(diagnosisData) {
updateSelectedDiseases();
// 设置地址选择组件初始值
// 根据地址名称查找对应的代码数组
const provName = diagnosisData?.addressProv || '';
let cityName = diagnosisData?.addressCity || '';
const cityName = diagnosisData?.addressCity || '';
const countyName = diagnosisData?.addressCounty || '';
const townName = diagnosisData?.addressTown || '';
// 处理直辖市情况:如果是直辖市,需要调整地址层级
let names = [];
if (municipalities.includes(provName)) {
// 直辖市:省级=市级,所以传给级联选择器的应该是 [省, 区县, 街道]
names = [provName, countyName, townName].filter(n => n);
} else {
// 非直辖市:正常处理 [省, 市, 区县, 街道]
names = [provName, cityName, countyName, townName].filter(n => n);
}
addressCodes.value = findCodesByNames(names);
initAddressByName(provName, cityName, countyName, townName);
if (birthInfo.year) {
calculateAge();
}
// 从后端获取下一个卡片编号(含流水号)
getNextCardNo(orgCode).then(res => {
if (res.code === 200 && res.data) {
form.value.cardNo = res.data;
}
}).catch(err => {
// 后端不可用时提示用户,不生成可能重复的编号
proxy.$modal.msgError('获取卡片编号失败,请稍后重试');
console.error('获取卡片编号失败:', err);
});
}
/**
@@ -1141,6 +1329,11 @@ async function buildSubmitData() {
function validateFormManually() {
const errors = [];
// 卡片编号验证可选但如果填写了必须是12位
if (form.value.cardNo && form.value.cardNo.length !== 12) {
errors.push('卡片编号必须为12位');
}
// 身份证号验证
if (!form.value.idNo) {
errors.push('请输入身份证号');
@@ -1356,6 +1549,26 @@ defineExpose({ show });
.card-number-input {
width: 240px;
:deep(.el-input__wrapper) {
border: none;
border-bottom: 1px solid #dcdfe6;
border-radius: 0;
box-shadow: none;
background: transparent;
}
:deep(.el-input__inner) {
font-size: 12px;
color: #666;
text-align: center;
letter-spacing: 2px;
}
:deep(.el-input__inner::placeholder) {
font-size: 12px;
color: #999;
}
}
/* 表单区域样式 */
@@ -1414,36 +1627,90 @@ defineExpose({ show });
gap: 4px;
}
/* 地址选择样式 */
.address-inputs {
/* 地址选择样式 - 四个下拉框并排 */
.address-selects {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* 地址级联选择器样式 */
.region-selects-wrapper {
flex: 1;
}
.region-selects-wrapper :deep(.el-cascader) {
width: 100%;
.address-selects .el-select {
flex: 1;
min-width: 0;
}
.region-selects-wrapper :deep(.el-cascader .el-input__wrapper) {
.address-selects :deep(.el-input__wrapper) {
border: none;
border-bottom: 1px solid #dcdfe6;
border-radius: 0;
box-shadow: none;
background: transparent;
}
.region-selects-wrapper :deep(.el-cascader .el-input__wrapper:hover) {
.address-selects :deep(.el-input__wrapper:hover) {
border-bottom-color: #c0c4cc;
}
.region-selects-wrapper :deep(.el-cascader .el-input__wrapper.is-focus) {
.address-selects :deep(.el-input__wrapper.is-focus) {
border-bottom-color: #409eff;
}
.address-selects :deep(.el-input__inner) {
font-size: 12px;
color: #666;
}
.address-selects :deep(.el-input__inner::placeholder) {
font-size: 12px;
color: #999;
}
/* 街道下拉框下划线样式 */
.underline-select {
width: 100%;
}
.underline-select :deep(.el-input__wrapper) {
border: none;
border-bottom: 1px solid #dcdfe6;
border-radius: 0;
box-shadow: none;
background: transparent;
}
.underline-select :deep(.el-input__wrapper:hover) {
border-bottom-color: #c0c4cc;
}
.underline-select :deep(.el-input__wrapper.is-focus) {
border-bottom-color: #409eff;
}
.underline-select :deep(.el-input__inner) {
font-size: 12px;
color: #666;
}
.underline-select :deep(.el-input__inner::placeholder) {
font-size: 12px;
color: #999;
}
/* 病例分类两行排布 */
.case-class-group {
width: 100%;
}
.case-class-row {
display: flex;
gap: 16px;
margin-bottom: 8px;
}
.case-class-row:last-child {
margin-bottom: 0;
}
.full-select {
width: 100%;
@@ -1502,6 +1769,10 @@ defineExpose({ show });
width: calc(25% - 8px);
}
.disease-list.four-columns .el-checkbox.wide-checkbox {
width: calc(50% - 8px);
}
.disease-with-subtype {
display: flex;
align-items: center;
@@ -1533,6 +1804,9 @@ defineExpose({ show });
.disease-with-subtype {
width: calc(50% - 8px);
}
.disease-list.four-columns .el-checkbox.wide-checkbox {
width: 100%;
}
}
@media (max-width: 600px) {

View File

@@ -184,10 +184,11 @@
</template>
<script setup>
import {watch} from 'vue';
import {getDiagnosisDefinitionList, getInit, getOrgList, handleHospitalization, wardList,} from './api.js';
const submitForm = reactive({
medTypeCode: '21',
medTypeCode: '', // 从字典动态获取默认值
});
const props = defineProps({
@@ -217,6 +218,17 @@ let diagnosisYbNo = '';
const { proxy } = getCurrentInstance();
const { med_type } = proxy.useDict('med_type');
// 监听诊断类别字典加载,默认选择第一项(仅当没有主诊断时)
watch(
med_type,
(newVal) => {
if (newVal && newVal.length > 0 && !submitForm.medTypeCode && !props.mainDiagnosis) {
submitForm.medTypeCode = newVal[0].value;
}
},
{ immediate: true }
);
const rules = reactive({
diagnosisDefinitionId: [
{
@@ -277,10 +289,38 @@ function openDialog() {
console.log('筛选后的组织机构数据:', organization.value);
// 如果当前患者所属科室在筛选结果中,则默认选中
// 默认选中当前医生所在科室(即使不在住院科室列表中)
if (props.patientInfo?.orgId) {
submitForm.inHospitalOrgId =
organization.value.find((item) => item?.id === props.patientInfo.orgId)?.id || '';
const doctorOrgId = props.patientInfo.orgId;
const exists = organization.value.some((item) => item?.id === doctorOrgId);
if (exists) {
// 如果医生科室在列表中,直接选中
submitForm.inHospitalOrgId = doctorOrgId;
} else {
// 如果医生科室不在列表中(如门诊科室),临时添加到列表并选中
// 从原始数据中找到医生科室信息
const findOrgInTree = (nodes, orgId) => {
for (const node of nodes) {
if (node.id === orgId) return node;
if (node.children) {
const found = findOrgInTree(node.children, orgId);
if (found) return found;
}
}
return null;
};
const doctorOrg = findOrgInTree(res.data.records, doctorOrgId);
if (doctorOrg) {
organization.value.unshift(doctorOrg);
submitForm.inHospitalOrgId = doctorOrgId;
}
}
// 触发科室选择事件,加载对应病区
if (submitForm.inHospitalOrgId) {
handleNodeClick({ id: submitForm.inHospitalOrgId });
}
}
} else {
organization.value = [];
@@ -310,9 +350,21 @@ function openDialog() {
submitForm.diagnosisDefinitionId = props.mainDiagnosis.definitionId;
diagnosisDefinitionId = props.mainDiagnosis.definitionId;
diagnosisYbNo = props.mainDiagnosis.ybNo || '';
submitForm.medTypeCode = props.mainDiagnosis.medTypeCode;
submitForm.diagnosisDesc = props.mainDiagnosis.name || ''; // 设置诊断描述
diagnosisDefinitionList.value = [props.mainDiagnosis];
// 诊断类别优先使用主诊断的medTypeCode但要验证是否在字典选项中
const diagnosisMedTypeCode = props.mainDiagnosis.medTypeCode;
const medTypeExists = med_type.value && med_type.value.some(item => item.value === diagnosisMedTypeCode);
if (medTypeExists) {
submitForm.medTypeCode = diagnosisMedTypeCode;
} else if (med_type.value && med_type.value.length > 0) {
// 如果主诊断的medTypeCode不在字典中默认选择字典第一项
submitForm.medTypeCode = med_type.value[0].value;
}
} else if (med_type.value && med_type.value.length > 0) {
// 无主诊断时,默认选择字典第一项
submitForm.medTypeCode = med_type.value[0].value;
}
}

View File

@@ -1,15 +1,17 @@
<template>
<el-drawer v-model="drawer" title="组套信息" direction="ltr">
<el-drawer v-model="drawer" title="医嘱组套" direction="ltr" size="700px">
<!-- 列表视图 -->
<div v-if="!editMode">
<div style="margin: 10px 0px">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入组套信息"
placeholder="请输入组套名称搜索"
clearable
style="width: 45%; margin-bottom: -6px; margin-right: 50px"
@keyup.enter="getList"
style="width: 45%; margin-bottom: -6px; margin-right: 20px"
@keyup.enter="handleSearch"
>
<template #append>
<el-button icon="Search" @click="getList" />
<el-button icon="Search" @click="handleSearch" />
</template>
</el-input>
<el-radio-group v-model="queryParams.rangeCode" @change="handelRadioChange">
@@ -17,22 +19,292 @@
<el-radio-button :label="2">科室</el-radio-button>
<el-radio-button :label="3">全院</el-radio-button>
</el-radio-group>
<el-button
type="primary"
style="float: right"
@click="handleManageGroup"
v-if="checkPermi(['personalization:ordersGroupPackage:manage'])"
>
管理组套
</el-button>
</div>
<el-table :data="orderList">
<el-table-column label="组套名称" align="center" prop="name" />
<!-- <el-table-column label="组套类型" align="center" prop="typeEnum_enumText" /> -->
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" />
<el-table-column label="操作" align="center">
<el-table
:data="filteredOrderList"
highlight-current-row
v-loading="loading"
border
stripe
style="margin-top: 10px"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="组套名称" align="left" prop="name" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-button type="primary" link @click="handleUseOrderGroup(scope.row)">选择</el-button>
<el-tooltip content="点击进入编辑" placement="top">
<span
style="cursor: pointer; color: #409eff; font-weight: 500; text-decoration: underline;"
@click="handleEditGroup(scope.row)"
>
{{ scope.row.name }}
</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" width="90" />
<el-table-column label="明细数量" align="center" width="80">
<template #default="scope">
<el-tag size="small" type="info">{{ scope.row.detailList?.length || 0 }} </el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handlePreviewGroup(scope.row)">
预览
</el-button>
<el-button type="success" link @click="handleUseOrderGroup(scope.row)">
<el-icon><Select /></el-icon>
应用
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="filteredOrderList.length === 0 && !loading" style="text-align: center; padding: 40px; color: #909399;">
<el-icon :size="48" style="margin-bottom: 10px"><Box /></el-icon>
<p>暂无组套数据</p>
<p style="font-size: 12px; margin-top: 5px;">您可以在"个性化设置 > 医嘱组套"中创建组套</p>
</div>
</div>
<!-- 编辑视图 -->
<div v-else>
<!-- 返回按钮 -->
<div style="margin-bottom: 15px; border-bottom: 1px solid #ebeef5; padding-bottom: 10px;">
<el-button @click="handleCancelEdit">
<el-icon><ArrowLeft /></el-icon>
返回列表
</el-button>
<span style="margin-left: 15px; font-weight: 600; font-size: 16px;">编辑组套</span>
<el-button type="primary" style="float: right" @click="handleSaveGroup" :loading="saving">
<el-icon><Check /></el-icon>
保存组套
</el-button>
</div>
<!-- 基本信息 -->
<div style="margin-bottom: 20px;">
<h4 style="margin: 0 0 15px 0; color: #606266;">基本信息</h4>
<el-form :model="editingGroup" label-width="100px" :inline="true">
<el-form-item label="组套名称" required>
<el-input
v-model="editingGroup.name"
placeholder="请输入组套名称"
style="width: 220px"
clearable
/>
</el-form-item>
<el-form-item label="组套类型">
<el-select v-model="editingGroup.typeEnum" placeholder="请选择" style="width: 150px">
<el-option label="医嘱组套" :value="1" />
<el-option label="诊疗组套" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="使用范围">
<el-select v-model="editingGroup.rangeCode" placeholder="请选择" style="width: 150px">
<el-option label="个人" :value="1" />
<el-option label="科室" :value="2" />
<el-option label="全院" :value="3" />
</el-select>
</el-form-item>
</el-form>
</div>
<!-- 项目列表 -->
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="margin: 0; color: #606266;">
项目列表
<el-tag size="small" type="info">{{ editingGroup.detailList?.length || 0 }} </el-tag>
</h4>
<el-button type="primary" link @click="handleAddItem">
<el-icon><Plus /></el-icon>
添加项目
</el-button>
</div>
<el-table :data="editingGroup.detailList" border size="small" v-loading="detailLoading">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="150" show-overflow-tooltip />
<el-table-column label="数量" width="80" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row.quantity"
:min="1"
:controls="false"
size="small"
style="width: 60px"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCodeName" width="60" align="center" />
<el-table-column label="用法" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.methodCode"
placeholder="用法"
size="small"
filterable
clearable
>
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="频次" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.rateCode"
placeholder="频次"
size="small"
filterable
clearable
>
<el-option
v-for="dict in rate_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="handleRemoveItem(scope.$index)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!editingGroup.detailList || editingGroup.detailList.length === 0"
style="text-align: center; padding: 30px; color: #909399; border: 1px dashed #dcdfe6; margin-top: 10px;">
<p>暂无项目点击"添加项目"按钮添加</p>
</div>
</div>
</div>
<!-- 组套预览对话框 -->
<el-dialog v-model="previewVisible" title="组套预览" width="600px" append-to-body>
<div v-if="currentGroup">
<h4>{{ currentGroup.name }}</h4>
<p style="color: #909399; font-size: 14px;">
使用范围{{ currentGroup.rangeCode_dictText }} |
包含 {{ currentGroup.detailList?.length || 0 }} 个医嘱项
</p>
<el-table :data="currentGroup.detailList" border size="small" style="margin-top: 15px;">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="180" show-overflow-tooltip />
<el-table-column label="数量" prop="quantity" width="80" align="center" />
<el-table-column label="单位" prop="unitCodeName" width="80" align="center" />
<el-table-column label="用法" prop="methodCode_dictText" width="100" align="center" />
<el-table-column label="频次" prop="rateCode_dictText" width="100" align="center" />
</el-table>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="previewVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUseGroup">应用到当前患者</el-button>
</span>
</template>
</el-dialog>
<!-- 添加项目对话框 -->
<el-dialog v-model="addItemVisible" title="添加医嘱项目" width="800px" append-to-body>
<div style="margin-bottom: 15px;">
<el-input
v-model="itemSearchKey"
placeholder="输入医嘱名称或拼音码搜索"
clearable
@keyup.enter="searchAdviceItems"
style="width: 300px"
>
<template #append>
<el-button icon="Search" @click="searchAdviceItems" />
</template>
</el-input>
</div>
<el-table
:data="adviceItemList"
border
highlight-current-row
@current-change="handleItemSelect"
v-loading="itemLoading"
max-height="400"
>
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="adviceName" min-width="150" show-overflow-tooltip />
<el-table-column label="类型" prop="adviceType_dictText" width="80" align="center" />
<el-table-column label="规格" prop="volume" width="100" show-overflow-tooltip />
<el-table-column label="单价" width="80" align="right">
<template #default="scope">
{{ scope.row.unitPrice?.toFixed(2) || '-' }}
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCode_dictText" width="60" align="center" />
</el-table>
<div v-if="adviceItemList.length === 0 && !itemLoading"
style="text-align: center; padding: 30px; color: #909399;">
<p>请输入关键词搜索医嘱项目</p>
</div>
<!-- 选中项目的配置 -->
<div v-if="selectedItem" style="margin-top: 15px; padding: 15px; background: #f5f7fa; border-radius: 4px;">
<h4 style="margin: 0 0 10px 0;">配置项目{{ selectedItem.adviceName }}</h4>
<el-form :inline="true" label-width="80px">
<el-form-item label="数量">
<el-input-number v-model="newItemConfig.quantity" :min="1" :controls="false" />
</el-form-item>
<el-form-item label="用法">
<el-select v-model="newItemConfig.methodCode" placeholder="用法" filterable clearable style="width: 120px">
<el-option v-for="dict in method_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="newItemConfig.rateCode" placeholder="频次" filterable clearable style="width: 120px">
<el-option v-for="dict in rate_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="addItemVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAddItem" :disabled="!selectedItem">
添加到组套
</el-button>
</span>
</template>
</el-dialog>
</el-drawer>
</template>
<script setup>
import {getOrderGroup} from '../api';
import { getOrderGroup, getAdviceBaseInfo, saveOrderGroup } from '../api';
import { Select, Box, ArrowLeft, Check, Plus, Delete } from '@element-plus/icons-vue';
import { checkPermi } from '@/utils/permission';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance();
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
const props = defineProps({
diagnosis: {
@@ -40,48 +312,352 @@ const props = defineProps({
required: true,
},
organizationId: {
type: String,
type: [String, Number],
required: true,
},
patientInfo: {
type: Object,
default: () => ({})
}
});
const drawer = ref(false);
const orderList = ref([]);
const result = ref([]);
const loading = ref(false);
const previewVisible = ref(false);
const currentGroup = ref(null);
// 编辑模式相关
const editMode = ref(false);
const editingGroup = ref({});
const saving = ref(false);
const detailLoading = ref(false);
// 添加项目相关
const addItemVisible = ref(false);
const itemSearchKey = ref('');
const adviceItemList = ref([]);
const itemLoading = ref(false);
const selectedItem = ref(null);
const newItemConfig = ref({
quantity: 1,
methodCode: '',
rateCode: ''
});
const emit = defineEmits(['useOrderGroup']);
const queryParams = ref({
typeEnum: 1,
searchKey: '',
rangeCode: 2,
});
function handleOpen() {
// 根据搜索关键字过滤组套列表
const filteredOrderList = computed(() => {
if (!queryParams.value.searchKey) {
return orderList.value;
}
const keyword = queryParams.value.searchKey.toLowerCase();
return orderList.value.filter(item =>
item.name && item.name.toLowerCase().includes(keyword)
);
});
function handleOpen(group = null, scope = 'personal') {
drawer.value = true;
// 设置范围
if (scope === 'personal') {
queryParams.value.rangeCode = 1;
} else if (scope === 'department') {
queryParams.value.rangeCode = 2;
} else if (scope === 'hospital') {
queryParams.value.rangeCode = 3;
}
// 如果有传入组套数据,进入编辑模式
if (group && group.groupPackageId) {
editMode.value = true;
editingGroup.value = JSON.parse(JSON.stringify(group));
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
} else {
editMode.value = false;
}
getList();
}
function handleSearch() {
console.log('搜索组套:', queryParams.value.searchKey);
}
function handelRadioChange(value) {
queryParams.value.rangeCode = value;
switch (value) {
case 1:
orderList.value = result.value.personalList;
orderList.value = result.value.personalList || [];
break;
case 2:
orderList.value = result.value.organizationList;
orderList.value = result.value.organizationList || [];
break;
case 3:
orderList.value = result.value.hospitalList;
orderList.value = result.value.hospitalList || [];
break;
}
}
// 进入编辑模式
function handleEditGroup(row) {
editMode.value = true;
// 深拷贝组套数据,避免直接修改原数据
editingGroup.value = JSON.parse(JSON.stringify(row));
// 确保 detailList 存在
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
}
// 取消编辑
function handleCancelEdit() {
editMode.value = false;
editingGroup.value = {};
}
// 保存组套
async function handleSaveGroup() {
if (!editingGroup.value.name?.trim()) {
ElMessage.warning('请输入组套名称');
return;
}
saving.value = true;
try {
// 构建保存数据
const saveData = {
groupPackageId: editingGroup.value.groupPackageId || editingGroup.value.id,
name: editingGroup.value.name,
packageTypeEnum: editingGroup.value.typeEnum || 1,
rangeCode: editingGroup.value.rangeCode,
detailList: editingGroup.value.detailList?.map((item, index) => ({
id: item.id,
orderDefinitionId: item.orderDefinitionId,
orderDefinitionName: item.orderDefinitionName,
quantity: item.quantity,
unitCode: item.unitCode,
unitCodeName: item.unitCodeName,
methodCode: item.methodCode,
rateCode: item.rateCode,
dose: item.dose,
doseQuantity: item.doseQuantity,
dispensePerDuration: item.dispensePerDuration,
sortOrder: index
})) || []
};
const res = await saveOrderGroup(saveData);
if (res.code === 200) {
ElMessage.success('保存成功');
editMode.value = false;
editingGroup.value = {};
// 刷新列表
getList();
} else {
ElMessage.error(res.message || '保存失败');
}
} catch (error) {
console.error('保存组套失败:', error);
ElMessage.error('保存失败:' + (error.message || '未知错误'));
} finally {
saving.value = false;
}
}
// 打开添加项目对话框
function handleAddItem() {
addItemVisible.value = true;
itemSearchKey.value = '';
adviceItemList.value = [];
selectedItem.value = null;
newItemConfig.value = {
quantity: 1,
methodCode: '',
rateCode: ''
};
}
// 搜索医嘱项目
async function searchAdviceItems() {
console.log('点击搜索按钮, itemSearchKey:', itemSearchKey.value, 'organizationId:', props.organizationId);
if (!itemSearchKey.value.trim()) {
ElMessage.warning('请输入搜索关键词');
return;
}
// 确保 organizationId 是数字类型
const orgId = Number(props.organizationId) || null;
itemLoading.value = true;
try {
const params = {
searchKey: itemSearchKey.value,
organizationId: orgId,
pageNo: 1,
pageSize: 50,
adviceTypes: '1,2,3'
};
console.log('搜索参数:', params);
const res = await getAdviceBaseInfo(params);
console.log('搜索返回结果:', res);
adviceItemList.value = res.data?.records || res.rows || [];
} catch (error) {
console.error('搜索医嘱项目失败:', error);
ElMessage.error('搜索失败: ' + (error.message || '未知错误'));
} finally {
itemLoading.value = false;
}
}
// 选择医嘱项目
function handleItemSelect(row) {
selectedItem.value = row;
// 设置默认值
newItemConfig.value = {
quantity: 1,
methodCode: row?.methodCode || '',
rateCode: row?.rateCode || ''
};
}
// 确认添加项目到组套
function confirmAddItem() {
if (!selectedItem.value) return;
const newItem = {
orderDefinitionId: selectedItem.value.adviceDefinitionId,
orderDefinitionName: selectedItem.value.adviceName,
quantity: newItemConfig.value.quantity,
unitCode: selectedItem.value.unitCode,
unitCodeName: selectedItem.value.unitCode_dictText,
methodCode: newItemConfig.value.methodCode,
rateCode: newItemConfig.value.rateCode,
dose: selectedItem.value.dose,
doseQuantity: selectedItem.value.doseQuantity,
dispensePerDuration: selectedItem.value.dispensePerDuration,
// 保留医嘱库完整信息用于后续应用
orderDetailInfos: selectedItem.value
};
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
editingGroup.value.detailList.push(newItem);
addItemVisible.value = false;
ElMessage.success('添加成功');
}
// 移除项目
function handleRemoveItem(index) {
editingGroup.value.detailList.splice(index, 1);
}
// 单击应用按钮
function handleUseOrderGroup(row) {
emit('useOrderGroup', row.detailList);
if (!row.detailList || row.detailList.length === 0) {
ElMessage.warning('该组套没有明细项');
return;
}
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
const processedDetailList = row.detailList.map(item => {
const orderDetail = item.orderDetailInfos || {};
return {
// 组套明细字段
...item,
// 医嘱库字段(可能为空,需要兜底)
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
adviceDefinitionId: item.orderDefinitionId || orderDetail.adviceDefinitionId,
// 价格和库存
unitPrice: orderDetail.unitPrice,
minUnitPrice: orderDetail.minUnitPrice,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent || 1,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
// 单位信息
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
minUnitCode: orderDetail.minUnitCode,
doseUnitCode: orderDetail.doseUnitCode,
// 合并后的完整对象(用于 setValue
mergedDetail: {
...orderDetail,
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
quantity: item.quantity,
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || orderDetail.dose,
rateCode: item.rateCode || orderDetail.rateCode,
methodCode: item.methodCode || orderDetail.methodCode,
dispensePerDuration: item.dispensePerDuration || orderDetail.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent || 1,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
}
};
});
emit('useOrderGroup', processedDetailList);
drawer.value = false;
editMode.value = false;
}
// 预览组套
function handlePreviewGroup(row) {
currentGroup.value = row;
previewVisible.value = true;
}
// 确认应用组套(从预览对话框)
function confirmUseGroup() {
if (currentGroup.value) {
handleUseOrderGroup(currentGroup.value);
previewVisible.value = false;
}
}
// 跳转到组套管理页面
function handleManageGroup() {
window.open('/basicmanage/ordersCombination', '_blank');
}
function getList() {
getOrderGroup({ organizationId: props.organizationId }).then((res) => {
result.value = res.data;
loading.value = true;
getOrderGroup({ organizationId: props.organizationId })
.then((res) => {
result.value = res.data || {};
handelRadioChange(queryParams.value.rangeCode);
})
.catch((err) => {
console.error('获取组套列表失败:', err);
ElMessage.error('获取组套列表失败');
})
.finally(() => {
loading.value = false;
});
}
@@ -89,3 +665,11 @@ defineExpose({
handleOpen,
});
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@@ -200,6 +200,7 @@
</div>
</template>
<script setup>
import {watch} from 'vue';
import {
diagnosisInit,
getPractitionerWard,
@@ -219,6 +220,17 @@ const { in_way_code, admit_source_code, med_type } = proxy.useDict(
'med_type'
);
// 监听诊断类别字典加载,默认选择第一项
watch(
med_type,
(newVal) => {
if (newVal && newVal.length > 0 && !submitForm.medTypeCode) {
submitForm.medTypeCode = newVal[0].value;
}
},
{ immediate: true }
);
const emits = defineEmits([]);
const props = defineProps({
patientInfo: {
@@ -337,7 +349,7 @@ const submitForm = reactive({
startTime: formatDateStr(new Date(), 'YYYY-MM-DD HH:mm:ss'), // 入院日期
priorityEnum: props.inHospitalInfo.priorityEnum, // 患者病情字典PatAdmCondition
ambDiagnosisName: props.inHospitalInfo.ambDiagnosisName,
medTypeCode: '21',
medTypeCode: '', // 从字典动态获取默认值
});
// /* 科室 病区 */
// watch(
@@ -445,6 +457,23 @@ function getInitOptions() {
// 过滤出与病区关联过的科室
organization.value = allOrgs.filter((org) => linkedOrgIds.has(org.id));
// Bug #178 Fix: 如果已选科室不在列表中,手动添加以确保正确显示
const selectedOrgId = props.inHospitalInfo?.inHospitalOrgId;
const selectedOrgName = props.inHospitalInfo?.inHospitalOrgName;
if (selectedOrgId && selectedOrgName) {
const exists = organization.value.some((org) => org.id === selectedOrgId);
if (!exists) {
// 将已选科室添加到列表中确保el-tree-select能正确显示名称
organization.value.unshift({
id: selectedOrgId,
name: selectedOrgName,
typeEnum: 2,
classEnum: '2',
children: []
});
}
}
});
// if (!props.noFile) {

View File

@@ -189,6 +189,7 @@ import {patientInfo} from '../../store/patient.js';
import {ElMessage} from 'element-plus';
// const diagnosisList = ref([]);
const allowAdd = ref(false);
const isSaving = ref(false);
const tree = ref([]);
const openDiagnosis = ref(false);
const openAddDiagnosisDialog = ref(false);
@@ -229,14 +230,41 @@ watch(
{ deep: true }
);
// 监听患者信息变化,自动获取病历详情和诊断列表
watch(
() => props.patientInfo,
(newVal) => {
if (newVal?.encounterId) {
getDetail(newVal.encounterId);
getList();
}
},
{ immediate: true, deep: true }
);
function getDetail(encounterId) {
if (!encounterId) {
console.warn('未提供有效的就诊ID无法获取病历详情');
allowAdd.value = false;
return;
}
getEmrDetail(encounterId).then((res) => {
console.log('正在获取病历详情encounterId:', encounterId);
getEmrDetail(encounterId)
.then((res) => {
console.log('病历详情API返回:', res);
if (res.code === 200) {
allowAdd.value = res.data ? true : false;
console.log('设置 allowAdd =', allowAdd.value, ', 病历数据:', res.data);
} else {
allowAdd.value = false;
console.warn('获取病历详情失败:', res.msg);
}
})
.catch((error) => {
console.error('获取病历详情异常:', error);
allowAdd.value = false;
});
}
@@ -246,6 +274,9 @@ function getList() {
return;
}
// 初始化中医诊断列表
const newList = [];
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
if (res.code == 200) {
const datas = (res.data || []).map((item) => {
@@ -405,17 +436,45 @@ function getTree() {
* 添加西医诊断
*/
function handleAddDiagnosis() {
proxy.$refs.formRef.validate((valid) => {
console.log('点击新增诊断按钮allowAdd:', allowAdd.value);
// 检查表单ref是否存在
if (!proxy.$refs.formRef) {
console.error('表单ref不存在');
// 直接添加诊断,不经过表单验证
addDiagnosisItem();
return;
}
proxy.$refs.formRef.validate((valid, fields) => {
console.log('表单验证结果:', valid, '错误字段:', fields);
if (valid) {
if (!allowAdd.value) {
proxy.$modal.msgWarning('请先填写病历');
return;
}
addDiagnosisItem();
} else {
console.warn('表单验证失败:', fields);
// 验证失败时也允许添加(因为是新增空行)
if (allowAdd.value) {
console.log('验证失败但允许添加,强制添加诊断');
addDiagnosisItem();
}
}
});
}
/**
* 添加诊断项
*/
function addDiagnosisItem() {
console.log('执行添加诊断,当前列表长度:', form.value.diagnosisList.length);
form.value.diagnosisList.push({
showPopover: false,
name: undefined,
verificationStatusEnum: 4,
medTypeCode: undefined, // 不设默认值
medTypeCode: undefined,
diagSrtNo: form.value.diagnosisList.length + 1,
iptDiseTypeCode: 2,
diagnosisDesc: '',
@@ -425,8 +484,7 @@ function handleAddDiagnosis() {
if (form.value.diagnosisList.length == 1) {
form.value.diagnosisList[0].maindiseFlag = 1;
}
}
});
console.log('添加完成,新列表长度:', form.value.diagnosisList.length);
}
// 添加中医诊断

View File

@@ -1165,7 +1165,8 @@ function setValue(row) {
let selectedStock = undefined;
if (row.adviceType != 3) {
if (row.inventoryList && row.inventoryList.length == 0) {
// 🔧 Bug #144 修复:检查 inventoryList 是否存在,避免 undefined 错误
if (!row.inventoryList || row.inventoryList.length == 0) {
expandOrder.value = [];
proxy.$modal.msgWarning(row.adviceName + '无库存');
return;
@@ -1229,35 +1230,75 @@ function setValue(row) {
prescriptionList.value[rowIndex.value] = updatedRow;
}
// 选择组套
// 选择组套 - 适配新版 OrderGroupDrawer 组件
function handleSaveGroup(orderGroupList) {
if (!orderGroupList || !Array.isArray(orderGroupList) || orderGroupList.length === 0) {
proxy.$modal.msgWarning('组套数据为空');
return;
}
let successCount = 0;
orderGroupList.forEach((item) => {
rowIndex.value = prescriptionList.value.length;
setValue(item.orderDetailInfos);
if (!item) {
console.warn('组套中的项目为空');
return;
}
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
const mergedDetail = item.mergedDetail || {
...(item.orderDetailInfos || {}),
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1,
positionId: item.orderDetailInfos?.positionId,
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
therapyEnum: item.orderDetailInfos?.therapyEnum || '1',
};
// 预初始化空行
prescriptionList.value[rowIndex.value] = {
uniqueKey: nextId.value++,
isEdit: true,
statusEnum: 1,
};
setValue(mergedDetail);
// 创建新的处方项目
const newRow = {
...prescriptionList.value[rowIndex.value],
uniqueKey: nextId.value++,
patientId: patientInfo.value.patientId,
encounterId: patientInfo.value.encounterId,
accountId: accountId.value, // 费用性质
quantity: item.quantity, // 数量
methodCode: item.methodCode, // 用法
rateCode: item.rateCode, // 频次
dispensePerDuration: item.dispensePerDuration, // 用药天数
dose: item.dose, // 单次用量
doseQuantity: item.doseQuantity, // 单次用量(小单位)
executeNum: 1, // 执行次数默认1
unitCode: item.unitCode, // 大单位
accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1,
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, // 签发状态 默认是待保存
orgId: item.positionId, // 默认执行科室
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', // 1新增2编辑
conditionId: conditionId.value, // 诊断id
conditionDefinitionId: conditionDefinitionId.value, // 诊断定义id
encounterDiagnosisId: encounterDiagnosisId.value, // 就诊诊断id
// 确保 therapyEnum 被正确设置,默认为长期医嘱('1')
statusEnum: 1,
orgId: item.orderDetailInfos?.positionId || mergedDetail.positionId,
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value,
therapyEnum: prescriptionList.value[rowIndex.value]?.therapyEnum || '1',
};
@@ -1270,12 +1311,17 @@ function handleSaveGroup(orderGroupList) {
} else {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity * item.orderDetailInfos.partPercent;
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
}
newRow.contentJson = JSON.stringify(newRow);
prescriptionList.value[rowIndex.value] = newRow;
successCount++;
});
if (successCount > 0) {
proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`);
}
}
// 历史医嘱复用

View File

@@ -137,6 +137,7 @@ const queryParams = ref({
name: '',
useRanges: [1, 2], // 0 暂不使用 1 全院 2 科室 3 个人
organizationId: userStore.orgId,
primaryMenuEnum: 1, // 1-住院病历 (DocTypeEnum.IN_DOC)
});
const loading = ref(false); // 数据加载状态
const currentSelectTemplate = ref({
@@ -560,12 +561,12 @@ const loadLatestMedicalRecord = async () => {
loading.value = true;
try {
// 获取患者的历史病历记录
// const res = await getRecordByEncounterIdList({
// isPage: 0,
// encounterId: patientInfo.value.encounterId,
// patientId: patientInfo.value.patientId,
// definitionId: currentSelectTemplate.value.id,
// });
const res = await getRecordByEncounterIdList({
isPage: 0,
encounterId: patientInfo.value.encounterId,
patientId: patientInfo.value.patientId,
definitionId: currentSelectTemplate.value.id,
});
const historyRecords = res.data || [];
if (historyRecords.length > 0) {
@@ -623,7 +624,8 @@ const loadLatestMedicalRecord = async () => {
loading.value = false;
}
} catch (error) {
ElMessage.error('加载最新病历数据失败=====>', error);
console.error('加载最新病历数据失败:', error);
ElMessage.error('加载最新病历数据失败');
// 出错时也清空选中状态
selectedHistoryRecordId.value = '';
// 出错时也要清空表单数据,避免显示之前患者的数据

View File

@@ -153,6 +153,7 @@ const handleItemClick = (node) => {
updateLocalPatientInfo(node);
diagnosisRef.value?.getList();
diagnosisRef.value?.getDetail(node?.encounterId);
adviceRef.value?.getListInfo();
adviceRef.value?.getDiagnosisInfo();
}, 100); // 100ms 防抖延迟
@@ -189,6 +190,7 @@ watch(activeTabName, (newTab) => {
provide('diagnosisInit', (value) => {
currentPatientInfo.value = value;
diagnosisRef.value.getList();
diagnosisRef.value.getDetail(value?.encounterId);
});
provide('getAdviceList', (value) => {
adviceRef.value.getListInfo();

View File

@@ -107,6 +107,7 @@ import PatientList from '../components/patientList.vue';
import PrescriptionList from './components/prescriptionList.vue';
import SummaryMedicineList from './components/summaryMedicineList.vue';
import {inpatientNurseNavs} from '../constants/navigation';
import { RequestStatus } from '@/utils/medicalConstants';
const { proxy } = getCurrentInstance();
const router = useRouter();
@@ -115,7 +116,7 @@ const activeName = ref('preparation');
const active = ref('first');
const exeStatus = ref(1);
const deadline = ref(proxy.formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const requestStatus = ref(3);
const requestStatus = ref(RequestStatus.COMPLETED);
const chooseAll = ref(false);
const drugType = ref('1');
const isDetails = ref('1');

View File

@@ -26,14 +26,12 @@
>
</el-radio-group>
</el-form-item>
<el-form-item label="" prop="checkboxGroup2">
<el-form-item label="" prop="nursingLevelList">
<el-checkbox-group v-model="queryParams.nursingLevelList" @change="getList">
<el-checkbox-button
v-for="item in levelList"
:key="item.value"
v-model="item.value"
style="display: inline-block"
:label="item.label"
:label="item.value"
>
{{ item.label }}
</el-checkbox-button>
@@ -187,7 +185,7 @@ const data = reactive({
pageNo: 1,
pageSize: 10,
searchKey: undefined, // 品名/商品名/英文品名/编码/拼音
checkboxGroup2: [], // 类型(包括 1中药2成药
nursingLevelList: [], // 护理级别列表(多选
statusEnum: 1, // 状态
},
rules: {},

View File

@@ -389,21 +389,36 @@ const interventionForm = ref({
startTime: '', //入院时间
});
watch(
() => props.pendingInfo,
(newVal, oldVal) => {
console.log(newVal);
if (newVal) {
getPatientInfo({ encounterId: newVal.encounterId }).then((res) => {
console.log('res============>', JSON.stringify(res.data));
pendingInfo.value = res.data;
interventionForm.value.admittingDoctorId = res.data.admittingDoctorId;
interventionForm.value.attendingDoctorId = res.data.attendingDoctorId;
if (res.data.chiefDoctorId) {
interventionForm.value.chiefDoctorId = res.data.chiefDoctorId;
/**
* 获取患者详细信息并填充表单
*/
const loadPatientInfo = () => {
if (!props.pendingInfo?.encounterId) {
return;
}
console.log('查询患者信息的 encounterId:', props.pendingInfo.encounterId);
getPatientInfo({ encounterId: props.pendingInfo.encounterId }).then((res) => {
console.log('后端返回的患者信息:', res.data);
pendingInfo.value = res.data;
// 从后端获取数据后设置医生和护士 ID
// 医生使用 ToStringSerializer返回字符串护士直接返回数字
console.log('admittingDoctorId:', res.data.admittingDoctorId);
console.log('attendingDoctorId:', res.data.attendingDoctorId);
console.log('chiefDoctorId:', res.data.chiefDoctorId);
console.log('primaryNurseId:', res.data.primaryNurseId);
if (res.data.admittingDoctorId) {
interventionForm.value.admittingDoctorId = String(res.data.admittingDoctorId);
}
if (res.data.attendingDoctorId) {
interventionForm.value.attendingDoctorId = String(res.data.attendingDoctorId);
}
if (res.data.chiefDoctorId) {
interventionForm.value.chiefDoctorId = String(res.data.chiefDoctorId);
}
if (res.data.primaryNurseId) {
// 护士ID也转换为字符串以匹配护士选项
interventionForm.value.primaryNurseId = String(res.data.primaryNurseId);
}
interventionForm.value.primaryNurseId = res.data.primaryNurseId;
if (res.data.startTime) {
interventionForm.value.startTime = dayjs(res.data.startTime).format(
'YYYY-MM-DD HH:mm:ss'
@@ -419,22 +434,32 @@ watch(
interventionForm.value.endBloodPressure = res.data.endBloodPressure;
interventionForm.value.highBloodPressure = res.data.highBloodPressure;
});
interventionForm.value.priorityEnum = newVal.priorityEnum;
interventionForm.value.organizationName = newVal.organizationName;
interventionForm.value.wardName = newVal.wardName;
interventionForm.value.bedName = newVal.bedName;
interventionForm.value.priorityEnum = props.pendingInfo.priorityEnum || '';
interventionForm.value.organizationName = props.pendingInfo.organizationName || '';
interventionForm.value.wardName = props.pendingInfo.wardName || '';
interventionForm.value.bedName = props.pendingInfo.bedName || '';
};
watch(
() => props.pendingInfo?.encounterId,
(newVal, oldVal) => {
// encounterId 存在时就获取数据
if (newVal) {
console.log('watch 触发, newVal:', newVal, 'oldVal:', oldVal);
loadPatientInfo();
}
}
},
{ deep: true }
);
/* 初始化数据 */
const init = () => {
initCurrentInPatient();
getInit()
const promises = [];
const initPromise = getInit()
.then((res) => {
InitInfoOptions.value = res.data;
// 安全地设置priorityListOptions
if (res.data && res.data.priorityListOptions) {
priorityListOptions.value = res.data.priorityListOptions;
}
@@ -442,9 +467,10 @@ const init = () => {
.catch((error) => {
console.error('获取初始化数据失败:', error);
});
promises.push(initPromise);
if (props.pendingInfo.wardLocationId) {
getBedInfo({ wardLocationId: props.pendingInfo.wardLocationId })
const bedPromise = getBedInfo({ wardLocationId: props.pendingInfo.wardLocationId })
.then((res) => {
bedInfoOptions.value = res.data || [];
})
@@ -452,18 +478,18 @@ const init = () => {
console.error('获取床位信息失败:', error);
bedInfoOptions.value = [];
});
promises.push(bedPromise);
}
if (props.pendingInfo.organizationId) {
// 主任医生
getDoctorInfo({ organizationId: props.pendingInfo.organizationId })
const doctorPromise = getDoctorInfo({ organizationId: props.pendingInfo.organizationId })
.then((res) => {
console.log('doctorInfoOptions======>', JSON.stringify(res.data));
doctorInfoOptions.value = res.data.records || [];
// 只有在新分配床位模式entranceType != 1时才设置默认主任医生
// 并且只在当前没有选择主任医生时才设置默认值(避免覆盖已从后端获取的数据)
if (props.pendingInfo.entranceType != 1) {
nextTick(() => {
// 如果存在主任医师显示主任,如果没有选择第一个展示
if (doctorInfoOptions.value.length > 0) {
if (doctorInfoOptions.value.length > 0 && !interventionForm.value.chiefDoctorId) {
let selectId = '';
doctorInfoOptions.value.forEach((item: any) => {
if (item.drProfttlCode == '231') {
@@ -477,27 +503,37 @@ const init = () => {
}
}
});
}
})
.catch((error) => {
console.error('获取医生信息失败:', error);
doctorInfoOptions.value = [];
});
promises.push(doctorPromise);
getNurseInfo({ organizationId: props.pendingInfo.organizationId })
const nursePromise = getNurseInfo({ organizationId: props.pendingInfo.organizationId })
.then((res) => {
nurseInfoOptions.value = res.data || [];
// 将护士ID转换为字符串以匹配医生选项的数据类型
nurseInfoOptions.value = (res.data || []).map((item: any) => ({
...item,
practitionerId: String(item.practitionerId),
}));
})
.catch((error) => {
console.error('获取护士信息失败:', error);
nurseInfoOptions.value = [];
});
promises.push(nursePromise);
}
return Promise.all(promises);
};
const rules = reactive<FormRules>({
admittingDoctorId: [{ required: true, message: '请选择住院医生', trigger: ['blur', 'change'] }],
primaryNurseId: [{ required: true, message: '请选择责任护士', trigger: ['blur', 'change'] }],
bedLocationId: [{ required: true, message: '请选择入住床位', trigger: ['blur', 'change'] }],
// 主治医生和主任医生为可选字段,不添加 required 验证
});
const printWristband = ref(false);
@@ -512,29 +548,10 @@ const cancelAct = () => {
};
const resetForm = () => {
// interventionForm.value = {
// height: undefined,
// weight: undefined,
// temperature: undefined,
// hertRate: undefined,
// pulse: undefined,
// endBloodPressure: undefined,
// highBloodPressure: undefined,
// bedLocationId: '', // 床号
// admittingDoctorId: '', // 住院医师
// attendingDoctorId: '', // 主治医师
// chiefDoctorId: '', // 主任医师
// primaryNurseId: '', // 责任护士
// priorityEnum: '', //患者病情
// organizationName: '',
// wardName: '',
// attendingDocUpdateId: '',
// startTime: '', //入院时间
// }
// 可选:清空校验状态
// 只重置表单验证状态,不清空数据
// 数据会在下次打开对话框时通过 loadPatientInfo 重新加载
if (interventionFormRef.value) {
interventionFormRef.value.resetFields();
interventionFormRef.value.clearValidate();
}
};
@@ -549,16 +566,27 @@ const handleSubmit = async () => {
try {
const valid = await interventionFormRef.value.validate();
if (valid) {
// 过滤掉空字符串的字段,只保留用户实际选择的值
const formData = {};
Object.keys(interventionForm.value).forEach(key => {
const value = interventionForm.value[key];
// 保留非空的值0、false等有效值也需要保留
if (value !== '' && value !== null && value !== undefined) {
formData[key] = value;
}
});
const params = {
...pendingInfo.value,
...interventionForm.value,
...formData,
targetBedId: props.pendingInfo.bedId,
busNo: props.pendingInfo.busNo,
inHosTime: props.pendingInfo.inHosTime,
targetHouseId: props.pendingInfo.targetHouseId,
targetEncounterId: props.pendingInfo.targetEncounterId,
editFlag: props.pendingInfo.entranceType == 1 ? 1 : 0,
editFlag: props.pendingInfo.entranceType == 1 ? '1' : '0',
};
console.log('提交参数:', params);
console.log('startTime:', interventionForm.value.startTime);
bedAssignment(params)
.then((res: any) => {
@@ -583,19 +611,15 @@ const handleSubmit = async () => {
});
}
} catch (error) {
console.log('表单验证失败:', error);
console.error('表单验证失败:', error);
}
};
const openAct = () => {
init();
if (props.pendingInfo) {
interventionForm.value.priorityEnum = props.pendingInfo.priorityEnum || '';
interventionForm.value.admittingDoctorId = props.pendingInfo.practitionerId || '';
interventionForm.value.organizationName = props.pendingInfo.organizationName || '';
interventionForm.value.wardName = props.pendingInfo.wardName || '';
interventionForm.value.attendingDocUpdateId = props.pendingInfo.admittingDoctorId || '';
}
// 先初始化数据(包括医生护士列表),等待完成后再加载患者信息
init().then(() => {
loadPatientInfo();
});
};
const closedAct = () => {

View File

@@ -54,11 +54,12 @@
import {getCurrentInstance} from 'vue';
import PatientList from '../components/patientList.vue';
import PrescriptionList from './components/prescriptionList.vue';
import { RequestStatus } from '@/utils/medicalConstants';
const activeName = ref('preparation');
const active = ref('first');
const exeStatus = ref(1);
const requestStatus = ref(3);
const requestStatus = ref(RequestStatus.COMPLETED);
const { proxy } = getCurrentInstance();
// 存储子组件引用的对象

View File

@@ -52,10 +52,11 @@
<script setup>
import PatientList from '../components/patientList.vue';
import PrescriptionList from './components/prescriptionList.vue';
import { RequestStatus } from '@/utils/medicalConstants';
const activeName = ref('unverified');
const active = ref('first');
const requestStatus = ref(2);
const requestStatus = ref(RequestStatus.ACTIVE);
// 存储子组件引用的对象
const prescriptionRefs = ref({});

View File

@@ -136,8 +136,7 @@ const queryParams = ref({
pageNo: 1,
pageSize: 10,
searchKey: undefined,
// 按病区过滤,显示当前护士负责的病区患者
wardLocationId: userStore.wardId,
orgId: userStore.orgId, // 按当前用户科室过滤
});
const recordQueryParams = ref({

File diff suppressed because it is too large Load Diff

View File

@@ -486,6 +486,14 @@
{{ getCheckTypeLabel(item.checkType) || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="number" min="0" placeholder="请输入曝光次数" v-model="item.exposureNum">
</template>
<template v-else>
{{ item.exposureNum || '0' }}
</template>
</td>
<td>
<template v-if="item.editing">
<el-select
@@ -872,41 +880,18 @@ onMounted(async () => {
inspectionTypeDicts.value = typeResponse.data;
// 从数据字典获取检查类型值
checkTypes.value = typeResponse.data.map(item => item.dictValue);
// 从数据字典获取检查类型下拉选项
checkTypeOptions.value = typeResponse.data.map(item => ({
value: item.dictValue,
label: item.dictLabel,
dictValue: item.dictValue,
dictLabel: item.dictLabel
}));
} else {
checkTypes.value = [];
inspectionTypeDicts.value = [];
}
// 从检查类型维护界面获取所有检查类型(用于下拉选项)
try {
const checkTypeListResponse = await getAllCheckTypes();
if (checkTypeListResponse && checkTypeListResponse.data) {
// 处理返回的数据,提取 name 和 code/type 用于下拉选项
const typeList = Array.isArray(checkTypeListResponse.data)
? checkTypeListResponse.data
: (checkTypeListResponse.data.records || []);
checkTypeOptions.value = typeList.map(item => ({
value: item.code || item.type || item.id,
label: item.name,
id: item.id,
code: item.code,
type: item.type
}));
}
} catch (e) {
console.error('获取检查类型列表失败', e);
checkTypeOptions.value = [];
}
// 获取服务范围数据(从数据字典获取)
const scopeResponse = await getDicts('scope_of_services');
if (scopeResponse && scopeResponse.data) {
// 保存完整的服务范围字典数据
serviceScopeDicts.value = scopeResponse.data;
} else {
serviceScopeDicts.value = [];
}
// 获取检查类型表格数据(分页获取)
await loadCheckTypeDataWithPagination();
@@ -1244,14 +1229,30 @@ async function handleConfirm(index) {
ElMessage.error('名称不能为空');
return;
}
// 根据不同菜单验证检查类型字段
if (activeMenu.value === '检查方法') {
if (!item.checkType || item.checkType.trim() === '') {
ElMessage.error('检查类型不能为空');
return;
}
} else if (activeMenu.value === '检查类型') {
if (!item.type || item.type.trim() === '') {
ElMessage.error('检查类型不能为空');
return;
}
} else if (activeMenu.value === '检查部位') {
if (!item.checkType || item.checkType.trim() === '') {
ElMessage.error('检查类型不能为空');
return;
}
}
// 检查类型才需要验证执行科室
if (activeMenu.value === '检查类型') {
if (!item.department || item.department.trim() === '') {
ElMessage.error('执行科室不能为空');
return;
}
}
try {
// 根据当前激活的菜单调用不同的API

View File

@@ -516,6 +516,7 @@ import {
getPharmacyList,
getTransferProductDetail,
getTransferProductDetails,
getWarehouseList,
productTransferApproved,
reject,
submitApproval,
@@ -775,6 +776,17 @@ function handleChangePurposeTypeEnum(value, type) {
}).catch(error => {
console.error('Error fetching dispensary list:', error);
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
purposeTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.purposeLocationId = '';
receiptHeaderForm.purposeLocationId1 = '';
}
}).catch(error => {
console.error('Error fetching warehouse list:', error);
});
} else {
purposeTypeListOptions.value = [];
}
@@ -801,6 +813,17 @@ function handleChangeSourceTypeEnum(value, type) {
}).catch(error => {
console.error('Error fetching dispensary list:', error);
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
sourceTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.sourceLocationId = '';
receiptHeaderForm.sourceLocationId1 = '';
}
}).catch(error => {
console.error('Error fetching warehouse list:', error);
});
} else {
sourceTypeListOptions.value = [];
}

View File

@@ -123,6 +123,14 @@ export function getDispensaryList() {
method: 'get',
})
}
// 获取耗材库列表
export function getWarehouseList() {
return request({
url: '/app-common/warehouse-list',
method: 'get',
})
}
// 获取仓库药房列表
export function getpharmacyCabinetList() {
return request({

View File

@@ -616,6 +616,7 @@ import {
getInit,
getPharmacyList,
getTransferProductDetail,
getWarehouseList,
productTransferApproved,
reject,
submitApproval,
@@ -1148,6 +1149,15 @@ function handleChangePurposeTypeEnum(value, type) {
receiptHeaderForm.purposeLocationId1 = '';
}
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
purposeTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.purposeLocationId = '';
receiptHeaderForm.purposeLocationId1 = '';
}
});
}
}
@@ -1169,6 +1179,15 @@ function handleChangeSourceTypeEnum(value, type) {
receiptHeaderForm.sourceLocationId1 = '';
}
});
} else if (value == 17) {
// 耗材库类型
getWarehouseList().then((res) => {
sourceTypeListOptions.value = res.data;
if (!route.query.supplyBusNo && !type) {
receiptHeaderForm.sourceLocationId = '';
receiptHeaderForm.sourceLocationId1 = '';
}
});
}
}

View File

@@ -239,7 +239,7 @@
<el-table-column type="selection" width="55" align="center" fixed="left"/>
<el-table-column prop="prescriptionNo" label="处方号" width="120" align="center"/>
<el-table-column prop="conditionName" label="诊断" width="120" align="center"/>
<el-table-column prop="medTypeCode_dictText" label="处方类型" width="120" align="center"/>
<el-table-column prop="dispenseEnum_enumText" label="处方类型" width="120" align="center"/>
<el-table-column prop="itemName" label="项目名称" width="160" align="center"/>
<el-table-column prop="merchandiseName" label="商品名称" width="160" align="center"/>
<el-table-column prop="quantity" label="发药数量" width="130" align="center">
@@ -836,7 +836,7 @@ function getRowMarkers(groupCounts, data) {
function spanMethod({row, column, rowIndex, columnIndex}) {
// 定义需要合并的列范围前6列包括selection列
const columnsToMerge = [1, 2, 3, 17]; // 假设selection列是第0列其他列依次是1, 2, 3, 4, 5
const columnsToMerge = [1, 2, 3]; // 假设selection列是第0列其他列依次是1, 2, 3, 4, 5
// 检查当前列是否在需要合并的列范围内
if (row.prescriptionNo) {
if (columnsToMerge.includes(columnIndex)) {

View File

@@ -0,0 +1,62 @@
-- =============================================
-- 创建 lab_observation 表
-- 用途: 实验室观察记录表
-- 创建时间: 2026-03-09
-- =============================================
-- 删除已存在的表(如果存在)
DROP TABLE IF EXISTS lab_observation;
-- 创建序列(如果不存在)
DROP SEQUENCE IF EXISTS lab_observation_id_seq;
CREATE SEQUENCE lab_observation_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
-- 创建表
CREATE TABLE lab_observation (
id BIGINT PRIMARY KEY DEFAULT nextval('lab_observation_id_seq'),
patient_id BIGINT,
encounter_id BIGINT,
specimen_id BIGINT,
observation_definition_id BIGINT,
observation_result TEXT,
observation_date TIMESTAMP,
technician_id BIGINT,
delete_flag VARCHAR(1) DEFAULT '0',
create_by VARCHAR(64) DEFAULT '',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64) DEFAULT '',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
remark VARCHAR(500)
);
-- 添加表注释
COMMENT ON TABLE lab_observation IS '实验室观察记录表';
-- 添加字段注释
COMMENT ON COLUMN lab_observation.id IS '观察记录的唯一标识符';
COMMENT ON COLUMN lab_observation.patient_id IS '患者ID';
COMMENT ON COLUMN lab_observation.encounter_id IS '就诊ID';
COMMENT ON COLUMN lab_observation.specimen_id IS '关联的样本ID';
COMMENT ON COLUMN lab_observation.observation_definition_id IS '观察定义ID';
COMMENT ON COLUMN lab_observation.observation_result IS '观察结果,可能是文本描述或数值';
COMMENT ON COLUMN lab_observation.observation_date IS '观察的日期和时间';
COMMENT ON COLUMN lab_observation.technician_id IS '执行观察的技术员ID';
COMMENT ON COLUMN lab_observation.delete_flag IS '删除状态0-未删除1-已删除';
COMMENT ON COLUMN lab_observation.create_by IS '创建者';
COMMENT ON COLUMN lab_observation.create_time IS '创建时间';
COMMENT ON COLUMN lab_observation.update_by IS '更新者';
COMMENT ON COLUMN lab_observation.update_time IS '更新时间';
COMMENT ON COLUMN lab_observation.remark IS '备注';
-- 创建索引
CREATE INDEX idx_lab_observation_patient_id ON lab_observation(patient_id);
CREATE INDEX idx_lab_observation_specimen_id ON lab_observation(specimen_id);
CREATE INDEX idx_lab_observation_observation_date ON lab_observation(observation_date);
-- 打印完成信息
SELECT 'lab_observation 表创建成功!' AS message;

View File

@@ -0,0 +1,86 @@
-- 创建 lab_specimen 检验标本表
CREATE TABLE hisdev.lab_specimen (
id BIGINT NOT NULL,
service_id BIGINT,
tenant_id BIGINT,
specimen_definition_id BIGINT,
collection_status_enum INTEGER,
collection_date TIMESTAMP WITHOUT TIME ZONE,
received_date TIMESTAMP WITHOUT TIME ZONE,
specimen_volume VARCHAR(50),
specimen_unit VARCHAR(20),
create_by VARCHAR(50),
create_time TIMESTAMP WITHOUT TIME ZONE,
update_by VARCHAR(50),
update_time TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id)
);
COMMENT ON TABLE hisdev.lab_specimen IS '检验标本表';
COMMENT ON COLUMN hisdev.lab_specimen.id IS '主键ID';
COMMENT ON COLUMN hisdev.lab_specimen.service_id IS '服务请求ID(关联wor_service_request)';
COMMENT ON COLUMN hisdev.lab_specimen.tenant_id IS '租户ID';
COMMENT ON COLUMN hisdev.lab_specimen.specimen_definition_id IS '标本定义ID(关联adm_specimen_definition)';
COMMENT ON COLUMN hisdev.lab_specimen.collection_status_enum IS '采集状态(1:待采集 2:已采集 3:已接收)';
COMMENT ON COLUMN hisdev.lab_specimen.collection_date IS '采集时间';
COMMENT ON COLUMN hisdev.lab_specimen.received_date IS '接收时间';
COMMENT ON COLUMN hisdev.lab_specimen.specimen_volume IS '标本量';
COMMENT ON COLUMN hisdev.lab_specimen.specimen_unit IS '标本单位';
COMMENT ON COLUMN hisdev.lab_specimen.create_by IS '创建人';
COMMENT ON COLUMN hisdev.lab_specimen.create_time IS '创建时间';
COMMENT ON COLUMN hisdev.lab_specimen.update_by IS '更新人';
COMMENT ON COLUMN hisdev.lab_specimen.update_time IS '更新时间';
-- 创建 adm_specimen_definition 标本定义表
CREATE TABLE hisdev.adm_specimen_definition (
id BIGINT NOT NULL,
code VARCHAR(50),
name VARCHAR(100),
specimen_name VARCHAR(100),
delete_flag CHAR(1) DEFAULT '0',
create_by VARCHAR(50),
create_time TIMESTAMP WITHOUT TIME ZONE,
update_by VARCHAR(50),
update_time TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id)
);
COMMENT ON TABLE hisdev.adm_specimen_definition IS '标本定义表';
COMMENT ON COLUMN hisdev.adm_specimen_definition.id IS '主键ID';
COMMENT ON COLUMN hisdev.adm_specimen_definition.code IS '标本编码';
COMMENT ON COLUMN hisdev.adm_specimen_definition.name IS '标本名称';
COMMENT ON COLUMN hisdev.adm_specimen_definition.specimen_name IS '标本显示名称';
COMMENT ON COLUMN hisdev.adm_specimen_definition.delete_flag IS '删除标志(0:正常 1:删除)';
-- 创建 adm_observation_definition 检验项目定义表
CREATE TABLE hisdev.adm_observation_definition (
id BIGINT NOT NULL,
code VARCHAR(50),
name VARCHAR(100),
category VARCHAR(50),
unit VARCHAR(20),
reference_range VARCHAR(200),
normal_range VARCHAR(200),
delete_flag CHAR(1) DEFAULT '0',
create_by VARCHAR(50),
create_time TIMESTAMP WITHOUT TIME ZONE,
update_by VARCHAR(50),
update_time TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id)
);
COMMENT ON TABLE hisdev.adm_observation_definition IS '检验项目定义表';
COMMENT ON COLUMN hisdev.adm_observation_definition.id IS '主键ID';
COMMENT ON COLUMN hisdev.adm_observation_definition.code IS '项目编码';
COMMENT ON COLUMN hisdev.adm_observation_definition.name IS '项目名称';
COMMENT ON COLUMN hisdev.adm_observation_definition.category IS '项目类别';
COMMENT ON COLUMN hisdev.adm_observation_definition.unit IS '单位';
COMMENT ON COLUMN hisdev.adm_observation_definition.reference_range IS '参考范围';
COMMENT ON COLUMN hisdev.adm_observation_definition.normal_range IS '正常范围';
COMMENT ON COLUMN hisdev.adm_observation_definition.delete_flag IS '删除标志(0:正常 1:删除)';
-- 创建索引
CREATE INDEX idx_lab_specimen_service_id ON hisdev.lab_specimen(service_id);
CREATE INDEX idx_lab_specimen_specimen_def ON hisdev.lab_specimen(specimen_definition_id);
CREATE INDEX idx_adm_specimen_definition_code ON hisdev.adm_specimen_definition(code);
CREATE INDEX idx_adm_observation_definition_code ON hisdev.adm_observation_definition(code);

View File

@@ -0,0 +1,327 @@
-- ============================================
-- STORY #104: 增加报卡管理界面
-- 创建时间: 2026-03-05
-- 功能说明: 在疾病报告管理目录下增加报卡管理界面菜单
-- ============================================
-- 1. 创建疾病报告管理目录(如果不存在)
-- 注意需要根据实际情况调整parent_id这里假设是一级菜单
INSERT INTO "sys_menu" (
"menu_name",
"parent_id",
"order_num",
"path",
"component",
"query",
"route_name",
"is_frame",
"is_cache",
"menu_type",
"visible",
"status",
"perms",
"icon",
"create_by",
"create_time",
"update_by",
"update_time",
"remark"
)
SELECT
'疾病报告管理',
0, -- 一级菜单,根据实际情况调整
100,
'diseaseReport',
NULL,
NULL,
NULL,
'1',
'0',
'M', -- M=目录
'0',
'0',
NULL,
'documentation',
'admin',
NOW(),
NULL,
NULL,
'疾病报告管理目录'
WHERE NOT EXISTS (
SELECT 1 FROM "sys_menu" WHERE "menu_name" = '疾病报告管理'
);
-- 2. 创建报卡管理菜单
-- 获取疾病报告管理目录的ID
INSERT INTO "sys_menu" (
"menu_name",
"parent_id",
"order_num",
"path",
"component",
"query",
"route_name",
"is_frame",
"is_cache",
"menu_type",
"visible",
"status",
"perms",
"icon",
"create_by",
"create_time",
"update_by",
"update_time",
"remark"
)
SELECT
'报卡管理',
(SELECT "menu_id" FROM "sys_menu" WHERE "menu_name" = '疾病报告管理' LIMIT 1),
1,
'cardManagement',
'cardmanagement/index',
NULL,
'CardManagement',
'1',
'0',
'C', -- C=菜单
'0',
'0',
'card:management:list',
'form',
'admin',
NOW(),
NULL,
NULL,
'报卡管理界面'
WHERE NOT EXISTS (
SELECT 1 FROM "sys_menu" WHERE "menu_name" = '报卡管理'
);
-- 3. 创建报卡管理相关按钮权限
-- 获取报卡管理菜单的ID
INSERT INTO "sys_menu" (
"menu_name",
"parent_id",
"order_num",
"path",
"component",
"query",
"route_name",
"is_frame",
"is_cache",
"menu_type",
"visible",
"status",
"perms",
"icon",
"create_by",
"create_time",
"update_by",
"update_time",
"remark"
)
SELECT
'报卡查询',
(SELECT "menu_id" FROM "sys_menu" WHERE "menu_name" = '报卡管理' LIMIT 1),
1,
'',
NULL,
NULL,
NULL,
'1',
'0',
'F', -- F=按钮
'0',
'0',
'card:management:query',
'#',
'admin',
NOW(),
NULL,
NULL,
''
WHERE NOT EXISTS (
SELECT 1 FROM "sys_menu" WHERE "perms" = 'card:management:query'
);
INSERT INTO "sys_menu" (
"menu_name",
"parent_id",
"order_num",
"path",
"component",
"query",
"route_name",
"is_frame",
"is_cache",
"menu_type",
"visible",
"status",
"perms",
"icon",
"create_by",
"create_time",
"update_by",
"update_time",
"remark"
)
SELECT
'报卡审核',
(SELECT "menu_id" FROM "sys_menu" WHERE "menu_name" = '报卡管理' LIMIT 1),
2,
'',
NULL,
NULL,
NULL,
'1',
'0',
'F',
'0',
'0',
'card:management:audit',
'#',
'admin',
NOW(),
NULL,
NULL,
''
WHERE NOT EXISTS (
SELECT 1 FROM "sys_menu" WHERE "perms" = 'card:management:audit'
);
INSERT INTO "sys_menu" (
"menu_name",
"parent_id",
"order_num",
"path",
"component",
"query",
"route_name",
"is_frame",
"is_cache",
"menu_type",
"visible",
"status",
"perms",
"icon",
"create_by",
"create_time",
"update_by",
"update_time",
"remark"
)
SELECT
'报卡导出',
(SELECT "menu_id" FROM "sys_menu" WHERE "menu_name" = '报卡管理' LIMIT 1),
3,
'',
NULL,
NULL,
NULL,
'1',
'0',
'F',
'0',
'0',
'card:management:export',
'#',
'admin',
NOW(),
NULL,
NULL,
''
WHERE NOT EXISTS (
SELECT 1 FROM "sys_menu" WHERE "perms" = 'card:management:export'
);
-- 4. 创建数据库表(如果不存在)
-- 传染病报卡表
CREATE TABLE IF NOT EXISTS "infectious_card" (
"id" BIGINT PRIMARY KEY,
"card_no" VARCHAR(20) UNIQUE,
"visit_id" BIGINT,
"diag_id" BIGINT,
"pat_id" BIGINT,
"id_type" INTEGER,
"id_no" VARCHAR(30),
"pat_name" VARCHAR(50),
"parent_name" VARCHAR(50),
"sex" CHAR(1),
"birthday" DATE,
"age" INTEGER,
"age_unit" CHAR(1),
"workplace" VARCHAR(100),
"phone" VARCHAR(20),
"contact_phone" VARCHAR(20),
"address_prov" VARCHAR(50),
"address_city" VARCHAR(50),
"address_county" VARCHAR(50),
"address_town" VARCHAR(50),
"address_village" VARCHAR(80),
"address_house" VARCHAR(40),
"patientbelong" VARCHAR(20),
"occupation" VARCHAR(20),
"disease_code" VARCHAR(10),
"disease_name" VARCHAR(100),
"disease_subtype" VARCHAR(50),
"other_disease" VARCHAR(100),
"disease_type" CHAR(1),
"onset_date" DATE,
"diag_date" TIMESTAMP,
"death_date" DATE,
"revised_disease_name" VARCHAR(100),
"return_reason" VARCHAR(200),
"report_org" VARCHAR(100),
"report_org_phone" VARCHAR(20),
"report_doc" VARCHAR(50),
"report_date" DATE,
"status" CHAR(1) DEFAULT '0',
"fail_msg" VARCHAR(500),
"xml_content" TEXT,
"card_name_code" INTEGER,
"registration_source" INTEGER,
"dept_id" BIGINT,
"dept_name" VARCHAR(100),
"doctor_id" BIGINT,
"create_by" VARCHAR(64),
"create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"update_by" VARCHAR(64),
"update_time" TIMESTAMP,
"remark" VARCHAR(500)
);
-- 审核记录表
CREATE TABLE IF NOT EXISTS "infectious_audit" (
"audit_id" BIGINT PRIMARY KEY,
"card_id" BIGINT NOT NULL,
"audit_seq" INTEGER NOT NULL,
"audit_type" CHAR(1) NOT NULL,
"audit_status_from" CHAR(1) NOT NULL,
"audit_status_to" CHAR(1) NOT NULL,
"audit_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"auditor_id" VARCHAR(20) NOT NULL,
"auditor_name" VARCHAR(50) NOT NULL,
"audit_opinion" TEXT,
"reason_for_return" TEXT,
"fail_reason_code" VARCHAR(20),
"fail_reason_desc" TEXT,
"is_batch" BOOLEAN DEFAULT FALSE,
"batch_size" INTEGER DEFAULT 1,
"create_by" VARCHAR(64),
"create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"update_by" VARCHAR(64),
"update_time" TIMESTAMP,
"remark" VARCHAR(500)
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_infectious_card_no ON "infectious_card"("card_no");
CREATE INDEX IF NOT EXISTS idx_infectious_card_status ON "infectious_card"("status");
CREATE INDEX IF NOT EXISTS idx_infectious_card_create_time ON "infectious_card"("create_time");
CREATE INDEX IF NOT EXISTS idx_infectious_audit_card_id ON "infectious_audit"("card_id");
-- 添加迁移记录
INSERT INTO __MigrationsHistory (MigrationId, ProductVersion)
VALUES ('202603051000_card_management_menu', '1.0.0')
ON CONFLICT DO NOTHING;