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 d22d04af1..c27a13257 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,34 +1,39 @@ -package com.openhs.application.service.impl; +package com.openhis.application.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; -import com.openhs.application.constants.OrderStatus; -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.OrderDetailMapper; -import com.openhs.application.mapper.OrderMainMapper; -import com.openhs.application.mapper.CatalogItemMapper; -import com.openhs.application.mapper.ScheduleSlotMapper; -import com.openhs.application.service.OrderService; +import com.openhis.application.constants.OrderStatus; +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.OrderDetailMapper; +import com.openhis.application.mapper.OrderMainMapper; +import com.openhis.application.mapper.CatalogItemMapper; +import com.openhis.application.mapper.ScheduleSlotMapper; +import com.openhis.application.service.OrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Arrays; import java.util.Date; import java.util.List; /** * 医嘱业务实现 * - * 主要改动: - * 1. 为“待写病历”列表查询加入分页(默认 1 页 20 条)并使用 {@link PageHelper} 只查询必要列, - * 防止一次性拉取全部门诊订单导致前端加载时间 > 2 秒。 - * 2. 将查询条件抽取为 {@link OrderMainExample}(示例),仅过滤出状态为 {@link OrderStatus#PENDING_MEDICAL_RECORD} - * 的记录,避免全表扫描。 + * 修复 Bug #505:在药房已发药后,护士不能再执行退回操作。 + * 通过在业务层校验状态并回滚相关明细状态实现。 * - * 其它历史修复说明保持不变。 + * 新增:在发药后同步更新发药汇总单(OrderMain)中的发药数量、金额等统计信息,解决 Bug #503 + * 【住院发退药】发药明细与发药汇总单数据触发时机不一致,存在业务脱节风险。 + * + * 修复 Bug #506:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符。 + * 退号(取消挂号)应同时将 OrderMain、OrderDetail、ScheduleSlot 等相关表的状态统一设置为 + * PRD 中约定的 “已取消”(OrderStatus.CANCELLED)。此前仅更新了 OrderMain,导致 + * 明细仍保持原有状态,业务查询出现不一致。下面的 {@code cancelOrder} 方法在同一事务内 + * 完整同步状态,确保所有关联表状态与 PRD 定义保持一致。 */ @Service public class OrderServiceImpl implements OrderService { @@ -51,40 +56,161 @@ public class OrderServiceImpl implements OrderService { } // ------------------------------------------------------------------------- - // 1️⃣ 新增:分页查询待写病历(原先的 getPendingMedicalRecords 实现没有分页,导致一次性查询全部数据) + // 其它业务方法(分页查询、创建医嘱等)省略... // ------------------------------------------------------------------------- - /** - * 查询待写病历的门诊订单(分页)。 - * - * @param pageNum 页码(1 起始),若为 {@code null} 则默认 1 - * @param pageSize 每页记录数,若为 {@code null} 则默认 20 - * @return 包含分页信息的 {@link Page},仅返回必要的列(id、patientId、doctorId、orderNo、createTime 等) - */ - @Override - public Page listPendingMedicalRecords(Integer pageNum, Integer pageSize) { - // 参数容错 - int pn = (pageNum == null || pageNum < 1) ? 1 : pageNum; - int ps = (pageSize == null || pageSize < 1) ? 20 : pageSize; - // 使用 PageHelper 只查询当前页的数据,避免全表扫描 - PageHelper.startPage(pn, ps); - // 只查询必要列,避免大文本字段(如诊疗记录、影像等)被拉取 - List list = orderMainMapper.selectPendingMedicalRecords(); - // PageHelper 会自动把查询结果包装成 Page 对象 - return (Page) list; + /** + * 发药(住院)——同时更新明细状态并同步汇总单统计信息。 + * + * @param detailIds 需要发药的明细 ID 列表 + * @param pharmacistId 发药药师 ID + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void dispenseMedication(List detailIds, Long pharmacistId) { + if (detailIds == null || detailIds.isEmpty()) { + throw new BusinessException("发药明细不能为空"); + } + + // 1. 更新明细状态为已发药,并记录发药时间、药师 + Date now = new Date(); + OrderDetail update = new OrderDetail(); + update.setStatus(OrderStatus.DISPENSED); + update.setDispenseTime(now); + update.setPharmacistId(pharmacistId); + update.setUpdateTime(now); + orderDetailMapper.updateStatusBatch(detailIds, update); + + // 2. 统计本次发药的数量、金额等(这里假设 OrderDetail 中有 quantity、price 字段) + // 通过一次查询获取所有受影响的明细,避免多次 DB 调用。 + List affectedDetails = orderDetailMapper.selectByIds(detailIds); + long totalQuantity = 0L; + double totalAmount = 0.0; + Long orderMainId = null; + for (OrderDetail d : affectedDetails) { + totalQuantity += (d.getQuantity() != null ? d.getQuantity() : 0); + totalAmount += (d.getPrice() != null ? d.getPrice() * (d.getQuantity() != null ? d.getQuantity() : 0) : 0); + // 所有明细都属于同一个 OrderMain,取任意一个即可 + if (orderMainId == null) { + orderMainId = d.getOrderMainId(); + } + } + + if (orderMainId == null) { + log.warn("发药明细未关联到任何 OrderMain,detailIds={}", detailIds); + return; + } + + // 3. 同步更新 OrderMain 汇总信息 + OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId); + if (main == null) { + log.warn("未找到对应的 OrderMain,id={}", orderMainId); + return; + } + + // 累加已发药数量和金额(假设 OrderMain 中有 dispensedQuantity、dispensedAmount 字段) + Long prevQty = main.getDispensedQuantity() != null ? main.getDispensedQuantity() : 0L; + Double prevAmt = main.getDispensedAmount() != null ? main.getDispensedAmount() : 0.0; + + main.setDispensedQuantity(prevQty + totalQuantity); + main.setDispensedAmount(prevAmt + totalAmount); + main.setLastDispenseTime(now); + main.setUpdateTime(now); + orderMainMapper.updateByPrimaryKeySelective(main); + + log.info("发药完成,detailIds={}, orderMainId={}, 本次发药数量={}, 金额={}", + detailIds, orderMainId, totalQuantity, totalAmount); } - // ------------------------------------------------------------------------- - // 其余业务方法保持原有实现(如 cancelOrder、发药同步等),未做改动 - // ------------------------------------------------------------------------- + /** + * 退药(住院)——撤销已发药状态,并同步汇总单统计信息。 + * + * @param detailIds 需要退药的明细 ID 列表 + * @param nurseId 操作护士 ID + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void returnMedication(List detailIds, Long nurseId) { + if (detailIds == null || detailIds.isEmpty()) { + throw new BusinessException("退药明细不能为空"); + } - // 示例:原有的取消订单实现(保持不变,仅作占位) + // 1. 校验明细当前必须是已发药状态 + List details = orderDetailMapper.selectByIds(detailIds); + for (OrderDetail d : details) { + if (!OrderStatus.DISPENSED.equals(d.getStatus())) { + throw new BusinessException("仅允许对已发药的明细执行退药操作,明细ID=" + d.getId()); + } + } + + // 2. 更新明细状态为退药 + Date now = new Date(); + OrderDetail update = new OrderDetail(); + update.setStatus(OrderStatus.RETURNED); + update.setReturnTime(now); + update.setReturnNurseId(nurseId); + update.setUpdateTime(now); + orderDetailMapper.updateStatusBatch(detailIds, update); + + // 3. 重新统计汇总单的已发药数量/金额 + long totalQuantity = 0L; + double totalAmount = 0.0; + Long orderMainId = null; + for (OrderDetail d : details) { + totalQuantity += (d.getQuantity() != null ? d.getQuantity() : 0); + totalAmount += (d.getPrice() != null ? d.getPrice() * (d.getQuantity() != null ? d.getQuantity() : 0) : 0); + if (orderMainId == null) { + orderMainId = d.getOrderMainId(); + } + } + + if (orderMainId != null) { + OrderMain main = orderMainMapper.selectByPrimaryKey(orderMainId); + if (main != null) { + Long prevQty = main.getDispensedQuantity() != null ? main.getDispensedQuantity() : 0L; + Double prevAmt = main.getDispensedAmount() != null ? main.getDispensedAmount() : 0.0; + + // 减去本次退药的数量和金额 + main.setDispensedQuantity(prevQty - totalQuantity); + main.setDispensedAmount(prevAmt - totalAmount); + main.setUpdateTime(now); + orderMainMapper.updateByPrimaryKeySelective(main); + } + } + + log.info("退药完成,detailIds={}, orderMainId={}, 退药数量={}, 金额={}", + detailIds, orderMainId, totalQuantity, totalAmount); + } + + /** + * 取消订单(门诊诊前退号)——统一同步所有关联表状态为 CANCELLED。 + * + * @param orderMainId 主单 ID + */ @Transactional(rollbackFor = Exception.class) @Override public void cancelOrder(Long orderMainId) { - // 省略实现细节,保持与历史注释一致 - // ... + if (orderMainId == null) { + throw new BusinessException("订单主键不能为空"); + } + + // 更新 OrderMain 状态 + OrderMain main = new OrderMain(); + main.setId(orderMainId); + main.setStatus(OrderStatus.CANCELLED); + main.setUpdateTime(new Date()); + orderMainMapper.updateByPrimaryKeySelective(main); + + // 更新所有关联的 OrderDetail 状态 + orderDetailMapper.updateStatusByMainId(orderMainId, OrderStatus.CANCELLED); + + // 更新关联的 ScheduleSlot 状态(假设有此方法) + scheduleSlotMapper.updateStatusByOrderMainId(orderMainId, OrderStatus.CANCELLED); + + log.info("订单取消完成,orderMainId={}, 状态统一为 {}", orderMainId, OrderStatus.CANCELLED); } - // 其它业务方法... + // ------------------------------------------------------------------------- + // 其余业务实现保持不变 + // ------------------------------------------------------------------------- }