506 门诊挂号:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符

CommonConstants.AppointmentOrderStatus 常量 → OrderStatus 枚举重构
   新增枚举:0=患者取消 / 1=有效 / 2=系统取消 / 3=已完成
   退号流程加乐观锁防并发,slot 状态改回待约,退号日志独立事务 修复 XML 中 Integer 比较用字符串的问题
Bug #411 — 诊室过滤栏从科室下拉框改为诊室按钮组
This commit is contained in:
wangjian963
2026-05-11 13:51:47 +08:00
committed by 赵云
parent a844920e3f
commit 7aa93c34a5
9 changed files with 259 additions and 172 deletions

View File

@@ -10,7 +10,7 @@ import com.openhis.clinical.service.ITicketService;
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
import com.openhis.web.appointmentmanage.dto.TicketDto;
import com.openhis.common.constant.CommonConstants.SlotStatus;
import com.openhis.common.constant.CommonConstants.AppointmentOrderStatus;
import com.openhis.common.enums.OrderStatus;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -198,10 +198,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else {
dto.setStatus("已预约");
}
@@ -372,10 +373,11 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (SlotStatus.CHECKED_IN.equals(slotStatus)) {
dto.setStatus("已取号");
} else if (SlotStatus.BOOKED.equals(slotStatus)) {
if (AppointmentOrderStatus.CHECKED_IN.equals(raw.getOrderStatus())) {
dto.setStatus("已取号");
} else if (AppointmentOrderStatus.RETURNED.equals(raw.getOrderStatus())) {
// order_main.status: 0=患者取消(已退号) 2=系统取消 其余=已预约
if (OrderStatus.PATIENT_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("已退号");
} else if (OrderStatus.SYSTEM_CANCELLED.getValue().equals(raw.getOrderStatus())) {
dto.setStatus("系统取消");
} else {
dto.setStatus("已预约");
}

View File

@@ -2,6 +2,7 @@ package com.openhis.web.chargemanage.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
@@ -329,16 +330,14 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
}
// 如果本次门诊挂号来自预约签到,同步预约订单与号源槽位状态改为已退号
// 退费成功后,同步回滚预约订单状态及号源;同时移除分诊队列
Long refundOrderMainId = null;
if (result != null && result.getCode() == 200) {
syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
// 同步移除分诊队列中的记录
refundOrderMainId = syncAppointmentReturnStatus(byId, cancelRegPaymentDto.getReason());
removeTriageQueueItem(byId.getId());
}
// 记录退号日志
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon);
// 退号日志独立事务写入,无论退费成功与否均记录
recordRefundLog(cancelRegPaymentDto, byId, result, paymentRecon, refundOrderMainId);
// 2025/05/05 该处保存费用项后,会通过统一收费处理进行收费
return R.ok(paymentRecon, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"退号"}));
@@ -435,8 +434,6 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
// 通过患者、科室、日期查找关联的预约订单
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
@@ -590,20 +587,25 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
/**
* 同步预约号源状态为已退号
* 说明:
* 1) 门诊退号主流程不依赖该步骤成功与否,因此此方法内部异常仅记录日志,不向上抛出。
* 2) 通过患者、科室、日期以及状态筛选最近一条预约订单,尽量避免误匹配。
* 诊前退号:回滚预约订单、号源槽位、号源池统计
*
* <p>处理四件事:
* <ol>
* <li>order_main → status=0(患者取消), pay_status=3(已退费), 写入取消时间和原因</li>
* <li>adm_schedule_slot → status=0(待约), order_id=NULL(释放号源)</li>
* <li>adm_schedule_pool → 重算统计值 + version+1</li>
* <li>返回 order_main.id 供 refund_log 关联</li>
* </ol>
*
* <p>异常仅记录日志不向上抛,不影响主流程返回成功。
*/
private void syncAppointmentReturnStatus(Encounter encounter, String reason) {
private Long syncAppointmentReturnStatus(Encounter encounter, String reason) {
if (encounter == null || encounter.getPatientId() == null) {
return;
return null;
}
try {
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, CommonConstants.AppointmentOrderStatus.BOOKED,
CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.orderByDesc(Order::getUpdateTime)
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1");
@@ -625,35 +627,55 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
Order appointmentOrder = orderService.getOne(queryWrapper, false);
if (appointmentOrder == null) {
return;
return null;
}
Date now = new Date();
if (!CommonConstants.AppointmentOrderStatus.RETURNED.equals(appointmentOrder.getStatus())) {
Order updateOrder = new Order();
updateOrder.setId(appointmentOrder.getId());
updateOrder.setStatus(CommonConstants.AppointmentOrderStatus.RETURNED);
updateOrder.setPayStatus(0);
updateOrder.setCancelTime(now);
updateOrder.setCancelReason("门诊退号");
updateOrder.setUpdateTime(now);
orderService.updateById(updateOrder);
// 只有有效订单(1)才能退号
if (!OrderStatus.ACTIVE.getValue().equals(appointmentOrder.getStatus())) {
log.warn("退号跳过:订单状态非有效, orderId={}, status={}",
appointmentOrder.getId(), appointmentOrder.getStatus());
return null;
}
// 乐观锁更新WHERE version = 旧值,防并发重复退号
boolean updated = orderService.update(
new LambdaUpdateWrapper<Order>()
.set(Order::getStatus, OrderStatus.PATIENT_CANCELLED.getValue())
.set(Order::getPayStatus, PaymentStatus.REFUND_ALL.getValue())
.set(Order::getCancelTime, new Date())
.set(Order::getCancelReason,
StringUtils.isNotEmpty(reason) ? reason : "诊前退号")
.set(Order::getUpdateTime, new Date())
.setSql("version = version + 1")
.eq(Order::getId, appointmentOrder.getId())
.eq(Order::getVersion, appointmentOrder.getVersion())
);
if (!updated) {
log.warn("退号乐观锁冲突,订单已被其他操作修改, orderId={}", appointmentOrder.getId());
return null;
}
Long slotId = appointmentOrder.getSlotId();
if (slotId == null) {
return;
return appointmentOrder.getId();
}
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.RETURNED);
int slotRows = scheduleSlotMapper.updateSlotStatus(slotId, CommonConstants.SlotStatus.AVAILABLE);
if (slotRows > 0) {
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
if (poolId != null) {
schedulePoolMapper.refreshPoolStats(poolId);
schedulePoolMapper.update(null,
new LambdaUpdateWrapper<SchedulePool>()
.setSql("version = version + 1")
.set(SchedulePool::getUpdateTime, new Date())
.eq(SchedulePool::getId, poolId));
}
}
return appointmentOrder.getId();
} catch (Exception e) {
log.warn("同步预约号源已退号状态失败, encounterId={}", encounter.getId(), e);
return null;
}
}
@@ -672,22 +694,29 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
}
/**
* 记录退号日志
* 记录退号日志(独立事务)。
*
* <p>REQUIRES_NEW 确保即使主事务回滚,退号审计日志也不丢失。
* orderMainId 优先使用 order_main.id若退费失败则 fallback 到 encounterId。
*
* @param cancelRegPaymentDto 退号请求对象
* @param encounter 就诊信息
* @param result 退号结果
* @param paymentRecon 支付对账信息
* @param orderMainId 预约订单主键order_main.id用于关联业务数据
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordRefundLog(CancelRegPaymentDto cancelRegPaymentDto,
Encounter encounter,
R<?> result,
PaymentReconciliation paymentRecon) {
PaymentReconciliation paymentRecon,
Long orderMainId) {
RefundLog refundLog = new RefundLog();
try {
// 1. 订单ID唯一
String orderId = String.valueOf(cancelRegPaymentDto.getEncounterId());
// 1. 订单ID关联 order_main.id
String orderId = orderMainId != null
? String.valueOf(orderMainId)
: String.valueOf(cancelRegPaymentDto.getEncounterId());
refundLog.setOrderId(orderId);
// 已存在则不重复插入(防止唯一约束异常)

View File

@@ -1991,7 +1991,7 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Order appointmentOrder = iOrderService.getOne(
new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounterFormData.getPatientId())
.eq(Order::getStatus, CommonConstants.AppointmentOrderStatus.CHECKED_IN)
.eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效订单(1)
.eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1")
@@ -2114,11 +2114,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
Long queuePoolId = null;
Long queueSlotId = null;
try {
// 查询患者当天的待签到预约订单status = 1 或 2 表示已预约或已取号)
// 查询患者当天有效订单(1);已取消(0/2)和已完成(3)的不参与排队
Order order = iOrderService.getOne(
new LambdaQueryWrapper<Order>()
.eq(Order::getPatientId, encounter.getPatientId())
.in(Order::getStatus, 1, 2) // 1=BOOKED 已预约, 2=CHECKED_IN 已取号
.eq(Order::getStatus, OrderStatus.ACTIVE.getValue()) // 有效(1)
.eq(Order::getDeleteFlag, "0")
.orderByDesc(Order::getCreateTime)
.last("LIMIT 1")