diff --git a/md/需求/97-门诊会诊申请管理界面_2026-1-19.md b/md/需求/97-门诊会诊申请管理界面_2026-1-19.md new file mode 100644 index 00000000..02afb1cf --- /dev/null +++ b/md/需求/97-门诊会诊申请管理界面_2026-1-19.md @@ -0,0 +1,267 @@ +## 门诊会诊申请管理界面PRD文档 + +### 一、页面概述 + +**页面名称**:门诊会诊申请管理界面 +**页面目标**:提供会诊申请的全流程管理功能,包括申请记录查询、编辑申请、查看详情、状态变更等核心操作 +**适用场景**:门诊医生需要查看会诊申请或管理已有申请记录时使用 +**页面类型**:列表页+表单弹窗复合型页面 + +**核心功能**: + +1. 多条件组合筛选会诊申请记录 +2. 会诊申请表格展示与操作(编辑/查看/删除) +3. 会诊申请单的填写与提交 +4. 会诊状态标记(提交/结束) + **用户价值**:规范会诊申请流程,减少纸质单据流转,提高多科室协作效率 +原型图地址:https://static.pm-ai.cn/prototype/20260116/aed1f102d614677f100c0d1fe3104999/index.html +**流程图:** +```mermaid +flowchart TD + Start([Start]) --> A[进入门诊会诊申请管理界面] + A --> B{用户操作类型} + B -->|筛选查询| C[设置筛选条件] + B -->|编辑申请| D[点击编辑按钮] + B -->|查看详情| E[点击查看按钮] + B -->|删除申请| G[点击删除按钮] + + C --> H{验证筛选条件} + H -->|有效| I[展示筛选结果] + H -->|无效| J[显示错误提示] + J --> C + + I --> K[用户浏览列表] + + D --> L{检查会诊状态} + L -->|未结束| M[打开编辑弹窗] + L -->|已结束| N[提示不可编辑] + N --> O[关闭弹窗] + + M --> P[修改表单内容] + P --> Q{表单验证} + Q -->|通过| R[保存修改] + Q -->|不通过| S[标红错误字段] + S --> P + + E --> T{检查会诊状态} + T -->|未结束| U[打开只读弹窗] + T -->|已结束| U + + G --> Z{删除验证} + Z -->|可删除| AA[确认删除] + Z -->|不可删除| AB[提示删除失败] + AB --> K + + AA --> AC[更新状态为已取消] + AC --> AD[更新列表显示] + + R --> AD + AD --> K + K --> AE([End]) +``` + +### 二、整体布局分析 + +**页面宽度**:自适应布局 +**主要区域划分**: + +1. 顶部筛选区(高度自适应,约80px) +2. 表格展示区(高度自适应,占主要空间) +3. 底部页码区(固定高度56px) + **布局特点**:上下布局+弹性布局,采用左右对齐方式 + +### 三、页面区域详细描述 + +#### 1. 顶部筛选区 + +**区域位置**:页面顶部 +**区域尺寸**:100%宽度,高度自适应 +**区域功能**:提供多维度筛选和快速搜索功能 +**包含元素**: + +- **时间类型选择器** + - 元素类型:下拉选择框 + - 显示内容:默认"会诊时间",可选"申请时间" + - 交互行为:点击展开下拉选项 + - 样式特征:宽度120px,高度32px,圆角4px +- **时间范围选择器**(开始/结束时间) + - 元素类型:日期时间输入框 + - 显示内容:placeholder提示"开始时间"/“结束时间” + - 交互行为:点击弹出日期选择面板 + - 样式特征:宽度180px,高度32px +- **申请科室/申请医生选择器** + - 元素类型:带datalist的输入框 + - 显示内容:placeholder提示"选择或输入科室/医生" + - 交互行为:输入时显示匹配选项 + - 数据来源:动态生成申请科室/医生候选列表,取值于门诊会诊申请单表(ConsultationRequest.Department/ RequestingPhysician) +- **会诊状态筛选器** + - 元素类型:下拉选择框 + - 可选值:全部/未提交/提交/结束 + - 默认值:全部 +- **病人姓名搜索框** + - 元素类型:文本输入框 + - 显示内容:placeholder提示"病人姓名" + - 交互行为:支持模糊搜索 +- **操作按钮组** + - 查询按钮 + - 样式:蓝色背景,带搜索图标 + - 交互:触发筛选条件应用 + - 重置按钮 + - 样式:灰色背景,带刷新图标 + - 交互:清空所有筛选条件 + - 打印按钮 + - 样式:深灰色背景,带打印图标 + - 交互:调起浏览器打印功能 + +#### 2. 表格展示区 + +**区域位置**:页面中部 +**区域尺寸**:100%宽度,高度自适应 +**区域功能**:展示会诊申请列表数据,支持行内操作 +**包含元素**: + +取值于门诊会诊申请单表(ConsultationRequest)和门诊会诊申请单表(ConsultationRequest) + +- **数据表格** + - 展示方式:11列固定表头表格 + - 数据字段: + - ID:文本 - 15 -申请单号 + - 急:复选框 - 布尔值 - 示例false – 不可编辑 + - 病人姓名:文本 - 朱某某 - 红色高亮 + - 会诊时间:日期 - 2026-01-05 15:08 + - 申请科室:文本 - 内科 + - 邀请对象:文本 - 吴院长 + - 申请时间:日期 - 2026-01-05 15:08 + - 申请医师:文本 - 演示测试 + - 提交:复选框 - 布尔值 - 示例false + - 结束:复选框 - 布尔值 - 示例false + - 操作功能: + - 编辑按钮(✏️):点击打开编辑弹窗 + - 查看按钮(👁️):点击打开只读弹窗 + - 删除按钮(🗑️):点击确认删除 + - 【删除】将状态改为“已取消” + - UPDATE ConsultationRequest + - SET ConsultationStatus = 50,cancelnatureDate = \<作废会诊时间\> + - WHERE ConsultationID = \<会诊申请单ID\> and ConsultationStatus \<\> 40 ; +- 交互行为: + - 行悬停效果:浅蓝色背景 + - 复选框点击:即时更新状态(需防抖处理) + +#### 3. 底部页码区 + +**区域位置**:页面底部 +**区域尺寸**:100%宽度,固定高度56px +**区域功能**:分页控制和数据统计 +**包含元素**: + +- **总数统计**:总数统计文本(如:“总数:15”) +- **分页控制器**: + - 上一页按钮(\<) + - 当前页按钮(1)active状态 + - 下一页按钮(\>) + - 交互反馈:hover时边框变蓝 + +#### 4. 会诊申请弹窗(模态框) + +**触发方式**:点击表格行操作列的编辑/查看按钮 +**区域功能**:展示/编辑会诊申请详细信息 +**包含元素**: + +- 头部区域 + - 标题:“会诊申请单” + - 关闭按钮(×图标) +- 表单区域(分两栏布局) + - 基础信息区: + - 申请单号(只读) + - 申请时间(不可编辑) + - 病人姓名(不可编辑) + - 性别/年龄(不可编辑) + - 就诊卡号(不可编辑) + - 会诊信息区: + - 会诊时间(日期时间选择器) + - 申请医师(不可编辑) + - 紧急程度(复选框) + - 申请科室(不可编辑) + - 门诊诊断(不可编辑) + - 会诊邀请对象 + - 会诊确认参加医师 + - 所属医生 + - 代表科室 + - 签名医生 + - 签名时间 + - 文本域: + - 病史及会诊目的(多行文本) + - 会诊意见(多行文本) +- 底部按钮区: + - 取消按钮(左对齐) + - 保存按钮(右对齐,蓝色) + +### 四、交互功能详细说明 + +#### 1. 会诊申请编辑功能 + +**功能描述**:修改已有会诊申请信息 +**触发条件**:点击表格行中的"✏️"按钮 +**操作流程**: + +1. 检查会诊状态是否为"结束",若已结束则提示不可编辑 +2. 弹出会诊申请编辑弹窗,填充当前行数据的会诊申请和确认相关的数据 +3. 用户修改表单内容(必填字段校验) +4. 点击"保存"按钮提交修改 + **异常处理**: +- 必填字段为空时,标红提示 +- 保存失败时显示toast提示"保存失败,请重试" + +#### 2. 会诊申请查看功能 + +**功能描述**:查看会诊申请详细信息 +**触发条件**:点击表格行中的"👁️"按钮 +**操作流程**: + +1. 弹出只读弹窗,显示完整申请信息 +2. 所有字段禁用编辑 +3. 仅显示"取消"按钮用于关闭弹窗 + +#### 3. 数据筛选功能 + +**功能描述**:多条件组合查询会诊记录 +**触发条件**:点击"查询"按钮 +**数据过滤逻辑**: + +- 时间范围:根据选择的时间类型(会诊/申请)进行筛选 +- 申请科室/申请医生:支持模糊匹配 +- 会诊状态筛选:支持多选逻辑(未提交/提交/结束) +1. 收集所有筛选条件值 +2. 发起异步请求(示例中为前端过滤) +3. 更新表格数据展示 + **异常处理**: +- 时间范围不合法:提示"结束时间不能早于开始时间" +- 无查询结果:显示空白表格+提示文字 + **性能优化**:前端本地缓存数据,减少服务器请求 + +### 五、数据结构说明 + +门诊会诊申请单表(ConsultationRequest)和门诊会诊申请单表(ConsultationRequest) + +### 六、开发实现要点 + +**样式规范**: + +- **主色调**:\#5D9CEC(按钮/交互元素) +- **辅助色**:\#8E8E8E(次要按钮) +- **字体规范**:14px/1.5(主要内容),16px/1.5(标题) +- **间距系统**:16px(元素间距),24px(区块间距) +- **组件样式**: + - 按钮:圆角6px,内边距0 16px + - 输入框:1px实线边框\#D9D9D9,圆角4px + +**技术要求**: + +- **浏览器兼容**:支持Chrome/Firefox/Edge最新版 +- **性能要求**:列表数据筛选响应时间\<200ms + +**注意事项**: + +1. 状态变更逻辑:已结束的记录不可编辑 +2. 时间字段需要做时区转换处理 +3. 申请科室/申请医生选择器需要支持拼音首字母检索 diff --git a/openhis-server-new/README.md b/openhis-server-new/README.md deleted file mode 100644 index 1d5661d8..00000000 --- a/openhis-server-new/README.md +++ /dev/null @@ -1,2 +0,0 @@ -

