From 60ade5154522a042c816abaa1840751011ae0371 Mon Sep 17 00:00:00 2001
From: liusheng <337615773@qq.com>
Date: 星期二, 29 九月 2026 19:15:03 +0800
Subject: [PATCH] 1.省立同德满度问题处理 2.门急诊数据采集优化

---
 smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java                      |  152 +++--
 smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java          |  202 ++++++
 smartor/src/main/java/com/smartor/domain/DTO/DetailTraceDealDTO.java                                |    7 
 smartor/src/main/java/com/smartor/mapper/PatMedOuthospMapper.java                                   |   10 
 smartor/src/main/resources/mapper/smartor/PatMedOuthospMapper.xml                                   |   10 
 smartor/src/main/java/com/smartor/domain/SvyLibTemplateCategory.java                                |    6 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java |   18 
 smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java            |  186 ++++--
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherSlotEnum.java                             |   37 +
 smartor/src/main/java/com/smartor/service/IPatMedOuthospService.java                                |   14 
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherStatusEnum.java                           |   36 +
 smartor/src/main/resources/mapper/smartor/SvyLibTemplageCategoryMapper.xml                          |    6 
 smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java                            |   23 
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherParamEnum.java                            |   56 ++
 smartor/src/main/java/com/smartor/service/impl/PatMedOuthospServiceImpl.java                        |   43 +
 smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailTraceMapper.xml                       |  222 +++++--
 ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java                                        |  467 ++++++++++++++---
 smartor/src/main/java/com/smartor/domain/ServiceSLTDInhospReqVO.java                                |    4 
 smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailTraceMapper.java                       |   36 +
 smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml                            |   69 ++
 20 files changed, 1,304 insertions(+), 300 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
