From 68ff33beff11310e830b0615b5a81f83fad0f683 Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期日, 08 十一月 2026 23:33:14 +0800
Subject: [PATCH] ai优化

---
 jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/appointment/AppointmentServiceImpl.java | 2354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 2,205 insertions(+), 149 deletions(-)

diff --git a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/appointment/AppointmentServiceImpl.java b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/appointment/AppointmentServiceImpl.java
index 7e5ef71..6df4631 100644
--- a/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/appointment/AppointmentServiceImpl.java
+++ b/jh-module-ecg/jh-module-ecg-biz/src/main/java/cn/lihu/jh/module/ecg/service/appointment/AppointmentServiceImpl.java
@@ -1,38 +1,48 @@
 package cn.lihu.jh.module.ecg.service.appointment;
 
+import cn.lihu.jh.framework.common.pojo.PageResult;
 import cn.lihu.jh.framework.common.util.date.DateUtils;
-import cn.lihu.jh.framework.common.util.date.LocalDateTimeUtils;
+import cn.lihu.jh.framework.common.util.object.BeanUtils;
+import cn.lihu.jh.module.ecg.controller.admin.appointment.vo.AppointmentConfirmReqVO;
+import cn.lihu.jh.module.ecg.controller.admin.appointment.vo.AppointmentPageReqVO;
+import cn.lihu.jh.module.ecg.controller.admin.appointment.vo.AppointmentSaveReqVO;
 import cn.lihu.jh.module.ecg.controller.admin.queue.vo.QueueSaveReqVO;
+import cn.lihu.jh.module.ecg.dal.dataobject.appointment.AppointmentDO;
+import cn.lihu.jh.module.ecg.dal.dataobject.patient.PatDetails;
+import cn.lihu.jh.module.ecg.dal.dataobject.queue.QueueDO;
+import cn.lihu.jh.module.ecg.dal.mysql.appointment.AppointmentMapper;
+import cn.lihu.jh.module.ecg.enums.AppointmentTypeEnum;
+import cn.lihu.jh.module.ecg.enums.HisCheckCodeEnum;
+import cn.lihu.jh.module.ecg.enums.HisCheckNameEnum;
+import cn.lihu.jh.module.ecg.enums.HisCheckStatusEnum;
 import cn.lihu.jh.module.ecg.feign.RemoteDataService;
-import cn.lihu.jh.module.ecg.feign.RestApiReqBodyVo;
-import cn.lihu.jh.module.ecg.feign.RestApiResult;
-import cn.lihu.jh.module.ecg.feign.dto.AppointmentExternal;
 import cn.lihu.jh.module.ecg.service.config.EcgConfigService;
+import cn.lihu.jh.module.ecg.service.devrent.ApplicationTemplate;
+import cn.lihu.jh.module.ecg.service.his.HisEcgFilter;
 import cn.lihu.jh.module.ecg.service.queue.QueueService;
 import cn.lihu.jh.module.ecg.service.queuesequence.QueueSequenceService;
+import cn.lihu.jh.module.ecg.webservice.WebServiceClient;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 import org.springframework.validation.annotation.Validated;
 
-import cn.lihu.jh.module.ecg.controller.admin.appointment.vo.*;
-import cn.lihu.jh.module.ecg.dal.dataobject.appointment.AppointmentDO;
-import cn.lihu.jh.framework.common.pojo.PageResult;
-import cn.lihu.jh.framework.common.util.object.BeanUtils;
-
-import cn.lihu.jh.module.ecg.dal.mysql.appointment.AppointmentMapper;
-
 import javax.annotation.Resource;
-import javax.xml.crypto.Data;
-
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import static cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.lihu.jh.framework.common.pojo.CommonResult.error;
 import static cn.lihu.jh.module.ecg.enums.ErrorCodeConstants.*;
 
 /**
@@ -44,6 +54,16 @@
 @Validated
 @Slf4j
 public class AppointmentServiceImpl implements AppointmentService {
+
+    /**
+     * 鎮h�呮�у埆銆屾湭鐭ャ�嶇殑鍏滃簳鍊笺��
+     * <p>
+     * {@code appointment.pat_gender} 鍦ㄦ暟鎹簱涓负 NOT NULL 涓旀棤榛樿鍊�
+     * 锛堝疄娴嬶細{@code Field 'pat_gender' doesn't have a default value}锛夈��
+     * 鑰� {@code S0405} 绛夋姤鏂囩殑鎬у埆瀛楁璺緞涓� {@code S0201ECG} 涓嶅悓銆佷笖鍙兘缂哄け锛�
+     * 瀹归敊鏂板鏃惰嫢鍙栦笉鍒帮紝鎸� 0锛堟湭鐭ワ級鍐欏叆锛岄伩鍏嶆彃鍏ュけ璐ャ��
+     */
+    private static final Byte UNKNOWN_GENDER = (byte) 0;
 
     @Resource
     private EcgConfigService ecgConfigService;
@@ -60,13 +80,22 @@
     @Resource
     private AppointmentMapper appointmentMapper;
 
