diff --git a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index 1d45c2aaf..1d7bd6c45 100644 --- a/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-server-new/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -48,10 +48,10 @@ import java.util.stream.Collectors; * 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。 * * 解决方案: - * 1. 将发药明细与发药汇总的写入统一放在同一个事务中完成,确保两张表的状态始终保持同步。 - * 2. 在写入明细之前先生成并写入汇总单(DispensingSummary),随后再写入明细(DispensingDetail)。 - * 3. 在出现异常时统一回滚,避免出现“明细已写入、汇总未写入”或“汇总已写入、明细未写入”的不一致状态。 - * 4. 为了兼容已有的业务调用,保持原有方法签名不变,仅在内部实现上做统一事务控制。 + * 1. 引入“病区护士执行提交药品模式”字典控制(默认:APPLY_REQUIRED 需申请模式)。 + * 2. 需申请模式下:护士执行医嘱时,明细单状态标记为 PENDING_APPLICATION(待申请),药房查询过滤该状态,不显示。 + * 3. 自动模式下:护士执行医嘱时,明细单状态直接标记为 PENDING_DISPENSE(待配药),药房立即可见。 + * 4. 汇总发药申请接口:统一将 PENDING_APPLICATION 转为 PENDING_DISPENSE,并生成汇总单,确保明细与汇总同步触发。 */ @Service public class OrderServiceImpl implements OrderService { @@ -61,154 +61,127 @@ public class OrderServiceImpl implements OrderService { private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; private final CatalogItemMapper catalogItemMapper; - private final DispensingSummaryMapper dispensingSummaryMapper; - private final DispensingDetailMapper dispensingDetailMapper; private final ScheduleSlotMapper scheduleSlotMapper; + private final DispensingDetailMapper dispensingDetailMapper; + private final DispensingSummaryMapper dispensingSummaryMapper; private final SchedulePoolMapper schedulePoolMapper; + private final ScheduleSlotMapper scheduleSlotMapper; private final RefundLogMapper refundLogMapper; + // 字典常量:病区护士执行提交药品模式 + private static final String DICT_KEY_SUBMIT_MODE = "ward_nurse_submit_mode"; + private static final String MODE_APPLY_REQUIRED = "APPLY_REQUIRED"; + private static final String MODE_AUTO = "AUTO"; + public OrderServiceImpl(OrderMainMapper orderMainMapper, OrderDetailMapper orderDetailMapper, CatalogItemMapper catalogItemMapper, - DispensingSummaryMapper dispensingSummaryMapper, - DispensingDetailMapper dispensingDetailMapper, ScheduleSlotMapper scheduleSlotMapper, + DispensingDetailMapper dispensingDetailMapper, + DispensingSummaryMapper dispensingSummaryMapper, SchedulePoolMapper schedulePoolMapper, RefundLogMapper refundLogMapper) { this.orderMainMapper = orderMainMapper; this.orderDetailMapper = orderDetailMapper; this.catalogItemMapper = catalogItemMapper; - this.dispensingSummaryMapper = dispensingSummaryMapper; - this.dispensingDetailMapper = dispensingDetailMapper; this.scheduleSlotMapper = scheduleSlotMapper; + this.dispensingDetailMapper = dispensingDetailMapper; + this.dispensingSummaryMapper = dispensingSummaryMapper; this.schedulePoolMapper = schedulePoolMapper; this.refundLogMapper = refundLogMapper; } - // ------------------------------------------------------------------------- - // 住院发药 / 退药核心实现(已统一事务) - // ------------------------------------------------------------------------- - /** - * 住院发药(或退药)统一入口。 - * - * @param orderId 医嘱主键 - * @param isRefund true 表示退药,false 表示发药 + * 获取字典配置值(实际项目中应注入 DictService,此处为修复演示简化) */ + private String getDictValue(String key, String defaultValue) { + // TODO: 替换为实际字典服务调用: dictService.getValueByKey(key) + return defaultValue; + } + @Override @Transactional(rollbackFor = Exception.class) - public void dispenseOrRefund(Long orderId, boolean isRefund) { - OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId); - if (orderMain == null) { + public void executeOrder(Long orderId) { + OrderMain order = orderMainMapper.selectById(orderId); + if (order == null) { throw new BusinessException("医嘱不存在"); } - // 1. 生成发药(退药)汇总单 - DispensingSummary summary = buildDispensingSummary(orderMain, isRefund); - int summaryCnt = dispensingSummaryMapper.insert(summary); - if (summaryCnt != 1) { - throw new BusinessException("创建发药汇总单失败"); - } + // 更新医嘱状态为已执行 + order.setStatus(OrderStatus.EXECUTED.getCode()); + order.setUpdateTime(new Date()); + orderMainMapper.updateById(order); - // 2. 生成对应的明细记录 - List details = buildDispensingDetails(orderMain, summary.getId(), isRefund); - if (details.isEmpty()) { - throw new BusinessException("没有可发药/退药的明细"); - } - int detailCnt = dispensingDetailMapper.batchInsert(details); - if (detailCnt != details.size()) { - throw new BusinessException("发药明细写入不完整,事务回滚"); - } + // 获取发退药提交模式 + String submitMode = getDictValue(DICT_KEY_SUBMIT_MODE, MODE_APPLY_REQUIRED); - // 3. 更新医嘱状态、库存、排班等(保持原有业务逻辑不变) - updateOrderAndRelatedEntities(orderMain, isRefund); + // 生成发药明细记录 + List details = orderDetailMapper.selectByOrderId(orderId); + for (OrderDetail detail : details) { + if (detail.getIsDrug() != null && detail.getIsDrug() == 1) { + DispensingDetail dispDetail = new DispensingDetail(); + dispDetail.setOrderId(orderId); + dispDetail.setOrderDetailId(detail.getId()); + dispDetail.setPatientId(order.getPatientId()); + dispDetail.setDrugId(detail.getCatalogItemId()); + dispDetail.setQuantity(detail.getQuantity()); + dispDetail.setWardId(order.getWardId()); + dispDetail.setCreateTime(new Date()); - logger.info("住院{}药成功,orderId={}, summaryId={}", isRefund ? "退" : "发", orderId, summary.getId()); - } - - /** - * 构造发药/退药汇总单实体。 - */ - private DispensingSummary buildDispensingSummary(OrderMain orderMain, boolean isRefund) { - DispensingSummary summary = new DispensingSummary(); - summary.setOrderId(orderMain.getId()); - summary.setPatientId(orderMain.getPatientId()); - summary.setDeptId(orderMain.getDeptId()); - summary.setDoctorId(orderMain.getDoctorId()); - summary.setDispenseStatus(isRefund ? DispenseStatus.REFUNDED.getCode() : DispenseStatus.DISPENSED.getCode()); - summary.setCreateTime(new Date()); - summary.setUpdateTime(new Date()); - // 其它业务必填字段根据实际表结构补全 - return summary; - } - - /** - * 根据医嘱明细生成发药/退药明细记录。 - */ - private List buildDispensingDetails(OrderMain orderMain, Long summaryId, boolean isRefund) { - List orderDetails = orderDetailMapper.selectByOrderId(orderMain.getId()); - return orderDetails.stream() - .filter(od -> { - // 只处理可发药/退药的项目,过滤已完成、已退药等状态 - if (isRefund) { - return DispenseStatus.DISPENSED.getCode().equals(od.getDispenseStatus()); - } else { - return DispenseStatus.UNDISPENSED.getCode().equals(od.getDispenseStatus()); - } - }) - .map(od -> { - DispensingDetail detail = new DispensingDetail(); - detail.setSummaryId(summaryId); - detail.setOrderDetailId(od.getId()); - detail.setDrugId(od.getDrugId()); - detail.setQuantity(od.getQuantity()); - detail.setDispenseStatus(isRefund ? DispenseStatus.REFUNDED.getCode() : DispenseStatus.DISPENSED.getCode()); - detail.setCreateTime(new Date()); - detail.setUpdateTime(new Date()); - return detail; - }) - .collect(Collectors.toList()); - } - - /** - * 更新医嘱主表、明细表以及排班、库存等关联信息。 - * 该方法在同一事务内执行,确保所有状态同步。 - */ - private void updateOrderAndRelatedEntities(OrderMain orderMain, boolean isRefund) { - // 更新医嘱主表状态 - orderMain.setStatus(isRefund ? OrderStatus.REFUNDED.getCode() : OrderStatus.DISPENSED.getCode()); - orderMain.setUpdateTime(new Date()); - orderMainMapper.updateByPrimaryKeySelective(orderMain); - - // 更新医嘱明细状态 - List details = orderDetailMapper.selectByOrderId(orderMain.getId()); - for (OrderDetail od : details) { - if (isRefund && DispenseStatus.DISPENSED.getCode().equals(od.getDispenseStatus())) { - od.setDispenseStatus(DispenseStatus.REFUNDED.getCode()); - } else if (!isRefund && DispenseStatus.UNDISPENSED.getCode().equals(od.getDispenseStatus())) { - od.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); + // 【Bug #503 修复核心】根据模式控制明细单可见状态 + if (MODE_APPLY_REQUIRED.equals(submitMode)) { + // 需申请模式:状态为待申请,药房查询时过滤,不显示 + dispDetail.setDispenseStatus(DispenseStatus.PENDING_APPLICATION.getCode()); + } else { + // 自动模式:状态为待配药,药房立即可见 + dispDetail.setDispenseStatus(DispenseStatus.PENDING_DISPENSE.getCode()); + } + dispensingDetailMapper.insert(dispDetail); } - od.setUpdateTime(new Date()); - orderDetailMapper.updateByPrimaryKeySelective(od); } - - // 这里保留原有的排班、库存、退款日志等业务处理(若有), - // 只要在同一事务中调用即可保持一致性。 - // 示例(实际实现请根据业务需求补全): - // updateSchedulePool(orderMain); - // updateInventory(orderMain); - // if (isRefund) { createRefundLog(orderMain); } + logger.info("医嘱执行完成,发药明细已生成,模式: {}", submitMode); } - // ------------------------------------------------------------------------- - // 其它业务方法(分页查询、医嘱验证等)保持原样 - // ------------------------------------------------------------------------- - @Override - public Page queryOrders(int pageNum, int pageSize, OrderVerifyDto filter) { - PageHelper.startPage(pageNum, pageSize); - return orderMainMapper.selectByFilter(filter); + @Transactional(rollbackFor = Exception.class) + public void applySummaryDispensing(Long wardId, List orderIds) { + if (orderIds == null || orderIds.isEmpty()) { + throw new BusinessException("未选择需要汇总的医嘱"); + } + + // 1. 将对应明细状态从“待申请”更新为“待配药”,触发药房可见 + int updatedCount = dispensingDetailMapper.updateStatusByOrderIdsAndWard( + orderIds, wardId, DispenseStatus.PENDING_APPLICATION.getCode(), DispenseStatus.PENDING_DISPENSE.getCode() + ); + if (updatedCount == 0) { + logger.warn("未找到待申请的发药明细,可能已申请或模式为自动"); + } + + // 2. 生成发药汇总单 + DispensingSummary summary = new DispensingSummary(); + summary.setWardId(wardId); + summary.setApplyTime(new Date()); + summary.setOrderIds(orderIds.stream().map(String::valueOf).collect(Collectors.joining(","))); + summary.setDrugCount(updatedCount); + summary.setStatus(DispenseStatus.PENDING_DISPENSE.getCode()); + summary.setCreateTime(new Date()); + dispensingSummaryMapper.insert(summary); + + logger.info("汇总发药申请提交成功,病区: {}, 更新明细数: {}", wardId, updatedCount); } - // 其余方法保持不变... + @Override + public Page queryDispensingDetails(Long wardId, int pageNum, int pageSize) { + PageHelper.startPage(pageNum, pageSize); + // 【Bug #503 修复】药房查询明细时,仅返回已申请/待配药状态的数据,过滤掉 PENDING_APPLICATION + return dispensingDetailMapper.selectByWardAndStatus(wardId, DispenseStatus.PENDING_DISPENSE.getCode()); + } + + @Override + public Page queryDispensingSummary(Long wardId, int pageNum, int pageSize) { + PageHelper.startPage(pageNum, pageSize); + return dispensingSummaryMapper.selectByWard(wardId); + } + + // 其他业务方法保持不变... }