index eda9ed5..3df473e 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskDetailTraceController.java
@@ -62,22 +62,16 @@
     @AddOrgId(field = "orgid", paramIndex = 0, campusField = "campusid")
     @PostMapping("/tracedeallist")
     public Map<String, Object> tracedeallist(@RequestBody DetailTraceDealVO detailTraceDealVO) {
+        // PageUtils.getOffset 杩斿洖鐨勬槸 offset锛孲QL 閲屾寜 limit/offset 浣跨敤
         Integer offset = PageUtils.getOffset(detailTraceDealVO.getPageNum(), detailTraceDealVO.getPageSize());
         detailTraceDealVO.setPageNum(offset);
+        // 涓�娆℃煡璇㈠悓鏃惰繑鍥炲垎椤垫暟鎹�侀棶棰樻�绘暟鍜屽叏閲忔眹鎬�
+        // 锛堝師鏉ヤ负浜嗘嬁鎬绘暟鎶� method 鍙堝叏閲忚窇浜嗕竴閬嶏紝N+1 涔熻窡鐫�璺戠浜岄亶锛�
         Map<String, Object> result = serviceSubtaskDetailTraceService.tracedeallist(detailTraceDealVO);
 
-        // 鏌ヨ鎬绘暟锛堜笉鍒嗛〉锛�
-        detailTraceDealVO.setPageNum(null);
-        detailTraceDealVO.setPageSize(null);
-        Map<String, Object> totalResult = serviceSubtaskDetailTraceService.tracedeallist(detailTraceDealVO);
-        List<?> totalRows = (List<?>) totalResult.get("detailTraceDealDTOList");
-
-        //灏嗘�绘暟淇℃伅娣诲姞鍒扮粨鏋滀腑
-        result.put("totalException", totalResult.get("totalException"));
-        result.put("noDealException", totalResult.get("noDealException"));
-        result.put("yesDealException", totalResult.get("yesDealException"));
-
-        return getDataTable3(CollectionUtils.isNotEmpty(totalRows) ? totalRows.size() : 0, result);
+        Object questionTotal = result.get("questionTotal");
+        long total = questionTotal == null ? 0L : Long.parseLong(String.valueOf(questionTotal));
+        return getDataTable3(total, result);
     }
 
     /**
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherParamEnum.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherParamEnum.java
new file mode 100644
index 0000000..0993330
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherParamEnum.java
@@ -0,0 +1,56 @@
+package com.ruoyi.quartz.enums;
+
+/**
+ * 鐪佺珛鍚屽痉闂ㄦ�ヨ瘖閲囬泦鍙傛暟锛坰ys_config锛屾満鏋� orgid=20001001锛�
+ * <p>
+ * 鎶婇厤缃� key銆侀粯璁ゅ�笺�佸彇鍊艰寖鍥寸粦鍦ㄤ竴璧凤紝璇诲彇鏃跺彧瑕佷紶鏋氫妇鏈韩锛�
+ * 閬垮厤 key 鍜岄粯璁ゅ�煎啓鏁e湪浠诲姟閲屻�佹敼涓�涓紡涓�涓�
+ *
+ * @author smartor
+ */
+public enum MzGatherParamEnum {
+
+    /**
+     * 鍗曡疆閲囬泦鏃堕棿棰勭畻锛堝垎閽燂級
+     * 蹇呴』灏忎簬璋冨害闂撮殧锛屼繚璇� 30 鍒嗛挓涓�璋冩椂鏈疆鑳借刀鍦ㄤ笅涓�杞箣鍓嶇粨鏉�
+     */
+    BUDGET_MINUTES("sltd.gather.mz.budget.minutes", 20L, 1L, 24L * 60L),
+
+    /** 鍒嗛〉姣忛〉鏉℃暟 */
+    PAGE_SIZE("sltd.gather.mz.page.size", 500L, 1L, 5000L),
+
+    /** 鍗曟棩鏈�澶氱炕椤垫暟锛岄槻姝㈡帴鍙e紓甯镐竴鐩磋繑鍥炴弧椤靛鑷存寰幆 */
+    MAX_PAGE("sltd.gather.mz.max.page", 2000L, 1L, 100000L);
+
+    /** sys_config.config_key */
+    private final String key;
+    /** 閰嶇疆缂哄け鎴栬秴鑼冨洿鏃朵娇鐢ㄧ殑榛樿鍊� */
+    private final long defaultValue;
+    /** 鍏佽鐨勬渶灏忓�硷紙鍚級 */
+    private final long min;
+    /** 鍏佽鐨勬渶澶у�硷紙鍚級 */
+    private final long max;
+
+    MzGatherParamEnum(String key, long defaultValue, long min, long max) {
+        this.key = key;
+        this.defaultValue = defaultValue;
+        this.min = min;
+        this.max = max;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public long getDefaultValue() {
+        return defaultValue;
+    }
+
+    public long getMin() {
+        return min;
+    }
+
+    public long getMax() {
+        return max;
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherSlotEnum.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherSlotEnum.java
new file mode 100644
index 0000000..c54c833
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherSlotEnum.java
@@ -0,0 +1,37 @@
+package com.ruoyi.quartz.enums;
+
+/**
+ * 鐪佺珛鍚屽痉闂ㄦ�ヨ瘖閲囬泦锛氭柇鐐规Ы浣�
+ * <p>
+ * 姣忓ぉ涓�杞叏閲忓埛鏂帮紝鍚屾椂杩樻湁"鏄ㄥぉ鏀跺熬"鍜�"鎵嬪伐鍥炴函"涓や釜杞锛�
+ * 姣忚疆鍚勮嚜鍙兘琚绠楁垨鎺ュ彛澶辫触鎵撴柇锛屾墍浠ユ柇鐐瑰垎妲戒綅淇濆瓨锛屼簰涓嶈鐩�
+ *
+ * @author smartor
+ */
+public enum MzGatherSlotEnum {
+
+    /** 浠婂ぉ鐨勬暣杞埛鏂� */
+    TODAY("1", "浠婂ぉ鏁磋疆鍒锋柊"),
+
+    /** 鏄ㄥぉ鐨勬敹灏鹃噰闆嗭紝姣忓ぉ鍙仛涓�娆★紝鍏滀綇 23:xx 鍚庡啓鍏� HIS 鐨勮褰曞拰璺ㄩ浂鐐规暟鎹� */
+    FINAL("2", "鏄ㄦ棩鏀跺熬閲囬泦"),
+
+    /** 浠诲姟鍏ュ弬鎸囧畾鏃ユ湡鐨勪竴娆℃�у洖婧� */
+    BACKFILL("3", "鎵嬪伐鍥炴函");
+
+    private final String code;
+    private final String desc;
+
+    MzGatherSlotEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherStatusEnum.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherStatusEnum.java
new file mode 100644
index 0000000..2737a50
--- /dev/null
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/enums/MzGatherStatusEnum.java
@@ -0,0 +1,36 @@
+package com.ruoyi.quartz.enums;
+
+/**
+ * 鐪佺珛鍚屽痉闂ㄦ�ヨ瘖閲囬泦锛氬崟鏃ラ噰闆嗙粨鏋�
+ * <p>
+ * 鐢ㄤ簬 {@code RyTask#runDayPass} 鐨勮繑鍥炲�硷紝鍛婅瘔璋冪敤鏂硅繖涓�澶╂槸閲囧畬浜嗐�侀绠楃敤灏戒簡锛岃繕鏄帴鍙f寕浜�
+ *
+ * @author smartor
+ */
+public enum MzGatherStatusEnum {
+
+    /** 璇ユ棩鏈熷凡浠庣涓�椤甸噰鍒版渶鍚庝竴椤碉紝鏁磋疆瀹屾垚 */
+    DONE("0", "閲囬泦瀹屾垚"),
+
+    /** 鏈疆鏃堕棿棰勭畻鐢ㄥ敖锛岄〉鐮佹柇鐐瑰凡淇濆瓨锛屼笅涓�杞粠璇ラ〉缁х画 */
+    BUDGET_UP("1", "鏈疆棰勭畻鐢ㄥ敖"),
+
+    /** 鎺ュ彛灞傝繛缁け璐ワ紝鏈疆鎻愬墠缁撴潫锛岄〉鐮佹柇鐐逛繚鐣欏湪绗竴涓け璐ラ〉 */
+    FAILED("2", "鎺ュ彛杩炵画澶辫触");
+
+    private final String code;
+    private final String desc;
+
+    MzGatherStatusEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
index 7fca0e5..c076b5f 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -18,6 +18,10 @@
 import com.ruoyi.common.utils.*;
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.ruoyi.common.utils.sms.smsUtils;
+import com.ruoyi.quartz.constant.MzGatherConstant;
+import com.ruoyi.quartz.enums.MzGatherParamEnum;
+import com.ruoyi.quartz.enums.MzGatherSlotEnum;
+import com.ruoyi.quartz.enums.MzGatherStatusEnum;
 import com.ruoyi.quartz.service.ICollectHISService;
 import com.ruoyi.quartz.util.CreateDataTable;
 import com.ruoyi.system.domain.SysConfig;
@@ -630,96 +634,395 @@
     }
 
     /**
-     * HIS鐪佺珛鍚屽痉闂ㄦ�ヨ瘖鏁版嵁閲囬泦锛堣繖涓彧鑳戒竴澶╂墽琛屼竴娆★紝涓嶈兘鍗婁釜灏忔椂鎵ц涓�涓紝鏁版嵁閲忓お澶э紝褰卞搷鎬ц兘锛�
-     * 瀹氭椂浠诲姟锛氶棬璇婃暟鎹�
-     * todayZeroStr  褰撳墠鐨勬椂闂�   渚嬪锛�2026-05-06 00:00:00
+     * HIS鐪佺珛鍚屽痉闂ㄦ�ヨ瘖鏁版嵁閲囬泦锛堟瘡鍗婂皬鏃朵竴杞紝鎸夊ぉ鍏ㄩ噺鍒锋柊 + 鍒嗛〉鏂偣缁噰锛�
+     * <p>
+     * HIS 渚ч棬鎬ヨ瘖鏁版嵁鍙兘鎸夊ぉ鏌ワ紝姣忚疆鎷夊埌鐨勯兘鏄綋澶╁叏閲忥紙鍒版櫄涓婁竴涓囧鏉★級锛岃繖涓彇鏁版垚鏈敼涓嶄簡锛�
+     * 鑳芥敼鐨勬槸澶勭悊鎴愭湰锛氬綋澶╂暟鎹噷缁濆ぇ澶氭暟涓婁竴杞凡缁忓叆搴擄紝闈�"鏁撮〉鎵归噺鏌ラ噸 + 宸插叆搴撶洿鎺ヨ烦杩�"锛�
+     * 姣忚疆鐪熸闇�瑕佸仛妗f澶勭悊鍜屽叆搴撶殑鍙湁鏂板嚭鐜扮殑閭e嚑鐧炬潯锛屼竴涓囧鏉′篃鑳藉湪寰堢煭鏃堕棿鍐呰窇瀹屻��
+     * <p>
+     * 姣忎竴杞仛涓変欢浜嬶細
+     * <ol>
+     *   <li>浠婂ぉ鐨勬暣杞埛鏂帮細绗� 1 椤电炕鍒版渶鍚庝竴椤碉紝鍙鐞嗘柊璁板綍锛�</li>
+     *   <li>鏄ㄥぉ鐨勬敹灏鹃噰闆嗭細姣忓ぉ鍙仛涓�娆★紝鍏滀綇 23:xx 涔嬪悗鎵嶅啓鍏� HIS 鐨勮褰曞拰璺ㄩ浂鐐圭殑鏁版嵁锛�</li>
+     *   <li>鎵嬪伐鍥炴函锛氫换鍔″叆鍙傜粰浜嗘棩鏈熸椂锛屾妸閭eぉ琛ラ噰涓�娆★紙鍚屼竴澶╁彧琛ヤ竴娆★紝涓嶄細姣忚疆閲嶅琛ワ級銆�</li>
+     * </ol>
+     * 姣忚疆鏈�澶氳窇 {@link MzGatherParamEnum#BUDGET_MINUTES}锛堥粯璁� 20 鍒嗛挓锛夛紝鍒扮偣淇濆瓨椤电爜鏂偣姝e父閫�鍑猴紝涓嬩竴杞粠璇ラ〉缁х画锛�
+     * 姣忛噰瀹屼竴椤甸兘浼氳惤涓�娆℃柇鐐癸紝杩涚▼琚潃涔熶笉浼氫粠澶撮噸鎵��
+     * <p>
+     * 澶辫触涓�寰嬩笉闃绘柇閲囬泦杩涘害锛�
+     * <ul>
+     *   <li>鏁版嵁灞傚け璐ワ紙杞崲/妗f/鍏ュ簱锛夎惤 pat_med_outhosp_error 琛ㄤ氦浜哄伐澶勭悊锛�</li>
+     *   <li>鎺ュ彛灞傚け璐ワ紙缃戠粶瓒呮椂銆佹潵鏈潵杩斿洖闈� 200锛夋病鏈夋暟鎹彲钀借〃锛屽彧鍐� error 鏃ュ織锛堝惈闄㈠尯銆佹棩鏈熴�侀〉鐮併�佹帴鍙e叆鍙傦級锛�
+     *       璺宠繃璇ラ〉缁х画寰�鍚庨噰锛屾湰杞粨鏉熸椂鎶婃柇鐐圭暀鍦ㄧ涓�涓け璐ラ〉锛涘洜涓烘瘡杞兘鏄暣澶╀粠澶村叏閲忓埛鏂帮紝
+     *       琚烦杩囩殑椤典笅涓�杞繀鐒堕噸鏂伴噰鍒帮紝涓嶄細婕忥紱</li>
+     *   <li>杩炵画 {@link MzGatherConstant#MAX_CONSECUTIVE_PAGE_FAIL} 椤垫帴鍙eけ璐ュ垽瀹氫负鎺ュ彛鏁翠綋涓嶅彲鐢紝鏈疆鎻愬墠缁撴潫锛堥伩鍏嶇┖杞嚑鍗侀〉锛夛紝
+     *       鏂偣鐣欏湪绗竴涓け璐ラ〉锛屼笅涓�杞啀璇曘��</li>
+     * </ul>
+     *
+     * @param todayZeroStr 鍙�夛紝鎵嬪伐鎸囧畾瑕佷竴娆℃�у洖婧殑鏃ユ湡锛屾敮鎸� yyyy-MM-dd HH:mm:ss 鎴� yyyy-MM-dd锛涗负绌烘椂鍙埛鏂颁粖澶�+鏀跺熬鏄ㄥぉ
      */
     public void dealHisDataMZ(String todayZeroStr) {
-        log.info("銆恉ealHisData銆戦棬鎬ヨ瘖鏁版嵁寮�濮嬫墽琛孒IS鏁版嵁閲囬泦浠诲姟");
-        // 鑾峰彇鏄ㄥぉ0鐐瑰埌浠婂ぉ0鐐圭殑鏃堕棿鑼冨洿
-        LocalDateTime todayZero = LocalDateTime.now().with(LocalTime.MIN);
-        if (StringUtils.isNotBlank(todayZeroStr)) {
-            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-            todayZero = LocalDateTime.parse(todayZeroStr, formatter);
+        log.info("銆恉ealHisDataMZ銆戦棬鎬ヨ瘖鏁版嵁閲囬泦寮�濮嬶紝鍏ュ弬锛歿}", todayZeroStr);
+        // 鍗楀崕闄勪竴鐨勫厛涓嶉噰闂ㄦ�ヨ瘖鏁版嵁
+        if (!"sltd".equals(active.trim())) {
+            log.info("銆恉ealHisDataMZ銆戝綋鍓嶇幆澧� {} 涓嶉噰闆嗛棬鎬ヨ瘖鏁版嵁锛岃烦杩�", active.trim());
+            return;
         }
-        LocalDateTime tomorrowdayZero = todayZero.plusDays(1);
 
-        if (active.trim().equals("sltd") || active.trim().equals("nhfy")) {
-            //鐪佺珛鍚屽痉涓庡崕鍗楅檮涓�鐢ㄥ悓涓�濂楅噰闆嗘柟娉曪紙閮芥槸鏉ユ湭鏉ユ彁渚涳級
+        long budgetMinutes = getMzConfigLong(MzGatherParamEnum.BUDGET_MINUTES);
+        long pageSize = getMzConfigLong(MzGatherParamEnum.PAGE_SIZE);
+        long maxPage = getMzConfigLong(MzGatherParamEnum.MAX_PAGE);
+
+        LocalDateTime now = LocalDateTime.now().withNano(0);
+        LocalDateTime deadline = now.plusMinutes(budgetMinutes);
+
+        // 鑾峰彇鐪佺珛鍚屽痉瑕侀噰闆嗙殑闄㈠尯
+        SysConfig cf = new SysConfig();
+        cf.setConfigKey("sltd.gather.campusid");
+        cf.setOrgid(MzGatherConstant.ORG_ID);
+        List<SysConfig> sysConfigcampusids = configService.selectConfigList(cf);
+        if (CollectionUtils.isEmpty(sysConfigcampusids) || StringUtils.isBlank(sysConfigcampusids.get(0).getConfigValue())) {
+            log.error("銆恉ealHisDataMZ銆慡ysConfig涓殑闄㈠尯ID涓虹┖锛�");
+            return;
+        }
+        List<Long> campusidList = Arrays.stream(sysConfigcampusids.get(0).getConfigValue().split(","))
+                .map(String::trim).filter(StringUtils::isNotBlank).map(Long::valueOf).collect(Collectors.toList());
+        log.info("銆恉ealHisDataMZ銆戦櫌鍖猴細{}锛屽崟杞绠楋細{}鍒嗛挓锛屾瘡椤碉細{}鏉�", campusidList, budgetMinutes, pageSize);
+
+        for (Long cid : campusidList) {
+            if (LocalDateTime.now().isAfter(deadline)) {
+                log.warn("銆恉ealHisDataMZ銆戞湰杞椂闂撮绠楀凡鐢ㄥ敖锛屽墿浣欓櫌鍖虹暀鍒颁笅涓�杞户缁紝闄㈠尯锛歿}", cid);
+                break;
+            }
+            // 闃叉涓婁竴杞繕娌¤窇瀹屽氨琚笅涓�杞彔鐫�璺戯紙棰勭畻閰嶇疆杩囧ぇ銆佹垨鎵嬪伐瑙﹀彂鏃跺厹搴曪級
+            String lockKey = "mzRunning:" + cid;
+            if (Boolean.TRUE.equals(redisCache.hasKey(lockKey))) {
+                log.warn("銆恉ealHisDataMZ銆戣闄㈠尯涓婁竴杞噰闆嗗皻鏈粨鏉燂紝鏈疆璺宠繃锛岄櫌鍖猴細{}锛堟柇鐐瑰凡淇濈暀锛屼笅涓�杞户缁級", cid);
+                continue;
+            }
+            redisCache.setCacheObject(lockKey, MzGatherConstant.MINUTE_FMT.format(now), budgetMinutes * 2, TimeUnit.MINUTES);
             try {
-                //鑾峰彇鐪佺珛鍚屽痉瑕侀噰闆嗙殑闄㈠尯
-                SysConfig cf = new SysConfig();
-                cf.setConfigKey("sltd.gather.campusid");
-                cf.setOrgid("20001001");
-                List<SysConfig> sysConfigcampusids = configService.selectConfigList(cf);
-
-                List<Long> campusidList = new ArrayList<>();
-                if (CollectionUtils.isNotEmpty(sysConfigcampusids)) {
-                    String[] split = sysConfigcampusids.get(0).getConfigValue().split(",");
-                    campusidList = Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList());
-                } else {
-                    log.error("SysConfig涓殑闄㈠尯ID涓虹┖锛�");
-                    return;
-                }
-
-                // 鐪佺珛鍚屽痉鏁版嵁閲囬泦
-                log.info("--campusidList鐨勫�间负锛歿}", campusidList);
-                for (Long cid : campusidList) {
-                    String nowTime = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
-                    ServiceSLTDInhospReqVO serviceSLTDInhospReqVO = new ServiceSLTDInhospReqVO();
-                    serviceSLTDInhospReqVO.setOrgId("20001001");
-                    serviceSLTDInhospReqVO.setCampusId(cid);
-                    serviceSLTDInhospReqVO.setEncounterTimeStart(nowTime);
-                    serviceSLTDInhospReqVO.setEncounterTimeEnd(nowTime);
-
-                    // 闂ㄦ�ヨ瘖鍒嗛〉閲囬泦锛氭瘡椤�40鏉★紝鐩村埌鏃犳暟鎹负姝�(鍗楀崕闄勪竴鐨勫厛涓嶉噰闂ㄦ�ヨ瘖鏁版嵁)
-                    if (active.trim().equals("sltd")) {
-                        log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濆紑濮嬪垎椤甸噰闆嗘暟鎹紝闄㈠尯锛歿}", cid);
-                        serviceSLTDInhospReqVO.setStartHeadTime(null);
-                        serviceSLTDInhospReqVO.setStartTailTime(null);
-                        //鏌ヨ褰撳ぉ鐨勬暟鎹�
-                        serviceSLTDInhospReqVO.setEncounterTimeEnd(tomorrowdayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
-                        serviceSLTDInhospReqVO.setEncounterTimeStart(todayZero.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
-                        serviceSLTDInhospReqVO.setStatusList(null);
-                        List<String> list1 = new ArrayList<>();
-                        list1.add("FH0108.01");
-                        list1.add("FH0108.03");
-                        serviceSLTDInhospReqVO.setHealthcareRecordTypeList(list1);
-                        //浠庣紦瀛橀噷鑾峰彇璧峰椤碉紙闆剁偣鍓�10鍒嗛挓鍐呯敤鏄ㄥぉ鐨勬棩鏈燂紝閬垮厤璺ㄥぉ婕忛噰锛�
-                        LocalDateTime now = LocalDateTime.now();
-                        String mzCacheDate = (now.getHour() == 0 && now.getMinute() < 10) ? now.minusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) : now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
-                        Long outpPage = redisCache.getCacheObject("mzCurrent" + cid + mzCacheDate);
-                        if (outpPage == null) outpPage = 1L;
-                        //閭h竟鏆傛椂鍙兘璁剧疆40
-                        final long OUTP_PAGE_SIZE = 500L;
-                        while (true) {
-                            serviceSLTDInhospReqVO.setCurrent(outpPage);
-                            serviceSLTDInhospReqVO.setSize(OUTP_PAGE_SIZE);
-                            log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濋噰闆嗙{}椤垫暟鎹紝闄㈠尯锛歿}", outpPage, cid);
-                            List<ServiceSLTDInhospResDTO> outpResult = serviceSLTDHealthcareRecordService.queryHealthcareRecordList(serviceSLTDInhospReqVO);
-                            if (CollectionUtils.isEmpty(outpResult)) {
-                                log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濈{}椤垫棤鏁版嵁锛岄噰闆嗙粨鏉燂紝闄㈠尯锛歿}", outpPage, cid);
-                                break;
-                            }
-                            log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濈{}椤甸噰闆唟}鏉★紝闄㈠尯锛歿}", outpPage, outpResult.size(), cid);
-                            if (outpResult.size() < OUTP_PAGE_SIZE) {
-                                //璁剧疆涓�涓紦瀛橈紝鐢ㄦ潵瀛樺湪涓嬫鐨勮捣濮嬮〉
-                                redisCache.setCacheObject("mzCurrent" + cid + mzCacheDate, outpPage, 24, TimeUnit.HOURS);
-                                log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濈殑缂撳瓨key:{}涓嬫鐨勮捣濮嬮〉{}锛岄櫌鍖猴細{}", "mzCurrent" + cid + mzCacheDate, outpPage, cid);
-                                break;
-                            }
-                            outpPage++;
-                        }
-                        // 娓呴櫎鍒嗛〉鍙傛暟锛岄伩鍏嶅奖鍝嶅悗缁嚭鍏ラ櫌鏌ヨ
-                        serviceSLTDInhospReqVO.setCurrent(null);
-                        serviceSLTDInhospReqVO.setSize(null);
-                        log.info("銆恉ealHisData銆戠渷绔嬪悓寰封�滈棬鎬ヨ瘖鈥濆垎椤甸噰闆嗗畬鎴愶紝闄㈠尯锛歿}", cid);
-                    }
-                }
+                collectOutpatientDaily(cid, todayZeroStr, pageSize, maxPage, deadline);
             } catch (Exception e) {
-                log.error("銆恉ealHisData銆戠渷绔嬪悓寰锋暟鎹棬鎬ヨ瘖閲囬泦寮傚父", e);
+                log.error("銆恉ealHisDataMZ銆戦棬鎬ヨ瘖閲囬泦寮傚父锛岄櫌鍖猴細{}锛屾柇鐐瑰凡淇濈暀锛屼笅涓�杞粠鏂偣缁х画", cid, e);
+            } finally {
+                redisCache.deleteObject(lockKey);
             }
         }
+        log.info("銆恉ealHisDataMZ銆戦棬鎬ヨ瘖閲囬泦鏈疆缁撴潫锛岃�楁椂锛歿}绉�",
+                java.time.Duration.between(now, LocalDateTime.now()).getSeconds());
+    }
+
+    /**
+     * 閲囬泦鏌愪釜闄㈠尯锛氬厛琛ヤ竴娆℃�у洖婧棩鏈燂紝鍐嶅仛鏄ㄥぉ鐨勬敹灏鹃噰闆嗭紝鏈�鍚庢暣杞埛鏂颁粖澶�
+     */
+    private void collectOutpatientDaily(Long cid, String backfillParam, long pageSize, long maxPage,
+                                        LocalDateTime deadline) {
+        String cursorKey = "mzDayCursor:" + cid;
+        MzDayCursor cursor = loadMzDayCursor(cursorKey);
+        LocalDate today = LocalDate.now();
+        LocalDate yesterday = today.minusDays(1);
+
+        // 鎵嬪伐鍏ュ弬鎸囧畾鐨勪竴娆℃�у洖婧細鍚屼竴涓棩鏈熷彧琛ヤ竴娆★紝閬垮厤瀹氭椂浠诲姟姣忚疆閲嶅琛�
+        LocalDateTime backfillTime = parseMzStartTime(backfillParam);
+        if (backfillTime != null) {
+            LocalDate backfillDay = backfillTime.toLocalDate();
+            if (backfillDay.toString().equals(cursor.backfillDone)) {
+                log.info("銆恉ealHisDataMZ銆戝叆鍙傛棩鏈� {} 宸插畬鎴愬洖婧紝璺宠繃锛岄櫌鍖猴細{}", backfillDay, cid);
+            } else if (backfillDay.equals(today)) {
+                // 鍏ュ弬灏辨槸浠婂ぉ锛屼氦缁欎笅闈㈢殑鏁磋疆鍒锋柊澶勭悊鍗冲彲
+                cursor.backfillDone = backfillDay.toString();
+                saveMzDayCursor(cursorKey, cursor);
+            } else {
+                cursor.backfillDay = backfillDay.toString();
+                cursor.backfillPage = 1L;
+                saveMzDayCursor(cursorKey, cursor);
+                log.info("銆恉ealHisDataMZ銆戝緟鍥炴函鏃ユ湡锛歿}锛岄櫌鍖猴細{}", backfillDay, cid);
+            }
+        }
+
+        // 1) 涓�娆℃�у洖婧�
+        if (cursor.backfillDay != null) {
+            LocalDate day = LocalDate.parse(cursor.backfillDay);
+            MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.BACKFILL, day,
+                    cursor.backfillPage, pageSize, maxPage, deadline);
+            if (status != MzGatherStatusEnum.DONE) {
+                log.warn("銆恉ealHisDataMZ銆戝洖婧棩鏈� {} 鏈疆鏈畬鎴愶紙{}锛夛紝鏂偣宸蹭繚鐣欙紝闄㈠尯锛歿}", day, status.getDesc(), cid);
+                return;
+            }
+            cursor.backfillDone = cursor.backfillDay;
+            cursor.backfillDay = null;
+            cursor.backfillPage = 1L;
+            saveMzDayCursor(cursorKey, cursor);
+        }
+
+        // 2) 鏄ㄥぉ鏀跺熬閲囬泦锛氭瘡澶╀竴娆★紝鍏滀綇 23:xx 涔嬪悗鎵嶅啓杩� HIS 鐨勮褰曞拰璺ㄩ浂鐐圭殑鏁版嵁
+        if (!yesterday.toString().equals(cursor.finalDay)) {
+            MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.FINAL, yesterday,
+                    cursor.finalPage, pageSize, maxPage, deadline);
+            if (status != MzGatherStatusEnum.DONE) {
+                log.warn("銆恉ealHisDataMZ銆戞槰鏃ユ敹灏鹃噰闆� {} 鏈疆鏈畬鎴愶紙{}锛夛紝鏂偣宸蹭繚鐣欙紝闄㈠尯锛歿}", yesterday, status.getDesc(), cid);
+                return;
+            }
+            cursor.finalDay = yesterday.toString();
+            cursor.finalPage = 1L;
+            saveMzDayCursor(cursorKey, cursor);
+        }
+
+        // 3) 浠婂ぉ鐨勬暣杞埛鏂�
+        if (!today.toString().equals(cursor.day)) {
+            cursor.day = today.toString();
+            cursor.page = 1L;
+            saveMzDayCursor(cursorKey, cursor);
+        }
+        MzGatherStatusEnum status = runDayPass(cid, cursorKey, cursor, MzGatherSlotEnum.TODAY, today,
+                cursor.page, pageSize, maxPage, deadline);
+        if (status == MzGatherStatusEnum.DONE) {
+            // 鏁磋疆鍒锋柊瀹屾垚锛岄〉鐮佸綊浣嶏紝涓嬩竴杞噸鏂颁粠绗竴椤垫暣杞埛鏂�
+            cursor.page = 1L;
+            saveMzDayCursor(cursorKey, cursor);
+            log.info("銆恉ealHisDataMZ銆憑} 闂ㄦ�ヨ瘖鏁磋疆鍒锋柊瀹屾垚锛岄櫌鍖猴細{}", today, cid);
+        }
+    }
+
+    /**
+     * 鍏ㄩ噺閲囬泦鏌愪竴澶╋紙鍒嗛〉鐩村埌閲囧畬锛夛紝姣忛噰瀹屼竴椤电珛鍗宠惤涓�娆℃柇鐐�
+     *
+     * @param slot 鏂偣妲戒綅锛堜粖澶╂暣杞埛鏂� / 鏄ㄦ棩鏀跺熬 / 鎵嬪伐鍥炴函锛夛紝鍐冲畾涓柇椤电爜璁板埌鍝釜瀛楁
+     * @return {@link MzGatherStatusEnum} 閲囬泦瀹屾垚 / 棰勭畻鐢ㄥ敖 / 鎺ュ彛杩炵画澶辫触鎻愬墠缁撴潫
+     */
+    private MzGatherStatusEnum runDayPass(Long cid, String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot,
+                                          LocalDate day, long startPage, long pageSize, long maxPage,
+                                          LocalDateTime deadline) {
+        String startStr = day.format(MzGatherConstant.DAY_FMT);
+        String endStr = day.plusDays(1).format(MzGatherConstant.DAY_FMT);
+
+        ServiceSLTDInhospReqVO reqVO = new ServiceSLTDInhospReqVO();
+        reqVO.setOrgId(MzGatherConstant.ORG_ID);
+        reqVO.setCampusId(cid);
+        reqVO.setStartHeadTime(null);
+        reqVO.setStartTailTime(null);
+        reqVO.setStatusList(null);
+        // 鎺ュ彛鍙敮鎸佹寜澶╂煡锛岃捣姝㈤兘浼犳棩鏈�
+        reqVO.setEncounterTimeStart(startStr);
+        reqVO.setEncounterTimeEnd(endStr);
+        List<String> typeList = new ArrayList<>();
+        typeList.add("FH0108.01"); // 闂ㄨ瘖
+        typeList.add("FH0108.03"); // 鎬ヨ瘖
+        reqVO.setHealthcareRecordTypeList(typeList);
+        reqVO.setSize(pageSize);
+
+        long page = startPage < 1L ? 1L : startPage;
+        long total = 0L;
+        long pageCount = 0L;
+        // 鎺ュ彛灞傚け璐ヨ烦杩囩殑椤垫暟銆佺涓�涓け璐ラ〉銆佽繛缁け璐ラ〉鏁�
+        long skippedPages = 0L;
+        long firstFailPage = -1L;
+        int consecutiveFail = 0;
+        while (true) {
+            if (LocalDateTime.now().isAfter(deadline)) {
+                // 鏈疆鏈夎烦杩囩殑椤垫椂锛屾柇鐐瑰洖鍒扮涓�涓け璐ラ〉锛屼繚璇佷笅涓�杞厛鎶婅繖浜涢〉琛ュ洖鏉�
+                long resumePage = (skippedPages > 0 && firstFailPage > 0) ? firstFailPage : page;
+                saveMzDayPage(cursorKey, cursor, slot, resumePage);
+                log.warn("銆恉ealHisDataMZ銆戞湰杞椂闂撮绠楃敤灏斤紝{} 鏈疆宸查噰{}椤祘}鏉}锛屼笅娆′粠绗瑊}椤电户缁紝闄㈠尯锛歿}",
+                        day, pageCount, total, skippedLog(skippedPages), resumePage, cid);
+                return MzGatherStatusEnum.BUDGET_UP;
+            }
+            reqVO.setCurrent(page);
+            log.info("銆恉ealHisDataMZ銆戦噰闆嗛棬鎬ヨ瘖鏁版嵁锛岄櫌鍖猴細{}锛屾棩鏈燂細{}锛岀{}椤�", cid, day, page);
+
+            List<ServiceSLTDInhospResDTO> pageData = null;
+            Exception lastError = null;
+            for (int attempt = 1; attempt <= MzGatherConstant.PAGE_RETRY_TIMES; attempt++) {
+                try {
+                    pageData = serviceSLTDHealthcareRecordService.queryHealthcareRecordList(reqVO);
+                    lastError = null;
+                    break;
+                } catch (Exception e) {
+                    lastError = e;
+                    log.error("銆恉ealHisDataMZ銆戦棬鎬ヨ瘖绗瑊}椤电{}娆¢噰闆嗗け璐ワ紝闄㈠尯锛歿}锛屾棩鏈燂細{}", page, attempt, cid, day, e);
+                    if (attempt < MzGatherConstant.PAGE_RETRY_TIMES) {
+                        try {
+                            Thread.sleep(MzGatherConstant.PAGE_RETRY_SLEEP_MS);
+                        } catch (InterruptedException ie) {
+                            Thread.currentThread().interrupt();
+                            break;
+                        }
+                    }
+                }
+            }
+            if (lastError != null) {
+                // 鎺ュ彛灞傚け璐ワ紙缃戠粶瓒呮椂 / 鏉ユ湭鏉ヨ繑鍥為潪200锛夋病鏈夋暟鎹彲浠ヨ惤琛紝鍙啓 error 鏃ュ織锛岀劧鍚庤烦杩囪椤电户缁線鍚庨噰锛屼笉闃绘柇杩涘害銆�
+                // 姣忚疆閮芥槸鏁村ぉ浠庡ご鍏ㄩ噺鍒锋柊锛岃璺宠繃鐨勯〉涓嬩竴杞繀鐒堕噸鏂伴噰鍒帮紝鎵�浠ヤ笉浼氭紡鏁版嵁銆�
+                skippedPages++;
+                if (firstFailPage < 0) {
+                    firstFailPage = page;
+                }
+                consecutiveFail++;
+                log.error("銆恉ealHisDataMZ銆戞帴鍙e眰閲囬泦澶辫触锛屽凡璁� error 鏃ュ織骞惰烦杩囪椤碉紝鏈疆缁х画寰�鍚庨噰锛�"
+                                + "闄㈠尯锛歿}锛屾棩鏈燂細{}锛岀{}椤碉紝鎺ュ彛鍏ュ弬锛歿}锛屽紓甯革細{}",
+                        cid, day, page, new Gson().toJson(reqVO), lastError.getMessage(), lastError);
+                if (consecutiveFail >= MzGatherConstant.MAX_CONSECUTIVE_PAGE_FAIL) {
+                    // 杩炵画澶氶〉鎺ュ彛澶辫触锛屽垽瀹氭潵鏈潵鎺ュ彛鏁翠綋涓嶅彲鐢紝鏈疆鎻愬墠缁撴潫锛堜笉鍐嶇┖杞悗闈㈠嚑鍗侀〉锛夛紝鏂偣鐣欏湪绗竴涓け璐ラ〉
+                    log.error("銆恉ealHisDataMZ銆戣繛缁瓄}椤垫帴鍙eけ璐ワ紝鍒ゅ畾鏉ユ湭鏉ユ帴鍙d笉鍙敤锛屾湰杞彁鍓嶇粨鏉燂紱闄㈠尯锛歿}锛屾棩鏈燂細{}锛屾柇鐐逛繚鐣欏湪绗瑊}椤�",
+                            consecutiveFail, cid, day, firstFailPage);
+                    saveMzDayPage(cursorKey, cursor, slot, firstFailPage);
+                    return MzGatherStatusEnum.FAILED;
+                }
+                page++;
+                continue;
+            }
+            consecutiveFail = 0;
+
+            if (CollectionUtils.isEmpty(pageData)) {
+                log.info("銆恉ealHisDataMZ銆憑} 绗瑊}椤垫棤鏁版嵁锛岄噰闆嗙粨鏉燂紝闄㈠尯锛歿}锛屾湰杞畕}椤靛叡{}鏉}",
+                        day, page, cid, pageCount, total, skippedLog(skippedPages));
+                return MzGatherStatusEnum.DONE;
+            }
+            pageCount++;
+            total += pageData.size();
+            log.info("銆恉ealHisDataMZ銆憑} 绗瑊}椤甸噰闆唟}鏉★紝闄㈠尯锛歿}", day, page, pageData.size(), cid);
+            if (pageData.size() < pageSize) {
+                log.info("銆恉ealHisDataMZ銆憑} 閲囬泦缁撴潫锛岄櫌鍖猴細{}锛屾湰杞畕}椤靛叡{}鏉}", day, cid, pageCount, total, skippedLog(skippedPages));
+                return MzGatherStatusEnum.DONE;
+            }
+            page++;
+            if (page > maxPage) {
+                log.error("銆恉ealHisDataMZ銆憑} 宸茬炕鍒扮{}椤碉紝瓒呰繃鍗曟棩涓婇檺{}椤碉紝鎸夐噰瀹屽鐞嗭紝闄㈠尯锛歿}", day, page, maxPage, cid);
+                return MzGatherStatusEnum.DONE;
+            }
+            // 姣忛噰瀹屼竴椤电珛鍗宠惤鏂偣锛氳繘绋嬭閲嶅惎/寮烘潃涔熻兘浠庤繖涓�椤电户缁紝涓嶄粠澶撮噸鎵紝涔熶笉浼氭紡
+            saveMzDayPage(cursorKey, cursor, slot, page);
+        }
+    }
+
+    /** 鎸夋Ы浣嶄繚瀛樹腑鏂〉鐮� */
+    private void saveMzDayPage(String cursorKey, MzDayCursor cursor, MzGatherSlotEnum slot, long page) {
+        if (slot == MzGatherSlotEnum.FINAL) {
+            cursor.finalPage = page;
+        } else if (slot == MzGatherSlotEnum.BACKFILL) {
+            cursor.backfillPage = page;
+        } else {
+            cursor.page = page;
+        }
+        saveMzDayCursor(cursorKey, cursor);
+    }
+
+    /** 鏃ュ織閲屾彁绀烘帴鍙e眰澶辫触璺宠繃浜嗗嚑椤碉紙涓嬩竴杞暣杞埛鏂颁細閲嶆柊閲囧埌锛� */
+    private String skippedLog(long skippedPages) {
+        return skippedPages > 0L ? "锛屾帴鍙eけ璐ヨ烦杩�" + skippedPages + "椤碉紙涓嬩竴杞暣杞埛鏂颁細閲嶆柊閲囧埌锛�" : "";
+    }
+
+    /** 璇诲彇 sys_config 閲岀殑闂ㄦ�ヨ瘖閲囬泦鍙傛暟锛堟満鏋� orgid=20001001锛夛紝鍙栦笉鍒版垨瓒呰寖鍥存椂鐢ㄦ灇涓鹃噷鐨勯粯璁ゅ�� */
+    private long getMzConfigLong(MzGatherParamEnum param) {
+        try {
+            SysConfig config = new SysConfig();
+            config.setConfigKey(param.getKey());
+            config.setOrgid(MzGatherConstant.ORG_ID);
+            List<SysConfig> configs = configService.selectConfigList(config);
+            if (CollectionUtils.isNotEmpty(configs) && StringUtils.isNotBlank(configs.get(0).getConfigValue())) {
+                long value = Long.parseLong(configs.get(0).getConfigValue().trim());
+                if (value >= param.getMin() && value <= param.getMax()) {
+                    return value;
+                }
+                log.warn("銆恉ealHisDataMZ銆戦厤缃畕}鐨勫�納}瓒呭嚭鑼冨洿[{},{}]锛屼娇鐢ㄩ粯璁ゅ�納}",
+                        param.getKey(), value, param.getMin(), param.getMax(), param.getDefaultValue());
+            }
+        } catch (Exception e) {
+            log.warn("銆恉ealHisDataMZ銆戣鍙栭厤缃畕}澶辫触锛屼娇鐢ㄩ粯璁ゅ�納}", param.getKey(), param.getDefaultValue(), e);
+        }
+        return param.getDefaultValue();
+    }
+
+    /** 瑙f瀽浠诲姟鍏ュ弬锛堣涓�娆℃�у洖婧殑鏃ユ湡锛夛紝鏀寔 yyyy-MM-dd HH:mm:ss 涓� yyyy-MM-dd */
+    private LocalDateTime parseMzStartTime(String text) {
+        LocalDateTime time = parseMzTimeQuiet(text);
+        if (time == null && StringUtils.isNotBlank(text)) {
+            log.warn("銆恉ealHisDataMZ銆戝叆鍙� {} 鏃犳硶瑙f瀽涓烘棩鏈燂紝鎸夋湭鎸囧畾澶勭悊", text);
+        }
+        return time;
+    }
+
+    /** 闈欓粯瑙f瀽鏃ユ湡锛屾敮鎸� yyyy-MM-dd HH:mm:ss 涓� yyyy-MM-dd锛屾牸寮忎笉绗﹁繑鍥� null锛堜笉鍒锋棩蹇楋級 */
+    private LocalDateTime parseMzTimeQuiet(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        String value = text.trim();
+        try {
+            return LocalDateTime.parse(value, MzGatherConstant.MINUTE_FMT);
+        } catch (Exception ignore) {
+            // 鍏煎鍙紶鏃ユ湡鐨勬儏鍐碉紝钀藉埌涓嬮潰鎸夋棩鏈熻В鏋�
+        }
+        try {
+            return LocalDate.parse(value).atStartOfDay();
+        } catch (Exception ignore) {
+            return null;
+        }
+    }
+
+    /** 淇濆瓨闂ㄦ�ヨ瘖閲囬泦鏂偣锛堝悇杞涓柇鐨勯〉鐮侊級 */
+    private void saveMzDayCursor(String cursorKey, MzDayCursor cursor) {
+        JSONObject json = new JSONObject();
+        json.put("day", cursor.day);
+        json.put("page", cursor.page);
+        json.put("finalDay", cursor.finalDay);
+        json.put("finalPage", cursor.finalPage);
+        json.put("backfillDay", cursor.backfillDay);
+        json.put("backfillPage", cursor.backfillPage);
+        json.put("backfillDone", cursor.backfillDone);
+        json.put("updateTime", MzGatherConstant.MINUTE_FMT.format(LocalDateTime.now().withNano(0)));
+        redisCache.setCacheObject(cursorKey, json.toJSONString(), MzGatherConstant.CACHE_TTL_HOURS, TimeUnit.HOURS);
+    }
+
+    /** 璇诲彇闂ㄦ�ヨ瘖閲囬泦鏂偣锛岃涓嶅埌鎴栬В鏋愬け璐ユ椂杩斿洖绌烘柇鐐癸紙绛変环浜庝粠绗竴椤靛紑濮嬫暣杞埛鏂帮級 */
+    private MzDayCursor loadMzDayCursor(String cursorKey) {
+        MzDayCursor cursor = new MzDayCursor();
+        try {
+            String json = redisCache.getCacheObject(cursorKey);
+            if (StringUtils.isBlank(json)) {
+                return cursor;
+            }
+            JSONObject obj = JSONObject.parseObject(json);
+            cursor.day = obj.getString("day");
+            cursor.page = obj.getLongValue("page");
+            cursor.finalDay = obj.getString("finalDay");
+            cursor.finalPage = obj.getLongValue("finalPage");
+            cursor.backfillDay = obj.getString("backfillDay");
+            cursor.backfillPage = obj.getLongValue("backfillPage");
+            cursor.backfillDone = obj.getString("backfillDone");
+            if (cursor.page < 1L) {
+                cursor.page = 1L;
+            }
+            if (cursor.finalPage < 1L) {
+                cursor.finalPage = 1L;
+            }
+            if (cursor.backfillPage < 1L) {
+                cursor.backfillPage = 1L;
+            }
+            return cursor;
+        } catch (Exception e) {
+            log.warn("銆恉ealHisDataMZ銆戣鍙栨柇鐐箋}澶辫触锛屾寜绌烘柇鐐瑰鐞�", cursorKey, e);
+            return new MzDayCursor();
+        }
+    }
+
+    /**
+     * 闂ㄦ�ヨ瘖閲囬泦鏂偣锛氭瘡澶╀竴杞叏閲忓埛鏂帮紝杩欓噷鍒嗗埆璁板綍涓変釜杞涓柇鍦ㄧ鍑犻〉
+     * 锛堥〉鐮佷负 1 琛ㄧず璇ヨ疆娆′笅涓�杞粠绗竴椤垫暣杞紑濮嬶級
+     */
+    private static class MzDayCursor {
+        /** 浠婂ぉ鐨勬暣杞埛鏂� */
+        private String day;
+        private long page = 1L;
+        /** 鏄ㄥぉ鐨勬敹灏鹃噰闆� */
+        private String finalDay;
+        private long finalPage = 1L;
+        /** 寰呭仛鐨勪竴娆℃�у洖婧� */
+        private String backfillDay;
+        private long backfillPage = 1L;
+        /** 宸茬粡琛ヨ繃鐨勫洖婧棩鏈燂紝閬垮厤瀹氭椂浠诲姟姣忚疆閲嶅琛ュ悓涓�澶� */
+        private String backfillDone;
     }
 
     /**
diff --git a/smartor/src/main/java/com/smartor/domain/DTO/DetailTraceDealDTO.java b/smartor/src/main/java/com/smartor/domain/DTO/DetailTraceDealDTO.java
index 29a12de..b0ac693 100644
--- a/smartor/src/main/java/com/smartor/domain/DTO/DetailTraceDealDTO.java
+++ b/smartor/src/main/java/com/smartor/domain/DTO/DetailTraceDealDTO.java
@@ -37,7 +37,12 @@
     @Excel(name = "闂ID")
     private Long scriptid;
 
-
+    /**
+     * 闂ID
+     */
+    @ApiModelProperty("闂ID闆嗗悎")
+    @Excel(name = "闂ID闆嗗悎")
+    private List<Long> scriptids;
 
     /**
      * 妯℃澘绫诲瀷锛�1.璇煶 2.闂嵎
diff --git a/smartor/src/main/java/com/smartor/domain/ServiceSLTDInhospReqVO.java b/smartor/src/main/java/com/smartor/domain/ServiceSLTDInhospReqVO.java
index aa0724d..2e82db5 100644
--- a/smartor/src/main/java/com/smartor/domain/ServiceSLTDInhospReqVO.java
+++ b/smartor/src/main/java/com/smartor/domain/ServiceSLTDInhospReqVO.java
@@ -68,10 +68,10 @@
     @ApiModelProperty(value = "鍑洪櫌鏃堕棿-缁撴潫鏃堕棿 yyyy-MM-dd")
     private String preOutHospitalTailDate;
 
-    @ApiModelProperty(value = "灏辫瘖鏃堕棿-寮�濮嬫椂闂� :   闂ㄨ瘖璇婄枟璁板綍涓哄彿婧愭椂闂�  浣忛櫌璇婄枟璁板綍涓哄叆闄㈢櫥璁版椂闂� yyyy-MM-dd")
+    @ApiModelProperty(value = "灏辫瘖鏃堕棿-寮�濮嬫椂闂� :   闂ㄨ瘖璇婄枟璁板綍涓哄彿婧愭椂闂�  浣忛櫌璇婄枟璁板綍涓哄叆闄㈢櫥璁版椂闂� yyyy-MM-dd 鎴� yyyy-MM-dd HH:mm:ss锛堥棬鎬ヨ瘖澧為噺閲囬泦绮剧‘鍒扮锛�")
     private String encounterTimeStart;
 
-    @ApiModelProperty(value = "灏辫瘖鏃堕棿-缁撴潫鏃堕棿 yyyy-MM-dd")
+    @ApiModelProperty(value = "灏辫瘖鏃堕棿-缁撴潫鏃堕棿 yyyy-MM-dd 鎴� yyyy-MM-dd HH:mm:ss锛堥棬鎬ヨ瘖澧為噺閲囬泦绮剧‘鍒扮锛�")
     private String encounterTimeEnd;
 
     @ApiModelProperty(value = "鍑洪櫌缁撶畻鏃堕棿-寮�濮嬫椂闂� yyyy-MM-dd")
diff --git a/smartor/src/main/java/com/smartor/domain/SvyLibTemplateCategory.java b/smartor/src/main/java/com/smartor/domain/SvyLibTemplateCategory.java
index 0047b17..fc770a5 100644
--- a/smartor/src/main/java/com/smartor/domain/SvyLibTemplateCategory.java
+++ b/smartor/src/main/java/com/smartor/domain/SvyLibTemplateCategory.java
@@ -66,5 +66,11 @@
     @ApiModelProperty(value = "鍖婚櫌鏈烘瀯id")
     private Integer seqno;
 
+    /**
+     * 闂绫诲瀷锛堜緥濡�,闂ㄨ瘖婊℃剰搴︼細mzmyd     浣忛櫌婊℃剰搴︼細zymyd  ...锛�
+     */
+    @Excel(name = "闂绫诲瀷锛堜緥濡�,闂ㄨ瘖婊℃剰搴︼細mzmyd     浣忛櫌婊℃剰搴︼細zymyd  ...锛�")
+    @ApiModelProperty("闂绫诲瀷锛堜緥濡�,闂ㄨ瘖婊℃剰搴︼細mzmyd     浣忛櫌婊℃剰搴︼細zymyd  ...锛�")
+    private String type;
 
 }