+    /**
+     * 蹇冪數涓氬姟鍗曞垽瀹氬櫒銆�
+     * <p>
+     * 鐢ㄤ簬 {@code S0405}锛堝叏闄㈤�氱敤鐘舵�佹祦锛夌殑杩囨护锛氶潪蹇冪數鍗曞彧璁版棩蹇椼�佷笉钀藉簱锛�
+     * 閬垮厤鍚� appointment 琛ㄧ亴鍏ユ斁灏�/瓒呭0绛夊叾浠栫瀹ょ殑鏁版嵁銆�
+     */
+    @Resource
+    private HisEcgFilter hisEcgFilter;
+
+    @Autowired
+    private WebServiceClient webServiceClient;
+
     @Override
     public Long createAppointment(AppointmentSaveReqVO createReqVO) {
         // 鎻掑叆
         AppointmentDO appointment = BeanUtils.toBean(createReqVO, AppointmentDO.class);
-
-        appointment.setBookTime( LocalDateTime.now() );
-        appointment.setBookSrc( 1 );
 
         try {
             appointmentMapper.insert(appointment);
@@ -78,6 +107,79 @@
         return appointment.getId();
     }
 
+    /** 骞跺彂鍐茬獊鍚庣殑閲嶈瘯娆℃暟涓庣瓑寰呴棿闅旓紙浠呯敤浜�"鎻掑叆鎾炲敮涓�绱㈠紩"杩欎竴绉嶆儏鍐碉級 */
+    private static final int APPLY_NO_CONFLICT_RETRY = 3;
+    private static final long APPLY_NO_CONFLICT_WAIT_MS = 150L;
+
+    /**
+     * 鎸� {@code apply_no} 鍘熷瓙钀藉簱锛氬凡瀛樺湪鍒欐洿鏂帮紝涓嶅瓨鍦ㄥ垯鎻掑叆锛涘苟鍙戦噸澶嶆彃鍏ユ椂涓嶄細鐣欓噸澶嶈銆�
+     *
+     * <h3>涓轰粈涔堥渶瑕佸畠</h3>
+     * 鍘熷疄鐜版槸銆寋@code getByApplyNo} 鏌ヤ竴涓� 鈫� 娌℃湁灏� insert銆嶏紝鏌ヤ笌鎻掍箣闂存病鏈夊師瀛愭�с��
+     * 瀹炴祴 2026-10-04 12:16锛欻IS 鍦� 423ms 鍐呮妸鍚屼竴寮犵敵璇峰崟鎺ㄤ簡涓ら亶锛屼袱涓嚎绋嬮兘璇诲埌 0 琛屻��
+     * 閮芥墽琛屼簡 insert锛屼簬鏄悓涓� {@code apply_no} 钀戒笅涓ゆ潯锛坕d 211740 / 211741锛夈��
+     * 琛ㄤ笂鍘熸湁鐨勫敮涓�绱㈠紩 {@code uniq-pat-book(pat_id, book_date, book_check_type)}
+     * 鍦� book_date / book_check_type 涓� NULL 鏃朵笉鐢熸晥锛屾嫤涓嶄綇銆�
+     *
+     * <h3>鐜板湪鐨勫仛娉�</h3>
+     * 鏁版嵁搴撲晶宸插姞鍞竴绱㈠紩 {@code uk_appointment_apply_no_active}锛堝彧绾︽潫鏈垹闄よ锛夈��
+     * 杩欓噷鏀规垚銆屽厛灏濊瘯鎻掑叆锛屾挒鍞竴閿氨鍥炶骞舵敼涓烘洿鏂般�嶏細
+     * <ul>
+     *   <li>涓茶鍦烘櫙锛氭彃鍏ユ垚鍔燂紝琛屼负涓庡師鏉ヤ竴鑷达紱</li>
+     *   <li>骞跺彂鍦烘櫙锛氬叾涓竴涓彃鍏ュ繀鐒跺け璐ワ紙DuplicateKeyException锛夛紝澶辫触鏂硅蛋鏇存柊锛屼笉鍐嶄骇鐢熼噸澶嶈锛�</li>
+     *   <li>瀵规柟浜嬪姟灏氭湭鎻愪氦瀵艰嚧鍥炶涓嶅埌鏃讹紝鐭殏绛夊緟鍚庨噸璇曪紙鏈�澶� {@value #APPLY_NO_CONFLICT_RETRY} 娆★級銆�</li>
+     * </ul>
+     *
+     * @return 钀藉簱璁板綍鐨� id
+     */
+    private Long atomicSaveByApplyNo(AppointmentSaveReqVO saveReqVO) {
+        String applyNo = saveReqVO.getApplyNo();
+        // 鈶� 鍏堟寜鐢宠鍗曞彿璧�"鏇存柊"鍒嗘敮锛堜笌鏀归�犲墠鐨勮涔変竴鑷达細鍛戒腑宸插瓨鍦ㄥ垯浠ユ湰娆℃姤鏂囧唴瀹规洿鏂帮級
+        AppointmentDO exists = (applyNo == null) ? null : appointmentMapper.getByApplyNo(applyNo);
+        if (exists != null) {
+            saveReqVO.setId(exists.getId());
+            updateAppointment(saveReqVO);
+            return exists.getId();
+        }
+
+        // 鈶� 涓嶅瓨鍦� 鈫� 灏濊瘯鎻掑叆锛涙挒鍞竴绱㈠紩璇存槑鏈夊苟鍙戞彃鍏ワ紝杞洿鏂伴噸璇�
+        for (int attempt = 0; attempt <= APPLY_NO_CONFLICT_RETRY; attempt++) {
+            try {
+                AppointmentDO toInsert = BeanUtils.toBean(saveReqVO, AppointmentDO.class);
+                toInsert.setId(null);
+                appointmentMapper.insert(toInsert);
+                return toInsert.getId();
+            } catch (DuplicateKeyException e) {
+                AppointmentDO concurrent = (applyNo == null) ? null : appointmentMapper.getByApplyNo(applyNo);
+                if (concurrent != null) {
+                    log.warn("[atomicSaveByApplyNo][骞跺彂閲嶅鎻掑叆宸叉嫤鎴紝鏀逛负鏇存柊宸叉湁璁板綍] applyNo={} existingId={}",
+                            applyNo, concurrent.getId());
+                    saveReqVO.setId(concurrent.getId());
+                    updateAppointment(saveReqVO);
+                    return concurrent.getId();
+                }
+                // 瀵规柟鍙兘杩樻病鎻愪氦锛氱瓑涓�涓嬪啀璇曪紙鏈�鍚庝粛鎷夸笉鍒板氨涓婃姏锛岀敱澶栧眰璁板綍閿欒鏃ュ織锛�
+                if (attempt < APPLY_NO_CONFLICT_RETRY) {
+                    try {
+                        Thread.sleep(APPLY_NO_CONFLICT_WAIT_MS);
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();
+                        throw e;
+                    }
+                    AppointmentDO retry = appointmentMapper.getByApplyNo(applyNo);
+                    if (retry != null) {
+                        saveReqVO.setId(retry.getId());
+                        updateAppointment(saveReqVO);
+                        return retry.getId();
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
+        throw exception(APPOINTMENT_CREATE_FAIL);
+    }
+
     @Override
     public void updateAppointment(AppointmentSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
@@ -85,6 +187,42 @@
         // 鏇存柊
         AppointmentDO updateObj = BeanUtils.toBean(updateReqVO, AppointmentDO.class);
         appointmentMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void updateAppointmentStatus(AppointmentSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateAppointmentExists(updateReqVO.getId());
+
+        // 鑾峰彇瀹屾暣鐨勯绾︿俊鎭�
+        AppointmentDO appointment = appointmentMapper.selectById(updateReqVO.getId());
+        if (appointment == null) {
+            throw exception(APPOINTMENT_NOT_EXISTS);
+        }
+
+        // 鏇存柊鐘舵��
+        appointment.setStatus(updateReqVO.getStatus());
+
+        // 璋冪敤HIS绯荤粺鏇存柊鐘舵��
+        String action = "S0405";
+        ApplicationTemplate app = new ApplicationTemplate();
+        String statusName = AppointmentTypeEnum.getByType(appointment.getStatus());
+        String message = app.getXML(appointment.getApplyNo(), appointment.getStatus(), statusName, null, appointment.getPatDeptCode(), appointment.getPatDeptDesc(), appointment.getPatWardCode(), appointment.getPatWardDesc(), appointment.getPatBedNo(), appointment.getEpisodeId(), String.valueOf(appointment.getBookSrc()), appointment.getPatId(), appointment.getPatName());
+
+        try {
+            String response = webServiceClient.callJHFWTYRK(action, "", message);
+            if (response.contains("鏇存柊鎴愬姛")) {
+                // 鏇存柊鏈湴鏁版嵁搴�
+                appointmentMapper.updateById(appointment);
+
+
+            } else {
+                throw exception(APPOINTMENT_UPDATE_FAIL);
+            }
+        } catch (Exception e) {
+            log.error("[updateAppointmentStatus][鏇存柊棰勭害鐘舵�佸け璐� appointment({})]", appointment, e);
+            throw exception(APPOINTMENT_UPDATE_FAIL);
+        }
     }
 
     @Override
@@ -106,11 +244,10 @@
         return appointmentMapper.selectById(id);
     }
 
-    // TODO 灏氫笉鏀寔, 涓�浣嶆偅鑰呭綋澶╀袱绉嶆鏌ラ」鐩殑鎯呭喌
     @Override
-    public AppointmentDO getCurAppointmentByPatId(String patId) {
-        List<AppointmentDO> appointmentDOList = appointmentMapper.getCurrentPatId( patId );
-        return 0 == appointmentDOList.size() ? null : appointmentDOList.get(0);
+    public AppointmentDO getCurAppointmentByPatIdAndCheckType(String patId, Integer checkType) {
+        AppointmentDO appointmentDO = appointmentMapper.getByPatAndCheckTypeAndBookDate(patId, checkType);
+        return appointmentDO;
     }
 
     /**
@@ -119,121 +256,243 @@
      */
     @Override
     public List<AppointmentDO> queryAndCreateAppointmentByPatId(String patCode) {
-        // 鍏堜粠鍖婚櫌骞冲彴鏌ヨ
-        List<AppointmentDO> appointmentExtermalList = getAppointmentExtermal( patCode );
-        return appointmentExtermalList;
+        // 浠庡尰闄㈠钩鍙版煡璇�
+        //
+        // 銆愭湰闇�姹傘�戝尮閰嶅彛寰勪粠銆岀敵璇峰崟鍙� / 灏辫瘖娴佹按鍙� / 浣忛櫌鍙� / 韬唤璇佸彿銆嶆墿灞曚负**鍐嶅姞涓� pat_id**
+        // 锛堣 AppointmentMapper.getCurrentCode 鐨� SQL锛夈�傚師鍥狅細
+        //   鎵嬪姩鏂板缓鐨勯绾︼紙/ecg/appointment/create锛夊彧鏈� pat_id锛�
+        //   apply_no / episode_id / hospital_no / pat_identity_id 鍏ㄤ负绌猴紝
+        //   绛惧埌椤垫寜鎮h�呯紪鍙锋煡璇㈡椂**姘歌繙鏌ヤ笉鍒�** 鈫� 椤甸潰鎻愮ず銆岃鍏堥绾︺�嶁啋 鏃犳硶绛惧埌銆�
+        List<AppointmentDO> appointmentExtermalList = appointmentMapper.getCurrentCode(patCode);
 
-/* 鍏堜繚鐣�
-        for ( int appointmentIndex = 0; appointmentIndex < appointmentExtermalList.size(); appointmentIndex++ ) {
+        for (int appointmentIndex = 0; appointmentIndex < appointmentExtermalList.size(); appointmentIndex++) {
             AppointmentDO appointmentExtermal = appointmentExtermalList.get(appointmentIndex);
-            if (null != appointmentExtermal && null != appointmentExtermal.getBookDate() && appointmentExtermal.getBookDate().isEqual(LocalDate.now())) {
-                // 鏌ュ埌褰撳ぉ鐨勯绾﹀悗锛屾煡璇B涓� 鏄惁宸茬粡瀛樺湪
-                AppointmentDO appointmentDO = appointmentMapper.getCurrentByPatAndCheckType(appointmentExtermal.getPatId(), appointmentExtermal.getBookCheckType());
-                if (null == appointmentDO) {
-                    appointmentMapper.insert(appointmentExtermal);
-                    return appointmentExtermal;
+            if (null == appointmentExtermal) continue;
+
+            // 浠嶥B鏌ヨ棰勭害锛屾槸鍚﹀凡缁忓瓨鍦�
+            //
+            // 銆愭湰闇�姹傘�戜紭鍏堟寜**涓婚敭 id** 鍒ゆ柇銆屾槸鍚﹀凡瀛樺湪銆嶏細
+            //   getCurrentCode 鏄湰鍦板簱鏌ヨ锛坰elect * 浼氭妸 id 甯﹀洖鏉ワ級锛�
+            //   鑰屽師閫昏緫鐢� getByEpisodeIdAndApplyNo(episodeId, applyNo) 鍒ゆ柇锛�
+            //   鎵嬪姩鏂板缓鐨勮杩欎袱涓瓧娈垫槸 NULL/绌� 鈬� `= NULL` 姘歌繙涓嶅尮閰� 鈬� 姣忔墦寮�涓�娆$鍒伴〉灏�**閲嶅鎻掑叆**涓�鏉°��
+            AppointmentDO appointmentDO = null;
+            if (null != appointmentExtermal.getId()) {
+                appointmentDO = appointmentMapper.selectById(appointmentExtermal.getId());
+            }
+            if (null == appointmentDO) {
+                appointmentDO = appointmentMapper.getByEpisodeIdAndApplyNo(
+                        appointmentExtermal.getEpisodeId(), appointmentExtermal.getApplyNo());
+            }
+            if (null == appointmentDO) {
+                appointmentMapper.insert(appointmentExtermal);
+            } else {
+                appointmentExtermal.setId(appointmentDO.getId()); // 纭繚 杩斿洖鍊煎寘鍚� appointment id
+
+                // 澶勭悊绗竴娆$‘璁ゆ椂杩樻病鏈夐绾︽椂闂达紝鍐嶆潵纭鏃跺凡鏈夐绾︽椂闂寸殑鎯呭喌
+                // 銆愭湰娆′慨澶嶃�戝師鍐欐硶 `appointmentDO.getBookDate() != appointmentExtermal.getBookDate()`
+                // 鏄�**寮曠敤姣旇緝**锛圠ocalDate 涓や釜涓嶅悓瀹炰緥鍗充娇鍊肩浉鍚屼篃浼氬垽涓轰笉绛夛級锛�
+                // 浼氬鑷存瘡娆¢兘鐧借窇涓�娆� updateById锛堟棤璋撴敼鍐� update_time锛夈�傛敼涓哄�兼瘮杈冦��
+                if (!java.util.Objects.equals(appointmentDO.getBookDate(), appointmentExtermal.getBookDate())) {
+                    appointmentMapper.updateById(appointmentExtermal);
                 }
-                return appointmentDO;
             }
         }
 
-        List<AppointmentDO> appointmentDOList = appointmentMapper.getCurrentPatId( patCode );
-        return 0 == appointmentDOList.size() ? null : appointmentDOList.get(0);
- */
+        // 鏌ヨ[queue琛╙琛ュ厖銆屾帓闃熷彿銆嶅埌杩斿洖鍊间腑
+        //
+        // 銆愬彿缁熶竴锛堟湰闇�姹傦級銆戞帓闃熷彿鏀逛负鍦ㄧ鍒版椂涓�娆℃�у啓杩� queue.seq_num锛屼笉鍐嶅啓 book_seq_num銆�
+        // 鍥犳杩欓噷鍙� seq_num锛涘澶栧瓧娈靛悕浠嶅彨 bookSeqNum锛堥渶姹傝姹傛帴鍙e瓧娈典繚鐣欎笉鍔級銆�
+        // 杩囨护 null锛氬瓨閲忚锛堟敼閫犲墠绛惧埌鐨勶級seq_num 鍙兘涓虹┖锛岃�� toMap 鐨� value 涓� null 浼氭姏 NPE銆�
+        if (!appointmentExtermalList.isEmpty()) {
+            List<Long> appointIdlist = appointmentExtermalList.stream().map(appointmentDO -> appointmentDO.getId()).toList();
+            List<QueueDO> simpleQueueDOList = queueService.selectQueueByAppointIdList(appointIdlist);
+            if (!simpleQueueDOList.isEmpty()) {
+                Map<Long, Integer> mapAppointIdVsSeqNo = simpleQueueDOList.stream()
+                        .filter(queueDO -> null != queueDO.getAppointId() && null != queueDO.getSeqNum())
+                        .collect(Collectors.toMap(QueueDO::getAppointId, QueueDO::getSeqNum, (a, b) -> a));
+                appointmentExtermalList.forEach(appointmentExterma -> appointmentExterma.setBookSeqNum(mapAppointIdVsSeqNo.get(appointmentExterma.getId())));
+            }
+        }
+
+        // 涓嶄粠DB鏌ヨ
+        //List<AppointmentDO> appointmentDOList = appointmentMapper.getCurrentPatId( patCode );
+        //return 0 == appointmentDOList.size() ? null : appointmentDOList.get(0);
+
+        return appointmentExtermalList;
     }
 
     @Override
     public PageResult<AppointmentDO> getAppointmentPage(AppointmentPageReqVO pageReqVO) {
+        //1. 鍏堟牴鎹畃atId鍘绘煡
+        PageResult<AppointmentDO> result = appointmentMapper.selectPage(pageReqVO);
+        log.info("--------------PATID    result鐨勫�间负锛歿}", result);
+        if (!result.getList().isEmpty()) {
+            return result;
+        }
+
+        if (StringUtils.isNotEmpty(pageReqVO.getPatId())) {
+            // 2. 鐢� applyNo 鏌ヨ
+            pageReqVO.setApplyNo(pageReqVO.getPatId());
+            pageReqVO.setPatId(null);
+            log.info("------------------ApplyNo    pageReqVO鐨勫�间负锛歿}", pageReqVO);
+            result = appointmentMapper.selectPage(pageReqVO);
+            if (!result.getList().isEmpty()) {
+                return result;
+            }
+
+            // 3. 鐢� episodeId 鏌ヨ
+            pageReqVO.setEpisodeId(pageReqVO.getApplyNo());
+            pageReqVO.setPatId(null);
+            pageReqVO.setApplyNo(null);
+            log.info("-----------EpisodeId    pageReqVO鐨勫�间负锛歿}", pageReqVO);
+        }
         return appointmentMapper.selectPage(pageReqVO);
     }
 
     @Override
     public List<AppointmentDO> getAppointmentExtermal(String patId) {
-        List<AppointmentDO> curAppointDOList = new ArrayList<AppointmentDO>();
-
-        RestApiReqBodyVo reqBodyVo = new RestApiReqBodyVo();
-        if (18 == patId.length()) {
-            reqBodyVo.setSfzh( patId );
-        } else {
-            reqBodyVo.setMzzyh(patId);
-        }
-
-        // QueryRisReportList   queryEcgRequest
-        RestApiResult<AppointmentExternal> result = remoteDataService.httpApi("queryEcgRequest", "ECG", "ECG", reqBodyVo);
-        if (0 == result.getRow().size()) {
-            return curAppointDOList;
-        }
-
-        for (int appointIndex=0; appointIndex < result.getRow().size(); appointIndex++ ) {
-            AppointmentExternal appointmentExternal = result.getRow().get( appointIndex );
-
-            AppointmentDO appointmentDO = BeanUtils.toBean(appointmentExternal, AppointmentDO.class);
-            appointmentDO.setApplyNo( appointmentExternal.getReqIdeApplyno() );
-            appointmentDO.setPatSrc( getCorrespondingPatientSource(appointmentExternal.getAdmTypeCode()) );
-            appointmentDO.setPatId(appointmentExternal.getPatientID()); // 鍐呭涓� 韬唤璇佸彿 鎴� 闂ㄨ瘖浣忛櫌鍙�
-            appointmentDO.setPatName(appointmentExternal.getPatName());
-            appointmentDO.setPatGender(Byte.valueOf(appointmentExternal.getPatgender()));
-            appointmentDO.setPatBirthday(DateUtils.ofUTC(appointmentExternal.getEncPatBirthDate()).toLocalDate());
-            appointmentDO.setPatIdentityId(appointmentExternal.getIdentityID());
-            appointmentDO.setPatAddr(appointmentExternal.getAddress());
-            appointmentDO.setPatDeptCode(appointmentExternal.getPatLocDeptCode());
-            appointmentDO.setPatDeptDesc(appointmentExternal.getPatLocDeptDesc());
-            appointmentDO.setPatWardCode(appointmentExternal.getPatLocWardCode());
-            appointmentDO.setPatWardDesc(appointmentExternal.getPatLocWardDesc());
-            appointmentDO.setPatMobile(appointmentExternal.getPhone());
-            appointmentDO.setPatBedNo(appointmentExternal.getEnBedno());
-            appointmentDO.setBookId(appointmentExternal.getReqIdeApplyno());
-            appointmentDO.setBookCheckType(getCorrespondingCheckType(appointmentExternal.getPlanDefItemList().getPlanDefItem().getPlanDefItemcode()));
-            appointmentDO.setBookTime(DateUtils.ofUTC(appointmentExternal.getReqAuthoredOn()));  // 寮�鍗曟椂闂�
-
-            // 鍏夊紑鍗曠殑鎯呭喌锛屾病鏈夐绾︽椂闂�
-            if (null != appointmentExternal.getReqExtBooktime() ) {
-                appointmentDO.setBookDate(DateUtils.ofUTC(appointmentExternal.getReqExtBooktime()).toLocalDate());
-                LocalDateTime bookStartTime = DateUtils.ofUTC(appointmentExternal.getReqExtBooktime());
-                LocalDateTime bookEndTime = bookStartTime.plusMinutes( ecgConfigService.getTimeslotLength());
-                appointmentDO.setBookTimeslot((bookStartTime.getHour() * 100 + bookStartTime.getMinute()) * 10000 + bookEndTime.getHour() * 100 + bookEndTime.getMinute());
-            }
-
-            appointmentDO.setBookSrc(0);
-            appointmentDO.setPaid(1);
-
-            curAppointDOList.add( appointmentDO );
-        }
-
-        return curAppointDOList;
+        return appointmentMapper.getCurrentPatId(patId);
     }
+
+//    @Override
+//    public List<AppointmentDO> getAppointmentExtermal(String patId) {
+//        List<AppointmentDO> curAppointDOList = new ArrayList<AppointmentDO>();
+//
+//        RestApiReqBodyVo reqBodyVo = new RestApiReqBodyVo();
+//        if (18 == patId.length()) {
+//            reqBodyVo.setSfzh( patId );
+//        } else {
+//            reqBodyVo.setMzzyh(patId);
+//        }
+//
+//        // QueryRisReportList   queryEcgRequest
+//        RestApiResult<AppointmentExternal> result = remoteDataService.httpApi("queryEcgRequest", "ECG", "ECG", reqBodyVo);
+//        if (0 == result.getRow().size()) {
+//            return curAppointDOList;
+//        }
+//
+//        for (int appointIndex=0; appointIndex < result.getRow().size(); appointIndex++ ) {
+//            AppointmentExternal appointmentExternal = result.getRow().get( appointIndex );
+//
+//            AppointmentDO appointmentDO = BeanUtils.toBean(appointmentExternal, AppointmentDO.class);
+//            appointmentDO.setApplyNo( appointmentExternal.getReqIdeApplyno() );
+//            appointmentDO.setEpisodeId( appointmentExternal.getEpisodeID() );
+//            appointmentDO.setPatSrc( getCorrespondingPatientSource(appointmentExternal.getAdmTypeCode()) );
+//            appointmentDO.setPatId(appointmentExternal.getPatientID()); // 鍐呭涓� 韬唤璇佸彿 鎴� 闂ㄨ瘖浣忛櫌鍙�
+//            appointmentDO.setPatName(appointmentExternal.getPatName());
+//            appointmentDO.setPatGender(Byte.valueOf(appointmentExternal.getPatgender()));
+//            appointmentDO.setPatBirthday(DateUtils.ofUTC(appointmentExternal.getEncPatBirthDate()).toLocalDate());
+//            appointmentDO.setPatIdentityId(appointmentExternal.getIdentityID());
+//            appointmentDO.setPatAddr(appointmentExternal.getAddress());
+//            appointmentDO.setPatDeptCode(appointmentExternal.getPatLocDeptCode());
+//            appointmentDO.setPatDeptDesc(appointmentExternal.getPatLocDeptDesc());
+//            appointmentDO.setPatWardCode(appointmentExternal.getPatLocWardCode());
+//            appointmentDO.setPatWardDesc(appointmentExternal.getPatLocWardDesc());
+//            appointmentDO.setPatMobile(appointmentExternal.getPhone());
+//            appointmentDO.setPatBedNo(appointmentExternal.getEnBedno());
+//            appointmentDO.setBookCheckType(getCorrespondingCheckType(appointmentExternal.getPlanDefItemList().getPlanDefItem().getPlanDefItemcode()));
+//            appointmentDO.setBookTime(DateUtils.ofUTC(appointmentExternal.getReqAuthoredOn()));  // 寮�鍗曟椂闂�
+//            appointmentDO.setBookSrc( 0 );
+//
+//            // 鍏夊紑鍗曠殑鎯呭喌锛屾病鏈夐绾︽椂闂�
+//            if (null != appointmentExternal.getReqExtBooktime() ) {
+//                appointmentDO.setBookDate(DateUtils.ofUTC(appointmentExternal.getReqExtBooktime()).toLocalDate());
+//                LocalDateTime bookStartTime = DateUtils.ofUTC(appointmentExternal.getReqExtBooktime());
+//                LocalDateTime bookEndTime = bookStartTime.plusMinutes( ecgConfigService.getTimeslotLength());
+//                appointmentDO.setBookTimeslot((bookStartTime.getHour() * 100 + bookStartTime.getMinute()) * 10000 + bookEndTime.getHour() * 100 + bookEndTime.getMinute());
+//            }
+//
+//            appointmentDO.setPaid( 0 );
+//
+//            curAppointDOList.add( appointmentDO );
+//        }
+//
+//        return curAppointDOList;
+//    }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Integer appoitmentConfirm(AppointmentConfirmReqVO confirmReqVO) {
         AppointmentDO appointment = getAppointment(confirmReqVO.getId());
-        if (null == appointment)
-            throw exception(PATIENT_NOT_EXISTS);
+        if (null == appointment) throw exception(PATIENT_NOT_EXISTS);
 
-        if ( !DateUtils.isToday(appointment.getBookDate()) )
-            throw exception( APPOINTMENT_NOT_TODAY);
+        boolean isVipFlag = (null != confirmReqVO.getIsVip() && 1 == confirmReqVO.getIsVip());
+        boolean forceCheckIn = Boolean.TRUE.equals(confirmReqVO.getForceCheckIn());
+        // 娉ㄦ剰锛氳繖閲岀殑"鏄惁褰撳ぉ"蹇呴』鍦ㄦ敼鍐� bookDate 涔嬪墠鍒ゆ柇锛堜笅闈細鎶婇潪褰撳ぉ鏀逛负褰撳ぉ锛�
+        boolean bookDateIsToday = DateUtils.isToday(appointment.getBookDate());
 
-
-        Integer newSeqNo = null;
-        if (confirmReqVO.getIsVip() == 0) {
-            newSeqNo = queueSequenceService.distributeSeqNo(confirmReqVO.getBookCheckType(), confirmReqVO.getBookTimeslot());
-        } else {
-            newSeqNo = queueSequenceService.distributeVipSeqNo(confirmReqVO.getBookCheckType(), confirmReqVO.getBookTimeslot());
+        // 鎵嬪姩棰勭害 / VIP 鍔犲彿锛氶绾︽棩鏈熺洿鎺ユ寜褰撳ぉ绛惧埌
+        if (isVipFlag) {
+            appointment.setBookDate(LocalDate.now());
+            bookDateIsToday = true;
         }
+
+        // 銆愭湰闇�姹傘�戦潪褰撳ぉ棰勭害鐨勭鍒帮細鍏堟彁閱掋�佺‘璁ゅ悗鍏佽绛惧叆锛堝墠绔脊绐楃‘璁ゅ悗甯� forceCheckIn=true锛�
+        //   鈶� 鏈‘璁わ紙鍓嶇杩樻病璁╃敤鎴风‘璁わ級鈫� 浠嶈繑鍥� APPOINTMENT_NOT_TODAY锛屽墠绔嵁姝ゅ脊銆岄潪褰撳ぉ锛屾槸鍚︾鍒般�嶏紱
+        //   鈶� 宸茬‘璁� 鈫� 鍏佽绛惧叆锛屽苟涓�**寮哄埗璧伴鐣欏彿閫氶亾**锛岄伩鍏嶅崰鐢ㄥ綋澶╁悓鏃舵鐨勬甯搁绾﹀彿锛�
+        //   鈶� 棰勭害鏃ユ湡鏀瑰啓涓哄綋澶╋細闃熷垪/澶у睆/宸ヤ綔鍙伴兘鎸� book_date = 浠婂ぉ 杩囨护锛�
+        //      鑻ヤ繚鐣欏師鏃ユ湡锛屾偅鑰呯鍏ュ悗涓嶄細鍑虹幇鍦ㄤ换浣曞垪琛ㄩ噷銆�
+        if (!bookDateIsToday) {
+            if (!forceCheckIn) {
+                throw exception(APPOINTMENT_NOT_TODAY);
+            }
+            log.info("[appoitmentConfirm] 闈炲綋澶╅绾︾鍏ワ紙鏀圭敤棰勭暀鍙凤紝棰勭害鏃ユ湡鏀逛粖澶╋級. appointmentId={} 鍘熼绾︽棩鏈�={} checkType={} timeslot={}",
+                    appointment.getId(), appointment.getBookDate(), confirmReqVO.getBookCheckType(),
+                    confirmReqVO.getBookTimeslot());
+            appointment.setBookDate(LocalDate.now());
+        }
+
+        // 銆愰渶姹傦細鎺掗槦搴忓彿瑙勫垯銆戞寜 check_type.seq_rule 鍐冲畾鍙栧彿鏂瑰紡锛�
+        //   0=棰勭害鍙凤紙鐢ㄩ绾︾郴缁熷凡涓嬪彂鐨勫彿锛涘綋鍓� HIS 鏈笅鍙戯紝浼氳嚜鍔ㄥ洖閫�鍒扮鍒板簭鍙凤級
+        //   1=绛惧埌搴忓彿锛堝綋澶╁悓妫�鏌ョ被鍨嬭繛缁�掑锛屾瘡 9 涓鐣� 1 涓粰鍔犳�ワ級
+        //   2=鏃舵鍙凤紙鎸� bookTimeslot 浠庡彿姹犲彇鍙� 鈥斺�� 鏀归�犲墠鐨勫師鏈夎涓猴紝涔熸槸鏈厤缃椂鐨勯粯璁わ級
+        //
+        // 銆愭湰闇�姹傦細鎸夋椂娈甸绾︺�戝鍔� VIP/鍔犲彿鏍囪锛�
+        //   路 VIP/鍔犲彿/闈炲綋澶╃鍏� 鈫� 鏈棰勭暀鍙� 鈫� 鎬婚鐣欐睜锛坱ime_slot=0锛�
+        //   路 鏅��               鈫� 鏈鏅�氬彿 鈫� 鏈棰勭暀鍙� 鈫� 鎬婚鐣欐睜
+        //   娌℃湁棰勭害鏃舵锛坆ookTimeslot 涓虹┖/0/瀛楀吀澶栵級鏃朵篃鑳界鍒帮細鐩存帴钀藉埌鎬婚鐣欐睜銆�
+        boolean urgent = isHisUrgent(appointment);
+        boolean vip = isVipFlag || urgent || !bookDateIsToday;
+        Integer newSeqNo = queueSequenceService.distributeSeqNoByRule(
+                confirmReqVO.getBookCheckType(),
+                confirmReqVO.getBookTimeslot(),
+                appointment.getBookSeqNum(),
+                urgent,
+                vip);
+        log.info("[appoitmentConfirm][鍒嗛厤鎺掗槦搴忓彿] appointmentId={} checkType={} timeslot={} 瑙勫垯鍙栧彿={} 鍔犳��={} VIP/鍔犲彿={}",
+                appointment.getId(), confirmReqVO.getBookCheckType(), confirmReqVO.getBookTimeslot(),
+                newSeqNo, urgent, vip);
 
         try {
             QueueSaveReqVO queueSaveReqVO = new QueueSaveReqVO();
+            queueSaveReqVO.setApplyNo(appointment.getApplyNo());
+            queueSaveReqVO.setEpisodeId(appointment.getEpisodeId());
+            queueSaveReqVO.setAppointId(appointment.getId());
             queueSaveReqVO.setPatId(appointment.getPatId());
             queueSaveReqVO.setPatName(appointment.getPatName());
             queueSaveReqVO.setPatGender(appointment.getPatGender());
             queueSaveReqVO.setBookDate(appointment.getBookDate());
-            queueSaveReqVO.setBookTimeslot(appointment.getBookTimeslot());
+            queueSaveReqVO.setBookTimeslot(confirmReqVO.getBookTimeslot()); /*appointment.getBookTimeslot()*/
             queueSaveReqVO.setBookCheckType(appointment.getBookCheckType());
-            queueSaveReqVO.setBookSeqNum( newSeqNo );
-            queueSaveReqVO.setIsVip( confirmReqVO.getIsVip() );
+            // 銆愬彿缁熶竴锛堟湰闇�姹傦級銆戠鍒版椂鍙栫殑鍙�**鐩存帴鍐欏叆 seq_num**锛堜笉鍐嶅啓 book_seq_num锛夈��
+            // 涔嬪悗浠讳綍鐜妭閮戒笉鍐嶆敼杩欎釜鍙凤細鍒嗘祦銆佽繃鍙枫�佸彫鍥炪�佸姞鎬ャ�佽鏈哄潎鍙敼鐘舵��/宸ヤ綅銆�
+            // 鍙栧彿瑙勫垯浠嶇敱 check_type.seq_rule 鍐冲畾锛堥绾﹀彿 / 绛惧埌搴忓彿 / 鏃舵鍙凤級锛岃 distributeSeqNoByRule銆�
+            queueSaveReqVO.setSeqNum(newSeqNo);
+            queueSaveReqVO.setIsVip(confirmReqVO.getIsVip());
             queueSaveReqVO.setPassed((byte) 0);
             queueSaveReqVO.setExpired((byte) 0);
-            queueSaveReqVO.setPatDetails( appointment.getPatDeptDesc() + "-" + appointment.getPatWardDesc() + "-" + appointment.getPatBedNo());
+            // 闇�姹�2锛欼SCB銆屾槸鍚﹀簥杈广�嶉殢闃熷垪涓�璧疯惤搴擄紝渚涘睆骞�/椤甸潰灞曠ず
+            queueSaveReqVO.setIsBedside(appointment.getIsBedside());
+            // HIS 鍔犳�ユ爣璁帮紙jjbz=J锛夆啋 鎺掗槦鍔犳�ワ紝涓庝汉宸ユ彃闃熷叡鐢� jump_flag銆�
+            // 銆愬彿缁熶竴鍚庛�戝姞鎬�**涓嶆敼鎺掗槦鍙�**锛堝彿鍦ㄤ笅闈� setSeqNum 鏃跺凡缁忓畾姝伙級锛�
+            // 鑰屾槸闈犳帓搴忎綋鐜帮細鍒嗘祦鍙栦汉銆佸彨鍙峰彇浜恒�佸伐浣滃彴/璇婇棿灞�/澶у睆鍒楄〃閮芥槸
+            // "jump_flag desc 浼樺厛 鈫� seq_num 鍗囧簭"锛岀疆 1 鍗虫帓鍒版渶鍓嶃��
+            if (urgent) {
+                queueSaveReqVO.setJumpFlag((byte) 1);
+                log.info("[appoitmentConfirm][HIS 鍔犳�ユ偅鑰咃紝鎺掗槦鏍囪涓哄姞鎬 applyNo={} patId={}",
+                        appointment.getApplyNo(), appointment.getPatId());
+            }
+            queueSaveReqVO.setPatDetails(getPatDetails(appointment));
             queueService.queue(queueSaveReqVO);
         } catch (DuplicateKeyException duplicateKeyException) {
             throw exception(APPOINTMENT_HAVE_QUEUED);
@@ -242,51 +501,1848 @@
         return newSeqNo;
     }
 
-    private Integer getCorrespondingCheckType(String strPlanDefItemcode) {
-        if (strPlanDefItemcode.equals("691133607"))
-            return 100;
-        else if (strPlanDefItemcode.equals("201605"))
-            return 200;
-        else if (strPlanDefItemcode.equals("200327"))
-            return 300;
-        else if (strPlanDefItemcode.equals("201652"))
-            return 400;
-        else if (strPlanDefItemcode.equals("502490914"))
-            return 500;
-        else if (strPlanDefItemcode.equals("419562119"))
-            return 600;
-        else if (strPlanDefItemcode.equals("201604"))
-            return 700;
-        else if (strPlanDefItemcode.equals("1202042"))
-            return 800;
-        else if (strPlanDefItemcode.equals("1202058"))
-            return 900;
-        else if (strPlanDefItemcode.equals("1202065"))
-            return 1000;
-        else if (strPlanDefItemcode.equals("559542128"))
-            return 1100;
-        else if (strPlanDefItemcode.equals("590244511"))
-            return 1200;
-        else if (strPlanDefItemcode.equals("666454217"))
-            return 1300;
-        else if (strPlanDefItemcode.equals("720791490"))
-            return 1400;
-        else if (strPlanDefItemcode.equals("720792077"))
-            return 1500;
-
-        return 100;
+    /**
+     * 鍒ゆ柇璇ラ绾︽槸鍚︿负 HIS 鏍囪鐨勫姞鎬ュ崟銆�
+     * <p>
+     * 渚濇嵁 {@code appointment.jjbz}锛堟潵婧� HIS 鎶ユ枃 {@code zdy.jjbz}锛夛細
+     * {@code J} / {@code 1} / {@code Y} 瑙嗕负鍔犳�ワ紝鍏朵綑锛堝 {@code N}锛変负甯歌銆�
+     * <p>
+     * <b>娉ㄦ剰</b>锛氬疄娴� 2026-09-17 鏃ュ織涓� 60 鏉� S0201ECG 鐨� {@code jjbz} **鍏ㄤ负 N**锛�
+     * 鏈彇寰� {@code J} 鐨勭湡瀹炴牱渚嬶紝鍥犳璇ュ垎鏀皻鏈粡杩囩湡瀹炴姤鏂囬獙璇併��
+     *
+     * @param appointment 棰勭害璁板綍锛屽彲涓� null
+     * @return true=鍔犳��
+     */
+    private boolean isHisUrgent(AppointmentDO appointment) {
+        if (appointment == null || StringUtils.isEmpty(appointment.getJjbz())) {
+            return false;
+        }
+        String flag = appointment.getJjbz().trim();
+        return "J".equalsIgnoreCase(flag) || "1".equals(flag) || "Y".equalsIgnoreCase(flag);
     }
 
-    private Integer getCorrespondingPatientSource(String admTypeCode) {
-        if (admTypeCode.equals("AMB"))
-            return 1;
-        else if (admTypeCode.equals("EMER"))
-            return 2;
-        else if (admTypeCode.equals("IMP"))
-            return 3;
-        else if (admTypeCode.equals("PHY"))
-            return 4;
-
-        return 0;
+    private PatDetails getPatDetails(AppointmentDO appointment) {        PatDetails patDetails = new PatDetails();
+        patDetails.setAppointmentId(appointment.getId());
+        patDetails.setId(appointment.getPatId());
+        patDetails.setName(appointment.getPatName());
+        patDetails.setMobile(appointment.getPatMobile());
+        patDetails.setDeptCode(appointment.getPatDeptCode());
+        patDetails.setDeptDesc(appointment.getPatDeptDesc());
+        patDetails.setWardCode(appointment.getPatWardCode());
+        patDetails.setWardDesc(appointment.getPatWardDesc());
+        patDetails.setBedNo(appointment.getPatBedNo());
+        patDetails.setSource(appointment.getPatSrc());
+        return patDetails;
     }
-}
\ No newline at end of file
+
+    @Override
+    public Long handleAppointmentCreate(Map<String, Object> dataMap) {
+        if (dataMap == null) {
+            throw exception(APPOINTMENT_CREATE_FAIL);
+        }
+
+        try {
+            // 鍒涘缓AppointmentDO瀵硅薄
+            AppointmentDO appointment = new AppointmentDO();
+
+            // 浠巇ataMap涓彁鍙栨暟鎹�
+            Map<String, Object> encounter = extractEncounter(dataMap);
+            if (encounter == null) {
+                throw exception(APPOINTMENT_CREATE_FAIL);
+            }
+
+            // 璁剧疆灏辫瘖娴佹按鍙�
+            Optional.ofNullable(encounter.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(1)).map(item -> (String) item.get("extension")).ifPresent(appointment::setEpisodeId);
+
+            Optional.ofNullable(encounter.get("location")).map(location -> (Map<String, Object>) location).map(location -> (Map<String, Object>) location.get("serviceDeliveryLocation")).map(serviceDeliveryLocation -> (Map<String, Object>) serviceDeliveryLocation.get("location")).map(location -> (Map<String, Object>) location.get("id")).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(appointment::setPatBedNo);
+
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("zdy")).map(zdy -> {
+                Object ISDN = zdy.get("ISDN");
+                if (ISDN != null) {
+                    return ISDN.toString();
+                }
+                return null;
+            }).ifPresent(appointment::setTolerance);
+
+            // 鏄惁搴婅竟锛堥渶姹�2锛夛細zdy.ISCB銆�0=鍚︼紝1=鏄��
+            //
+            // 涓� ISDN 鍚屽湪 zdy 鑷畾涔夊瓧娈靛尯锛屼笖**鍚屽睘瀹樻柟鏂囨。鏈畾涔夌殑 7 涓墿灞曞瓧娈�**
+            // 锛堝疄娴嬬湡瀹炴姤鏂� 26 涓瓧娈碉紝鏂囨。鍙畾涔変簡 19 涓級銆�
+            // 瑙f瀽閫昏緫鎶藉埌 findBedsideFlag锛屼究浜庡崟鍏冩祴璇曘��
+            String bedsideFlag = findBedsideFlag(dataMap);
+            if (bedsideFlag != null) {
+                appointment.setIsBedside(bedsideFlag);
+            }
+
+            // 浼樺厛绾�/鍔犳�ユ爣璁帮紙zdy.jjbz锛歂=甯歌锛孞=鍔犳�ワ級
+            Map<String, Object> zdyNode = dig(dataMap, "controlActProcess", "subject",
+                    "observationRequest", "zdy");
+            if (zdyNode != null) {
+                Object jjbz = zdyNode.get("jjbz");
+                if (jjbz != null && StringUtils.isNotEmpty(jjbz.toString().trim())) {
+                    appointment.setJjbz(jjbz.toString().trim());
+                }
+            }
+
+            // 妫�鏌ユ柟寮忕紪鐮侊細methodCode.item[0].code
+            // 绫诲瀷瀹夊叏锛歮ethodCode 鐨� item 鍙兘涓嶅瓨鍦紝鍙兘鏄崟涓� Map锛屼篃鍙兘鏄� List
+            Map<String, Object> c2 = dig(dataMap, "controlActProcess", "subject",
+                    "observationRequest", "component2", "observationRequest");
+            if (c2 != null) {
+                String methodCode = digText(c2, "methodCode", "item", "code");
+                if (StringUtils.isEmpty(methodCode)) {
+                    // 浼� List 鏃� digText 浼氳嚜鍔ㄥ彇棣栧厓绱犲啀涓嬮捇锛岃繖閲屽啀鍏滀竴娆� code
+                    methodCode = digText(c2, "methodCode", "item", "part", "value");
+                }
+                if (StringUtils.isNotEmpty(methodCode)) {
+                    appointment.setMethodCode(methodCode);
+                }
+                String targetSite = digText(c2, "targetSiteCode", "item", "code");
+                if (StringUtils.isNotEmpty(targetSite)) {
+                    appointment.setTargetSiteCode(targetSite);
+                }
+
+                // 棰勭害鏃堕棿/鏃堕棿娈�/璇婂锛圫0201ECG 鎶ユ枃鐨� component2.observationRequest.ZDY锛屽疄娴嬪舰濡傦細
+                //   ZDY={time=2026-10-06 08:00-08:30, area=..., roomname=6鍙疯瘖瀹�(鍘呭彸渚�), roomcode=, jcxmcode=201652, jcxmname=骞虫澘杩愬姩璇曢獙}
+                // 鍘熷疄鐜板彧鎶婂畠褰�"妫�鏌ョ被鍨�"鐢紝娌¤В鏋愰绾︿俊鎭紝瀵艰嚧杩欐潯娴佸缓鍑烘潵鐨勮褰�
+                // book_date / book_timeslot / book_time / room_name 鍏ㄤ负绌猴紙瀹炴祴 211741銆�211962 灏辨槸杩欑鍗婃潯鏁版嵁锛夈��
+                Object zdyRaw = c2.get("ZDY");
+                if (zdyRaw == null) {
+                    zdyRaw = c2.get("zdy");
+                }
+                if (zdyRaw instanceof Map) {
+                    Map<String, Object> zdyMap = (Map<String, Object>) zdyRaw;
+                    Object timeObj = zdyMap.get("time");
+                    String timeRange = timeObj == null ? null : timeObj.toString();
+                    Integer timeslot = parseBookTimeslot(timeRange);
+                    if (timeslot != null) {
+                        appointment.setBookTimeslot(timeslot);
+                        // 鐢辨椂闂存璧峰鎺ㄧ畻棰勭害鏃跺埢锛堝 8000830 鈫� 08:00锛夛紱book_date 瑙佷笅鏂� 鈶� 鐨勭粺涓�鎺ㄥ
+                        LocalTime startTime = timeOfTimeslot(timeslot);
+                        if (startTime != null) {
+                            appointment.setBookTime(LocalDateTime.of(LocalDate.now(), startTime));
+                        }
+                    } else {
+                        // 鏃堕棿娈佃В鏋愪笉鍑烘潵鏃讹紝閫�鍥炵敤 effectiveTime锛堟姤鏂囬噷鍙︿竴涓绾︽椂鍒诲瓧娈碉級
+                        LocalDateTime bookTime = parseDateTime(digText(c2, "effectiveTime", "any", "value"));
+                        if (bookTime != null) {
+                            appointment.setBookTime(bookTime);
+                            appointment.setBookDate(bookTime.toLocalDate());
+                        }
+                    }
+                    Object roomName = zdyMap.get("roomname");
+                    if (roomName != null && StringUtils.isNotEmpty(roomName.toString())) {
+                        appointment.setRoomName(truncate(roomName.toString(), ROOM_NAME_MAX_LEN));
+                    }
+                }
+
+                // 鍖诲槺鍙凤細component2.observationRequest.id.item[root=2.16.156.10011.1.28].extension
+                //
+                // 渚濇嵁锛圚IS銆婁氦浜掓湇鍔¢�氱敤鎺ュ彛鏂囨。銆嬶級锛�
+                //   路 6.1 鐢宠鍗曟柊澧炴湇鍔★紙鏈」鐩墍鐢� S0201ECG锛夌殑绀轰緥涓鑺傜偣娉ㄩ噴鍗充负銆屽尰鍢盜D銆嶏細
+                //       <id><!--鍖诲槺ID--><item root="2.16.156.10011.1.28" extension=""/></id>
+                //   路 6.4/6.5 妫�鏌ラ绾︾姸鎬佹湇鍔$殑绀轰緥涓悓鏍锋敞閲婁负銆屽尰鍢卞彿銆嶏細
+                //       <!--  鍖诲槺鍙�  --> ... <id root="2.16.156.10011.1.28" extension="11"/>
+                //   瀹炴祴 S0201ECG 220 鏉°�丼0202ECG 29 鏉�**鍧囨惡甯﹁鍊间笖鏃犵┖鍊�**
+                //   锛堝舰濡� 112663505 / 112663700锛屼笖鍚屼竴鍖诲槺鍦ㄦ柊澧炰笌鏇存柊鎶ユ枃涓彇鍊间竴鑷达級銆�
+                //
+                // 銆愪负浠�涔堜笉鐢� digText 鐩存帴鍙� item.extension銆�
+                //   digText 閬囧埌 List **鍙彇棣栧厓绱�**锛涜�� id.item 鍦ㄩ儴鍒嗘湇鍔′腑鏄暟缁�
+                //   锛堝 6.4 鍚屾椂鍚� 1.24 鐢宠鍗曞彿涓� 1.28 鍖诲槺鍙凤級銆傛晠蹇呴』**鎸� root 鍖归厤**锛�
+                //   鍚﹀垯鍙兘鎶婄敵璇峰崟鍙峰綋鎴愬尰鍢卞彿銆�
+                String doctorAdviceNo = findExtensionByRoot(c2.get("id"), ORDER_NO_ROOT);
+                if (StringUtils.isNotEmpty(doctorAdviceNo)) {
+                    appointment.setDoctorAdviceNo(doctorAdviceNo);
+                }
+            }
+
+            // 鍖荤敓寮�鍗曟椂闂达紙鎶ユ枃璺緞锛歝ontrolActProcess.subject.observationRequest.author.time.value锛�
+            //
+            // 銆愪负浠�涔堜笉鐢ㄤ竴琛� Optional 閾剧洿鎺� parse銆�
+            // 鍘熷疄鐜板啓姝� `SimpleDateFormat("yyyyMMddHHmmss")`锛屾牸寮忎笉绗﹀彧浼� **闈欓粯涓㈠純**
+            // 锛坈atch 閲屼粎 printStackTrace锛屼笉钀芥棩蹇楀瓧娈碉級鈥斺�旂嚎涓婂嚭鐜伴棶棰樻椂鏃犱粠瀹氫綅銆�
+            // 鐜版敼涓烘樉寮忓垎鏀� + 缁撴瀯鍖� WARN 鏃ュ織锛岃嚦灏戞妸銆屾姤鏂囧師鍊� / 鐢宠鍗曞彿銆嶈涓嬫潵銆�
+            String doctorTimeRaw = digText(dataMap, "controlActProcess", "subject",
+                    "observationRequest", "author", "time", "value");
+            // 璇ュ灏氭湭鎵ц鍒颁笅鏂圭殑 setApplyNo锛堝師 L575锛夛紝鏁呭崟鐙彇涓�娆$敵璇峰崟鍙凤紝浠呯敤浜庢棩蹇楀畾浣�
+            String applyNoForLog = digText(dataMap, "controlActProcess", "subject",
+                    "observationRequest", "id", "item", "extension");
+            if (StringUtils.isNotEmpty(doctorTimeRaw)) {
+                // 鍘熸牱閲囩撼 HIS 鎵�鍙戠殑鍊硷紙涓嶅仛鍙栬垗锛岄伩鍏嶈鍒ゅ弽鑰屽啓鍧忔暟鎹級銆�
+                // 鑻ヤ负銆屾棩鏈熷�笺�嶏紙鏃跺垎绉�=000000锛屽疄娴嬬害 61%锛夛紝parseHisDoctorTime 浼氭墦 WARN
+                // 璁板綍鍘熷鎶ユ枃鍊� + 鐢宠鍗曞彿锛屼究浜庡悜 HIS 渚у弽棣堬紱鍐欏叆琛屼负涓庢敼鍔ㄥ墠涓�鑷淬��
+                java.util.Date parsedDoctorTime = parseHisDoctorTime(doctorTimeRaw, applyNoForLog);
+                if (parsedDoctorTime != null) {
+                    appointment.setDoctorTime(parsedDoctorTime);
+                }
+            } else {
+                log.warn("[handleAppointmentCreate][鎶ユ枃缂哄皯寮�鍗曟椂闂� author/time/value锛宒octor_time 灏嗕负绌篯 applyNo={}",
+                        applyNoForLog);
+            }
+
+            //璁剧疆寮�鍗曞尰鐢熷鍚�
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("author")).map(author -> (Map<String, Object>) author.get("assignedEntity")).map(assignedEntity -> (Map<String, Object>) assignedEntity.get("assignedPerson")).map(assignedPerson -> (Map<String, Object>) assignedPerson.get("name")).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(appointment::setDoctor);
+
+            //璁剧疆寮�鍗曞尰鐢熺紪鐮�
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("author")).map(author -> (Map<String, Object>) author.get("assignedEntity")).map(assignedEntity -> (Map<String, Object>) assignedEntity.get("id")).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(appointment::setDoctorCode);
+
+
+            // 璁剧疆鎮h�呮潵婧愮被鍨�
+            Optional.ofNullable(encounter.get("code")).map(code -> (Map<String, Object>) code).map(code -> (String) code.get("code")).map(Integer::parseInt).ifPresent(appointment::setPatSrc);
+
+            // 璁剧疆鎮h�呬俊鎭�
+            Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("patientPerson")).ifPresent(patientPerson -> {
+                // 璁剧疆鎮h�匢D
+                Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(1)).map(item -> (String) item.get("extension")).ifPresent(appointment::setPatId);
+
+                // 璁剧疆鎮h�呴棬璇婂彿
+                Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(2)).map(item -> (String) item.get("extension")).ifPresent(appointment::setOutpatientNo);
+
+                // 璁剧疆鎮h�呬綇闄㈠彿
+                Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(3)).map(item -> (String) item.get("extension")).ifPresent(appointment::setHospitalNo);
+
+                // 璁剧疆鎮h�呭鍚�
+                Optional.ofNullable(patientPerson.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(appointment::setPatName);
+
+                // 璁剧疆鎮h�呮墜鏈哄拰鐢佃瘽
+                Optional.ofNullable(patientPerson.get("telecom")).map(telecom -> (Map<String, Object>) telecom).map(telecom -> (Map<String, Object>) telecom.get("item")).map(item -> (String) item.get("value")).ifPresent(phone -> {
+                    appointment.setPatMobile(phone);
+                    appointment.setPatPhone(phone);
+                });
+
+                // 璁剧疆鎮h�呮�у埆
+                Optional.ofNullable(patientPerson.get("administrativeGenderCode")).map(genderCode -> (Map<String, Object>) genderCode).map(genderCode -> (String) genderCode.get("code")).map(Byte::parseByte).ifPresent(appointment::setPatGender);
+
+                // 璁剧疆鎮h�呯敓鏃�
+                Optional.ofNullable(patientPerson.get("birthTime")).map(birthTime -> (Map<String, Object>) birthTime).map(birthTime -> (String) birthTime.get("value")).map(birthdayStr -> LocalDate.parse(birthdayStr, DateTimeFormatter.ofPattern("yyyyMMdd"))).ifPresent(appointment::setPatBirthday);
+
+                // 璁剧疆鎮h�呭湴鍧�
+                Optional.ofNullable(patientPerson.get("addr")).map(addr -> (Map<String, Object>) addr).map(addr -> (Map<String, Object>) addr.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(appointment::setPatAddr);
+
+                // 璁剧疆鎮h�呰韩浠借瘉鍙�
+                Optional.ofNullable(patientPerson.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> !items.isEmpty()).map(items -> (Map<String, Object>) items.get(0)).map(item -> (String) item.get("extension")).ifPresent(appointment::setPatIdentityId);
+
+                //鎮h�呭尰淇濆崱
+                Optional.ofNullable(patientPerson.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> !items.isEmpty()).map(items -> (Map<String, Object>) items.get(1)).map(item -> (String) item.get("extension")).ifPresent(appointment::setMedicalCard);
+            });
+
+            // 璁剧疆鏀粯鐘舵��
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("zdy")).map(zdy -> {
+                Object payStatus = zdy.get("PayStatus");
+                if (payStatus != null) {
+                    try {
+                        return Integer.parseInt(payStatus.toString());
+                    } catch (NumberFormatException e) {
+                        log.warn("PayStatus杞崲澶辫触: {}", payStatus);
+                        return null;
+                    }
+                }
+                return null;
+            }).ifPresent(appointment::setPaid);
+            // 璁剧疆鐢宠鍗曞彿
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("id")).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(appointment::setApplyNo);
+
+            // 棰勭害鏃ユ湡锛氭姤鏂囬噷鐨勯绾︽椂闂存 ZDY.time 褰㈠ "2026-10-06 08:00-08:30"銆�
+            // 涓婇潰瑙f瀽鏃堕棿娈垫椂鍙嬁鍒颁簡鏃跺垎锛堢敤浜� book_timeslot/book_time锛夛紝杩欓噷琛ユ棩鏈燂細
+            // 鑻ュ彧鎷垮埌鏃堕棿娈佃�屾棩鏈熶负绌猴紝灏变粠 ZDY.time 鐨勬棩鏈熼儴鍒嗚ˉ涓婏紝骞舵嵁姝や慨姝� book_time 鐨勬棩鏈熴��
+            String zdyTimeRange = digText(dataMap, "controlActProcess", "subject", "observationRequest",
+                    "component2", "observationRequest", "ZDY", "time");
+            if (StringUtils.isEmpty(zdyTimeRange)) {
+                zdyTimeRange = digText(dataMap, "controlActProcess", "subject", "observationRequest",
+                        "component2", "observationRequest", "zdy", "time");
+            }
+            if (StringUtils.isNotEmpty(zdyTimeRange)) {
+                String[] parts = zdyTimeRange.trim().split("\\s+");
+                if (parts.length >= 1) {
+                    LocalDateTime parsedDate = parseDateTime(parts[0] + " 00:00");
+                    if (parsedDate != null) {
+                        LocalDate zdyBookDate = parsedDate.toLocalDate();
+                        if (appointment.getBookDate() == null) {
+                            appointment.setBookDate(zdyBookDate);
+                        }
+                        // book_time 涔嬪墠鍙槸鐢�"浠婂ぉ"鍗犱綅锛岃繖閲屾崲鎴愮湡瀹為绾︽棩鏈�
+                        if (appointment.getBookTime() != null) {
+                            appointment.setBookTime(appointment.getBookTime().withYear(zdyBookDate.getYear())
+                                    .withMonth(zdyBookDate.getMonthValue()).withDayOfMonth(zdyBookDate.getDayOfMonth()));
+                        }
+                    }
+                }
+            }
+
+            //璁剧疆妫�鏌ョ被鍨�
+//            String checkType = Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("component2")).map(component2 -> (Map<String, Object>) component2.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("code")).map(code -> (String) code.get("code")).orElseThrow(() -> exception(APPOINTMENT_CREATE_FAIL));
+            String checkType = Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("component2")).map(component2 -> (Map<String, Object>) component2.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("code")).map(code -> (String) code.get("code")).orElseThrow(() -> exception(APPOINTMENT_CREATE_FAIL));
+            String checkName = Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("component2")).map(component2 -> (Map<String, Object>) component2.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("code")).map(code -> (Map<String, Object>) code.get("displayName")).map(displayName -> (String) displayName.get("value")).orElseThrow(() -> exception(APPOINTMENT_CREATE_FAIL));
+            // 渚濇嵁妫�鏌ョ被鍨嬭В鏋愭湰鍦� book_check_type銆�
+            // 淇鐐癸紙鍘熷疄鐜版湁缂洪櫡锛夛細
+            //   1. 鍒ゆ柇鍐欐垚 `checkType != null || checkType != ""`锛屾亽涓� true锛�
+            //   2. patSrc 涓� Integer锛宍!= 4` 鍦ㄨ秴鍑� Integer 缂撳瓨鑼冨洿鏃朵細澶辨晥锛屾敼鐢� Objects.equals锛�
+            //   3. HIS 浠g爜鍙兘鏄犲皠涓嶅埌锛岀洿鎺� Integer.valueOf(null) 浼氭姏
+            //      NumberFormatException锛堝疄娴嬫棩蹇椾腑鏈� "Cannot parse null string"锛夈��
+            if (Objects.equals(appointment.getPatSrc(), 4)) {
+                String normalizedName = StringUtils.isNotEmpty(checkName)
+                        ? checkName.replaceAll("\\s+", "") : null;
+                String mapped = StringUtils.isNotEmpty(normalizedName)
+                        ? HisCheckNameEnum.getByName(normalizedName) : null;
+                Integer value = parseIntegerQuietly(mapped);
+                if (value != null) {
+                    appointment.setBookCheckType(value);
+                } else {
+                    // 鍚嶇О鏄犲皠涓嶅埌灏遍��鍥炪�屾寜 HIS 妫�鏌ヤ唬鐮佹槧灏勩�嶏細
+                    // 瀹炴祴杩愬姩璇曢獙鍦� HIS 渚ф敼杩囧悕锛堝钩鏉胯繍鍔ㄥ績鐢� 鈫� 骞虫澘杩愬姩璇曢獙锛夛紝
+                    // 鍙鍚嶇О浼氳 book_check_type 钀界┖锛涜�屾姤鏂囬噷鐨� code锛堝 201652锛夋槸绋冲畾鐨勩��
+                    Integer fallback = resolveBookCheckTypeFromComponent2(dataMap);
+                    if (fallback != null) {
+                        appointment.setBookCheckType(fallback);
+                        log.info("[handleAppointmentCreate][浣撴鎮h�呭悕绉版湭鏄犲皠锛屽凡鎸夋鏌ヤ唬鐮佸厹搴昡 checkName={} bookCheckType={}",
+                                checkName, fallback);
+                    } else {
+                        log.warn("[handleAppointmentCreate][浣撴鎮h�呮鏌ラ」鐩悕绉颁笌浠g爜鍧囨棤娉曟槧灏刔 checkName={}", checkName);
+                    }
+                }
+            } else {
+                Integer value = resolveBookCheckTypeFromComponent2(dataMap);
+                if (value != null) {
+                    appointment.setBookCheckType(value);
+                } else {
+                    log.warn("[handleAppointmentCreate][HIS 妫�鏌ヤ唬鐮佹棤娉曟槧灏勫埌鏈湴妫�鏌ョ被鍨媇 checkType={}", checkType);
+                }
+            }
+
+
+            // 璁剧疆绉戝鍜岀梾鍖轰俊鎭�
+            Optional.ofNullable(encounter.get("location")).map(location -> (Map<String, Object>) location).map(location -> (Map<String, Object>) location.get("serviceDeliveryLocation")).map(location -> (Map<String, Object>) location.get("serviceProviderOrganization")).ifPresent(org -> {
+                // 璁剧疆绉戝浠g爜鍜屽悕绉�
+                Optional.ofNullable(org.get("id")).map(id -> (Map<String, Object>) id).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(appointment::setPatDeptCode);
+
+                Optional.ofNullable(org.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(appointment::setPatDeptDesc);
+
+                // 璁剧疆鐥呭尯浠g爜鍜屽悕绉�
+                Optional.ofNullable(org.get("asOrganizationPartOf")).map(wardOrg -> (Map<String, Object>) wardOrg).map(wardOrg -> (Map<String, Object>) wardOrg.get("wholeOrganization")).ifPresent(wardOrg -> {
+                    Optional.ofNullable(wardOrg.get("id")).map(id -> (Map<String, Object>) id).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(appointment::setPatWardCode);
+                    if (appointment.getPatSrc() != null && appointment.getPatSrc() == 4) {
+                        //浣撴灏辫繖涓�涓瀹�
+                        appointment.setPatDeptDesc("鍋ュ悍绠$悊涓績");
+                    } else {
+                        Optional.ofNullable(wardOrg.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(appointment::setPatWardDesc);
+                    }
+                });
+            });
+
+
+            // 灏咥ppointmentDO杞崲涓篈ppointmentSaveReqVO骞朵繚瀛�
+            AppointmentSaveReqVO saveReqVO = BeanUtils.toBean(appointment, AppointmentSaveReqVO.class);
+
+            // 鎸夌敵璇峰崟鍙锋鏌ユ槸鍚﹀凡瀛樺湪銆�
+            // 銆愭湰娆℃敼鍔� Q5銆戝師瀹炵幇鍛戒腑宸插瓨鍦ㄦ椂鐩存帴 return锛堜笉鏇存柊锛夛紝
+            // 涓� S0202ECG / S0405 涓ゆ潯鏇存柊娴佺殑"鎵句笉鍒板垯鍏滃簳鏂板"鏂瑰悜鐩稿弽锛岃涔変笉缁熶竴銆�
+            // 鐜版敼涓猴細鍛戒腑宸插瓨鍦ㄥ垯浠ユ湰娆℃姤鏂囧唴瀹规墽琛屾洿鏂帮紝閬垮厤 HIS 閲嶅鎺ㄩ�佹椂鏃ф暟鎹笉琚慨姝c��
+            AppointmentDO appointmentDO = appointmentMapper.getByApplyNo(saveReqVO.getApplyNo());
+            log.info("[handleAppointmentCreate][鏄惁宸插瓨鍦╙ applyNo={} existingId={}",
+                    saveReqVO.getApplyNo(), appointmentDO == null ? null : appointmentDO.getId());
+            if (appointmentDO != null) {
+                // 淇濈暀鍘熻褰曚腑鏈鎶ユ枃鏈彁渚涚殑瀛楁锛岄伩鍏嶈绌哄�艰鐩�
+                mergeUnsetFields(appointment, appointmentDO);
+                // 鍚堝苟缁撴灉瑕佸洖鍒� saveReqVO 涓婏紝鍚﹀垯鏇存柊/鎻掑叆鐢ㄧ殑杩樻槸"鏈悎骞�"鐨勬姤鏂囧��
+                saveReqVO = BeanUtils.toBean(appointment, AppointmentSaveReqVO.class);
+            }
+            // 浜ょ粰鍘熷瓙钀藉簱锛氫笉瀛樺湪鍒欐彃鍏ワ紝宸插瓨鍦ㄥ垯鏇存柊锛涘苟鍙戦噸澶嶆彃鍏ヤ細琚敮涓�绱㈠紩鎷︿笅骞惰浆涓烘洿鏂帮紝
+            // 涓嶄細鍐嶅嚭鐜�"鍚屼竴 apply_no 涓ゆ潯璁板綍"锛堝師瀹炵幇鍦ㄨ繖閲�"鏌ヤ竴娆″啀鎻�"锛屽苟鍙戜笅蹇呯劧婕忥級銆�
+            return atomicSaveByApplyNo(saveReqVO);
+        } catch (Exception e) {
+            log.error("[handleAppointmentCreate][澶勭悊棰勭害鍒涘缓澶辫触 dataMap({})]", dataMap, e);
+            throw exception(APPOINTMENT_CREATE_FAIL);
+        }
+    }
+
+    /**
+     * 鏇存柊鍓嶅悎骞讹細鏈鎶ユ枃鏈彁渚涳紙涓� null锛夌殑瀛楁锛屾部鐢ㄦ暟鎹簱涓殑鍘熷�笺��
+     * <p>
+     * 鐩殑锛歿@code S0201ECG} 鐨勬煇浜涘瓧娈靛湪閮ㄥ垎鍦烘櫙涓嬩細缂哄け锛堜緥濡備綋妫�鎮h�呮棤棰勭害鏃堕棿銆�
+     * 搴婂ご灞忔秷鎭棤绉戝淇℃伅锛夈�傝嫢鐩存帴浠ユ姤鏂囧唴瀹规暣浣撹鐩栵紝浼氭妸宸叉湁鏁版嵁娓呯┖銆�
+     * 杩欐槸"閮ㄥ垎鏇存柊"璇箟锛屼笌鏇存柊娴佺殑 {@code ifPresent} 閫愬瓧娈佃祴鍊间繚鎸佷竴鑷淬��
+     * <p>
+     * 娉ㄦ剰锛氬湪 {@link AppointmentDO} 灞傚悎骞讹紙鑰岄潪 VO 灞傦級锛屽洜涓�
+     * {@code AppointmentSaveReqVO} 涓� DO 鐨勫瓧娈电被鍨嬪苟涓嶄竴鑷�
+     * 锛圴O 鐨� {@code bookDate/patBirthday} 涓� {@code LocalDateTime}銆�
+     * {@code registerDate/doctorTime} 涓� {@code java.sql.Date}锛�
+     * DO 瀵瑰簲涓� {@code LocalDate} / {@code java.util.Date}锛夈��
+     *
+     * @param target 鏈鎶ユ枃瑙f瀽鍑虹殑瀵硅薄锛堜細琚氨鍦拌ˉ鍏級
+     * @param source 鏁版嵁搴撲腑宸叉湁鐨勮褰�
+     */
+    private void mergeUnsetFields(AppointmentDO target, AppointmentDO source) {
+        if (target.getApplyNo() == null) target.setApplyNo(source.getApplyNo());
+        if (target.getEpisodeId() == null) target.setEpisodeId(source.getEpisodeId());
+        if (target.getPatSrc() == null) target.setPatSrc(source.getPatSrc());
+        if (target.getPatId() == null) target.setPatId(source.getPatId());
+        if (target.getPatName() == null) target.setPatName(source.getPatName());
+        if (target.getPatGender() == null) target.setPatGender(source.getPatGender());
+        if (target.getPatBirthday() == null) target.setPatBirthday(source.getPatBirthday());
+        if (target.getPatMobile() == null) target.setPatMobile(source.getPatMobile());
+        if (target.getPatPhone() == null) target.setPatPhone(source.getPatPhone());
+        if (target.getPatIdentityId() == null) target.setPatIdentityId(source.getPatIdentityId());
+        if (target.getPatAddr() == null) target.setPatAddr(source.getPatAddr());
+        if (target.getPatDeptCode() == null) target.setPatDeptCode(source.getPatDeptCode());
+        if (target.getPatDeptDesc() == null) target.setPatDeptDesc(source.getPatDeptDesc());
+        if (target.getPatWardCode() == null) target.setPatWardCode(source.getPatWardCode());
+        if (target.getPatWardDesc() == null) target.setPatWardDesc(source.getPatWardDesc());
+        if (target.getPatBedNo() == null) target.setPatBedNo(source.getPatBedNo());
+        if (target.getBookDate() == null) target.setBookDate(source.getBookDate());
+        if (target.getBookTimeslot() == null) target.setBookTimeslot(source.getBookTimeslot());
+        if (target.getBookTime() == null) target.setBookTime(source.getBookTime());
+        if (target.getBookCheckType() == null) target.setBookCheckType(source.getBookCheckType());
+        if (target.getBookSrc() == null) target.setBookSrc(source.getBookSrc());
+        if (target.getPaid() == null) target.setPaid(source.getPaid());
+        if (target.getStatus() == null) target.setStatus(source.getStatus());
+        if (target.getRegisterDate() == null) target.setRegisterDate(source.getRegisterDate());
+        if (target.getTolerance() == null) target.setTolerance(source.getTolerance());
+        if (target.getHospitalNo() == null) target.setHospitalNo(source.getHospitalNo());
+        if (target.getMedicalCard() == null) target.setMedicalCard(source.getMedicalCard());
+        if (target.getOutpatientNo() == null) target.setOutpatientNo(source.getOutpatientNo());
+        if (target.getDoctor() == null) target.setDoctor(source.getDoctor());
+        if (target.getDoctorCode() == null) target.setDoctorCode(source.getDoctorCode());
+        if (target.getDoctorTime() == null) target.setDoctorTime(source.getDoctorTime());
+        // 鈥斺�� HIS 琛ュ厖瀛楁锛堥殢琛ㄦ柊澧炲垪涓�璧峰姞鍏ワ紝淇濇寔"閮ㄥ垎鏇存柊涓嶆竻绌�"璇箟锛夆�斺��
+        if (target.getJjbz() == null) target.setJjbz(source.getJjbz());
+        if (target.getMethodCode() == null) target.setMethodCode(source.getMethodCode());
+        if (target.getTargetSiteCode() == null) target.setTargetSiteCode(source.getTargetSiteCode());
+        if (target.getRoomName() == null) target.setRoomName(source.getRoomName());
+        if (target.getRemark() == null) target.setRemark(source.getRemark());
+        if (target.getBookSeqNum() == null) target.setBookSeqNum(source.getBookSeqNum());
+    }
+
+    @Override
+    public void handleAppointmentUpdate(Map<String, Object> dataMap) {
+        if (dataMap == null) {
+            throw exception(APPOINTMENT_UPDATE_FAIL);
+        }
+
+        try {
+            // 浠巇ataMap涓彁鍙栫敵璇峰崟鍙�
+            String applyNo = Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("id")).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).orElseThrow(() -> exception(APPOINTMENT_CREATE_FAIL));
+
+            // 鏌ヨ鐜版湁棰勭害璁板綍
+            AppointmentDO existingAppointment = appointmentMapper.getByApplyNo(applyNo);
+
+            // 濡傛灉璁板綍涓嶅瓨鍦紝鍒欒皟鐢╤andleAppointmentCreate杩涜鏂板
+            if (existingAppointment == null) {
+                handleAppointmentCreate(dataMap);
+                return;
+            }
+
+            // 浠巇ataMap涓彁鍙栨暟鎹�
+            Map<String, Object> encounter = extractEncounter(dataMap);
+            if (encounter == null) {
+                throw exception(APPOINTMENT_CREATE_FAIL);
+            }
+
+            // 鏇存柊灏辫瘖娴佹按鍙�
+            Optional.ofNullable(encounter.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(1)).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setEpisodeId);
+
+            // 璁剧疆搴婁綅鍙�
+            Optional.ofNullable(encounter.get("location")).map(location -> (Map<String, Object>) location).map(location -> (Map<String, Object>) location.get("serviceDeliveryLocation")).map(serviceDeliveryLocation -> (Map<String, Object>) serviceDeliveryLocation.get("location")).map(location -> (Map<String, Object>) location.get("id")).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setPatBedNo);
+
+
+            // 鏇存柊鎮h�呮潵婧愮被鍨�
+            Optional.ofNullable(encounter.get("code")).map(code -> (Map<String, Object>) code).map(code -> (String) code.get("code")).map(Integer::parseInt).ifPresent(existingAppointment::setPatSrc);
+
+            // 鏇存柊鎮h�呬俊鎭�
+            Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("patientPerson")).ifPresent(patientPerson -> {
+                // 鏇存柊鎮h�匢D
+                Optional.ofNullable(encounter.get("subject")).map(subject -> (Map<String, Object>) subject).map(subject -> (Map<String, Object>) subject.get("patient")).map(patient -> (Map<String, Object>) patient.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> items.size() > 1).map(items -> (Map<String, Object>) items.get(1)).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setPatId);
+
+                // 鏇存柊鎮h�呭鍚�
+                Optional.ofNullable(patientPerson.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(existingAppointment::setPatName);
+
+                // 鏇存柊鎮h�呮墜鏈哄拰鐢佃瘽
+                Optional.ofNullable(patientPerson.get("telecom")).map(telecom -> (Map<String, Object>) telecom).map(telecom -> (Map<String, Object>) telecom.get("item")).map(item -> (String) item.get("value")).ifPresent(phone -> {
+                    existingAppointment.setPatMobile(phone);
+                    existingAppointment.setPatPhone(phone);
+                });
+
+                // 鏇存柊鎮h�呮�у埆
+                Optional.ofNullable(patientPerson.get("administrativeGenderCode")).map(genderCode -> (Map<String, Object>) genderCode).map(genderCode -> (String) genderCode.get("code")).map(Byte::parseByte).ifPresent(existingAppointment::setPatGender);
+
+                // 鏇存柊鎮h�呯敓鏃�
+                Optional.ofNullable(patientPerson.get("birthTime")).map(birthTime -> (Map<String, Object>) birthTime).map(birthTime -> (String) birthTime.get("value")).map(birthdayStr -> LocalDate.parse(birthdayStr, DateTimeFormatter.ofPattern("yyyyMMdd"))).ifPresent(existingAppointment::setPatBirthday);
+
+                // 鏇存柊鎮h�呭湴鍧�
+                Optional.ofNullable(patientPerson.get("addr")).map(addr -> (Map<String, Object>) addr).map(addr -> (Map<String, Object>) addr.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(existingAppointment::setPatAddr);
+
+                // 鏇存柊鎮h�呰韩浠借瘉鍙�
+                Optional.ofNullable(patientPerson.get("id")).map(id -> (Map<String, Object>) id).map(id -> (List<?>) id.get("item")).filter(items -> !items.isEmpty()).map(items -> (Map<String, Object>) items.get(0)).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setPatIdentityId);
+            });
+
+            // 鏇存柊绉戝鍜岀梾鍖轰俊鎭�
+            Optional.ofNullable(encounter.get("location")).map(location -> (Map<String, Object>) location).map(location -> (Map<String, Object>) location.get("serviceDeliveryLocation")).map(location -> (Map<String, Object>) location.get("serviceProviderOrganization")).ifPresent(org -> {
+                // 鏇存柊绉戝浠g爜鍜屽悕绉�
+                Optional.ofNullable(org.get("id")).map(id -> (Map<String, Object>) id).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setPatDeptCode);
+
+                Optional.ofNullable(org.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(existingAppointment::setPatDeptDesc);
+
+                // 鏇存柊鐥呭尯浠g爜鍜屽悕绉�
+                Optional.ofNullable(org.get("asOrganizationPartOf")).map(wardOrg -> (Map<String, Object>) wardOrg).map(wardOrg -> (Map<String, Object>) wardOrg.get("wholeOrganization")).ifPresent(wardOrg -> {
+                    Optional.ofNullable(wardOrg.get("id")).map(id -> (Map<String, Object>) id).map(id -> (Map<String, Object>) id.get("item")).map(item -> (String) item.get("extension")).ifPresent(existingAppointment::setPatWardCode);
+
+                    Optional.ofNullable(wardOrg.get("name")).map(name -> (Map<String, Object>) name).map(name -> (Map<String, Object>) name.get("item")).map(item -> (Map<String, Object>) item.get("part")).map(part -> (String) part.get("value")).ifPresent(existingAppointment::setPatWardDesc);
+                });
+            });
+
+            // 鏇存柊鏀粯鐘舵��
+            Optional.ofNullable(dataMap.get("controlActProcess")).map(controlActProcess -> (Map<String, Object>) controlActProcess).map(controlActProcess -> (Map<String, Object>) controlActProcess.get("subject")).map(subject -> (Map<String, Object>) subject.get("observationRequest")).map(observationRequest -> (Map<String, Object>) observationRequest.get("zdy")).map(zdy -> {
+                Object payStatus = zdy.get("PayStatus");
+                if (payStatus != null) {
+                    try {
+                        return Integer.parseInt(payStatus.toString());
+                    } catch (NumberFormatException e) {
+                        log.warn("PayStatus杞崲澶辫触: {}", payStatus);
+                        return null;
+                    }
+                }
+                return null;
+            }).ifPresent(existingAppointment::setPaid);
+            // 璁剧疆妫�鏌ョ被鍨�
+            Integer checkTypeValue = resolveBookCheckTypeFromComponent2(dataMap);
+            if (checkTypeValue != null) {
+                existingAppointment.setBookCheckType(checkTypeValue);
+            } else {
+                log.warn("[handleAppointmentUpdate][鏈兘瑙f瀽妫�鏌ョ被鍨嬶紝淇濈暀鍘熷�糫 applyNo={}", applyNo);
+            }
+
+            // 灏咥ppointmentDO杞崲涓篈ppointmentSaveReqVO骞舵洿鏂�
+            AppointmentSaveReqVO updateReqVO = BeanUtils.toBean(existingAppointment, AppointmentSaveReqVO.class);
+            updateAppointment(updateReqVO);
+        } catch (Exception e) {
+            log.error("[handleAppointmentUpdate][鐢宠鍗曟洿鏂板け璐� dataMap({})]", dataMap, e);
+            throw exception(APPOINTMENT_UPDATE_FAIL);
+        }
+    }
+
+    /**
+     * 浠庢姤鏂� {@code component2.observationRequest.code.code} 瑙f瀽鏈湴妫�鏌ョ被鍨嬪�笺��
+     *
+     * <h3>淇鐨勭己闄�</h3>
+     * 鍘熷疄鐜颁负锛�
+     * <pre>
+     *   if (checkType != null || checkType != "")            // 鎭掍负 true锛屼笖閫昏緫搴斾负 &amp;&amp;
+     *       setBookCheckType(Integer.valueOf(
+     *           HisCheckCodeEnum.getByCode(checkType.substring(1))));  // 鏃犻暱搴︽牎楠�
+     * </pre>
+     * 瀛樺湪涓夊闂锛�
+     * <ol>
+     *   <li>{@code ||} 浣垮垽鏂亽鐪燂紱</li>
+     *   <li>{@code substring(1)} 鐩存帴鎴柇锛屾姤鏂囧�奸暱搴︿笉瓒虫椂鎶�
+     *       {@code StringIndexOutOfBoundsException}锛堝疄娴嬫棩蹇椾腑鏈夌被浼煎紓甯革級锛�</li>
+     *   <li>{@code HisCheckCodeEnum.getByCode()} 鍙兘杩斿洖 null锛�
+     *       闅忓悗 {@code Integer.valueOf(null)} 鎶� {@code NumberFormatException}
+     *       锛堝疄娴嬫棩蹇椾腑纭湁 {@code Cannot parse null string}锛夈��</li>
+     * </ol>
+     * 鐜版敼涓猴細HIS 浠g爜鍏煎甯�/涓嶅甫 {@code Y} 鍓嶇紑锛堝搴斿師 substring(1) 璇箟锛夛紝
+     * 涓斾粎鍦ㄨ兘鏄犲皠鍒版湰鍦� {@code check_type} 鍊兼椂鎵嶈繑鍥烇紝鍚﹀垯杩斿洖 null 鐢辫皟鐢ㄦ柟淇濈暀鍘熷�笺��
+     *
+     * @return 鏈湴妫�鏌ョ被鍨嬪�硷紙濡� 1000锛夛紱鏃犳硶瑙f瀽鏃惰繑鍥� null
+     */
+    private Integer resolveBookCheckTypeFromComponent2(Map<String, Object> dataMap) {
+        // 绫诲瀷瀹夊叏锛歰bservationRequest.code 鍙兘鏄� Map锛屼篃鍙兘鏄┖瀛楃涓�
+        String hisCode = digText(dataMap, "controlActProcess", "subject", "observationRequest",
+                "component2", "observationRequest", "code", "code");
+        if (StringUtils.isEmpty(hisCode)) {
+            return null;
+        }
+        String raw = hisCode.trim().replaceAll("\\s+", "");
+        // 鍏煎锛欻IS 鎶ユ枃鍊煎舰濡� "Y1202065"锛岃�屾灇涓� code 涓哄幓鎺夐瀛楃鍚庣殑 "1202065"锛�
+        // 鍚屾椂涔熷蹇嶆姤鏂囨湰韬笉甯﹀墠缂�
+        String[] candidates = raw.length() > 1
+                ? new String[]{raw, raw.substring(1)}
+                : new String[]{raw};
+        for (String candidate : candidates) {
+            String mapped = HisCheckCodeEnum.getByCode(candidate);
+            if (StringUtils.isNotEmpty(mapped)) {
+                return parseIntegerQuietly(mapped);
+            }
+        }
+        log.warn("[resolveBookCheckTypeFromComponent2][HIS 妫�鏌ヤ唬鐮佹棤娉曟槧灏勫埌鏈湴妫�鏌ョ被鍨媇 hisCode={}", hisCode);
+        return null;
+    }
+
+    /**
+     * 鐢宠鍗曠姸鎬佹洿鏂帮紙action: {@code S0405} / {@code S0405HIS} / {@code S040501HIS}锛夈��
+     *
+     * <h3>鈿狅笍 璇ユ湇鍔℃槸鍏ㄩ櫌閫氱敤鐨�</h3>
+     * 鎺ュ彛鏂囨。 6.3 瀹氫箟鍏� action 涓� {@code S0405|S0405HIS|S040501HIS}锛�**涓嶅尯鍒嗙瀹�**銆�
+     * 鍖婚櫌浼氭妸鎵�鏈夋鏌ョ瀹わ紙CT銆丮RI銆丏R銆佽秴澹般�侀瀵嗗害鈥︼級鐨勬鏌ョ姸鎬佷竴骞舵帹閫併��
+     * 瀹炴祴 2026-09-17锛氭湰鏈嶅姟鏀跺埌 1225 娆¤姹傦紝鍏朵腑浠� 3.5% 鐨勭敵璇峰崟鍙峰睘蹇冪數涓氬姟锛�
+     * 鍏朵綑涓烘斁灏�/瓒呭0绛夌瀹ょ殑鍗曪紙濡� {@code 2126091700001} 鑳搁儴CT骞虫壂 / 鎬ヨ瘖CT瀹わ級銆�
+     *
+     * <h3>澶勭悊绛栫暐</h3>
+     * <ol>
+     *   <li>鎻愬彇鐢宠鍗曞彿涓庢鏌ョ姸鎬佺爜锛屽苟瀵圭姸鎬佺爜鍋�**褰掍竴鍖�**锛堝幓鍓嶅闆躲�佹牎楠屽悎娉曟�э級</li>
+     *   <li>鏌ユ湰鍦伴绾﹁褰曪細
+     *     <ul>
+     *       <li><b>瀛樺湪</b> 鈫� 鏇存柊鐘舵�侊紱鐘舵�佷负 3锛堟鏌ョ櫥璁帮級鏃跺悓鏃跺啓鍏ョ櫥璁版椂闂�</li>
+     *       <li><b>涓嶅瓨鍦� + 鏄績鐢靛崟</b> 鈫� 璁� WARN 骞�**鎻掑叆鏂拌褰�**锛圦4 瑕佹眰瀹归敊鏂板锛�</li>
+     *       <li><b>涓嶅瓨鍦� + 闈炲績鐢靛崟</b> 鈫� 浠呰 DEBUG 鏃ュ織锛�**涓嶆彃鍏�**锛堥伩鍏嶇亴鍏ユ棤鍏虫暟鎹級</li>
+     *     </ul>
+     *   </li>
+     * </ol>
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleAppointmentStateUpdate(Map<String, Object> dataMap) {
+        if (dataMap == null) {
+            throw exception(APPOINTMENT_STATUS_UPDATE_FAIL);
+        }
+
+        // 1. 鎻愬彇鐢宠鍗曞彿锛堢被鍨嬪畨鍏ㄥ鑸級
+        String applyNo = digText(dataMap, "controlActProcess", "subject", "placerGroup",
+                "component2", "observationRequest", "id", "item", "extension");
+        if (StringUtils.isEmpty(applyNo)) {
+            log.warn("[handleAppointmentStateUpdate][鎶ユ枃缂哄皯鐢宠鍗曞彿锛屽拷鐣 dataMap={}", dataMap);
+            throw exception(HIS_APPLY_NO_MISSING);
+        }
+
+        // 2. 鎻愬彇骞跺綊涓�鍖栨鏌ョ姸鎬佺爜
+        //    绫诲瀷瀹夊叏锛歱rocessStep.code 鍦ㄩ儴鍒嗘姤鏂囦腑鏄� Map锛坈ode={code=7,...}锛夛紝
+        //    鍦ㄥ彟涓�浜涙姤鏂囦腑鍙兘鏄┖瀛楃涓诧紝鐩存帴鐢ㄩ摼寮忓己杞細鎶� ClassCastException
+        String rawStatus = digText(dataMap, "controlActProcess", "subject", "placerGroup",
+                "component2", "observationRequest", "component1", "processStep", "code", "code");
+        String status = HisCheckStatusEnum.normalize(rawStatus);
+        if (status == null) {
+            // 瀹炴祴鎶ユ枃涓鍊煎瓨鍦� "3" / "03" / "7" / "08" / "10" 绛変笉鍚屽啓娉曪紝
+            // 褰掍竴鍖栧悗浠嶆棤娉曡瘑鍒鏄庝笉鏄紪鐮佽〃鍐呯殑鐘舵�侊紙濡傜梾鐞嗕笓鐢� 21/22/23锛�
+            log.warn("[handleAppointmentStateUpdate][妫�鏌ョ姸鎬佺紪鐮侀潪娉曟垨涓嶅湪缂栫爜琛ㄥ唴锛屽拷鐣 applyNo={} rawStatus={}",
+                    applyNo, rawStatus);
+            throw exception(HIS_STATUS_INVALID);
+        }
+
+        // 3. 鏈湴璁板綍鍖归厤
+        AppointmentDO existingAppointment = appointmentMapper.getByApplyNo(applyNo);
+        if (existingAppointment != null) {
+            // 鐘舵�佹祦鍙厑璁告敼 status / register_date锛�
+            // 鍘熷疄鐜版妸鏌ュ嚭鏉ョ殑鏁磋瀵硅薄鍐� updateById 鍐欏洖锛堣-鏀�-鍐欙級锛屼細鎶� book_date / book_check_type
+            // 绛夊瓧娈垫寜鏃у揩鐓ц鐩栥�傚疄娴� 2026-10-05 07:58锛屼竴绗旈绾﹀垰琚� S050401 鍐欏ソ棰勭害鏃堕棿涓庢鏌ョ被鍨嬶紝
+            // 3.6 绉掑悗 S0405 鐘舵�佹祦鍙堟妸鏁磋鍐欏洖涓�娆★紝check_type 琚啓鎴愪簡绌恒��
+            // 杩欓噷鏀规垚鍙洿鏂扮姸鎬侊紙register_date 浠呭湪鐘舵��=鐧昏鏃跺啓鍏ワ紝鐢� updateStatusByApplyNo 澶勭悊锛夈��
+            Date registerDate = HisCheckStatusEnum.REGISTERED.getCode().toString().equals(status)
+                    ? new Date() : null;
+            Integer updated = registerDate != null
+                    ? appointmentMapper.updateStatusByApplyNo(applyNo, status, registerDate)
+                    : appointmentMapper.updateStatusOnlyByApplyNo(applyNo, status);
+            log.info("[handleAppointmentStateUpdate][浠呮洿鏂扮姸鎬乚 applyNo={} status={} 褰卞搷琛屾暟={}",
+                    applyNo, status, updated);
+            return;
+        }
+
+        // 4. 璁板綍涓嶅瓨鍦細鍏堝垽瀹氭槸鍚﹀績鐢典笟鍔″崟锛岄潪蹇冪數鍗曞彧璁版棩蹇椼�佷笉鎻掑叆
+        //
+        //    S0405 鏄叏闄㈤�氱敤娴併�傚彲鐢ㄧ殑蹇冪數鍒ゅ畾渚濇嵁锛堟寜瀹炴祴鎶ユ枃锛夛細
+        //      A. apply_no 鍚庣紑锛�1101/6401锛�
+        //      B. component2.observationRequest.component1.processStep.code.ZDY
+        //         涓殑 jcxmcode / jcxmname锛堝疄娴� 1138/1225 鏉″甫璇ュ瓧娈碉紝
+        //         褰㈠ jcxmname=鑳搁儴CT骞虫壂,涓婅吂閮–T骞虫壂 / roomname=鎬ヨ瘖CT瀹わ級
+        //      C. 鎵ц绉戝锛堥渶閰嶇疆鐧藉悕鍗曪級
+        Map<String, Object> zdyFromS0405 = extractZdyFromStateMessage(dataMap);
+        String s0405CheckCode = extractZdyValueFromMap(zdyFromS0405, "jcxmcode");
+        String s0405CheckName = extractZdyValueFromMap(zdyFromS0405, "jcxmname");
+        String hisDeptCode = extractDeptCodeFromComponent2(dataMap);
+        if (!hisEcgFilter.isEcgOrder("S0405", applyNo, s0405CheckCode, s0405CheckName, hisDeptCode)) {
+            // 鍏ㄩ櫌娴佷腑鐨勫叾浠栫瀹ゅ崟鎹紝闈欓粯璺宠繃锛圖EBUG 鏃ュ織鐢� filter 杈撳嚭锛�
+            return;
+        }
+
+        // 5. 鏄績鐢靛崟浣嗘湰鍦版棤璁板綍 鈫� 瀹归敊鏂板锛圦4 瑕佹眰锛�
+        log.warn("[handleAppointmentStateUpdate][蹇冪數涓氬姟鍗曚絾鏈湴鏃犺褰曪紝鎵ц瀹归敊鏂板] applyNo={} status={} dept={}",
+                applyNo, status, hisDeptCode);
+        insertAppointmentFromStateMessage(applyNo, status, dataMap);
+    }
+
+    /**
+     * 浠� S0405 鎶ユ枃涓瀯閫犲苟鎻掑叆涓�鏉¢绾﹁褰曘��
+     *
+     * <h3>鈿狅笍 宸茬煡鐨勬暟鎹己澶�</h3>
+     * {@code S0405} 鎶ユ枃**涓嶅寘鍚鏌ラ」鐩俊鎭�**锛堟棤 {@code component2.observationRequest.code}锛夛紝
+     * 鍥犳鏃犳硶纭畾 {@code book_check_type}锛堟鏌ョ被鍨嬶級銆傚疄娴嬬‘璁わ細鐢宠鍗曞彿鍚庣紑
+     * 涓庢鏌ョ被鍨�**鏃犲搴斿叧绯�**锛堝悓涓� {@code ...1101} 鍚庣紑鐨勮褰曞瓨鍦� 1000/100/1100 绛夊绉嶇被鍨嬶級锛�
+     * 鏁呬笉鍋氱寽娴嬶紝淇濇寔涓虹┖骞惰緭鍑� WARN锛屽緟鍚庣画 {@code S0202ECG}锛堢敵璇峰崟鏇存柊锛�
+     * 鎴栦汉宸ヨˉ褰曚慨姝c��
+     * <p>
+     * 鍚岀悊 {@code book_date}锛堥绾︽棩鏈燂級涔熶笉鍦ㄦ姤鏂囦腑锛屼繚鎸佷负绌� 鈥斺�� 杩欎笌鐜版湁鏁版嵁涓�鑷�
+     * 锛堝簱涓害 48% 璁板綍 {@code book_date} 涓虹┖锛岄绾︽棩鏈熺敱 S050401/S040501HIS 鍚庣画琛ュ叆锛夈��
+     */
+    private void insertAppointmentFromStateMessage(String applyNo, String status, Map<String, Object> dataMap) {
+        AppointmentDO appointment = new AppointmentDO();
+        appointment.setApplyNo(applyNo);
+        appointment.setStatus(status);
+        if (HisCheckStatusEnum.REGISTERED.getCode().toString().equals(status)) {
+            appointment.setRegisterDate(new Date());
+        }
+
+        // 鎮h�呬俊鎭紙S0405 鐨� patient.id.item 涓哄垪琛細鍩烮D/鎮h�匢D/闂ㄨ瘖鍙�/浣忛櫌鍙�/灏辫瘖娆℃暟/灏辫瘖娴佹按鍙凤級
+        // 鐢ㄧ被鍨嬪畨鍏ㄥ鑸紝閬垮厤鏌愪竴绾т笉鏄� Map 鏃舵姏 ClassCastException
+        Map<String, Object> encounter = dig(dataMap, "controlActProcess", "subject",
+                "placerGroup", "componentOf1", "encounter");
+        if (encounter != null) {
+            // 灏辫瘖娴佹按鍙凤細id.item[1]
+            List<?> encIdItems = asList(dig(encounter, "id"), "item");
+            if (encIdItems.size() > 1 && encIdItems.get(1) instanceof Map) {
+                appointment.setEpisodeId(str(((Map<String, Object>) encIdItems.get(1)).get("extension")));
+            }
+            // 鎮h�呬俊鎭�
+            Map<String, Object> patient = dig(encounter, "subject", "patient");
+            if (patient != null) {
+                List<?> patIdItems = asList(patient, "id", "item");
+                // 鎮h�匢D item[1]锛岄棬璇婂彿 item[2]锛屼綇闄㈠彿 item[3]
+                if (patIdItems.size() > 1 && patIdItems.get(1) instanceof Map) {
+                    appointment.setPatId(str(((Map<String, Object>) patIdItems.get(1)).get("extension")));
+                }
+                if (patIdItems.size() > 2 && patIdItems.get(2) instanceof Map) {
+                    appointment.setOutpatientNo(str(((Map<String, Object>) patIdItems.get(2)).get("extension")));
+                }
+                if (patIdItems.size() > 3 && patIdItems.get(3) instanceof Map) {
+                    appointment.setHospitalNo(str(((Map<String, Object>) patIdItems.get(3)).get("extension")));
+                }
+                // 濮撳悕
+                String patName = digText(patient, "patientPerson", "name", "item", "part", "value");
+                if (StringUtils.isNotEmpty(patName)) {
+                    appointment.setPatName(patName);
+                }
+                // 鎬у埆锛歱at_gender 涓� NOT NULL锛屽敖閲忓彇锛涘彇涓嶅埌鏃剁敱涓嬫柟鍏滃簳涓� 0
+                String gender = digText(patient, "patientPerson", "administrativeGenderCode", "code");
+                Byte genderByte = parseByteQuietly(gender);
+                if (genderByte != null) {
+                    appointment.setPatGender(genderByte);
+                }
+            }
+        }
+
+        // 鎵ц绉戝锛堝啓鍏� pat_dept_code / pat_dept_desc锛�
+        Map<String, Object> org = dig(dataMap, "controlActProcess", "subject", "placerGroup",
+                "component2", "observationRequest", "location", "serviceDeliveryLocation",
+                "serviceProviderOrganization");
+        if (org != null) {
+            String deptCode = digText(org, "id", "item", "extension");
+            if (StringUtils.isNotEmpty(deptCode)) {
+                appointment.setPatDeptCode(deptCode);
+            }
+            String deptDesc = digText(org, "name", "item", "part", "value");
+            if (StringUtils.isNotEmpty(deptDesc)) {
+                appointment.setPatDeptDesc(deptDesc);
+            }
+        }
+
+        if (StringUtils.isEmpty(appointment.getPatName())) {
+            log.warn("[insertAppointmentFromStateMessage][瀹归敊鏂板鐨勮褰曠己灏戞偅鑰呭鍚峕 applyNo={}", applyNo);
+        }
+        log.warn("[insertAppointmentFromStateMessage][娉ㄦ剰锛歋0405 鎶ユ枃涓嶅惈妫�鏌ラ」鐩紝" +
+                "book_check_type 灏嗕负绌猴紝闇�鍚庣画 S0202ECG 鎴栦汉宸ヨˉ褰昡 applyNo={}", applyNo);
+        // pat_id / pat_name / pat_gender 鍦ㄦ暟鎹簱涓潎涓� NOT NULL锛岀己鍊兼椂鎻愬墠鎶ラ敊锛�
+        // 閬垮厤 SQL 寮傚父瀵艰嚧浜嬪姟鍥炴粴锛堝疄娴嬫姤閿欙細Field 'pat_gender' doesn't have a default value锛�
+        if (StringUtils.isEmpty(appointment.getPatId())) {
+            log.error("[insertAppointmentFromStateMessage][鎶ユ枃缂烘偅鑰呯紪鍙�(pat_id)锛屾棤娉曟柊澧瀅 applyNo={}", applyNo);
+            throw exception(PATIENT_NOT_EXISTS);
+        }
+        if (appointment.getPatGender() == null) {
+            log.warn("[insertAppointmentFromStateMessage][鎶ユ枃鏈彁渚涙偅鑰呮�у埆锛屾寜鏈煡(0)鍐欏叆] applyNo={}", applyNo);
+            appointment.setPatGender(UNKNOWN_GENDER);
+        }
+
+        AppointmentSaveReqVO saveReqVO = BeanUtils.toBean(appointment, AppointmentSaveReqVO.class);
+        createAppointment(saveReqVO);
+    }
+
+    /**
+     * 鍙� S0405 鎶ユ枃涓殑 {@code ZDY} 鑺傜偣銆�
+     * <p>
+     * 瀹炴祴璺緞锛歿@code controlActProcess.subject.placerGroup.component2.observationRequest
+     * .component1.processStep.code.ZDY}锛屽唴瀹瑰舰濡傦細
+     * <pre>
+     *   ZDY={roomcode=111, roomname=鎬ヨ瘖CT瀹�, jcxmcode=1080,3016,3014,
+     *        jcxmname=鑳搁儴CT骞虫壂,涓婅吂閮–T骞虫壂,涓嬭吂閮–T骞虫壂,
+     *        pdh=000336, yysd=00:00-01:00, isjj=1}
+     * </pre>
+     * 娉ㄦ剰锛歿@code observationRequest.code} 鍦ㄦ姤鏂囦腑**鍙兘鏄┖瀛楃涓茶�岄潪 Map**锛�
+     * 鏁呭繀椤荤敤 {@link #dig(Object, String...)} 鍋氱被鍨嬪畨鍏ㄥ鑸��
+     */
+    private Map<String, Object> extractZdyFromStateMessage(Map<String, Object> dataMap) {
+        return dig(dataMap, "controlActProcess", "subject", "placerGroup", "component2",
+                "observationRequest", "component1", "processStep", "code", "ZDY");
+    }
+
+    /** 浠� Map 涓彇瀛楃涓插瓧娈碉紙鍙负绌猴級 */
+    private String extractZdyValueFromMap(Map<String, Object> map, String key) {
+        if (map == null) {
+            return null;
+        }
+        Object v = map.get(key);
+        return v == null ? null : v.toString();
+    }
+
+    /** 瀹夊叏杞瓧绗︿覆锛歯ull 杩斿洖 null锛屽叾浣� toString() */
+    private static String str(Object o) {
+        return o == null ? null : o.toString();
+    }
+
+    /**
+     * 浠� S050401/S050501锛堟鏌ラ绾︼級鎶ユ枃鐨� {@code ZDY} 鑺傜偣瑙f瀽鏈湴 {@code book_check_type}銆�
+     *
+     * <h3>涓轰粈涔堥渶瑕佽繖涓柟娉�</h3>
+     * {@code S050401/S050501} 鎶ユ枃鐨勬鏌ラ」鐩斁鍦� {@code ZDY.jcxmcode / ZDY.jcxmname}
+     * 锛堝舰濡� {@code jcxmcode=201652, jcxmname=骞虫澘杩愬姩璇曢獙}锛夛紝
+     * 涓� {@code S0201ECG} 鐨� {@code component2.observationRequest.code.code} **涓嶆槸鍚屼竴璺緞**銆�
+     * 鍘熷厛鍙湁 S0201ECG 閭f潯璺緞瑙f瀽妫�鏌ョ被鍨嬶紝浜庢槸銆孲050401 鍏堝埌銆丼0201ECG 鏈埌銆嶆椂璧�
+     * {@link #insertAppointmentFromCheckAppointment} 瀹归敊鏂板锛�
+     * {@code book_check_type} 浼�**姘歌繙涓虹┖** 鈥斺�� 瀹炴祴 2026-09-29~10-09 鍏� 279 鏉�
+     * 锛堝 {@code apply_no=8826092420371106}锛屾姤鏂囬噷 {@code jcxmcode=201652} 鏄庢槑瀛樺湪锛夈��
+     *
+     * <h3>瑙f瀽椤哄簭</h3>
+     * <ol>
+     *   <li><b>鍚嶇О浼樺厛</b>锛歿@code jcxmname} 鈫� {@link HisCheckNameEnum#getByName}銆�
+     *       璇ユ灇涓惧凡鍚�屽钩鏉胯繍鍔ㄨ瘯楠屻�嶇瓑 HIS 鏀瑰悕鍒悕锛屼笖鍐呯疆瑙勮寖鍖栧尮閰嶏紱</li>
+     *   <li><b>浠g爜鍏滃簳</b>锛歿@code jcxmcode} 鈫� {@link HisCheckCodeEnum}锛�
+     *       鍏煎甯�/涓嶅甫 {@code Y} 鍓嶇紑銆�</li>
+     * </ol>
+     *
+     * <h3>澶氬�煎鐞�</h3>
+     * 瀹炴祴璇ュ瓧娈靛彲鑳戒负閫楀彿鍒嗛殧澶氬�硷紙濡� S0405 鐨� {@code jcxmcode=1080,3016,3014}锛夈��
+     * 绛栫暐锛�**鎸夐『搴忓彇绗竴涓兘鏄犲皠鎴愬姛鐨勫��**锛涘厛鏁翠綋璇曟帰锛堝崟鍊煎満鏅級锛屽啀閫愭璇曟帰銆�
+     *
+     * @param zdy 鎶ユ枃涓殑 {@code ZDY} 鑺傜偣锛屽彲涓� null
+     * @return 鏈湴妫�鏌ョ被鍨嬪�硷紙濡� 400锛夛紱鏃犳硶瑙f瀽鏃惰繑鍥� null锛堣皟鐢ㄦ柟淇濇寔鍘熷�间笉鍐欙級
+     */
+    private Integer resolveBookCheckTypeFromZdy(Map<String, Object> zdy, String applyNo) {
+        if (zdy == null) {
+            log.warn("[resolveBookCheckTypeFromZdy][鎶ユ枃鏃� ZDY 鑺傜偣锛屾棤娉曡В鏋愭鏌ョ被鍨媇 applyNo={}", applyNo);
+            return null;
+        }
+        String jcxmName = extractZdyValueFromMap(zdy, "jcxmname");
+        String jcxmCode = extractZdyValueFromMap(zdy, "jcxmcode");
+
+        // 鈶� 鍚嶇О鏄犲皠锛堜紭鍏堬細鍙锛屼笖鏋氫妇宸茶鐩� HIS 鏀瑰悕涓庤鑼冨寲宸紓锛�
+        Integer byName = mapCheckNameToType(jcxmName);
+        if (byName != null) {
+            return byName;
+        }
+        // 鈶� 浠g爜鏄犲皠锛堝厹搴曪細瀹炴祴 HIS 渚ф敼杩囧悕锛屼唬鐮佹洿绋冲畾锛�
+        Integer byCode = mapCheckCodeToType(jcxmCode);
+        if (byCode != null) {
+            log.info("[resolveBookCheckTypeFromZdy][鍚嶇О鏈槧灏勶紝宸叉寜妫�鏌ヤ唬鐮佸厹搴昡 "
+                            + "applyNo={} jcxmname={} jcxmcode={} bookCheckType={}",
+                    applyNo, jcxmName, jcxmCode, byCode);
+            return byCode;
+        }
+
+        log.warn("[resolveBookCheckTypeFromZdy][妫�鏌ラ」鐩悕绉颁笌浠g爜鍧囨棤娉曟槧灏勶紝book_check_type 淇濇寔鍘熷�糫 "
+                + "applyNo={} jcxmname={} jcxmcode={}", applyNo, jcxmName, jcxmCode);
+        return null;
+    }
+
+    /** 妫�鏌ラ」鐩悕绉� 鈫� 鏈湴妫�鏌ョ被鍨嬶紙鏀寔閫楀彿鍒嗛殧澶氬�硷紝鍙栫涓�涓彲鏄犲皠鐨勶級 */
+    private Integer mapCheckNameToType(String rawName) {
+        if (StringUtils.isEmpty(rawName)) {
+            return null;
+        }
+        for (String candidate : splitMultiValue(rawName)) {
+            // 鍘荤┖鐧斤細HIS 鎶ユ枃閲屽伓瑙併�屽钩鏉胯繍鍔ㄨ瘯楠� 銆嶈繖绫诲熬闅忕┖鏍�
+            String mapped = HisCheckNameEnum.getByName(candidate.replaceAll("\\s+", ""));
+            Integer value = parseIntegerQuietly(mapped);
+            if (value != null) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    /** HIS 妫�鏌ヤ唬鐮� 鈫� 鏈湴妫�鏌ョ被鍨嬶紙鏀寔閫楀彿鍒嗛殧澶氬�硷紱鍏煎甯�/涓嶅甫 Y 鍓嶇紑锛� */
+    private Integer mapCheckCodeToType(String rawCode) {
+        if (StringUtils.isEmpty(rawCode)) {
+            return null;
+        }
+        for (String candidate : splitMultiValue(rawCode)) {
+            String code = candidate.replaceAll("\\s+", "");
+            if (code.isEmpty()) {
+                continue;
+            }
+            // 鍏煎涓ょ鎶ユ枃褰㈡�侊細甯﹀墠缂�锛圷1202065锛変笌涓嶅甫鍓嶇紑锛�201652锛�
+            String[] tries = code.length() > 1
+                    ? new String[]{code, code.substring(1)}
+                    : new String[]{code};
+            for (String t : tries) {
+                Integer value = parseIntegerQuietly(HisCheckCodeEnum.getByCode(t));
+                if (value != null) {
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鎷嗗垎銆屽彲鑳界殑澶氬�笺�嶅瓧娈点��
+     * <p>
+     * 鍏堣繑鍥�**鏁翠綋**锛堣鐩栧崟鍊煎満鏅紝涔熼伩鍏嶆妸鍚嶇О閲屾湰灏卞惈閫楀彿鐨勯」鎷嗗潖锛夛紝
+     * 鍐嶈繑鍥炴寜 {@code ,} / {@code 锛寎 鎷嗗嚭鐨勫悇娈点��
+     */
+    private List<String> splitMultiValue(String raw) {
+        List<String> result = new ArrayList<>();
+        if (StringUtils.isEmpty(raw)) {
+            return result;
+        }
+        result.add(raw);
+        for (String part : raw.split("[,锛宂")) {
+            String p = part == null ? "" : part.trim();
+            if (!p.isEmpty() && !p.equals(raw)) {
+                result.add(p);
+            }
+        }
+        return result;
+    }
+
+    /** {@code appointment.remark} 鍒楀涓婇檺锛堣 08_fix_remark_length.sql锛屽綋鍓� varchar(2000)锛� */
+    private static final int REMARK_MAX_LEN = 2000;
+    /** {@code appointment.room_name} 鍒楀涓婇檺 */
+    private static final int ROOM_NAME_MAX_LEN = 200;
+
+    /**
+     * 鎸夊垪瀹芥埅鏂枃鏈紝闃叉 {@code Data too long for column} 瀵艰嚧**鏁翠釜浜嬪姟鍥炴粴**銆�
+     *
+     * <p><b>琛�娉暀璁�</b>锛�2026-09-28 鐢熶骇鐜璇ラ敊璇嚭鐜� 52 娆°�傚績鐢靛浘杩愬姩璇曢獙鐨�
+     * 娉ㄦ剰浜嬮」闀胯揪 474 瀛楃锛岃�� {@code remark} 鍒楀師涓� {@code varchar(255)}锛�
+     * 婧㈠嚭鍚庢姏 {@code MysqlDataTruncation}锛屼娇鏁存潯 UPDATE 鍥炴粴 鈥斺��
+     * 杩炲甫鍚屼竴浜嬪姟涓殑 {@code book_time}銆亄@code room_name} 涓�璧蜂涪澶憋紝
+     * 琛ㄧ幇涓�"棰勭害鏃堕棿娈垫病瀛樿繘鏁版嵁搴�"銆�
+     *
+     * @param value 鍘熷鏂囨湰
+     * @param maxLen 鐩爣鍒楀
+     * @return 鎴柇鍚庣殑鏂囨湰锛坣ull 鍘熸牱杩斿洖锛�
+     */
+    private static String truncate(String value, int maxLen) {
+        if (value == null) {
+            return null;
+        }
+        if (value.length() <= maxLen) {
+            return value;
+        }
+        log.warn("[truncate][鏂囨湰瓒呴暱宸叉埅鏂璢 鍘熼暱搴�={} 鎴柇鑷�={} 鍐呭鍓� 60 瀛�={}",
+                value.length(), maxLen,
+                value.substring(0, Math.min(60, value.length())));
+        return value.substring(0, maxLen);
+    }
+
+    /**
+     * 灞傜骇瀵艰埅鍚庡彇銆屽垪琛ㄣ�嶈妭鐐癸細{@code dig(root, path...)} 寰楀埌鐨勫�艰嫢鏄� List 鍒欒繑鍥烇紝
+     * 鍚﹀垯杩斿洖绌哄垪琛紙**涓嶆姏寮傚父**锛夈��
+     */
+    @SuppressWarnings("unchecked")
+    private static List<?> asList(Map<String, Object> root, String... path) {
+        Object cur = root;
+        for (String key : path) {
+            if (!(cur instanceof Map)) {
+                return Collections.emptyList();
+            }
+            cur = ((Map<String, Object>) cur).get(key);
+        }
+        return cur instanceof List ? (List<?>) cur : Collections.emptyList();
+    }
+
+    /**
+     * 绫诲瀷瀹夊叏鐨勫眰绾у鑸細閫愮骇鍙� Map 瀛愯妭鐐广��
+     *
+     * <p><b>涓轰粈涔堥渶瑕佸畠</b>锛欻IS 鎶ユ枃缁� Jackson 鍙嶅簭鍒楀寲鍚庯紝鍚屼竴瀛楁鐨勭被鍨�**骞朵笉绋冲畾**銆�
+     * 渚嬪 {@code observationRequest.code} 鍦ㄩ儴鍒嗘姤鏂囦腑鏄� {@code Map}锛坽@code code={code=7,...}}锛夛紝
+     * 鍦ㄥ彟涓�浜涙姤鏂囦腑鏄�**绌哄瓧绗︿覆**锛坽@code code=}锛夈�傝嫢鐢�
+     * {@code .map(m -> (Map<String,Object>) m.get("code"))} 杩欑閾惧紡寮哄埗杞崲锛�
+     * 閬囧埌瀛楃涓蹭細鎶� {@code ClassCastException}锛�**瀵艰嚧鏁存潯娑堟伅澶勭悊涓柇**銆�
+     *
+     * <p>瀹炴祴鐢熶骇鏃ュ織 2026-09-28 涓绫诲紓甯稿嚭鐜� **287 娆�**锛屾鏄敱姝ゅ啓娉曞紩璧�
+     * 锛坽@code AppointmentServiceImpl.lambda$extractCheckCodeFromComponent2}锛夈��
+     *
+     * @param root 璧峰鑺傜偣
+     * @param path 渚濇瑕佸彇鐨勯敭
+     * @return 鏈鑺傜偣锛涗换涓�鐜妭涓嶆槸 Map 鎴栫己澶辨椂杩斿洖 null锛�**涓嶆姏寮傚父**锛�
+     */
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> dig(Map<String, Object> root, String... path) {
+        Object cur = root;
+        for (String key : path) {
+            if (!(cur instanceof Map)) {
+                return null;
+            }
+            cur = ((Map<String, Object>) cur).get(key);
+        }
+        return cur instanceof Map ? (Map<String, Object>) cur : null;
+    }
+
+    /** HIS 鎶ユ枃涓�屽尰鍢卞彿/鍖诲槺ID銆嶇殑 id root锛堣銆婁氦浜掓湇鍔¢�氱敤鎺ュ彛鏂囨。銆�6.1 / 6.4 / 6.5锛� */
+    private static final String ORDER_NO_ROOT = "2.16.156.10011.1.28";
+
+    /**
+     * HIS銆屽紑鍗曟椂闂淬��({@code author/time/value}) 鐨勬椂闂存牸寮忋��
+     * 娉ㄦ剰锛歿@link SimpleDateFormat} **涓嶆槸绾跨▼瀹夊叏**鐨勶紝鏁呰繖閲屽彧淇濆瓨 pattern 瀛楃涓诧紝
+     * 姣忔閮芥柊寤哄疄渚嬶紙瑙� {@link #parseHisDoctorTime}锛夈��
+     */
+    private static final String HIS_DOCTOR_TIME_PATTERN = "yyyyMMddHHmmss";
+
+    /**
+     * 鍒ゆ柇 HIS 寮�鍗曟椂闂存槸鍚︿负銆屾棩鏈熷�笺�嶏紙褰㈠ {@code 20261001000000}锛屾椂鍒嗙鍏� 0锛夈��
+     *
+     * <p>鐢� {@code endsWith} 鑰屼笉鏄鍒欙細璇箟鐩寸櫧涓旀棤杞箟鍧�
+     * 锛堟浘鍐欒繃 {@code value.matches("\\d{6}000000")}锛屽洜杞箟灞傜骇鍑洪敊瀵艰嚧姘镐笉鍖归厤锛夈��
+     */
+    private boolean isHisTimeOnlyDate(String value) {
+        if (value == null || value.length() != 14) {
+            return false;
+        }
+        // 鍚� 6 浣嶏紙HHmmss锛夊叏涓� '0' 鍗充负銆屾棩鏈熷�笺�嶅舰鎬�
+        return value.endsWith("000000");
+    }
+
+    /**
+     * 鍦� HL7 鐨� {@code id} 鑺傜偣涓紝鎸� {@code root} 鍖归厤骞跺彇鍑� {@code extension}銆�
+     * <p>
+     * 鍏煎涓夌鐪熷疄褰㈡�侊紙鍧囧凡鍦ㄦ姤鏂�/鏂囨。涓嚭鐜帮級锛�
+     * <pre>
+     *   id={item={root=..., extension=...}}          // 鍗曚釜 Map
+     *   id={item=[{root=..., extension=...}, {...}]} // List锛堝鏍囪瘑锛岄』鎸� root 鍖哄垎锛�
+     *   {root=..., extension=...}                    // 鐩存帴灏辨槸鏍囪瘑鏈韩
+     * </pre>
+     * <p>
+     * <b>涓轰粈涔堜笉澶嶇敤 {@link #digText}</b>锛歿@code digText} 閬囧埌 List 鍙彇棣栧厓绱狅紝
+     * 鑰� {@code id.item} 涓烘暟缁勬椂鍙兘棣栧厓绱犳槸銆岀敵璇峰崟鍙�(root 1.24)銆嶈�岄潪銆屽尰鍢卞彿(root 1.28)銆嶏紝
+     * 浼氬彇閿欏�笺�傛晠杩欓噷鏄惧紡閬嶅巻骞舵寜 root 鍖归厤銆�
+     *
+     * @param idNode {@code id} 鑺傜偣锛圡ap 鎴� List锛屽彲涓� null锛�
+     * @param root   鐩爣 root 鍊�
+     * @return 鍖归厤椤圭殑 extension锛涙湭鍖归厤/涓虹┖鏃惰繑鍥� null
+     */
+    private static String findExtensionByRoot(Object idNode, String root) {
+        if (idNode == null) {
+            return null;
+        }
+        Object item = (idNode instanceof Map) ? ((Map<String, Object>) idNode).get("item") : idNode;
+        if (item == null) {
+            return null;
+        }
+        List<Object> candidates = new ArrayList<>();
+        if (item instanceof List) {
+            candidates.addAll((List<Object>) item);
+        } else {
+            candidates.add(item);
+        }
+        for (Object cand : candidates) {
+            if (!(cand instanceof Map)) {
+                continue;
+            }
+            Map<String, Object> m = (Map<String, Object>) cand;
+            Object r = m.get("root");
+            if (r != null && root.equals(r.toString().trim())) {
+                Object ext = m.get("extension");
+                if (ext != null && StringUtils.isNotEmpty(ext.toString().trim())) {
+                    return ext.toString().trim();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 浠� HIS 鎶ユ枃涓彇 {@code zdy} 鑷畾涔夊瓧娈靛尯鐨勩�屾槸鍚﹀簥杈广�嶆爣璁帮紙闇�姹�2锛夈��
+     *
+     * <h4>瀛楁鏉ユ簮</h4>
+     * {@code controlActProcess.subject.observationRequest.zdy.ISCB}锛�
+     * 鍙栧�� {@code 0=鍚� 1=鏄瘆銆�
+     *
+     * <p>璇ュ瓧娈�**鍦� HIS 瀹樻柟銆婁氦浜掓湇鍔¢�氱敤鎺ュ彛鏂囨。銆嬩腑鏈畾涔�** 鈥斺�� 鏂囨。閲� 25 涓�
+     * {@code zdy} 鍧楀叡 19 涓瓧娈碉紝瀹炴祴鐪熷疄鎶ユ枃鍗存湁 26 涓紝{@code ISCB} 灞炲叾涓�
+     * 7 涓湭鍏紑鎵╁睍瀛楁涔嬩竴锛坽@code ISCB/ISDN/BLLSH/SWCJBS/KDYSSJH/KLX/VTEGWBZ}锛夈��
+     * 瀹炴祴鏍锋湰涓� {@code ISCB} 鎭掍负 0銆�
+     *
+     * <h4>涓庛�屽簥杈规鏌ョ被鍨嬨�嶇殑鍖哄埆</h4>
+     * {@code check_type.value=1000} 鐨勫悕绉版槸"搴婅竟蹇冪數鍥�(甯歌+棰戣氨)"锛岄偅鏄�**涓�涓鏌ラ」鐩�**锛�
+     * 鏈爣璁拌〃杈剧殑鏄�**鍚屼竴椤圭洰銆佹偅鑰呴渶鍒板簥杈瑰仛**锛屼袱鑰呰涔変笉鍚岋紝涓嶅彲浜掔浉鏇夸唬銆�
+     *
+     * <p>鎶芥垚闈欐�佹柟娉曟槸涓轰簡鍙崟鍏冩祴璇曪紙瑙� {@code HisParseSafetyTest}锛夈��
+     *
+     * @param dataMap HIS 鎶ユ枃鏍硅妭鐐�
+     * @return {@code "0"}/{@code "1"}锛涘瓧娈电己澶辨垨涓虹┖鏃惰繑鍥� null
+     */
+    static String findBedsideFlag(Map<String, Object> dataMap) {
+        if (dataMap == null) {
+            return null;
+        }
+        Object cap = dataMap.get("controlActProcess");
+        if (!(cap instanceof Map)) {
+            return null;
+        }
+        Object subject = ((Map<String, Object>) cap).get("subject");
+        if (!(subject instanceof Map)) {
+            return null;
+        }
+        Object obs = ((Map<String, Object>) subject).get("observationRequest");
+        if (!(obs instanceof Map)) {
+            return null;
+        }
+        Object zdy = ((Map<String, Object>) obs).get("zdy");
+        if (!(zdy instanceof Map)) {
+            return null;
+        }
+        Object iscb = ((Map<String, Object>) zdy).get("ISCB");
+        if (iscb == null) {
+            return null;
+        }
+        String v = iscb.toString().trim();
+        return v.isEmpty() ? null : v;
+    }
+
+    /**
+     * 鍙栨湯绔妭鐐圭殑鏍囬噺鍊硷紙鐢ㄤ簬 {@code code} 鍙兘鏄� Map 涔熷彲鑳芥槸瀛楃涓茬殑鍦烘櫙锛夈��
+     *
+     * @return 鏍囬噺鏂囨湰锛涙湯绔负 Map 鏃跺彇鍏朵腑鐨� {@code value}/{@code code}锛涘彇涓嶅埌杩斿洖 null
+     */
+    private static String digText(Map<String, Object> root, String... path) {
+        Object cur = root;
+        for (int i = 0; i < path.length; i++) {
+            if (cur instanceof Map) {
+                cur = ((Map<String, Object>) cur).get(path[i]);
+            } else if (cur instanceof List) {
+                // 鍒楄〃褰㈡�侊紙濡� item=[{...}]锛夊彇绗竴涓厓绱犵户缁�
+                List<?> list = (List<?>) cur;
+                if (list.isEmpty() || !(list.get(0) instanceof Map)) {
+                    return null;
+                }
+                cur = ((Map<String, Object>) list.get(0)).get(path[i]);
+            } else {
+                return null;
+            }
+        }
+        if (cur == null) {
+            return null;
+        }
+        if (cur instanceof Map) {
+            Map<String, Object> m = (Map<String, Object>) cur;
+            Object v = m.get("value");
+            if (v == null) {
+                v = m.get("code");
+            }
+            return v == null ? null : v.toString();
+        }
+        if (cur instanceof String) {
+            String s = (String) cur;
+            return s.trim().isEmpty() ? null : s;
+        }
+        return cur.toString();
+    }
+
+    /** 浠庢姤鏂囩殑 {@code component2.observationRequest.code.code} 鍙栨鏌ラ」鐩唬鐮侊紙鍙负绌猴級 */
+    private String extractCheckCodeFromComponent2(Map<String, Object> dataMap) {
+        // 绫诲瀷瀹夊叏锛歝ode 鍙兘鏄� Map锛屼篃鍙兘鏄┖瀛楃涓�
+        return digText(dataMap, "controlActProcess", "subject", "placerGroup", "component2",
+                "observationRequest", "code", "code");
+    }
+
+    /** 浠庢姤鏂囩殑 {@code component2.observationRequest.location...} 鍙栨墽琛岀瀹ょ紪鐮侊紙鍙负绌猴級 */
+    private String extractDeptCodeFromComponent2(Map<String, Object> dataMap) {
+        return digText(dataMap, "controlActProcess", "subject", "placerGroup", "component2",
+                "observationRequest", "location", "serviceDeliveryLocation",
+                "serviceProviderOrganization", "id", "item", "extension");
+    }
+
+    /**
+     * 浠� dataMap 涓彁鍙� encounter 鏁版嵁銆�
+     *
+     * <p>鎶ユ枃璺緞锛歿@code controlActProcess.subject.observationRequest.componentOf1.encounter}銆�
+     *
+     * <p><b>娉ㄦ剰</b>锛歿@code componentOf1} 浣嶄簬 {@code observationRequest} 涔嬩笅锛�
+     * 涓� {@code id}銆亄@code code}銆亄@code component2} 鍚岀骇锛�**涓嶆槸** {@code subject} 鐨勭洿鎺ュ瓙鑺傜偣銆�
+     * 鏋勯�犳祴璇曟姤鏂囨垨瑙f瀽鏃ュ織鏃跺鏄撴悶閿欏眰绾э紝姝ゅ閫愮骇鍒ょ┖浠ヤ究瀹氫綅銆�
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> extractEncounter(Map<String, Object> dataMap) {
+        Map<String, Object> cap = (Map<String, Object>) dataMap.get("controlActProcess");
+        if (cap == null) {
+            return null;
+        }
+        Map<String, Object> subject = (Map<String, Object>) cap.get("subject");
+        if (subject == null) {
+            return null;
+        }
+        Map<String, Object> observationRequest = (Map<String, Object>) subject.get("observationRequest");
+        if (observationRequest == null) {
+            return null;
+        }
+        Object componentOf1 = observationRequest.get("componentOf1");
+        if (componentOf1 == null) {
+            return null;
+        }
+        return (Map<String, Object>) ((Map<String, Object>) componentOf1).get("encounter");
+    }
+
+    /**
+     * 妫�鏌ラ绾︾姸鎬佹柊澧�/鏇存柊
+     * 锛坅ction: {@code S050401}/{@code S050402} 鏂板銆亄@code S050501}/{@code S050502} 鏇存柊锛夈��
+     *
+     * <h3>鏈鏀瑰姩</h3>
+     * <ol>
+     *   <li><b>鍒犻櫎姝讳唬鐮�</b>锛氬師瀹炵幇鍦ㄨ褰曚笉瀛樺湪鏃惰皟鐢� {@code handleAppointmentCreate(dataMap)}锛�
+     *       浣嗘湰鏈嶅姟鐨勬姤鏂囩粨鏋勬槸 {@code subject.actAppointment}锛岃�� create 鏈熸湜
+     *       {@code subject.observationRequest} 鈥斺�� 涓よ�呭畬鍏ㄤ笉鍏煎锛寋@code extractEncounter}
+     *       蹇呯劧杩斿洖 null 骞舵姏寮傚父锛涘師绱ч殢鍏跺悗鐨� {@code log.warn("鏈壘鍒伴绾﹁褰�")}
+     *       鍥犳**姘歌繙涓嶄細鎵ц**銆傜幇鏀逛负鏄庣‘璁版棩蹇� + 瀹归敊鏂板銆�</li>
+     *   <li><b>鐘舵�佸綊涓�鍖�</b>锛歿@code ZDY.statusCode} 鏄�**棰勭害鐘舵��**锛堝 1=宸查绾︼級锛�
+     *       涓� {@code S0405} 鐨�**妫�鏌ョ姸鎬佺紪鐮�**璇箟涓嶅悓锛屼絾鍚屽啓鍏� {@code status} 鍒椼��
+     *       杩欓噷缁熶竴璧� {@link HisCheckStatusEnum#normalize(String)} 鏍¢獙銆�</li>
+     *   <li><b>瀹归敊鏂板</b>锛圦4 瑕佹眰锛夛細璁板綍涓嶅瓨鍦ㄦ椂璁� WARN 骞朵緷鎹湰鎶ユ枃鎻掑叆鏂拌褰曘��</li>
+     *   <li><b>鍚敤鏈娇鐢ㄥ瓧娈�</b>锛圦7 瑕佹眰锛夛細{@code ZDY.remark}锛堟敞鎰忎簨椤癸級銆�
+     *       {@code ZDY.area}锛堜綅缃級銆亄@code ZDY.roomcode}/{@code roomname}锛堟埧闂达級銆�
+     *       {@code patient.id.item[4]}锛堥绾︽帓搴忓彿锛夈��</li>
+     *   <li><b>绌哄�奸槻寰�</b>锛氬師瀹炵幇鐩存帴 {@code LocalDateTime.parse(bookTimeStr)}
+     *       涓� {@code Integer.parseInt(bookSrc)}锛屾姤鏂囩己瀛楁鏃朵細鎶涘紓甯稿鑷存暣鏉℃秷鎭け璐ャ��</li>
+     * </ol>
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleCheckAppointmentUpdate(Map<String, Object> dataMap) {
+        Map<String, Object> controlActProcess = (Map<String, Object>) dataMap.get("controlActProcess");
+        if (controlActProcess == null) {
+            throw exception(HIS_MESSAGE_PARSE_FAIL);
+        }
+        Map<String, Object> subject = (Map<String, Object>) controlActProcess.get("subject");
+        Map<String, Object> actAppointment = subject == null ? null
+                : (Map<String, Object>) subject.get("actAppointment");
+        if (actAppointment == null) {
+            throw exception(HIS_MESSAGE_PARSE_FAIL);
+        }
+
+        // 1. 鑾峰彇鐢宠缂栧彿銆�
+        //    娉ㄦ剰锛氭帴鍙f枃妗e畾涔夐绾﹀彿鍦� actAppointment.id.extension锛堝 3360270锛夛紝
+        //    鑰屽疄闄呮姤鏂囧悓鏃舵彁渚涗簡 referencedOrder[].actOrder.id.extension锛堝 2126091700001锛�
+        //    涓� S0201ECG 鍒涘缓鐨勭敵璇峰崟鍙蜂竴鑷达級銆傛湰鍦� appointment.apply_no 瀛樼殑鏄悗鑰咃紝
+        //    鍥犳**浼樺厛鍙� referencedOrder**锛屽彇涓嶅埌鍐嶅洖閫�鍒� actAppointment.id銆�
+        String applyNo = extractApplyNoFromCheckAppointment(actAppointment);
+        if (StringUtils.isEmpty(applyNo)) {
+            log.warn("[handleCheckAppointmentUpdate][鏈壘鍒扮敵璇风紪鍙凤紝蹇界暐] dataMap={}", dataMap);
+            throw exception(HIS_APPLY_NO_MISSING);
+        }
+
+        // 1.1 銆愬繀鍋氳繃婊ゃ�慡050401/S050501 鍚屾牱鏄�**鍏ㄩ櫌閫氱敤**鏈嶅姟锛屼細鎺ㄩ�佹斁灏�/瓒呭0绛�
+        //     闈炲績鐢电瀹ょ殑棰勭害鍗曪紙瀹炴祴 2026-09-17锛歋050401 109/256銆丼050501 4/19 涓洪潪蹇冪數锛�
+        //     妫�鏌ラ」鐩"鍙宠倶姝d晶浣�/2鍙锋満鎴�(DR)"銆�"褰╄秴鍙充笅鑲㈠姩鑴�/5鍙疯瘖瀹�"锛夈��
+        //     姝ゅ墠璇ュ鐞嗗櫒**娌℃湁浠讳綍杩囨护**锛屽鑷磋繖浜涘崟琚綋浣滃績鐢电敵璇峰崟閲囬泦鍏ュ簱銆�
+        //     闈炲績鐢靛崟鐩存帴璺宠繃锛堝彧璁版棩蹇椼�佷笉钀藉簱銆佷笉杩斿洖閿欒锛岄伩鍏� HIS 鍙嶅閲嶆帹锛夈��
+        Map<String, Object> zdyNode = (Map<String, Object>) actAppointment.get("ZDY");
+        String checkDeptCode = extractDeptCodeFromCheckAppointment(actAppointment);
+        if (!hisEcgFilter.isEcgOrderWithZdy("S050401/S050501", applyNo, checkDeptCode, zdyNode)) {
+            return;
+        }
+
+        // 2. 瑙f瀽棰勭害鏃堕棿锛堝彲鑳戒负绌猴紝鍘熷疄鐜颁細鐩存帴鎶涘紓甯革級
+        Map<String, Object> zdy = zdyNode;
+        Map<String, Object> effectiveTime = (Map<String, Object>) actAppointment.get("effectiveTime");
+        Map<String, Object> anyTime = effectiveTime == null ? null
+                : (Map<String, Object>) effectiveTime.get("any");
+        String bookTimeStr = anyTime == null ? null : (String) anyTime.get("value");
+        LocalDateTime bookTime = parseDateTime(bookTimeStr);
+        LocalDate bookDate = bookTime == null ? null : bookTime.toLocalDate();
+
+        // 3. 鏃堕棿娈碉紙ZDY.time锛屽舰濡� "2026-09-17 08:30-09:00"锛夛紝瑙f瀽澶辫触涓嶅奖鍝嶅叾浠栧瓧娈�
+        Integer bookTimeslot = parseBookTimeslot(zdy == null ? null : (String) zdy.get("time"));
+
+        // 4. 灏辫瘖绫诲埆 鈫� bookSrc锛堟枃妗o細actAppointment.code.code锛屼綇闄�/闂ㄨ瘖/鎬ヨ瘖浠g爜锛�
+        Map<String, Object> code = (Map<String, Object>) actAppointment.get("code");
+        String bookSrc = code == null ? null : (String) code.get("code");
+        Integer bookSrcValue = parseIntegerQuietly(bookSrc);
+
+        // 5. 棰勭害鐘舵�佸綊涓�鍖栵紙ZDY.statusCode锛�
+        String rawStatus = zdy == null || zdy.get("statusCode") == null
+                ? null : zdy.get("statusCode").toString();
+        String status = HisCheckStatusEnum.normalize(rawStatus);
+        if (status == null && rawStatus != null) {
+            log.warn("[handleCheckAppointmentUpdate][棰勭害鐘舵�佺紪鐮佷笉鍦ㄧ紪鐮佽〃鍐咃紝灏嗕笉鏇存柊鐘舵�乚 applyNo={} rawStatus={}",
+                    applyNo, rawStatus);
+        }
+
+        // 6. 鍖归厤鏈湴璁板綍
+        AppointmentDO appointment = appointmentMapper.getByApplyNo(applyNo);
+        if (appointment == null) {
+            log.warn("[handleCheckAppointmentUpdate][鏈壘鍒伴绾﹁褰曪紝鎵ц瀹归敊鏂板] applyNo={} status={}",
+                    applyNo, status);
+            insertAppointmentFromCheckAppointment(applyNo, actAppointment, bookDate, bookTime,
+                    bookTimeslot, bookSrcValue, status);
+            return;
+        }
+
+        // 7. 鏇存柊宸叉湁璁板綍
+        if (bookDate != null) {
+            appointment.setBookDate(bookDate);
+        }
+        if (bookTime != null) {
+            appointment.setBookTime(bookTime);
+        }
+        if (bookTimeslot != null) {
+            appointment.setBookTimeslot(bookTimeslot);
+        }
+        if (bookSrcValue != null) {
+            appointment.setBookSrc(bookSrcValue);
+        }
+        if (status != null) {
+            appointment.setStatus(status);
+        }
+        // 鈥斺�� HIS 琛ュ厖瀛楁锛堥殢琛ㄦ柊澧炲垪涓�璧峰惎鐢級鈥斺��
+        // 娉ㄦ剰锛歳emark 鍙兘闀胯揪鏁扮櫨瀛楃锛堝瀹炴祴蹇冪數鍥捐繍鍔ㄨ瘯楠屾敞鎰忎簨椤� 474 瀛楋級锛�
+        // 蹇呴』鎸夊垪瀹芥埅鏂紝鍚﹀垯 Data too long 浼氬鑷存暣涓簨鍔″洖婊氾紙瑙� truncate 鏂规硶璇存槑锛�
+        if (zdy != null) {
+            Object remark = zdy.get("remark");
+            if (remark != null && StringUtils.isNotEmpty(remark.toString())) {
+                appointment.setRemark(truncate(remark.toString(), REMARK_MAX_LEN));
+            }
+            Object roomName = zdy.get("roomname");
+            if (roomName != null && StringUtils.isNotEmpty(roomName.toString())) {
+                appointment.setRoomName(truncate(roomName.toString(), ROOM_NAME_MAX_LEN));
+            }
+            // 妫�鏌ョ被鍨嬶細**浠呭湪涓虹┖鏃惰ˉ**
+            //   绛栫暐 (a)锛氫笉瑕嗙洊宸叉湁鍊硷紝閬垮厤鏀瑰潖鏁版嵁锛堜緥濡傝鍗曟浘鐢� S0201ECG 姝g‘鍐欏叆杩囩被鍨嬶級銆�
+            //   涓庡閿欐柊澧炲垎鏀繚鎸佸悓涓�閫昏緫銆�
+            if (appointment.getBookCheckType() == null) {
+                Integer checkTypeValue = resolveBookCheckTypeFromZdy(zdy, applyNo);
+                if (checkTypeValue != null) {
+                    appointment.setBookCheckType(checkTypeValue);
+                }
+            }
+        }
+        // 棰勭害鎺掑簭鍙凤紙patient.id.item[4]锛�
+        Integer seqNum = extractBookSeqNum(actAppointment);
+        if (seqNum != null) {
+            appointment.setBookSeqNum(seqNum);
+        }
+        appointmentMapper.updateById(appointment);
+    }
+
+    /**
+     * 鍙栨鏌ラ绾︽姤鏂囦腑鐨勬墽琛�/鐢宠绉戝缂栫爜銆�
+     * <p>
+     * 鎶ユ枃璺緞锛歿@code actAppointment.subject.patient.providerOrganization.id.item.extension}
+     * 锛堝疄娴嬫牱渚� {@code ZY012}锛夈�備粎鐢ㄤ簬蹇冪數鍒ゅ畾鐨�"渚濇嵁 C"锛堥渶閰嶇疆鐧藉悕鍗曟墠鐢熸晥锛夈��
+     */
+    private String extractDeptCodeFromCheckAppointment(Map<String, Object> actAppointment) {
+        return digText(actAppointment, "subject", "patient", "providerOrganization", "id",
+                "item", "extension");
+    }
+
+    /**
+     * 棰勭害鎺掑簭鍙峰湪鎶ユ枃涓殑 root 鏍囪瘑銆�
+     * <p>
+     * 鎺ュ彛鏂囨。 6.4 鐨勭ず渚嬮噷鎶娿�岄绾︽帓搴忓彿銆嶆敞鍦� {@code item[4]} 浣嶇疆锛�
+     * 浣�**瀹為檯鎶ユ枃鐨� root 涓庝綅缃笉涓�鑷�**锛氱湡瀹炴姤鏂囷紙2026-09-17 鏃ュ織锛変负
+     * <pre>
+     *   item[0] root=2.16.156.10011.2.5.1.5    鍩烮D
+     *   item[1] root=2.16.156.10011.2.5.1.4    鎮h�匢D
+     *   item[2] root=2.16.156.10011.1.10       闂紙鎬ワ級璇婂彿
+     *   item[3] root=2.16.156.10011.1.12       浣忛櫌鍙�
+     *   item[4] root=2.16.156.10011.2.5.1.8    灏辫瘖娆℃暟   鈫� 涓嶆槸鎺掑簭鍙�
+     *   item[5] root=2.16.156.10011.2.5.1.9    灏辫瘖娴佹按鍙�
+     *   item[6] root=2.16.156.10011.2.5.1.11   棰勭害鎺掑簭鍙� 鈫� 瀹為檯鍦ㄦ
+     * </pre>
+     * 鍥犳**鎸� root 鍖归厤**鑰岄潪鎸変綅缃彇鍊硷紝閬垮厤浣嶇疆婕傜Щ瀵艰嚧鍙栭敊瀛楁銆�
+     */
+    private static final String ROOT_BOOK_SEQ_NUM = "2.16.156.10011.2.5.1.11";
+
+    /**
+     * 鍙栥�岄绾︽帓搴忓彿銆嶃��
+     * <p>
+     * 鎶ユ枃璺緞锛歿@code actAppointment.subject.patient.id.item[]} 涓�
+     * {@code root=2.16.156.10011.2.5.1.11} 鐨勯偅涓�椤圭殑 {@code extension}銆�
+     *
+     * @return 鎺掑簭鍙凤紱鍙栦笉鍒版椂杩斿洖 null
+     */
+    @SuppressWarnings("unchecked")
+    private Integer extractBookSeqNum(Map<String, Object> actAppointment) {
+        List<?> items = asList(actAppointment, "subject", "patient", "id", "item");
+        for (Object it : items) {
+            if (!(it instanceof Map)) {
+                continue;
+            }
+            Map<String, Object> item = (Map<String, Object>) it;
+            Object root = item.get("root");
+            if (root != null && ROOT_BOOK_SEQ_NUM.equals(root.toString().trim())) {
+                Object ext = item.get("extension");
+                return ext == null ? null : parseIntegerQuietly(ext.toString());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鍙栨鏌ラ绾︽姤鏂囩殑鐢宠鍗曞彿銆�
+     * <p>
+     * 浼樺厛 {@code referencedOrder[0].actOrder.id.extension}锛堜笌 S0201ECG 鍒涘缓鐨勭敵璇峰崟鍙蜂竴鑷达紝
+     * 瀹炴祴 {@code 2126091700001}锛夛紱鍙栦笉鍒版椂鍥為��鍒� {@code actAppointment.id.extension}
+     * 锛堟帴鍙f枃妗e畾涔夌殑棰勭害鍙凤紝瀹炴祴 {@code 3360270}锛夈��
+     */
+    @SuppressWarnings("unchecked")
+    private String extractApplyNoFromCheckAppointment(Map<String, Object> actAppointment) {
+        Object referencedOrders = actAppointment.get("referencedOrder");
+        if (referencedOrders instanceof List) {
+            List<?> list = (List<?>) referencedOrders;
+            if (!list.isEmpty() && list.get(0) instanceof Map) {
+                Map<String, Object> first = (Map<String, Object>) list.get(0);
+                Map<String, Object> actOrder = (Map<String, Object>) first.get("actOrder");
+                if (actOrder != null) {
+                    Map<String, Object> id = (Map<String, Object>) actOrder.get("id");
+                    if (id != null && id.get("extension") != null) {
+                        return id.get("extension").toString();
+                    }
+                }
+            }
+        }
+        Map<String, Object> id = (Map<String, Object>) actAppointment.get("id");
+        if (id != null && id.get("extension") != null) {
+            return id.get("extension").toString();
+        }
+        return null;
+    }
+
+    /** 渚濇嵁妫�鏌ラ绾︽姤鏂囨彃鍏ユ柊璁板綍锛圦4 瀹归敊鏂板锛� */
+    @SuppressWarnings("unchecked")
+    private void insertAppointmentFromCheckAppointment(String applyNo, Map<String, Object> actAppointment,
+                                                       LocalDate bookDate, LocalDateTime bookTime,
+                                                       Integer bookTimeslot, Integer bookSrcValue, String status) {
+        AppointmentDO appointment = new AppointmentDO();
+        appointment.setApplyNo(applyNo);
+        appointment.setBookDate(bookDate);
+        appointment.setBookTime(bookTime);
+        appointment.setBookTimeslot(bookTimeslot);
+        appointment.setBookSrc(bookSrcValue);
+        appointment.setStatus(status);
+
+        // 鎮h�呮潵婧愶紙pat_src锛夛細涓� S0201ECG 淇濇寔鍚屼竴鍋氭硶 鈥斺�� 鐩存帴鍙栨姤鏂囦腑鐨勬偅鑰呯被鍨嬩唬鐮併��
+        //
+        // 鈿狅笍 宸茬煡椋庨櫓锛堜繚鐣欑幇鐘讹紝涓嶅湪鏈鏀瑰姩鑼冨洿鍐咃級锛�
+        //    瀹炴祴 S050401 鎶ユ枃瀵规湰鏉′綋妫�鍗曠粰鍑虹殑鏄� code=3 / displayName=浣撴锛�
+        //    鑰屾湰鍦板瓧鍏革紙ecg_patient_source锛変笌鏁版嵁鍒嗗竷閮借〃鏄庛�屼綋妫� = 4銆佷綇闄� = 3銆嶏細
+        //    鍏ㄥ簱 pat_src=4 鐨� 55,729 鏉$瀹ゅ潎涓恒�屽仴搴风鐞嗕腑蹇冦�嶏紝88 鍓嶇紑锛堜綋妫�锛夊崟 55,874 鏉′负 pat_src=4銆�
+        //    鍗虫姤鏂� code 涓庢湰鍦板瓧鍏哥紪鐮佸苟涓嶄竴鑷淬�傛澶�**鍒绘剰娌跨敤鎶ユ枃鍘熷��**浠ヤ笌 S0201ECG 琛屼负涓�鑷达紝
+        //    濡傞渶鎸夊悕绉扮籂姝o紙浣撴鈫�4锛夛紝搴旂粺涓�鍦ㄤ袱澶勪竴璧锋敼锛岄伩鍏嶄袱鏉℃祦鍐欏嚭涓嶅悓鍊笺��
+        Map<String, Object> codeNode = (Map<String, Object>) actAppointment.get("code");
+        Integer patSrcValue = codeNode == null ? null : parseIntegerQuietly(str(codeNode.get("code")));
+        if (patSrcValue != null) {
+            appointment.setPatSrc(patSrcValue);
+        } else {
+            log.warn("[insertAppointmentFromCheckAppointment][鎶ユ枃缂烘偅鑰呯被鍨�(actAppointment.code.code)锛�"
+                    + "pat_src 灏嗕负绌篯 applyNo={} code={}", applyNo, codeNode);
+        }
+
+        Map<String, Object> zdy = (Map<String, Object>) actAppointment.get("ZDY");
+        // ZDY 涓殑 remark锛堟敞鎰忎簨椤癸級涓� roomname锛堟埧闂村悕绉帮級宸查殢琛ㄦ柊澧炲垪钀藉簱锛�
+        // area锛堜綅缃級涓� roomcode锛堟埧闂寸紪鐮侊級鐩墠浠嶆棤瀵瑰簲鍒楋紝鏆備笉淇濆瓨銆�
+        if (zdy != null) {
+            Object remark = zdy.get("remark");
+            if (remark != null && StringUtils.isNotEmpty(remark.toString())) {
+                appointment.setRemark(truncate(remark.toString(), REMARK_MAX_LEN));
+            }
+            Object roomName = zdy.get("roomname");
+            if (roomName != null && StringUtils.isNotEmpty(roomName.toString())) {
+                appointment.setRoomName(truncate(roomName.toString(), ROOM_NAME_MAX_LEN));
+            }
+            // 妫�鏌ョ被鍨嬶細鎶ユ枃閲岀殑 ZDY.jcxmcode / ZDY.jcxmname
+            //
+            // 銆愪慨澶嶃�戝師鍏堟澶�**瀹屽叏娌℃湁**瑙f瀽妫�鏌ョ被鍨嬶紝瀵艰嚧銆孲050401 鍏堝埌銆佷笖灏氭棤 S0201ECG 璁板綍銆�
+            // 鏃跺閿欐柊澧炲嚭鏉ョ殑 appointment.book_check_type 姘歌繙涓虹┖銆�
+            // 瀹炴祴 2026-09-29~10-09 鍏� 279 鏉★紙濡� apply_no=8826092420371106锛�
+            // 鎶ユ枃 ZDY 閲� jcxmcode=201652 / jcxmname=骞虫澘杩愬姩璇曢獙 鏄庢槑瀛樺湪锛夈��
+            if (appointment.getBookCheckType() == null) {
+                Integer checkTypeValue = resolveBookCheckTypeFromZdy(zdy, applyNo);
+                if (checkTypeValue != null) {
+                    appointment.setBookCheckType(checkTypeValue);
+                }
+            }
+        }
+        // 棰勭害鎺掑簭鍙凤紙patient.id.item[4]锛�
+        Integer bookSeqNum = extractBookSeqNum(actAppointment);
+        if (bookSeqNum != null) {
+            appointment.setBookSeqNum(bookSeqNum);
+        }
+
+        // 鎮h�呬俊鎭�
+        Map<String, Object> subjectNode = (Map<String, Object>) actAppointment.get("subject");
+        if (subjectNode != null) {
+            Map<String, Object> patient = (Map<String, Object>) subjectNode.get("patient");
+            if (patient != null) {
+                List<?> patIdItems = Optional.ofNullable(patient.get("id"))
+                        .map(id -> (Map<String, Object>) id).map(id -> id.get("item"))
+                        .filter(it -> it instanceof List).map(it -> (List<?>) it)
+                        .orElse(Collections.emptyList());
+                if (patIdItems.size() > 1) {
+                    appointment.setPatId((String) ((Map<String, Object>) patIdItems.get(1)).get("extension"));
+                }
+                if (patIdItems.size() > 2) {
+                    appointment.setOutpatientNo((String) ((Map<String, Object>) patIdItems.get(2)).get("extension"));
+                }
+                if (patIdItems.size() > 3) {
+                    appointment.setHospitalNo((String) ((Map<String, Object>) patIdItems.get(3)).get("extension"));
+                }
+                // 灏辫瘖娴佹按鍙凤紙episode_id锛夛細patient.id.item[5]
+                //
+                // 涓� S0201ECG 鍚屾簮锛堥偅杈瑰彇鑷� placerGroup.componentOf1.encounter.id.item[1]锛�
+                // 瀹炴祴涓ゅ鍊肩浉鍚岋細鏈潯鍧囦负 882609242037锛夈��
+                // 銆愪慨澶嶃�戝師鍏堝閿欐柊澧�**瀹屽叏娌℃湁**鍐欏叆灏辫瘖娴佹按鍙凤紝瀵艰嚧璇ュ瓧娈典负绌恒��
+                if (patIdItems.size() > 5) {
+                    String episodeId = (String) ((Map<String, Object>) patIdItems.get(5)).get("extension");
+                    if (StringUtils.isNotEmpty(episodeId)) {
+                        appointment.setEpisodeId(episodeId);
+                    }
+                }
+                // 棰勭害鎺掑簭鍙峰凡鍦ㄤ笂鏂归�氳繃 extractBookSeqNum 缁熶竴璧嬪�硷紝姝ゅ涓嶅啀閲嶅澶勭悊
+                // 鎮h�呭鍚�
+                Optional.ofNullable(patient.get("patientPerson"))
+                        .map(p -> (Map<String, Object>) p)
+                        .map(p -> (Map<String, Object>) p.get("name"))
+                        .map(n -> (Map<String, Object>) n.get("item"))
+                        .map(i -> (Map<String, Object>) i.get("part"))
+                        .map(part -> (String) part.get("value"))
+                        .ifPresent(appointment::setPatName);
+                // 鐥呬汉绉戝锛坧roviderOrganization锛夆�斺�� Q7 鍚敤
+                Optional.ofNullable(patient.get("providerOrganization"))
+                        .map(org -> (Map<String, Object>) org)
+                        .ifPresent(org -> {
+                            Optional.ofNullable(org.get("id"))
+                                    .map(id -> (Map<String, Object>) id)
+                                    .map(id -> (Map<String, Object>) id.get("item"))
+                                    .map(item -> (Map<String, Object>) item)
+                                    .map(item -> (String) item.get("extension"))
+                                    .ifPresent(appointment::setPatDeptCode);
+                            Optional.ofNullable(org.get("name"))
+                                    .map(n -> (Map<String, Object>) n)
+                                    .map(n -> (Map<String, Object>) n.get("item"))
+                                    .map(i -> (Map<String, Object>) i.get("part"))
+                                    .map(part -> (String) part.get("value"))
+                                    .ifPresent(appointment::setPatDeptDesc);
+                        });
+            }
+        }
+
+        if (StringUtils.isEmpty(appointment.getPatName())) {
+            log.warn("[insertAppointmentFromCheckAppointment][瀹归敊鏂板鐨勮褰曠己灏戞偅鑰呭鍚峕 applyNo={}", applyNo);
+        }
+        // appointment.pat_id / pat_name 鍦ㄦ暟鎹簱涓负 NOT NULL锛�
+        // 缂哄�兼椂鑻ョ洿鎺� insert 浼氭姏 SQL 寮傚父骞跺鑷存暣涓簨鍔″洖婊氥��
+        // 杩欓噷鎻愬墠缁欏嚭鏄庣‘閿欒锛屼究浜� HIS 渚у畾浣嶆姤鏂囬棶棰樸��
+        if (StringUtils.isEmpty(appointment.getPatId())) {
+            log.error("[insertAppointmentFromCheckAppointment][鎶ユ枃缂烘偅鑰呯紪鍙�(pat_id)锛屾棤娉曟柊澧瀅 applyNo={}", applyNo);
+            throw exception(PATIENT_NOT_EXISTS);
+        }
+        if (appointment.getPatGender() == null) {
+            log.warn("[insertAppointmentFromCheckAppointment][鎶ユ枃鏈彁渚涙偅鑰呮�у埆锛屾寜鏈煡(0)鍐欏叆] applyNo={}", applyNo);
+            appointment.setPatGender(UNKNOWN_GENDER);
+        }
+        // 鎮h�呭浐鏈夊睘鎬у洖濉紙鐢熸棩 / 鐢佃瘽 / 韬唤璇� / 鍦板潃锛�
+        //
+        // 銆愪负浠�涔堥渶瑕併�慡050401 鎶ユ枃鐨� patient.patientPerson **鍙甫濮撳悕**锛�
+        // 涓嶅惈鐢熸棩/鐢佃瘽/韬唤璇侊紙S0201ECG 鏈夈�丼0405 涔熸湁锛屼絾杩欎袱鏉℃姤鏂囧湪鏈満鏅病鏈夊厛鍒帮級锛�
+        // 浜庢槸瀹归敊鏂板鍑烘潵鐨勮褰曡繖浜涘瓧娈靛叏涓虹┖锛屼笌 S0201ECG 寤虹殑鍗曠浉姣�"缂哄瓧娈�"銆�
+        //
+        // 銆愪负浠�涔堣繖鏍疯ˉ鏄噯纭殑銆戣繖浜涙槸**鎮h�呭浐鏈夊睘鎬�**锛堜笉鏄祦姘村睘鎬э級锛�
+        // 鍚屼竴 pat_id 鐨勫巻鍙茶褰曢噷蹇呯劧涓�鑷达紙鍏ㄥ簱濉厖鐜� 99%锛夈��
+        // 娉ㄦ剰锛氬紑鍗曞尰鐢�/寮�鍗曟椂闂村睘娴佹按灞炴�э紝**鍒绘剰涓嶇户鎵�**锛堥偅灞炰簬鎺ㄦ柇锛屽彲鑳藉紶鍐犳潕鎴达級銆�
+        inheritPatientDemographics(appointment, applyNo);
+
+        AppointmentSaveReqVO saveReqVO = BeanUtils.toBean(appointment, AppointmentSaveReqVO.class);
+        createAppointment(saveReqVO);
+    }
+
+    /**
+     * 瀹归敊鏂板鏃讹紝浠庤鎮h�呮渶杩戜竴鏉¤褰曠户鎵�**鎮h�呭浐鏈夊睘鎬�**銆�
+     *
+     * <p>鍙ˉ銆屾湰娆′负绌恒�嶇殑瀛楁锛岀粷涓嶈鐩栨姤鏂囧凡鎻愪緵鐨勫�笺��
+     * 缁ф壙澶辫触锛堟棤鍘嗗彶璁板綍 / 鏌ヨ寮傚父锛変笉褰卞搷钀藉簱锛屼粎璁版棩蹇椼��</p>
+     *
+     * <p><b>鍒绘剰涓嶇户鎵�</b>鐨勫瓧娈碉細{@code doctor}/{@code doctor_code}/{@code doctor_time}
+     * 锛堝紑鍗曞尰鐢熶笌寮�鍗曟椂闂村睘娴佹按灞炴�э紝鎶ユ枃鏈彁渚涙椂鍙兘鎺ㄦ柇锛屽疄娴嬪悓鎮h�呭悓鏃ヤ竴鑷寸巼 90%锛�
+     * 瀛樺湪寮犲啝鏉庢埓椋庨櫓锛屾晠鎸変笟鍔$‘璁や繚鎸佷负绌猴級銆亄@code register_date}
+     * 锛堝彧搴斿湪 HIS 鎶ャ�屾鏌ョ櫥璁般�嶇姸鎬佹椂鍐欏叆锛夈��</p>
+     *
+     * @param target  鏈瀹归敊鏂板鐨勫璞★紙灏卞湴琛ュ叏锛�
+     * @param applyNo 鐢宠鍗曞彿锛屼粎鐢ㄤ簬鏃ュ織
+     */
+    private void inheritPatientDemographics(AppointmentDO target, String applyNo) {
+        if (StringUtils.isEmpty(target.getPatId())) {
+            return;
+        }
+        // 涓夐」鍏抽敭浜哄彛瀛︿俊鎭姤鏂囬兘缁欎簡灏辨棤闇�缁ф壙锛堟姤鏂囧�间紭鍏堬級
+        if (target.getPatBirthday() != null
+                && StringUtils.isNotEmpty(target.getPatIdentityId())
+                && StringUtils.isNotEmpty(target.getPatMobile())) {
+            return;
+        }
+        AppointmentDO sibling;
+        try {
+            sibling = appointmentMapper.getLatestByPatId(target.getPatId());
+        } catch (Exception e) {
+            log.warn("[inheritPatientDemographics][鏌ヨ鍚屾偅鑰呭巻鍙茶褰曞け璐ワ紝璺宠繃缁ф壙] applyNo={} patId={} err={}",
+                    applyNo, target.getPatId(), e.getMessage());
+            return;
+        }
+        if (sibling == null) {
+            log.warn("[inheritPatientDemographics][璇ユ偅鑰呮棤鍘嗗彶璁板綍鍙户鎵匡紝鎮h�呬俊鎭皢涓嶅畬鏁碷 "
+                    + "applyNo={} patId={}", applyNo, target.getPatId());
+            return;
+        }
+        StringBuilder inherited = new StringBuilder();
+        if (target.getPatBirthday() == null && sibling.getPatBirthday() != null) {
+            target.setPatBirthday(sibling.getPatBirthday());
+            inherited.append("pat_birthday ");
+        }
+        if (StringUtils.isEmpty(target.getPatIdentityId()) && StringUtils.isNotEmpty(sibling.getPatIdentityId())) {
+            target.setPatIdentityId(sibling.getPatIdentityId());
+            inherited.append("pat_identity_id ");
+        }
+        if (StringUtils.isEmpty(target.getPatMobile()) && StringUtils.isNotEmpty(sibling.getPatMobile())) {
+            target.setPatMobile(sibling.getPatMobile());
+            inherited.append("pat_mobile ");
+        }
+        if (StringUtils.isEmpty(target.getPatPhone()) && StringUtils.isNotEmpty(sibling.getPatPhone())) {
+            target.setPatPhone(sibling.getPatPhone());
+            inherited.append("pat_phone ");
+        }
+        if (StringUtils.isEmpty(target.getPatAddr()) && StringUtils.isNotEmpty(sibling.getPatAddr())) {
+            target.setPatAddr(sibling.getPatAddr());
+            inherited.append("pat_addr ");
+        }
+        if (inherited.length() > 0) {
+            log.info("[inheritPatientDemographics][S050401 鎶ユ枃涓嶅惈鎮h�呬汉鍙e淇℃伅锛�"
+                            + "宸蹭粠鍚屾偅鑰呮渶杩戣褰曠户鎵縘 applyNo={} patId={} fromApplyNo={} fields=[{}]",
+                    applyNo, target.getPatId(), sibling.getApplyNo(), inherited.toString().trim());
+        }
+    }
+
+    /** 瑙f瀽 {@code yyyyMMddHHmmss} 鏃堕棿锛屽け璐ユ垨涓虹┖杩斿洖 null锛堜笉鎶涘紓甯革級 */
+    private LocalDateTime parseDateTime(String value) {
+        if (StringUtils.isEmpty(value)) {
+            return null;
+        }
+        try {
+            return LocalDateTime.parse(value.trim(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+        } catch (Exception e) {
+            log.warn("[parseDateTime][鏃堕棿鏍煎紡闈炴硶锛屽拷鐣 value={}", value);
+            return null;
+        }
+    }
+
+    /**
+     * 瑙f瀽 HIS 鎶ユ枃涓殑銆屽紑鍗曟椂闂淬��({@code author/time/value})銆�
+     *
+     * <h3>鑳屾櫙锛堝疄娴嬬粨璁猴紝2026-10 鎺掓煡锛�</h3>
+     * 搴撲腑绾� <b>61%</b> 鐨� {@code appointment.doctor_time} 涓� {@code yyyy-MM-dd 00:00:00}銆�
+     * 缁忔牳鏌�**涓嶆槸**鏈」鐩殑鏍煎紡鍖栫己闄凤細
+     * <ul>
+     *   <li>鍒楃被鍨嬫槸 {@code datetime}锛屽啓鍏ョ殑鏄� {@code java.util.Date}锛屼繚鐣欐椂鍒嗙锛�</li>
+     *   <li>鐪熷疄鎶ユ枃鏍蜂緥涓瀛楁褰㈠ {@code 20260917005255}锛堝甫鏃跺垎绉掞級锛�</li>
+     *   <li>鏈夌湡瀹炴椂鍒荤殑璁板綍鍛堟甯稿伐浣滄椂娈靛垎甯冿紙06:00鈫�08:00 涓洪珮宄帮級锛�
+     *       鑰岃惤鍦ㄥ紑璇婂墠鐨� 00:00-05:59 鐨勪粎 15 鏉� 鈥斺�� 璇存槑璇ユ椂娈垫湰灏卞嚑涔庝笉寮�鍗曪紱</li>
+     *   <li>鎸夊氨璇婄被鍒湅锛氶棬璇�({@code book_src=1}) 99.6%銆佷綇闄�({@code book_src=2}) 90.5%
+     *       涓� 0 鐐癸紝涓旇鐜拌薄璐┛ 2026 鍏ㄥ勾锛堜笌 2026-09-27 鐨� {@code register_date}
+     *       鎴柇缂洪櫡鏃犲叧锛夈��</li>
+     * </ul>
+     * 鈬� 缁撹锛�**鏄� HIS 渚ф妸璇ュ瓧娈电殑鏃跺垎绉掑彂鎴愪簡 {@code 000000}**锛堝緢鍙兘鏄�屽彧绮剧‘鍒版棩銆�
+     * 鎴栬ˉ褰�/鎵归噺鍥炰紶鐨勫尰鍢憋級銆�
+     *
+     * <h3>鏈柟娉曞仛浠�涔�</h3>
+     * <ul>
+     *   <li>鎸� {@code yyyyMMddHHmmss} 瑙f瀽锛�</li>
+     *   <li>璇嗗埆銆屾椂鍒嗙 = 000000銆嶇殑**鏃ユ湡鍊�**锛屾墦鍑哄甫**鍘熷鎶ユ枃鍊� + 鐢宠鍗曞彿**鐨� WARN
+     *       鈥斺�� 鍘熷疄鐜板彧 {@code printStackTrace()}锛屽嚭闂鏃舵棤浠庡畾浣嶏紱</li>
+     *   <li><b>浠嶇劧鍐欏叆</b>锛堜繚鐣欐棩鏈熼儴鍒嗭級锛氬洜涓恒�岃窛浠婂ぉ鏁般�嶃�屾寜鏃ユ湡鎺掑簭/绛涢�夈�嶉兘鐢ㄦ棩鏈燂紝
+     *       鍐欏叆 NULL 浼氳杩欎簺鍔熻兘閫�鍖栵紝涓旇鍊肩‘涓� HIS 鎵�鍙戙�佸師鏍蜂繚瀛樻洿鍙拷婧��</li>
+     * </ul>
+     *
+     * @param rawValue HIS 鎶ユ枃鍘熷�硷紙褰㈠ {@code 20260917005255}锛�
+     * @param applyNo  鐢宠鍗曞彿锛屼粎鐢ㄤ簬鏃ュ織瀹氫綅
+     * @return 瑙f瀽鍚庣殑鏃堕棿锛涙牸寮忛潪娉曟椂杩斿洖 {@code null}锛堜笉鍐欏簱锛�
+     */
+    private java.util.Date parseHisDoctorTime(String rawValue, String applyNo) {
+        String v = rawValue == null ? null : rawValue.trim();
+        if (StringUtils.isEmpty(v)) {
+            return null;
+        }
+        if (!v.matches("\\d{14}")) {
+            // 瀹炴祴 HIS 鏂囨。鏍蜂緥涓瓨鍦� 12 浣嶏紙yyyyMMddHHmm锛夌敋鑷崇┖鍊硷紝缁熶竴璁� WARN 渚夸簬鍚� HIS 鍙嶉
+            log.warn("[parseHisDoctorTime][寮�鍗曟椂闂存牸寮忛潪 14 浣嶏紝鏃犳硶瑙f瀽锛宒octor_time 灏嗕负绌篯 "
+                    + "applyNo={} raw={} length={}", applyNo, v, v.length());
+            return null;
+        }
+        try {
+            // 鈿狅笍 蹇呴』 setLenient(false)锛歋impleDateFormat 榛樿瀹芥澗锛�
+            // 浼氭妸闈炴硶鏃ユ湡闈欓粯婊氭垚鍚堟硶鍊硷紙濡� 13 鏈� 鈫� 娆″勾 1 鏈堬級锛屽鑷磋剰鏁版嵁鍏ュ簱銆�
+            // 瀹炴祴锛歭enient 涓� "20261301000000" 琚В鏋愭垚 2027-01-01 涓斾笉鎶ラ敊銆�
+            SimpleDateFormat sdf = new SimpleDateFormat(HIS_DOCTOR_TIME_PATTERN);
+            sdf.setLenient(false);
+            java.util.Date parsed = sdf.parse(v);
+            if (isHisTimeOnlyDate(v)) {
+                log.warn("[parseHisDoctorTime][HIS 寮�鍗曟椂闂存椂鍒嗙涓� 000000锛堟棩鏈熷�硷級锛�"
+                        + "doctor_time 灏嗕繚瀛樹负褰撴棩 00:00:00 鈥斺�� 璇ュ�肩敱 HIS 鎶ユ枃鍘熸牱鑰屾潵] "
+                        + "applyNo={} raw={}", applyNo, v);
+            }
+            return parsed;
+        } catch (Exception e) {
+            log.warn("[parseHisDoctorTime][寮�鍗曟椂闂磋В鏋愬け璐ワ紝doctor_time 灏嗕负绌篯 applyNo={} raw={} err={}",
+                    applyNo, v, e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 鐢� {@code book_timeslot}锛坔hmmhhmm锛屽 8000830=08:00-08:30銆�13301400=13:30-14:00锛夊彇棰勭害鏃跺埢锛堝彧鍚椂鍒嗭級銆�
+     * <p>鐢ㄦ潵缁� S0201ECG 杩欐潯娴佽ˉ {@code book_time}锛氬師瀹炵幇鏁存潯娴佷笉鍐欓绾︽椂闂达紝
+     * 寤哄嚭鏉ョ殑璁板綍 book_date/book_timeslot/book_time 鍏ㄧ┖锛堝疄娴� 211741銆�211962锛夈��</p>
+     */
+    private LocalTime timeOfTimeslot(Integer timeslot) {
+        if (timeslot == null) {
+            return null;
+        }
+        try {
+            String s = String.valueOf(timeslot);
+            if (s.length() < 5) {
+                return null;
+            }
+            String start = s.substring(0, s.length() - 4);   // 鍓嶄竴娈� = 寮�濮嬫椂闂� hhmm
+            int hhmm = Integer.parseInt(start);
+            int hour = hhmm / 100;
+            int minute = hhmm % 100;
+            if (hour > 23 || minute > 59) {
+                return null;
+            }
+            return LocalTime.of(hour, minute);
+        } catch (Exception e) {
+            log.warn("[timeOfTimeslot][鏃堕棿娈佃В鏋愬け璐ワ紝蹇界暐] timeslot={}", timeslot);
+            return null;
+        }
+    }
+
+    /** 瑙f瀽 ZDY.time锛堝舰濡� "2026-09-17 08:30-09:00"锛変负 hhmmhhmm 鏁存暟锛屽け璐ヨ繑鍥� null */
+    private Integer parseBookTimeslot(String timeRange) {
+        if (StringUtils.isEmpty(timeRange)) {
+            return null;
+        }
+        try {
+            String[] parts = timeRange.trim().split("\\s+");
+            if (parts.length < 2) {
+                return null;
+            }
+            String[] times = parts[1].split("-");
+            if (times.length < 2) {
+                return null;
+            }
+            String startTime = times[0].trim().replace(":", "");
+            String endTime = times[1].trim().replace(":", "");
+            return Integer.parseInt(startTime) * 10000 + Integer.parseInt(endTime);
+        } catch (Exception e) {
+            log.warn("[parseBookTimeslot][鏃堕棿娈佃В鏋愬け璐ワ紝蹇界暐] timeRange={}", timeRange);
+            return null;
+        }
+    }
+
+    /** 瀹芥澗鏁存暟瑙f瀽锛氶潪鏁板瓧鎴栫┖杩斿洖 null锛堥伩鍏� Integer.parseInt(null) 鎶涘紓甯革級 */
+    private Integer parseIntegerQuietly(String value) {
+        if (StringUtils.isEmpty(value) || !value.trim().matches("-?\\d+")) {
+            return null;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 銆愭湰闇�姹傦細绱ф�ユ偅鑰呬笉鎺掗槦鐩存帴妫�鏌ャ�戞寜銆岀敵璇峰崟鍙� / 灏辫瘖鍙� / 鎮h�呯紪鍙枫�嶆煡杩戜竴涓湀鐨勯绾﹁褰曘��
+     * <p>
+     * 绐楀彛 [浠婂ぉ-30 澶�, 浠婂ぉ+30 澶锛屾寜鏈獥鍙e紑灞曠殑妫�鏌ラ」鐩繃婊わ紝缁撴灉鎸�**棰勭害鏃堕棿鍊掑簭**銆�
+     */
+    @Override
+    public List<AppointmentDO> getEmergencyCandidates(String code, String checkTypes) {
+        if (StringUtils.isEmpty(code)) {
+            return new ArrayList<>();
+        }
+        List<Integer> checkTypeList = splitToIntList(checkTypes);
+        List<AppointmentDO> list = appointmentMapper.getEmergencyCandidates(code.trim(), checkTypeList);
+        log.info("[getEmergencyCandidates] 绱ф�ュ姞鍋氬�欓�夋煡璇�. code={} checkTypes={} 鍛戒腑={}",
+                code, checkTypes, null == list ? 0 : list.size());
+        return null == list ? new ArrayList<>() : list;
+    }
+
+    /** 鎶� `100,200` 杩欑被閫楀彿鍒嗛殧涓茶В鏋愭垚 List&lt;Integer&gt;锛涢潪娉曢」蹇界暐 */
+    private List<Integer> splitToIntList(String raw) {
+        List<Integer> result = new ArrayList<>();
+        if (StringUtils.isEmpty(raw)) {
+            return result;
+        }
+        for (String part : raw.split("[,锛宂")) {
+            Integer v = parseIntegerQuietly(part);
+            if (null != v && !result.contains(v)) {
+                result.add(v);
+            }
+        }
+        return result;
+    }
+
+    /** 瀹芥澗 Byte 瑙f瀽锛氶潪鏁板瓧鎴栫┖杩斿洖 null锛堢敤浜庢偅鑰呮�у埆绛� tinyint 瀛楁锛� */
+    private Byte parseByteQuietly(String value) {
+        if (StringUtils.isEmpty(value) || !value.trim().matches("-?\\d+")) {
+            return null;
+        }
+        try {
+            return Byte.parseByte(value.trim());
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public void handleAppointmentStatusUpdate(Map<String, Object> dataMap) {
+        if (dataMap == null) {
+            throw exception(APPOINTMENT_STATUS_REGISTER_UPDATE_FAIL);
+        }
+        //鑾峰彇applyNo
+        Map<String, Object> controlActProcess = (Map<String, Object>) dataMap.get("controlActProcess");
+        Map<String, Object> subject = (Map<String, Object>) controlActProcess.get("subject");
+        Map<String, Object> placerGroup = (Map<String, Object>) subject.get("placerGroup");
+        Map<String, Object> component2 = (Map<String, Object>) placerGroup.get("component2");
+        Map<String, Object> observationRequest = (Map<String, Object>) component2.get("observationRequest");
+        Map<String, Object> id = (Map<String, Object>) observationRequest.get("id");
+        Map<String, Object> item = (Map<String, Object>) id.get("item");
+        String applyNo = (String) item.get("extension");
+
+        //鑾峰彇status
+        Map<String, Object> component1 = (Map<String, Object>) observationRequest.get("component1");
+        Map<String, Object> processStep = (Map<String, Object>) component1.get("processStep");
+        Map<String, Object> codeMap = (Map<String, Object>) processStep.get("code");
+        String status = String.valueOf(codeMap.get("code"));
+        Date date = null;
+        if (status.equals("3")) {
+            //璁板綍鐧昏鏃堕棿
+            date = new Date();
+        }
+        // 銆愯瘖鏂棩蹇椼�戠櫥璁版椂闂村啓鍏ュ墠鍚庡甫绫诲瀷淇℃伅锛岀敤浜庡揩閫熷畾浣嶃��00:00:00 鎴柇銆嶇被闂銆�
+        //
+        // 鑳屾櫙锛�2026-09-27 璧� appointment.register_date 鍑虹幇澶ч噺 yyyy-MM-dd 00:00:00锛�
+        //       缁忓疄楠岀‘璁ゆ垚鍥犳槸銆屽啓鍏ュ�兼槸 DATE 璇箟鐨勫�笺�嶈�岄潪椹卞姩闂锛�
+        //         路 java.util.Date + DATETIME 鍒� 鈫� 淇濈暀鏃跺垎绉�
+        //         路 java.sql.Date  / DATE 鍒�     鈫� 蹇呯劧鍙樻垚 00:00:00
+        //      鏈棩蹇楁墦鍗� date 鐨勫疄闄呯被鍚嶄笌鍊硷紝鑻ユ棩蹇楅噷鍑虹幇 java.sql.Date 鎴栭潪 3 鐘舵�佸嵈琚啓锛�
+        //      鍗冲彲鐩存帴瀹氫綅鍐欏叆鏂广��
+        if (date != null) {
+            log.info("[handleAppointmentStatusUpdate][鐧昏鏃堕棿鍐欏叆] applyNo={} status={} "
+                            + "鏃ユ湡瀵硅薄绫诲瀷={} 鍊�={}锛堟椂闂撮儴鍒嗗簲涓哄綋鍓嶆椂鍒伙紝鑻ヤ负 00:00:00 璇存槑琚埅鏂級",
+                    applyNo, status, date.getClass().getName(),
+                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
+        }
+        //鐘舵�佹洿鏂�
+        Integer i = appointmentMapper.updateStatusByApplyNo(applyNo, status, date);
+        log.info("鐢宠鍗曠櫥璁癮pplyNo:{},鐘舵�乻tatus:{},鏇存柊缁撴灉锛歿}", applyNo, status, i);
+
+    }
+
+    public AppointmentDO getByApplyNo(String applyNo) {
+        return appointmentMapper.getByApplyNo(applyNo);
+    }
+}
+

--
Gitblit v1.9.3