From 3d047130b059efd46f56be37465a751ab16ee8cb Mon Sep 17 00:00:00 2001
From: 陈昶聿 <chychen@nbjetron.com>
Date: 星期三, 11 十一月 2026 14:06:56 +0800
Subject: [PATCH] Merge branch 'smartor_ls_merge' into 20261111-妇幼档案随访

---
 ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java | 2070 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 1,832 insertions(+), 238 deletions(-)

diff --git a/ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java b/ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
index 18d40e8..15440e7 100644
--- a/ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
+++ b/ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
@@ -42,6 +42,7 @@
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -101,11 +102,8 @@
 import com.ruoyi.task.mapper.ServiceOutPathMapper;
 import com.ruoyi.task.service.IServiceOutPathService;
 import com.ruoyi.task.domain.ServiceSubtaskPreachform;
-import com.ruoyi.task.api.spi.TaskPreachformPort;
+import com.ruoyi.task.mapper.ServiceSubtaskPreachformMapper;
 import com.ruoyi.task.api.spi.TaskArchivePort;
-import com.ruoyi.task.api.spi.TaskFtpPort;
-import com.ruoyi.task.api.spi.TaskSysConfigPort;
-import com.ruoyi.task.api.spi.TaskSysDeptPort;
 import com.ruoyi.task.common.ScheduleSubtaskBuilder;
 import com.ruoyi.task.domain.ServiceTaskScheduleVO;
 import com.ruoyi.task.service.IServiceSubtaskService;
@@ -124,6 +122,11 @@
 import com.ruoyi.task.domain.ServiceSubtaskTemplateVO;
 import com.ruoyi.task.domain.ServiceTaskVO;
 import com.ruoyi.task.domain.VO.TransferDeptVO;
+import com.ruoyi.system.service.ISysConfigService;
+import com.ruoyi.system.service.ISysDeptService;
+import com.ruoyi.system.mapper.ext.SysDept2Mapper;
+import com.ruoyi.task.api.contract.OperationSubtaskWriter;
+import com.ruoyi.task.event.FollowupCompletedEventPublisher;
 
 /**
  * 鍗曚竴浠诲姟锛堥殢璁匡級Service涓氬姟灞傚鐞�
@@ -134,6 +137,18 @@
 @Slf4j
 @Service
 public class ServiceSubtaskServiceImpl implements IServiceSubtaskService {
+
+    /**
+     * 闅忚瀛愪换鍔$粍瑁呭櫒锛堝師 smartor 渚� IPatMedInhospService#boxedServiceSubtask 鍦ㄦ湰妯″潡鐨勮惤鍦板疄鐜帮級銆�
+     * <p>
+     * 浜屾闅忚瑕佺敤銆屼笅涓�浠诲姟閰嶇疆 + 鍑洪櫌璁板綍 + 鎮h�呮。妗堛�嶇粍瑁呮柊鐨� service_subtask锛�
+     * 杩欐鍙e緞涓庨暱鏈熶换鍔$敓鎴愬畬鍏ㄤ竴鑷达紝鍥犳鐩存帴澶嶇敤鏈ā鍧楀凡鏈夌殑缁勮鍣紝涓嶅啀澶嶅埗涓�浠姐��
+     */
+    @Autowired
+    private OperationSubtaskWriter operationSubtaskWriter;
+
+    @Autowired
+    private FollowupCompletedEventPublisher followupCompletedEventPublisher;
 
     /**
      * 婊℃剰搴︽ā鍧楀澶栭棬闈紙甯搁┗ Bean锛夈��
@@ -166,7 +181,7 @@
     private ServiceSubtaskMapper serviceSubtaskMapper;
 
     @Autowired
-    private TaskPreachformPort taskPreachformPort;
+    private ServiceSubtaskPreachformMapper taskPreachformPort;
 
     @Autowired
     private ServiceSubtaskDetailMapper serviceSubtaskDetailMapper;
@@ -219,10 +234,10 @@
     private TaskArchivePort taskArchivePort;
 
     @Autowired
-    private ScheduleSubtaskBuilder scheduleSubtaskBuilder;
+    private ISysDeptService sysDeptService;
 
     @Autowired
-    private TaskFtpPort taskFtpPort;
+    private ScheduleSubtaskBuilder scheduleSubtaskBuilder;
 
 
 
@@ -236,7 +251,7 @@
 
 
     @Autowired
-    private TaskSysConfigPort taskSysConfigPort;
+    private ISysConfigService sysConfigService;
 
     /**
      * 娑堟伅鍙戦�佹柟宸ュ巶锛堢煭淇�/寰俊鍏紬鍙凤級锛屾浛浠e師鏉ョ洿鎺ヨ皟鐢ㄥ悇绔欑偣鍙戦�佷唬鐮佺殑鍐欐硶
@@ -255,7 +270,7 @@
 
 
     @Autowired
-    private TaskSysDeptPort taskSysDeptPort;
+    private SysDept2Mapper sysDept2Mapper;
 
     @Value("${spring.profiles.active}")
     private String active;
@@ -264,6 +279,10 @@
     private ServiceOutPathMapper serviceOutPathMapper;
 
     private static final String KEY_SEP = "\u0001";
+
+    /** 浜屾闅忚鐢熸垚閿佸墠缂�锛岄槻姝㈠悓涓�鍘熶换鍔″苟鍙戠敓鎴愬鏉′簩娆¢殢璁� */
+    private static final String AGAIN_LOCK_KEY_PREFIX = "smartor:again:lock:";
+    private static final long AGAIN_LOCK_SECONDS = 30L;
 
     /**
      * 鏌ヨ鍗曚竴浠诲姟锛堥殢璁匡級
@@ -383,8 +402,8 @@
                 patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname());
                 patTaskRelevance.setPatid(serviceSubtask1.getPatid());
                 patTaskRelevance.setSendStatus(serviceSubtask1.getSendstate());
-                patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode());
-                patTaskRelevance.setDeptName(serviceSubtask1.getDeptname());
+                patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode());
+                patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname());
                 patTaskRelevance.setLeavehospitaldistrictcode(serviceSubtask1.getLeavehospitaldistrictcode());
                 patTaskRelevance.setLeavehospitaldistrictname(serviceSubtask1.getLeavehospitaldistrictname());
                 patTaskRelevance.setInhosptime(serviceSubtask1.getInhosptime());
@@ -404,8 +423,8 @@
                 patTaskRelevance.setTelcode(serviceSubtask1.getPhone());
                 patTaskRelevance.setFinishtime(serviceSubtask1.getFinishtime());
                 patTaskRelevance.setAddr(serviceSubtask1.getAddr());
-                patTaskRelevance.setDeptName(serviceSubtask1.getDeptname());
-                patTaskRelevance.setDeptcode(serviceSubtask1.getDeptcode());
+                patTaskRelevance.setDeptName(serviceSubtask1.getLeaveldeptname());
+                patTaskRelevance.setDeptcode(serviceSubtask1.getLeaveldeptcode());
                 patTaskRelevance.setBedNo(serviceSubtask1.getBedNo());
                 patTaskRelevance.setDiagname(serviceSubtask1.getLeavediagname());
                 patTaskRelevance.setPatid(serviceSubtask1.getPatid());
@@ -469,6 +488,12 @@
         List<ServiceSubtask> selectServiceSubtaskList = getServiceSubtasks(serviceSubtaskEntity);
 
         List<ServiceSubtaskRes> serviceSubtaskResList = new ArrayList<>();
+        // 鍚屼竴娆¤姹傦紙鍒楄〃鍒嗛〉 / 鍏ㄩ噺瀵煎嚭锛夐噷锛宨nhospid 涓� patid 楂樺害閲嶅
+        // 锛堝啀娆¢殢璁裤�佸悓涓�鎮h�呭浠诲姟閮戒細鎸囧悜鍚屼竴鏉′綇闄㈣褰�/鍚屼竴浠芥。妗堬級銆�
+        // 鍘熸潵姣忚閮借 2 娆″崟鏉℃煡璇紝瀵煎嚭鍑犲崈琛屽氨鏄嚑鍗兠�2 娆″線杩旓紱杩欓噷鎸� id 鍋氳繘绋嬪唴缂撳瓨锛�
+        // 鎶娿�屾瘡琛� 2 娆°�嶅帇鎴愩�屾瘡涓笉鍚� id 1 娆°�嶏紝鍙栨暟璇箟涓嶅彉銆�
+        Map<Long, PatMedInhosp> inhospCache = new HashMap<>();
+        Map<Long, PatArchive> archiveCache = new HashMap<>();
         for (ServiceSubtask serviceSubtask : selectServiceSubtaskList) {
             ServiceSubtaskRes serviceSubtaskRes = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtaskRes.class);
             if (ObjectUtils.isNotEmpty(serviceSubtask.getContinueContent())) {
@@ -483,12 +508,12 @@
             if (StringUtils.isEmpty(serviceSubtask.getCurrentPreachform())) {
                 if (StringUtils.isNotEmpty(serviceSubtask.getPreachform())) {
                     //榛樿鍙栫涓�涓�
-                    serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0]);
+                    serviceSubtaskRes.setCurrentPreachform(serviceSubtask.getPreachform().split(",")[0].trim());
                 }
             }
-            PatMedInhosp inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(serviceSubtask.getInhospid());
+            PatMedInhosp inhosp = loadInhospCached(inhospCache, serviceSubtask.getInhospid());
             if (ObjectUtils.isNotEmpty(inhosp)) {
-                PatArchive patArchive = taskArchivePort.findByPatid(inhosp.getPatid());
+                PatArchive patArchive = loadArchiveCached(archiveCache, inhosp.getPatid());
                 if (ObjectUtils.isNotEmpty(patArchive)) {
                     Map<String, String> stringStringMap = calculateAge(patArchive.getBirthdate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalDate.now());
                     serviceSubtaskRes.setAge(StringUtils.isNotEmpty(stringStringMap.get("age")) ? Long.valueOf(stringStringMap.get("age")) : null);
@@ -504,11 +529,41 @@
         return serviceSubtaskResList;
     }
 
+    /**
+     * 鎸� inhospid 鍙栦綇闄㈣褰曪紙甯﹁繘绋嬪唴缂撳瓨锛夈��
+     * <p>
+     * 鍙槸鎶娿�屽悓涓�娆¤姹傚唴鍚屼竴涓� id 鍙煡涓�娆°�嶈繖浠朵簨鏀跺彛锛�<b>涓嶆敼鍙樺彇鏁拌涔�</b>锛�
+     * 鏌ヤ笉鍒帮紙null锛変篃浼氱紦瀛橈紝閬垮厤鍚屼竴鎵规暟鎹噷瀵逛笉瀛樺湪鐨� id 鍙嶅鎵撳簱銆�
+     * 缂撳瓨鐢熷懡鍛ㄦ湡 = 鍗曟 {@code patItem} 璋冪敤锛屼笉璺ㄨ姹傘�佷笉鍏变韩銆�
+     */
+    private PatMedInhosp loadInhospCached(Map<Long, PatMedInhosp> cache, Long inhospid) {
+        if (inhospid == null) {
+            return null;
+        }
+        if (!cache.containsKey(inhospid)) {
+            cache.put(inhospid, inhospVisitFacade.selectPatMedInhospByInhospid(inhospid));
+        }
+        return cache.get(inhospid);
+    }
+
+    /**
+     * 鎸� patid 鍙栨偅鑰呮。妗堬紙甯﹁繘绋嬪唴缂撳瓨锛夛紝璇箟鍚� {@link #loadInhospCached}銆�
+     */
+    private PatArchive loadArchiveCached(Map<Long, PatArchive> cache, Long patid) {
+        if (patid == null) {
+            return null;
+        }
+        if (!cache.containsKey(patid)) {
+            cache.put(patid, taskArchivePort.findByPatid(patid));
+        }
+        return cache.get(patid);
+    }
+
     public List<ServiceSubtask> getServiceSubtasks(ServiceSubtaskEntity serviceSubtaskEntity) {
         if (serviceSubtaskEntity.getSendstateView() != null) {
-            //            1锛氬緟闅忚锛�1 琚鍙栥��2 寰呭彂閫併��3 宸插彂閫併��5 鍙戦�佸け璐ャ��7銆佽秴鏃讹級锛�2锛氬凡瀹屾垚锛� 6 宸插畬鎴愶級銆�3锛氭棤闇�闅忚锛�4 涓嶆墽琛岋級
+            //            1锛氬緟闅忚锛�1 琚鍙栥��2 寰呭彂閫併��3 宸插彂閫併��7 瓒呮椂锛涘彂閫佸け璐�(5) 鍗曠嫭缁熻锛夛紱2锛氬凡瀹屾垚锛� 6 宸插畬鎴愶級銆�3锛氭棤闇�闅忚锛�4 涓嶆墽琛岋級
             if (serviceSubtaskEntity.getSendstateView() == 1)
-                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L)));
+                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
             if (serviceSubtaskEntity.getSendstateView() == 2)
                 serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L)));
             if (serviceSubtaskEntity.getSendstateView() == 3)
@@ -524,8 +579,63 @@
                 }
             }
         }
-        List<ServiceSubtask> selectServiceSubtaskList = this.selectServiceSubtaskList(serviceSubtaskEntity);
-        return selectServiceSubtaskList;
+        // 鍒嗛〉璇锋眰璧般�屼袱闃舵銆嶏細鍏堝彧鍙栧綋椤� id锛屽啀鎸� id 鍥炶〃銆�
+        // 鍒楄〃 select 绾� 100 鍒楋紝娣辩炕椤垫椂琚� offset 璺宠繃鐨勬瘡涓�琛屽師鏈兘瑕佸洖琛ㄨ鏁磋锛�
+        // 鍙彇 id 鐨勬煡璇㈣兘璧拌鐩栫储寮曞苟鎻愬墠鎴柇锛屼唬浠峰皬寰楀銆�
+        if (serviceSubtaskEntity.getPageNum() != null && serviceSubtaskEntity.getPageSize() != null) {
+            return selectServiceSubtaskPage(serviceSubtaskEntity);
+        }
+        return this.selectServiceSubtaskList(serviceSubtaskEntity);
+    }
+
+    /**
+     * 涓ら樁娈靛垎椤碉細鈶� 鍙煡 id锛堣鐩栫储寮� + limit offset锛夆憽 鎸� id 鍥炶〃鍙栨暣琛屻��
+     * <p>
+     * 绗簩闃舵澶嶇敤 {@code selectServiceSubtaskList} 鐨� subIdList 鏉′欢鍥炶〃锛�
+     * 蹇呴』鎶� pageNum/pageSize 鎽樻帀 鈥斺�� 鍚﹀垯浼氬湪銆屽綋椤� id 闆嗗悎銆嶄笂鍐嶇炕涓�娆¢〉銆�
+     * <p>
+     * 缁熻缂撳瓨锛圕ontroller 鐨� {@code getPatItemCountWithCache}锛夌敤 ReflectUtils 姣斿鏁翠釜
+     * entity 浣滀负缂撳瓨鍛戒腑鏉′欢锛寋@code subIdList} 鍙備笌姣斿锛屾墍浠ョ敤瀹屽繀椤诲師鏍疯繕鍘燂紝
+     * 鍚﹀垯缂撳瓨姘歌繙涓嶅懡涓紝姣忔璇锋眰閮借閲嶇畻涓�娆¤仛鍚堛��
+     */
+    private List<ServiceSubtask> selectServiceSubtaskPage(ServiceSubtaskEntity serviceSubtaskEntity) {
+        List<Long> pageIds = serviceSubtaskMapper.selectServiceSubtaskIdsPage(serviceSubtaskEntity);
+        if (CollectionUtils.isEmpty(pageIds)) {
+            return new ArrayList<>();
+        }
+        Integer pageNum = serviceSubtaskEntity.getPageNum();
+        Integer pageSize = serviceSubtaskEntity.getPageSize();
+        List<Long> originSubIdList = serviceSubtaskEntity.getSubIdList();
+        serviceSubtaskEntity.setPageNum(null);
+        serviceSubtaskEntity.setPageSize(null);
+        serviceSubtaskEntity.setSubIdList(pageIds);
+        List<ServiceSubtask> rows;
+        try {
+            rows = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
+        } finally {
+            serviceSubtaskEntity.setPageNum(pageNum);
+            serviceSubtaskEntity.setPageSize(pageSize);
+            serviceSubtaskEntity.setSubIdList(originSubIdList);
+        }
+        if (CollectionUtils.isEmpty(rows)) {
+            return new ArrayList<>();
+        }
+        // 浠ョ涓�闃舵鐨� id 椤哄簭涓哄噯閲嶆帓锛氱浜岄樁娈电殑 order by 鍙繚璇併�屽悓涓�椤靛唴銆嶆湁搴忥紝
+        // 閬囧埌鎺掑簭瀛楁鍊肩浉鍚岀殑琛屾椂鐩稿椤哄簭涓嶄竴瀹氫笌绗竴闃舵涓�鑷淬��
+        Map<Long, ServiceSubtask> byId = new HashMap<>(Math.max(16, rows.size() * 2));
+        for (ServiceSubtask row : rows) {
+            if (row.getId() != null) {
+                byId.put(row.getId(), row);
+            }
+        }
+        List<ServiceSubtask> ordered = new ArrayList<>(pageIds.size());
+        for (Long id : pageIds) {
+            ServiceSubtask row = byId.get(id);
+            if (row != null) {
+                ordered.add(row);
+            }
+        }
+        return ordered;
     }
 
     @Override
@@ -604,11 +714,22 @@
 
     @Override
     public List<ServiceSubtask> patItem2(ServiceSubtaskEntity serviceSubtaskEntity) {
+        if (serviceSubtaskEntity.getSendstateView() != null) {
+            //            1锛氬緟闅忚锛�1 琚鍙栥��2 寰呭彂閫併��3 宸插彂閫併��5 鍙戦�佸け璐ャ��7銆佽秴鏃讹級锛�2锛氬凡瀹屾垚锛� 6 宸插畬鎴愶級銆�3锛氭棤闇�闅忚锛�4 涓嶆墽琛岋級
+            if (serviceSubtaskEntity.getSendstateView() == 1)
+                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 7L)));
+            if (serviceSubtaskEntity.getSendstateView() == 2)
+                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(6L)));
+            if (serviceSubtaskEntity.getSendstateView() == 3)
+                serviceSubtaskEntity.setSendstates(new ArrayList<>(Arrays.asList(4L)));
+        }
         List<ServiceSubtask> serviceSubtaskList = new ArrayList<>();
-        for (Long sendstate : serviceSubtaskEntity.getSendstates()) {
-            serviceSubtaskEntity.setSendstate(sendstate);
-            List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
-            serviceSubtaskList.addAll(serviceSubtasks);
+        if(CollectionUtils.isNotEmpty(serviceSubtaskEntity.getSendstates())){
+            for (Long sendstate : serviceSubtaskEntity.getSendstates()) {
+                serviceSubtaskEntity.setSendstate(sendstate);
+                List<ServiceSubtask> serviceSubtasks = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskEntity);
+                serviceSubtaskList.addAll(serviceSubtasks);
+            }
         }
         return serviceSubtaskList;
     }
@@ -678,6 +799,7 @@
             result.putIfAbsent("xsf", 0);
             result.putIfAbsent("dsf", 0);
             result.putIfAbsent("ywc", 0);
+            result.putIfAbsent("cs", 0);
             result.putIfAbsent("yc", 0);
             result.putIfAbsent("jg", 0);
         }
@@ -714,11 +836,56 @@
             result.putIfAbsent("dsf", 0);
             result.putIfAbsent("dfs", 0);
             result.putIfAbsent("yfs", 0);
+            result.putIfAbsent("cs", 0);
             result.putIfAbsent("ywc", 0);
             result.putIfAbsent("yc", 0);
             result.putIfAbsent("jg", 0);
         }
         return result;