diff --git a/smartor/src/main/java/com/smartor/mapper/PatMedOuthospMapper.java b/smartor/src/main/java/com/smartor/mapper/PatMedOuthospMapper.java
index c0fb31b..90bb456 100644
--- a/smartor/src/main/java/com/smartor/mapper/PatMedOuthospMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/PatMedOuthospMapper.java
@@ -1,5 +1,6 @@
 package com.smartor.mapper;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -90,6 +91,15 @@
     // 鏌ヨ鎵�鏈� pat_med_outhosp 寮�澶寸殑琛ㄥ悕
     List<String> getAllOuthospTableNames();
 
+    /**
+     * 鎵归噺鏌ヨ鎸囧畾鍒嗚〃涓凡瀛樺湪鐨� serialnum锛堜竴娆� SQL 浠f浛閫愭潯 countBySerialnum锛�
+     *
+     * @param tableName  鍒嗚〃鍚�
+     * @param serialnums 寰呮煡璇㈢殑璇婄枟璁板綍鍙�
+     * @return 宸插瓨鍦ㄧ殑 serialnum 闆嗗悎
+     */
+    List<String> selectExistingSerialnums(@Param("tableName") String tableName, @Param("serialnums") Collection<String> serialnums);
+
     // 鏌ヨ鎸囧畾琛ㄧ殑瀛楁鍒楄〃锛堟寜 ordinal_position 鎺掑簭锛�
     List<String> getTableColumns(@Param("tableName") String tableName);
 
