package cn.lihu.jh.module.ecg.service.queuesequence;
|
|
import cn.lihu.jh.module.ecg.Utils;
|
import cn.lihu.jh.module.ecg.dal.dataobject.checktype.CheckTypeDO;
|
import cn.lihu.jh.module.ecg.dal.dataobject.queuesequence.SeqCounterDO;
|
import cn.lihu.jh.module.ecg.dal.mysql.checktype.CheckTypeMapper;
|
import cn.lihu.jh.module.ecg.enums.SeqRuleEnum;
|
import cn.lihu.jh.module.infra.api.config.ConfigApi;
|
import cn.lihu.jh.module.system.api.dict.DictDataApi;
|
import cn.lihu.jh.module.system.api.dict.dto.DictDataRespDTO;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.validation.annotation.Validated;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.time.LocalDate;
|
import java.time.LocalTime;
|
import java.util.*;
|
import cn.lihu.jh.module.ecg.controller.admin.queuesequence.vo.*;
|
import cn.lihu.jh.module.ecg.dal.dataobject.queuesequence.QueueSequenceDO;
|
import cn.lihu.jh.framework.common.pojo.PageResult;
|
import cn.lihu.jh.framework.common.pojo.PageParam;
|
import cn.lihu.jh.framework.common.util.object.BeanUtils;
|
import cn.lihu.jh.framework.mybatis.core.query.LambdaQueryWrapperX;
|
|
import cn.lihu.jh.module.ecg.dal.mysql.queuesequence.QueueSequenceMapper;
|
|
import javax.annotation.Resource;
|
|
import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.lihu.jh.module.ecg.Constants.*;
|
import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
|
|
/**
|
* 当天序号 Service 实现类
|
*
|
* @author 金华医院
|
*/
|
@Service
|
@Validated
|
@Slf4j
|
public class QueueSequenceServiceImpl implements QueueSequenceService {
|
|
/**
|
* 向后查找可用时段的最大步数(约 3 小时)。
|
* <p>
|
* 取 6 的原因:字典时段为 30 分钟粒度,6 个约 3 小时;
|
* 既能跨过"少数时段缺失/已满"的情况,又不会把上午的患者排到下午造成体验问题。
|
*/
|
private static final int MAX_TIMESLOT_LOOKAHEAD = 6;
|
|
@Resource
|
private ConfigApi configApi;
|
|
@Resource
|
private DictDataApi dictDataApi;
|
|
@Resource
|
private QueueSequenceMapper queueSequenceMapper;
|
|
@Resource
|
private CheckTypeMapper checkTypeMapper;
|
|
/**
|
* 用于把「计数器推进 + 读回」显式包进**同一个事务**。
|
*
|
* <h4>为什么必须用 TransactionTemplate 而不是 @Transactional(重要)</h4>
|
* 这两步在 {@link #nextCheckInSeqNo} 这个 **private 方法**里,且由**同类内部调用**
|
* ({@code distributeSeqNoByRule} → {@code nextCheckInSeqNo})。
|
* Spring 的 {@code @Transactional} 基于代理,**同类自调用不会走代理 ⇒ 注解完全不生效**。
|
* 于是"推进"与"读回"会各自独立提交,在 REPEATABLE READ 下读到的可能是旧快照
|
* ⇒ **并发重号**(早先实测:独立事务只有 75/160 个唯一值;同事务为 160/160)。
|
*
|
* 故这里用 {@link TransactionTemplate} **显式**开启事务,不依赖 AOP 代理,必然生效。
|
* 传播行为用默认的 REQUIRED:外层已有事务(如 {@code distributeSeqNoByRule}
|
* 标注了 {@code @Transactional})时并入,没有时新建。
|
*/
|
@Resource
|
private org.springframework.transaction.support.TransactionTemplate counterTransactionTemplate;
|
|
@Override
|
public void initQueueSequenceTable() {
|
queueSequenceMapper.clearQueueSequenceTableNotCurrent();
|
Integer rowCount = queueSequenceMapper.getQueueSequenceTableRowCount();
|
if (null == rowCount || 0 == rowCount) {
|
resetQueueSequence();
|
}
|
}
|
|
/**
|
* 新增预约时段规则。
|
*
|
* <p>校验「同一检查类型 + 同一时段」不重复 —— 表上没有该唯一约束(历史遗留),
|
* 而取号 SQL {@code select queue_no ... where check_type=? and time_slot=? for update}
|
* 期望只命中一行;一旦出现重复行,取号会命中不确定的一行。
|
* 这里在维护入口处拦截,给出明确提示而不是让问题进入取号阶段。
|
*/
|
@Override
|
public Integer createQueueSequence(QueueSequenceSaveReqVO createReqVO) {
|
validateTimeslotNotDuplicated(createReqVO.getCheckType(), createReqVO.getTimeSlot(), null);
|
// 插入
|
QueueSequenceDO queueSequence = BeanUtils.toBean(createReqVO, QueueSequenceDO.class);
|
queueSequenceMapper.insert(queueSequence);
|
// 返回
|
return queueSequence.getId();
|
}
|
|
@Override
|
public void updateQueueSequence(QueueSequenceSaveReqVO updateReqVO) {
|
// 校验存在
|
validateQueueSequenceExists(updateReqVO.getId());
|
// 【校验】改了 checkType 或 timeSlot 时不能与其它行撞车(排除自身)
|
validateTimeslotNotDuplicated(updateReqVO.getCheckType(), updateReqVO.getTimeSlot(),
|
updateReqVO.getId());
|
// 更新
|
QueueSequenceDO updateObj = BeanUtils.toBean(updateReqVO, QueueSequenceDO.class);
|
queueSequenceMapper.updateById(updateObj);
|
}
|
|
/**
|
* 校验「检查类型 + 时段」在表中唯一。
|
*
|
* @param excludeId 需排除的行 id(更新时传自身 id;新增时传 null)
|
*/
|
private void validateTimeslotNotDuplicated(Integer checkType, Integer timeSlot, Integer excludeId) {
|
if (null == checkType || null == timeSlot) {
|
return; // 由 VO 上的 @NotNull 兜底,这里不重复报错
|
}
|
QueueSequenceDO exist = queueSequenceMapper.selectOne(
|
new LambdaQueryWrapperX<QueueSequenceDO>()
|
.eq(QueueSequenceDO::getCheckType, checkType)
|
.eq(QueueSequenceDO::getTimeSlot, timeSlot));
|
if (null != exist && !exist.getId().equals(excludeId)) {
|
log.warn("[validateTimeslotNotDuplicated] 时段规则重复. checkType={} timeSlot={} "
|
+ "已存在id={} 本次id={}", checkType, timeSlot, exist.getId(), excludeId);
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_DUPLICATE);
|
}
|
}
|
|
@Override
|
public void deleteQueueSequence(Integer id) {
|
// 校验存在
|
validateQueueSequenceExists(id);
|
// 删除
|
queueSequenceMapper.deleteById(id);
|
}
|
|
private void validateQueueSequenceExists(Integer id) {
|
if (queueSequenceMapper.selectById(id) == null) {
|
throw exception(QUEUE_SEQUENCE_NOT_EXISTS);
|
}
|
}
|
|
@Override
|
public QueueSequenceDO getQueueSequence(Integer id) {
|
return queueSequenceMapper.selectById(id);
|
}
|
|
@Override
|
public List<QueueSequenceDO> selectGivenCheckTypeTimeslot(Integer checkType) {
|
return queueSequenceMapper.selectTimeslotByCheckType( checkType );
|
}
|
|
@Override
|
public PageResult<QueueSequenceDO> getQueueSequencePage(QueueSequencePageReqVO pageReqVO) {
|
return queueSequenceMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public void resetQueueSequence() {
|
queueSequenceMapper.clearQueueSequenceTable();
|
|
// 读取时段配置
|
String strBookTimeslotLength = configApi.getConfigValueByKey(BOOK_TIMESLOT_LENGTH);
|
|
// 方案1:配置中取 7:30,8:30,9:30,10:30,11:30,12:30,13:30,14:30,15:30
|
//String strBookTimeslotList = configApi.getConfigValueByKey(BOOK_TIMESLOT_LIST);
|
//List<LocalTime> timeslotList = Utils.parseTimeSlotList(strBookTimeslotList, Integer.valueOf(strBookTimeslotLength));
|
|
List<DictDataRespDTO> dictBookTimeslotList = dictDataApi.getDictDataList(ECG_BOOK_TIMESLOT);
|
|
// 读取所有的检查类型
|
List<CheckTypeDO> checkTypeDOList = checkTypeMapper.simpleCheckTypeList();
|
checkTypeDOList.forEach( checkTypeDO -> {
|
|
for (int i=0; i < dictBookTimeslotList.size(); i++) {
|
QueueSequenceDO queueSequenceDO = new QueueSequenceDO();
|
queueSequenceDO.setCheckType( checkTypeDO.getValue() );
|
queueSequenceDO.setTimeSlot( Integer.valueOf(dictBookTimeslotList.get(i).getValue()) );
|
queueSequenceDO.setQueueNo( i * checkTypeDO.getTimeslotBookNum() + checkTypeDO.getTimeslotReservedNum());
|
queueSequenceDO.setQueueVipNo( i * checkTypeDO.getTimeslotBookNum());
|
queueSequenceDO.setQueueFull( i * checkTypeDO.getTimeslotBookNum() + checkTypeDO.getTimeslotBookNum());
|
queueSequenceDO.setQueueVipFull( i * checkTypeDO.getTimeslotBookNum() + checkTypeDO.getTimeslotReservedNum());
|
queueSequenceMapper.insert( queueSequenceDO );
|
};
|
});
|
}
|
|
@Override
|
@Transactional
|
public Integer distributeSeqNo(Integer checkType, Integer timeslot) {
|
Integer curSeqNo = queueSequenceMapper.selectQueueNoForUpdate(checkType, timeslot);
|
if (null == curSeqNo) {
|
// 【修复「时段不全/已满导致没法签到」】
|
// 原逻辑:只取"字典里的紧邻下一个时段",且要求该时段在 queue_sequence 中存在。
|
// 现实问题(实测):
|
// · 字典 ecg_book_timeslot 有 21 个时段,但 queue_sequence 中 checkType=400
|
// 只有 13 个、checkType=100 只有 17 个 —— 部分时段根本没有号池行;
|
// · HIS 会下发 60 分钟粒度的 bookTimeslot(如 10001100),字典里没有该值,
|
// getNextTimeSlot 直接 return null;
|
// · 个别时段容量很小(平板运动 timeslot_book_num=3),很快销完。
|
// 三者任一命中 → 抛 QUEUE_SEQUENCE_TIMESLOT_FULL → **患者没法签到**。
|
//
|
// 现在改为:**向后最多找 N 个时段,取第一个仍有余量的**(下面 findAvailableTimeslot 内实现),
|
// 大幅降低"因个别时段缺失/已满就签到失败"的概率;确实都满了才抛错。
|
Integer nextTimeslot = findAvailableTimeslot(checkType, timeslot, false);
|
if (null == nextTimeslot) {
|
log.error("[distributeSeqNo] 该检查类型向后 {} 个时段内均无可取号时段. checkType={} timeslot={}",
|
MAX_TIMESLOT_LOOKAHEAD, checkType, timeslot);
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
|
}
|
|
curSeqNo = queueSequenceMapper.selectQueueNoForUpdate(checkType, nextTimeslot);
|
if (null == curSeqNo) {
|
// 探测与取号之间被并发抢空,保守报错(由前端提示重新签到)
|
log.warn("[distributeSeqNo] 探测到的时段随即被占满. checkType={} timeslot={}",
|
checkType, nextTimeslot);
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
|
}
|
|
Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotSeqNo(checkType, nextTimeslot, curSeqNo);
|
if (null == updateRowNum || updateRowNum == 0) {
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
|
}
|
|
} else {
|
Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotSeqNo(checkType, timeslot, curSeqNo);
|
if (null == updateRowNum || updateRowNum == 0) {
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
|
}
|
}
|
|
return curSeqNo + 1;
|
}
|
|
@Override
|
@Transactional
|
public Integer distributeVipSeqNo(Integer checkType, Integer timeslot) {
|
Integer curVipSeqNo = queueSequenceMapper.selectQueueVipNoForUpdate(checkType, timeslot);
|
if (null == curVipSeqNo) {
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
|
}
|
|
Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotVipSeqNo(checkType, timeslot, curVipSeqNo);
|
if (null == updateRowNum || updateRowNum == 0) {
|
throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
|
}
|
|
return curVipSeqNo + 1;
|
}
|
|
/**
|
* 按【检查类型配置的规则】分配排队序号。
|
*
|
* <h3>三种规则</h3>
|
* <ol>
|
* <li><b>预约号(0)</b>:直接用预约系统已下发的号,不再分配。
|
* 注意 {@code existingSeqNo} 为空时**回退到签到序号规则** —— 因为若直接返回
|
* null,调用方会把 null 写入 {@code book_seq_num},患者将拿不到号。</li>
|
* <li><b>签到序号(1)</b>:当天同检查类型内连续递增(第 1 个签到 = 1 号),
|
* 并按「每 9 个号预留 1 个」跳过预留号给加急患者。
|
* **不设当日上限**(与规则 2 的时段容量不同)。</li>
|
* <li><b>时段号(2)</b>:调用原有的 {@link #distributeSeqNo}(按 {@code time_slot} 从号池取号),
|
* 保留时段容量控制。这也是 {@code seq_rule} 未配置时的默认值。</li>
|
* </ol>
|
*
|
* <h3>为什么规则 1 需要独立计数器</h3>
|
* 不能用 {@code MAX(book_seq_num)+1} 计算:预留号(10、20…)若无人使用就不会出现在
|
* 数据里,下一个普通号按 MAX+1 会再次算出同一个预留号,造成冲突。
|
* 因此用 {@code seq_counter} 表**必须被消耗**地递增(见 Mapper 的 LAST_INSERT_ID 写法)。
|
*/
|
@Override
|
@Transactional
|
public Integer distributeSeqNoByRule(Integer checkType, Integer timeslot,
|
Integer existingSeqNo, boolean urgent) {
|
SeqRuleEnum rule = resolveRule(checkType);
|
|
switch (rule) {
|
case APPOINT:
|
// ① 预约号:预约系统已给号则直接用;否则回退到签到序号(避免患者拿不到号)
|
if (null != existingSeqNo && existingSeqNo > 0) {
|
log.info("[distributeSeqNoByRule] 规则=预约号,直接使用预约系统号. checkType={} seqNo={}",
|
checkType, existingSeqNo);
|
return existingSeqNo;
|
}
|
log.warn("[distributeSeqNoByRule] 规则=预约号,但预约系统未下发号,回退到签到序号. checkType={}",
|
checkType);
|
return nextCheckInSeqNo(checkType, urgent);
|
|
case CHECKIN:
|
// ② 签到序号:当天同检查类型连续递增,每 9 个预留 1 个
|
return nextCheckInSeqNo(checkType, urgent);
|
|
case TIMESLOT:
|
default:
|
// ③ 时段号:保持改造前的行为(含时段容量控制)
|
return distributeSeqNo(checkType, timeslot);
|
}
|
}
|
|
/**
|
* 解析某检查类型配置的排队序号规则;查不到配置或未配置时返回默认(时段号)。
|
*/
|
private SeqRuleEnum resolveRule(Integer checkType) {
|
if (null == checkType) {
|
return SeqRuleEnum.DEFAULT;
|
}
|
CheckTypeDO checkTypeDO = checkTypeMapper.getCheckTypeByValue(checkType);
|
if (null == checkTypeDO) {
|
log.warn("[resolveRule] 未找到检查类型配置,使用默认规则(时段号). checkType={}", checkType);
|
return SeqRuleEnum.DEFAULT;
|
}
|
return SeqRuleEnum.parse(checkTypeDO.getSeqRule());
|
}
|
|
/**
|
* 计算下一个「签到序号」。
|
*
|
* <h4>两个独立计数器(这是踩坑后的结论)</h4>
|
* {@code seq_counter} 维护两个互不干扰的计数器:
|
* <ul>
|
* <li>{@code normal_next} —— 普通号,序列 1..9、11..19、21..29…(跳过 10 的倍数);</li>
|
* <li>{@code reserved_next} —— 预留号(加急用),序列 10、20、30…。</li>
|
* </ul>
|
* <b>为什么不能只用一个计数器</b>:若用单个 {@code candidate} 递增后再"跳过预留号",
|
* 必然撞车 —— 普通患者拿到 candidate=10 → 跳过得 11;下一个普通患者拿到 candidate=11
|
* → 也得 11。实测 20 次取号中第 10、11 次分配号相同。
|
* 两个计数器物理隔离后,二者取值域不相交,**不可能重号**。
|
*
|
* <h4>并发(两处坑,均已修复)</h4>
|
* <ol>
|
* <li><b>推进必须原子</b>:用单条 {@code INSERT .. ON DUPLICATE KEY UPDATE}
|
* 在数据库端完成"读-改-写",避免先查后改造成重号。
|
* 注意该语句必须加 {@code @InterceptorIgnore(dataPermission="true")},
|
* 否则 MyBatis-Plus 的 DataPermissionInterceptor 会用 JSqlParser 解析它并
|
* **抛 UnsupportedOperationException**(JSqlParser 不支持该 MySQL 专有语法),
|
* 表现为**预约确认直接 500** —— 详见 Mapper 上的注释。</li>
|
* <li><b>推进与读回必须同事务</b>:MySQL 默认 REPEATABLE READ,
|
* 若读回在另一个事务里,会读到旧快照 ⇒ 重号。
|
* 本方法是 private 且被**同类内部调用**,{@code @Transactional} 因不走代理而**失效**,
|
* 故改用 {@link org.springframework.transaction.support.TransactionTemplate} 显式开启事务。</li>
|
* </ol>
|
* 实测 8 线程 × 20 次 = 160 次取号:同事务 160/160 唯一、号值连续;
|
* 独立事务只有 75/160 —— 详见 Mapper 注释。
|
*/
|
private Integer nextCheckInSeqNo(Integer checkType, boolean urgent) {
|
final LocalDate today = LocalDate.now();
|
|
// 用显式事务把「推进」与「读回」包在一起(@Transactional 在此处不生效,见方法注释)
|
Integer assigned = counterTransactionTemplate.execute(status -> {
|
if (urgent) {
|
queueSequenceMapper.advanceReservedSeq(checkType, today);
|
} else {
|
queueSequenceMapper.advanceNormalSeq(checkType, today);
|
}
|
SeqCounterDO counter = queueSequenceMapper.getSeqCounter(checkType, today);
|
if (null == counter) {
|
return null;
|
}
|
return urgent ? counter.getReservedNext() : counter.getNormalNext();
|
});
|
|
if (null == assigned) {
|
// 理论上不会发生(刚推进过);保守回退,避免抛错阻断签到
|
assigned = urgent ? SeqRuleEnum.ROUND_SIZE : 1;
|
log.error("[nextCheckInSeqNo] 计数器读取失败,回退为默认号. checkType={} 加急={} 回退号={}",
|
checkType, urgent, assigned);
|
}
|
|
log.info("[nextCheckInSeqNo] 规则=签到序号. checkType={} 加急={} 本次分配={}",
|
checkType, urgent, assigned);
|
return assigned;
|
}
|
|
/**
|
* 向后查找一个**仍有余量**的预约时段。
|
* <p>
|
* 与旧的 {@code getNextTimeSlot} 的区别:
|
* <ol>
|
* <li>旧实现只看"紧邻的下一个"字典项,且**不检查该时段在 queue_sequence 中是否存在**
|
* —— 若下一个时段恰好缺失(号池没有这一行),会直接返回它,随后
|
* {@code selectQueueNoForUpdate} 取不到值 → 抛 TIMESLOT_FULL。
|
* 这也是"运动试验没法签到"的直接原因之一。</li>
|
* <li>旧实现在 {@code timeslot} 不在字典中时(HIS 会下发 60 分钟粒度值,
|
* 如 10001100,而字典只有 30 分钟粒度)直接 {@code return null}。
|
* 本实现改为**取字典中第一个晚于 {@code timeslot} 的时段**继续向后找,
|
* 从而仍能为这类患者分配号。</li>
|
* <li>最多向后找 {@link #MAX_TIMESLOT_LOOKAHEAD} 个时段(约 3 小时),
|
* 避免"当前是最后一个时段"时无处可去;全都满才返回 null。</li>
|
* </ol>
|
*
|
* @param checkType 检查类型
|
* @param timeslot HIS 传入的预约时段(可能是字典外的 60 分钟粒度值)
|
* @param includeVip true 时要求 VIP 号也有余量
|
* @return 可用的时段值;无可用时段返回 null
|
*/
|
private Integer findAvailableTimeslot(Integer checkType, Integer timeslot, boolean includeVip) {
|
List<DictDataRespDTO> dictBookTimeslotList = dictDataApi.getDictDataList(ECG_BOOK_TIMESLOT);
|
if (dictBookTimeslotList == null || dictBookTimeslotList.isEmpty()) {
|
log.error("[findAvailableTimeslot] 字典 {} 为空,无法分配时段号", ECG_BOOK_TIMESLOT);
|
return null;
|
}
|
|
// 起点:严格晚于 timeslot 的第一个字典项。
|
// 这样即使 timeslot 不在字典内(60 分钟粒度值),也能落到其后的时段继续找。
|
int startIdx = -1;
|
for (int i = 0; i < dictBookTimeslotList.size(); i++) {
|
Integer v = parseSlotValue(dictBookTimeslotList.get(i).getValue());
|
if (null != v && v > timeslot) {
|
startIdx = i;
|
break;
|
}
|
}
|
if (startIdx < 0) {
|
// timeslot 已是最后一个时段(或全部时段都早于它)
|
log.warn("[findAvailableTimeslot] 已是最后一个时段或无更晚时段. checkType={} timeslot={}",
|
checkType, timeslot);
|
return null;
|
}
|
|
int end = Math.min(dictBookTimeslotList.size(), startIdx + MAX_TIMESLOT_LOOKAHEAD);
|
for (int i = startIdx; i < end; i++) {
|
Integer candidate = parseSlotValue(dictBookTimeslotList.get(i).getValue());
|
if (null == candidate) {
|
continue;
|
}
|
Integer avail = queueSequenceMapper.countAvailableTimeslot(
|
checkType, candidate, includeVip ? 1 : 0);
|
if (null != avail && avail > 0) {
|
log.info("[findAvailableTimeslot] checkType={} 原时段={} → 改用时段={}(第 {} 个候选)",
|
checkType, timeslot, candidate, i - startIdx + 1);
|
return candidate;
|
}
|
}
|
|
log.warn("[findAvailableTimeslot] 向后 {} 个时段内均无余量. checkType={} timeslot={}",
|
MAX_TIMESLOT_LOOKAHEAD, checkType, timeslot);
|
return null;
|
}
|
|
/** 安全解析字典值(形如 "9301000")为 Integer,忽略脏数据 */
|
private Integer parseSlotValue(String value) {
|
if (null == value || value.isEmpty()) {
|
return null;
|
}
|
try {
|
return Integer.valueOf(value.trim());
|
} catch (NumberFormatException e) {
|
log.warn("[parseSlotValue] 字典时段值非法,已忽略: {}", value);
|
return null;
|
}
|
}
|
|
/**
|
* 旧的"取紧邻下一个时段"。保留供参考/兼容,新逻辑请用
|
* {@link #findAvailableTimeslot(Integer, Integer, boolean)}。
|
*
|
* @deprecated 只看下一个时段且不校验号池是否存在,会导致签到失败
|
*/
|
@Deprecated
|
private Integer getNextTimeSlot(Integer timeslot, Integer checkType) {
|
List<DictDataRespDTO> dictBookTimeslotList = dictDataApi.getDictDataList(ECG_BOOK_TIMESLOT);
|
Optional<Integer> index = dictBookTimeslotList.stream()
|
.filter(e -> e.getValue().equals(String.valueOf(timeslot)))
|
.findFirst()
|
.map(dictBookTimeslotList::indexOf);
|
if (!index.isPresent())
|
return null;
|
|
if (dictBookTimeslotList.size() <= (index.get() + 1))
|
return null;
|
|
DictDataRespDTO dictDataRespDTO = dictBookTimeslotList.get(index.get() + 1);
|
return Integer.valueOf(dictDataRespDTO.getValue());
|
}
|
@Override
|
public void initNumber() {
|
queueSequenceMapper.initNumber();
|
}
|
|
}
|