+    }
+
+    /**
+     * 缁熶竴銆岄殢璁跨梾鍖恒�嶅彛寰勶細涓�寰嬩互銆愬嚭闄㈣褰曘�憑@code pat_med_inhosp} 涓哄噯锛�2026-10-29 涓氬姟纭锛夈��
+     *
+     * <h3>涓轰粈涔堥渶瑕佸畠</h3>
+     * 鍑洪櫌闅忚瀛愪换鍔$殑绉戝/鐥呭尯鏈簲鏉ヨ嚜鍑洪櫌璁板綍锛屼絾銆屽啀娆¢殢璁� / 鎵嬪伐鏂板銆嶈繖涓や釜鍏ュ彛鏄�
+     * <b>鐩存帴鎸夊墠绔紶鏉ョ殑瀛楁钀藉簱</b>鐨勶細鍓嶇涓嶅甫杩欎簺瀛楁鏃�
+     * {@code leaveldeptcode} / {@code leavehospitaldistrictcode} / {@code campusid} 灏辨槸绌恒��
+     * 鑰岄殢璁垮垪琛ㄦ槸鎸夌瀹�/鐥呭尯杩囨护鐨勶紙{@code and leavehospitaldistrictcode = #{...}}锛夛紝
+     * 绌哄�间笉鍖归厤浠讳綍鍊� 鈫� 杩欎簺浠诲姟"鐢熸垚浜嗕絾椤甸潰涓婃煡涓嶅埌"锛堢嚎涓� 2026-10 瀹炴祴 314 鏉★級銆�
+     *
+     * <p>鍙e緞锛�<b>鐥呭尯鍙鍑洪櫌璁板綍</b> 鈥斺�� 涓嶆寜鎮h�呮。妗堝弽鎺ㄣ�佷篃涓嶇敤鍓嶇浼犵殑鍊笺��
+     * 鍙鍑洪櫌闅忚锛坽@code service_type='2'}锛変笖鑳藉畾浣嶅埌鍑洪櫌璁板綍锛坽@code inhospid} 闈炵┖锛夌殑琛岀敓鏁堬紱
+     * 鍏朵綑鎯呭喌鍘熸牱鏀捐锛屽婊℃剰搴�(6)/瀹f暀/闂ㄨ瘖绛夊叾瀹冪被鍨嬫病鏈変换浣曞奖鍝嶃��
+     *
+     * @param serviceSubtask 寰呰惤搴撶殑瀛愪换鍔★紙灏卞湴淇敼锛�
+     */
+    private void alignDischargeWardFromInhosp(ServiceSubtask serviceSubtask) {
+        if (serviceSubtask == null || serviceSubtask.getInhospid() == null) {
+            return;
+        }
+        if (!"2".equals(serviceSubtask.getServiceType())) {
+            return;
+        }
+        PatMedInhosp inhosp;
+        try {
+            inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(serviceSubtask.getInhospid());
+        } catch (Exception e) {
+            log.warn("銆愰殢璁夸换鍔°�戞寜 inhospid={} 鍙栧嚭闄㈣褰曞け璐ワ紝鐥呭尯娌跨敤鍏ュ弬锛歿}",
+                    serviceSubtask.getInhospid(), e.toString());
+            return;
+        }
+        if (inhosp == null) {
+            log.warn("銆愰殢璁夸换鍔°�慽nhospid={} 鏌ヤ笉鍒板嚭闄㈣褰曪紝鐥呭尯娌跨敤鍏ュ弬", serviceSubtask.getInhospid());
+            return;
+        }
+        serviceSubtask.setLeaveldeptcode(inhosp.getLeaveldeptcode());
+        serviceSubtask.setLeaveldeptname(inhosp.getLeaveldeptname());
+        serviceSubtask.setLeavehospitaldistrictcode(inhosp.getLeavehospitaldistrictcode());
+        serviceSubtask.setLeavehospitaldistrictname(inhosp.getLeavehospitaldistrictname());
+        if (StringUtils.isNotEmpty(inhosp.getCampusid())) {
+            serviceSubtask.setCampusid(inhosp.getCampusid());
+        }
     }
 
     /**
@@ -750,7 +917,7 @@
             serviceOutPath.setSubId(serviceSubtask1.getId());
             List<ServiceOutPath> serviceOutPaths = serviceOutPathMapper.selectServiceOutPathList(serviceOutPath);
             if (CollectionUtils.isNotEmpty(serviceOutPaths))
-                format = String.format("%03X", serviceOutPaths.get(0).getId());
+                format = ShortCode.encode(serviceOutPaths.get(0).getId());
             else format = getOutPath(serviceSubtask1, rsaPublicKeyExample);
             serviceSubtask = serviceSubtask1;
         } else {
@@ -763,6 +930,8 @@
             }
 
             serviceSubtask.setCreateTime(DateUtils.getNowDate());
+            // 鍙e緞锛�2026-10-29 涓氬姟纭锛夛細鍑洪櫌闅忚鐨勭瀹�/鐥呭尯涓�寰嬩互鍑洪櫌璁板綍涓哄噯
+            alignDischargeWardFromInhosp(serviceSubtask);
             serviceSubtaskMapper.insertServiceSubtask(serviceSubtask);
             //淇敼浣忛櫌婊℃剰搴︾姸鎬侊紙閬垮厤閲嶅鏌ヨ锛�
             if (serviceSubtask.getServiceType() != null && "6".equals(serviceSubtask.getServiceType())) {
@@ -800,7 +969,7 @@
         serviceOutPath.setSubId(serviceSubtask.getId());
         iServiceOutPathService.insertServiceOutPath(serviceOutPath);
         //杞垚16杩涘埗
-        format = String.format("%03X", serviceOutPath.getId());
+        format = ShortCode.encode(serviceOutPath.getId());
         serviceOutPath.setRadix(format);
         serviceOutPath.setUpdateTime(new Date());
         iServiceOutPathService.updateServiceOutPath(serviceOutPath);
@@ -814,6 +983,7 @@
      * @return 缁撴灉
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public String addSubTaskAgain(ServiceSubtask serviceSubtask) {
         ServiceSubtask serviceSubtask1 = selectServiceSubtaskById(serviceSubtask.getId());
         PatMedInhosp patMedInhosp = new PatMedInhosp();
@@ -823,7 +993,7 @@
         PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask1.getPatid());
         //鏅畞 鏂板鍐嶆闅忚涓嶈兘瑙﹀彂鎮h�呭啀鍏ラ櫌
         boolean inhospAgain = true;
-        if (serviceSubtask1.getOrgid().equals("47255004333112711A1001")) {
+        if ("47255004333112711A1001".equals(serviceSubtask1.getOrgid())) {
             inhospAgain = false;
         }
         if (patMedInhospList.size() > 0 && inhospAgain) {
@@ -837,13 +1007,17 @@
             serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";鏃犻渶鍐嶆闅忚锛屼换鍔¤鏍囪涓嶅湪鎵ц" : "鏃犻渶鍐嶆闅忚锛屼换鍔¤鏍囪涓嶅湪鎵ц");
             serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
             return "鏃犻渶鍐嶆闅忚锛屼换鍔¤鏍囪涓嶅湪鎵ц";
-        } else if (StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
+        } else if (patArchive != null && StringUtils.isNotEmpty(patArchive.getNotrequiredFlag()) && patArchive.getNotrequiredFlag().equals("1")) {
             serviceSubtask1.setIsVisitAgain(2);
             serviceSubtaskMapper.updateServiceSubtask(serviceSubtask1);
             serviceSubtask1.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + ";鏃犻渶鍐嶆闅忚锛屾偅鑰呰鏍囪涓嶉渶瑕佽鏈嶅姟" : "鏃犻渶鍐嶆闅忚锛屾偅鑰呰鏍囪涓嶉渶瑕佽鏈嶅姟");
             return "鏃犻渶鍐嶆闅忚锛屾偅鑰呰鏍囪涓嶉渶瑕佽鏈嶅姟";
         } else if (ObjectUtils.isNotEmpty(serviceSubtask1.getIsVisitAgain()) && serviceSubtask1.getIsVisitAgain() == 2) {
             return "鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡";
+        }
+        //涓芥按涓尰闄㈡牴鎹笅娆′换鍔D鐢熸垚鍐嶆闅忚
+        if ("47231022633110211A2101".equals(serviceSubtask1.getOrgid())) {
+            return addSubTaskAgainNextTaskId(serviceSubtask1, serviceSubtask);
         }
         //鏂板鍐嶆闅忚浠诲姟
         ServiceSubtask serviceSubtask2 = DtoConversionUtils.sourceToTarget(serviceSubtask, ServiceSubtask.class);
@@ -856,6 +1030,13 @@
         serviceSubtask2.setVisitType(serviceSubtask.getVisitType());
         serviceSubtask2.setVisitDeptCode(serviceSubtask.getVisitDeptCode());
         serviceSubtask2.setVisitDeptName(serviceSubtask.getVisitDeptName());
+        // 鍙e緞锛�2026-10-29 涓氬姟纭锛夛細闅忚鐥呭尯涓�寰嬪彇鍑洪櫌璁板綍鐨�
+        //   鈶� 鍐嶆闅忚鏄粠璇锋眰 DTO 澶嶅埗鍑烘潵鐨勶紝鍑洪櫌璁板綍ID 涓嶅湪璇锋眰閲� 鈫� 鍏堢敤鍘熶换鍔$殑涓婏紱
+        //   鈶� 鍐嶆寜鍑洪櫌璁板綍鎶婄瀹�/鐥呭尯/闄㈠尯缁熶竴杩囨潵锛堝彇涓嶅埌灏变繚鎸佷笉鍙橈級銆�
+        if (serviceSubtask2.getInhospid() == null) {
+            serviceSubtask2.setInhospid(serviceSubtask1.getInhospid());
+        }
+        alignDischargeWardFromInhosp(serviceSubtask2);
         int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtask2);
         if (i == 1) {
             //鏂板鎴愬姛鍚庯紝闇�瑕佸皢涔嬪墠鐨勪换鍔¤缃负涓嶈鍐嶉殢璁匡紙闃叉閲嶅鐐癸紝鐒跺悗閲嶅鐢熸垚锛�
@@ -864,6 +1045,495 @@
             return "鍐嶆闅忚浠诲姟鍒涘缓鎴愬姛";
         }
         return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽啀璇锋閲嶈瘯";
+    }
+
+    public String addSubTaskAgainNextTaskId(ServiceSubtask serviceSubtask, ServiceSubtask request) {
+        if (ObjectUtils.isEmpty(serviceSubtask.getTaskid())) {
+            log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛岀己灏慣askId, 鍐嶈娆¢噸璇�");
+            return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛岀己灏慣askId, 鍐嶈娆¢噸璇�";
+        }
+        // 鍚屼竴鍘熶换鍔′覆琛岀敓鎴愶紝闃叉骞跺彂閲嶅鎻掑叆
+        String lockToken = tryLockAgainTask(serviceSubtask.getId());
+        if (lockToken == null) {
+            return "鍐嶆闅忚浠诲姟姝e湪鍒涘缓涓紝璇峰嬁閲嶅鎻愪氦";
+        }
+        try {
+            ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
+            if (ObjectUtils.isEmpty(serviceTask)) {
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 taskid = {}, 鍐嶈娆¢噸璇�", serviceSubtask.getTaskid());
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 taskid = " + serviceSubtask.getTaskid() + ", 鍐嶈娆¢噸璇�";
+            }
+            // nexttaskflag=0 琛ㄧず浠诲姟閾剧粓姝紝涓嶅厑璁稿啀鐢熸垚
+//            if ("0".equals(serviceTask.getNexttaskflag())) {
+//                log.info("鏃犻渶鍐嶆闅忚锛屼换鍔″凡鏍囪鏃犲悗缁殢璁�, taskid = {}", serviceSubtask.getTaskid());
+//                return "鏃犻渶鍐嶆闅忚锛岃浠诲姟鏃犲悗缁殢璁垮畨鎺�";
+//            }
+            // 骞傜瓑鏍¢獙锛氬悓涓�鍘熶换鍔″彧鍏佽鐢熸垚涓�鏉′簩娆¢殢璁�
+            ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity();
+            againQuery.setUpid(serviceSubtask.getId().intValue());
+            if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) {
+                return "鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡";
+            }
+            // 鏌ヨ鍑洪櫌璁板綍
+            PatMedInhosp patMedInhospVo = new PatMedInhosp();
+            patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
+            patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
+            patMedInhospVo.setCry(1);
+            List<PatMedInhosp> patMedInhospList = inhospVisitFacade.selectPatMedInhospList(patMedInhospVo);
+            PatMedInhosp patMedInhosp = new PatMedInhosp();
+            if (CollectionUtils.isNotEmpty(patMedInhospList)) {
+                patMedInhosp = patMedInhospList.get(0);
+            } else {
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屼换鍔$己灏� 瀵瑰簲鐨勫嚭闄㈣褰�, taskid = {}, 鍐嶈娆¢噸璇�", serviceSubtask.getTaskid());
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屼换鍔$己灏� 瀵瑰簲鐨勫嚭闄㈣褰�, taskid = " + serviceSubtask.getTaskid() + ", 鍐嶈娆¢噸璇�";
+            }
+            // 鏈厤缃� nexttaskid 鏃跺鐢ㄥ綋鍓嶄换鍔¢厤缃紝闃叉鍘嗗彶浠诲姟鐩存帴澶辫触
+            Long nexttaskid = serviceTask.getNexttaskid();
+            if (ObjectUtils.isEmpty(nexttaskid) || Long.valueOf(0L).equals(nexttaskid)) {
+                nexttaskid = serviceTask.getTaskid();
+                log.warn("鍐嶆闅忚浠诲姟鏈厤缃� nexttaskid锛屾寜褰撳墠浠诲姟閰嶇疆鐢熸垚, taskid = {}", nexttaskid);
+            }
+            ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(nexttaskid);
+            if (ObjectUtils.isEmpty(nextServiceTask)) {
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 nexttaskid = {}, 鍐嶈娆¢噸璇�", nexttaskid);
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 nexttaskid = " + nexttaskid + ", 鍐嶈娆¢噸璇�";
+            }
+            PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask.getPatid());
+            if (ObjectUtils.isEmpty(patArchive)) {
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭鎵惧埌鎮h�呮。妗� patid = {}, subTaskId = {}", serviceSubtask.getPatid(), serviceSubtask.getId());
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭鎵惧埌鎮h�呮。妗�, subTaskId = " + serviceSubtask.getId() + ", 鍐嶈娆¢噸璇�";
+            }
+            String config = sysConfigService.selectConfigByKey("visit.early.day");
+            Integer type = 1;
+            String resource = resolveAgainResource(request);
+            if (StringUtils.isEmpty(resource)) {
+                String requestResource = request == null ? null : request.getResource();
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽啀娆¢殢璁挎柟寮忎笉鍚堟硶, resource = {}, subTaskId = {}", requestResource, serviceSubtask.getId());
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽啀娆¢殢璁挎柟寮忎笉鍚堟硶, resource = " + requestResource + ", 鍐嶈娆¢噸璇�";
+            }
+            String visitDeptCode;
+            String visitDeptName;
+            if ("1".equals(resource)) {
+                // 鏈瀹ら殢璁匡細璇锋眰绉戝浼樺厛锛屾湭浼犳椂浣跨敤鍘熶换鍔″嚭闄㈢瀹わ紱涓嶄緷璧栧師浠诲姟 visitType
+                String requestDeptCode = firstNonBlank(request == null ? null : request.getVisitDeptCode());
+                String requestDeptName = firstNonBlank(request == null ? null : request.getVisitDeptName());
+                if (StringUtils.isNotEmpty(requestDeptCode)) {
+                    visitDeptCode = requestDeptCode;
+                    visitDeptName = requestDeptName;
+                } else {
+                    // 鍑洪櫌绉戝锛歴ervice_subtask 閲屽搴旂殑鏄� leaveldeptcode锛坉eptcode 宸叉敼涓哄湪闄㈢瀹わ級
+                    visitDeptCode = serviceSubtask.getLeaveldeptcode();
+                    visitDeptName = serviceSubtask.getLeaveldeptname();
+                }
+                if (StringUtils.isEmpty(visitDeptCode)) {
+                    log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湰绉戝闅忚鏈壘鍒伴殢璁跨瀹�, subTaskId = {}", serviceSubtask.getId());
+                    return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湰绉戝闅忚鏈壘鍒伴殢璁跨瀹�, subTaskId = " + serviceSubtask.getId() + ", 鍐嶈娆¢噸璇�";
+                }
+                SysDept ownDept = selectDeptByCode(serviceSubtask.getOrgid(), visitDeptCode);
+                if (ObjectUtils.isNotEmpty(ownDept)) {
+                    visitDeptCode = ownDept.getDeptCode();
+                    visitDeptName = ownDept.getDeptName();
+                } else if (StringUtils.isNotEmpty(requestDeptCode) || StringUtils.isEmpty(visitDeptName)) {
+                    log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湰绉戝闅忚绉戝涓嶅瓨鍦�, deptCode = {}, subTaskId = {}", visitDeptCode, serviceSubtask.getId());
+                    return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湰绉戝闅忚绉戝涓嶅瓨鍦�, deptCode = " + visitDeptCode + ", 鍐嶈娆¢噸璇�";
+                }
+            } else {
+                // 闅忚涓績绉戝闅忚
+                String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtask.getOrgid());
+                if (ObjectUtils.isEmpty(centerDept)) {
+                    log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭閰嶇疆闅忚涓績绉戝鍙傛暟 sys.smartor.center.dept ,subTaskId = {}, 鍐嶈娆¢噸璇�", serviceSubtask.getId());
+                    return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭閰嶇疆闅忚涓績绉戝鍙傛暟 sys.smartor.center.dept ,subTaskId = " + serviceSubtask.getId() + ", 鍐嶈娆¢噸璇�";
+                }
+                SysDept sysDeptVo = new SysDept();
+                sysDeptVo.setDeptType("1");
+                sysDeptVo.setOrgid(serviceSubtask.getOrgid());
+                sysDeptVo.setDeptCode(centerDept);
+                List<SysDept> sysDepts = sysDeptService.selectDeptList(sysDeptVo);
+                SysDept sysDept = new SysDept();
+                if (CollectionUtils.isNotEmpty(sysDepts)) {
+                    sysDept = sysDepts.get(0);
+                } else {
+                    log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭鎵惧埌鍙傛暟 sys.smartor.center.dept 閰嶇疆鐨勯殢璁夸腑蹇冪瀹� deptCode = {}, 鍐嶈娆¢噸璇�", centerDept);
+                    return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭鎵惧埌鍙傛暟 sys.smartor.center.dept 閰嶇疆鐨勯殢璁夸腑蹇冪瀹� deptCode = " + centerDept + ", 鍐嶈娆¢噸璇�";
+                }
+                visitDeptCode = sysDept.getDeptCode();
+                visitDeptName = sysDept.getDeptName();
+            }
+
+            // 鎸変笅涓�浠诲姟鐢熸垚浜屾闅忚瀛愪换鍔★紙琛ㄥ崟銆佸彂閫佹柟寮忓彇鑷笅涓�浠诲姟锛夛紱
+            // 缁勮閫昏緫宸插湪鏈ā鍧楃殑 OperationSubtaskWriter 閲岋紙鍘� IPatMedInhospService#boxedServiceSubtask锛夛紝杩欓噷鐩存帴澶嶇敤
+            ServiceSubtask serviceSubtaskAgain = operationSubtaskWriter.boxedServiceSubtask(nextServiceTask,
+                    patMedInhosp, patArchive, config, type);
+            if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) {
+                log.error("鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屼笅涓�浠诲姟鐢熸垚瀛愪换鍔′负绌�, subTaskId = {}, nextTaskId = {}", serviceSubtask.getId(), nexttaskid);
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾偅鑰呭嚭闄㈣褰曚笉瀹屾暣鎴栦笅涓�浠诲姟閰嶇疆寮傚父, 鍐嶈娆¢噸璇�";
+            }
+            Date now = new Date();
+            applyAgainVisitTime(serviceSubtaskAgain, serviceSubtask, nextServiceTask, config, now);
+            // 鍖荤敓澶勭悊鎰忚鍙栬嚜鏈璇锋眰锛屾寜涓嬩竴浠诲姟閲嶅缓瀛愪换鍔℃椂闇�鏄惧紡甯﹀叆锛屽惁鍒欎笉浼氳惤搴�
+            if (request != null) {
+                serviceSubtaskAgain.setDoctorOpinion(request.getDoctorOpinion());
+                serviceSubtaskAgain.setRemark(request.getRemark());
+                serviceSubtaskAgain.setDrcode(request.getDrcode());
+                serviceSubtaskAgain.setDrname(request.getDrname());
+                serviceSubtaskAgain.setNurseId(request.getNurseId());
+                serviceSubtaskAgain.setNurseName(request.getNurseName());
+                serviceSubtaskAgain.setManagementDoctor(request.getManagementDoctor());
+                serviceSubtaskAgain.setManagementDoctorCode(request.getManagementDoctorCode());
+            }
+            serviceSubtaskAgain.setId(null);
+            serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
+            serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount())
+                    ? serviceSubtask.getVisitCount() + 1 : 2);
+            serviceSubtaskAgain.setSendstate(2L);
+            serviceSubtaskAgain.setIsVisitAgain(1);
+            serviceSubtaskAgain.setCreateTime(now);
+            serviceSubtaskAgain.setUpdateTime(now);
+            // 鏂颁换鍔$殑闅忚鏂瑰紡璁板綍鏈閫夋嫨鐨勫啀娆¢殢璁挎柟寮忥紱resource 鏈韩涓嶈惤搴�
+            serviceSubtaskAgain.setVisitType(resource);
+            serviceSubtaskAgain.setVisitDeptCode(visitDeptCode);
+            serviceSubtaskAgain.setVisitDeptName(visitDeptName);
+            int i = serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain);
+            if (i != 1) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽啀璇锋閲嶈瘯";
+            }
+            // 鍒濆鍖栧彂閫佹柟寮忔槑缁嗭紝琛ュ伩锛坈ompensateTask锛夊拰瓒呮椂鍒ゆ柇渚濊禆璇ヨ〃
+            addServiceSubtaskPreachform(serviceSubtaskAgain, null);
+            // 鏉′欢鏇存柊鍘熶换鍔★細0 琛岃鏄庡凡琚叾浠栬姹傜敓鎴愶紝鎶涘紓甯稿洖婊氭湰娆℃彃鍏ョ殑閲嶅鏁版嵁
+            int updated = serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2);
+            if (updated != 1) {
+                throw new BaseException("鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡");
+            }
+            return "鍐嶆闅忚浠诲姟鍒涘缓鎴愬姛";
+        } finally {
+            unlockAgainTask(serviceSubtask.getId(), lockToken);
+        }
+    }
+
+    /**
+     * 瑙f瀽鍐嶆闅忚鏂瑰紡锛氫粎鍙栬姹傚弬鏁� resource锛屼笉浣跨敤鍘熶换鍔� visitType锛涙湭浼犳椂榛樿闅忚涓績銆�
+     *
+     * @return 1-鏈瀹ら殢璁匡紱2-闅忚涓績闅忚锛沶ull-闈炴硶鍊�
+     */
+    private String resolveAgainResource(ServiceSubtask request) {
+        String resource = request == null ? null : request.getResource();
+        if (StringUtils.isEmpty(resource) || StringUtils.isEmpty(resource.trim())) {
+            return "2";
+        }
+        resource = resource.trim();
+        return ("1".equals(resource) || "2".equals(resource)) ? resource : null;
+    }
+
+    private String firstNonBlank(String... values) {
+        if (values == null) {
+            return null;
+        }
+        for (String value : values) {
+            if (StringUtils.isNotEmpty(value) && StringUtils.isNotEmpty(value.trim())) {
+                return value.trim();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鎸� orgid + deptCode 鏌ヨ鍚敤绉戝锛屾煡涓嶅埌杩斿洖 null銆�
+     */
+    private SysDept selectDeptByCode(String orgid, String deptCode) {
+        if (StringUtils.isEmpty(orgid) || StringUtils.isEmpty(deptCode)) {
+            return null;
+        }
+        SysDept query = new SysDept();
+        query.setDeptType("1");
+        query.setOrgid(orgid);
+        query.setDeptCode(deptCode);
+        List<SysDept> deptList = sysDeptService.selectDeptList(query);
+        return CollectionUtils.isEmpty(deptList) ? null : deptList.get(0);
+    }
+
+    /**
+     * 浜屾闅忚鏃堕棿锛氶娆¢殢璁垮畬鎴愭椂闂达紙鏈畬鎴愬彇褰撳墠鏃堕棿锛�+ 涓嬩竴浠诲姟鍙戦�佸ぉ鏁帮紝杩囧幓鏃堕棿鍏滃簳涓哄綋鍓嶆椂闂淬��
+     * 閫氱煡绫讳换鍔℃寜 visit.early.day 鎻愬墠鍙戦�併��
+     */
+    private void applyAgainVisitTime(ServiceSubtask target, ServiceSubtask origin, ServiceTask nextTask,
+                                     String earlyDayConfig, Date now) {
+        if (target == null || origin == null || nextTask == null || now == null) {
+            return;
+        }
+        Date baseTime = ObjectUtils.isNotEmpty(origin.getFinishtime()) ? origin.getFinishtime() : now;
+        int intervalDays = ObjectUtils.isNotEmpty(nextTask.getSendDay()) ? nextTask.getSendDay().intValue() : 0;
+        Date dueTime = addDays(baseTime, intervalDays);
+        if (dueTime.before(now)) {
+            dueTime = now;
+        }
+        if(ObjectUtils.isNotEmpty(origin.getVisitTime())){
+            dueTime = ObjectUtils.isNotEmpty(origin.getVisitTime()) ? origin.getVisitTime() : now;
+        }
+
+        target.setLongSendTime(dueTime);
+        Date visitTime = dueTime;
+        if ("3".equals(nextTask.getType())) {
+            visitTime = addDays(dueTime, -parseIntConfig(earlyDayConfig));
+            if (visitTime.before(now)) {
+                visitTime = now;
+            }
+        }
+        target.setVisitTime(visitTime);
+    }
+
+    private int parseIntConfig(String value) {
+        if (StringUtils.isEmpty(value)) {
+            return 0;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException e) {
+            log.warn("閰嶇疆鍊间笉鏄悎娉曟暣鏁帮紝鎸�0澶勭悊, value={}", value);
+            return 0;
+        }
+    }
+
+    /**
+     * 鏃ユ湡鍔犲噺澶╂暟锛坉ays 鍙负璐燂級銆�
+     * <p>
+     * 璇存槑锛氭湰妯″潡鍏跺畠鍦版柟鐢ㄧ殑鏄� {@code DateUtils.addDays}锛屼絾 ruoyi-common 鐨� DateUtils 骞舵湭鎻愪緵璇ユ柟娉曪紝
+     * 杩欓噷鏄簩娆¢殢璁挎椂闂磋绠楄嚜甯︾殑绉佹湁瀹炵幇锛岄伩鍏嶄緷璧栦笉瀛樺湪鐨勫伐鍏锋柟娉曘��
+     */
+    private Date addDays(Date date, int days) {
+        if (date == null) {
+            return null;
+        }
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.add(Calendar.DAY_OF_MONTH, days);
+        return calendar.getTime();
+    }
+
+    /**
+     * 棣栨闅忚浠诲姟瓒呮椂鍚庯紝鎸変笅涓�浠诲姟鑷姩鐢熸垚鍐嶆闅忚浠诲姟銆�
+     * <p>
+     * 瀹氭椂浠诲姟鍏ュ彛 {@code RyTask#dealSubtaskDeadline} 浣跨敤銆備笌鎵嬪姩鍏ュ彛涓�鑷寸殑閮ㄥ垎锛�
+     * 琛ㄥ崟/鍙戦�佹柟寮忓彇涓嬩竴浠诲姟銆侀摼璺� upid + visit_count銆佸彂閫佹柟寮忔槑缁嗗垵濮嬪寲锛�
+     * 宸紓锛氳秴鏃跺啀娆¢殢璁垮浐瀹氳蛋闅忚涓績绉戝锛堥殢璁夸腑蹇冩湭閰嶇疆鏃跺洖閫�鍘熶换鍔$瀹わ級锛屽師浠诲姟缃负鍙戦�佸け璐ワ紙sendstate=5锛夈��
+     * <p>
+     * 骞傜瓑锛氬悓涓�鍘熶换鍔″彧鍏佽鐢熸垚涓�鏉″啀娆¢殢璁匡紙Redis 閿� + upid 鏍¢獙 + 鍘熶换鍔℃潯浠舵洿鏂帮級锛�
+     * 澶氳妭鐐�/澶氱嚎绋嬪悓鏃舵壂鎻忔椂锛屽悗鍒扮殑浜嬪姟浼氬湪鏉′欢鏇存柊澶勬嬁鍒� 0 琛屽苟鍥炴粴鏈鎻掑叆銆�
+     *
+     * @param serviceSubtask 宸茶秴鏃剁殑棣栨闅忚瀛愪换鍔�
+     * @return 缁撴灉鎻忚堪
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String addSubTaskAgainByDeadline(ServiceSubtask serviceSubtask) {
+        if (ObjectUtils.isEmpty(serviceSubtask) || ObjectUtils.isEmpty(serviceSubtask.getId())) {
+            return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽師浠诲姟涓嶅瓨鍦�";
+        }
+        String lockToken = tryLockAgainTask(serviceSubtask.getId());
+        if (lockToken == null) {
+            return "鍐嶆闅忚浠诲姟姝e湪鍒涘缓涓紝璇峰嬁閲嶅鎻愪氦";
+        }
+        try {
+            // 骞傜瓑鏍¢獙锛氬悓涓�鍘熶换鍔″彧鍏佽鐢熸垚涓�鏉″啀娆¢殢璁�
+            ServiceSubtaskEntity againQuery = new ServiceSubtaskEntity();
+            againQuery.setUpid(serviceSubtask.getId().intValue());
+            if (CollectionUtils.isNotEmpty(serviceSubtaskMapper.selectServiceSubtaskList(againQuery))) {
+                return "鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡";
+            }
+            // 鍘熶换鍔″凡琚爣璁颁负涓嶅啀闅忚锛堟墜鍔ㄥ叆鍙e啓杩� is_visit_again=2锛�
+            if (ObjectUtils.isNotEmpty(serviceSubtask.getIsVisitAgain()) && serviceSubtask.getIsVisitAgain() == 2) {
+                return "鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡";
+            }
+            ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
+            if (ObjectUtils.isEmpty(serviceTask)) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 taskid = " + serviceSubtask.getTaskid();
+            }
+            if ("0".equals(serviceTask.getNexttaskflag())) {
+                return "鏃犻渶鍐嶆闅忚锛岃浠诲姟鏃犲悗缁殢璁垮畨鎺�";
+            }
+            if (ObjectUtils.isEmpty(serviceTask.getNexttaskid())) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屼换鍔$己灏� nexttaskid, taskid = " + serviceSubtask.getTaskid();
+            }
+            ServiceTask nextServiceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getNexttaskid());
+            if (ObjectUtils.isEmpty(nextServiceTask)) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾壘涓嶅埌瀵瑰簲浠诲姟 nexttaskid = " + serviceTask.getNexttaskid();
+            }
+            PatMedInhosp patMedInhosp = selectOutHospRecord(serviceSubtask);
+            if (ObjectUtils.isEmpty(patMedInhosp)) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屼换鍔$己灏戝搴旂殑鍑洪櫌璁板綍, taskid = " + serviceSubtask.getTaskid();
+            }
+            PatArchive patArchive = taskArchivePort.findByPatid(serviceSubtask.getPatid());
+            if (ObjectUtils.isEmpty(patArchive)) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾湭鎵惧埌鎮h�呮。妗�, subTaskId = " + serviceSubtask.getId();
+            }
+            String config = sysConfigService.selectConfigByKey("visit.early.day");
+            String visitDeptCode;
+            String visitDeptName;
+            // 闅忚涓績绉戝锛氭湭閰嶇疆锛堟垨绉戝涓嶅瓨鍦級鏃跺洖閫�鍘熶换鍔$瀹わ紝淇濇寔瓒呮椂鍏ュ彛鍘熸湁琛屼负
+            SysDept centerDept = selectCenterDept(serviceSubtask.getOrgid());
+            if (ObjectUtils.isNotEmpty(centerDept)) {
+                visitDeptCode = centerDept.getDeptCode();
+                visitDeptName = centerDept.getDeptName();
+            } else {
+                String fallbackDeptCode = firstNonBlank(serviceSubtask.getVisitDeptCode());
+                if (StringUtils.isNotEmpty(fallbackDeptCode)) {
+                    visitDeptCode = fallbackDeptCode;
+                    visitDeptName = serviceSubtask.getVisitDeptName();
+                } else {
+                    // 鍑洪櫌绉戝锛歴ervice_subtask 閲屽搴旂殑鏄� leaveldeptcode锛坉eptcode 宸叉敼涓哄湪闄㈢瀹わ級
+                    visitDeptCode = serviceSubtask.getLeaveldeptcode();
+                    visitDeptName = serviceSubtask.getLeaveldeptname();
+                }
+            }
+            // 缁勮閫昏緫澶嶇敤鏈ā鍧� OperationSubtaskWriter锛堝師 IPatMedInhospService#boxedServiceSubtask锛�
+            ServiceSubtask serviceSubtaskAgain = operationSubtaskWriter.boxedServiceSubtask(nextServiceTask,
+                    patMedInhosp, patArchive, config, 1);
+            if (ObjectUtils.isEmpty(serviceSubtaskAgain) || ObjectUtils.isEmpty(serviceSubtaskAgain.getTaskid())) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屾偅鑰呭嚭闄㈣褰曚笉瀹屾暣鎴栦笅涓�浠诲姟閰嶇疆寮傚父, subTaskId = " + serviceSubtask.getId();
+            }
+            Date now = new Date();
+            serviceSubtaskAgain.setId(null);
+            serviceSubtaskAgain.setUpid(serviceSubtask.getId().intValue());
+            serviceSubtaskAgain.setVisitCount(ObjectUtils.isNotEmpty(serviceSubtask.getVisitCount())
+                    ? serviceSubtask.getVisitCount() + 1 : 2);
+            serviceSubtaskAgain.setSendstate(2L);
+            serviceSubtaskAgain.setIsVisitAgain(1);
+            serviceSubtaskAgain.setCreateTime(now);
+            serviceSubtaskAgain.setUpdateTime(now);
+            // 瓒呮椂鍐嶆闅忚鍥哄畾涓洪殢璁夸腑蹇冮殢璁�
+            serviceSubtaskAgain.setVisitType("2");
+            serviceSubtaskAgain.setVisitDeptCode(visitDeptCode);
+            serviceSubtaskAgain.setVisitDeptName(visitDeptName);
+            if (serviceSubtaskMapper.insertServiceSubtask(serviceSubtaskAgain) != 1) {
+                return "鍐嶆闅忚浠诲姟鍒涘缓澶辫触锛屽啀璇锋閲嶈瘯";
+            }
+            // 鍒濆鍖栧彂閫佹柟寮忔槑缁嗭紝琛ュ伩锛坈ompensateTask锛夊拰瓒呮椂鍒ゆ柇渚濊禆璇ヨ〃
+            addServiceSubtaskPreachform(serviceSubtaskAgain, null);
+            // 鏉′欢鏇存柊鍘熶换鍔★細0 琛岃鏄庡凡琚叾浠栬姹傜敓鎴愶紝鎶涘紓甯稿洖婊氭湰娆℃彃鍏ョ殑閲嶅鏁版嵁
+            if (serviceSubtaskMapper.updateIsVisitAgainById(serviceSubtask.getId(), 2) != 1) {
+                throw new BaseException("鏃犻渶鍐嶆闅忚锛岃浠诲姟宸茬粡鍐嶆闅忚杩囦簡");
+            }
+            // 鍘熶换鍔¤秴鏃讹紝缃负鍙戦�佸け璐�
+            ServiceSubtask originUpdate = new ServiceSubtask();
+            originUpdate.setId(serviceSubtask.getId());
+            originUpdate.setSendstate(5L);
+            originUpdate.setUpdateTime(now);
+            serviceSubtaskMapper.updateServiceSubtask(originUpdate);
+            return "鍐嶆闅忚浠诲姟鍒涘缓鎴愬姛";
+        } finally {
+            unlockAgainTask(serviceSubtask.getId(), lockToken);
+        }
+    }
+
+    /**
+     * 鎸� orgid + inhospid 鍙栧嚭闄㈣褰曪紙瓒呮椂鍐嶆闅忚鐢級銆�
+     */
+    private PatMedInhosp selectOutHospRecord(ServiceSubtask serviceSubtask) {
+        PatMedInhosp patMedInhospVo = new PatMedInhosp();
+        patMedInhospVo.setOrgid(serviceSubtask.getOrgid());
+        patMedInhospVo.setInhospid(serviceSubtask.getInhospid());
+        patMedInhospVo.setCry(1);
+        List<PatMedInhosp> patMedInhospList = inhospVisitFacade.selectPatMedInhospList(patMedInhospVo);
+        if (CollectionUtils.isEmpty(patMedInhospList)) {
+            return null;
+        }
+        return patMedInhospList.get(0);
+    }
+
+    /**
+     * 鍙栭殢璁夸腑蹇冪瀹わ紝鏈厤缃垨绉戝涓嶅瓨鍦ㄦ椂杩斿洖 null锛堢敱璋冪敤鏂瑰洖閫�鍘熶换鍔$瀹わ級銆�
+     */
+    private SysDept selectCenterDept(String orgid) {
+        String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", orgid);
+        if (StringUtils.isEmpty(centerDept)) {
+            return null;
+        }
+        SysDept sysDeptVo = new SysDept();
+        sysDeptVo.setDeptType("1");
+        sysDeptVo.setOrgid(orgid);
+        sysDeptVo.setDeptCode(centerDept);
+        // 瀹氭椂浠诲姟绾跨▼娌℃湁鐧诲綍涓婁笅鏂囷紝涓嶈兘璋冪敤甯� @DataScope 鐨� selectDeptList锛屽惁鍒欎細鎶涒�滆幏鍙栫敤鎴蜂俊鎭紓甯糕�濄��
+        List<SysDept> sysDepts = sysDeptService.selectSysDeptList(sysDeptVo);
+        return CollectionUtils.isEmpty(sysDepts) ? null : sysDepts.get(0);
+    }
+
+    /**
+     * 鑾峰彇浜屾闅忚鐢熸垚閿併�俁edis 寮傚父鏃堕檷绾т负鏁版嵁搴撳箓绛夋牎楠岋紝涓嶉樆鏂笟鍔°��
+     */
+    private String tryLockAgainTask(Long subId) {
+        String token = UUID.randomUUID().toString();
+        try {
+            Boolean locked = redisCache.setCacheObjectIfAbsent(
+                    AGAIN_LOCK_KEY_PREFIX + subId, token, AGAIN_LOCK_SECONDS, TimeUnit.SECONDS);
+            return Boolean.TRUE.equals(locked) ? token : null;
+        } catch (Exception e) {
+            log.warn("鍐嶆闅忚鑾峰彇Redis閿佸け璐ワ紝闄嶇骇浣跨敤鏁版嵁搴撳箓绛夋牎楠�, subId={}", subId, e);
+            return token;
+        }
+    }
+
+    /**
+     * 閲婃斁浜屾闅忚鐢熸垚閿侊紝浠呴噴鏀捐嚜宸辨寔鏈夌殑閿併��
+     */
+    private void unlockAgainTask(Long subId, String token) {
+        if (token == null) {
+            return;
+        }
+        try {
+            String lockKey = AGAIN_LOCK_KEY_PREFIX + subId;
+            Object current = redisCache.getCacheObject(lockKey);
+            if (token.equals(current)) {
+                redisCache.deleteObject(lockKey);
+            }
+        } catch (Exception e) {
+            log.warn("鍐嶆闅忚閲婃斁Redis閿佸け璐�, subId={}", subId, e);
+        }
+    }
+
+    /**
+     * 鎸変换鍔$殑 preachformDesc 鍒濆鍖栧彂閫佹柟寮忔槑缁嗭紙service_subtask_preachform锛夈��
+     * <p>
+     * 杩佺Щ璇存槑锛氬師瀹炵幇鏄� smartor 渚� {@code IPatMedInhospService#addServiceSubtaskPreachform}锛�
+     * 璇诲啓鐨勪袱寮犺〃閮藉睘浜庢湰妯″潡锛屾晠鎸夋ā鍧楄竟鐣屾惉鍒拌繖閲岋紝璇箟淇濇寔涓�鑷达細
+     * 鍏堟寜 subid+preachform 鏌ラ噸锛堝凡瀛樺湪灏变笉鏂板锛夛紝sendstate 榛樿涓� "1"锛堜紶鍙傚彲瑕嗙洊锛夈��
+     *
+     * @param serviceSubtask 宸茶惤搴撶殑瀛愪换鍔�
+     * @param sendstate      鍙戦�佺姸鎬佽鐩栧�硷紝涓虹┖鍒欑敤 "1"
+     * @return 鍥哄畾杩斿洖 0锛堜笌鍘熷疄鐜颁竴鑷达級
+     */
+    private Integer addServiceSubtaskPreachform(ServiceSubtask serviceSubtask, String sendstate) {
+        ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
+        if (serviceTask == null) {
+            log.error("addServiceSubtaskPreachform taskid={} 鏌ヤ笉鍒颁换鍔★紝璺宠繃preachform瀛愯〃鐢熸垚", serviceSubtask.getTaskid());
+            return 0;
+        }
+        String preachformDesc = serviceTask.getPreachformDesc();
+        if (StringUtils.isEmpty(preachformDesc)) {
+            return 0;
+        }
+        List<ServiceSubtaskPreachform> preachformList;
+        try {
+            preachformList = JSON.parseArray(preachformDesc, ServiceSubtaskPreachform.class);
+        } catch (Exception e) {
+            log.error("preachform杞琇ist<ServiceSubtaskPreachform>鎶ラ敊浜嗭細{}", e.getMessage());
+            return 0;
+        }
+        if (CollectionUtils.isEmpty(preachformList)) {
+            log.error("preachformDesc瑙f瀽鍚庝负绌猴紝璺宠繃preachform瀛愯〃鐢熸垚锛宼askid涓猴細{}锛宲reachformDesc涓猴細{}",
+                    serviceSubtask.getTaskid(), preachformDesc);
+            return 0;
+        }
+        for (ServiceSubtaskPreachform serviceSubtaskPreachform : preachformList) {
+            serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid());
+            serviceSubtaskPreachform.setSubid(serviceSubtask.getId());
+            //鍏堟煡璇竴涓嬶紝鏄惁瀛樺湪锛屽鏋滃凡缁忓瓨鍦紝灏变笉鏂板浜�
+            if (CollectionUtils.isNotEmpty(taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform))) {
+                continue;
+            }
+            serviceSubtaskPreachform.setSendstate(StringUtils.isNotEmpty(sendstate) ? sendstate : "1");
+            serviceSubtaskPreachform.setOrgid(serviceSubtask.getOrgid());
+            taskPreachformPort.insertServiceSubtaskPreachform(serviceSubtaskPreachform);
+        }
+        return 0;
     }
 
     /**
@@ -878,7 +1548,12 @@
         if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 6)
             serviceSubtask.setFinishtime(new Date());
 
-        return serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
+        Boolean updated = serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
+        if (Boolean.TRUE.equals(updated) && serviceSubtask.getSendstate() != null
+                && serviceSubtask.getSendstate() == 6 && serviceSubtask.getId() != null) {
+            followupCompletedEventPublisher.publish(serviceSubtask.getId());
+        }
+        return updated;
     }
 
     @Override
@@ -919,6 +1594,75 @@
     }
 
     /**
+     * 绉戝缂栫爜鍘婚噸锛屼繚鎸佸師椤哄簭锛岄伩鍏� service_task.deptcode 瓒呴暱鍜� service_taskdept 閲嶅缁戝畾
+     */
+    private String normalizeDeptCodes(String deptCodes) {
+        if (StringUtils.isEmpty(deptCodes)) {
+            return deptCodes;
+        }
+        return Arrays.stream(deptCodes.split(","))
+                .map(String::trim)
+                .filter(code -> StringUtils.isNotEmpty(code))
+                .distinct()
+                .collect(Collectors.joining(","));
+    }
+
+    /**
+     * 鍙栭�楀彿涓查噷鐨勭涓�涓�硷紙浠诲姟涓婄瀹�/鐥呭尯鏄閫夛紝鑰� {@code service_subtask} 涓婇兘鏄崟鍊煎垪锛夈��
+     */
+    private String firstValue(String value) {
+        if (StringUtils.isEmpty(value)) {
+            return null;
+        }
+        for (String item : value.split(",")) {
+            if (StringUtils.isNotEmpty(item.trim())) {
+                return item.trim();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 浠诲姟閰嶇疆锛坽@link ServiceTaskVO}锛夆啋 瀛愪换鍔★紙{@link ServiceSubtask}锛夌殑瀛楁鎼繍銆�
+     * <p>
+     * <b>涓轰粈涔堜笉鑳界洿鎺� {@code DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class)}锛�</b>
+     * 瀹冩槸绾悓鍚嶆嫹璐濓紝鑰屼袱杈瑰悓鍚嶇殑 {@code deptcode / deptname} <b>璇箟涓嶅悓</b>锛�
+     * <ul>
+     *   <li>{@code ServiceTaskVO.deptcode / deptname} = 浠诲姟鐨勩�岄�傜敤绉戝銆嶏紝
+     *       鍑洪櫌闅忚锛坰erviceType=2锛�/ 鍑洪櫌瀹f暀锛�4锛�/ 闂ㄨ瘖闅忚锛�3锛夌瓑鍦烘櫙涓嬪氨鏄�<b>绂婚櫌绉戝</b>锛�</li>
+     *   <li>{@code ServiceSubtask.deptcode / deptname} = <b>鍦ㄩ櫌绉戝</b>
+     *       锛�2026-11 鐢卞師 sendlimitpbegin/pend 澶嶇敤鑰屾潵锛岃 {@code ServiceSubtask} 瀛楁娉ㄩ噴锛夈��</li>
+     * </ul>
+     * 浜庢槸绾悓鍚嶆嫹璐濅細鎶娿�岀闄㈢瀹ゃ�嶅啓杩涖�屽湪闄㈢瀹ゃ�嶅垪锛屽悗鏋滄湁涓や釜锛�
+     * <ol>
+     *   <li>鎸夊湪闄㈢淮搴︾瓫锛坽@code deptcodes} 鈫� {@code service_subtask.deptcode}锛変細璇懡涓繖浜涘瓙浠诲姟锛�</li>
+     *   <li>鎸夌闄㈢淮搴︾瓫锛坽@code leaveldeptcodes} 鈫� {@code service_subtask.leaveldeptcode}锛夋椂锛�
+     *       鎮h�呰嚜宸辨病甯︾瀹ょ殑閭d簺锛堝墠绔墜鍔ㄥ嬀閫夋偅鑰呭緢甯歌锛変細鍥犱负 {@code leaveldeptcode} 涓虹┖鑰屾煡涓嶅埌銆�</li>
+     * </ol>
+     * 鍥犳杩欓噷鎸夐」鐩棦瀹氬彛寰勶紙{@code doc/service_subtask_瀛楁鏀瑰悕_鏀瑰姩娓呭崟.md} 搂8锛氳繖鍑犳潯
+     * 銆岄暱鏈熶换鍔� / 浠诲姟缁� / 鎵嬪姩寤轰换鍔°�嶈矾寰�<b>涓嶅~鍦ㄩ櫌缁村害</b>锛夌粺涓�澶勭悊锛�
+     * <b>鍦ㄩ櫌缁村害 4 鍒楁竻绌�</b>锛堝湪闄㈢瀹�/鍦ㄩ櫌鐥呭尯鍙湁 HIS 閾捐矾鑳戒粠 {@code pat_med_inhosp} 鎷垮埌锛夛紝
+     * <b>绉戝 / 鐥呭尯涓�寰嬭惤绂婚櫌缁村害</b>锛坽@code leaveldeptcode/name}銆亄@code leavehospitaldistrictcode/name}锛夈��
+     *
+     * @param serviceTaskVO 寤轰换鍔″叆鍙�
+     * @return 宸叉寜绂婚櫌缁村害褰掍綅鐨勫瓙浠诲姟瀵硅薄锛堝湪闄㈢淮搴︿负绌猴級
+     */
+    private ServiceSubtask toSubtaskFromTaskVO(ServiceTaskVO serviceTaskVO) {
+        ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class);
+        // 鈶� 鍦ㄩ櫌缁村害蹇呴』娓呯┖锛氫换鍔¢厤缃殑绉戝涓嶆槸鎮h�呮湰浜虹殑鍦ㄩ櫌绉戝
+        serviceSubtask.setDeptcode(null);
+        serviceSubtask.setDeptname(null);
+        serviceSubtask.setHospitaldistrictcode(null);
+        serviceSubtask.setHospitaldistrictname(null);
+        // 鈶� 绉戝 / 鐥呭尯 鈫� 绂婚櫌缁村害锛堝閫変覆鍙栫涓�涓紝瀛愪换鍔℃槸鍗曞�煎垪锛�
+        serviceSubtask.setLeaveldeptcode(firstValue(serviceTaskVO.getDeptcode()));
+        serviceSubtask.setLeaveldeptname(firstValue(serviceTaskVO.getDeptname()));
+        serviceSubtask.setLeavehospitaldistrictcode(firstValue(serviceTaskVO.getLeavehospitaldistrictcode()));
+        serviceSubtask.setLeavehospitaldistrictname(firstValue(serviceTaskVO.getLeavehospitaldistrictname()));
+        return serviceSubtask;
+    }
+
+    /**
      * 鍗曚竴浠诲姟
      *
      * @return 缁撴灉
@@ -930,6 +1674,8 @@
             log.info("浠诲姟鍏ュ弬涓虹┖锛岃妫�鏌ュ叆鍙�");
             throw new BaseException("浠诲姟鍏ュ弬涓虹┖锛岃妫�鏌ュ叆鍙�");
         }
+        // 绉戝缂栫爜鍘婚噸锛岄伩鍏� service_task.deptcode 瓒呴暱鍙� service_taskdept 閲嶅缁戝畾
+        serviceTaskVO.setDeptcode(normalizeDeptCodes(serviceTaskVO.getDeptcode()));
         ServiceTask serviceTask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceTask.class);
         if (CollectionUtils.isEmpty(serviceTaskVO.getPreachformList())) throw new BaseException("浠诲姟鐨勫彂閫佹柟寮忎负绌�");
         //灏唒reachfromList鏀惧埌serviceTask涓殑preachfrom涓�
@@ -994,7 +1740,7 @@
             }
             serviceTask.setLeaveldeptcodes(serviceTaskVO.getLeaveldeptcodes());
             if (StringUtils.isEmpty(serviceTask.getLeavehospitaldistrictname()) && StringUtils.isNotEmpty(serviceTask.getLeavehospitaldistrictcode())) {
-                SysDept sysDept = taskSysDeptPort.selectDeptByCode(serviceTask.getLeavehospitaldistrictcode(), null);
+                SysDept sysDept = sysDept2Mapper.selectDeptByCode(serviceTask.getLeavehospitaldistrictcode(), null);
                 serviceTask.setLeavehospitaldistrictname(sysDept.getDeptName());
             }
             if (serviceTaskVO.getLongTask() == 1) serviceTask.setSendState(2L);
@@ -1143,7 +1889,8 @@
 //            }
 
             //灏嗕换鍔′俊鎭斁鍒版湇鍔¤〃涓�
-            ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class);
+            // 绉戝/鐥呭尯鎸夈�岀闄㈢淮搴︺�嶅綊浣嶏紙鍑洪櫌闅忚鐨勭瀹ょ梾鍖哄氨鏄闄㈢瀹�/鐥呭尯锛夛紝瑙� toSubtaskFromTaskVO
+            ServiceSubtask serviceSubtask = toSubtaskFromTaskVO(serviceTaskVO);
             serviceSubtask.setTaskid(serviceTask.getTaskid().longValue());
             serviceSubtask.setTemplatename(ObjectUtils.isNotEmpty(tempid) ? tempName : serviceTask.getTemplatename());
             serviceSubtask.setTemplateid(ObjectUtils.isNotEmpty(tempid) ? tempid : serviceTask.getTemplateid());
@@ -1164,10 +1911,18 @@
                     serviceSubtask.setInhospid(patTaskRelevance.getHospid());
                     //鏂板缓瀹屾垚灏辨槸寰呭彂閫佺姸鎬�
                     serviceSubtask.setSendstate(2L);
-                    serviceSubtask.setDeptcode(StringUtils.isNotEmpty(patTaskRelevance.getDeptcode()) ? patTaskRelevance.getDeptcode() : serviceTaskVO.getDeptcode());
-                    serviceSubtask.setDeptname(StringUtils.isNotEmpty(patTaskRelevance.getDeptName()) ? patTaskRelevance.getDeptName() : serviceTaskVO.getDeptname());
-                    serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode());
-                    serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname());
+                    // 绂婚櫌缁村害锛堝嚭闄㈤殢璁� = 鍑洪櫌绉戝 / 鍑洪櫌鐥呭尯锛夛細
+                    // 鎮h�呰嚜甯︾殑鍑洪櫌绉戝/鐥呭尯浼樺厛锛屾偅鑰呬晶娌″甫锛堝墠绔墜鍔ㄥ嬀閫夊緢甯歌锛夋椂鍥為��鍒颁换鍔¢厤缃紝
+                    // 涓嶈兘鏃犳潯浠惰鐩� 鈥斺�� 鍚﹀垯浼氭妸 toSubtaskFromTaskVO 鎸変换鍔¢厤缃厹濂界殑绂婚櫌缁村害鎶规垚 null锛�
+                    // 缁撴灉銆屼换鍔¢厤浜嗙瀹ょ梾鍖恒�佺敓鎴愬嚭鏉ョ殑瀛愪换鍔$闄㈢瀹�/鐥呭尯鍗存槸绌恒�嶃��
+                    serviceSubtask.setLeaveldeptcode(firstNonBlank(patTaskRelevance.getDeptcode(),
+                            firstValue(serviceTaskVO.getDeptcode())));
+                    serviceSubtask.setLeaveldeptname(firstNonBlank(patTaskRelevance.getDeptName(),
+                            firstValue(serviceTaskVO.getDeptname())));
+                    serviceSubtask.setLeavehospitaldistrictcode(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictcode(),
+                            firstValue(serviceTaskVO.getLeavehospitaldistrictcode())));
+                    serviceSubtask.setLeavehospitaldistrictname(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictname(),
+                            firstValue(serviceTaskVO.getLeavehospitaldistrictname())));
                     serviceSubtask.setType(serviceTaskVO.getType());
                     serviceSubtask.setHospType(patTaskRelevance.getHospType());
                     serviceSubtask.setOpenid(patTaskRelevance.getOpenid());
@@ -1237,6 +1992,8 @@
             ServiceTask serviceTask1 = serviceTaskService.selectServiceTaskByTaskid(serviceTask.getTaskid());
             ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity();
             serviceSubtaskVO.setTaskid(serviceTask1.getTaskid());
+            //鍙垹闄ら娆¢殢璁挎湭瀹屾垚鐨�
+            serviceSubtaskVO.setVisitCount(1);
             serviceSubtaskVO.setSendstate(2L);
             List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.selectServiceSubtaskList(serviceSubtaskVO);
             serviceSubtaskVO.setSendstate(1L);
@@ -1302,6 +2059,11 @@
             if (StringUtils.isNotEmpty(serviceTaskVO.getAppltype())) {
                 if (serviceTaskVO.getAppltype().equals("1") || serviceTaskVO.getAppltype().equals("2")) {
                     if (StringUtils.isNotEmpty(serviceTaskVO.getDeptcode())) {
+                        // 淇敼浠诲姟鏃跺厛杞垹闄ゆ棫鐨勭瀹ょ粦瀹氾紝閬垮厤鏃х瀹ゆ畫鐣�
+                        ServiceTaskdept oldDept = new ServiceTaskdept();
+                        oldDept.setTaskId(serviceTask.getTaskid());
+                        oldDept.setDeptType("1");
+                        serviceTaskdeptService.deleteServiceTaskdeptByTaskId(oldDept);
                         String[] deptcodes = serviceTaskVO.getDeptcode().split(",");
                         for (String deptCode : deptcodes) {
                             ServiceTaskdept serviceTaskdept = new ServiceTaskdept();
@@ -1518,7 +2280,8 @@
             }
             if (CollectionUtils.isNotEmpty(serviceTaskVO.getPatTaskRelevances())) {
                 for (PatTaskRelevance patTaskRelevance : serviceTaskVO.getPatTaskRelevances()) {
-                    ServiceSubtask serviceSubtask = DtoConversionUtils.sourceToTarget(serviceTaskVO, ServiceSubtask.class);
+                    // 绉戝/鐥呭尯鎸夈�岀闄㈢淮搴︺�嶅綊浣嶏紙鍑洪櫌闅忚鐨勭瀹ょ梾鍖哄氨鏄闄㈢瀹�/鐥呭尯锛夛紝瑙� toSubtaskFromTaskVO
+                    ServiceSubtask serviceSubtask = toSubtaskFromTaskVO(serviceTaskVO);
                     serviceSubtask.setLibtemplateid(ObjectUtils.isEmpty(serviceTaskVO.getLibtemplateid()) ? null : Long.valueOf(serviceTaskVO.getLibtemplateid()));
                     serviceSubtask.setLibtemplatename(serviceTaskVO.getLibtemplatename());
                     serviceSubtask.setTemplateid(serviceTask.getTemplateid());
@@ -1530,10 +2293,16 @@
                     serviceSubtask.setAddr(patTaskRelevance.getAddr());
                     serviceSubtask.setPatid(patTaskRelevance.getPatid());
                     serviceSubtask.setOpenid(patTaskRelevance.getOpenid());
-                    serviceSubtask.setDeptcode(patTaskRelevance.getDeptcode());
-                    serviceSubtask.setLeavehospitaldistrictname(patTaskRelevance.getLeavehospitaldistrictname());
-                    serviceSubtask.setLeavehospitaldistrictcode(patTaskRelevance.getLeavehospitaldistrictcode());
-                    serviceSubtask.setDeptname(patTaskRelevance.getDeptName());
+                    // 绂婚櫌缁村害锛氭偅鑰呰嚜甯︾殑鍑洪櫌绉戝/鐥呭尯浼樺厛锛屾病甯︽椂鍥為��鍒颁换鍔¢厤缃紙鍚﹀垯浼氭妸
+                    // toSubtaskFromTaskVO 鎸変换鍔¢厤缃厹濂界殑鍊兼姽鎴� null锛屽嚭闄㈤殢璁垮氨鎴愩�岀闄㈢瀹�/鐥呭尯涓虹┖銆嶏級
+                    serviceSubtask.setLeaveldeptcode(firstNonBlank(patTaskRelevance.getDeptcode(),
+                            firstValue(serviceTaskVO.getDeptcode())));
+                    serviceSubtask.setLeaveldeptname(firstNonBlank(patTaskRelevance.getDeptName(),
+                            firstValue(serviceTaskVO.getDeptname())));
+                    serviceSubtask.setLeavehospitaldistrictcode(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictcode(),
+                            firstValue(serviceTaskVO.getLeavehospitaldistrictcode())));
+                    serviceSubtask.setLeavehospitaldistrictname(firstNonBlank(patTaskRelevance.getLeavehospitaldistrictname(),
+                            firstValue(serviceTaskVO.getLeavehospitaldistrictname())));
                     serviceSubtask.setType(serviceTaskVO.getType());
                     serviceSubtask.setCreateTime(DateUtils.getNowDate());
                     serviceSubtask.setDrname(patTaskRelevance.getDrname());
@@ -1765,14 +2534,31 @@
             if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
                 groupKey = drcode;
             } else {
-                groupKey = "deptcode";
+                // 2026-11 瀛楁閲嶅懡鍚嶅悗锛歞eptcode 鏄湪闄㈢瀹わ紙鍘嗗彶鏁版嵁涓虹┖锛夛紝
+                // 鍑洪櫌绉戝宸叉敼鍚嶄负 leaveldeptcode锛屽垎缁勫繀椤荤敤鍚庤�咃紝
+                // 鍚﹀垯 getSfStatisticsGroupKey 浼氳繑鍥� NULL锛屾嫾鎴� IN (NULL) 瀵艰嚧鏁磋〃鏌ョ┖銆�
+                groupKey = "leaveldeptcode";
             }
+        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
+            // 鐥呯缁熻锛氭寜浠诲姟ID鍒嗙粍锛屾瘡涓换鍔″嵆涓�涓梾绉嶉殢璁夸换鍔�
+            groupKey = "taskid";
         }
         List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>();
         // 鏍规嵁鏉′欢杩涜鍒嗙粍
         Map<String, List<ServiceSubtask>> collect = new HashMap<>();
         serviceSubtaskCountReq.setGroupKey(groupKey);
         List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq);
+        /*
+         * 鍒嗙粍閿彲鑳戒负 NULL锛堝巻鍙叉暟鎹�佹垨閲嶅懡鍚嶅悗灏氭湭鍥炲~鐨勫湪闄㈢淮搴﹀垪锛夛紝
+         * 鐩存帴鎷艰繘 SQL 浼氬彉鎴� `AND IFNULL(鍒�,'Unknown') IN (NULL)`锛屾亽涓嶆垚绔嬶紝鏁村紶缁熻琛ㄤ负绌恒��
+         * 杩欓噷缁熶竴鎴� 'Unknown'锛屼笌鑱氬悎 SQL 鐨� IFNULL(鍒�,'Unknown') 鍙e緞瀵归綈銆�
+         */
+        if (groupKeyList != null) {
+            groupKeyList = groupKeyList.stream()
+                    .map(key -> key == null ? "Unknown" : key)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
         serviceSubtaskCountReq.setGroupKeyList(groupKeyList);
         List<ServiceSubtask> rawData = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq);
         switch (groupKey) {
@@ -1787,10 +2573,14 @@
                 collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getManagementDoctorCode()).orElse("Unknown")));
                 break;
             case "deptcode":