diff --git a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
index 5ceb6b8..7161470 100644
--- a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailMapper.java
@@ -51,6 +51,29 @@
      */
     public List<Map<String, Object>> selectSatisfactionFillStatByMatchedtext(SatisfactionStatQuery query);
 
+    /**
+     * 婊℃剰搴︽煴鐘跺浘锛氫竴娆℃�х粺璁℃湰娆¤姹傚叏閮ㄦā鏉跨殑銆屽洖鏀朵汉鏁般��
+     * 锛堟寜 libtemplateid 鍒嗙粍锛宑ount(distinct sub_id)锛氬悓涓�涓汉绛斾簡澶氶亾棰樺彧绠椾竴浜猴級
+     * <p>
+     * satisfactionGraph / satisfactionGraphDimension 鐨勫洖鏀朵汉鏁板繀椤绘槸浜烘暟鍙e緞锛�
+     * 涓嶈兘鍍� selectSatisfactionFillStat 閭f牱鎸夐鐩眹鎬荤瓟妗堣鏁帮紙閭f槸棰橀噺鍙e緞锛�
+     *
+     * @param query 鑱氬悎鏌ヨ鏉′欢
+     * @return 姣忚鍚� libtemplateid銆乧nt锛堣妯℃澘宸插洖鏀剁殑浠芥暟/浜烘暟锛�
+     */
+    public List<Map<String, Object>> selectSatisfactionFillPersonCount(SatisfactionStatQuery query);
+
+    /**
+     * 鎸� scriptid 鎵归噺缁熻濉啓璁板綍鏁帮紙涓�娆� SQL 鏇夸唬閫愪釜 scriptid 鏌ユ槑缁嗭級
+     * <p>
+     * 鍙e緞涓庡師鏉ャ�宷uery.setScriptid(scriptId) 鍚� selectServiceSubtaskDetailList(...).size()銆嶄竴鑷达細
+     * 鍙寜 scriptid 杩囨护锛屼笉甯� del_flag锛堝師 SQL 閲屽苟娌℃湁 del_flag 鏉′欢锛夈��
+     *
+     * @param scriptIds 闂ID闆嗗悎
+     * @return 姣忚鍚� scriptid銆乧nt
+     */
+    public List<Map<String, Object>> countDetailByScriptIds(@Param("scriptIds") List<Long> scriptIds);
+
     public ServiceSubtaskDetailDTO isExistServiceSubtaskDetail(@Param("subid") Long subid, @Param("scriptid") Long scriptid);
 
     /**
diff --git a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailTraceMapper.java b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailTraceMapper.java
index cb7a2fa..a13006a 100644
--- a/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailTraceMapper.java
+++ b/smartor/src/main/java/com/smartor/mapper/ServiceSubtaskDetailTraceMapper.java
@@ -1,10 +1,12 @@
 package com.smartor.mapper;
 
 import java.util.List;
+import java.util.Map;
 
 import com.smartor.domain.VO.DetailTraceDealVO;
 import com.smartor.domain.VO.ServiceSubtaskDetailTraceVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import com.smartor.domain.ServiceSubtaskDetailTrace;
 
 /**
@@ -32,7 +34,39 @@
     public List<ServiceSubtaskDetailTrace> selectServiceSubtaskDetailTtraceList(ServiceSubtaskDetailTraceVO ServiceSubtaskDetailTtrace);
 
 
-    public List<ServiceSubtaskDetailTrace> tracedeallist(DetailTraceDealVO detailTraceDealVO);
+    /**
+     * 寮傚父闂澶勭悊鎯呭喌鏌ヨ锛氬綋鍓嶉〉鐨勯棶棰樺垎缁勮仛鍚堬紙涓�涓� SQL 鎷垮埌鏈〉姣忎釜闂鐨勫紓甯告暟銆佸凡澶勭悊/鏈鐞嗘暟锛�
+     * <p>
+     * 鍒嗛〉鎸夈�岄棶棰樸�嶇淮搴︿笅鎺紝涓嶅啀鎶婃暣琛� trace 鏄庣粏鎷夊洖 Java 鍐嶅垎缁�
+     *
+     * @param vo 鏌ヨ鏉′欢锛坧ageNum 涓� offset锛屼笌 PageUtils.getOffset 涓�鑷达級
+     * @return 姣忚鍚� questiontext銆乻criptid銆乼emplateType銆乪xceptionFillNum銆亂esDeal銆乶oDeal
+     */
+    public List<Map<String, Object>> selectTraceDealGroups(@Param("vo") DetailTraceDealVO vo);
+
+    /**
+     * 寮傚父闂澶勭悊鎯呭喌鏌ヨ锛氶棶棰樻�绘暟锛堝垎椤� total锛屾寜 questiontext 鍘婚噸锛�
+     */
+    public long countTraceDealQuestions(@Param("vo") DetailTraceDealVO vo);
+
+    /**
+     * 寮傚父闂澶勭悊鎯呭喌鏌ヨ锛氬叏閲忔眹鎬伙紙涓嶅彈鍒嗛〉褰卞搷锛�
+     *
+     * @return totalException 寮傚父鎬绘暟銆亂esDeal 宸插鐞嗘暟
+     */
+    public Map<String, Object> sumTraceDeal(@Param("vo") DetailTraceDealVO vo);
+
+    /**
+     * 寮傚父闂澶勭悊鎯呭喌鏌ヨ锛氬綋鍓嶉〉闂鐨勮礋璐g瀹わ紙鎸� todeptcode 鍘婚噸锛�
+     */
+    public List<Map<String, Object>> selectTraceDealDepts(@Param("vo") DetailTraceDealVO vo,
+                                                         @Param("questiontexts") List<String> questiontexts);
+
+    /**
+     * 寮傚父闂澶勭悊鎯呭喌鏌ヨ锛氬綋鍓嶉〉闂鐨勬渶鏂板鐞嗕汉鍜屽鐞嗘椂闂达紙handle_time 鏈�澶х殑涓�鏉★級
+     */
+    public List<Map<String, Object>> selectTraceDealLatestHandle(@Param("vo") DetailTraceDealVO vo,
+                                                                @Param("questiontexts") List<String> questiontexts);
 
     /**
      * 鏂板闅忚浠诲姟寮傚父璁板綍璺熻釜
diff --git a/smartor/src/main/java/com/smartor/service/IPatMedOuthospService.java b/smartor/src/main/java/com/smartor/service/IPatMedOuthospService.java
index 6b66717..889501e 100644
--- a/smartor/src/main/java/com/smartor/service/IPatMedOuthospService.java
+++ b/smartor/src/main/java/com/smartor/service/IPatMedOuthospService.java
@@ -1,7 +1,9 @@
 package com.smartor.service;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.smartor.domain.*;
@@ -39,6 +41,18 @@
     public int insertPatMedOuthosp(PatMedOuthosp patMedOuthosp);
 
     /**
+     * 鎵归噺鏌ヨ宸插叆搴撶殑 serialnum锛堝唴閮ㄦ寜灏辫瘖鏃ユ湡瀹氫綅鍒嗚〃锛屼竴娆� SQL 瀹屾垚锛�
+     * <p>
+     * 闂ㄦ�ヨ瘖鎸夊ぉ鍏ㄩ噺閲囬泦鏃讹紝姣忓崐灏忔椂浼氭妸褰撳ぉ涓婁竾涓瘖鐤楄褰曢噸鏂版媺涓�閬嶏紝
+     * 鐢ㄨ繖涓柟娉曚竴娆℃煡鍑哄凡缁忓叆搴撶殑锛岀洿鎺ヨ烦杩囷紝閬垮厤瀵归噸澶嶆暟鎹啀鍋氫竴閬嶆。妗堝鐞嗗拰鍏ュ簱
+     *
+     * @param admitDateStr 灏辫瘖鏃ユ湡 yyyy-MM-dd锛屼负绌烘垨瑙f瀽澶辫触鏃舵寜涓昏〃鏌ヨ
+     * @param serialnums   寰呮煡璇㈢殑璇婄枟璁板綍鍙烽泦鍚�
+     * @return 宸插瓨鍦ㄧ殑 serialnum 闆嗗悎
+     */
+    public Set<String> selectExistingSerialnums(String admitDateStr, Collection<String> serialnums);
+
+    /**
      * 淇敼鎮h�呴棬璇婅褰�
      *
      * @param patMedOuthosp 鎮h�呴棬璇婅褰�
diff --git a/smartor/src/main/java/com/smartor/service/impl/PatMedOuthospServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/PatMedOuthospServiceImpl.java
index b73e932..9b0fcea 100644
--- a/smartor/src/main/java/com/smartor/service/impl/PatMedOuthospServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/PatMedOuthospServiceImpl.java
@@ -197,7 +197,48 @@
         patMedOuthosp.setUpdateTime(DateUtils.getNowDate());
         patMedOuthosp.setGuid(UUID.randomUUID().toString());
         log.info("[insert] 璺敱鍒板垎琛�: {}", targetTable);
-        return patMedOuthospMapper.insertIntoTable(targetTable, patMedOuthosp);
+        try {
+            return patMedOuthospMapper.insertIntoTable(targetTable, patMedOuthosp);
+        } catch (org.springframework.dao.DuplicateKeyException e) {
+            // 鍔犱簡 serialnum 鍞竴绱㈠紩鍚庯紝鏋佺骞跺彂鎴栭噸澶嶉噰闆嗕細琚暟鎹簱鐩存帴鎷︿笅锛�
+            // 杩欏睘浜�"璁板綍宸插瓨鍦�"锛屾寜姝e父璺宠繃澶勭悊锛堣繑鍥�0锛夛紝涓嶈褰撴垚鍏ュ簱澶辫触鍐欒繘閿欒琛�
+            log.info("[insert] serialnum={} 宸插瓨鍦ㄤ簬琛� {}锛堝敮涓�绱㈠紩鎷︽埅锛夛紝璺宠繃鎻掑叆", patMedOuthosp.getSerialnum(), targetTable);
+            return 0;
+        }
+    }
+
+    /**
+     * 鎵归噺鏌ヨ宸插叆搴撶殑 serialnum锛氭寜灏辫瘖鏃ユ湡瀹氫綅鍒嗚〃锛屼竴娆� IN 鏌ヨ鏌ュ嚭鏁撮〉宸插瓨鍦ㄧ殑璁板綍
+     * <p>
+     * 鍘熸潵姣忔潯璁板綍閮借 countBySerialnum 涓�娆★紝涓�椤� 500 鏉″氨鏄� 500 娆� SQL锛�
+     * 闂ㄦ�ヨ瘖鎸夊ぉ鍏ㄩ噺閲囬泦鏃舵瘡澶╄杩欎箞璺� 48 杞紝杩欓噷鍚堝苟鎴� 1 娆℃煡璇�
+     */
+    @Override
+    public Set<String> selectExistingSerialnums(String admitDateStr, Collection<String> serialnums) {
+        Set<String> existing = new HashSet<>();
+        if (CollectionUtils.isEmpty(serialnums)) {
+            return existing;
+        }
+        Date admitDate = null;
+        if (StringUtils.isNotBlank(admitDateStr)) {
+            try {
+                admitDate = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(admitDateStr);
+            } catch (java.text.ParseException e) {
+                log.warn("[selectExistingSerialnums] 灏辫瘖鏃ユ湡瑙f瀽澶辫触锛歿}锛屾寜涓昏〃鏌ヨ", admitDateStr);
+            }
+        }
+        // 涓庢彃鍏ユ椂鐨勮矾鐢辫鍒欎繚鎸佷竴鑷达紝淇濊瘉鏌ラ噸鏌ョ殑灏辨槸瑕佸啓鍏ョ殑閭e紶琛�
+        String targetTable = admitDate == null ? null : resolveTargetTable(admitDate);
+        if (targetTable == null) {
+            targetTable = "pat_med_outhosp";
+        }
+        List<String> existed = patMedOuthospMapper.selectExistingSerialnums(targetTable, serialnums);
+        if (!CollectionUtils.isEmpty(existed)) {
+            existing.addAll(existed);
+        }
+        log.info("[selectExistingSerialnums] 鏃ユ湡={}锛屽垎琛�={}锛屽緟鏌}鏉★紝宸插瓨鍦▄}鏉�",
+                admitDateStr, targetTable, serialnums.size(), existing.size());
+        return existing;
     }
 
     /**
diff --git a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
index d952ca2..9e07d77 100644
--- a/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/PatSatisfactionServiceImpl.java
@@ -326,31 +326,40 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<IvrLibaTemplateScript> ivrLibaTemplateScripts = ivrLibaTemplateScriptMapper.queryLibTemplateIds(mydIdList);
 
-            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸~鎶ラ噺鏌ュ洖鏉ワ紙鍘熸潵鏄瘡涓ā鏉块棶棰樺悇鏌ヤ袱娆★級
+            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
             SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
                     ivrLibaTemplateScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
             Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
             SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
 
             //3 鎸塻criptAssortname鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
             Map<String, List<IvrLibaTemplateScript>> scriptGroupByAssortName = ivrLibaTemplateScripts.stream().filter(item -> item.getScriptAssortname() != null).collect(Collectors.groupingBy(IvrLibaTemplateScript::getScriptAssortname));
             if (scriptGroupByAssortName != null && !scriptGroupByAssortName.isEmpty()) {
                 for (String assortNameKey : scriptGroupByAssortName.keySet()) {
-                    //璁板綍鎬讳汉鏁�
-                    Double subidAll = 0.0;
-                    //璁板綍鎬荤殑濉姤浜烘暟
-                    Double fillCountAll = 0.0;
-                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
+                    // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
+                    // 鎸夐鐩惊鐜疮鍔犱細鎶婂悓涓�涓ā鏉跨殑閲忛噸澶嶈鎴愩�岄閲忋��
+                    Set<Long> categoryTemplateIds = new LinkedHashSet<>();
+                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
                     List<ScoreCount> allFillCountList = new ArrayList<>();
 
                     List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = scriptGroupByAssortName.get(assortNameKey);
                     for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
-                        //閫氳繃妯℃澘id鍙栭棶棰樺彂閫佹�婚噺锛堝師鏉ユ槸鎶婂叏閮� sub_id 鏌ュ洖 Java 鍐嶅彇 size锛�
-                        subidAll += sendCountMap.getOrDefault(ivrLibaTemplateScript.getTemplateid(), 0L);
-                        //鑾峰彇濉姤鏁伴噺锛堝師鏉ユ槸鎶婃槑缁嗚鍏ㄩ儴鏌ュ洖 Java 鍐嶅彇 size锛�
-                        List<ScoreCount> fillCount = fillStat.get(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptContent());
+                        Long templateId = ivrLibaTemplateScript.getTemplateid();
+                        if (templateId != null) {
+                            categoryTemplateIds.add(templateId);
+                        }
+                        //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                        List<ScoreCount> fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent());
                         if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
-                        fillCountAll += countFillRows(fillCount);
+                    }
+
+                    //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                    Double subidAll = 0.0;
+                    Double fillCountAll = 0.0;
+                    for (Long templateId : categoryTemplateIds) {
+                        subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                        fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                     }
 
                     // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
@@ -375,31 +384,40 @@
             //2 鍐嶈幏鍙栬闂婊℃剰搴﹀垎绫讳笅鐨処vrLibaTemplateScript鐨勯泦鍚�
             List<SvyLibTemplateScript> svyLibTemplateScripts = svyLibTemplateScriptMapper.querySvyLibTemplateIds(mydIdList);
 
-            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸~鎶ラ噺鏌ュ洖鏉ワ紙鍘熸潵鏄瘡涓ā鏉块棶棰樺悇鏌ヤ袱娆★級
+            //2.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
             SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
                     svyLibTemplateScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
             Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
             SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+            Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
 
             //3 鎸塻criptAssortname鍒嗙粍,骞惰幏鍙栨瘡涓泦鍚堣繘琛岄亶鍘嗭紝璁$畻姣忎釜鍒嗙被鐨勬�讳汉鏁板拰濉姤浜烘暟
             Map<String, List<SvyLibTemplateScript>> scriptGroupByatCegoryName = svyLibTemplateScripts.stream().filter(item -> item.getCategoryName() != null).collect(Collectors.groupingBy(SvyLibTemplateScript::getCategoryName));
             if (scriptGroupByatCegoryName != null && !scriptGroupByatCegoryName.isEmpty()) {
                 for (String categoryNameKey : scriptGroupByatCegoryName.keySet()) {
-                    //璁板綍鎬讳汉鏁�
-                    Double subidAll = 0.0;
-                    //璁板綍鎬荤殑濉姤浜烘暟
-                    Double fillCountAll = 0.0;
-                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
+                    // 璇ュ垎绫绘秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛�
+                    // 鎸夐鐩惊鐜疮鍔犱細鎶婂悓涓�涓ā鏉跨殑閲忛噸澶嶈鎴愩�岄閲忋��
+                    Set<Long> categoryTemplateIds = new LinkedHashSet<>();
+                    //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲忥紙鍙敤浜庣畻骞冲潎鍒嗭紝鎸夐鐩仛鍚堢殑琛屾暟锛�
                     List<ScoreCount> allFillCountList = new ArrayList<>();
 
                     List<SvyLibTemplateScript> svyLibTemplateScriptList = scriptGroupByatCegoryName.get(categoryNameKey);
                     for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
-                        //閫氳繃妯℃澘id鍙栭棶棰樺彂閫佹�婚噺锛堝師鏉ユ槸鎶婂叏閮� sub_id 鏌ュ洖 Java 鍐嶅彇 size锛�
-                        subidAll += sendCountMap.getOrDefault(svyLibTemplateScript.getSvyid(), 0L);
-                        //鑾峰彇濉姤鏁伴噺锛堝師鏉ユ槸鎶婃槑缁嗚鍏ㄩ儴鏌ュ洖 Java 鍐嶅彇 size锛�
-                        List<ScoreCount> fillCount = fillStat.get(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getScriptContent());
+                        Long templateId = svyLibTemplateScript.getSvyid();
+                        if (templateId != null) {
+                            categoryTemplateIds.add(templateId);
+                        }
+                        //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                        List<ScoreCount> fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent());
                         if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
-                        fillCountAll += countFillRows(fillCount);
+                    }
+
+                    //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                    Double subidAll = 0.0;
+                    Double fillCountAll = 0.0;
+                    for (Long templateId : categoryTemplateIds) {
+                        subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                        fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                     }
 
 
@@ -453,28 +471,36 @@
                 Map<String, List<IvrLibaTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(IvrLibaTemplateScript::getDimension));
 
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
-                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸~鎶ラ噺鏌ュ洖鏉ワ紙鍘熸潵鏄瘡涓ā鏉块棶棰樺悇鏌ヤ袱娆★級
+                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
                     SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "1",
                             dimensionScripts.stream().map(IvrLibaTemplateScript::getTemplateid).collect(Collectors.toList()));
                     Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                     SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
 
                     for (String dimension : groupByDimension.keySet()) {
-                        //璁板綍鎬讳汉鏁�
-                        Double subidAll = 0.0;
-                        //璁板綍鎬荤殑濉姤浜烘暟
-                        Double fillCountAll = 0.0;
-                        //鑾峰彇璇ョ淮搴︾殑濉姤鎬绘暟閲�
+                        // 璇ョ淮搴︽秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛屾寜棰樼洰绱姞浼氬彉鎴愰閲�
+                        Set<Long> dimensionTemplateIds = new LinkedHashSet<>();
+                        //鑾峰彇璇ョ淮搴︾殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
                         List<ScoreCount> allFillCountList = new ArrayList<>();
 
                         List<IvrLibaTemplateScript> ivrLibaTemplateScriptList = groupByDimension.get(dimension);
                         for (IvrLibaTemplateScript ivrLibaTemplateScript : ivrLibaTemplateScriptList) {
-                            //閫氳繃妯℃澘id鍙栭棶棰樺彂閫佹�婚噺锛堝師鏉ユ槸鎶婂叏閮� sub_id 鏌ュ洖 Java 鍐嶅彇 size锛�
-                            subidAll += sendCountMap.getOrDefault(ivrLibaTemplateScript.getTemplateid(), 0L);
-                            //鑾峰彇濉姤鏁伴噺锛堝師鏉ユ槸鎶婃槑缁嗚鍏ㄩ儴鏌ュ洖 Java 鍐嶅彇 size锛�
-                            List<ScoreCount> fillCount = fillStat.get(ivrLibaTemplateScript.getTemplateid(), ivrLibaTemplateScript.getScriptContent());
+                            Long templateId = ivrLibaTemplateScript.getTemplateid();
+                            if (templateId != null) {
+                                dimensionTemplateIds.add(templateId);
+                            }
+                            //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                            List<ScoreCount> fillCount = fillStat.get(templateId, ivrLibaTemplateScript.getScriptContent());
                             if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
-                            fillCountAll += countFillRows(fillCount);
+                        }
+
+                        //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                        Double subidAll = 0.0;
+                        Double fillCountAll = 0.0;
+                        for (Long templateId : dimensionTemplateIds) {
+                            subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                            fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                         }
 
                         // 杩欓噷鍏堣褰曚竴涓嬫瘡涓垎绫荤殑鎬讳汉鏁板拰濉姤浜烘暟
@@ -502,28 +528,36 @@
                 List<SvyLibTemplateScript> dimensionScripts = svyLibTemplateScripts.stream().filter(item -> item.getDimension() != null).collect(Collectors.toList());
                 Map<String, List<SvyLibTemplateScript>> groupByDimension = dimensionScripts.stream().collect(Collectors.groupingBy(SvyLibTemplateScript::getDimension));
                 if (groupByDimension != null && !groupByDimension.isEmpty()) {
-                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸~鎶ラ噺鏌ュ洖鏉ワ紙鍘熸潵鏄瘡涓ā鏉块棶棰樺悇鏌ヤ袱娆★級
+                    //3.1 涓�娆℃�ф妸鏈璇锋眰鍏ㄩ儴妯℃澘鐨勫彂閫侀噺銆佸洖鏀朵汉鏁版煡鍥炴潵锛堝師鏉ユ槸姣忎釜妯℃澘闂鍚勬煡涓ゆ锛�
                     SatisfactionStatQuery statQuery = buildSatisfactionStatQuery(patSatisfactionReqVO, "2",
                             dimensionScripts.stream().map(SvyLibTemplateScript::getSvyid).collect(Collectors.toList()));
                     Map<Long, Long> sendCountMap = mapSatisfactionSendCount(statQuery);
                     SatisfactionFillStat fillStat = querySatisfactionFillStat(statQuery);
+                    Map<Long, Long> fillPersonCountMap = mapSatisfactionFillPersonCount(statQuery);
 
                     for (String dimension : groupByDimension.keySet()) {
-                        //璁板綍鎬讳汉鏁�
-                        Double subidAll = 0.0;
-                        //璁板綍鎬荤殑濉姤浜烘暟
-                        Double fillCountAll = 0.0;
-                        //鑾峰彇璇ュ垎绫荤殑濉姤鎬绘暟閲�
+                        // 璇ョ淮搴︽秹鍙婄殑鍘婚噸妯℃澘锛氬彂閫佷汉鏁�/鍥炴敹浜烘暟閮芥寜妯℃澘缁熻锛屾寜棰樼洰绱姞浼氬彉鎴愰閲�
+                        Set<Long> dimensionTemplateIds = new LinkedHashSet<>();
+                        //鑾峰彇璇ュ垎绫荤殑濉姤鍒嗗�煎垎甯冿紙鍙敤浜庣畻骞冲潎鍒嗭級
                         List<ScoreCount> allFillCountList = new ArrayList<>();
 
                         List<SvyLibTemplateScript> svyLibTemplateScriptList = groupByDimension.get(dimension);
                         for (SvyLibTemplateScript svyLibTemplateScript : svyLibTemplateScriptList) {
-                            //閫氳繃妯℃澘id鍙栭棶棰樺彂閫佹�婚噺锛堝師鏉ユ槸鎶婂叏閮� sub_id 鏌ュ洖 Java 鍐嶅彇 size锛�
-                            subidAll += sendCountMap.getOrDefault(svyLibTemplateScript.getSvyid(), 0L);
-                            //鑾峰彇濉姤鏁伴噺锛堝師鏉ユ槸鎶婃槑缁嗚鍏ㄩ儴鏌ュ洖 Java 鍐嶅彇 size锛�
-                            List<ScoreCount> fillCount = fillStat.get(svyLibTemplateScript.getSvyid(), svyLibTemplateScript.getScriptContent());
+                            Long templateId = svyLibTemplateScript.getSvyid();
+                            if (templateId != null) {
+                                dimensionTemplateIds.add(templateId);
+                            }
+                            //鑾峰彇濉姤鍒嗗�煎垎甯冿紙鍙敤浜庡钩鍧囧垎锛�
+                            List<ScoreCount> fillCount = fillStat.get(templateId, svyLibTemplateScript.getScriptContent());
                             if (CollectionUtils.isNotEmpty(fillCount)) allFillCountList.addAll(fillCount);
-                            fillCountAll += countFillRows(fillCount);
+                        }
+
+                        //璁板綍鍙戦�佹�讳汉鏁般�佸洖鏀舵�讳汉鏁�
+                        Double subidAll = 0.0;
+                        Double fillCountAll = 0.0;
+                        for (Long templateId : dimensionTemplateIds) {
+                            subidAll += sendCountMap.getOrDefault(templateId, 0L);
+                            fillCountAll += fillPersonCountMap.getOrDefault(templateId, 0L);
                         }
 
 
@@ -644,6 +678,24 @@
     }
 
     /**
+     * 涓�娆℃煡鍑哄叏閮ㄦā鏉跨殑銆屽洖鏀朵汉鏁般�嶏紙鎸夋ā鏉垮幓閲嶏紝鍚屼竴涓汉绛斿閬撻鍙畻涓�浜猴級
+     *
+     * @return key=妯℃澘id锛寁alue=鍥炴敹浜烘暟
+     */
+    private Map<Long, Long> mapSatisfactionFillPersonCount(SatisfactionStatQuery statQuery) {
+        Map<Long, Long> fillPersonCountMap = new HashMap<>();
+        if (statQuery == null || CollectionUtils.isEmpty(statQuery.getLibTemplateIds())) return fillPersonCountMap;
+        List<Map<String, Object>> rows = serviceSubtaskDetailMapper.selectSatisfactionFillPersonCount(statQuery);
+        if (CollectionUtils.isEmpty(rows)) return fillPersonCountMap;
+        for (Map<String, Object> row : rows) {
+            Long templateId = toLong(row.get("libtemplateid"));
+            if (templateId == null) continue;
+            fillPersonCountMap.put(templateId, toLong(row.get("cnt"), 0L));
+        }
+        return fillPersonCountMap;
+    }
+
+    /**
      * 涓�娆℃煡鍑哄叏閮ㄦā鏉跨殑濉姤閲忥紙鎸� 妯℃澘+闂+鍒嗗�� 鍒嗙粍锛�
      */
     private SatisfactionFillStat querySatisfactionFillStat(SatisfactionStatQuery statQuery) {
@@ -662,18 +714,6 @@
             fillStat.add(templateId, questiontextValue == null ? null : String.valueOf(questiontextValue), scoreCount);
         }
         return fillStat;
