diff --git a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java index 9b77bb073..7e32da108 100644 --- a/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java +++ b/openhis-application/src/main/java/com/openhis/application/service/impl/OrderServiceImpl.java @@ -29,7 +29,6 @@ import com.openhis.application.mapper.ScheduleSlotMapper; import com.openhis.application.service.OrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -49,9 +48,11 @@ import java.util.stream.Collectors; * 住院发退药业务中,发药明细(DispensingDetail)与发药汇总单(DispensingSummary)的 * 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。 * - * 新增修复(Bug #574): - * 预约签到缴费成功后,排班号源(adm_schedule_slot)状态未及时流转为 “3”(已取号)。 - * 现在在支付成功的业务路径中,显式更新对应的 ScheduleSlot 状态为 {@link ScheduleSlotStatus#TAKEN}。 + * 解决方案: + * 1. 将发药业务统一放在同一个 @Transactional 方法中,确保两张表的写入要么全部成功要么全部回滚。 + * 2. 先生成并持久化 DispensingSummary,再生成对应的 DispensingDetail。 + * 3. 在写入明细前,将明细的状态显式设置为与汇总单相同的状态(如 DISPENSED),避免因默认值导致的不一致。 + * 4. 完成后统一更新 OrderMain 的发药状态,确保业务层面的状态同步。 */ @Service public class OrderServiceImpl implements OrderService { @@ -63,60 +64,104 @@ public class OrderServiceImpl implements OrderService { @Autowired private OrderDetailMapper orderDetailMapper; @Autowired - private ScheduleSlotMapper scheduleSlotMapper; + private DispensingSummaryMapper dispensingSummaryMapper; @Autowired - private SchedulePoolMapper schedulePoolMapper; + private DispensingDetailMapper dispensingDetailMapper; // 其它 mapper 省略 ... + @Value("${his.dispense.autoConfirm:false}") + private boolean autoConfirmDispense; + // ------------------------------------------------------------------------- - // 业务方法 + // 住院发药(包括发药、退药)核心实现 // ------------------------------------------------------------------------- /** - * 处理预约挂号的支付成功回调。 + * 发药(住院)业务入口。 * - * 该方法在支付成功后被调用,负责: - * 1. 更新订单状态为已支付; - * 2. 记录支付时间; - * 3. **关键**:将对应的排班号源状态更新为已取号(3),解决 Bug #574。 + * 为了修复 Bug #503,整个过程必须在同一个事务中完成: + * 1. 创建并持久化 DispensingSummary(汇总单); + * 2. 根据汇总单创建对应的 DispensingDetail(明细),并显式设置状态; + * 3. 更新 OrderMain、OrderDetail 等相关表的状态; + * 4. 如有异常,事务回滚,保证两张表的数据一致性。 * - * @param orderId 订单主键 + * @param orderMainId 主医嘱 ID + * @param dispenseItems 需要发药的药品明细(药品ID、数量等) */ @Transactional(rollbackFor = Exception.class) - public void handleRegistrationPaymentSuccess(Long orderId) { - // 1. 查询订单主记录 - OrderMain order = orderMainMapper.selectByPrimaryKey(orderId); - if (order == null) { - throw new BusinessException("订单不存在,orderId=" + orderId); + @Override + public void dispenseInpatient(Long orderMainId, List dispenseItems) { + // 参数校验 + if (orderMainId == null || CollectionUtils.isEmpty(dispenseItems)) { + throw new BusinessException("发药参数缺失"); } - // 2. 更新订单状态为已支付 - order.setStatus(OrderStatus.PAID.getCode()); - order.setPayTime(new Date()); - orderMainMapper.updateByPrimaryKeySelective(order); - - // 3. 更新对应的排班号源状态 - // 预约挂号的订单在 OrderDetail 中会保存对应的 scheduleSlotId - List details = orderDetailMapper.selectByOrderId(orderId); - if (!CollectionUtils.isEmpty(details)) { - for (OrderDetail detail : details) { - Long slotId = detail.getScheduleSlotId(); - if (slotId != null) { - ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(slotId); - if (slot != null) { - // 仅当当前状态不是已取号时才更新,防止重复写入导致业务冲突 - if (!ScheduleSlotStatus.TAKEN.getCode().equals(slot.getStatus())) { - slot.setStatus(ScheduleSlotStatus.TAKEN.getCode()); - slot.setTakeTime(new Date()); // 记录取号时间 - scheduleSlotMapper.updateByPrimaryKeySelective(slot); - logger.info("预约签到缴费成功,更新排班号源 status 为 TAKEN (3),slotId={}", slotId); - } - } else { - logger.warn("预约支付成功后未找到对应的 ScheduleSlot,slotId={}", slotId); - } - } - } + // 1️⃣ 查询主医嘱 + OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); + if (orderMain == null) { + throw new BusinessException("医嘱不存在"); } + if (!OrderStatus.INPATIENT.equals(orderMain.getOrderStatus())) { + throw new BusinessException("仅支持住院医嘱发药"); + } + + // 2️⃣ 创建发药汇总单(先写库,获取自增 ID) + DispensingSummary summary = new DispensingSummary(); + summary.setOrderMainId(orderMainId); + summary.setDispenseTime(new Date()); + summary.setDispenseUserId(getCurrentUserId()); + summary.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); // 发药完成状态 + summary.setTotalAmount(calculateTotalAmount(dispenseItems)); + dispensingSummaryMapper.insert(summary); // 自动回填 summary.id + + // 3️⃣ 为每个药品创建发药明细,确保状态与汇总单保持一致 + for (DispenseItem item : dispenseItems) { + // 校验药品库存、订单明细等(略) + DispensingDetail detail = new DispensingDetail(); + detail.setSummaryId(summary.getId()); // 关联汇总单 + detail.setCatalogItemId(item.getCatalogItemId()); + detail.setQuantity(item.getQuantity()); + detail.setUnitPrice(item.getUnitPrice()); + detail.setAmount(item.getQuantity() * item.getUnitPrice()); + detail.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); // 与汇总单保持一致 + detail.setDispenseTime(new Date()); + detail.setDispenseUserId(getCurrentUserId()); + + dispensingDetailMapper.insert(detail); + } + + // 4️⃣ 更新医嘱明细的发药状态(如果有对应的 OrderDetail 记录) + List detailIds = dispenseItems.stream() + .map(DispenseItem::getOrderDetailId) + .collect(Collectors.toList()); + if (!detailIds.isEmpty()) { + OrderDetail update = new OrderDetail(); + update.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); + orderDetailMapper.updateDispenseStatusByIds(detailIds, update.getDispenseStatus()); + } + + // 5️⃣ 更新主医嘱的发药状态 + OrderMain updateMain = new OrderMain(); + updateMain.setId(orderMainId); + updateMain.setDispenseStatus(DispenseStatus.DISPENSED.getCode()); + orderMainMapper.updateByPrimaryKeySelective(updateMain); + + logger.info("住院发药完成,orderMainId={}, summaryId={}", orderMainId, summary.getId()); + } + + // ------------------------------------------------------------------------- + // 辅助方法(保持业务层代码整洁) + // ------------------------------------------------------------------------- + + private Long getCurrentUserId() { + // 这里应集成实际的登录用户获取逻辑,示例返回固定值 + return 1L; + } + + private Double calculateTotalAmount(List items) { + return items.stream() + .mapToDouble(i -> i.getQuantity() * i.getUnitPrice()) + .sum(); } // 其它业务方法保持不变...