-                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown")));
+            case "leaveldeptcode":
+                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptcode()).orElse("Unknown")));
                 break;
             case "leavehospitaldistrictcode":
                 collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
+                break;
+            case "taskid":
+                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getTaskid()).map(String::valueOf).orElse("Unknown")));
                 break;
             default:
                 collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
@@ -1804,6 +2594,9 @@
                 continue;
             }
             ServiceSubtaskStatistic statistic = getStatistic(serviceSubtaskList, serviceSubtaskCountReq.getFollowUpCountStyle(), serviceSubtaskCountReq.getVisitCount());
+            if ("taskid".equals(groupKey)) {
+                applyTaskGroupInfo(statistic, serviceSubtaskList);
+            }
             if (ObjectUtils.isNotEmpty(jsRates)) {
                 Map<String, Object> jsRateMap = jsRates.get(collectKey);
                 BigDecimal jsRate = new BigDecimal("0");
@@ -1852,6 +2645,21 @@
      * @return
      */
     public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, Map<String, List<ServiceSubtask>> collect) {
+        if (ObjectUtils.isEmpty(serviceSubtaskCountReq) || ObjectUtils.isEmpty(collect)) {
+            return null;
+        }
+        return getSsRateForSfStatistics(serviceSubtaskCountReq, new ArrayList<>(collect.keySet()));
+    }
+
+    /**
+     * 闅忚缁熻-鍙婃椂鐜囩粺璁★紙鍒嗙粍閿洿鎺ヤ紶鍊肩殑閲嶈浇锛�
+     * <p>
+     * 缁熻涓嬫帹鍒� SQL 鐨� {@code getSfStatisticsScript} 涓嶅啀鎶婃槑缁嗚鎹炲洖鍐呭瓨锛屾嬁涓嶅埌
+     * {@code Map<鍒嗙粍, List<鏄庣粏>>}锛屽洜姝ょ洿鎺ヤ紶鍒嗙粍閿泦鍚堬紱鏌ヨ璇彞涓庡師鏉ュ畬鍏ㄥ悓涓�涓��
+     *
+     * @param groupKeyList 鍒嗙粍閿泦鍚堬紙drcode / leaveldeptcode / leavehospitaldistrictcode 鐨勫疄闄呭彇鍊硷級
+     */
+    public Map<String, Map<String, Object>> getSsRateForSfStatistics(ServiceSubtaskCountReq serviceSubtaskCountReq, List<String> groupKeyList) {
         if (ObjectUtils.isEmpty(serviceSubtaskCountReq)) {
             return null;
         }
@@ -1859,7 +2667,6 @@
         if (StringUtils.isEmpty(groupKey)) {
             return null;
         }
-        List<String> groupKeyList = new ArrayList<>(collect.keySet());
         if (ObjectUtils.isEmpty(groupKeyList)) {
             return null;
         }
@@ -1878,11 +2685,30 @@
         ssRate.setGroupKey(groupKey);
         ssRate.setGroupKeyList(groupKeyList);
         ssRate.setRateDay(serviceSubtaskCountReq.getRateDay());
+        Map<String, Map<String, Object>> rateResult = serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
+        if (rateResult == null || rateResult.isEmpty()) {
+            return rateResult;
+        }
+        // taskid 涓烘暟鍊肩被鍨嬶紝MyBatis 杩斿洖鐨� Map key 鍙兘鏄� Long锛岃繖閲岀粺涓�杞垚 String 涓庡垎缁� key 瀵归綈
         Map<String, Map<String, Object>> jsRates = new HashMap<>();
-        if (!collect.isEmpty()) {
-            jsRates = serviceSubtaskMapper.selectTimelyRateBatch(ssRate);
+        for (Map.Entry<String, Map<String, Object>> entry : rateResult.entrySet()) {
+            jsRates.put(String.valueOf(entry.getKey()), entry.getValue());
         }
         return jsRates;
+    }
+
+    /**
+     * 绌哄�煎厹搴曪細SUM(CASE ...) 鐞嗚涓婁笉浼氭槸 null锛岃繖閲屽彧鍋氶槻寰★紝閬垮厤鎷嗙 NPE銆�
+     */
+    private long nvlLong(Long value) {
+        return value == null ? 0L : value;
+    }
+
+    /**
+     * 鎶婃暟鎹簱鑱氬悎鍊硷紙BigDecimal / Long / Integer 绛夛級杞垚 int銆�
+     */
+    private int toIntValue(Object value) {
+        return value instanceof Number ? ((Number) value).intValue() : 0;
     }
 
     public List<ServiceSubtaskStatistic> getSfStatisticsExport(ServiceSubtaskCountReq serviceSubtaskCountReq) {
@@ -1892,6 +2718,9 @@
             ServiceSubtaskStatistic totalRow = calculateTotalRow(sfStatistics);
             totalRow.setLeavehospitaldistrictname("鎬昏");
             totalRow.setDeptname("鎬昏");
+            if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
+                totalRow.setTaskName("鎬昏");
+            }
             sfStatistics.add(totalRow);
         }
 