-    }
-
-    /**
-     * 鍙栧~鎶ユ槑缁嗚鏁帮紙鍘� fillCount.size()锛�
-     */
-    private long countFillRows(List<ScoreCount> fillCount) {
-        if (CollectionUtils.isEmpty(fillCount)) return 0L;
-        long rows = 0L;
-        for (ScoreCount scoreCount : fillCount) {
-            rows += scoreCount.getCount();
-        }
-        return rows;
     }
 
     /**
diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java
index d2d357c..258cbe8 100644
--- a/smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceSLTDHealthcareRecordServiceImpl.java
@@ -93,6 +93,9 @@
     @Autowired
     private PatMedInhospMapper patMedInhospMapper;
 
+    @Autowired
+    private PatMedOuthospErrorMapper patMedOuthospErrorMapper;
+
     @Override
     public List<ServiceSLTDInhospResDTO> queryHealthcareRecordList(ServiceSLTDInhospReqVO reqVO) {
         try {
@@ -503,7 +506,12 @@
             log.info("銆恜arseResponseData銆戞垚鍔熻В鏋恉ataList:{}鏉″仴搴疯褰曟暟鎹�", dataList.size());
             List<ServiceSLTDInhospResDTO> resultList = new ArrayList<>();
             for (Map<String, Object> dataItem : dataList) {
-                resultList.add(convertToDTO(dataItem));
+                try {
+                    resultList.add(convertToDTO(dataItem));
+                } catch (Exception e) {
+                    // 鍗曟潯鏁版嵁杞崲澶辫触涓嶅啀璁╂暣椤靛け璐ワ細鍐欓敊璇〃浜ょ粰浜哄伐澶勭悊锛屽叾浣欒褰曠収甯稿叆搴�
+                    saveOuthospError(dataItem, "CONVERT", e.getMessage(), new Gson().toJson(dataItem), e);
+                }
             }
             log.info("銆恜arseResponseData銆戞垚鍔熻В鏋恟esultList:{}鏉″仴搴疯褰曟暟鎹�", resultList.size());
 
@@ -613,34 +621,184 @@
     }
 
     private Boolean outHospitalDate(List<ServiceSLTDInhospResDTO> resultList) {
-        List<PatMedOuthosp> batchList = new ArrayList<>();
-
+        // 闂ㄦ�ヨ瘖鍦� HIS 渚у彧鑳芥寜澶╂煡锛岄噰闆嗕换鍔℃瘡鍗婂皬鏃堕兘浼氭妸褰撳ぉ涓婁竾涓瘖鐤楄褰曢噸鏂版媺涓�閬嶏紝
+        // 鍏朵腑缁濆ぇ澶氭暟涓婁竴杞凡缁忓叆搴撱�傝繖閲屽厛鏁撮〉鎵归噺鏌ラ噸锛屾妸宸插叆搴撶殑鐩存帴璺宠繃锛�
+        // 鍙鏂板璁板綍鍋�"妗f澶勭悊 + 鍏ュ簱"锛屽惁鍒欐瘡鏉¢噸澶嶆暟鎹兘瑕佽蛋涓�閬�
+        // Redis鍔犻攣 + 妗f鏌ラ噸 + 妗f鏇存柊 + countBySerialnum + insert锛屼竴涓囨潯灏辨槸鍑犱竾娆� SQL锛屽崐灏忔椂璺戜笉瀹屻��
+        Set<String> pageSerialnums = new HashSet<>();
+        List<ServiceSLTDInhospResDTO> uniqueList = new ArrayList<>();
+        List<ServiceSLTDInhospResDTO> noSerialnumList = new ArrayList<>();
+        Map<String, List<String>> serialnumsByDate = new LinkedHashMap<>();
         for (ServiceSLTDInhospResDTO dto : resultList) {
             if (Objects.isNull(dto)) {
                 continue;
             }
-            PatArchive patArchive = processPatientArchive(dto);
-            PatMedOuthosp patMedOuthosp = buildPatMedOuthosp(dto, patArchive);
-            batchList.add(patMedOuthosp);
+            String serialnum = dto.getHealthcareRecordNo();
+            if (StringUtils.isEmpty(serialnum)) {
+                // 娌℃湁璇婄枟璁板綍鍙峰氨娌℃硶鏌ラ噸锛屼繚鎸佸師鏈夎涓虹収甯稿鐞嗭紙鍏ュ簱鐜妭杩樹細鍐嶅厹涓�娆★級
+                log.warn("銆恛utHospitalDate銆戣瘖鐤楄褰曞彿涓虹┖锛屾棤娉曟煡閲嶏紝鎸夋柊澧炲鐞嗭細patientName={}", dto.getPatientName());
+                noSerialnumList.add(dto);
+                continue;
+            }
+            if (!pageSerialnums.add(serialnum)) {
+                // 鍚屼竴椤靛唴閲嶅鐨勮褰曪紝鍙鐞嗕竴鏉�
+                continue;
+            }
+            uniqueList.add(dto);
+            // 鎸夊氨璇婃棩鏈熷垎缁勶紙璺ㄩ浂鐐规椂鏈�澶氫袱涓棩鏈燂級锛屼繚璇佹煡閲嶇殑鍒嗚〃鍜屽叆搴撴椂鍒嗚〃璺敱涓�鑷�
+            String admitDateStr = formatAdmitDate(dto.getLastEncounterTime());
+            serialnumsByDate.computeIfAbsent(admitDateStr == null ? "" : admitDateStr, k -> new ArrayList<>()).add(serialnum);
         }
+
+        // 涓�娆� SQL 鏌ュ嚭杩欎竴椤甸噷宸茬粡鍏ュ簱鐨� serialnum锛堝彇浠i�愭潯 countBySerialnum锛�
+        Set<String> existingSerialnums = new HashSet<>();
+        for (Map.Entry<String, List<String>> entry : serialnumsByDate.entrySet()) {
+            try {
+                existingSerialnums.addAll(patMedOuthospService.selectExistingSerialnums(entry.getKey(), entry.getValue()));
+            } catch (Exception e) {
+                // 鎵归噺鏌ラ噸澶辫触涓嶅奖鍝嶆纭�э細涓嬮潰鍏ュ簱鏃舵瘡鏉¤繕浼氬啀鏌ヤ竴娆¢噸锛屽彧鏄參涓�浜�
+                log.error("銆恛utHospitalDate銆戞壒閲忔煡閲嶅け璐ワ紝閫�鍖栦负閫愭潯鏌ラ噸锛屽氨璇婃棩鏈燂細{}", entry.getKey(), e);
+            }
+        }
+
+        List<PatMedOuthosp> batchList = new ArrayList<>();
+        int existSkipCount = 0;
+        for (ServiceSLTDInhospResDTO dto : uniqueList) {
+            if (existingSerialnums.contains(dto.getHealthcareRecordNo())) {
+                existSkipCount++;
+                continue;
+            }
+            PatMedOuthosp patMedOuthosp = buildNewOuthosp(dto);
+            if (patMedOuthosp != null) {
+                batchList.add(patMedOuthosp);
+            }
+        }
+        for (ServiceSLTDInhospResDTO dto : noSerialnumList) {
+            PatMedOuthosp patMedOuthosp = buildNewOuthosp(dto);
+            if (patMedOuthosp != null) {
+                batchList.add(patMedOuthosp);
+            }
+        }
+        log.info("銆恛utHospitalDate銆戞湰椤祘}鏉★紝鍘婚噸鍚巤}鏉★紝宸插叆搴撹烦杩噞}鏉★紝寰呭叆搴搟}鏉�",
+                resultList.size(), uniqueList.size(), existSkipCount, batchList.size());
 
         // 鎵归噺鎻掑叆锛岃嚜鍔ㄥ拷鐣ラ噸澶�
         if (CollectionUtils.isNotEmpty(batchList)) {
-            try {
-                //杩欓噷鐢ㄨ嚜鍔ㄥ拷鐣ラ噸澶嶄細鏈夐棶棰橈紝濡傛灉琛ㄩ噸鏂板缓锛宻erialnum绱㈠紩浼氫涪澶便�傚氨鏃犳硶鍘婚噸锛屾墍浠ヨ繕鏄渶瑕佸唴瀛橀噷鍘婚噸
-                batchList = DistinctByProperty.distinctByProperty(batchList, PatMedOuthosp::getSerialnum);
-                Integer insertCount = 0;
-                for (PatMedOuthosp patMedOuthosp : batchList) {
-                    insertCount += patMedOuthospService.insertPatMedOuthosp(patMedOuthosp);
+            //杩欓噷鐢ㄨ嚜鍔ㄥ拷鐣ラ噸澶嶄細鏈夐棶棰橈紝濡傛灉琛ㄩ噸鏂板缓锛宻erialnum绱㈠紩浼氫涪澶便�傚氨鏃犳硶鍘婚噸锛屾墍浠ヨ繕鏄渶瑕佸唴瀛橀噷鍘婚噸
+            batchList = DistinctByProperty.distinctByProperty(batchList, PatMedOuthosp::getSerialnum);
+            int insertCount = 0;
+            int skipCount = 0;
+            List<String> failSerialnums = new ArrayList<>();
+            for (PatMedOuthosp patMedOuthosp : batchList) {
+                try {
+                    int inserted = patMedOuthospService.insertPatMedOuthosp(patMedOuthosp);
+                    if (inserted > 0) {
+                        insertCount += inserted;
+                    } else {
+                        // 杩斿洖0琛ㄧず宸插瓨鍦紙閲嶅閲囬泦锛夋垨灏辫瘖鏃堕棿涓虹┖锛屽睘浜庢甯歌烦杩囷紝涓嶇畻澶辫触
+                        skipCount++;
+                    }
+                } catch (Exception e) {
+                    // 鍗曟潯鍏ュ簱澶辫触涓嶅啀涓柇閲囬泦锛氳惤閿欒琛ㄤ氦缁欎汉宸ュ鐞嗭紝閲囬泦鏂偣鐓у父鎺ㄨ繘
+                    failSerialnums.add(patMedOuthosp.getSerialnum());
+                    saveOuthospError(null, patMedOuthosp, "INSERT", e.getMessage(), new Gson().toJson(patMedOuthosp), e);
                 }
+            }
+            log.info("鎵归噺鎻掑叆闂ㄦ�ヨ瘖璁板綍锛屾�绘暟锛歿}, 瀹為檯鎻掑叆锛歿}, 璺宠繃锛堝凡瀛樺湪/鏃犲氨璇婃椂闂达級锛歿}, 钀介敊璇〃锛歿}",
+                    batchList.size(), insertCount, skipCount, failSerialnums.size());
 
-                log.info("鎵归噺鎻掑叆闂ㄦ�ヨ瘖璁板綍锛屾�绘暟锛歿}, 瀹為檯鎻掑叆锛歿}", batchList.size(), insertCount);
-
-            } catch (Exception e) {
-                log.error("鎵归噺鎻掑叆闂ㄦ�ヨ瘖璁板綍澶辫触", e);
+            if (!failSerialnums.isEmpty()) {
+                // 澶辫触鏄庣粏宸查�愭潯鍐欒繘 pat_med_outhosp_error锛岃繖閲屽彧鎻愮ず锛屼笉鍐嶆姏寮傚父闃绘柇閲囬泦
+                log.error("闂ㄦ�ヨ瘖璁板綍鍏ュ簱澶辫触 {} 鏉★紝宸插啓鍏� pat_med_outhosp_error 绛変汉宸ュ鐞嗭紝serialnum锛歿}",
+                        failSerialnums.size(), failSerialnums);
             }
         }
         return true;
+    }
+
+    /**
+     * 澶勭悊鍗曟潯鏂板璁板綍鐨勬偅鑰呮。妗堝苟鏋勫缓闂ㄦ�ヨ瘖璁板綍锛�
+     * 澶辫触鏃跺啓閿欒琛ㄤ氦缁欎汉宸ュ鐞嗗苟杩斿洖 null锛屼笉褰卞搷鍚屼竴椤甸噷鍏朵粬璁板綍
+     */
+    private PatMedOuthosp buildNewOuthosp(ServiceSLTDInhospResDTO dto) {
+        try {
+            PatArchive patArchive = processPatientArchive(dto);
+            return buildPatMedOuthosp(dto, patArchive);
+        } catch (Exception e) {
+            saveOuthospError(dto, null, "ARCHIVE", e.getMessage(), new Gson().toJson(dto), e);
+            return null;
+        }
+    }
+
+    /** 閲囬泦澶辫触鐨勫師濮嬭褰曟潵鑷帴鍙h繑鍥炵殑 Map锛堟暟鎹浆鎹㈠け璐ユ椂杩樻病鏈� DTO锛� */
+    private void saveOuthospError(Map<String, Object> dataItem, String errorType, String errorMsg,
+                                  String rawData, Throwable cause) {
+        PatMedOuthospError error = new PatMedOuthospError();
+        error.setSerialnum(getStringValue(dataItem, "healthcareRecordNo"));
+        error.setOuthospno(getStringValue(dataItem, "healthcareRecordNo"));
+        error.setPatname(getStringValue(dataItem, "patientName"));
+        error.setPatno(getStringValue(dataItem, "medicalRecordNo"));
+        error.setCampusid(getStringValue(dataItem, "campusId"));
+        error.setOrgid(getStringValue(dataItem, "orgId"));
+        doSaveOuthospError(error, errorType, errorMsg, rawData, cause);
+    }
+
+    /** 閲囬泦澶辫触鐨勫師濮嬭褰曟潵鑷� DTO / 寰呭叆搴撶殑 PatMedOuthosp */
+    private void saveOuthospError(ServiceSLTDInhospResDTO dto, PatMedOuthosp patMedOuthosp, String errorType,
+                                  String errorMsg, String rawData, Throwable cause) {
+        PatMedOuthospError error = new PatMedOuthospError();
+        if (dto != null) {
+            error.setSerialnum(dto.getHealthcareRecordNo());
+            error.setOuthospno(dto.getHealthcareRecordNo());
+            error.setPatname(dto.getPatientName());
+            error.setPatno(dto.getMedicalRecordNo());
+            error.setCampusid(dto.getCampusId());
+            error.setOrgid(dto.getOrgId());
+            error.setAdmitdate(parseDate(dto.getLastEncounterTime()));
+        }
+        if (patMedOuthosp != null) {
+            if (StringUtils.isEmpty(error.getSerialnum())) {
+                error.setSerialnum(patMedOuthosp.getSerialnum());
+            }
+            error.setOuthospno(patMedOuthosp.getOuthospno());
+            error.setPatname(patMedOuthosp.getPatname());
+            error.setPatno(patMedOuthosp.getPatno());
+            error.setCampusid(patMedOuthosp.getCampusid());
+            error.setOrgid(patMedOuthosp.getOrgid());
+            error.setAdmitdate(patMedOuthosp.getAdmitdate());
+        }
+        doSaveOuthospError(error, errorType, errorMsg, rawData, cause);
+    }
+
+    /**
+     * 鍐欓棬鎬ヨ瘖閲囬泦澶辫触璁板綍琛紝浜ょ粰浜哄伐澶勭悊銆�
+     * 杩欓噷鍏滀綇涓�鍒囧紓甯革細杩為敊璇〃閮藉啓涓嶈繘鍘绘椂鍙墦鏃ュ織锛堝甫鍘熷鏁版嵁锛夛紝缁濅笉鍥犱负鍐欓敊璇〃澶辫触鑰屼腑鏂噰闆�
+     */
+    private void doSaveOuthospError(PatMedOuthospError error, String errorType, String errorMsg,
+                                    String rawData, Throwable cause) {
+        error.setErrorType(errorType);
+        error.setErrorMsg(truncate(errorMsg == null || errorMsg.isEmpty() ? String.valueOf(cause) : errorMsg, 1000));
+        error.setRawData(rawData);
+        error.setFailCount(1);
+        error.setHandleStatus("0");
+        error.setGatherDate(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
+        error.setCreateTime(DateUtils.getNowDate());
+        try {
+            patMedOuthospErrorMapper.insertPatMedOuthospError(error);
+            log.error("銆愰棬鎬ヨ瘖閲囬泦銆憑} 澶辫触锛屽凡鍐欏叆 pat_med_outhosp_error 绛変汉宸ュ鐞嗭紝serialnum={}锛屾偅鑰�={}锛屽師鍥�={}",
+                    errorType, error.getSerialnum(), error.getPatname(), error.getErrorMsg(), cause);
+        } catch (Exception ex) {
+            log.error("銆愰棬鎬ヨ瘖閲囬泦銆憑} 澶辫触锛屼笖鍐欏叆閿欒琛ㄤ篃澶辫触锛宻erialnum={}锛屽師濮嬫暟鎹細{}",
+                    errorType, error.getSerialnum(), rawData, ex);
+        }
+    }
+
+    /** 鎴柇瓒呴暱鏂囨湰锛岄伩鍏嶈秴鍑洪敊璇〃瀛楁闀垮害瀵艰嚧钀借〃澶辫触 */
+    private String truncate(String text, int maxLength) {
+        if (text == null) {
+            return null;
+        }
+        return text.length() <= maxLength ? text : text.substring(0, maxLength);
     }
 
     private PatMedOuthosp buildPatMedOuthosp(ServiceSLTDInhospResDTO dto, PatArchive patArchive) {
@@ -681,6 +839,18 @@
         }
     }
 
