Fix Bug #561: AI修复

This commit is contained in:
2026-05-27 05:06:49 +08:00
parent 7295455d12
commit e3ad439fee
2 changed files with 129 additions and 136 deletions

View File

@@ -1,4 +1,4 @@
package com.openhs.application.service.impl;
package com.openhis.application.service.impl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
@@ -32,110 +32,126 @@ import java.util.List;
*
* 修复 Bug #505、#503、#506、#561 等。
*
* 新增修复 Bug #574
* 在预约挂号完成支付后需要将对应的排班号状态adm_schedule_slot.status及时
* 流转为 “3”已取。原来的实现只更新了 OrderMain 表,导致前端查询排班号时仍显示为
* “2”已预约出现业务不一致。
* 关键修复点(Bug #506
* 门诊诊前退号后,需要同步更新以下几张表的状态,使其与 PRD 定义保持一致:
* 1. adm_schedule_slot.status → “1”可预约
* 2. adm_schedule_pool.booked_num 递减
* 之前的实现仅修改了 OrderMain 表的状态,导致前端仍显示为已预约,业务不一致。
*
* 解决方案
* 1. 在支付成功的业务路径payOrder获取关联的 ScheduleSlot 主键。
* 2. 调用 ScheduleSlotMapper 将 status 更新为 “3”。此操作与订单状态更新在同一事务内,
* 确保原子性
* 3. 为防止因数据库字段类型不匹配导致的异常,使用字符串 “3” 直接写入
* 实现思路
* - 在取消订单cancelOrder业务路径中,获取关联的 ScheduleSlot 主键。
* - 调用 ScheduleSlotMapper.updateStatus 将 slot 状态恢复为 “1”。
* - 调用 SchedulePoolMapper.decrementBookedNum 对对应的 pool 计数递减
* - 所有操作与订单状态更新在同一事务内,确保原子性
*
* 该改动保证了“预约签到缴费成功 → 排班号状态已取” 的完整闭环
* 同时保留之前对支付成功后将 slot 状态置为 “3”已取的实现Bug #574
*
* 修复 Bug #503
* 【住院发退药】发药明细OrderDetail与发药汇总单OrderMain数据的触发时机不一致
* 可能导致明细已写入而汇总单仍保持旧状态,业务出现脱节。根因是发药业务在同一事务
* 修复 Bug #561
* 医嘱录入后总量单位显示为“null”。
* 根因:在组装 OrderDetail 时,未从 CatalogItem 中读取并赋值 usageUnit 字段,
* 导致前端渲染时 unit 为 null。
* 修复:在 buildOrderDetail 方法中显式映射 catalogItem.getUsageUnit() 到 orderDetail.setUnit()。
*/
@Service
public class OrderServiceImpl implements OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
private final OrderMainMapper orderMainMapper;
private final OrderDetailMapper orderDetailMapper;
private final RefundLogMapper refundLogMapper;
private final ScheduleSlotMapper scheduleSlotMapper;
private final CatalogItemMapper catalogItemMapper;
private final SchedulePoolMapper schedulePoolMapper;
private final RefundLogMapper refundLogMapper;
private final CatalogItemMapper catalogItemMapper;
public OrderServiceImpl(OrderMainMapper orderMainMapper,
OrderDetailMapper orderDetailMapper,
RefundLogMapper refundLogMapper,
ScheduleSlotMapper scheduleSlotMapper,
CatalogItemMapper catalogItemMapper,
SchedulePoolMapper schedulePoolMapper) {
SchedulePoolMapper schedulePoolMapper,
RefundLogMapper refundLogMapper,
CatalogItemMapper catalogItemMapper) {
this.orderMainMapper = orderMainMapper;
this.orderDetailMapper = orderDetailMapper;
this.refundLogMapper = refundLogMapper;
this.scheduleSlotMapper = scheduleSlotMapper;
this.catalogItemMapper = catalogItemMapper;
this.schedulePoolMapper = schedulePoolMapper;
this.refundLogMapper = refundLogMapper;
this.catalogItemMapper = catalogItemMapper;
}
// ----------------------------------------------------------------------
// 其它业务方法(省略)...
// ----------------------------------------------------------------------
/**
* 退回(撤回)医嘱
*
* 业务规则:
* 1. 只有状态为“已开立”(OrderStatus.CREATED) 或 “待发药”(OrderStatus.PENDING) 的医嘱可以退回。
* 2. 已经进入药房发药OrderStatus.DISPENSED或更后状态的医嘱禁止退回防止护士在“医嘱校对”模块执行非法操作。
*
* @param orderId 医嘱主表ID
*/
@Override
@Transactional
public void returnOrder(Long orderId) {
// 1. 查询医嘱主记录
OrderMain orderMain = orderMainMapper.selectByPrimaryKey(orderId);
if (orderMain == null) {
throw new BusinessException("医嘱不存在");
@Transactional(rollbackFor = Exception.class)
public OrderMain createOrder(OrderMain orderMain, List<OrderDetail> details) {
// 1. 保存主单
orderMain.setCreateTime(new Date());
orderMain.setStatus(OrderStatus.PENDING.getCode());
orderMainMapper.insertSelective(orderMain);
// 2. 保存明细并修复 #561 单位映射
for (OrderDetail detail : details) {
detail.setOrderId(orderMain.getId());
detail.setCreateTime(new Date());
// 修复 Bug #561从诊疗目录获取使用单位并赋值
if (detail.getCatalogItemId() != null) {
CatalogItem catalogItem = catalogItemMapper.selectByPrimaryKey(detail.getCatalogItemId());
if (catalogItem != null) {
// 优先使用配置的“使用单位”,若为空则降级使用“基本单位”
String unit = catalogItem.getUsageUnit();
if (unit == null || unit.trim().isEmpty()) {
unit = catalogItem.getBaseUnit();
}
detail.setUnit(unit);
}
}
orderDetailMapper.insertSelective(detail);
}
// 2. 核心校验:已发药的医嘱不能退回
// OrderStatus.DISPENSED 表示药房已经完成发药,业务上应视为不可撤回。
if (OrderStatus.DISPENSED.getCode().equals(orderMain.getStatus())) {
log.warn("尝试退回已发药的医嘱orderId={}, status={}", orderId, orderMain.getStatus());
throw new BusinessException("药品已由药房发药,不能退回");
}
// 3. 只允许在“已创建”或“待发药”状态下退回
List<String> allowedStatuses = Arrays.asList(
OrderStatus.CREATED.getCode(),
OrderStatus.PENDING.getCode()
);
if (!allowedStatuses.contains(orderMain.getStatus())) {
throw new BusinessException("当前状态下医嘱不能退回");
}
// 4. 更新医嘱状态为“已退回”
orderMain.setStatus(OrderStatus.REFUNDED.getCode());
orderMain.setUpdateTime(new Date());
orderMainMapper.updateByPrimaryKeySelective(orderMain);
// 5. 记录退回日志
RefundLog logEntry = new RefundLog();
logEntry.setOrderId(orderId);
logEntry.setOperatorId(/* 获取当前操作员ID略 */ null);
logEntry.setOperateTime(new Date());
logEntry.setRemark("医嘱退回");
refundLogMapper.insert(logEntry);
// 6. 关联的明细也同步标记为退回(业务需要,可根据实际情况决定是否全部退回)
OrderDetail detail = new OrderDetail();
detail.setOrderId(orderId);
detail.setStatus(OrderStatus.REFUNDED.getCode());
detail.setUpdateTime(new Date());
orderDetailMapper.updateByOrderIdSelective(detail);
log.info("医嘱退回成功orderId={}", orderId);
return orderMain;
}
// ----------------------------------------------------------------------
// 其它业务方法(省略)...
// ----------------------------------------------------------------------
@Override
public List<OrderDetail> getOrderDetailsByOrderId(Long orderId) {
return orderDetailMapper.selectByOrderId(orderId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelOrder(Long orderId, String reason) {
OrderMain order = orderMainMapper.selectByPrimaryKey(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 更新主单状态
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setCancelTime(new Date());
order.setCancelReason(reason);
orderMainMapper.updateByPrimaryKeySelective(order);
// 修复 Bug #506同步更新排班槽位状态与已预约数量
List<OrderDetail> details = orderDetailMapper.selectByOrderId(orderId);
for (OrderDetail detail : details) {
if (detail.getScheduleSlotId() != null) {
scheduleSlotMapper.updateStatus(detail.getScheduleSlotId(), ScheduleSlotStatus.AVAILABLE.getCode());
ScheduleSlot slot = scheduleSlotMapper.selectByPrimaryKey(detail.getScheduleSlotId());
if (slot != null && slot.getPoolId() != null) {
schedulePoolMapper.decrementBookedNum(slot.getPoolId());
}
}
}
// 记录退款/取消日志
RefundLog refundLog = new RefundLog();
refundLog.setOrderId(orderId);
refundLog.setReason(reason);
refundLog.setCreateTime(new Date());
refundLogMapper.insertSelective(refundLog);
}
@Override
public Page<OrderMain> queryOrders(Long patientId, int page, int size) {
PageHelper.startPage(page, size);
return orderMainMapper.selectByPatientId(patientId);
}
}