@@ -1930,7 +2759,7 @@
             if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
                 collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrname()).orElse("Unknown")));
             } else {
-                collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptname()).orElse("Unknown")));
+                collect = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq).stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeaveldeptname()).orElse("Unknown")));
             }
         }
         List<ServiceSubtaskCount> joySubCount = serviceSubtaskMapper.getSfStatisticsJoy(serviceSubtaskCountReq);
@@ -1951,7 +2780,7 @@
                 if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getLeavehospitaldistrictcodes())) {
                     ss.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
                 } else if (CollectionUtils.isNotEmpty(serviceSubtaskCountReq.getDeptcodes())) {
-                    ss.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+                    ss.setLeaveldeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
                 }
             }
 
@@ -1990,6 +2819,22 @@
         return serviceSubtaskStatistics;
     }
 
+    /**
+     * 鐥呯缁熻锛堟寜 taskid 鍒嗙粍锛夋椂琛ラ綈浠诲姟ID銆佷换鍔″悕绉板拰鐤剧梾鐥呯銆�
+     *
+     * @param statistic          缁熻缁撴灉
+     * @param serviceSubtaskList 褰撳墠浠诲姟鍒嗙粍涓嬬殑瀛愪换鍔�
+     */
+    private void applyTaskGroupInfo(ServiceSubtaskStatistic statistic, List<ServiceSubtask> serviceSubtaskList) {
+        if (statistic == null || CollectionUtils.isEmpty(serviceSubtaskList)) {
+            return;
+        }
+        ServiceSubtask first = serviceSubtaskList.get(0);
+        statistic.setTaskId(first.getTaskid());
+        statistic.setTaskName(first.getTaskName());
+        statistic.setDiagType(first.getDiagType());
+    }
+
     private ServiceSubtaskStatistic getStatistic(List<ServiceSubtask> serviceSubtaskList, String followUpCountStyle, Integer visitCount) {
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
 
@@ -2000,12 +2845,14 @@
         } else {
             //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
             DecimalFormat percentFormat = new DecimalFormat("##.##%");
-            serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
-            serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+            serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
+            serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
             serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
             serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
             serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
             serviceSubtaskStatistic.setDrname(serviceSubtaskList.get(0).getDrname());
+            //闅忚涓績绉戝锛岀敤浜庣粺璁″啀娆¢殢璁挎帹閫佸埌闅忚涓績鐨勪汉鏁�
+            String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtaskList.get(0).getOrgid());
             //鍑洪櫌浜烘
             serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
             for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
@@ -2020,9 +2867,31 @@
                 if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                     serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
                 }
+                //鍙婃椂闅忚浜烘锛氬凡瀹屾垚涓旀湭瓒呮椂
+                if (isTimelyFollowUp(serviceSubtask)) {
+                    serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
+                }
+
+                //棣栨鍑洪櫌浜烘锛歷isitCount = 1锛屼綔涓哄啀娆¢殢璁挎帹閫佺巼鐨勫垎姣�
+                if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) {
+                    serviceSubtaskStatistic.setFirstDischargeCount(serviceSubtaskStatistic.getFirstDischargeCount() + 1L);
+                }
+                //鍐嶆闅忚鎺ㄩ�佷汉鏁帮細棣栦釜鍐嶆闅忚锛坴isitCount = 2锛変笖鎵ц绉戝涓洪殢璁夸腑蹇冪瀹�
+                if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 2
+                        && StringUtils.isNotEmpty(centerDept)
+                        && StringUtils.equals(StringUtils.trim(centerDept), StringUtils.trim(serviceSubtask.getVisitDeptCode()))) {
+                    serviceSubtaskStatistic.setAgainFollowUpPushCount(serviceSubtaskStatistic.getAgainFollowUpPushCount() + 1L);
+                }
+
+                //浜哄伐宸叉嫧閫氭�婚噺
+                if (ObjectUtils.isNotEmpty(serviceSubtask.getIsManuallyCalled())
+                        && serviceSubtask.getIsManuallyCalled().equals(1L)) {
+                    serviceSubtaskStatistic.setManuallyCalled(serviceSubtaskStatistic.getManuallyCalled() + 1L);
+                }
 
                 //棣栨鍑洪櫌闅忚
                 if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() == 1) {
+                    applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                     //棣栨搴旈殢璁�
                     if (serviceSubtask.getSendstate() != null && !serviceSubtask.getSendstate().equals(4L)) {
                         serviceSubtaskStatistic.setNeedFollowUp(serviceSubtaskStatistic.getNeedFollowUp() + 1L);
@@ -2144,6 +3013,7 @@
                     }
                 }//浜屾鍑洪櫌闅忚
                 else if (serviceSubtask.getVisitCount() != null && serviceSubtask.getVisitCount() > 1) {
+                    applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
                     //鍐嶆搴旈殢璁�
                     if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                         serviceSubtaskStatistic.setNeedFollowUpAgain(serviceSubtaskStatistic.getNeedFollowUpAgain() + 1L);
@@ -2282,6 +3152,80 @@
     }
 
     /**
+     * 鏄惁灞炰簬銆屽強鏃堕殢璁裤�嶏細闅忚宸插畬鎴愪笖瀹屾垚鏃剁姸鎬佷负姝e父瀹屾垚锛堟湭瓒呮椂锛夈��
+     * <p>
+     * finish_state锛�1-姝e父瀹屾垚 2-寤舵湡瀹屾垚 3-瓒呮椂瀹屾垚锛屽彧鏈� 1 璁″叆鍙婃椂闅忚銆�
+     *
+     * @param serviceSubtask 闅忚瀛愪换鍔�
+     * @return true-璁″叆鍙婃椂闅忚浜烘
+     */
+    private boolean isTimelyFollowUp(ServiceSubtask serviceSubtask) {
+        return serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate().equals(6L)
+                && serviceSubtask.getFinishState() != null && serviceSubtask.getFinishState().equals(1L);
+    }
+
+    /**
+     * 甯備竴缁熻鍙e緞锛氶娆″緟闅忚銆佸凡鍙戦�佸強浜哄伐鐢佃瘽瓒呮椂銆�
+     */
+    private void applyFirstFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
+        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() != 1 || serviceSubtask.getSendstate() == null) {
+            return;
+        }
+        Long sendstate = serviceSubtask.getSendstate();
+        if (sendstate.equals(3L)) {
+            statistic.setSent(statistic.getSent() + 1L);
+        }
+        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
+            statistic.setManualCallTimeout(statistic.getManualCallTimeout() + 1L);
+        }
+        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
+            statistic.setPendingFollowUpAll(statistic.getPendingFollowUpAll() + 1L);
+        }
+        if (sendstate.equals(1L) || sendstate.equals(2L)) {
+            statistic.setPendingFollowUpWaitSend(statistic.getPendingFollowUpWaitSend() + 1L);
+        }
+        if (sendstate.equals(3L) || sendstate.equals(7L)) {
+            statistic.setPendingFollowUpSent(statistic.getPendingFollowUpSent() + 1L);
+        }
+    }
+
+    /**
+     * 鍐嶆闅忚闄勫姞缁熻锛氬緟闅忚銆佸凡鍙戦�併�佷汉宸ョ數璇濊秴鏃跺強鍥為櫌澶嶈瘖澶嶆煡锛坰uggest=7锛夈��
+     */
+    private void applyAgainFollowUpExtraStatistics(ServiceSubtaskStatistic statistic, ServiceSubtask serviceSubtask) {
+        if (serviceSubtask.getVisitCount() == null || serviceSubtask.getVisitCount() <= 1) {
+            return;
+        }
+        if (serviceSubtask.getSendstate() == null) {
+            return;
+        }
+        Long sendstate = serviceSubtask.getSendstate();
+        // 鍥為櫌澶嶈瘖澶嶆煡锛氬啀娆¢殢璁夸腑鎸夊鐞嗘剰瑙� suggest=7 缁熻
+        if (StringUtils.equals("7", StringUtils.trim(serviceSubtask.getSuggest()))) {
+            statistic.setSuggest7CountAgain(statistic.getSuggest7CountAgain() + 1L);
+        }
+        if (sendstate.equals(3L)) {
+            statistic.setSentAgain(statistic.getSentAgain() + 1L);
+        }
+        if (sendstate.equals(7L) && isManuallyNotCalled(serviceSubtask)) {
+            statistic.setManualCallTimeoutAgain(statistic.getManualCallTimeoutAgain() + 1L);
+        }
+        if (sendstate.equals(1L) || sendstate.equals(2L) || sendstate.equals(3L) || sendstate.equals(7L)) {
+            statistic.setPendingFollowUpAllAgain(statistic.getPendingFollowUpAllAgain() + 1L);
+        }
+        if (sendstate.equals(1L) || sendstate.equals(2L)) {
+            statistic.setPendingFollowUpWaitSendAgain(statistic.getPendingFollowUpWaitSendAgain() + 1L);
+        }
+        if (sendstate.equals(3L) || sendstate.equals(7L)) {
+            statistic.setPendingFollowUpSentAgain(statistic.getPendingFollowUpSentAgain() + 1L);
+        }
+    }
+
+    private boolean isManuallyNotCalled(ServiceSubtask serviceSubtask) {
+        return serviceSubtask.getIsManuallyCalled() != null && serviceSubtask.getIsManuallyCalled().equals(0L);
+    }
+
+    /**
      * 棣栨闅忚缁熻
      *
      * @param serviceSubtaskList
@@ -2292,8 +3236,8 @@
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
         //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
         DecimalFormat percentFormat = new DecimalFormat("##.##%");
-        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
-        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
+        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
         serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
         serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
         serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2302,6 +3246,7 @@
         serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
 
         for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
+            applyFirstFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
             //鏃犻渶闅忚浜烘
             if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                 serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -2312,6 +3257,10 @@
             //搴旈殢璁夸汉娆�
             if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                 serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
+            }
+            //鍙婃椂闅忚浜烘锛氬凡瀹屾垚涓旀湭瓒呮椂
+            if (isTimelyFollowUp(serviceSubtask)) {
+                serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
             }
 
             //棣栨鍑洪櫌闅忚
@@ -2462,8 +3411,8 @@
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
         //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
         DecimalFormat percentFormat = new DecimalFormat("##.##%");
-        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
-        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
+        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
         serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
         serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
         serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2471,6 +3420,7 @@
         //鍑洪櫌浜烘
         serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
         for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
+            applyAgainFollowUpExtraStatistics(serviceSubtaskStatistic, serviceSubtask);
             //鏃犻渶闅忚浜烘
             if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 4) {
                 serviceSubtaskStatistic.setNonFollowUp(serviceSubtaskStatistic.getNonFollowUp() + 1L);
@@ -2481,6 +3431,10 @@
             //搴旈殢璁夸汉娆�
             if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() != 4) {
                 serviceSubtaskStatistic.setFollowUpNeeded(serviceSubtaskStatistic.getFollowUpNeeded() + 1L);
+            }
+            //鍙婃椂闅忚浜烘锛氬凡瀹屾垚涓旀湭瓒呮椂
+            if (isTimelyFollowUp(serviceSubtask)) {
+                serviceSubtaskStatistic.setTimelyFollowUp(serviceSubtaskStatistic.getTimelyFollowUp() + 1L);
             }
 
             //鍐嶆鍑洪櫌闅忚
@@ -2624,8 +3578,8 @@
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
         //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
         DecimalFormat percentFormat = new DecimalFormat("##.##%");
-        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
-        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
+        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
         serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
         serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
         serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
@@ -2729,15 +3683,16 @@
         ServiceSubtaskStatistic serviceSubtaskStatistic = new ServiceSubtaskStatistic();
         //鏍煎紡鍖栦负涓や綅灏忔暟鐨勭櫨鍒嗘瘮
         DecimalFormat percentFormat = new DecimalFormat("##.##%");
-        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getDeptname());
-        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getDeptcode());
+        serviceSubtaskStatistic.setDeptname(serviceSubtaskList.get(0).getLeaveldeptname());
+        serviceSubtaskStatistic.setDeptcode(serviceSubtaskList.get(0).getLeaveldeptcode());
         serviceSubtaskStatistic.setLeavehospitaldistrictname(serviceSubtaskList.get(0).getLeavehospitaldistrictname());
         serviceSubtaskStatistic.setLeavehospitaldistrictcode(serviceSubtaskList.get(0).getLeavehospitaldistrictcode());
         serviceSubtaskStatistic.setDrcode(serviceSubtaskList.get(0).getDrcode());
         serviceSubtaskStatistic.setDrname(serviceSubtaskList.get(0).getDrname());
         serviceSubtaskStatistic.setJoyCount(0);
         serviceSubtaskStatistic.setJoyAllCount(0);
-
+        //鍑洪櫌浜烘
+        serviceSubtaskStatistic.setDischargeCount(serviceSubtaskList.size());
         //鑾峰彇闅忚缁熻
         if (subCounts == null) {
             subCounts = new ArrayList<>();
@@ -2846,11 +3801,14 @@
      * 鑾峰彇闅忚缁熻瓒呴摼鎺�
      * dischargeCountInfo
      * followUpNeededAllInfo - sendstate != 4 (1,2,3,5,6,7)
+     * firstDischargeCountInfo - visitCount = 1
+     * againFollowUpPushCountInfo - visitCount = 2 && visitDeptCode = sys.smartor.center.dept锛堥殢璁夸腑蹇冪瀹わ級
      * nonFollowUpInfo - sendstate = 4
      * needFollowUpInfo - visitCount = 1 && sendstate != 4
      * pendingFollowUpInfo - visitCount = 1 && sendstate = 2
      * followUpSuccessInfo - visitCount = 1 && sendstate = 6
      * followUpFailInfo - visitCount = 1 && (sendstate = 5 || sendstate = 7)
+     * timelyFollowUpInfo - sendstate = 6 && finish_state = 1锛堟部鐢ㄨ姹傜殑 visitCount 鍙e緞锛�
      * manualInfo - visitCount = 1 && currentPreachForm = "1"
      * voiceInfo - visitCount = 1 && currentPreachForm = "3"
      * smsInfo - visitCount = 1 && currentPreachForm = "4"
@@ -2879,6 +3837,18 @@
      * taskSituation4Info - taskSituation = 4
      * taskSituation5Info - taskSituation = 5
      * taskSituation6Info - taskSituation = 7
+     * manuallyCalledInfo - isManuallyCalled = 1L
+     * manualCallTimeoutInfo - visitCount = 1 && sendstate = 7 && isManuallyCalled = 0
+     * manualCallTimeoutAgainInfo - visitCount > 1 && sendstate = 7 && isManuallyCalled = 0
+     * pendingFollowUpAllInfo - visitCount = 1 && sendstate in (1,2,3,7)
+     * pendingFollowUpAllAgainInfo - visitCount > 1 && sendstate in (1,2,3,7)
+     * pendingFollowUpWaitSendInfo - visitCount = 1 && sendstate in (1,2)
+     * pendingFollowUpWaitSendAgainInfo - visitCount > 1 && sendstate in (1,2)
+     * pendingFollowUpSentInfo - visitCount = 1 && sendstate in (3,7)
+     * pendingFollowUpSentAgainInfo - visitCount > 1 && sendstate in (3,7)
+     * sentInfo - visitCount = 1 && sendstate = 3
+     * sentAgainInfo - visitCount > 1 && sendstate = 3
+     * suggest7CountAgainInfo - visitCount > 1 && suggest = 7
      * filterCountList - taskSituation = 6 && sendstate = 4
      *
      */