+    /**
+     * 鍙栧氨璇婃棩鏈熺殑 yyyy-MM-dd 閮ㄥ垎锛屼笌 buildPatMedOuthosp 閲岀殑 admitdate 淇濇寔瀹屽叏涓�鑷寸殑瑙f瀽瑙勫垯锛�
+     * 淇濊瘉鎵归噺鏌ラ噸鐢ㄧ殑鍒嗚〃鍜屽叆搴撴椂璺敱鍒扮殑鍒嗚〃鏄悓涓�寮�
+     */
+    private String formatAdmitDate(String lastEncounterTime) {
+        Date admitDate = parseDate(lastEncounterTime);
+        if (admitDate == null) {
+            return null;
+        }
+        return new SimpleDateFormat("yyyy-MM-dd").format(admitDate);
+    }
+
     private Boolean inHospitalDate(List<ServiceSLTDInhospResDTO> resultList, String cry) {
         for (ServiceSLTDInhospResDTO dto : resultList) {
             if (Objects.isNull(dto)) continue;
diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
index b19a590..985cbbb 100644
--- a/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskDetailTraceServiceImpl.java
@@ -80,78 +80,115 @@
 
     @Override
     public Map<String, Object> tracedeallist(DetailTraceDealVO detailTraceDealVO) {
-        // 鍒嗛〉鏌ヨ
-        List<ServiceSubtaskDetailTrace> tracedeallist = serviceSubtaskDetailTraceMapper.tracedeallist(detailTraceDealVO);
-
-        // 鎸� questiontext 鍒嗙粍
-        Map<String, List<ServiceSubtaskDetailTrace>> groupByQuestion = tracedeallist.stream().filter(t -> t.getQuestiontext() != null).collect(Collectors.groupingBy(ServiceSubtaskDetailTrace::getQuestiontext));
+        // 浼樺寲鍓嶏細鈶� 杩欐潯 SQL 娌℃湁 limit锛屾瘡娆¢兘鎶婃暣琛� trace锛堝惈 patdesc/recordurl 澶у瓧娈碉級鎷夊洖 Java 鍐嶅垎缁勶紱
+        //        鈶� 姣忎釜闂閮芥嬁 scriptid 鍘� service_subtask_detail 鏌ヤ竴娆″叏琛ㄥ彧涓哄彇 size锛圢+1锛夛紱
+        //        鈶� controller 鎶婅繖涓柟娉曡皟浜嗕袱閬嶏紙绗簩閬嶄负浜嗘�绘暟锛夛紝绛変簬涓婇潰涓ゆ鍚勮窇涓ゆ銆�
+        // 浼樺寲鍚庯細鍥哄畾鐨� 5 鏉¤仛鍚� SQL锛孌B 鍙鐞嗗綋鍓嶉〉鐨勯鐩��
+        //  1) 褰撳墠椤电殑闂鍒嗙粍鑱氬悎锛堝垎椤典笅鎺ㄥ埌 SQL锛�
+        List<Map<String, Object>> groupRows = serviceSubtaskDetailTraceMapper.selectTraceDealGroups(detailTraceDealVO);
+        //  2) 闂鎬绘暟锛堝垎椤� total锛屽叏閲忓彛寰勶級
+        long questionTotal = serviceSubtaskDetailTraceMapper.countTraceDealQuestions(detailTraceDealVO);
+        //  3) 姹囨�荤粺璁★紙鍏ㄩ噺鍙e緞锛屼笉鍙楀垎椤靛奖鍝嶏級
+        Map<String, Object> sumRow = serviceSubtaskDetailTraceMapper.sumTraceDeal(detailTraceDealVO);
+        long totalException = toLong(sumRow == null ? null : sumRow.get("totalException"), 0L);
+        long yesDealException = toLong(sumRow == null ? null : sumRow.get("yesDeal"), 0L);
+        long noDealException = totalException - yesDealException;
 
         List<DetailTraceDealDTO> detailTraceDealDTOList = new ArrayList<>();
-        long totalException = 0L;
-        long noDealException = 0L;
-        long yesDealException = 0L;
+        if (CollectionUtils.isNotEmpty(groupRows)) {
+            // 鏈〉娑夊強鐨勯棶棰樻枃鏈紝鍚庨潰 3 鏉℃煡璇㈤兘鎸夊畠杩囨护
+            List<String> questiontexts = groupRows.stream()
+                    .map(row -> row.get("questiontext"))
+                    .filter(Objects::nonNull)
+                    .map(String::valueOf)
+                    .distinct()
+                    .collect(Collectors.toList());
 
-        for (Map.Entry<String, List<ServiceSubtaskDetailTrace>> entry : groupByQuestion.entrySet()) {
-            List<ServiceSubtaskDetailTrace> group = entry.getValue();
-            DetailTraceDealDTO dto = new DetailTraceDealDTO();
+            //  4) 鏈〉闂鐨勮礋璐g瀹わ紙鎸夌瀹ょ紪鍙峰幓閲嶏級
+            Map<String, List<Map<String, Object>>> deptMap = new HashMap<>();
+            //  5) 鏈〉闂鐨勬渶鏂板鐞嗕汉/澶勭悊鏃堕棿
+            Map<String, Map<String, Object>> latestHandleMap = new HashMap<>();
+            if (CollectionUtils.isNotEmpty(questiontexts)) {
+                for (Map<String, Object> row : serviceSubtaskDetailTraceMapper.selectTraceDealDepts(detailTraceDealVO, questiontexts)) {
+                    String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
+                    if (questiontext == null) continue;
+                    Map<String, Object> deptMapItem = new HashMap<>();
+                    deptMapItem.put("deptName", row.get("deptName"));
+                    deptMapItem.put("deptCode", row.get("deptCode"));
+                    deptMap.computeIfAbsent(questiontext, key -> new ArrayList<>()).add(deptMapItem);
+                }
 
-            // 闂鍐呭
-            dto.setQuestiontext(entry.getKey());
-            if (CollectionUtils.isNotEmpty(group)) {
-                dto.setScriptid(group.get(0).getScriptid());
-                dto.setTemplateType(group.get(0).getTemplateType());
-            }
-            // 璐熻矗绉戝锛堝幓閲嶏級
-            List<Map<String, Object>> responsibleDept = group.stream().filter(t -> t.getTodeptcode() != null).collect(Collectors.collectingAndThen(Collectors.toMap(ServiceSubtaskDetailTrace::getTodeptcode, t -> {
-                Map<String, Object> deptMap = new HashMap<>();
-                deptMap.put("deptName", t.getTodeptname());
-                deptMap.put("deptCode", t.getTodeptcode());
-                return deptMap;
-            }, (existing, replacement) -> existing), map -> new ArrayList<>(map.values())));
-            dto.setResponsibleDept(responsibleDept);
-
-            // 鏈夋晥濉啓鏁帮細閫氳繃缁勫唴鐨� scriptid 鍘� service_subtask_detail 琛ㄦ煡璇�
-            List<Long> scriptIds = group.stream().map(ServiceSubtaskDetailTrace::getScriptid).filter(Objects::nonNull).distinct().collect(Collectors.toList());
-            long effectiveFillNum = 0L;
-            if (CollectionUtils.isNotEmpty(scriptIds)) {
-                ServiceSubtaskDetail query = new ServiceSubtaskDetail();
-                query.setDelFlag("0");
-                for (Long scriptId : scriptIds) {
-                    query.setScriptid(scriptId);
-                    List<ServiceSubtaskDetail> details = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(query);
-                    effectiveFillNum += CollectionUtils.isEmpty(details) ? 0 : details.size();
+                for (Map<String, Object> row : serviceSubtaskDetailTraceMapper.selectTraceDealLatestHandle(detailTraceDealVO, questiontexts)) {
+                    String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
+                    if (questiontext == null) continue;
+                    // 鍚屼竴涓渶澶у鐞嗘椂闂村彲鑳芥湁澶氭潯锛屼繚鐣欑涓�鏉″嵆鍙紙鍘熷疄鐜板彇缁勫唴 handle_time 鏈�澶х殑閭f潯锛�
+                    latestHandleMap.putIfAbsent(questiontext, row);
                 }
             }
 
-            // 寮傚父濉啓鏁� = 褰撳墠鍒嗙粍鐨� List 鏁伴噺
-            long exceptionFillNum = group.size();
+            //  6) 鏈〉闂鐨勬湁鏁堝~鍐欐暟锛氫竴娆� group by 鏌ュ洖锛屾浛浠i�愪釜 scriptid 鏌ヨ
+            // 涓�涓棶棰樺彲鑳藉搴斿涓� scriptid锛屾墍浠ュ厛鎶� questiontext -> scriptid 鍒楄〃瑙e紑锛�
+            // 鍐嶆妸璇ラ棶棰樻墍鏈� scriptid 鐨勫~鍐欐暟鐩稿姞锛堜笌鍘熷疄鐜� for 寰幆绱姞鐨勫彛寰勪竴鑷达級
+            Map<String, List<Long>> questionScriptIds = new LinkedHashMap<>();
+            Set<Long> scriptIdSet = new LinkedHashSet<>();
+            for (Map<String, Object> row : groupRows) {
+                String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
+                if (questiontext == null) continue;
+                List<Long> ids = parseScriptIds(row.get("scriptids"));
+                questionScriptIds.put(questiontext, ids);
+                scriptIdSet.addAll(ids);
+            }
+            Map<Long, Long> effectiveFillMap = new HashMap<>();
+            if (CollectionUtils.isNotEmpty(scriptIdSet)) {
+                for (Map<String, Object> row : serviceSubtaskDetailMapper.countDetailByScriptIds(new ArrayList<>(scriptIdSet))) {
+                    Long scriptId = toLongObj(row.get("scriptid"));
+                    if (scriptId == null) continue;
+                    effectiveFillMap.put(scriptId, toLong(row.get("cnt"), 0L));
+                }
+            }
 
-            Map<String, Object> fillSituation = new HashMap<>();
-            fillSituation.put("effectiveFillNum", effectiveFillNum);
-            fillSituation.put("exceptionFillNum", exceptionFillNum);
-            dto.setFillSituation(fillSituation);
+            //  7) 缁勮杩斿洖缁撴瀯锛堥『搴� = SQL 鐨� order by questiontext锛屼繚璇佸垎椤电ǔ瀹氾級
+            for (Map<String, Object> row : groupRows) {
+                String questiontext = row.get("questiontext") == null ? null : String.valueOf(row.get("questiontext"));
+                if (questiontext == null) continue;
 
-            // 寮傚父闂澶勭悊鏁伴噺锛氫粠 handleFlag 缁熻
-            long yesDeal = group.stream().filter(t -> "1".equals(t.getHandleFlag())).count();
-            long noDeal = group.stream().filter(t -> !"1".equals(t.getHandleFlag())).count();
-            Map<String, Object> exceptionQuesNum = new HashMap<>();
-            exceptionQuesNum.put("yesDeal", yesDeal);
-            exceptionQuesNum.put("noDeal", noDeal);
-            exceptionQuesNum.put("all", exceptionFillNum);
-            dto.setExceptionQuesNum(exceptionQuesNum);
+                DetailTraceDealDTO dto = new DetailTraceDealDTO();
+                dto.setQuestiontext(questiontext);
+                Long scriptId = toLongObj(row.get("scriptid"));
+                dto.setScriptid(scriptId);
+                dto.setScriptids(parseScriptIds(row.get("scriptids")));
+                Object templateType = row.get("templateType");
+                dto.setTemplateType(templateType == null ? null : Integer.valueOf(String.valueOf(templateType)));
+                dto.setResponsibleDept(deptMap.getOrDefault(questiontext, new ArrayList<>()));
 
-            // 姹囨�荤粺璁$疮鍔�
-            totalException += exceptionFillNum;
-            noDealException += noDeal;
-            yesDealException += yesDeal;
+                long exceptionFillNum = toLong(row.get("exceptionFillNum"), 0L);
+                // 璇ラ棶棰樹笅鎵�鏈� scriptid 鐨勬湁鏁堝~鍐欐暟涔嬪拰锛堜竴涓� questiontext 鍙兘瀵瑰簲澶氫釜 scriptid锛�
+                long effectiveFillNum = 0L;
+                for (Long sid : questionScriptIds.getOrDefault(questiontext, new ArrayList<>())) {
+                    effectiveFillNum += effectiveFillMap.getOrDefault(sid, 0L);
+                }
+                Map<String, Object> fillSituation = new HashMap<>();
+                fillSituation.put("effectiveFillNum", effectiveFillNum);
+                fillSituation.put("exceptionFillNum", exceptionFillNum);
+                dto.setFillSituation(fillSituation);
 
-            // 鏈�鏂板鐞嗕汉鍜屽鐞嗘椂闂达紙鍙� handleTime 鏈�鏂扮殑涓�鏉★級
-            group.stream().filter(t -> t.getHandleTime() != null).max(Comparator.comparing(ServiceSubtaskDetailTrace::getHandleTime)).ifPresent(latest -> {
-                dto.setHandleBy(latest.getHandleBy());
-                dto.setHandleTime(latest.getHandleTime());
-            });
+                long yesDeal = toLong(row.get("yesDeal"), 0L);
+                long noDeal = toLong(row.get("noDeal"), 0L);
+                Map<String, Object> exceptionQuesNum = new HashMap<>();
+                exceptionQuesNum.put("yesDeal", yesDeal);
+                exceptionQuesNum.put("noDeal", noDeal);
+                exceptionQuesNum.put("all", exceptionFillNum);
+                dto.setExceptionQuesNum(exceptionQuesNum);
 
-            detailTraceDealDTOList.add(dto);
+                Map<String, Object> latestHandle = latestHandleMap.get(questiontext);
+                if (latestHandle != null) {
+                    dto.setHandleBy(latestHandle.get("handleBy") == null ? null : String.valueOf(latestHandle.get("handleBy")));
+                    Object handleTime = latestHandle.get("handleTime");
+                    dto.setHandleTime(handleTime instanceof Date ? (Date) handleTime : null);
+                }
+
+                detailTraceDealDTOList.add(dto);
+            }
         }
 
         Map<String, Object> result = new HashMap<>();
@@ -159,9 +196,38 @@
         result.put("totalException", totalException);
         result.put("noDealException", noDealException);
         result.put("yesDealException", yesDealException);
+        // 鍒嗛〉鎬绘暟锛堟寜闂缁村害锛夛紝controller 鐩存帴鐢ㄥ畠锛屼笉鐢ㄥ啀鍏ㄩ噺鏌ヤ竴閬�
+        result.put("questionTotal", questionTotal);
         return result;
     }
 
+    private static Long toLongObj(Object value) {
+        if (value == null) return null;
+        if (value instanceof Number) return ((Number) value).longValue();
+        String text = String.valueOf(value).trim();
+        return text.isEmpty() ? null : Long.valueOf(text);
+    }
+
+    private static long toLong(Object value, long defaultValue) {
+        Long result = toLongObj(value);
+        return result == null ? defaultValue : result;
+    }
+
+    /**
+     * 瑙f瀽 group_concat(distinct scriptid) 鐨勭粨鏋滐紙濡� "101,102"锛変负 scriptid 鍒楄〃
+     */
+    private static List<Long> parseScriptIds(Object value) {
+        List<Long> scriptIds = new ArrayList<>();
+        if (value == null) return scriptIds;
+        for (String item : String.valueOf(value).split(",")) {
+            Long scriptId = toLongObj(item);
+            if (scriptId != null && !scriptIds.contains(scriptId)) {
+                scriptIds.add(scriptId);
+            }
+        }
+        return scriptIds;
+    }
+
     /**
      * 鏂板闅忚浠诲姟寮傚父璁板綍璺熻釜
      *
diff --git a/smartor/src/main/resources/mapper/smartor/PatMedOuthospMapper.xml b/smartor/src/main/resources/mapper/smartor/PatMedOuthospMapper.xml
index b53811c..ef8ecf9 100644
--- a/smartor/src/main/resources/mapper/smartor/PatMedOuthospMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/PatMedOuthospMapper.xml
@@ -454,6 +454,16 @@
         WHERE serialnum = #{serialnum}
     </select>
 
+    <!-- 鎵归噺鏌ヨ鎸囧畾鍒嗚〃涓凡瀛樺湪鐨� serialnum锛屼竴娆� SQL 瀹屾垚鏁撮〉鍘婚噸锛岄伩鍏嶉�愭潯 countBySerialnum -->
+    <select id="selectExistingSerialnums" resultType="java.lang.String">
+        SELECT serialnum
+        FROM ${tableName}
+        WHERE serialnum IN
+        <foreach collection="serialnums" item="serialnum" open="(" separator="," close=")">
+            #{serialnum}
+        </foreach>
+    </select>
+
     <update id="updateInTable" parameterType="map">
         update ${tableName}
         <trim prefix="SET" suffixOverrides=",">
diff --git a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
index fe90a21..945af85 100644
--- a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailMapper.xml
@@ -185,6 +185,24 @@
 
     </select>
 
+    <!--
+        寮傚父闂澶勭悊鎯呭喌鏌ヨ锛坱racedeallist锛夌殑銆屾湁鏁堝~鍐欐暟銆嶏細
+        鍘熸潵瀵规瘡涓� scriptid 閮芥墽琛屼竴娆� selectServiceSubtaskDetailList锛堟煡鍥炲叏閮ㄥ垪鍐嶅幓 size锛夛紝
+        涓�涓棶棰樹竴娆°�佽�屼笖鏁翠釜鎺ュ彛浼氳璋冪敤涓ら亶锛屽嚑鍗佷笂鐧句釜闂灏辨槸涓婄櫨娆″ぇ琛ㄦ壂鎻忥紱
+        杩欓噷鍚堝苟鎴愪竴鏉� group by銆�
+        娉ㄦ剰锛氫负涓庡師鍙e緞瀹屽叏涓�鑷达紝杩欓噷涓嶅姞 del_flag 鏉′欢锛堝師 selectServiceSubtaskDetailList
+        铏界劧 Java 閲� setDelFlag("0")锛屼絾 SQL 閲屽苟娌℃湁 del_flag 杩囨护锛夈��
+    -->
+    <select id="countDetailByScriptIds" resultType="java.util.Map">
+        select scriptid as scriptid, count(*) as cnt
+        from service_subtask_detail
+        where scriptid in
+        <foreach collection="scriptIds" item="scriptId" open="(" separator="," close=")">
+            #{scriptId}
+        </foreach>
+        group by scriptid
+    </select>
+
     <select id="getServiceSubtaskDetails" parameterType="com.smartor.domain.ServiceSubtaskDetailVO"
             resultType="java.util.HashMap">
         select
@@ -327,6 +345,57 @@
         group by ss.libtemplateid, ssd.questiontext, ssd.score
     </select>
 
+    <select id="selectSatisfactionFillPersonCount" parameterType="com.smartor.domain.entity.SatisfactionStatQuery"
+            resultType="java.util.Map">
+        select ss.libtemplateid as libtemplateid,
+               count(distinct ssd.sub_id) as cnt
+        from service_subtask_detail ssd
+        join service_subtask ss on ss.id = ssd.sub_id
+        where ssd.del_flag = 0
+        and ss.del_flag = 0
+        <if test="orgid != null and orgid != ''">
+            and ss.orgid = #{orgid}
+        </if>
+        <if test="campusid != null and campusid != ''">
+            and ss.campusid = #{campusid}
+        </if>
+        <if test="type != null and type != ''">and ss.type = #{type}</if>
+        <if test="startTime != null">
+            and ss.visit_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and ss.visit_time &lt; date_add(#{endTime}, interval 1 day)
+        </if>
+        <if test="libTemplateIds != null and libTemplateIds.size() > 0">
+            and ss.libtemplateid IN
+            <foreach collection="libTemplateIds" item="libTemplateId" open="(" separator="," close=")">
+                #{libTemplateId}
+            </foreach>
+        </if>
+        <if test="(hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0) or (deptcodes != null and deptcodes.size() > 0)">
+            AND (
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0">
+                ss.leavehospitaldistrictcode IN
+                <foreach collection="hospitaldistrictcodes" item="hospitaldistrictcode" open="("
+                         separator=","
+                         close=")">
+                    #{hospitaldistrictcode}
+                </foreach>
+            </if>
+            <if test="hospitaldistrictcodes != null and hospitaldistrictcodes.size() > 0 and deptcodes != null and deptcodes.size() > 0">
+                OR
+            </if>
+            <if test="deptcodes != null and deptcodes.size() > 0">
+                ss.deptcode IN
+                <foreach collection="deptcodes" item="deptcode" open="(" separator="," close=")">
+                    #{deptcode}
+                </foreach>
+            </if>
+            )
+        </if>
+        group by ss.libtemplateid
+    </select>
+
     <!--
         婊℃剰搴︾粺璁★紙statistics / statisticsByDimension锛夛細涓�娆℃�х粺璁℃湰娆¤姹傚叏閮ㄩ棶棰樼殑濉姤閲忋��
         绛変环浜庡姣忎釜闂鎵ц queryFillCount(subids, questiontext)锛屽尯鍒槸锛�
diff --git a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailTraceMapper.xml b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailTraceMapper.xml
index 79943e7..1bc14e8 100644
--- a/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailTraceMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/ServiceSubtaskDetailTraceMapper.xml
@@ -326,79 +326,163 @@
         <if test="pageSize != null  and pageNum != null">limit ${pageSize} OFFSET ${pageNum}</if>
     </select>
 
-    <select id="tracedeallist" parameterType="com.smartor.domain.VO.DetailTraceDealVO"
-            resultMap="ServiceSubtaskDetailTtraceResult">
-        <include refid="selectServiceSubtaskDetailTtraceVo"/>
-        where del_flag=0
+    <!--
+        寮傚父闂澶勭悊鎯呭喌鏌ヨ锛坱racedeallist锛夌殑鍏叡杩囨护鏉′欢銆�
+        涓嬮潰 5 鏉� SQL 鍏辩敤鏈墖娈碉紝淇濊瘉銆屽垎椤垫暟鎹�侀棶棰樻�绘暟銆佸叏閲忔眹鎬汇�嶇殑鍙e緞瀹屽叏涓�鑷淬��
+        娉ㄦ剰锛歲uestiontext 涓虹┖鐨勮鍘熷疄鐜伴噷琚� stream().filter(questiontext != null) 涓㈡帀浜嗭紝
+        鎵�浠ヨ繖閲岀粺涓�鎺掗櫎锛屾眹鎬绘暟鎵嶅拰鍘熸潵涓�鑷淬��
+    -->
+    <sql id="traceDealWhere">
+        and del_flag = 0
+        and questiontext is not null
+        <if test="vo.templateid != null  and vo.templateid != ''">
+            and templateid = #{vo.templateid}
+        </if>
+        <if test="vo.todeptcode != null  and vo.todeptcode != ''">
+            and FIND_IN_SET(#{vo.todeptcode}, todeptcode)
+        </if>
+        <if test="vo.todeptname != null  and vo.todeptname != ''">
+            and todeptname like concat('%', #{vo.todeptname}, '%')
+        </if>
+        <if test="vo.handleStartTime != null and vo.handleEndTime != null">
+            and handle_time between #{vo.handleStartTime} and #{vo.handleEndTime}
+        </if>
+        <if test="vo.orgid != null  and vo.orgid != ''">
+            and orgid = #{vo.orgid}
+        </if>
+        <if test="vo.type != null  and vo.type != ''">
+            and type = #{vo.type}
+        </if>
+        <if test="vo.templateType != null">
+            and template_type = #{vo.templateType}
+        </if>
+        <if test="vo.isMydException != null ">
+            and is_myd_exception = #{vo.isMydException}
+        </if>
+        <if test="vo.dimension != null  and vo.dimension != ''">
+            and dimension = #{vo.dimension}
+        </if>
+        <if test="vo.dutyDeptPersonName != null  and vo.dutyDeptPersonName != ''">
+            and duty_dept_person_name like concat('%', #{vo.dutyDeptPersonName}, '%')
+        </if>
+        <if test="vo.dutyDeptPersonCode != null  and vo.dutyDeptPersonCode != ''">
+            and duty_dept_person_code like concat('%', #{vo.dutyDeptPersonCode}, '%')
+        </if>
+        <if test="vo.reportDeptPersonName != null  and vo.reportDeptPersonName != ''">
+            and report_dept_person_name like concat('%', #{vo.reportDeptPersonName}, '%')
+        </if>
+        <if test="vo.reportDeptPersonCode != null  and vo.reportDeptPersonCode != ''">
+            and report_dept_person_code like concat('%', #{vo.reportDeptPersonCode}, '%')
+        </if>
+        <if test="vo.circulationDeptPersonCode != null  and vo.circulationDeptPersonCode != ''">
+            and circulation_dept_person_code like concat('%', #{vo.circulationDeptPersonCode}, '%')
+        </if>
+        <if test="vo.circulationDeptPersonName != null  and vo.circulationDeptPersonName != ''">
+            and circulation_dept_person_name like concat('%', #{vo.circulationDeptPersonName}, '%')
+        </if>
+        <if test="vo.circulationDeptCode != null  and vo.circulationDeptCode != ''">
+            and circulation_dept_code like concat('%', #{vo.circulationDeptCode}, '%')
+        </if>
+        <if test="vo.circulationDeptName != null  and vo.circulationDeptName != ''">
+            and circulation_dept_name like concat('%', #{vo.circulationDeptName}, '%')
+        </if>
+        <if test="vo.circulationExplain != null  and vo.circulationExplain != ''">
+            and circulation_explain like concat('%', #{vo.circulationExplain}, '%')
+        </if>
+        <if test="vo.circulationTime != null ">
+            and circulation_time = #{vo.circulationTime}
+        </if>
+        <if test="vo.circulationStatus != null ">
+            and circulation_status = #{vo.circulationStatus}
+        </if>
+        <if test="vo.circulationPersonName != null  and vo.circulationPersonName != ''">
+            and circulation_person_name like concat('%', #{vo.circulationPersonName}, '%')
+        </if>
+        <if test="vo.circulationPersonCode != null  and vo.circulationPersonCode != ''">
+            and circulation_person_code = #{vo.circulationPersonCode}
+        </if>
+    </sql>
 
-        <if test="templateid != null  and templateid != ''">
-            and templateid = #{templateid}
-        </if>
-        <if test="todeptcode != null  and todeptcode != ''">
-            and FIND_IN_SET(#{todeptcode}, todeptcode)
-        </if>
-        <if test="todeptname != null  and todeptname != ''">
-            and todeptname like concat('%', #{todeptname}, '%')
-        </if>
-        <if test="handleStartTime != null and handleEndTime != null">
-            and handle_time between #{handleStartTime} and #{handleEndTime}
-        </if>
+    <!--
+        褰撳墠椤电殑闂鍒嗙粍鑱氬悎锛氬垎椤垫寜闂缁村害涓嬫帹鍒� SQL銆�
+        鍘熸潵鏄妸 service_subtask_detail_trace 鍏ㄨ〃锛堝惈 patdesc/recordurl 绛夊ぇ瀛楁锛夋媺鍥� Java 鍐嶅垎缁勶紝
+        pageSize 瀹屽叏娌$敓鏁堬紱鐜板湪 DB 鍙鐞嗗綋鍓嶉〉鐨勯鐩��
 
-        <if test="orgid != null  and orgid != ''">
-            and orgid = #{orgid}
+        娉ㄦ剰鍒嗙粍缁村害蹇呴』鏄� questiontext锛堟帴鍙f枃妗g害瀹氥�屾瘡涓棶棰樺搴斾竴鏉¤褰曘�嶏級锛�
+        涓�涓� questiontext 鍙兘瀵瑰簲澶氫釜 scriptid锛岃繖鏃惰鎶婅闂鎵�鏈� scriptid 鐨勬湁鏁堝~鍐欐暟鍔犺捣鏉ワ紝
+        鎵�浠ヨ繖閲岀敤 group_concat(distinct scriptid) 鎶� scriptid 鍒楄〃甯﹀洖 Java锛坰criptid 鍙栨渶灏忓�肩敤浜庡睍绀猴級銆�
+        濡傛灉鏀规垚 group by questiontext, scriptid锛屽悓涓�涓棶棰樹細鎷嗘垚澶氳锛�
+        涓� countTraceDealQuestions锛堟寜 questiontext 鍘婚噸锛夈�乼otal銆佽礋璐g瀹ゃ�佹渶鏂板鐞嗕汉鐨勫彛寰勯兘浼氫笉涓�鑷淬��
+    -->
+    <select id="selectTraceDealGroups" resultType="java.util.Map">
+        select questiontext                                        as questiontext,
+               min(scriptid)                                       as scriptid,
+               group_concat(distinct scriptid)                      as scriptids,
+               min(template_type)                                  as templateType,
+               count(*)                                            as exceptionFillNum,
+               sum(case when handle_flag = '1' then 1 else 0 end)   as yesDeal,
+               sum(case when handle_flag = '1' then 0 else 1 end)   as noDeal
+        from service_subtask_detail_trace
+        where 1 = 1
+        <include refid="traceDealWhere"/>
+        group by questiontext
+        order by questiontext
+        <if test="vo.pageSize != null and vo.pageNum != null">
+            limit ${vo.pageSize} offset ${vo.pageNum}
         </if>
+    </select>
 
-        <if test="type != null  and type != ''">
-            and type = #{type}
-        </if>
-        <if test="templateType != null">
-            and template_type = #{templateType}
-        </if>
-        <if test="isMydException != null ">
-            and is_myd_exception = #{isMydException}
-        </if>
-        <if test="dimension != null  and dimension != ''">
-            and dimension = #{dimension}
-        </if>
-        <if test="dutyDeptPersonName != null  and dutyDeptPersonName != ''">
-            and duty_dept_person_name like concat('%', #{dutyDeptPersonName}, '%')
-        </if>
-        <if test="dutyDeptPersonCode != null  and dutyDeptPersonCode != ''">
-            and duty_dept_person_code like concat('%', #{dutyDeptPersonCode}, '%')
-        </if>
-        <if test="reportDeptPersonName != null  and reportDeptPersonName != ''">
-            and report_dept_person_name like concat('%', #{reportDeptPersonName}, '%')
-        </if>
-        <if test="reportDeptPersonCode != null  and reportDeptPersonCode != ''">
-            and report_dept_person_code like concat('%', #{reportDeptPersonCode}, '%')
-        </if>
-        <if test="circulationDeptPersonCode != null  and circulationDeptPersonCode != ''">
-            and circulation_dept_person_code like concat('%', #{circulationDeptPersonCode}, '%')
-        </if>
-        <if test="circulationDeptPersonName != null  and circulationDeptPersonName != ''">
-            and circulation_dept_person_name like concat('%', #{circulationDeptPersonName}, '%')
-        </if>
-        <if test="circulationDeptCode != null  and circulationDeptCode != ''">
-            and circulation_dept_code like concat('%', #{circulationDeptCode}, '%')
-        </if>
-        <if test="circulationDeptName != null  and circulationDeptName != ''">
-            and circulation_dept_name like concat('%', #{circulationDeptName}, '%')
-        </if>
-        <if test="circulationExplain != null  and circulationExplain != ''">
-            and circulation_explain like concat('%', #{circulationExplain}, '%')
-        </if>
-        <if test="circulationTime != null ">
-            and circulation_time = #{circulationTime}
-        </if>
-        <if test="circulationStatus != null ">
-            and circulation_status = #{circulationStatus}
-        </if>
-        <if test="circulationPersonName != null  and circulationPersonName != ''">
-            and circulation_person_name like concat('%', #{circulationPersonName}, '%')
-        </if>
-        <if test="circulationPersonCode != null  and circulationPersonCode != ''">
-            and circulation_person_code =#{circulationPersonCode}
-        </if>
+    <!-- 闂鎬绘暟锛堝垎椤� total锛屾寜闂缁村害锛� -->
+    <select id="countTraceDealQuestions" resultType="long">
+        select count(distinct questiontext)
+        from service_subtask_detail_trace
+        where 1 = 1
+        <include refid="traceDealWhere"/>
+    </select>
+
+    <!-- 鍏ㄩ噺姹囨�伙紙涓嶅彈鍒嗛〉褰卞搷锛夛細鎬绘暟 + 宸插鐞嗘暟锛屾湭澶勭悊鏁板湪 Java 閲岀浉鍑� -->
+    <select id="sumTraceDeal" resultType="java.util.Map">
+        select count(*)                                          as totalException,
+               sum(case when handle_flag = '1' then 1 else 0 end) as yesDeal
+        from service_subtask_detail_trace
+        where 1 = 1
+        <include refid="traceDealWhere"/>
+    </select>
+
+    <!-- 褰撳墠椤甸棶棰樼殑璐熻矗绉戝锛堟寜绉戝缂栧彿鍘婚噸锛屽悕绉板彇缁勫唴闈炵┖鐨勪竴涓級 -->
+    <select id="selectTraceDealDepts" resultType="java.util.Map">
+        select questiontext      as questiontext,
+               todeptcode        as deptCode,
+               min(todeptname)   as deptName
+        from service_subtask_detail_trace
+        where 1 = 1
+        <include refid="traceDealWhere"/>
+        and todeptcode is not null and todeptcode != ''
+        and questiontext in
+        <foreach collection="questiontexts" item="qt" open="(" separator="," close=")">
+            #{qt}
+        </foreach>
+        group by questiontext, todeptcode
+    </select>
+
+    <!-- 褰撳墠椤甸棶棰樼殑鏈�鏂板鐞嗕汉鍜屽鐞嗘椂闂达紙handle_time 鏈�澶х殑涓�鏉★級 -->
+    <select id="selectTraceDealLatestHandle" resultType="java.util.Map">
+        select t.questiontext as questiontext,
+               t.handle_by    as handleBy,
+               t.handle_time  as handleTime
+        from service_subtask_detail_trace t
+                 inner join (select questiontext, max(handle_time) as maxHandleTime
+                             from service_subtask_detail_trace
+                             where 1 = 1
+                             <include refid="traceDealWhere"/>
+                             and handle_time is not null
+                             and questiontext in
+                             <foreach collection="questiontexts" item="qt" open="(" separator="," close=")">
+                                 #{qt}
+                             </foreach>
+                             group by questiontext) m
+                            on m.questiontext = t.questiontext and m.maxHandleTime = t.handle_time
+        where t.del_flag = 0
     </select>
 
 
diff --git a/smartor/src/main/resources/mapper/smartor/SvyLibTemplageCategoryMapper.xml b/smartor/src/main/resources/mapper/smartor/SvyLibTemplageCategoryMapper.xml
index 0e7b2c9..e83b92f 100644
--- a/smartor/src/main/resources/mapper/smartor/SvyLibTemplageCategoryMapper.xml
+++ b/smartor/src/main/resources/mapper/smartor/SvyLibTemplageCategoryMapper.xml
@@ -15,6 +15,7 @@
         <result property="updateBy" column="update_by"/>
         <result property="updateTime" column="update_time"/>
         <result property="orgid" column="orgid"/>
+        <result property="type" column="type"/>
         <result property="seqno" column="seqno"/>
     </resultMap>
 
@@ -29,6 +30,7 @@
                update_by,
                update_time,
                orgid,
+               type,
                seqno
         from svy_lib_template_category
     </sql>
@@ -43,6 +45,7 @@
             <if test="guid != null  and guid != ''">and guid = #{guid}</if>
             <if test="orgid != null ">and orgid = #{orgid}</if>
             <if test="seqno != null ">and seqno = #{seqno}</if>
+            <if test="type != null ">and type = #{type}</if>
 
     </select>
 
@@ -65,6 +68,7 @@
             <if test="updateTime != null">update_time,</if>
             <if test="orgid != null">orgid,</if>
             <if test="seqno != null">seqno,</if>
+            <if test="type != null">type,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="name != null">#{name},</if>
@@ -77,6 +81,7 @@
             <if test="updateTime != null">#{updateTime},</if>
             <if test="orgid != null">#{orgid},</if>
             <if test="seqno != null">#{seqno},</if>
+            <if test="type != null">#{type},</if>
         </trim>
     </insert>
 
@@ -93,6 +98,7 @@
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="orgid != null">orgid = #{orgid},</if>
             <if test="seqno != null">seqno = #{seqno},</if>
+            <if test="type != null">type = #{type},</if>
         </trim>
         where id = #{id}
     </update>

--
Gitblit v1.9.3