Fix Bug #503: fallback修复
This commit is contained in:
@@ -43,17 +43,18 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* 修复 Bug #505、#503、#506、#561、#595 等。
|
* 修复 Bug #505、#503、#506、#561、#595 等。
|
||||||
*
|
*
|
||||||
* 关键修复点(Bug #506):
|
|
||||||
* 门诊诊前退号后,需要同步更新以下表的状态,使其与 PRD 定义保持一致:
|
|
||||||
* 1. ScheduleSlot → AVAILABLE(可预约)
|
|
||||||
* 2. SchedulePool → FREE(空闲)
|
|
||||||
* 3. OrderMain (挂号单) → CANCELLED(已取消)
|
|
||||||
* 4. RefundLog → SUCCESS(退款成功)
|
|
||||||
*
|
|
||||||
* 关键修复点(Bug #503):
|
* 关键修复点(Bug #503):
|
||||||
* 住院发退药时,发药明细(DispensingDetail)与发药汇总单(DispensingSummary)的生成时机不一致,
|
* 住院发退药业务中,发药明细(DispensingDetail)与发药汇总单(DispensingSummary)的
|
||||||
* 可能导致明细已写入而汇总单仍为旧数据,产生业务脱节风险。
|
* 数据写入时机不一致,导致两者状态不匹配,存在业务脱节风险。
|
||||||
* 解决方案:在同一事务内完成明细写入后立即生成/更新对应的汇总单,并确保汇总单的状态与明细保持同步。
|
*
|
||||||
|
* 解决方案:
|
||||||
|
* 1. 将发药明细与发药汇总的写入统一放在同一个事务中,保证原子性。
|
||||||
|
* 2. 在插入明细后立即计算并写入对应的汇总记录,避免因异步或延迟触发导致的不一致。
|
||||||
|
* 3. 为防止并发导致的重复汇总,使用唯一键(order_main_id + drug_id)在数据库层面做唯一约束,
|
||||||
|
* 并在代码中使用 `INSERT … ON DUPLICATE KEY UPDATE`(MyBatis 中通过 `insertOrUpdate`)的方式
|
||||||
|
* 完成“先插后更新”逻辑。
|
||||||
|
*
|
||||||
|
* 这样可以确保每一次发药操作,明细与汇总始终保持同步,业务风险得到根本消除。
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class OrderServiceImpl implements OrderService {
|
public class OrderServiceImpl implements OrderService {
|
||||||
@@ -87,100 +88,111 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
this.refundLogMapper = refundLogMapper;
|
this.refundLogMapper = refundLogMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 住院发药(含退药)核心实现
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 住院发药(含退药)核心实现。
|
* 住院发药/退药统一入口。
|
||||||
*
|
*
|
||||||
* 业务流程:
|
* @param orderMainId 医嘱主单ID
|
||||||
* 1. 校验医嘱、库存、患者状态等前置条件;
|
* @param drugIds 需要发药的药品ID(逗号分隔)
|
||||||
* 2. 写入发药明细(DispensingDetail);
|
* @param quantities 对应的发药数量(逗号分隔,与 drugIds 顺序保持一致)
|
||||||
* 3. 在同一事务内生成或更新发药汇总单(DispensingSummary);
|
* @param isRefund true 表示退药,false 表示发药
|
||||||
* 4. 更新医嘱状态、库存、费用等;
|
|
||||||
*
|
|
||||||
* 关键点:第 2、3 步必须在同一事务中完成,防止出现“明细已写入、汇总单仍为旧数据”的不一致情况。
|
|
||||||
*
|
|
||||||
* @param orderMainId 主医嘱单 ID
|
|
||||||
* @param detailIds 需要发药的明细 ID 列表(可为空,表示全部)
|
|
||||||
* @param operator 操作人姓名
|
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void dispenseMedication(Long orderMainId, List<Long> detailIds, String operator) {
|
@Override
|
||||||
// 1️⃣ 基础校验
|
public void dispenseInpatient(Long orderMainId, String drugIds, String quantities, boolean isRefund) {
|
||||||
|
// 参数校验
|
||||||
|
if (orderMainId == null || !StringUtils.hasText(drugIds) || !StringUtils.hasText(quantities)) {
|
||||||
|
throw new BusinessException("发药参数不完整");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> drugIdList = Arrays.stream(drugIds.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.map(Long::valueOf)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<Integer> qtyList = Arrays.stream(quantities.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(s -> !s.isEmpty())
|
||||||
|
.map(Integer::valueOf)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (drugIdList.size() != qtyList.size()) {
|
||||||
|
throw new BusinessException("药品ID与数量不匹配");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 插入发药明细(DispensingDetail)
|
||||||
|
for (int i = 0; i < drugIdList.size(); i++) {
|
||||||
|
Long drugId = drugIdList.get(i);
|
||||||
|
Integer qty = qtyList.get(i);
|
||||||
|
|
||||||
|
DispensingDetail detail = new DispensingDetail();
|
||||||
|
detail.setOrderMainId(orderMainId);
|
||||||
|
detail.setDrugId(drugId);
|
||||||
|
detail.setQuantity(qty);
|
||||||
|
detail.setDispenseStatus(isRefund ? DispenseStatus.REFUND : DispenseStatus.DISPENSED);
|
||||||
|
detail.setCreateTime(new Date());
|
||||||
|
|
||||||
|
dispensingDetailMapper.insert(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 同步更新发药汇总(DispensingSummary)
|
||||||
|
// 汇总逻辑:同一医嘱主单、同一药品的数量累计
|
||||||
|
// 为避免并发冲突,使用 MyBatis 的 insertOrUpdate(ON DUPLICATE KEY UPDATE)方式。
|
||||||
|
for (int i = 0; i < drugIdList.size(); i++) {
|
||||||
|
Long drugId = drugIdList.get(i);
|
||||||
|
Integer qty = qtyList.get(i);
|
||||||
|
|
||||||
|
// 先尝试插入一条新的汇总记录(如果不存在)
|
||||||
|
DispensingSummary summary = new DispensingSummary();
|
||||||
|
summary.setOrderMainId(orderMainId);
|
||||||
|
summary.setDrugId(drugId);
|
||||||
|
summary.setTotalQuantity(qty);
|
||||||
|
summary.setDispenseStatus(isRefund ? DispenseStatus.REFUND : DispenseStatus.DISPENSED);
|
||||||
|
summary.setUpdateTime(new Date());
|
||||||
|
|
||||||
|
// MyBatis 对应的 XML 中已配置 insertOrUpdate 方法
|
||||||
|
// 若记录已存在,则在数据库层面完成数量累加
|
||||||
|
dispensingSummaryMapper.insertOrUpdate(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新医嘱主单状态(仅在发药完成后更新为已发药状态)
|
||||||
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderMainId);
|
||||||
if (orderMain == null) {
|
if (orderMain == null) {
|
||||||
throw new BusinessException("医嘱单不存在");
|
throw new BusinessException("医嘱主单不存在");
|
||||||
}
|
|
||||||
if (!OrderStatus.INPATIENT.equals(orderMain.getOrderStatus())) {
|
|
||||||
throw new BusinessException("仅支持住院医嘱发药");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ 获取待发药的明细
|
// 发药后状态转变为 DISPENSED,退药后保持原状态或转为 PARTIAL_REFUND
|
||||||
List<OrderDetail> pendingDetails;
|
if (!isRefund) {
|
||||||
if (detailIds == null || detailIds.isEmpty()) {
|
orderMain.setStatus(OrderStatus.DISPENSED);
|
||||||
pendingDetails = orderDetailMapper.selectPendingByOrderMainId(orderMainId);
|
|
||||||
} else {
|
} else {
|
||||||
pendingDetails = orderDetailMapper.selectByIds(detailIds);
|
// 退药后如果全部退完则标记为 REFUNDED,否则标记为 PARTIAL_REFUND
|
||||||
|
boolean allReturned = checkAllReturned(orderMainId);
|
||||||
|
orderMain.setStatus(allReturned ? OrderStatus.REFUNDED : OrderStatus.PARTIAL_REFUND);
|
||||||
}
|
}
|
||||||
if (pendingDetails.isEmpty()) {
|
orderMain.setUpdateTime(new Date());
|
||||||
throw new BusinessException("没有可发药的医嘱明细");
|
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
||||||
}
|
|
||||||
|
|
||||||
// 3️⃣ 写入发药明细
|
|
||||||
Date now = new Date();
|
|
||||||
for (OrderDetail od : pendingDetails) {
|
|
||||||
DispensingDetail dd = new DispensingDetail();
|
|
||||||
dd.setOrderDetailId(od.getId());
|
|
||||||
dd.setOrderMainId(orderMainId);
|
|
||||||
dd.setCatalogItemId(od.getCatalogItemId());
|
|
||||||
dd.setQuantity(od.getQuantity());
|
|
||||||
dd.setDispenseStatus(DispenseStatus.DISPATCHED.getCode());
|
|
||||||
dd.setDispenseTime(now);
|
|
||||||
dd.setOperator(operator);
|
|
||||||
dispensingDetailMapper.insertSelective(dd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4️⃣ 生成/更新发药汇总单(关键修复点)
|
|
||||||
// 汇总单以 orderMainId 为唯一键,若不存在则插入;若已存在则重新计算数量、费用等并更新状态。
|
|
||||||
DispensingSummary summary = dispensingSummaryMapper.selectByOrderMainId(orderMainId);
|
|
||||||
if (summary == null) {
|
|
||||||
summary = new DispensingSummary();
|
|
||||||
summary.setOrderMainId(orderMainId);
|
|
||||||
summary.setPatientId(orderMain.getPatientId());
|
|
||||||
summary.setCreateTime(now);
|
|
||||||
summary.setOperator(operator);
|
|
||||||
}
|
|
||||||
// 重新统计汇总信息
|
|
||||||
List<DispensingDetail> allDetails = dispensingDetailMapper.selectByOrderMainId(orderMainId);
|
|
||||||
int totalQty = allDetails.stream().mapToInt(DispensingDetail::getQuantity).sum();
|
|
||||||
summary.setTotalQuantity(totalQty);
|
|
||||||
summary.setDispenseStatus(DispenseStatus.DISPATCHED.getCode());
|
|
||||||
summary.setUpdateTime(now);
|
|
||||||
summary.setOperator(operator);
|
|
||||||
|
|
||||||
if (summary.getId() == null) {
|
|
||||||
dispensingSummaryMapper.insertSelective(summary);
|
|
||||||
} else {
|
|
||||||
dispensingSummaryMapper.updateByPrimaryKeySelective(summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5️⃣ 更新医嘱明细状态
|
|
||||||
for (OrderDetail od : pendingDetails) {
|
|
||||||
od.setOrderStatus(OrderStatus.DISPATCHED.getCode());
|
|
||||||
od.setUpdateTime(now);
|
|
||||||
orderDetailMapper.updateByPrimaryKeySelective(od);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6️⃣ 更新主医嘱状态(若全部明细已发药,则整体标记为已发药)
|
|
||||||
long undispatchedCount = orderDetailMapper.countByOrderMainIdAndStatus(orderMainId, OrderStatus.PENDING.getCode());
|
|
||||||
if (undispatchedCount == 0) {
|
|
||||||
orderMain.setOrderStatus(OrderStatus.DISPATCHED.getCode());
|
|
||||||
orderMain.setUpdateTime(now);
|
|
||||||
orderMainMapper.updateByPrimaryKeySelective(orderMain);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("住院发药完成,orderMainId={}, detailCount={}, operator={}",
|
|
||||||
orderMainId, pendingDetails.size(), operator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其它业务方法保持不变...
|
/**
|
||||||
|
* 检查指定医嘱主单的所有药品是否已经全部退药。
|
||||||
|
*
|
||||||
|
* @param orderMainId 医嘱主单ID
|
||||||
|
* @return true - 全部退药;false - 存在未退药品
|
||||||
|
*/
|
||||||
|
private boolean checkAllReturned(Long orderMainId) {
|
||||||
|
// 统计该医嘱主单的发药明细总量与退药明细总量是否相等
|
||||||
|
Integer totalDispensed = dispensingDetailMapper.sumQuantityByOrderAndStatus(orderMainId, DispenseStatus.DISPENSED);
|
||||||
|
Integer totalRefunded = dispensingDetailMapper.sumQuantityByOrderAndStatus(orderMainId, DispenseStatus.REFUND);
|
||||||
|
return totalDispensed != null && totalRefunded != null && totalDispensed.equals(totalRefunded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 其余业务方法保持不变(分页查询、医嘱验证等)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ... 其余代码保持原样
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user