@@ -2886,7 +3856,10 @@
     public List<ServiceSubtask> getSfStatisticsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq) {
         String hyperLinkInfoType = serviceSubtaskCountReq.getHyperLinkInfoType();
         if (StringUtils.isNotEmpty(hyperLinkInfoType)) {
-            applyHyperLinkInfoType(serviceSubtaskCountReq, hyperLinkInfoType);
+            if (!applyHyperLinkInfoType(serviceSubtaskCountReq, hyperLinkInfoType)) {
+                // 涓嬮捇渚濊禆鐨勯厤缃己澶辨椂杩斿洖绌烘槑缁嗭紝閬垮厤閫�鍖栨垚鏃犺繃婊ゆ潯浠剁殑鏌ヨ
+                return new ArrayList<>();
+            }
         }
 
         String followUpCountStyle = serviceSubtaskCountReq.getFollowUpCountStyle();
@@ -2907,15 +3880,40 @@
         return serviceSubtasks;
     }
 
-    private void applyHyperLinkInfoType(ServiceSubtaskCountReq serviceSubtaskCountReq, String hyperLinkInfoType) {
+    private boolean applyHyperLinkInfoType(ServiceSubtaskCountReq serviceSubtaskCountReq, String hyperLinkInfoType) {
+        //鍙婃椂闅忚鏄庣粏闇�瑕佹部鐢ㄨ姹傜殑棣栨/鍐嶆鍙e緞
+        Integer originVisitCount = serviceSubtaskCountReq.getVisitCount();
         serviceSubtaskCountReq.setVisitCount(null);
         serviceSubtaskCountReq.setSendstates(null);
         serviceSubtaskCountReq.setCurrentPreachform(null);
         serviceSubtaskCountReq.setExcep(null);
         serviceSubtaskCountReq.setTaskSituation(null);
+        serviceSubtaskCountReq.setFinishState(null);
+        serviceSubtaskCountReq.setSuggest(null);
 
         switch (hyperLinkInfoType) {
             case "dischargeCountInfo":
+                break;
+            case "firstDischargeCountInfo":
+                //棣栨鍑洪櫌浜烘锛歷isitCount = 1
+                serviceSubtaskCountReq.setVisitCount(1);
+                break;
+            case "againFollowUpPushCountInfo": {
+                //鍐嶆闅忚鎺ㄩ�佷汉鏁帮細visitCount = 2 涓旀墽琛岀瀹や负闅忚涓績绉戝
+                String centerDept = sysConfigService.selectConfigByKey("sys.smartor.center.dept", serviceSubtaskCountReq.getOrgid());
+                if (StringUtils.isBlank(centerDept)) {
+                    log.warn("鑾峰彇鍐嶆闅忚鎺ㄩ�佹槑缁嗗け璐ワ紝鏈厤缃殢璁夸腑蹇冪瀹ゅ弬鏁� sys.smartor.center.dept, orgid={}", serviceSubtaskCountReq.getOrgid());
+                    return false;
+                }
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setVisitDeptCode(centerDept.trim());
+                serviceSubtaskCountReq.setVisitDeptName(null);
+                break;
+            }
+            case "timelyFollowUpInfo":
+                serviceSubtaskCountReq.setVisitCount(originVisitCount);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
+                serviceSubtaskCountReq.setFinishState(1L);
                 break;
             case "followUpNeededAllInfo":
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 5L, 6L, 7L)));
@@ -2951,7 +3949,7 @@
                 serviceSubtaskCountReq.setVisitCount(1);
                 serviceSubtaskCountReq.setCurrentPreachform("4");
                 break;
-            case "wechatInfo":
+            case "weChatInfo":
                 serviceSubtaskCountReq.setVisitCount(1);
                 serviceSubtaskCountReq.setCurrentPreachform("5");
                 break;
@@ -2970,7 +3968,7 @@
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                 serviceSubtaskCountReq.setCurrentPreachform("4");
                 break;
-            case "wechatSuccessInfo":
+            case "weChatSuccessInfo":
                 serviceSubtaskCountReq.setVisitCount(1);
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                 serviceSubtaskCountReq.setCurrentPreachform("5");
@@ -3007,7 +4005,7 @@
                 serviceSubtaskCountReq.setVisitCount(2);
                 serviceSubtaskCountReq.setCurrentPreachform("4");
                 break;
-            case "wechatAgainInfo":
+            case "weChatAgainInfo":
                 serviceSubtaskCountReq.setVisitCount(2);
                 serviceSubtaskCountReq.setCurrentPreachform("5");
                 break;
@@ -3026,7 +4024,7 @@
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                 serviceSubtaskCountReq.setCurrentPreachform("4");
                 break;
-            case "wechatAgainSuccessInfo":
+            case "weChatAgainSuccessInfo":
                 serviceSubtaskCountReq.setVisitCount(2);
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(6L)));
                 serviceSubtaskCountReq.setCurrentPreachform("5");
@@ -3034,6 +4032,11 @@
             case "abnormalAgainInfo":
                 serviceSubtaskCountReq.setVisitCount(2);
                 serviceSubtaskCountReq.setExcep("1");
+                break;
+            case "suggest7CountAgainInfo":
+                // 鍐嶆闅忚鍥為櫌澶嶈瘖澶嶆煡锛歷isitCount > 1 涓� suggest = 7
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSuggest("7");
                 break;
             case "taskSituation1Info":
                 serviceSubtaskCountReq.setTaskSituation(1);
@@ -3057,9 +4060,55 @@
                 serviceSubtaskCountReq.setTaskSituation(6);
                 serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(4L)));
                 break;
+            case "manuallyCalledInfo":
+                serviceSubtaskCountReq.setIsManuallyCalled(1L);
+                break;
+            case "manualCallTimeoutInfo":
+                serviceSubtaskCountReq.setVisitCount(1);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
+                serviceSubtaskCountReq.setIsManuallyCalled(0L);
+                break;
+            case "manualCallTimeoutAgainInfo":
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(7L)));
+                serviceSubtaskCountReq.setIsManuallyCalled(0L);
+                break;
+            case "pendingFollowUpAllInfo":
+                serviceSubtaskCountReq.setVisitCount(1);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
+                break;
+            case "pendingFollowUpAllAgainInfo":
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 7L)));
+                break;
+            case "pendingFollowUpWaitSendInfo":
+                serviceSubtaskCountReq.setVisitCount(1);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
+                break;
+            case "pendingFollowUpWaitSendAgainInfo":
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(1L, 2L)));
+                break;
+            case "pendingFollowUpSentInfo":
+                serviceSubtaskCountReq.setVisitCount(1);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
+                break;
+            case "pendingFollowUpSentAgainInfo":
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Arrays.asList(3L, 7L)));
+                break;
+            case "sentInfo":
+                serviceSubtaskCountReq.setVisitCount(1);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
+                break;
+            case "sentAgainInfo":
+                serviceSubtaskCountReq.setVisitCount(2);
+                serviceSubtaskCountReq.setSendstates(new ArrayList<>(Collections.singletonList(3L)));
+                break;
             default:
                 break;
         }
+        return true;
     }
 
     /**
@@ -3069,72 +4118,186 @@
      * @return
      */
     @Override
-    @Cacheable(value = "getSfStatisticsScript", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()")
+//    @Cacheable(value = "getSfStatisticsScript", key = "T(org.springframework.util.DigestUtils).md5DigestAsHex(#serviceSubtaskCountReq.toString().getBytes())", unless = "#result == null or #result.isEmpty()")
     public List<ServiceSubtaskStatistic> getSfStatisticsScript(ServiceSubtaskCountReq serviceSubtaskCountReq) {
         log.info("getSfStatisticsScript鐨勫叆鍙備负锛歿}", serviceSubtaskCountReq);
         List<ServiceSubtaskStatistic> serviceSubtaskStatistics = new ArrayList<>();
 
-        // 鏍规嵁鏉′欢杩涜鍒嗙粍
-        Map<String, List<ServiceSubtask>> collect = new HashMap<>();
+        // 鏍规嵁鏉′欢鍐冲畾鍒嗙粍缁村害锛氱闄㈢淮搴︽寜 leaveldeptcode / leavehospitaldistrictcode锛堝惈鍘嗗彶 deptcodes 鍏ュ弬锛夛紝
+        // 鍦ㄩ櫌缁村害鎸� deptcode / hospitaldistrictcode
         String groupKey = "drcode";
+        boolean hasDrcode = serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty();
         if (serviceSubtaskCountReq.getLeavehospitaldistrictcodes() != null && !serviceSubtaskCountReq.getLeavehospitaldistrictcodes().isEmpty()) {
-            if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
-                groupKey = "drcode";
-            } else {
-                groupKey = "leavehospitaldistrictcode";
-            }
+            groupKey = hasDrcode ? "drcode" : "leavehospitaldistrictcode";
+        } else if (serviceSubtaskCountReq.getLeaveldeptcodes() != null && !serviceSubtaskCountReq.getLeaveldeptcodes().isEmpty()) {
+            groupKey = hasDrcode ? "drcode" : "leaveldeptcode";
+        } else if (serviceSubtaskCountReq.getHospitaldistrictcodes() != null && !serviceSubtaskCountReq.getHospitaldistrictcodes().isEmpty()) {
+            // 鍦ㄩ櫌婊℃剰搴︼細鎸夊湪闄㈢梾鍖哄垎缁勶紙hospitaldistrictcode锛�
+            groupKey = hasDrcode ? "drcode" : "hospitaldistrictcode";
+        } else if (serviceSubtaskCountReq.getHospitaldeptcodes() != null && !serviceSubtaskCountReq.getHospitaldeptcodes().isEmpty()) {
+            // 鍦ㄩ櫌婊℃剰搴︼細鎸夊湪闄㈢瀹ゅ垎缁勶紙deptcode锛�
+            groupKey = hasDrcode ? "drcode" : "deptcode";
         } else if (serviceSubtaskCountReq.getDeptcodes() != null && !serviceSubtaskCountReq.getDeptcodes().isEmpty()) {
-            if (serviceSubtaskCountReq.getDrcode() != null && !serviceSubtaskCountReq.getDrcode().isEmpty()) {
-                groupKey = "drcode";
-            } else {
-                groupKey = "deptcode";
-            }
+            // 鍘嗗彶鍏ュ弬 deptcodes 琛ㄧず绂婚櫌绉戝
+            groupKey = hasDrcode ? "drcode" : "leaveldeptcode";
+        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 1) {
+            // 鍘嗗彶鍏ュ弬锛堝競涓�绛夎�佸墠绔彧浼� statisticaltype锛夛細1 = 鐥呭尯缁熻
+            groupKey = hasDrcode ? "drcode" : "leavehospitaldistrictcode";
+        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 2) {
+            // 鍘嗗彶鍏ュ弬锛�2 = 绉戝缁熻锛堝師 deptcode 鍗崇幇鍦ㄧ殑绂婚櫌绉戝 leaveldeptcode锛�
+            groupKey = hasDrcode ? "drcode" : "leaveldeptcode";
+        } else if (serviceSubtaskCountReq.getStatisticaltype() != null && serviceSubtaskCountReq.getStatisticaltype() == 3) {
+            // 鐥呯缁熻锛氭寜浠诲姟ID鍒嗙粍锛屾瘡涓换鍔″嵆涓�涓梾绉嶉殢璁夸换鍔�
+            groupKey = "taskid";
         }
         serviceSubtaskCountReq.setGroupKey(groupKey);
+        // 鍒嗙粍鍒椾笌杩囨护鍒椾繚鎸佷竴鑷达細绂婚櫌绉戝 leaveldeptcode銆佸湪闄㈢瀹� deptcode
+        serviceSubtaskCountReq.setGroupColumn(groupKey);
+        /*
+         * 婊℃剰搴︺�岀梾鍖虹粺璁°�嶏紙绂婚櫌缁村害锛夛細鍑洪櫌鐥呭尯涓虹┖鍒欏洖閫�鍒板湪闄㈢梾鍖恒��
+         * 婊℃剰搴﹀瓙浠诲姟锛坰ervice_type 6/14/15/16锛夌殑 leavehospitaldistrictcode 绾︿笁鎴愪负绌猴紝
+         * 涔嬪墠涓轰簡鈥滄煡寰楀埌鏁版嵁鈥濇妸鐥呭尯鏉′欢鍐欐垚 `xxx IS NULL OR xxx IN (...)`锛�
+         * 浜庢槸閫変簡 A 鐥呭尯涔熶細鎶婄┖鐥呭尯鐨勫叏閲忔暟鎹甫杩涙潵锛屽啀缁熶竴褰掑埌 'Unknown' 缁勶紝
+         * 璇ョ粍鐨勭梾鍖哄悕鍙堟槸 MAX(hospitaldistrictname) 闅忎究鎸戠殑 鈥斺�� 琛ㄧ幇灏辨槸
+         * 鈥滈�変簡琛�娑插唴绉戠梾鍖猴紝鍗存煡鍑轰竴琛屾樉绀洪绉戜腑蹇冧笁鐥呭尯鈥濄��
+         * 鐢ㄢ�滄湁鏁堢梾鍖衡�濆垪杩囨护 + 鍒嗙粍鍚庯細绛涢�夌湡姝g敓鏁堬紝绌虹梾鍖虹殑璁板綍涔熷洖鍒板畠鐪熷疄鐨勫湪闄㈢梾鍖恒��
+         */
+        if ("leavehospitaldistrictcode".equals(groupKey)) {
+            String districtColumn = "IFNULL(leavehospitaldistrictcode, hospitaldistrictcode)";
+            serviceSubtaskCountReq.setGroupColumn(districtColumn);
+            serviceSubtaskCountReq.setDistrictColumn(districtColumn);
+        }
+        //鍒嗙粍閿泦鍚堬細杩欎竴姝ヤ繚鎸佷笉鍙橈紙鍒嗛〉鍙傛暟鐓ф棫浣滅敤鍦ㄨ鏌ヨ涓婏級锛屼繚璇佲�滃弬涓庣粺璁$殑鍒嗙粍鈥濅笌鏀归�犲墠涓�鑷�
         List<String> groupKeyList = serviceSubtaskMapper.getSfStatisticsGroupKey(serviceSubtaskCountReq);
+        /*
+         * 鍒嗙粍鍒楀彲鑳戒负 NULL锛氭弧鎰忓害瀛愪换鍔★紙service_type 6/14/15/16锛夌殑 leavehospitaldistrictcode
+         * 澶ч噺涓� NULL锛実etSfStatisticsGroupKey 浼氭妸 NULL 鍘熸牱甯﹀洖鏉ワ紝
+         * 鎷艰繘 SQL 灏卞彉鎴� `AND xxx IN (null)`锛堟亽涓嶆垚绔嬶級锛屾暣寮犵粺璁¤〃鐩存帴绌恒��
+         * 杩欓噷缁熶竴鎴� 'Unknown'锛屼笌鑱氬悎 SQL 鐨� GROUP BY IFNULL(鍒�,'Unknown') 瀵归綈銆�
+         */
+        if (groupKeyList != null) {
+            groupKeyList = groupKeyList.stream()
+                    .map(key -> key == null ? "Unknown" : key)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
         serviceSubtaskCountReq.setGroupKeyList(groupKeyList);
-        List<ServiceSubtask> rawData = serviceSubtaskMapper.getSfStatistics(serviceSubtaskCountReq);
-        switch (groupKey) {
-            case "deptcode":
-                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDeptcode()).orElse("Unknown")));
-                break;
-            case "drcode":
-                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
-                break;
-            case "leavehospitaldistrictcode":
-                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getLeavehospitaldistrictcode()).orElse("Unknown")));
-                break;
-            default:
-                collect = rawData.stream().collect(Collectors.groupingBy(subtask -> Optional.ofNullable(subtask.getDrcode()).orElse("Unknown")));
-                break;
+
+        /*
+         * 缁熻涓嬫帹鍒版暟鎹簱锛氫竴娆� GROUP BY 鍙繑鍥炩�滃垎缁勬暟鈥濊銆�
+         * 鏀归�犲墠鏄� serviceSubtaskMapper.getSfStatistics() 鎶婂懡涓殑鍏ㄩ儴鏄庣粏琛岋紙39 涓瓧娈碉紝
+         * 鍚� continue_content 绛夊ぇ瀛楁锛夋崬鍥� JVM 鍚庨�愯绱姞锛屽嚑鍗冭鏃朵紶杈撳拰閬嶅巻閮藉緢鎱€��
+         * 杩欓噷鐨勮仛鍚堝彛寰勪笌鍘� getStatisticCategory 閫愭潯瀵归綈锛堣 Mapper 涓� getSfStatisticsScriptSum 鐨勬敞閲婏級銆�
+         */
+        List<ServiceSubtaskStatistic> sumStatistics = serviceSubtaskMapper.getSfStatisticsScriptSum(serviceSubtaskCountReq);
+        if (CollectionUtils.isEmpty(sumStatistics)) {
+            return serviceSubtaskStatistics;
         }
