Fix: 门诊预约挂号→签到→退号 slot/pool 状态流转对齐需求
- 枚举重排: SlotStatus LOCKED=4→2, CANCELLED=2→4,匹配需求编号 - 预约: lockSlotForBooking 写入 LOCKED(2) 替代 BOOKED(1),pool locked_num+1 原子递增 - 签到: LOCKED(2)→BOOKED(1) 替代 CHECKED_IN(3),加前置状态校验 - 退号: 加 BOOKED(1) 前置校验 - 池计数: refreshPoolStats booked_num=COUNT(1), locked_num=COUNT(2) - SQL 状态值全部由 SlotStatus 枚举传入,消除硬编码 - 查询/显示: 加 locked 筛选分支,BOOKED→已取号, LOCKED→已锁定 - 前端常量同步,签到列表查询 book→locked
This commit is contained in:
@@ -10,10 +10,11 @@ import org.springframework.stereotype.Repository;
|
||||
public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||
|
||||
/**
|
||||
* 按号源池实时重算统计值,避免并发场景下计数漂移。
|
||||
* 按号源池实时重算统计值。
|
||||
*
|
||||
* 说明:available_num 在当前项目中可能为数据库生成列,因此这里仅维护
|
||||
* booked_num / locked_num,剩余号由数据库或查询逻辑计算。
|
||||
* @param poolId 号源池ID
|
||||
* @param bookedStatus 已约状态值,由 SlotStatus.BOOKED.getValue() 传入
|
||||
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
||||
*/
|
||||
@Update("""
|
||||
UPDATE adm_schedule_pool p
|
||||
@@ -23,20 +24,22 @@ public interface SchedulePoolMapper extends BaseMapper<SchedulePool> {
|
||||
FROM adm_schedule_slot s
|
||||
WHERE s.pool_id = p.id
|
||||
AND s.delete_flag = '0'
|
||||
AND s.status = 1
|
||||
AND s.status = #{bookedStatus}
|
||||
), 0),
|
||||
locked_num = COALESCE((
|
||||
SELECT COUNT(1)
|
||||
FROM adm_schedule_slot s
|
||||
WHERE s.pool_id = p.id
|
||||
AND s.delete_flag = '0'
|
||||
AND s.status = 3
|
||||
AND s.status = #{lockedStatus}
|
||||
), 0),
|
||||
update_time = now()
|
||||
WHERE p.id = #{poolId}
|
||||
AND p.delete_flag = '0'
|
||||
""")
|
||||
int refreshPoolStats(@Param("poolId") Long poolId);
|
||||
int refreshPoolStats(@Param("poolId") Long poolId,
|
||||
@Param("bookedStatus") Integer bookedStatus,
|
||||
@Param("lockedStatus") Integer lockedStatus);
|
||||
|
||||
/**
|
||||
* 签到时更新号源池统计:锁定数-1,已预约数+1
|
||||
|
||||
@@ -22,9 +22,12 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
||||
TicketSlotDTO selectTicketSlotById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 原子抢占槽位:仅当当前状态=0(可用)时,更新为1(已预约)。
|
||||
* 原子抢占槽位:仅当当前状态=0(待约)时,更新为目标锁定状态。
|
||||
*
|
||||
* @param slotId 槽位ID
|
||||
* @param lockedStatus 锁定状态值,由 SlotStatus.LOCKED.getValue() 传入
|
||||
*/
|
||||
int lockSlotForBooking(@Param("slotId") Long slotId);
|
||||
int lockSlotForBooking(@Param("slotId") Long slotId, @Param("lockedStatus") Integer lockedStatus);
|
||||
|
||||
/**
|
||||
* 按主键更新槽位状态。
|
||||
@@ -34,12 +37,16 @@ public interface ScheduleSlotMapper extends BaseMapper<ScheduleSlot> {
|
||||
/**
|
||||
* 更新槽位状态并记录签到时间
|
||||
*
|
||||
* @param slotId 槽位ID
|
||||
* @param status 状态
|
||||
* @param checkInTime 签到时间
|
||||
* @param slotId 槽位ID
|
||||
* @param status 目标状态,由 SlotStatus.BOOKED.getValue() 传入
|
||||
* @param checkInTime 签到时间
|
||||
* @param requiredStatus 前置状态,由 SlotStatus.LOCKED.getValue() 传入
|
||||
* @return 结果
|
||||
*/
|
||||
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId, @Param("status") Integer status, @Param("checkInTime") Date checkInTime);
|
||||
int updateSlotStatusAndCheckInTime(@Param("slotId") Long slotId,
|
||||
@Param("status") Integer status,
|
||||
@Param("checkInTime") Date checkInTime,
|
||||
@Param("requiredStatus") Integer requiredStatus);
|
||||
|
||||
/**
|
||||
* 根据槽位ID查询所属号源池ID。
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.openhis.clinical.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.appointmentmanage.domain.AppointmentConfig;
|
||||
import com.openhis.appointmentmanage.service.IAppointmentConfigService;
|
||||
import com.openhis.appointmentmanage.domain.TicketSlotDTO;
|
||||
import com.openhis.appointmentmanage.domain.SchedulePool;
|
||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||
import com.openhis.appointmentmanage.mapper.SchedulePoolMapper;
|
||||
import com.openhis.appointmentmanage.mapper.ScheduleSlotMapper;
|
||||
@@ -13,7 +15,7 @@ import com.openhis.clinical.domain.Ticket;
|
||||
import com.openhis.clinical.mapper.TicketMapper;
|
||||
import com.openhis.clinical.service.IOrderService;
|
||||
import com.openhis.clinical.service.ITicketService;
|
||||
import com.openhis.common.constant.CommonConstants.SlotStatus;
|
||||
import com.openhis.common.enums.SlotStatus;
|
||||
import com.openhis.common.enums.OrderStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -177,7 +179,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
logger.error("安全拦截:号源底库核对失败,slotId: {}", slotId);
|
||||
throw new RuntimeException("号源数据不存在");
|
||||
}
|
||||
if (slot.getSlotStatus() != null && !SlotStatus.AVAILABLE.equals(slot.getSlotStatus())) {
|
||||
if (slot.getSlotStatus() != null && SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.AVAILABLE) {
|
||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||
}
|
||||
if (Boolean.TRUE.equals(slot.getIsStopped())) {
|
||||
@@ -205,7 +207,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
}
|
||||
|
||||
// 原子抢占:避免并发下同一槽位被重复预约
|
||||
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId);
|
||||
int lockRows = scheduleSlotMapper.lockSlotForBooking(slotId, SlotStatus.LOCKED.getValue());
|
||||
if (lockRows <= 0) {
|
||||
throw new RuntimeException("手慢了!该号源已刚刚被他人抢占");
|
||||
}
|
||||
@@ -260,7 +262,15 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
throw new RuntimeException("预约成功但号源回填订单失败,请重试");
|
||||
}
|
||||
|
||||
refreshPoolStatsBySlotId(slotId);
|
||||
// 6. 预约成功后 locked_num+1(原子递增替代全量 recount,避免并发计数漂移)
|
||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||
if (poolId != null) {
|
||||
schedulePoolMapper.update(null,
|
||||
new LambdaUpdateWrapper<SchedulePool>()
|
||||
.setSql("locked_num = locked_num + 1, version = version + 1")
|
||||
.set(SchedulePool::getUpdateTime, new Date())
|
||||
.eq(SchedulePool::getId, poolId));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -277,7 +287,8 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
if (slot == null) {
|
||||
throw new RuntimeException("号源槽位不存在");
|
||||
}
|
||||
if (slot.getSlotStatus() == null || !SlotStatus.BOOKED.equals(slot.getSlotStatus())) {
|
||||
// 只有锁定态(2)的号源可以取消预约
|
||||
if (slot.getSlotStatus() == null || SlotStatus.getByValue(slot.getSlotStatus()) != SlotStatus.LOCKED) {
|
||||
throw new RuntimeException("号源不可取消预约");
|
||||
}
|
||||
|
||||
@@ -292,7 +303,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
orderService.cancelAppointmentOrder(order.getId(), "患者取消预约");
|
||||
}
|
||||
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE);
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.AVAILABLE.getValue());
|
||||
if (updated > 0) {
|
||||
refreshPoolStatsBySlotId(slotId);
|
||||
}
|
||||
@@ -318,11 +329,14 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
orderService.updateOrderStatusById(latestOrder.getId(), OrderStatus.ACTIVE.getValue());
|
||||
orderMapper.updatePayStatus(latestOrder.getId(), 1, new Date());
|
||||
|
||||
// 2. 查询号源槽位信息
|
||||
// 2. 只有锁定态(2)的号源才能签到,签到时 2→1(LOCKED→BOOKED)
|
||||
ScheduleSlot slot = scheduleSlotMapper.selectById(slotId);
|
||||
if (slot == null || !SlotStatus.LOCKED.getValue().equals(slot.getStatus())) {
|
||||
throw new RuntimeException("号源状态异常,无法签到");
|
||||
}
|
||||
|
||||
// 3. 更新号源槽位状态为已签到,记录签到时间
|
||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.CHECKED_IN, new Date());
|
||||
// 3. 更新号源槽位状态 2→1(LOCKED→BOOKED,已预约=已签到)
|
||||
scheduleSlotMapper.updateSlotStatusAndCheckInTime(slotId, SlotStatus.BOOKED.getValue(), new Date(), SlotStatus.LOCKED.getValue());
|
||||
|
||||
// 4. 更新号源池统计:锁定数-1,已预约数+1
|
||||
if (slot != null && slot.getPoolId() != null) {
|
||||
@@ -351,7 +365,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
orderService.cancelAppointmentOrder(order.getId(), "医生停诊");
|
||||
}
|
||||
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED);
|
||||
int updated = scheduleSlotMapper.updateSlotStatus(slotId, SlotStatus.CANCELLED.getValue());
|
||||
if (updated > 0) {
|
||||
refreshPoolStatsBySlotId(slotId);
|
||||
}
|
||||
@@ -364,7 +378,7 @@ public class TicketServiceImpl extends ServiceImpl<TicketMapper, Ticket> impleme
|
||||
private void refreshPoolStatsBySlotId(Long slotId) {
|
||||
Long poolId = scheduleSlotMapper.selectPoolIdBySlotId(slotId);
|
||||
if (poolId != null) {
|
||||
schedulePoolMapper.refreshPoolStats(poolId);
|
||||
schedulePoolMapper.refreshPoolStats(poolId, SlotStatus.BOOKED.getValue(), SlotStatus.LOCKED.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user