From fce96ef468291fb9a0e6a4d34ab371315e9485d4 Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期六, 03 十月 2026 00:59:44 +0800
Subject: [PATCH] ai优化

---
 jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java |  347 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 342 insertions(+), 5 deletions(-)

diff --git a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
index ec1e59b..ebfc4db 100644
--- a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
+++ b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/queuesequence/QueueSequenceServiceImpl.java
@@ -2,14 +2,18 @@
 
 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.*;
@@ -17,6 +21,7 @@
 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;
 
@@ -33,7 +38,16 @@
  */
 @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;
@@ -47,8 +61,43 @@
     @Resource
     private CheckTypeMapper checkTypeMapper;
 
+    /**
+     * 鐢ㄤ簬鎶娿�岃鏁板櫒鎺ㄨ繘 + 璇诲洖銆嶆樉寮忓寘杩�**鍚屼竴涓簨鍔�**銆�
+     *
+     * <h4>涓轰粈涔堝繀椤荤敤 TransactionTemplate 鑰屼笉鏄� @Transactional锛堥噸瑕侊級</h4>
+     * 杩欎袱姝ュ湪 {@link #nextCheckInSeqNo} 杩欎釜 **private 鏂规硶**閲岋紝涓旂敱**鍚岀被鍐呴儴璋冪敤**
+     * 锛坽@code distributeSeqNoByRule} 鈫� {@code nextCheckInSeqNo}锛夈��
+     * Spring 鐨� {@code @Transactional} 鍩轰簬浠g悊锛�**鍚岀被鑷皟鐢ㄤ笉浼氳蛋浠g悊 鈬� 娉ㄨВ瀹屽叏涓嶇敓鏁�**銆�
+     * 浜庢槸"鎺ㄨ繘"涓�"璇诲洖"浼氬悇鑷嫭绔嬫彁浜わ紝鍦� REPEATABLE READ 涓嬭鍒扮殑鍙兘鏄棫蹇収
+     * 鈬� **骞跺彂閲嶅彿**锛堟棭鍏堝疄娴嬶細鐙珛浜嬪姟鍙湁 75/160 涓敮涓�鍊硷紱鍚屼簨鍔′负 160/160锛夈��
+     *
+     * 鏁呰繖閲岀敤 {@link TransactionTemplate} **鏄惧紡**寮�鍚簨鍔★紝涓嶄緷璧� AOP 浠g悊锛屽繀鐒剁敓鏁堛��
+     * 浼犳挱琛屼负鐢ㄩ粯璁ょ殑 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}
+     * 鏈熸湜鍙懡涓竴琛岋紱涓�鏃﹀嚭鐜伴噸澶嶈锛屽彇鍙蜂細鍛戒腑涓嶇‘瀹氱殑涓�琛屻��
+     * 杩欓噷鍦ㄧ淮鎶ゅ叆鍙e鎷︽埅锛岀粰鍑烘槑纭彁绀鸿�屼笉鏄闂杩涘叆鍙栧彿闃舵銆�
+     */
     @Override
     public Integer createQueueSequence(QueueSequenceSaveReqVO createReqVO) {
+        validateTimeslotNotDuplicated(createReqVO.getCheckType(), createReqVO.getTimeSlot(), null);
         // 鎻掑叆
         QueueSequenceDO queueSequence = BeanUtils.toBean(createReqVO, QueueSequenceDO.class);
         queueSequenceMapper.insert(queueSequence);
@@ -60,9 +109,32 @@
     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={} "
+                            + "宸插瓨鍦╥d={} 鏈id={}", checkType, timeSlot, exist.getId(), excludeId);
+            throw exception(QUEUE_SEQUENCE_TIMESLOT_DUPLICATE);
+        }
     }
 
     @Override
@@ -82,6 +154,11 @@
     @Override
     public QueueSequenceDO getQueueSequence(Integer id) {
         return queueSequenceMapper.selectById(id);
+    }
+
+    @Override
+    public List<QueueSequenceDO> selectGivenCheckTypeTimeslot(Integer checkType) {
+        return queueSequenceMapper.selectTimeslotByCheckType( checkType );
     }
 
     @Override