-        //鑾峰彇鍙婃椂鐜囩粺璁″悎闆�
-        Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, collect);
-        //涓�娆℃�ф煡璇㈡墍鏈夊垎缁勭殑棰樼洰缁熻锛岄伩鍏嶆瘡涓垎缁勪竴娆QL锛圢+1锛�
-        List<Long> allSubTaskIds = rawData.stream().map(ServiceSubtask::getId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
-        List<ServiceSubtaskCount> subCounts = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(allSubTaskIds)) {
-            serviceSubtaskCountReq.setSubTaskIds(allSubTaskIds);
-            subCounts = serviceSubtaskMapper.getSfStatisticsCategory(serviceSubtaskCountReq);
-        }
-        for (String collectKey : collect.keySet()) {
-            List<ServiceSubtask> serviceSubtaskList = collect.get(collectKey);
-            if (CollectionUtils.isEmpty(serviceSubtaskList)) {
-                continue;
-            }
-            ServiceSubtaskStatistic statistic = getStatisticCategory(serviceSubtaskList, serviceSubtaskCountReq, subCounts);
-            if (ObjectUtils.isNotEmpty(jsRates)) {
-                Map<String, Object> jsRateMap = jsRates.get(collectKey);
-                BigDecimal jsRate = new BigDecimal("0");
-                if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) {
-                    jsRate = new BigDecimal(jsRateMap.get("rate").toString());
+
+        /*
+         * 鐥呯缁熻锛堟寜 taskid 鍒嗙粍锛夋椂琛ラ綈浠诲姟ID銆佷换鍔″悕绉板拰鐤剧梾鐥呯銆�
+         * 鑱氬悎 SQL 宸茶繑鍥� taskid/task_name锛岃繖閲屽啀琛� diag_type銆�
+         */
+        if ("taskid".equals(groupKey)) {
+            Map<Long, String> diagTypeCache = new HashMap<>();
+            for (ServiceSubtaskStatistic statistic : sumStatistics) {
+                Long taskId = statistic.getTaskId();
+                if (taskId == null && StringUtils.isNotEmpty(statistic.getGroupCode())) {
+                    try {
+                        taskId = Long.valueOf(statistic.getGroupCode());
+                    } catch (NumberFormatException ignore) {
+                        // 鍘嗗彶鏁版嵁鍙兘涓嶆槸鏁板�� taskid锛屼繚鎸佸師鍒嗙粍閿嵆鍙�
+                    }
                 }
-                Double jsRateDouble = (jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue();
-                statistic.setRate(jsRateDouble);
+                statistic.setTaskId(taskId);
+                if (StringUtils.isEmpty(statistic.getTaskName())) {
+                    statistic.setTaskName(statistic.getGroupName());
+                }
+                if (taskId != null) {
+                    if (!diagTypeCache.containsKey(taskId)) {
+                        ServiceTask task = serviceTaskService.selectServiceTaskByTaskid(taskId);
+                        diagTypeCache.put(taskId, task == null ? null : task.getDiagType());
+                    }
+                    statistic.setDiagType(diagTypeCache.get(taskId));
+                }
             }
-            if (ObjectUtils.isNotEmpty(statistic)) {
-                serviceSubtaskStatistics.add(statistic);
+        }
+
+        /*
+         * 婊℃剰搴﹂鐩噺锛氫篃鏀规垚鎸夊垎缁勫湪搴撻噷鑱氬悎銆�
+         * 鏀归�犲墠鏄厛鎶婃湰娆″懡涓殑鍏ㄩ儴瀛愪换鍔� id 鐏岃繘 getSfStatisticsCategory 鐨� IN 鍒楄〃鏌ヤ竴閬嶏紝
+         * 鍐嶅湪鍐呭瓨閲屽姣忎釜瀛愪换鍔″仛涓�娆� subCounts.stream().filter(...)锛圢*M 娆℃瘮杈冿級銆�
+         */
+        Map<String, Integer> joyCountMap = new HashMap<>();
+        Map<String, Integer> joyAllCountMap = new HashMap<>();
+        if (StringUtils.isNotEmpty(serviceSubtaskCountReq.getConfigValue())) {
+            List<Map<String, Object>> joySums = serviceSubtaskMapper.getSfStatisticsScriptJoySum(serviceSubtaskCountReq);
+            if (CollectionUtils.isNotEmpty(joySums)) {
+                for (Map<String, Object> joySum : joySums) {
+                    String joyGroupCode = joySum.get("groupCode") == null ? "Unknown" : joySum.get("groupCode").toString();
+                    joyCountMap.put(joyGroupCode, toIntValue(joySum.get("joyCount")));
+                    joyAllCountMap.put(joyGroupCode, toIntValue(joySum.get("joyAllCount")));
+                }
             }
+        }
+
+        //闅忚鐜囷細鍙e緞涓庡師 getStatisticCategory 涓�鑷达紙棣栨鐜囩殑鍒ゆ柇娌跨敤鍘熸潵鐨� needFollowUp銆佸啀娆$巼娌跨敤鍘熸潵鐨勫垎姣嶏級
+        DecimalFormat percentFormat = new DecimalFormat("##.##%");
+        List<String> statisticsGroupKeys = new ArrayList<>();
+        for (ServiceSubtaskStatistic statistic : sumStatistics) {
+            String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode();
+            statistic.setJoyCount(joyCountMap.getOrDefault(statisticGroupCode, 0));
+            statistic.setJoyAllCount(joyAllCountMap.getOrDefault(statisticGroupCode, 0));
+            //棣栨闅忚鐜囷細鍙湁璇ュ垎缁勫瓨鍦ㄩ娆¢殢璁胯褰曟椂鎵嶄細琚祴鍊硷紙涓嶅瓨鍦ㄥ垯鏄� null锛屼笌鏀归�犲墠涓�鑷达級
+            if (statistic.getFirstVisitCount() != null && statistic.getFirstVisitCount() > 0) {
+                if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) {
+                    double rate = (double) (nvlLong(statistic.getFollowUpSuccess()) + nvlLong(statistic.getFollowUpFail())) / statistic.getNeedFollowUp();
+                    statistic.setFollowUpRate(percentFormat.format(rate));
+                } else {
+                    statistic.setFollowUpRate("0.00%");
+                }
+            }
+            //鍐嶆闅忚鐜囷細鍒ゆ柇鏉′欢涓庡師瀹炵幇涓�鑷达紙鍘熸潵鍒ゆ柇鐨勬槸 needFollowUp锛岃繖閲岀収鎼互鍏嶅彛寰勫彉鍖栵級
+            if (statistic.getAgainVisitCount() != null && statistic.getAgainVisitCount() > 0) {
+                if (statistic.getNeedFollowUp() != null && statistic.getNeedFollowUp() > 0) {
+                    double rateAgain = (double) (nvlLong(statistic.getFollowUpSuccessAgain()) + nvlLong(statistic.getFollowUpFailAgain())) / nvlLong(statistic.getNeedFollowUpAgain());
+                    statistic.setFollowUpRateAgain(percentFormat.format(rateAgain));
+                } else {
+                    statistic.setFollowUpRateAgain("0.00%");
+                }
+            }
+            statisticsGroupKeys.add(statisticGroupCode);
+            serviceSubtaskStatistics.add(statistic);
+        }
+
+        /*
+         * 鐥呭尯缁熻锛氳繖涓�琛岀殑鐥呭尯灏辨槸鍒嗙粍閿湰韬紝灞曠ず鍚嶅彇璇ョ粍鑷繁鐨勭梾鍖哄悕銆�
+         * 鑱氬悎 SQL 閲屽師鏉ョ殑 MAX(hospitaldistrictname) 鍦ㄢ�滀竴涓粍娣蜂簡澶氫釜鐥呭尯鈥濇椂浼氭寫鍑�
+         * 涓�涓涓嶇浉骞茬殑鐥呭尯鍚嶏紙閫夎娑插唴绉戠梾鍖烘樉绀烘垚楠ㄧ涓績涓夌梾鍖哄氨鏄繖涔堟潵鐨勶級銆�
+         * 杩欓噷鎶婂垎缁勯敭鍥炲~鍒板湪闄�/绂婚櫌涓ゅ鐥呭尯瀛楁锛屼繚璇佸墠绔笉璁鸿鍝竴鍒楅兘鏄悓涓�涓梾鍖猴紝
+         * 涔熻鈥滄煡鐪嬭鎯呪�濆甫涓婃潵鐨勭梾鍖虹紪鐮佽兘鏌ュ洖鍚屼竴鎵规暟鎹��
+         */
+        if (StringUtils.isNotEmpty(serviceSubtaskCountReq.getDistrictColumn())) {
+            for (ServiceSubtaskStatistic statistic : serviceSubtaskStatistics) {
+                String districtCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode();
+                String districtName = StringUtils.isNotEmpty(statistic.getGroupName()) ? statistic.getGroupName() : districtCode;
+                statistic.setLeavehospitaldistrictcode(districtCode);
+                statistic.setLeavehospitaldistrictname(districtName);
+                statistic.setHospitaldistrictcode(districtCode);
+                statistic.setHospitaldistrictname(districtName);
+            }
+        }
+
+        //鑾峰彇鍙婃椂鐜囩粺璁″悎闆嗭紙涓�娆℃壒閲忔煡璇紝涓嶆寜鍒嗙粍寰幆鏌ワ級
+        Map<String, Map<String, Object>> jsRates = getSsRateForSfStatistics(serviceSubtaskCountReq, statisticsGroupKeys);
+        for (ServiceSubtaskStatistic statistic : serviceSubtaskStatistics) {
+            String statisticGroupCode = statistic.getGroupCode() == null ? "Unknown" : statistic.getGroupCode();
+            Map<String, Object> jsRateMap = jsRates == null ? null : jsRates.get(statisticGroupCode);
+            BigDecimal jsRate = new BigDecimal("0");
+            if (ObjectUtils.isNotEmpty(jsRateMap) && ObjectUtils.isNotEmpty(jsRateMap.get("rate"))) {
+                jsRate = new BigDecimal(jsRateMap.get("rate").toString());
+            }
+            statistic.setRate((jsRate.setScale(4, RoundingMode.HALF_UP)).doubleValue());
         }
 
         //缁熻闅忚棰樼洰鍒嗙被
@@ -3154,7 +4317,9 @@
                     sum = new BigDecimal(serviceSubtaskStatistic.getJoyAllCount());
                 }
                 if (sum.intValue() != 0) {
-                    finishedPercentage = finishedSum.divide(sum, 2, RoundingMode.HALF_UP).doubleValue();
+                    // 鍘熸潵鍙繚鐣� 2 浣嶅皬鏁帮紙23634/26106 浼氳鎴垚 0.91锛夛紝鍓嶇鍐� 脳100
+                    // 灏辨樉绀烘垚 91.00%锛屼笌瀹為檯鐨� 90.53% 宸簡鍗婁釜鐧惧垎鐐广�傝繖閲屼繚鐣� 4 浣嶃��
+                    finishedPercentage = finishedSum.divide(sum, 4, RoundingMode.HALF_UP).doubleValue();
                 }
                 serviceSubtaskStatistic.setJoyAllCount(sum.intValue());
                 serviceSubtaskStatistic.setJoyCount(finishedSum.intValue());
@@ -3199,8 +4364,7 @@
     }
 
     @Override
-    public Map<String, ServiceSubtaskScriptCount> getSfStatisticsScriptDetails(ServiceSubtaskCountReq serviceSubtaskCountReq) {
-        Map<String, ServiceSubtaskScriptCount> result = new HashMap<>();
+    public Map<String, Object> getSfStatisticsScriptDetails(ServiceSubtaskCountReq serviceSubtaskCountReq) {
         List<ServiceSubtaskScriptCount> allCounts = new ArrayList<>();
         List<ServiceSubtaskScriptCount> allItemCounts = new ArrayList<>();
         String type = serviceSubtaskCountReq.getType();
@@ -3224,7 +4388,23 @@
             allItemCounts = serviceSubtaskMapper.getSfStatisticsScriptItemDetails(serviceSubtaskCountReq);
         }
 
-        for (ServiceSubtaskScriptCount count : allCounts) {
+        //杩斿洖缁撴瀯鏄� Map锛屼笖涓嶄紶 type 鏃朵細鎵ц涓ゆ潯 SQL锛孭ageHelper 娌℃硶瀵规暣浣撳垎椤碉紝
+        //鎵�浠ュ湪棰樼洰鍚堝苟瀹屾垚涔嬪悗鎸夐鐩紙scriptid锛夊湪杩欓噷鍒嗛〉锛屼繚璇� total 鍜屾瘡椤垫暟鎹兘鏄鐩淮搴︾殑銆�
+        allCounts.sort(Comparator
+                .comparing(ServiceSubtaskScriptCount::getSubtaskType, Comparator.nullsLast(String::compareTo))
+                .thenComparing(ServiceSubtaskScriptCount::getScriptid, Comparator.nullsLast(Long::compareTo)));
+
+        int pageNum = ObjectUtils.isNotEmpty(serviceSubtaskCountReq.getPageNum()) && serviceSubtaskCountReq.getPageNum() > 0
+                ? serviceSubtaskCountReq.getPageNum() : 1;
+        int pageSize = ObjectUtils.isNotEmpty(serviceSubtaskCountReq.getPageSize()) && serviceSubtaskCountReq.getPageSize() > 0
+                ? serviceSubtaskCountReq.getPageSize() : 10;
+        int total = allCounts.size();
+        int fromIndex = (int) Math.min((long) (pageNum - 1) * pageSize, total);
+        int toIndex = Math.min(fromIndex + pageSize, total);
+        List<ServiceSubtaskScriptCount> pageCounts = allCounts.subList(fromIndex, toIndex);
+
+        Map<String, ServiceSubtaskScriptCount> pageData = new LinkedHashMap<>();
+        for (ServiceSubtaskScriptCount count : pageCounts) {
             Map<String, ServiceSubtaskScriptCount> itemCountMap = new HashMap<>();
             //闂嵎鍔犱笂绌虹櫧閫夐」
             if (ObjectUtils.isNotEmpty(count.getTemplateid()) && ObjectUtils.isNotEmpty(count.getSubtaskType()) && count.getSubtaskType().equals("2")) {
@@ -3255,32 +4435,125 @@
                 }
             }
             allItemCounts.forEach(itemCount -> {
-                if (ObjectUtils.isNotEmpty(count.getScriptid()) && ObjectUtils.isNotEmpty(itemCount.getScriptid())) {
-                    if (count.getScriptid().equals(itemCount.getScriptid())) {
-                        //鏀惧叆缁熻淇℃伅
-                        itemCount.setScriptContent(count.getScriptContent());
-                        itemCount.setScriptType(count.getScriptType());
-                        itemCount.setSubtaskType(count.getSubtaskType());
-                        itemCount.setCompletedQuantity(count.getCompletedQuantity());
-                        itemCount.setAllQuantity(count.getAllQuantity());
-                        itemCount.setCompletedPercentage(count.getCompletedPercentage());
-                        itemCount.setTemplateid(count.getTemplateid());
-                        //璁$畻閫夋嫨姣斾緥
-                        int chosenQuantity = ObjectUtils.isNotEmpty(itemCount.getChosenQuantity()) ? itemCount.getChosenQuantity() : 0;
-                        int completedQuantity = itemCount.getCompletedQuantity();
-                        if (ObjectUtils.isNotEmpty(completedQuantity) && completedQuantity != 0) {
-                            BigDecimal chosenPercentage = (new BigDecimal(chosenQuantity)).divide(new BigDecimal(completedQuantity), 2, RoundingMode.HALF_UP);
-                            itemCount.setChosenPercentage(chosenPercentage.toString());
-                        }
-                        itemCountMap.put(itemCount.getOptionText(), itemCount);
+                //鏄庣粏琛屽凡鎸夆�滈鐩唴瀹�+浠诲姟绫诲瀷鈥濊仛鍚堝苟甯﹀嚭 script_content锛屼笉鑳界敤 scriptid 鍖归厤锛�
+                //鍚屼竴閬撻浼氳澶嶅埗鍒板涓换鍔℃ā鏉匡紝姣忎釜妯℃澘鐨� scriptid 閮戒笉鍚�
+                boolean sameQuestion = ObjectUtils.isNotEmpty(count.getScriptContent())
+                        && ObjectUtils.isNotEmpty(itemCount.getScriptContent())
+                        && count.getScriptContent().equals(itemCount.getScriptContent())
+                        && Objects.equals(count.getSubtaskType(), itemCount.getSubtaskType());
+                if (sameQuestion) {
+                    //鏀惧叆缁熻淇℃伅
+                    itemCount.setScriptContent(count.getScriptContent());
+                    itemCount.setScriptType(count.getScriptType());
+                    itemCount.setSubtaskType(count.getSubtaskType());
+                    itemCount.setCompletedQuantity(count.getCompletedQuantity());
+                    itemCount.setAllQuantity(count.getAllQuantity());
+                    itemCount.setCompletedPercentage(count.getCompletedPercentage());
+                    itemCount.setTemplateid(count.getTemplateid());
+                    //璁$畻閫夋嫨姣斾緥
+                    int chosenQuantity = ObjectUtils.isNotEmpty(itemCount.getChosenQuantity()) ? itemCount.getChosenQuantity() : 0;
+                    int completedQuantity = itemCount.getCompletedQuantity();
+                    if (ObjectUtils.isNotEmpty(completedQuantity) && completedQuantity != 0) {
+                        BigDecimal chosenPercentage = (new BigDecimal(chosenQuantity)).divide(new BigDecimal(completedQuantity), 2, RoundingMode.HALF_UP);
+                        itemCount.setChosenPercentage(chosenPercentage.toString());
                     }
+                    itemCountMap.put(itemCount.getOptionText(), itemCount);
                 }
             });
             List<ServiceSubtaskScriptCount> itemCounts = new ArrayList<>(itemCountMap.values());
             count.setDetails(itemCounts);
-            result.put(count.getScriptid().toString(), count);
+            pageData.put(count.getScriptid().toString(), count);
         }
+        Map<String, Object> result = new HashMap<>();
+        // total 鏄鐩�绘暟锛堝凡鎸夐鐩悎骞讹級锛宺ows/data 鏄綋鍓嶉〉鏁版嵁
+        result.put("total", total);
+        result.put("pageNum", pageNum);
+        result.put("pageSize", pageSize);
+        result.put("rows", new ArrayList<>(pageCounts));
+        result.put("data", pageData);
         return result;
+    }
+
+    /**
+     * 闂閫夐」鏁板瓧涓嬮捇锛氳繑鍥為�夋嫨浜嗘寚瀹氶�夐」鐨� service_subtask 鍒楄〃銆�
+     * <p>
+     * 缁熻鏄庣粏涓殑閫夐」鏁伴噺鏉ヨ嚜 service_subtask_detail 鐨�
+     * IFNULL(matchedtext, asrtext)锛屽洜姝や笅閽诲繀椤讳娇鐢ㄥ悓涓�棰樼洰鐨勭浉鍚岄�夐」杩涜鍖归厤銆�
+     *
+     * @param serviceSubtaskCountReq 涓嬮捇鏉′欢
+     * @return service_subtask 鍒楄〃
+     */
+    @Override
+    public List<ServiceSubtask> getSfStatisticsScriptDetailsHyperlink(ServiceSubtaskCountReq serviceSubtaskCountReq) {
+        log.info("getSfStatisticsScriptDetailsHyperlink鐨勫叆鍙備负锛歿}", serviceSubtaskCountReq);
+        if (serviceSubtaskCountReq == null) {
+            return new ArrayList<>();
+        }
+        // 鍏煎鍓嶇鐩存帴鍥炰紶缁熻鏄庣粏琛屼腑鐨� subtaskType
+        if (StringUtils.isBlank(serviceSubtaskCountReq.getType())
+                && StringUtils.isNotBlank(serviceSubtaskCountReq.getSubtaskType())) {
+            serviceSubtaskCountReq.setType(serviceSubtaskCountReq.getSubtaskType());
+        }
+        resolveScriptContentAndType(serviceSubtaskCountReq);
+        if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())
+                && serviceSubtaskCountReq.getScriptid() == null) {
+            log.warn("getSfStatisticsScriptDetailsHyperlink-棰樼洰scriptid鍜宻criptContent鍧囦负绌猴紝杩斿洖绌哄垪琛�");
+            return new ArrayList<>();
+        }
+        if (serviceSubtaskCountReq.getOptionText() == null) {
+            log.warn("getSfStatisticsScriptDetailsHyperlink-閫夐」optionText涓虹┖锛岃繑鍥炵┖鍒楄〃");
+            return new ArrayList<>();
+        }
+        return serviceSubtaskMapper.getSfStatisticsScriptDetailsHyperlink(serviceSubtaskCountReq);
+    }
+
+    /**
+     * 鏍规嵁 scriptid 琛ラ綈棰樼洰鍐呭鍜屼换鍔$被鍨嬨��
+     * <p>
+     * 缁熻鏄庣粏鎸夆�滈鐩唴瀹� + 浠诲姟绫诲瀷鈥濆悎骞讹紝鍚屼竴閬撻澶嶅埗鍒板涓换鍔℃ā鏉挎椂 scriptid 鍙兘涓嶅悓锛�
+     * 鍥犳浼樺厛鎸夐鐩唴瀹逛笅閽伙紱鍙湁鏌ヤ笉鍒伴鐩唴瀹规椂鎵嶉��鍖栦负 scriptid 绮剧‘鍖归厤銆�
+     */
+    private void resolveScriptContentAndType(ServiceSubtaskCountReq serviceSubtaskCountReq) {
+        if (serviceSubtaskCountReq.getScriptid() == null) {
+            return;
+        }
+        // 宸叉湁棰樼洰鍐呭鍜屼换鍔$被鍨嬫椂鏃犻渶鍐嶆煡妯℃澘闂琛�
+        if (StringUtils.isNotBlank(serviceSubtaskCountReq.getScriptContent())
+                && StringUtils.isNotBlank(serviceSubtaskCountReq.getType())) {
+            return;
+        }
+
+        String type = serviceSubtaskCountReq.getType();
+        if (StringUtils.isBlank(type) || "1".equals(type)) {
+            IvrTaskTemplateScript ivrTaskTemplateScript =
+                    iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid());
+            if (ObjectUtils.isNotEmpty(ivrTaskTemplateScript)) {
+                if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) {
+                    serviceSubtaskCountReq.setType("1");
+                }
+                if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) {
+                    serviceSubtaskCountReq.setScriptContent(ivrTaskTemplateScript.getScriptContent());
+                }
+                return;
+            }
+            // 鏄庣‘鎸囧畾浜嗚闊崇被鍨嬫椂涓嶅啀灏濊瘯闂嵎琛�
+            if (StringUtils.isNotBlank(type)) {
+                return;
+            }
+        }
+
+        if (StringUtils.isBlank(serviceSubtaskCountReq.getType()) || "2".equals(type)) {
+            SvyTaskTemplateScript svyTaskTemplateScript =
+                    svyTaskTemplateScriptService.selectSvyTaskTemplateScriptByID(serviceSubtaskCountReq.getScriptid());
+            if (ObjectUtils.isNotEmpty(svyTaskTemplateScript)) {
+                if (StringUtils.isBlank(serviceSubtaskCountReq.getType())) {
+                    serviceSubtaskCountReq.setType("2");
+                }
+                if (StringUtils.isBlank(serviceSubtaskCountReq.getScriptContent())) {
+                    serviceSubtaskCountReq.setScriptContent(svyTaskTemplateScript.getScriptContent());
+                }
+            }
+        }
     }
 
     /**
@@ -3294,6 +4567,8 @@
 
         // 鍒濆鍖栨暟鍊煎瓧娈�
         total.setDischargeCount(0);
+        total.setFirstDischargeCount(0L);
+        total.setAgainFollowUpPushCount(0L);
         total.setNonFollowUp(0L);
         total.setFilterCount(0L);
         total.setFollowUpNeeded(0L);
@@ -3301,14 +4576,26 @@
         total.setPendingFollowUp(0L);
         total.setFollowUpSuccess(0L);
         total.setFollowUpFail(0L);
+        total.setTimelyFollowUp(0L);
         total.setManual(0L);
         total.setSms(0L);
         total.setWeChat(0L);
         total.setAbnormal(0L);
         total.setNeedFollowUpAgain(0L);
         total.setPendingFollowUpAgain(0L);
+        total.setManualCallTimeout(0L);
+        total.setManualCallTimeoutAgain(0L);
+        total.setPendingFollowUpAll(0L);
+        total.setPendingFollowUpAllAgain(0L);
+        total.setPendingFollowUpWaitSend(0L);
+        total.setPendingFollowUpWaitSendAgain(0L);
+        total.setPendingFollowUpSent(0L);
+        total.setPendingFollowUpSentAgain(0L);
+        total.setSent(0L);
+        total.setSentAgain(0L);
         total.setFollowUpSuccessAgain(0L);
         total.setFollowUpFailAgain(0L);
+        total.setSuggest7CountAgain(0L);
         total.setManualAgain(0L);
         total.setSmsAgain(0L);
         total.setWeChatAgain(0L);
@@ -3325,6 +4612,8 @@
             if (stat.getDischargeCount() != null) {
                 total.setDischargeCount(total.getDischargeCount() + stat.getDischargeCount());
             }
+            total.setFirstDischargeCount(total.getFirstDischargeCount() + (stat.getFirstDischargeCount() != null ? stat.getFirstDischargeCount() : 0L));
+            total.setAgainFollowUpPushCount(total.getAgainFollowUpPushCount() + (stat.getAgainFollowUpPushCount() != null ? stat.getAgainFollowUpPushCount() : 0L));
             total.setNonFollowUp(total.getNonFollowUp() + (stat.getNonFollowUp() != null ? stat.getNonFollowUp() : 0L));
             total.setFilterCount(total.getFilterCount() + (stat.getFilterCount() != null ? stat.getFilterCount() : 0L));
             total.setFollowUpNeeded(total.getFollowUpNeeded() + (stat.getFollowUpNeeded() != null ? stat.getFollowUpNeeded() : 0L));
@@ -3332,14 +4621,26 @@
             total.setPendingFollowUp(total.getPendingFollowUp() + (stat.getPendingFollowUp() != null ? stat.getPendingFollowUp() : 0L));
             total.setFollowUpSuccess(total.getFollowUpSuccess() + (stat.getFollowUpSuccess() != null ? stat.getFollowUpSuccess() : 0L));
             total.setFollowUpFail(total.getFollowUpFail() + (stat.getFollowUpFail() != null ? stat.getFollowUpFail() : 0L));
+            total.setTimelyFollowUp(total.getTimelyFollowUp() + (stat.getTimelyFollowUp() != null ? stat.getTimelyFollowUp() : 0L));
             total.setManual(total.getManual() + (stat.getManual() != null ? stat.getManual() : 0L));
             total.setSms(total.getSms() + (stat.getSms() != null ? stat.getSms() : 0L));
             total.setWeChat(total.getWeChat() + (stat.getWeChat() != null ? stat.getWeChat() : 0L));
             total.setAbnormal(total.getAbnormal() + (stat.getAbnormal() != null ? stat.getAbnormal() : 0L));
             total.setNeedFollowUpAgain(total.getNeedFollowUpAgain() + (stat.getNeedFollowUpAgain() != null ? stat.getNeedFollowUpAgain() : 0L));
             total.setPendingFollowUpAgain(total.getPendingFollowUpAgain() + (stat.getPendingFollowUpAgain() != null ? stat.getPendingFollowUpAgain() : 0L));
+            total.setManualCallTimeout(total.getManualCallTimeout() + (stat.getManualCallTimeout() != null ? stat.getManualCallTimeout() : 0L));
+            total.setManualCallTimeoutAgain(total.getManualCallTimeoutAgain() + (stat.getManualCallTimeoutAgain() != null ? stat.getManualCallTimeoutAgain() : 0L));
+            total.setPendingFollowUpAll(total.getPendingFollowUpAll() + (stat.getPendingFollowUpAll() != null ? stat.getPendingFollowUpAll() : 0L));
+            total.setPendingFollowUpAllAgain(total.getPendingFollowUpAllAgain() + (stat.getPendingFollowUpAllAgain() != null ? stat.getPendingFollowUpAllAgain() : 0L));
+            total.setPendingFollowUpWaitSend(total.getPendingFollowUpWaitSend() + (stat.getPendingFollowUpWaitSend() != null ? stat.getPendingFollowUpWaitSend() : 0L));
+            total.setPendingFollowUpWaitSendAgain(total.getPendingFollowUpWaitSendAgain() + (stat.getPendingFollowUpWaitSendAgain() != null ? stat.getPendingFollowUpWaitSendAgain() : 0L));
+            total.setPendingFollowUpSent(total.getPendingFollowUpSent() + (stat.getPendingFollowUpSent() != null ? stat.getPendingFollowUpSent() : 0L));
+            total.setPendingFollowUpSentAgain(total.getPendingFollowUpSentAgain() + (stat.getPendingFollowUpSentAgain() != null ? stat.getPendingFollowUpSentAgain() : 0L));
+            total.setSent(total.getSent() + (stat.getSent() != null ? stat.getSent() : 0L));
+            total.setSentAgain(total.getSentAgain() + (stat.getSentAgain() != null ? stat.getSentAgain() : 0L));
             total.setFollowUpSuccessAgain(total.getFollowUpSuccessAgain() + (stat.getFollowUpSuccessAgain() != null ? stat.getFollowUpSuccessAgain() : 0L));
             total.setFollowUpFailAgain(total.getFollowUpFailAgain() + (stat.getFollowUpFailAgain() != null ? stat.getFollowUpFailAgain() : 0L));
+            total.setSuggest7CountAgain(total.getSuggest7CountAgain() + (stat.getSuggest7CountAgain() != null ? stat.getSuggest7CountAgain() : 0L));
             total.setManualAgain(total.getManualAgain() + (stat.getManualAgain() != null ? stat.getManualAgain() : 0L));
             total.setSmsAgain(total.getSmsAgain() + (stat.getSmsAgain() != null ? stat.getSmsAgain() : 0L));
             total.setWeChatAgain(total.getWeChatAgain() + (stat.getWeChatAgain() != null ? stat.getWeChatAgain() : 0L));
@@ -3401,7 +4702,7 @@
             diagname.setPhone(subtask.getPhone());
             diagname.setNurseName(subtask.getNurseName());
             diagname.setDrname(subtask.getDrname());
-            diagname.setDeptname(subtask.getDeptname());
+            diagname.setDeptname(subtask.getLeaveldeptname());
             diagname.setLeavehospitaldistrictname(subtask.getLeavehospitaldistrictname());
             diagname.setTemplatename(subtask.getTemplatename());
             diagname.setPreachform(subtask.getPreachform());
@@ -3413,6 +4714,55 @@
 
     public Map<String, Object> getCurrentUserServiceSubtaskCount(ServiceSubtaskEntity entity) {
         return serviceSubtaskMapper.getCurrentUserServiceSubtaskCount(entity);
+    }
+
+    /**
+     * 褰撳墠鐧诲綍浜哄緟澶勭悊闅忚浠诲姟锛屾寜鍑洪櫌骞存湀鍒嗙粍缁熻锛堝嚭闄㈤殢璁裤�侀棬璇婇殢璁垮垎寮�璁℃暟锛�
+     * <p>
+     * 鍙繑鍥炴湁鏁版嵁鐨勬湀浠斤紝鍓嶇鎸夈�屽綋鏈� + 鍓� 3 涓湀銆嶈ˉ榻愶紝娌℃湁鏁版嵁鏄剧ず 0 涓��
+     */
+    @Override
+    public Map<String, Object> getPendingManualTaskMonthStats(ServiceSubtaskEntity entity) {
+        List<Map<String, Object>> rows = serviceSubtaskMapper.countPendingManualTaskByMonth(entity);
+        List<Map<String, Object>> months = new ArrayList<>();
+        long total = 0L;
+        if (rows != null) {
+            for (Map<String, Object> row : rows) {
+                if (row == null || row.get("month") == null) {
+                    continue;
+                }
+                long dischargeCount = parsePendingCount(row.get("dischargeCnt"));
+                long outpatientCount = parsePendingCount(row.get("outpatientCnt"));
+                if (dischargeCount <= 0 && outpatientCount <= 0) {
+                    continue;
+                }
+                Map<String, Object> item = new LinkedHashMap<>();
+                item.put("month", row.get("month").toString());
+                item.put("dischargeCount", dischargeCount);
+                item.put("outpatientCount", outpatientCount);
+                months.add(item);
+                total += dischargeCount + outpatientCount;
+            }
+        }
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("total", total);
+        result.put("months", months);
+        return result;
+    }
+
+    /**
+     * 缁熻缁撴灉閲岀殑鏁伴噺杞� long锛岃В鏋愬け璐ユ寜 0 澶勭悊
+     */
+    private long parsePendingCount(Object value) {
+        if (value == null) {
+            return 0L;
+        }
+        try {
+            return Long.parseLong(value.toString());
+        } catch (NumberFormatException e) {
+            log.warn("缁熻寰呭鐞嗛殢璁夸换鍔″け璐ワ紝鏁伴噺鏃犳硶瑙f瀽锛歿}", value);
+            return 0L;
+        }
     }
 
     @Override