OpenHis v0.0.1

- diff --git a/openhis-server-new/add_fields_to_activity_definition.sql b/openhis-server-new/add_fields_to_activity_definition.sql deleted file mode 100644 index 41fb35f1..00000000 --- a/openhis-server-new/add_fields_to_activity_definition.sql +++ /dev/null @@ -1,12 +0,0 @@ --- 为诊疗定义表添加序号和服务范围字段 --- 执行前请先备份数据库 - -ALTER TABLE wor_activity_definition ADD COLUMN IF NOT EXISTS sort_order INTEGER DEFAULT NULL; -ALTER TABLE wor_activity_definition ADD COLUMN IF NOT EXISTS service_range VARCHAR(50) DEFAULT '全部'; - --- 添加注释 -COMMENT ON COLUMN wor_activity_definition.sort_order IS '序号'; -COMMENT ON COLUMN wor_activity_definition.service_range IS '服务范围'; - --- 为现有数据设置默认值 -UPDATE wor_activity_definition SET service_range = '全部' WHERE service_range IS NULL; diff --git a/openhis-server-new/core-framework/pom.xml b/openhis-server-new/core-framework/pom.xml index 08dc1f46..2d3991ea 100644 --- a/openhis-server-new/core-framework/pom.xml +++ b/openhis-server-new/core-framework/pom.xml @@ -54,6 +54,12 @@ oshi-core + + + org.springframework.boot + spring-boot-starter-security + + com.core @@ -65,6 +71,12 @@ core-common + + + com.baomidou + mybatis-plus-boot-starter + + com.github.jsqlparser diff --git a/openhis-server-new/core-generator/pom.xml b/openhis-server-new/core-generator/pom.xml index 24a68b78..cf40a84e 100644 --- a/openhis-server-new/core-generator/pom.xml +++ b/openhis-server-new/core-generator/pom.xml @@ -18,6 +18,12 @@ + + + com.baomidou + mybatis-plus-boot-starter + + org.apache.velocity @@ -36,6 +42,24 @@ druid-spring-boot-starter + + + org.projectlombok + lombok + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.fasterxml.jackson.core + jackson-annotations + + \ No newline at end of file diff --git a/openhis-server-new/core-system/pom.xml b/openhis-server-new/core-system/pom.xml index dc5eaf5b..8c4a79f2 100644 --- a/openhis-server-new/core-system/pom.xml +++ b/openhis-server-new/core-system/pom.xml @@ -23,6 +23,12 @@ + + + com.baomidou + mybatis-plus-boot-starter + + com.core diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java index 6754610a..78f0b1d6 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java @@ -38,6 +38,7 @@ 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -92,6 +93,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp @Resource DoctorStationSendApplyUtil doctorStationSendApplyUtil; + /** * 查询医嘱信息 * @@ -111,6 +113,20 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp public IPage getAdviceBaseInfo(AdviceBaseDto adviceBaseDto, String searchKey, Long locationId, List adviceDefinitionIdParamList, Long organizationId, Integer pageNo, Integer pageSize, Integer pricingFlag, List adviceTypes, String orderPricing) { + + // 生成缓存键,处理可能的null值 + String safeSearchKey = searchKey != null ? searchKey : ""; + String safeAdviceTypesStr = ""; + if (adviceTypes != null && !adviceTypes.isEmpty()) { + safeAdviceTypesStr = String.join(",", adviceTypes.stream().map(String::valueOf).collect(Collectors.toList())); + } + String safeOrganizationId = organizationId != null ? organizationId.toString() : ""; + String safePricingFlag = pricingFlag != null ? pricingFlag.toString() : ""; + String safePageNo = pageNo != null ? pageNo.toString() : ""; + String safePageSize = pageSize != null ? pageSize.toString() : ""; + + log.info("从数据库查询医嘱基础信息"); + // 设置默认科室 (不取前端传的了) organizationId = SecurityUtils.getLoginUser().getOrgId(); @@ -172,98 +188,190 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp .add(cfg.getLocationId()); } } - // 费用定价子表信息 - List childCharge = doctorStationAdviceAppMapper - .getChildCharge(ConditionCode.LOT_NUMBER_PRICE.getCode(), chargeItemDefinitionIdList); - // 费用定价主表信息 - List mainCharge - = doctorStationAdviceAppMapper.getMainCharge(chargeItemDefinitionIdList, PublicationStatus.ACTIVE.getValue()); + // 费用定价子表信息 - 使用分批处理避免大量参数问题 + List childCharge = new ArrayList<>(); + if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) { + // 分批处理,每批最多1000个ID,增加批次大小以减少查询次数 + int batchSize = 1000; + for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size()); + List batch = chargeItemDefinitionIdList.subList(i, endIndex); + childCharge.addAll(doctorStationAdviceAppMapper + .getChildCharge(ConditionCode.LOT_NUMBER_PRICE.getCode(), batch)); + } + } + + // 费用定价主表信息 - 使用分批处理避免大量参数问题 + List mainCharge = new ArrayList<>(); + if (chargeItemDefinitionIdList != null && !chargeItemDefinitionIdList.isEmpty()) { + // 分批处理,每批最多500个ID + int batchSize = 500; + for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size()); + List batch = chargeItemDefinitionIdList.subList(i, endIndex); + mainCharge.addAll(doctorStationAdviceAppMapper.getMainCharge(batch, PublicationStatus.ACTIVE.getValue())); + } + } String unitCode = ""; // 包装单位 Long chargeItemDefinitionId; // 费用定价主表ID for (AdviceBaseDto baseDto : adviceBaseDtoList) { - switch (baseDto.getAdviceTableName()) { - case CommonConstants.TableName.MED_MEDICATION_DEFINITION: // 药品 - // 是否皮试 - baseDto - .setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag())); - // 是否为注射药物 - baseDto.setInjectFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getInjectFlag())); - case CommonConstants.TableName.ADM_DEVICE_DEFINITION: // 耗材 - // 每一条医嘱的库存集合信息 , 包装单位库存前端计算 - List inventoryList = adviceInventory.stream().filter(e -> baseDto - .getAdviceDefinitionId().equals(e.getItemId()) - && baseDto.getAdviceTableName().equals(e.getItemTable()) - && (pharmacyMultipleChoice - || (baseDto.getPositionId() == null || baseDto.getPositionId().equals(e.getLocationId())))) - .collect(Collectors.toList()); - // 库存信息 - baseDto.setInventoryList(inventoryList); - // 设置默认产品批号 - if (!inventoryList.isEmpty()) { - // 库存大于0 - List hasInventoryList = inventoryList.stream() - .filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList()); - if (!hasInventoryList.isEmpty()) { - baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber()); - } - } - if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) { - // 第一步:在medLocationConfig中匹配categoryCode - AdviceInventoryDto result1 = medLocationConfig.stream() - .filter(dto -> baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst() - .orElse(null); - if (result1 != null) { - // 第二步:在inventoryList中匹配locationId - AdviceInventoryDto result2 = inventoryList.stream() - .filter(dto -> result1.getLocationId().equals(dto.getLocationId())).findFirst() - .orElse(null); - if (result2 != null && result2.getLotNumber() != null) { - baseDto.setDefaultLotNumber(result2.getLotNumber()); - } - } - } + String tableName = baseDto.getAdviceTableName(); + if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(tableName)) { // 药品 + // 是否皮试 + baseDto + .setSkinTestFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getSkinTestFlag())); + // 是否为注射药物 + baseDto.setInjectFlag_enumText(EnumUtils.getInfoByValue(Whether.class, baseDto.getInjectFlag())); - unitCode = baseDto.getUnitCode(); - chargeItemDefinitionId = baseDto.getChargeItemDefinitionId(); - List priceDtoList = new ArrayList<>(); - // 库存信息里取 命中条件 去匹配价格 - for (AdviceInventoryDto adviceInventoryDto : inventoryList) { - Long finalChargeItemDefinitionId = chargeItemDefinitionId; - String finalUnitCode = unitCode; - // 从定价子表取价格(适用于批次售卖场景) - List childPrice = childCharge.stream() - .filter(e -> e.getDefinitionId().equals(finalChargeItemDefinitionId) - && e.getConditionValue().equals(adviceInventoryDto.getLotNumber())) - .peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode - .collect(Collectors.toList()); - // 从定价主表取价格(适用于统一零售价场景) - List mainPrice = mainCharge.stream() - .filter(e -> baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) - .collect(Collectors.toList()); - // 按批次售价 - if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) { - priceDtoList.addAll(childPrice); - } else { - priceDtoList.addAll(mainPrice); + // fallthrough to 耗材处理逻辑(保持原有逻辑) + // 每一条医嘱的库存集合信息 , 包装单位库存前端计算 + List inventoryList = adviceInventory.stream().filter(e -> + baseDto.getAdviceDefinitionId() != null && e.getItemId() != null + && baseDto.getAdviceDefinitionId().equals(e.getItemId()) + && baseDto.getAdviceTableName() != null && e.getItemTable() != null + && baseDto.getAdviceTableName().equals(e.getItemTable()) + && (pharmacyMultipleChoice + || (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId()))))) + .collect(Collectors.toList()); + // 库存信息 + baseDto.setInventoryList(inventoryList); + // 设置默认产品批号 + if (!inventoryList.isEmpty()) { + // 库存大于0 + List hasInventoryList = inventoryList.stream() + .filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList()); + if (!hasInventoryList.isEmpty()) { + baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber()); + } + } + if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) { + // 第一步:在medLocationConfig中匹配categoryCode + AdviceInventoryDto result1 = medLocationConfig.stream() + .filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null + && baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst() + .orElse(null); + if (result1 != null) { + // 第二步:在inventoryList中匹配locationId + AdviceInventoryDto result2 = inventoryList.stream() + .filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null + && result1.getLocationId().equals(dto.getLocationId())).findFirst() + .orElse(null); + if (result2 != null && result2.getLotNumber() != null) { + baseDto.setDefaultLotNumber(result2.getLotNumber()); } } - // 价格信息 - baseDto.setPriceList(priceDtoList); - break; - case CommonConstants.TableName.WOR_ACTIVITY_DEFINITION: // 诊疗 - List priceList - = mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) - .collect(Collectors.toList()); - // 价格信息 - baseDto.setPriceList(priceList); - // 活动类型 - baseDto.setActivityType_enumText( - EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType())); - break; - default: - break; + } + + unitCode = baseDto.getUnitCode(); + chargeItemDefinitionId = baseDto.getChargeItemDefinitionId(); + List priceDtoList = new ArrayList<>(); + // 库存信息里取 命中条件 去匹配价格 + for (AdviceInventoryDto adviceInventoryDto : inventoryList) { + Long finalChargeItemDefinitionId = chargeItemDefinitionId; + String finalUnitCode = unitCode; + // 从定价子表取价格(适用于批次售卖场景) + List childPrice = childCharge.stream() + .filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null + && e.getDefinitionId().equals(finalChargeItemDefinitionId) + && e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null + && e.getConditionValue().equals(adviceInventoryDto.getLotNumber())) + .peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode + .collect(Collectors.toList()); + // 从定价主表取价格(适用于统一零售价场景) + List mainPrice = mainCharge.stream() + .filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null + && baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) + .collect(Collectors.toList()); + // 按批次售价 + if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) { + priceDtoList.addAll(childPrice); + } else { + priceDtoList.addAll(mainPrice); + } + } + // 价格信息 + baseDto.setPriceList(priceDtoList); + } else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(tableName)) { // 耗材 + // 每一条医嘱的库存集合信息 , 包装单位库存前端计算 + List inventoryList = adviceInventory.stream().filter(e -> + baseDto.getAdviceDefinitionId() != null && e.getItemId() != null + && baseDto.getAdviceDefinitionId().equals(e.getItemId()) + && baseDto.getAdviceTableName() != null && e.getItemTable() != null + && baseDto.getAdviceTableName().equals(e.getItemTable()) + && (pharmacyMultipleChoice + || (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId()))))) + .collect(Collectors.toList()); + // 库存信息 + baseDto.setInventoryList(inventoryList); + // 设置默认产品批号 + if (!inventoryList.isEmpty()) { + // 库存大于0 + List hasInventoryList = inventoryList.stream() + .filter(e -> e.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList()); + if (!hasInventoryList.isEmpty()) { + baseDto.setDefaultLotNumber(hasInventoryList.get(0).getLotNumber()); + } + } + if (!inventoryList.isEmpty() && !medLocationConfig.isEmpty()) { + // 第一步:在medLocationConfig中匹配categoryCode + AdviceInventoryDto result1 = medLocationConfig.stream() + .filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null + && baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst() + .orElse(null); + if (result1 != null) { + // 第二步:在inventoryList中匹配locationId + AdviceInventoryDto result2 = inventoryList.stream() + .filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null + && result1.getLocationId().equals(dto.getLocationId())).findFirst() + .orElse(null); + if (result2 != null && result2.getLotNumber() != null) { + baseDto.setDefaultLotNumber(result2.getLotNumber()); + } + } + } + + unitCode = baseDto.getUnitCode(); + chargeItemDefinitionId = baseDto.getChargeItemDefinitionId(); + List priceDtoList = new ArrayList<>(); + // 库存信息里取 命中条件 去匹配价格 + for (AdviceInventoryDto adviceInventoryDto : inventoryList) { + Long finalChargeItemDefinitionId = chargeItemDefinitionId; + String finalUnitCode = unitCode; + // 从定价子表取价格(适用于批次售卖场景) + List childPrice = childCharge.stream() + .filter(e -> e.getDefinitionId() != null && finalChargeItemDefinitionId != null + && e.getDefinitionId().equals(finalChargeItemDefinitionId) + && e.getConditionValue() != null && adviceInventoryDto.getLotNumber() != null + && e.getConditionValue().equals(adviceInventoryDto.getLotNumber())) + .peek(e -> e.setUnitCode(finalUnitCode)) // 设置 unitCode + .collect(Collectors.toList()); + // 从定价主表取价格(适用于统一零售价场景) + List mainPrice = mainCharge.stream() + .filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null + && baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) + .collect(Collectors.toList()); + // 按批次售价 + if (OrderPricingSource.BATCH_SELLING_PRICE.getCode().equals(orderPricingSource)) { + priceDtoList.addAll(childPrice); + } else { + priceDtoList.addAll(mainPrice); + } + } + // 价格信息 + baseDto.setPriceList(priceDtoList); + } else if (CommonConstants.TableName.WOR_ACTIVITY_DEFINITION.equals(tableName)) { // 诊疗 + List priceList + = mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null + && baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId())) + .collect(Collectors.toList()); + // 价格信息 + baseDto.setPriceList(priceList); + // 活动类型 + baseDto.setActivityType_enumText( + EnumUtils.getInfoByValue(ActivityType.class, baseDto.getActivityType())); } } + return adviceBaseInfo; } @@ -288,83 +396,99 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp */ @Override public R saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) { - // 患者挂号对应的科室id - Long organizationId = adviceSaveParam.getOrganizationId(); - // 医嘱分类信息 - List adviceSaveList = adviceSaveParam.getAdviceSaveList(); - // 药品 - List medicineList = adviceSaveList.stream() - .filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); - // 耗材 - List deviceList = adviceSaveList.stream() - .filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); - // 诊疗活动 - List activityList = adviceSaveList.stream() - .filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); + try { + // 患者挂号对应的科室id + Long organizationId = adviceSaveParam.getOrganizationId(); + // 医嘱分类信息 + List adviceSaveList = adviceSaveParam.getAdviceSaveList(); + // 药品 + List medicineList = adviceSaveList.stream() + .filter(e -> ItemType.MEDICINE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); + // 耗材 + List deviceList = adviceSaveList.stream() + .filter(e -> ItemType.DEVICE.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); + // 诊疗活动 + List activityList = adviceSaveList.stream() + .filter(e -> ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); - /** - * 保存时,校验库存 - */ - if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) { - List medUpdateList - = medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); - List devUpdateList - = deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); - // 编辑时,释放本身占用的库存发放 - for (AdviceSaveDto adviceSaveDto : medUpdateList) { - iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId()); + /** + * 保存时,校验库存 + */ + if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) { + List medUpdateList + = medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); + List devUpdateList + = deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList()); + // 编辑时,释放本身占用的库存发放 + for (AdviceSaveDto adviceSaveDto : medUpdateList) { + iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId()); + } + for (AdviceSaveDto adviceSaveDto : devUpdateList) { + iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId()); + } + + List needCheckList + = adviceSaveList.stream().filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) + && !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); + // 校验库存 + String tipRes = adviceUtils.checkInventory(needCheckList); + if (tipRes != null) { + return R.fail(null, tipRes); + } } - for (AdviceSaveDto adviceSaveDto : devUpdateList) { - iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId()); + // 当前时间 + Date curDate = new Date(); + // 医嘱签发编码 + String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10); + + /** + * 处理药品请求 + */ + List medRequestIdList + = this.handMedication(medicineList, curDate, adviceOpType, organizationId, signCode); + + /** + * 处理诊疗项目请求 + */ + this.handService(activityList, curDate, adviceOpType, organizationId, signCode); + + /** + * 处理耗材请求 + */ + this.handDevice(deviceList, curDate, adviceOpType); + + // 签发时,把草稿状态的账单更新为待收费 + if (AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType) && !adviceSaveList.isEmpty()) { + // 签发的医嘱id集合 + List requestIds = adviceSaveList.stream() + .filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null) + .collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList()); + // 就诊id + Long encounterId = adviceSaveList.get(0).getEncounterId(); + iChargeItemService.update(new LambdaUpdateWrapper() + .set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue()) + .eq(ChargeItem::getEncounterId, encounterId) + .eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue()) + .in(ChargeItem::getServiceId, requestIds)); } - List needCheckList - = adviceSaveList.stream().filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) - && !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList()); - // 校验库存 - String tipRes = adviceUtils.checkInventory(needCheckList); - if (tipRes != null) { - return R.fail(null, tipRes); - } + // 数据变更后清理相关缓存 + clearRelatedCache(); + + return R.ok(medRequestIdList, + MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"})); + } catch (Exception e) { + // 异常处理 + throw e; } - // 当前时间 - Date curDate = new Date(); - // 医嘱签发编码 - String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), 10); + } - /** - * 处理药品请求 - */ - List medRequestIdList - = this.handMedication(medicineList, curDate, adviceOpType, organizationId, signCode); - - /** - * 处理诊疗项目请求 - */ - this.handService(activityList, curDate, adviceOpType, organizationId, signCode); - - /** - * 处理耗材请求 - */ - this.handDevice(deviceList, curDate, adviceOpType); - - // 签发时,把草稿状态的账单更新为待收费 - if (AdviceOpType.SIGN_ADVICE.getCode().equals(adviceOpType) && !adviceSaveList.isEmpty()) { - // 签发的医嘱id集合 - List requestIds = adviceSaveList.stream() - .filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null) - .collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList()); - // 就诊id - Long encounterId = adviceSaveList.get(0).getEncounterId(); - iChargeItemService.update(new LambdaUpdateWrapper() - .set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue()) - .eq(ChargeItem::getEncounterId, encounterId) - .eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue()) - .in(ChargeItem::getServiceId, requestIds)); - } - - return R.ok(medRequestIdList, - MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"})); + /** + * 清理相关缓存 + */ + private void clearRelatedCache() { + // 目前不使用缓存,此方法为空实现 + // 如果将来启用缓存,可以在这里实现缓存清理逻辑 } /** diff --git a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml index 31e63278..6c3d5c7d 100644 --- a/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml +++ b/openhis-server-new/openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml @@ -395,20 +395,18 @@ T1.condition_value, T1.condition_code, T1.amount AS price - FROM - adm_charge_item_def_detail AS T1 - LEFT JOIN adm_charge_item_definition AS T2 ON T2.ID = T1.definition_id - AND T2.delete_flag = '0' - WHERE - T1.delete_flag = '0' - AND T1.condition_code = #{conditionCode} + FROM adm_charge_item_def_detail AS T1 + INNER JOIN adm_charge_item_definition AS T2 ON T2.ID = T1.definition_id + WHERE T1.delete_flag = '0' + AND T2.delete_flag = '0' + AND T1.condition_code = #{conditionCode} AND T1.definition_id IN #{itemId} - ORDER BY T1.priority DESC + ORDER BY T1.priority DESC, T1.definition_id