@@ -124,12 +201,43 @@
     public Integer distributeSeqNo(Integer checkType, Integer timeslot) {
         Integer curSeqNo = queueSequenceMapper.selectQueueNoForUpdate(checkType, timeslot);
         if (null == curSeqNo) {
-            throw exception(QUEUE_SEQUENCE_TIMESLOT_FULL);
-        }
+            // 銆愪慨澶嶃�屾椂娈典笉鍏�/宸叉弧瀵艰嚧娌℃硶绛惧埌銆嶃��
+            // 鍘熼�昏緫锛氬彧鍙�"瀛楀吀閲岀殑绱ч偦涓嬩竴涓椂娈�"锛屼笖瑕佹眰璇ユ椂娈靛湪 queue_sequence 涓瓨鍦ㄣ��
+            // 鐜板疄闂锛堝疄娴嬶級锛�
+            //   路 瀛楀吀 ecg_book_timeslot 鏈� 21 涓椂娈碉紝浣� queue_sequence 涓� checkType=400
+            //     鍙湁 13 涓�乧heckType=100 鍙湁 17 涓� 鈥斺�� 閮ㄥ垎鏃舵鏍规湰娌℃湁鍙锋睜琛岋紱
+            //   路 HIS 浼氫笅鍙� 60 鍒嗛挓绮掑害鐨� bookTimeslot锛堝 10001100锛夛紝瀛楀吀閲屾病鏈夎鍊硷紝
+            //     getNextTimeSlot 鐩存帴 return null锛�
+            //   路 涓埆鏃舵瀹归噺寰堝皬锛堝钩鏉胯繍鍔� timeslot_book_num=3锛夛紝寰堝揩閿�瀹屻��
+            // 涓夎�呬换涓�鍛戒腑 鈫� 鎶� QUEUE_SEQUENCE_TIMESLOT_FULL 鈫� **鎮h�呮病娉曠鍒�**銆�
+            //
+            // 鐜板湪鏀逛负锛�**鍚戝悗鏈�澶氭壘 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);
+            }
 
-        Integer updateRowNum = queueSequenceMapper.updateGivenCheckTypeTimeslotSeqNo(checkType, timeslot, curSeqNo);
-        if (null == updateRowNum || updateRowNum == 0) {
-            throw exception(QUEUE_SEQUENCE_TIMESLOT_EXCEPTION);
+            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;
@@ -151,5 +259,234 @@
         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:
+                // 鈶� 棰勭害鍙凤細棰勭害绯荤粺宸茬粰鍙峰垯鐩存帴鐢紱鍚﹀垯鍥為��鍒扮鍒板簭鍙凤紙閬垮厤鎮h�呮嬁涓嶅埌鍙凤級
+                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);
+        }
+    }
+
+    /**
+     * 瑙f瀽鏌愭鏌ョ被鍨嬮厤缃殑鎺掗槦搴忓彿瑙勫垯锛涙煡涓嶅埌閰嶇疆鎴栨湭閰嶇疆鏃惰繑鍥為粯璁わ紙鏃舵鍙凤級銆�
+     */
+    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 瑙f瀽瀹冨苟
+     *       **鎶� UnsupportedOperationException**锛圝SqlParser 涓嶆敮鎸佽 MySQL 涓撴湁璇硶锛夛紝
+     *       琛ㄧ幇涓�**棰勭害纭鐩存帴 500** 鈥斺�� 璇﹁ Mapper 涓婄殑娉ㄩ噴銆�</li>
+     *   <li><b>鎺ㄨ繘涓庤鍥炲繀椤诲悓浜嬪姟</b>锛歁ySQL 榛樿 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} 涓嶅湪瀛楀吀涓椂锛圚IS 浼氫笅鍙� 60 鍒嗛挓绮掑害鍊硷紝
+     *       濡� 10001100锛岃�屽瓧鍏稿彧鏈� 30 鍒嗛挓绮掑害锛夌洿鎺� {@code return null}銆�
+     *       鏈疄鐜版敼涓�**鍙栧瓧鍏镐腑绗竴涓櫄浜� {@code timeslot} 鐨勬椂娈�**缁х画鍚戝悗鎵撅紝
+     *       浠庤�屼粛鑳戒负杩欑被鎮h�呭垎閰嶅彿銆�</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;
+    }
+
+    /** 瀹夊叏瑙f瀽瀛楀吀鍊硷紙褰㈠ "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();
+    }
+
 }
 

--
Gitblit v1.9.3