@@ -3482,8 +4832,8 @@
     public void setFailSubTaskDefalutVal(Long failDay) {
         //1.鍏堝幓sys_config涓紝鑾峰彇鍝簺绉戝鎴栫梾鍖洪渶瑕侀粯璁ら棶鍗烽粯璁ゅ��
         IConfigService configService = SpringUtils.getBean(IConfigService.class);
-        String deptConfigValue = taskSysConfigPort.selectConfigValue("hosp.default.dept");
-        String wardConfigValue = taskSysConfigPort.selectConfigValue("hosp.default.ward");
+        String deptConfigValue = sysConfigService.selectConfigByKey("hosp.default.dept");
+        String wardConfigValue = sysConfigService.selectConfigByKey("hosp.default.ward");
         List<String> leaveWard = null;
         List<String> leaveDept = null;
         if (deptConfigValue != null && !deptConfigValue.isEmpty()) {
@@ -3508,7 +4858,6 @@
         //3.濡傛灉鏌ュ嚭鏉ョ殑闆嗗悎涓嶄负绌猴紝鍒欒繘琛屽惊鐜�
         for (ServiceSubtask serviceSubtask : serviceSubtasks) {
             Integer preachformSize = 0;
-            Boolean flag = false;
             ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform();
             serviceSubtaskPreachform.setSubid(serviceSubtask.getId());
             List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = taskPreachformPort.selectServiceSubtaskPreachformList(serviceSubtaskPreachform);
@@ -3516,33 +4865,282 @@
                 preachformSize = serviceSubtaskPreachforms.size();
             if (serviceSubtask.getUpdateTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getUpdateTime()) >= failDay) {
                 //3.1 鍒ゆ柇褰撳墠serviceSubtask鐨剈pdateTime鏄笉鏄负绌猴紝濡傛灉涓嶄负绌猴紝鍒欏垽鏂綋鍓嶆椂闂存槸涓嶆槸涓巙pdateTime宸窛澶т簬绛変簬failDay
-                flag = saveServiceSubtaskDetail(serviceSubtask);
+                log.debug("銆愬け璐ヤ换鍔¢粯璁ゅ�笺�戝瓙浠诲姟 {} 杈惧埌澶辫触澶╂暟(updateTime)", serviceSubtask.getId());
             } else if (serviceSubtask.getVisitTime() != null && DateUtils.differentDaysByMillisecond(new Date(), serviceSubtask.getVisitTime()) >= failDay + preachformSize) {
                 //3.2 濡傛灉褰撳墠serviceSubtask鐨剈pdateTime涓虹┖锛屾牴鎹綋鍓峴erviceSubtask鐨剆ubid鍘绘煡service_subtask_preachform寰楀埌size锛堝鏋渟ize涓虹┖锛屽垯榛樿0锛夛紝鍒欐牴鎹綋鍓嶆椂闂存槸涓嶆槸涓庡綋鍓峴erviceSubtask鐨剉isitTime宸窛澶т簬绛変簬failDay + size
-                flag = saveServiceSubtaskDetail(serviceSubtask);
+                log.debug("銆愬け璐ヤ换鍔¢粯璁ゅ�笺�戝瓙浠诲姟 {} 杈惧埌澶辫触澶╂暟(visitTime+preachformSize)", serviceSubtask.getId());
             } else {
                 //3.3 濡傛灉2.1涓�2.2閮戒负false锛屽垯continue
                 continue;
             }
 
-            //4.濡傛灉flag涓簍rue锛屽垯鏇存柊service_subtask琛ㄤ腑鐨剆endstate涓�6锛宲reachform涓�1
-            if (flag) {
-                serviceSubtask.setSendstate(6L);
-                serviceSubtask.setPreachform("1");
-                serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
+            //4. 鈿狅笍 2026-10-21 鍔犲浐锛氬厛"绠楀畬"鍐�"钀藉簱"锛屽交搴曞幓鎺�"鍏堟彃鍏ャ�佸け璐ュ啀鍒犻櫎"鐨勫啓娉曘��
+            //   鏃ч�昏緫鏄竟鎻掕竟鍒ゆ柇锛屽彧瑕佹湁涓�棰樻病閰嶉粯璁ゅ�硷紝灏辨妸杩欎釜 sub_id 涓�**鎵�鏈�**鏄庣粏锛堝惈鎮h�呯湡瀹炰綔绛斻��
+            //   鐢氳嚦鍒氭彃杩涘幓鐨勯偅鎵癸級缁熺粺缃� del_flag='1' 鈥斺�� nhfy 2026-09-08 閭� 10 浣嶆偅鑰呯殑婊℃剰搴︿綔绛�
+            //   灏辨槸杩欐牱琚垹鎺夈�佸鑷村鍑恒�岄棶棰樿鎯呭崰姣斻��370 鑰屻�岄殢璁夸俊鎭〃鍗曘��380 鐨勩��
+            //   鐜板湪锛氣憼 娓呭崟涓虹┖锛�=鏈夐娌¢厤榛樿鍊硷級鈫� 鏁翠唤璺宠繃锛屼笉鍐欎篃涓嶅垹锛�
+            //         鈶� 娓呭崟榻愬叏 鈫� 缁熶竴钀藉簱锛堝瓨鍦ㄥ垯鏇存柊骞跺娲伙紝涓嶅瓨鍦ㄥ垯鎻掑叆锛夈��
+            List<ServiceSubtaskDetail> defaultDetails = buildDefaultDetails(serviceSubtask);
+            if (CollectionUtils.isEmpty(defaultDetails)) {
+                log.info("銆愬け璐ヤ换鍔¢粯璁ゅ�笺�戜换鍔� {} 瀛愪换鍔� {} 鏈夐鐩湭閰嶇疆榛樿鍊硷紝鏁翠唤璺宠繃锛堜笉鎻掑叆銆佷笉鍒犻櫎浠讳綍鏄庣粏锛�",
+                        serviceSubtask.getTaskid(), serviceSubtask.getId());
+                continue;
+            }
+            persistDefaultDetails(defaultDetails);
+
+            //5.鍏ㄩ儴钀藉簱鎴愬姛鍚庡啀鎶婁换鍔$疆涓哄凡瀹屾垚
+            serviceSubtask.setSendstate(6L);
+            serviceSubtask.setPreachform("1");
+            serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
+            followupCompletedEventPublisher.publish(serviceSubtask.getId());
+        }
+    }
+
+    /**
+     * 鎸夋ā鏉挎瀯閫�"榛樿鍊兼槑缁�"娓呭崟锛�<b>鍙绠椼�佷笉钀藉簱</b>锛�
+     * <p>
+     * 2026-10-21 鏂板锛氭妸鍘熸潵鐨� {@code saveServiceSubtaskDetail}锛堣竟绠楄竟鎻掋�佸け璐ュ啀鍒狅級鎷嗘垚
+     * 銆宐uild锛堟湰鏂规硶锛夈��+銆宲ersist锛坽@link #persistDefaultDetails}锛夈�嶄袱姝ワ紝
+     * 淇濊瘉"榛樿鍊间笉鍏�"鏃�**涓�涓瓧鑺傞兘涓嶄細鍐欍�佹洿涓嶄細鍒�**銆�
+     *
+     * @param serviceSubtask 瀛愪换鍔�
+     * @return 鏄庣粏娓呭崟锛涗换涓�棰樻病閰嶉粯璁ゅ�硷紙鎴栨ā鏉夸负绌猴級鏃惰繑鍥� {@code null}锛岃〃绀烘暣浠戒笉澶勭悊
+     */
+    private List<ServiceSubtaskDetail> buildDefaultDetails(ServiceSubtask serviceSubtask) {
+        if (serviceSubtask.getType() == null) {
+            return null;
+        }
+        List<ServiceSubtaskDetail> details = new ArrayList<>();
+        if (serviceSubtask.getType().equals("1")) {
+            //3.3.1 濡傛灉type鏄� 1锛屽垯鐢ㄥ綋鍓峴erviceSubtask鐨則emplateid鍘籭vr_task_temaplate_script涓幏鍙栭棶棰樺拰閫夐」
+            IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript();
+            ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
+            List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript);
+            if (CollectionUtils.isEmpty(ivrTaskTemplateScripts)) return null;
+            for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) {
+                IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class);
+                IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption();
+                ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId());
+                List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption);
+                ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions);
+                ServiceSubtaskDetail detail = buildSFDetail(ivrTaskTemplateScriptVO, serviceSubtask);
+                if (detail == null) return null;
+                details.add(detail);
+            }
+        } else if (serviceSubtask.getType().equals("2")) {
+            //3.3.2 濡傛灉type鏄� 2锛屽垯鐢ㄥ綋鍓峴erviceSubtask鐨則emplateid鍘籹vy_task_temaplate_script涓幏鍙栭棶棰樺拰閫夐」
+            SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript();
+            svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
+            List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript);
+            if (CollectionUtils.isEmpty(svyTaskTemplateScripts)) return null;
+            for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) {
+                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class);
+                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO);
+                ServiceSubtaskDetail detail = buildWJDetail(svyTaskTemplateScriptVO1, serviceSubtask);
+                if (detail == null) return null;
+                details.add(detail);
+            }
+        } else {
+            return null;
+        }
+        return details;
+    }
+
+    /**
+     * 钀藉簱"榛樿鍊兼槑缁�"锛氬瓨鍦ㄥ垯鏇存柊骞跺娲伙紝涓嶅瓨鍦ㄥ垯鎻掑叆锛�<b>鍙涓嶅垹</b>锛�
+     */
+    private void persistDefaultDetails(List<ServiceSubtaskDetail> details) {
+        for (ServiceSubtaskDetail detail : details) {
+            ServiceSubtaskDetailDTO exist = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(detail.getSubId(), detail.getScriptid());
+            if (ObjectUtils.isNotEmpty(exist)) {
+                detail.setId(exist.getId());
+                detail.setUpdateTime(new Date());
+                serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail);
+                //鍛戒腑鍘嗗彶琚垹鍓湰鏃剁疆鍥� del_flag='0'锛岄伩鍏嶅啀鎻掍竴浠芥柊鍓湰
+                serviceSubtaskDetailMapper.reviveServiceSubtaskDetail(exist.getId());
             } else {
-                //鍒犻櫎璇柊澧瀌etail閲岀殑鏁版嵁
-                ServiceSubtaskDetail serviceSubtaskDetail = new ServiceSubtaskDetail();
-                serviceSubtaskDetail.setSubId(serviceSubtask.getId());
-                List<ServiceSubtaskDetail> serviceSubtaskDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(serviceSubtaskDetail);
-                if (CollectionUtils.isNotEmpty(serviceSubtaskDetails)) {
-                    for (ServiceSubtaskDetail serviceSubtaskDetail1 : serviceSubtaskDetails) {
-                        serviceSubtaskDetail1.setDelFlag("1");
-                        serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail1);
-                    }
-                }
+                serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail);
             }
         }
+    }
+
+    /**
+     * 寰俊鍏紬鍙峰凡棰嗗彇鏈~鍐欙細鎸夐鐩粯璁ら�夐」琛ラ綈褰撳墠瀛愪换鍔★紝骞跺皢褰撳墠瀛愪换鍔″畬鎴愩��
+     * <p>
+     * 鎵�鏈夋煡璇€�佹槑缁嗗啓鍏ャ�佺姸鎬佹洿鏂伴兘鍙娇鐢ㄥ綋鍓� subid锛岄伩鍏嶅奖鍝嶅叾浠栦换鍔�/瀛愪换鍔°��
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean completeWechatClaimedByDefault(ServiceSubtask serviceSubtask) {
+        if (serviceSubtask == null || serviceSubtask.getId() == null) {
+            return false;
+        }
+
+        // 閲嶆柊璇诲彇褰撳墠瀛愪换鍔★紝鎵�鏈夊垽鏂彧閽堝杩欎竴涓� subid
+        ServiceSubtask current = serviceSubtaskMapper.selectServiceSubtaskById(serviceSubtask.getId());
+        if (current == null) {
+            return false;
+        }
+        // 鍙鐞嗭細涓芥按涓尰闄€�侀棶鍗枫�佸綋鍓嶆柟寮忓叕浼楀彿銆佸凡棰嗗彇銆佹湭鎻愪氦
+        // 鍏朵粬鍖婚櫌 orgid 涓嶅尮閰嶆椂鐩存帴杩斿洖 false锛岀户缁蛋鍘熻ˉ鍋块�昏緫銆�
+        if (!IServiceSubtaskService.WECHAT_DEFAULT_COMPLETE_ORGID.equals(current.getOrgid())
+                || !"2".equals(current.getType())
+                || !"5".equals(current.getCurrentPreachform())
+                || !Long.valueOf(1L).equals(current.getSendstate())
+                || Long.valueOf(1L).equals(current.getSubmit())) {
+            return false;
+        }
+
+        // 鏌ヨ褰撳墠瀛愪换鍔¤嚜宸辩殑鍏紬鍙峰彂閫佹柟寮忥紝蹇呴』鏄凡鍙戦��(2)鎴栧凡棰嗗彇(3)
+        ServiceSubtaskPreachform query = new ServiceSubtaskPreachform();
+        query.setSubid(current.getId());
+        query.setTaskid(current.getTaskid());
+        query.setPreachform("5");
+        List<ServiceSubtaskPreachform> preachforms = taskPreachformPort.selectServiceSubtaskPreachformList(query);
+        if (CollectionUtils.isEmpty(preachforms)) {
+            return false;
+        }
+        ServiceSubtaskPreachform currentPreachform = preachforms.stream()
+                .filter(item -> "2".equals(item.getSendstate()) || "3".equals(item.getSendstate()))
+                .findFirst().orElse(null);
+        if (currentPreachform == null) {
+            return false;
+        }
+
+        // 鍙粰褰撳墠 subid 琛ラ綈榛樿閫夐」锛涜嚦灏戞湁涓�棰橀粯璁ら�夐」鎵嶇户缁�
+        int filled = fillWechatDefaultDetailForCurrentSubtask(current);
+        if (filled <= 0) {
+            log.info("銆愬井淇″叕浼楀彿榛樿瀹屾垚銆憇ubid锛歿} 娌℃湁鍙~鍏呯殑榛樿閫夐」锛岃烦杩�", current.getId());
+            return false;
+        }
+
+        // 褰撳墠鍏紬鍙峰彂閫佹柟寮忥細宸插彂閫�/宸查鍙� -> 瀹屾垚(9)
+        ServiceSubtaskPreachform updatePreachform = new ServiceSubtaskPreachform();
+        updatePreachform.setId(currentPreachform.getId());
+        updatePreachform.setSubid(current.getId());
+        updatePreachform.setPreachform("5");
+        updatePreachform.setOrgid(current.getOrgid());
+        updatePreachform.setRemark("宸查鍙栨湭濉啓锛屾寜榛樿閫夐」瀹屾垚");
+        int preachformUpdated = taskPreachformPort.updateWechatPreachformComplete(updatePreachform);
+        if (preachformUpdated == 0) {
+            throw new BaseException("寰俊鍏紬鍙烽粯璁ゅ畬鎴愬け璐ワ細鍙戦�佹柟寮忕姸鎬佸凡鍙樺寲锛宻ubid=" + current.getId());
+        }
+
+        // 褰撳墠瀛愪换鍔★細宸查鍙�(1) -> 瀹屾垚(6)锛屽彧鏇存柊杩欎竴涓� subid
+        ServiceSubtask complete = new ServiceSubtask();
+        complete.setId(current.getId());
+        complete.setOrgid(current.getOrgid());
+        complete.setFinishtime(new Date());
+        complete.setResult("success");
+        complete.setRemark("宸查鍙栨湭濉啓锛屾寜榛樿閫夐」瀹屾垚");
+        int subtaskUpdated = serviceSubtaskMapper.updateWechatDefaultComplete(complete);
+        if (subtaskUpdated == 0) {
+            throw new BaseException("寰俊鍏紬鍙烽粯璁ゅ畬鎴愬け璐ワ細瀛愪换鍔$姸鎬佸凡鍙樺寲锛宻ubid=" + current.getId());
+        }
+        followupCompletedEventPublisher.publish(current.getId());
+
+        log.info("銆愬井淇″叕浼楀彿榛樿瀹屾垚銆憇ubid锛歿}锛屽凡鎸夐粯璁ら�夐」瀹屾垚锛屽~鍏呴鐩暟锛歿}", current.getId(), filled);
+        return true;
+    }
+
+    /**
+     * 浠呯粰褰撳墠瀛愪换鍔¤ˉ榻愰棶鍗峰崟閫�/澶氶�夐涓� default_value=2 鐨勯�夐」銆�
+     * 宸叉湁闈炵┖绛旀鐨勯鐩烦杩囷紝涓嶈鐩栨偅鑰呭凡濉啓鍐呭銆�
+     *
+     * @return 瀹為檯濉厖锛堟垨姝ゅ墠宸茬敱绯荤粺榛樿濉厖锛夌殑棰樼洰鏁�
+     */
+    private int fillWechatDefaultDetailForCurrentSubtask(ServiceSubtask serviceSubtask) {
+        Long templateId = serviceSubtask.getTemplateid();
+        ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
+        if (templateId == null && serviceTask != null) {
+            templateId = serviceTask.getTemplateid();
+        }
+        if (templateId == null) {
+            return 0;
+        }
+
+        SvyTaskTemplateScript scriptQuery = new SvyTaskTemplateScript();
+        scriptQuery.setTemplateID(templateId);
+        List<SvyTaskTemplateScript> scripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(scriptQuery);
+        if (CollectionUtils.isEmpty(scripts)) {
+            return 0;
+        }
+
+        int filled = 0;
+        for (SvyTaskTemplateScript script : scripts) {
+            SvyTaskTemplateScriptVO scriptVO = DtoConversionUtils.sourceToTarget(script, SvyTaskTemplateScriptVO.class);
+            SvyTaskTemplateScriptVO scriptWithOptions = svyTaskTemplateScriptService.selectInfoByCondition(scriptVO);
+            if (scriptWithOptions == null
+                    || scriptWithOptions.getId() == null
+                    || CollectionUtils.isEmpty(scriptWithOptions.getSvyTaskTemplateTargetoptions())
+                    || (!"1".equals(scriptWithOptions.getScriptType()) && !"2".equals(scriptWithOptions.getScriptType()))) {
+                continue;
+            }
+
+            List<SvyTaskTemplateTargetoption> options = scriptWithOptions.getSvyTaskTemplateTargetoptions();
+            List<SvyTaskTemplateTargetoption> defaultOptions = options.stream()
+                    .filter(option -> option.getDefaultValue() != null && option.getDefaultValue() == 2L)
+                    .filter(option -> StringUtils.isNotBlank(option.getOptioncontent()))
+                    .collect(Collectors.toList());
+            if (CollectionUtils.isEmpty(defaultOptions)) {
+                continue;
+            }
+
+            // 宸叉湁闈炵┖绛旀鍒欎笉瑕嗙洊锛岄伩鍏嶅奖鍝嶆偅鑰呭凡濉啓鍐呭
+            ServiceSubtaskDetail detailQuery = new ServiceSubtaskDetail();
+            detailQuery.setSubId(serviceSubtask.getId());
+            detailQuery.setScriptid(scriptWithOptions.getId());
+            List<ServiceSubtaskDetail> existingDetails = serviceSubtaskDetailMapper.selectServiceSubtaskDetailList(detailQuery);
+            ServiceSubtaskDetail existing = CollectionUtils.isNotEmpty(existingDetails) ? existingDetails.get(0) : null;
+            if (existing != null
+                    && (StringUtils.isNotBlank(existing.getAsrtext()) || StringUtils.isNotBlank(existing.getMatchedtext()))) {
+                // 涔嬪墠宸茬敱绯荤粺榛樿濉厖杩囩殑锛岃鍏ュ凡濉厖锛屼繚璇侀噸璇曞箓绛�
+                if ("DEFAULT".equals(existing.getOperate())) {
+                    filled++;
+                }
+                continue;
+            }
+
+            String targetValue = options.stream()
+                    .map(SvyTaskTemplateTargetoption::getOptioncontent)
+                    .filter(StringUtils::isNotBlank)
+                    .collect(Collectors.joining("&"));
+            String defaultValue = defaultOptions.stream()
+                    .map(SvyTaskTemplateTargetoption::getOptioncontent)
+                    .collect(Collectors.joining("&"));
+
+            ServiceSubtaskDetail detail = new ServiceSubtaskDetail();
+            detail.setSubId(serviceSubtask.getId());
+            detail.setTaskid(serviceSubtask.getTaskid());
+            detail.setPatid(serviceSubtask.getPatid());
+            detail.setScriptid(scriptWithOptions.getId());
+            detail.setTemplatequestionnum(scriptWithOptions.getId());
+            detail.setQuestiontext(scriptWithOptions.getScriptContent());
+            detail.setCategoryid(scriptWithOptions.getCategoryid());
+            detail.setTargetid(scriptWithOptions.getTargetid() != null ? scriptWithOptions.getTargetid().intValue() : null);
+            detail.setValueType(scriptWithOptions.getScriptType());
+            detail.setTargetvalue(targetValue);
+            detail.setAsrtext(defaultValue);
+            detail.setMatchedtext(defaultValue);
+            detail.setOperate("DEFAULT");
+            detail.setTemplateType(2);
+            detail.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null);
+            detail.setTemplateid(templateId.toString());
+            detail.setGuid(scriptWithOptions.getGuid());
+            detail.setOrgid(StringUtils.isNotEmpty(scriptWithOptions.getOrgid()) ? scriptWithOptions.getOrgid()
+                    : (serviceTask != null ? serviceTask.getOrgid() : null));
+            detail.setCreateTime(new Date());
+            detail.setUpdateTime(new Date());
+
+            if (existing != null) {
+                detail.setId(existing.getId());
+                serviceSubtaskDetailMapper.updateServiceSubtaskDetail(detail);
+            } else {
+                serviceSubtaskDetailMapper.insertServiceSubtaskDetail(detail);
+            }
+            filled++;
+        }
+        return filled;
     }
 
     @Override
