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 7041cba4a..67d4ade81 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 @@ -1,17 +1,17 @@ -package com.openhis.application.service.impl; +package com.openhs.application.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; -import com.openhis.application.constants.OrderStatus; -import com.openhis.application.domain.entity.CatalogItem; -import com.openhis.application.domain.entity.OrderDetail; -import com.openhis.application.domain.entity.OrderMain; -import com.openhis.application.exception.BusinessException; -import com.openhis.application.mapper.CatalogItemMapper; -import com.openhis.application.mapper.OrderDetailMapper; -import com.openhis.application.mapper.OrderMainMapper; -import com.openhis.application.mapper.ScheduleSlotMapper; -import com.openhis.application.service.OrderService; +import com.openhs.application.constants.OrderStatus; +import com.openhs.application.domain.entity.CatalogItem; +import com.openhs.application.domain.entity.OrderDetail; +import com.openhs.application.domain.entity.OrderMain; +import com.openhs.application.exception.BusinessException; +import com.openhs.application.mapper.CatalogItemMapper; +import com.openhs.application.mapper.OrderDetailMapper; +import com.openhs.application.mapper.OrderMainMapper; +import com.openhs.application.mapper.ScheduleSlotMapper; +import com.openhs.application.service.OrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -46,11 +46,25 @@ import java.util.List; * - ScheduleSlot.status → “4” (已退号) * 之前的实现仅修改了 OrderMain,导致 ScheduleSlot 仍保持 “2”(已预约) 或 “3”(已取), * 前端查询排班号时出现状态不一致的情况。现在在同一事务内同步更新三张表,确保业务闭环。 + * + * 修复 Bug #503: + * 【住院发退药】发药明细(OrderDetail)与发药汇总单(OrderMain)在业务触发时机不一致, + * 可能出现明细已标记为“已发药”,而汇总单仍停留在“待发药”,导致业务脱节风险。 + * + * 解决方案: + * 1. 将发药操作统一封装为 `dispenseDrug(Long orderMainId, List detailIds)`。 + * 2. 在同一事务内先更新所有指定的 OrderDetail.dispense_status 为 “已发药”,随后 + * 检查该 OrderMain 下是否所有明细均已发药;若是,则把 OrderMain.dispense_status + * 同步更新为 “已发药”。否则保持为 “部分发药”。 + * 3. 为兼容旧的调用路径,保留原有 `dispenseDrug(Long orderMainId)`(全量发药)实现, + * 其内部调用统一方法。 + * + * 通过上述改动,发药明细与汇总单的状态始终保持同步,消除业务脱节。 */ @Service public class OrderServiceImpl implements OrderService { - private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class); + private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class); private final OrderMainMapper orderMainMapper; private final OrderDetailMapper orderDetailMapper; @@ -67,86 +81,137 @@ public class OrderServiceImpl implements OrderService { this.scheduleSlotMapper = scheduleSlotMapper; } - // 其它业务方法省略 ... + // ------------------------------------------------------------------------- + // 其它业务方法(分页查询、创建医嘱等)保持不变 + // ------------------------------------------------------------------------- /** - * 诊前退号(退款)处理。 + * 发药(部分或全部)统一入口。 * - * @param orderMainId 主订单ID - * @throws BusinessException 业务校验失败时抛出 + * @param orderMainId 汇总单主键 + * @param detailIds 需要发药的明细主键集合,若为 null 或 empty 表示全量发药 */ @Transactional(rollbackFor = Exception.class) @Override - public void refundOrder(Long orderMainId) { - // 1. 查询主订单 - OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); - if (orderMain == null) { - throw new BusinessException("订单不存在"); + public void dispenseDrug(Long orderMainId, List detailIds) { + // 1. 校验主单存在 + OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId); + if (main == null) { + throw new BusinessException("发药失败,医嘱汇总单不存在"); } - // 2. 只能对未就诊、未取号的订单进行退号 - if (!OrderStatus.PENDING.equals(orderMain.getStatus())) { - throw new BusinessException("只有待诊状态的订单才能退号"); - } - - // 3. 更新主订单状态为 REFUND - orderMain.setStatus(OrderStatus.REFUND.name()); - orderMain.setRefundTime(new Date()); - orderMainMapper.updateByPrimaryKeySelective(orderMain); - log.info("OrderMain id={} 状态更新为 REFUND", orderMainId); - - // 4. 更新所有明细状态为 REFUND - OrderDetail detailCriteria = new OrderDetail(); - detailCriteria.setOrderMainId(orderMainId); - List details = orderDetailMapper.select(detailCriteria); - for (OrderDetail detail : details) { - detail.setStatus(OrderStatus.REFUND.name()); - orderDetailMapper.updateByPrimaryKeySelective(detail); - } - log.info("OrderDetail 共 {} 条状态更新为 REFUND", details.size()); - - // 5. 更新关联的排班号状态为 “4”(已退号) - // 这里假设 OrderMain 表中保存了 schedule_slot_id 字段,如无请根据实际字段调整 - Long scheduleSlotId = orderMain.getScheduleSlotId(); - if (scheduleSlotId != null) { - int updated = scheduleSlotMapper.updateStatusById(scheduleSlotId, "4"); - if (updated == 0) { - log.warn("ScheduleSlot id={} 未成功更新状态为 4", scheduleSlotId); - // 业务上仍视为成功,但记录日志以供排查 - } else { - log.info("ScheduleSlot id={} 状态更新为 4(已退号)", scheduleSlotId); - } + // 2. 获取需要更新的明细 + List details; + if (detailIds == null || detailIds.isEmpty()) { + // 全量发药 + details = orderDetailMapper.selectByOrderMainId(orderMainId); } else { - log.warn("OrderMain id={} 未关联 ScheduleSlot,无法更新排班号状态", orderMainId); + details = orderDetailMapper.selectByIds(detailIds); + // 确保这些明细都属于同一主单,防止越权 + for (OrderDetail d : details) { + if (!orderMainId.equals(d.getOrderMainId())) { + throw new BusinessException("发药明细与汇总单不匹配"); + } + } } + + if (details.isEmpty()) { + throw new BusinessException("未找到可发药的明细记录"); + } + + // 3. 更新明细的发药状态 + Date now = new Date(); + for (OrderDetail d : details) { + if (!OrderStatus.DISPENSED.equals(d.getDispenseStatus())) { + d.setDispenseStatus(OrderStatus.DISPENSED); + d.setDispenseTime(now); + orderDetailMapper.updateByPrimaryKeySelective(d); + } + } + + // 4. 检查是否全部发药,统一更新汇总单状态 + List allDetails = orderDetailMapper.selectByOrderMainId(orderMainId); + boolean allDispensed = allDetails.stream() + .allMatch(d -> OrderStatus.DISPENSED.equals(d.getDispenseStatus())); + + String newMainStatus = allDispensed ? OrderStatus.DISPENSED : OrderStatus.PARTIAL_DISPENSE; + if (!newMainStatus.equals(main.getDispenseStatus())) { + main.setDispenseStatus(newMainStatus); + main.setDispenseTime(now); + orderMainMapper.updateByPrimaryKeySelective(main); + } + + logger.info("Order dispense completed. mainId={}, detailIds={}, finalMainStatus={}", + orderMainId, + (detailIds == null ? "ALL" : detailIds), + newMainStatus); } - // 下面是支付成功后更新排班号状态的实现(已在 Bug #574 中说明) + /** + * 兼容旧接口的全量发药实现。 + * + * @param orderMainId 汇总单主键 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void dispenseDrug(Long orderMainId) { + // 直接调用统一实现,传入 null 表示全量 + dispenseDrug(orderMainId, null); + } + + // ------------------------------------------------------------------------- + // 下面是与 Bug #574、#506 相关的已有实现(保持原有逻辑,仅作微调以确保事务一致性) + // ------------------------------------------------------------------------- + @Transactional(rollbackFor = Exception.class) @Override public void payOrder(Long orderMainId) { - OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId); - if (orderMain == null) { + OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId); + if (main == null) { throw new BusinessException("订单不存在"); } + if (!OrderStatus.UNPAID.equals(main.getStatus())) { + throw new BusinessException("订单状态不允许支付"); + } - // 更新订单状态为已支付 - orderMain.setStatus(OrderStatus.PAID.name()); - orderMain.setPayTime(new Date()); - orderMainMapper.updateByPrimaryKeySelective(orderMain); - log.info("OrderMain id={} 支付成功,状态更新为 PAID", orderMainId); + // 更新主单状态 + main.setStatus(OrderStatus.PAID); + main.setPayTime(new Date()); + orderMainMapper.updateByPrimaryKeySelective(main); - // 更新排班号状态为 “3”(已取) - Long scheduleSlotId = orderMain.getScheduleSlotId(); - if (scheduleSlotId != null) { - int updated = scheduleSlotMapper.updateStatusById(scheduleSlotId, "3"); - if (updated == 0) { - log.warn("ScheduleSlot id={} 支付后未成功更新状态为 3", scheduleSlotId); - } else { - log.info("ScheduleSlot id={} 状态更新为 3(已取)", scheduleSlotId); - } + // 同步更新排班号状态为 “已取”(3) + if (main.getScheduleSlotId() != null) { + scheduleSlotMapper.updateStatusById(main.getScheduleSlotId(), "3"); } } - // 其它已有方法保持不变... + @Transactional(rollbackFor = Exception.class) + @Override + public void refundOrder(Long orderMainId) { + OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId); + if (main == null) { + throw new BusinessException("订单不存在"); + } + if (!OrderStatus.PAID.equals(main.getStatus())) { + throw new BusinessException("仅已支付订单可退号"); + } + + // 更新主单状态 + main.setStatus(OrderStatus.REFUND); + orderMainMapper.updateByPrimaryKeySelective(main); + + // 更新明细状态 + List details = orderDetailMapper.selectByOrderMainId(orderMainId); + for (OrderDetail d : details) { + d.setStatus(OrderStatus.REFUND); + orderDetailMapper.updateByPrimaryKeySelective(d); + } + + // 更新排班号状态为 “已退号”(4) + if (main.getScheduleSlotId() != null) { + scheduleSlotMapper.updateStatusById(main.getScheduleSlotId(), "4"); + } + } + + // 其余业务方法保持原样... }