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锛屼笖閫昏緫搴斾负 &&
+ * 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<Integer>锛涢潪娉曢」蹇界暐 */
+ 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