@@ -3613,12 +5211,6 @@
         String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
         String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
 
-        //涓芥按涓尰闄㈠彂閫佺殑鏄井淇″叕浼楀彿
-        if (StringUtils.isNotEmpty(serviceSubtask.getOrgid()) && serviceSubtask.getOrgid().equals("47231022633110211A2101")) {
-            resultMap = wechatSubTask(subid);
-            return resultMap;
-        }
-
         //鍙戦�佹暟鎹殑灏佽
         ServiceOutPath serviceOutPath = new ServiceOutPath();
         serviceOutPath.setParam1(taskId);
@@ -3628,7 +5220,7 @@
         serviceOutPath.setCreateTime(new Date());
         serviceOutPath.setOrgid(serviceSubtask.getOrgid());
         iServiceOutPathService.insertServiceOutPath(serviceOutPath);
-        String format = String.format("%03X", serviceOutPath.getId());
+        String format = ShortCode.encode(serviceOutPath.getId());
         String url = "";
         SendMagParam sendMagParam = new SendMagParam();
         sendMagParam.setType("4");
@@ -3643,8 +5235,8 @@
 
         // 鍙傛暟鍊肩粡绔彛鍙栫敤锛堝師涓� sysConfigMapper.checkConfigKeyUnique + getConfigValue锛夈��
         // 璇箟绛変环锛氬師鍒ゅ畾"琛屽瓨鍦�"銆佽繖閲屽垽瀹�"鍊奸潪 null"锛岃�� sys_config.config_value 鏄�
-        // varchar(500) NOT NULL DEFAULT '' 鈬� 浜岃�呬笉鍙兘涓嶅悓锛堣瑙� TaskSysConfigPort javadoc锛夈��
-        String smsContemt = taskSysConfigPort.findConfigValueByKey("wj.sms.template");
+        // varchar(500) NOT NULL DEFAULT '' 鈬� 浜岃�呬笉鍙兘涓嶅悓锛堣瑙� ISysConfigService javadoc锛夈��
+        String smsContemt = sysConfigService.selectConfigByKey("wj.sms.template");
 
         if (smsContemt != null) {
             sendMagParam.setContent(smsContemt.replace("${wjurl}", sendMagParam.getUrl()));
@@ -3663,6 +5255,25 @@
                 serviceSubtask.setCurrentPreachform("4");
             }
         }
+        return resultMap;
+    }
+
+    @Override
+    public Map<String, Object> weChatSubTask(Long subid) {
+        log.info("smsSubTask鐨勫叆鍙備负锛歿}", subid);
+        Map<String, Object> resultMap = new HashMap<>();
+        resultMap.put("code", "500");
+        resultMap.put("msg", "鍙戦�佸け璐�");
+
+        //鑾峰彇鎮h�呯殑鍩烘湰淇℃伅
+        ServiceSubtask serviceSubtask = selectServiceSubtaskById(subid);
+        RSAPublicKeyExample rsaPublicKeyExample = new RSAPublicKeyExample();
+        String patid = rsaPublicKeyExample.encryptedData(serviceSubtask.getPatid().toString(), pub_key);
+        String subId = rsaPublicKeyExample.encryptedData(serviceSubtask.getId().toString(), pub_key);
+        String taskId = rsaPublicKeyExample.encryptedData(serviceSubtask.getTaskid().toString(), pub_key);
+
+        //涓芥按涓尰闄㈠彂閫佺殑鏄井淇″叕浼楀彿
+        resultMap = wechatSubTask(subid);
         return resultMap;
     }
 
@@ -3693,14 +5304,22 @@
             serviceOutPath.setCreateTime(new Date());
             serviceOutPath.setOrgid(serviceSubtask.getOrgid());
             iServiceOutPathService.insertServiceOutPath(serviceOutPath);
-            String format = String.format("%03X", serviceOutPath.getId());
+            String format = ShortCode.encode(serviceOutPath.getId());
             serviceOutPath.setRadix(format);
             serviceOutPath.setUpdateTime(new Date());
             serviceOutPath.setUrl(url);
             iServiceOutPathService.updateServiceOutPath(serviceOutPath);
 
-            //his鎮h�卛d鏍¢獙锛堜笌鍘熷疄鐜颁竴鑷达細鍏堟牎楠岃繖涓�椤癸級
-            if (StringUtils.isEmpty(serviceSubtask.getPatidHis())) {
+
+            if (patArchive == null) {
+                resultMap.put("code", -1);
+                resultMap.put("message", "鍙戦�佸紓甯革細" + "銆愬叕浼楀彿銆戞湭鏌ヨ鍒版偅鑰呮。妗堜俊鎭紝鏃犳硶閫氳繃鍏紬鍙峰彂閫�");
+                return resultMap;
+            }
+
+            // his绯荤粺鐨勬偅鑰卛d鍙栬嚜鎮h�呮。妗坧at_archive.patid_his锛�
+            // service_subtask鏌ヨ鏈叧鑱旇瀛楁锛坰erviceSubtask.getPatidHis()鎭掍负绌猴級锛屼笉鑳界敤浜庢牎楠�
+            if (StringUtils.isEmpty(patArchive.getPatidHis())) {
                 resultMap.put("code", -1);
                 resultMap.put("message", "鍙戦�佸紓甯革細" + "銆愬叕浼楀彿銆戝叕浼楀彿鍙戦�佸け璐�,his绯荤粺鐨勬偅鑰卛d涓虹┖");
                 return resultMap;
@@ -3740,44 +5359,30 @@
         return resultMap;
     }
 
-    private Boolean saveServiceSubtaskDetail(ServiceSubtask serviceSubtask) {
-        Boolean result = true;
-        //3.4 濡傛灉3.1鎴�3.2涓簍rue鐨勮瘽锛屾牴鎹畉ype鍒ゆ柇浠�涔堢被鍨�
-        if (serviceSubtask.getType().equals("1")) {
-            //3.3.1    濡傛灉type鏄� 1 锛屽垯鐢ㄥ綋鍓峴erviceSubtask鐨則emplateid鍘籭vr_task_temaplate_script涓幏鍙栭棶棰樺拰閫夐」锛屽皢闂鍜岄�夐」鐨勯粯璁ゅ�兼斁鍒皊ervice_detail涓�
-            IvrTaskTemplateScript ivrTaskTemplateScript = new IvrTaskTemplateScript();
-            ivrTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
-            List<IvrTaskTemplateScript> ivrTaskTemplateScripts = iIvrTaskTemplateScriptService.selectIvrTaskTemplateScriptList(ivrTaskTemplateScript);
-            for (IvrTaskTemplateScript its : ivrTaskTemplateScripts) {
-                IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(its, IvrTaskTemplateScriptVO.class);
-                IvrTaskTemplateTargetoption ivrTaskTemplateTargetoption = new IvrTaskTemplateTargetoption();
-                ivrTaskTemplateTargetoption.setScriptid(ivrTaskTemplateScriptVO.getId());
-                List<IvrTaskTemplateTargetoption> ivrTaskTemplateTargetoptions = iIvrTaskTemplateTargetoptionService.selectIvrTaskTemplateTargetoptionList(ivrTaskTemplateTargetoption);
-                ivrTaskTemplateScriptVO.setIvrTaskScriptTargetoptionList(ivrTaskTemplateTargetoptions);
-                Integer integer = setSFDetailInfo(ivrTaskTemplateScriptVO, serviceSubtask);
-                if (Objects.isNull(integer) || integer == 0) result = false;
-            }
+    /*
+     * 2026-10-21 宸插垹闄� saveServiceSubtaskDetail(...)銆�
+     * 瀹冨師鏉ョ殑璇箟鏄�"杈圭畻榛樿鍊艰竟钀藉簱锛屾渶鍚庤繑鍥炲竷灏斿��"锛岃皟鐢ㄦ柟鍦ㄨ繑鍥� false 鏃舵妸璇� sub_id 涓嬬殑
+     * 鍏ㄩ儴鏄庣粏缃� del_flag='1'锛堣繛鎮h�呯湡瀹炰綔绛斾竴璧峰垹锛夈��
+     * 鐜板湪鎷嗘垚 buildDefaultDetails(...)锛堝彧绠椾笉鍐欙級 + persistDefaultDetails(...)锛堝彧澧炰笉鍒狅級锛�
+     * 浠讳綍涓�棰樼己榛樿鍊煎氨鏁翠唤璺宠繃锛屼笉浼氬啀浜х敓"鍒犻櫎"鍔ㄤ綔銆�
+     */
 
-        } else if (serviceSubtask.getType().equals("2")) {
-            //3.3.2    濡傛灉type鏄� 2,鍒欑敤褰撳墠serviceSubtask鐨則emplateid鍘籹vy_task_temaplate_script涓幏鍙栭棶棰樺拰閫夐」锛屽皢闂鍜岄�夐」鐨勯粯璁ゅ�兼斁鍒皊ervice_detail涓�
-            SvyTaskTemplateScript svyTaskTemplateScript = new SvyTaskTemplateScript();
-            svyTaskTemplateScript.setTemplateID(serviceSubtask.getTemplateid());
-            List<SvyTaskTemplateScript> svyTaskTemplateScripts = svyTaskTemplateScriptService.selectSvyTaskTemplateScriptList(svyTaskTemplateScript);
-            for (SvyTaskTemplateScript taskTemplateScript : svyTaskTemplateScripts) {
-                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO = DtoConversionUtils.sourceToTarget(taskTemplateScript, SvyTaskTemplateScriptVO.class);
-                SvyTaskTemplateScriptVO svyTaskTemplateScriptVO1 = svyTaskTemplateScriptService.selectInfoByCondition(svyTaskTemplateScriptVO);
-                Integer integer = setWJDetailInfo(svyTaskTemplateScriptVO1, serviceSubtask);
-                if (Objects.isNull(integer) || integer == 0) result = false;
-            }
+    /**
+     * 鎸夈�岄棶鍗锋ā鏉裤�嶆瀯閫犱竴鏉�"榛樿鍊兼槑缁�"锛�<b>鍙绠椼�佷笉钀藉簱</b>锛�
+     * <p>
+     * 2026-10-21 鐢� {@code setWJDetailInfo}锛堢畻瀹屽氨鐩存帴 insert/update锛屽け璐ヨ繑鍥� 0锛夋敼閫犺�屾潵锛�
+     * 鐜板湪鍙礋璐f妸瀹炰綋鎷煎嚭鏉ワ紝鐢� {@link #persistDefaultDetails} 缁熶竴钀藉簱銆�
+     *
+     * @return 鏄庣粏锛涜棰樻病閰嶉粯璁ゅ�硷紙鍗�/澶氶�夐涓�涓粯璁ら�夐」閮芥病鏈夛級鏃惰繑鍥� {@code null}锛岃〃绀烘暣浠介棶鍗蜂笉澶勭悊
+     */
+    private ServiceSubtaskDetail buildWJDetail(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
+        if (svyTaskTemplateScriptVO == null) {
+            return null;
         }
-        return result;
-    }
-
-    private Integer setWJDetailInfo(SvyTaskTemplateScriptVO svyTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
-        log.info("-----setWJDetailInfo鐨勫叆鍙備负锛歿}", svyTaskTemplateScriptVO);
+        log.info("-----buildWJDetail鐨勫叆鍙備负锛歿}", svyTaskTemplateScriptVO);
 
         ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO();
-        if (svyTaskTemplateScriptVO.getScriptType().equals("1") || svyTaskTemplateScriptVO.getScriptType().equals("2")) {
+        if ("1".equals(svyTaskTemplateScriptVO.getScriptType()) || "2".equals(svyTaskTemplateScriptVO.getScriptType())) {
             //1銆�2涓哄崟閫夋垨澶氶��
             for (SvyTaskTemplateTargetoption svyTaskTemplateTargetoption : svyTaskTemplateScriptVO.getSvyTaskTemplateTargetoptions()) {
                 //灏嗛棶棰橀�夐」璁板綍涓嬫潵
@@ -3792,9 +5397,9 @@
                 }
 
             }
-            //濡傛灉asrtext鍜宮atchedtext閮戒负绌猴紝鍒欐病鏈夐粯璁ゅ�硷紝鍒欎笉杩涜澶勭悊
+            //濡傛灉asrtext鍜宮atchedtext閮戒负绌猴紝鍒欐病鏈夐粯璁ゅ�硷紝鍒欎笉杩涜澶勭悊锛堣繑鍥� null = 鏁翠唤闂嵎璺宠繃锛�
             if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) {
-                return 0;
+                return null;
             }
         }
         ServiceTask serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceSubtask.getTaskid());
@@ -3817,21 +5422,16 @@
         }
         serviceSubtaskDetailVO.setSubId(serviceSubtask.getId());
         serviceSubtaskDetailVO.setTemplatequestionnum(svyTaskTemplateScriptVO.getId());
-        ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
-
-        //鍏堢敤subid鍜宻criptid鏌ヨ涓�涓嬫槸鍚﹀瓨鍦紝濡傛灉瀛樺湪鍒欐洿鏂帮紝涓嶅瓨鍦ㄥ垯鎻掑叆
-        ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid());
-        if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) {
-            serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId());
-            serviceSubtaskDetail.setUpdateTime(new Date());
-            return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail);
-        } else {
-            return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail);
-        }
+        return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
     }
 
-    private Integer setSFDetailInfo(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
-
+    /**
+     * 鎸夈�岀數璇濇ā鏉裤�嶆瀯閫犱竴鏉�"榛樿鍊兼槑缁�"锛�<b>鍙绠椼�佷笉钀藉簱</b>锛夛紝璇箟鍚� {@link #buildWJDetail}
+     */
+    private ServiceSubtaskDetail buildSFDetail(IvrTaskTemplateScriptVO ivrTaskTemplateScriptVO, ServiceSubtask serviceSubtask) {
+        if (ivrTaskTemplateScriptVO == null) {
+            return null;
+        }
         ServiceSubtaskDetailVO serviceSubtaskDetailVO = new ServiceSubtaskDetailVO();
         if (ivrTaskTemplateScriptVO.getScriptType() != null && (ivrTaskTemplateScriptVO.getScriptType().equals("1") || ivrTaskTemplateScriptVO.getScriptType().equals("2"))) {
             //1銆�2涓哄崟閫夋垨澶氶��
@@ -3848,10 +5448,12 @@
                     serviceSubtaskDetailVO.setMatchedtext(StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext()) ? ivrTaskTemplateTargetoption.getTargetvalue() : serviceSubtaskDetailVO.getMatchedtext() + "&" + ivrTaskTemplateTargetoption.getTargetvalue());
                 }
             }
-            //濡傛灉asrtext鍜宮atchedtext閮戒负绌猴紝鍒欐病鏈夐粯璁ゅ�硷紝涓嶈繘琛屽鐞�
+            //濡傛灉asrtext鍜宮atchedtext閮戒负绌猴紝鍒欐病鏈夐粯璁ゅ�硷紝涓嶈繘琛屽鐞嗭紙杩斿洖 null = 鏁翠唤闂嵎璺宠繃锛�
             if (StringUtils.isEmpty(serviceSubtaskDetailVO.getAsrtext()) && StringUtils.isEmpty(serviceSubtaskDetailVO.getMatchedtext())) {
-                return 0;
+                return null;
             }
+        } else if (ivrTaskTemplateScriptVO.getScriptType() == null) {
+            return null;
         }
 
         serviceSubtaskDetailVO.setTargetid(ivrTaskTemplateScriptVO.getTargetid() != null ? ivrTaskTemplateScriptVO.getTargetid().intValue() : null);
@@ -3871,17 +5473,9 @@
         serviceSubtaskDetailVO.setTemplateType(1);
         serviceSubtaskDetailVO.setTemplateid(ivrTaskTemplateScriptVO.getTemplateID() == null ? null : ivrTaskTemplateScriptVO.getTemplateID().toString());
         serviceSubtaskDetailVO.setLibTemplateid(serviceSubtask.getLibtemplateid() != null ? serviceSubtask.getLibtemplateid().intValue() : null);
-        ServiceSubtaskDetail serviceSubtaskDetail = DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
-        //鍏堢敤subid鍜宻criptid鏌ヨ涓�涓嬫槸鍚﹀瓨鍦紝濡傛灉瀛樺湪鍒欐洿鏂帮紝涓嶅瓨鍦ㄥ垯鎻掑叆
-        ServiceSubtaskDetailDTO existServiceSubtaskDetail = serviceSubtaskDetailMapper.isExistServiceSubtaskDetail(serviceSubtaskDetailVO.getSubId(), serviceSubtaskDetailVO.getScriptid());
-        if (ObjectUtils.isNotEmpty(existServiceSubtaskDetail)) {
-            serviceSubtaskDetail.setId(existServiceSubtaskDetail.getId());
-            serviceSubtaskDetail.setUpdateTime(new Date());
-            return serviceSubtaskDetailMapper.updateServiceSubtaskDetail(serviceSubtaskDetail);
-        } else {
-            return serviceSubtaskDetailMapper.insertServiceSubtaskDetail(serviceSubtaskDetail);
-        }
+        return DtoConversionUtils.sourceToTarget(serviceSubtaskDetailVO, ServiceSubtaskDetail.class);
     }
+
 
     public String scanGenerateSubtask(ServiceTask serviceTaskVo) {
         String content = "";
@@ -3925,7 +5519,7 @@
                         serviceOutPath.setCreateTime(new Date());
                         serviceOutPath.setOrgid(serviceTask.getOrgid());
                         iServiceOutPathService.insertServiceOutPath(serviceOutPath);
-                        String format = String.format("%03X", serviceOutPath.getId());
+                        String format = ShortCode.encode(serviceOutPath.getId());
                         serviceOutPath.setRadix(format);
                         serviceOutPath.setUpdateTime(new Date());
                         String url = StringUtils.isNotEmpty(req_path) ? ip + ":" + req_path + "/wt?p=" + format : ip + "/wt?p=" + format;
@@ -3953,8 +5547,8 @@
 //        serviceSubtask.setDrcode(patMedInhosp1.getDrcode());
 //        serviceSubtask.setDrname(patMedInhosp1.getDrname());
         if (StringUtils.isNotEmpty(serviceTaskdept.getDeptType()) && serviceTaskdept.getDeptType().equals("1")) {
-            serviceSubtask.setDeptcode(serviceTaskdept.getDeptCode());
-            serviceSubtask.setDeptname(serviceTaskdept.getDeptName());
+            serviceSubtask.setLeaveldeptcode(serviceTaskdept.getDeptCode());
+            serviceSubtask.setLeaveldeptname(serviceTaskdept.getDeptName());
         } else {
             serviceSubtask.setLeavehospitaldistrictcode(serviceTaskdept.getDeptCode());
             serviceSubtask.setLeavehospitaldistrictname(serviceTaskdept.getDeptName());
@@ -3990,7 +5584,7 @@
         // 杩欓噷鐢ㄤ换鍔¢厤缃殑鐥呭尯锛堝叾娆$瀹わ級鍙嶆煡 sys_dept.campusid 钀藉簱锛屼繚璇侀棶鍗风粺璁¤兘鎸夐櫌鍖哄彇鍒般��
         String campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeavehospitaldistrictcode(), serviceTask.getOrgid());
         if (StringUtils.isEmpty(campusIdByDept)) {
-            campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getDeptcode(), serviceTask.getOrgid());
+            campusIdByDept = queryCampusIdByDeptCode(serviceSubtask.getLeaveldeptcode(), serviceTask.getOrgid());
         }
         if (StringUtils.isNotEmpty(campusIdByDept)) {
             serviceSubtask.setCampusid(campusIdByDept);
@@ -3999,7 +5593,7 @@
         if (StringUtils.isEmpty(serviceSubtask.getLeavehospitaldistrictcode())
                 && ("6".equals(serviceSubtask.getServiceType()) || "14".equals(serviceSubtask.getServiceType()))) {
             log.warn("銆愭壂鐮佺敓鎴愪换鍔°�戞弧鎰忓害瀛愪换鍔℃病鏈夌梾鍖猴紝鎸夌梾鍖虹瓫閫夌殑鎶ヨ〃灏嗙粺璁′笉鍒拌绛斿嵎锛歵askid={}, deptcode={}, campusid={}",
-                    serviceSubtask.getTaskid(), serviceSubtask.getDeptcode(), serviceSubtask.getCampusid());
+                    serviceSubtask.getTaskid(), serviceSubtask.getLeaveldeptcode(), serviceSubtask.getCampusid());
         }
         return serviceSubtask;
     }
@@ -4012,7 +5606,7 @@
             return null;
         }
         try {
-            SysDept sysDept = taskSysDeptPort.selectDeptByCode(deptCode, orgid);
+            SysDept sysDept = sysDept2Mapper.selectDeptByCode(deptCode, orgid);
             return sysDept == null ? null : sysDept.getCampusid();
         } catch (Exception e) {
             log.warn("鎸夌紪鐮佸弽鏌ラ櫌鍖哄け璐ワ細deptCode={}, orgid={}, err={}", deptCode, orgid, e.getMessage());

--
Gitblit v1.9.3