From af6c73841b54f0c9002ce85b8c0d1679e25d5d7c Mon Sep 17 00:00:00 2001
From: 陈昶聿 <chychen@nbjetron.com>
Date: 星期三, 01 七月 2026 18:06:02 +0800
Subject: [PATCH] 【景宁】任务组开发

---
 smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java |  356 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 356 insertions(+), 0 deletions(-)

diff --git a/smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java b/smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java
new file mode 100644
index 0000000..4f0a035
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/service/impl/ServiceTaskScheduleServiceImpl.java
@@ -0,0 +1,356 @@
+package com.smartor.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.common.exception.base.BaseException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.spring.SpringUtils;
+import com.smartor.domain.ServiceSubtask;
+import com.smartor.domain.ServiceSubtaskPreachform;
+import com.smartor.domain.ServiceTaskSchedule;
+import com.smartor.domain.ServiceTaskScheduleDetail;
+import com.smartor.domain.ServiceTaskScheduleVO;
+import com.smartor.mapper.ServiceSubtaskMapper;
+import com.smartor.mapper.ServiceSubtaskPreachformMapper;
+import com.smartor.mapper.ServiceTaskScheduleDetailMapper;
+import com.smartor.mapper.ServiceTaskScheduleMapper;
+import com.smartor.service.IServiceTaskScheduleService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 闅忚浠诲姟璁″垝 Service 瀹炵幇
+ *
+ * @author smartor
+ * @date 2026-06-30
+ */
+@Slf4j
+@Service
+public class ServiceTaskScheduleServiceImpl implements IServiceTaskScheduleService {
+
+    @Autowired
+    private ServiceTaskScheduleMapper serviceTaskScheduleMapper;
+
+    @Autowired
+    private ServiceTaskScheduleDetailMapper serviceTaskScheduleDetailMapper;
+
+    @Autowired
+    private ServiceSubtaskMapper serviceSubtaskMapper;
+
+    @Autowired
+    private ServiceSubtaskPreachformMapper serviceSubtaskPreachformMapper;
+
+    /** 鍗曟鎵弿鐨勬渶澶ц鍒掓暟锛岄伩鍏嶉暱浜嬪姟銆� */
+    private static final int ROLL_BATCH_LIMIT = 200;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ServiceTaskSchedule saveScheduleWithDetail(ServiceTaskScheduleVO vo) {
+        if (vo == null || vo.getTaskid() == null) {
+            throw new BaseException("浠诲姟璁″垝鍏ュ弬闈炴硶锛歵askid 涓嶈兘涓虹┖");
+        }
+        if (CollectionUtils.isEmpty(vo.getDetailList())) {
+            throw new BaseException("浠诲姟璁″垝鏄庣粏涓嶈兘涓虹┖锛堣嚦灏戜竴鏉� detail锛�");
+        }
+
+        // 1. 鏃ц鍒掕蒋鍒�
+        serviceTaskScheduleMapper.deleteByTaskid(vo.getTaskid());
+        serviceTaskScheduleDetailMapper.deleteByTaskid(vo.getTaskid());
+
+        // 2. 涓昏鍒掕惤搴�
+        Date now = DateUtils.getNowDate();
+        ServiceTaskSchedule schedule = new ServiceTaskSchedule();
+        // 鎷疯礉鐖剁被锛圫erviceTaskSchedule锛夊瓧娈�
+        schedule.setTaskid(vo.getTaskid());
+        schedule.setMaxLoopCount(vo.getMaxLoopCount());
+        schedule.setCurrentLoopCount(vo.getCurrentLoopCount() == null ? 0L : vo.getCurrentLoopCount());
+        schedule.setCurrentSeq(vo.getCurrentSeq() == null ? 0 : vo.getCurrentSeq());
+        schedule.setNextSeq(vo.getNextSeq() == null ? 1 : vo.getNextSeq());
+        schedule.setScheduleStyle(vo.getScheduleStyle() == null ? 0 : vo.getScheduleStyle());
+        schedule.setScheduleTimeNum(vo.getScheduleTimeNum() == null ? 0 : vo.getScheduleTimeNum());
+        schedule.setScheduleTimeUnit(vo.getScheduleTimeUnit() == null ? 0 : vo.getScheduleTimeUnit());
+        schedule.setNextExecuteTime(vo.getNextExecuteTime());
+        schedule.setScheduleBenchmark(vo.getScheduleBenchmark() == null ? 0 : vo.getScheduleBenchmark());
+        schedule.setScheduleBenchmarkCustomTime(vo.getScheduleBenchmarkCustomTime());
+        schedule.setRemark(vo.getRemark());
+        schedule.setDelFlag("0");
+        schedule.setCreateBy(vo.getCreateBy());
+        schedule.setUpdateBy(vo.getCreateBy());
+        schedule.setCreateTime(now);
+        schedule.setUpdateTime(now);
+        schedule.setOrgid(vo.getOrgid());
+        schedule.setCampusid(vo.getCampusid());
+        serviceTaskScheduleMapper.insertServiceTaskSchedule(schedule);
+
+        // 3. 鏄庣粏钀藉簱
+        List<ServiceTaskScheduleDetail> detailList = vo.getDetailList();
+        int seq = 1;
+        for (ServiceTaskScheduleDetail detail : detailList) {
+            detail.setTaskid(vo.getTaskid());
+            detail.setScheduleid(schedule.getId());
+            if (detail.getSeq() == null) {
+                detail.setSeq(seq);
+            }
+            if (detail.getDayOffset() == null) {
+                throw new BaseException("璁″垝鏄庣粏 seq=" + detail.getSeq() + " 鐨� dayOffset 涓嶈兘涓虹┖");
+            }
+            detail.setDelFlag("0");
+            detail.setCreateBy(vo.getCreateBy());
+            detail.setUpdateBy(vo.getCreateBy());
+            detail.setCreateTime(now);
+            detail.setUpdateTime(now);
+            detail.setOrgid(vo.getOrgid());
+            detail.setCampusid(vo.getCampusid());
+            seq++;
+        }
+        serviceTaskScheduleDetailMapper.batchInsertServiceTaskScheduleDetail(detailList);
+
+        log.info("[浠诲姟缁刔 taskid={} 璁″垝淇濆瓨瀹屾垚, scheduleId={}, detail 鏉℃暟={}",
+                vo.getTaskid(), schedule.getId(), detailList.size());
+        return schedule;
+    }
+
+    @Override
+    public ServiceTaskScheduleVO getScheduleByTaskid(Long taskid) {
+        ServiceTaskSchedule schedule = serviceTaskScheduleMapper.selectByTaskid(taskid);
+        if (schedule == null) {
+            return null;
+        }
+        ServiceTaskScheduleVO vo = new ServiceTaskScheduleVO();
+        vo.setId(schedule.getId());
+        vo.setTaskid(schedule.getTaskid());
+        vo.setMaxLoopCount(schedule.getMaxLoopCount());
+        vo.setCurrentLoopCount(schedule.getCurrentLoopCount());
+        vo.setCurrentSeq(schedule.getCurrentSeq());
+        vo.setNextSeq(schedule.getNextSeq());
+        vo.setScheduleStyle(schedule.getScheduleStyle());
+        vo.setScheduleTimeNum(schedule.getScheduleTimeNum());
+        vo.setScheduleTimeUnit(schedule.getScheduleTimeUnit());
+        vo.setNextExecuteTime(schedule.getNextExecuteTime());
+        vo.setScheduleBenchmark(schedule.getScheduleBenchmark());
+        vo.setScheduleBenchmarkCustomTime(schedule.getScheduleBenchmarkCustomTime());
+        vo.setRemark(schedule.getRemark());
+        vo.setOrgid(schedule.getOrgid());
+        vo.setCampusid(schedule.getCampusid());
+        vo.setDetailList(serviceTaskScheduleDetailMapper.selectByScheduleid(schedule.getId()));
+        return vo;
+    }
+
+    @Override
+    public ServiceTaskSchedule selectByTaskid(Long taskid) {
+        return serviceTaskScheduleMapper.selectByTaskid(taskid);
+    }
+
+    @Override
+    public List<ServiceTaskScheduleDetail> selectDetailByScheduleid(Long scheduleid) {
+        return serviceTaskScheduleDetailMapper.selectByScheduleid(scheduleid);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int removeByTaskid(Long taskid) {
+        int n = serviceTaskScheduleMapper.deleteByTaskid(taskid);
+        serviceTaskScheduleDetailMapper.deleteByTaskid(taskid);
+        return n;
+    }
+
+    @Override
+    public int rollCycleTasks() {
+        List<ServiceTaskSchedule> schedules = serviceTaskScheduleMapper.selectActiveCycleSchedules(ROLL_BATCH_LIMIT);
+        if (CollectionUtils.isEmpty(schedules)) {
+            return 0;
+        }
+        log.info("[浠诲姟缁�-婊氬姩] 鎵弿鍒� {} 涓緟璇勪及鐨勫惊鐜鍒�", schedules.size());
+        int rolled = 0;
+        for (ServiceTaskSchedule schedule : schedules) {
+            try {
+                // 閫氳繃 AOP 浠g悊璋冪敤锛岀‘淇� @Transactional 鐢熸晥
+                if (SpringUtils.getAopProxy(this).rollOneSchedule(schedule)) {
+                    rolled++;
+                }
+            } catch (Exception e) {
+                // 鍗曟潯澶辫触涓嶅奖鍝嶅叾瀹冭鍒掞紝璁版棩蹇楃户缁�
+                log.error("[浠诲姟缁�-婊氬姩] scheduleId={} 婊氬姩澶辫触锛歿}", schedule.getId(), e.getMessage(), e);
+            }
+        }
+        log.info("[浠诲姟缁�-婊氬姩] 鏈鎺ㄨ繘 {} 涓鍒�", rolled);
+        return rolled;
+    }
+
+    /**
+     * 鍗曚釜璁″垝鐨勬粴鍔細鏈疆鍏ㄩ儴 finalize 鎵嶆帹杩涳紝姣忔浠呮帹杩� 1 杞��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public boolean rollOneSchedule(ServiceTaskSchedule schedule) {
+        Long scheduleId = schedule.getId();
+        Long currentLoop = schedule.getCurrentLoopCount() == null ? 0L : schedule.getCurrentLoopCount();
+
+        // 涓婇檺鏍¢獙
+        Long max = schedule.getMaxLoopCount();
+        if (max != null && max > 0 && currentLoop + 1 >= max) {
+            // 娉ㄦ剰锛氬厛鍒ゆ柇"鍐嶅紑涓�杞槸鍚︿細瓒呴檺"鈥斺�攃urrentLoop 鏄凡瀹屾垚杞锛屼笅涓�杞彿 = currentLoop+1
+            // 褰� currentLoop+1 == max 鏃舵濂藉浜庢渶鍚庝竴杞紝涓嶅啀浜х敓鏂颁竴杞�
+            if (currentLoop >= max) {
+                log.info("[浠诲姟缁�-婊氬姩] scheduleId={} 宸茶揪 maxLoopCount={}, 璺宠繃", scheduleId, max);
+                return false;
+            }
+        }
+
+        // 鏈疆鏄惁瀹屾垚
+        int active = serviceSubtaskMapper.countActiveByScheduleIdAndLoop(scheduleId, currentLoop);
+        if (active > 0) {
+            log.debug("[浠诲姟缁�-婊氬姩] scheduleId={} loop={} 浠嶆湁 {} 鏉� subtask 鏈粨鏉�, 璺宠繃", scheduleId, currentLoop, active);
+            return false;
+        }
+
+        // 鎷夋湰杞� subtask 鍋氬厠闅嗘ā鏉匡紙鍙栧幓閲嶅悗鐨勬偅鑰呴泦鍚堬級
+        List<ServiceSubtask> currentLoopSubtasks =
+                serviceSubtaskMapper.selectByScheduleIdAndLoop(scheduleId, currentLoop);
+        if (CollectionUtils.isEmpty(currentLoopSubtasks)) {
+            log.warn("[浠诲姟缁�-婊氬姩] scheduleId={} loop={} 鎵句笉鍒版湰杞� subtask, 璺宠繃", scheduleId, currentLoop);
+            return false;
+        }
+
+        // 鎷夋湰璁″垝鏄庣粏锛歴eq -> detail锛岀敤鏉ュ畾浣嶄笅涓�杞瘡鏉� subtask 鐢ㄥ摢涓ā鏉�
+        List<ServiceTaskScheduleDetail> detailList =
+                serviceTaskScheduleDetailMapper.selectByScheduleid(scheduleId);
+        if (CollectionUtils.isEmpty(detailList)) {
+            log.warn("[浠诲姟缁�-婊氬姩] scheduleId={} 鏄庣粏涓虹┖, 璺宠繃", scheduleId);
+            return false;
+        }
+
+        // 鎺ㄨ繘姝ラ暱锛堣疆闂撮殧锛�
+        int stepNum = schedule.getScheduleTimeNum() == null ? 0 : schedule.getScheduleTimeNum();
+        int stepUnit = schedule.getScheduleTimeUnit() == null ? 1 : schedule.getScheduleTimeUnit();
+        if (stepNum <= 0) {
+            log.warn("[浠诲姟缁�-婊氬姩] scheduleId={} scheduleTimeNum={} 闈炴硶, 璺宠繃", scheduleId, stepNum);
+            return false;
+        }
+
+        long nextLoop = currentLoop + 1;
+
+        // 鎸� patid+inhospid 鍘婚噸锛堝悓鎮h�呮湰杞彲鑳芥湁 N 鏉� detail锛屾ā鏉夸换閫夊叾涓�鍗冲彲鈥斺�斿厠闅嗘椂鎸� seq 澶嶇敤鏄庣粏锛�
+        // 涓虹畝鍖栵細鐩存帴鎸� schedule_seq 澶嶅埗 N 鏉★紝姣忔潯鐢ㄥ搴� detail銆�
+        Date now = DateUtils.getNowDate();
+        // 鎶� detailList 鎸� seq 绱㈠紩
+        java.util.Map<Integer, ServiceTaskScheduleDetail> detailBySeq = new java.util.HashMap<>(detailList.size());
+        for (ServiceTaskScheduleDetail d : detailList) {
+            detailBySeq.put(d.getSeq(), d);
+        }
+
+        int produced = 0;
+        for (ServiceSubtask src : currentLoopSubtasks) {
+            ServiceTaskScheduleDetail detail = detailBySeq.get(src.getScheduleSeq());
+            if (detail == null) {
+                log.warn("[浠诲姟缁�-婊氬姩] scheduleId={} 鎵句笉鍒� seq={} 瀵瑰簲鐨勬槑缁�, 璺宠繃 subtask id={}",
+                        scheduleId, src.getScheduleSeq(), src.getId());
+                continue;
+            }
+            ServiceSubtask copy = cloneForNextLoop(src, detail, nextLoop, stepNum, stepUnit, now);
+            serviceSubtaskMapper.insertServiceSubtask(copy);
+            // 澶嶅埗 preachform
+            copyPreachform(src.getId(), copy);
+            produced++;
+        }
+
+        // 鎺ㄨ繘 schedule 鐨� current_loop_count / next_seq / next_execute_time
+        ServiceTaskSchedule upd = new ServiceTaskSchedule();
+        upd.setId(scheduleId);
+        upd.setCurrentLoopCount(nextLoop);
+        upd.setCurrentSeq(1);
+        upd.setNextSeq(detailList.size() > 1 ? 2 : 1);
+        upd.setNextExecuteTime(advance(now, stepNum, stepUnit));
+        upd.setUpdateTime(now);
+        serviceTaskScheduleMapper.updateServiceTaskSchedule(upd);
+
+        log.info("[浠诲姟缁�-婊氬姩] scheduleId={} 鎺ㄨ繘鍒扮 {} 杞�, 鏂板缓 {} 鏉� subtask", scheduleId, nextLoop, produced);
+        return true;
+    }
+
+    /**
+     * 鍏嬮殕 subtask 鍒颁笅涓�杞細娓呬富閿�佹竻鍥炵瀛楁銆佹帹杩� visit_time銆佽缃� loop_count銆�
+     */
+    private ServiceSubtask cloneForNextLoop(ServiceSubtask src, ServiceTaskScheduleDetail detail,
+                                            long nextLoop, int stepNum, int stepUnit, Date now) {
+        ServiceSubtask copy = JSON.parseObject(JSON.toJSONString(src), ServiceSubtask.class);
+        copy.setId(null);
+        copy.setSenduuid(null);
+        copy.setResult(null);
+        copy.setFinishtime(null);
+        copy.setExrecallcount(null);
+        copy.setCurrentPreachform(null);
+        copy.setSendstate(2L);
+        copy.setLoopCount(nextLoop);
+        copy.setScheduleDetailId(detail.getId());
+        // 妯℃澘锛歞etail 浼樺厛
+        if (detail.getTemplateid() != null) {
+            copy.setTemplateid(detail.getTemplateid());
+            copy.setTemplatename(detail.getTemplatename());
+        }
+        if (detail.getLibtemplateid() != null) {
+            copy.setLibtemplateid(detail.getLibtemplateid());
+        }
+        Date base = src.getVisitTime() != null ? src.getVisitTime() : now;
+        Date nextVisit = advance(base, stepNum, stepUnit);
+        copy.setVisitTime(nextVisit);
+        copy.setSenddate(nextVisit);
+        copy.setLongSendTime(nextVisit);
+        copy.setCreateTime(now);
+        copy.setUpdateTime(now);
+        return copy;
+    }
+
+    /**
+     * 澶嶅埗婧� subtask 鐨勫彂閫佹柟寮忚鍒版柊 subtask銆�
+     */
+    private void copyPreachform(Long srcSubId, ServiceSubtask newSubtask) {
+        ServiceSubtaskPreachform q = new ServiceSubtaskPreachform();
+        q.setSubid(srcSubId);
+        q.setTaskid(newSubtask.getTaskid());
+        List<ServiceSubtaskPreachform> list = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(q);
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        for (ServiceSubtaskPreachform src : list) {
+            ServiceSubtaskPreachform pf = new ServiceSubtaskPreachform();
+            pf.setSort(src.getSort());
+            pf.setPreachform(src.getPreachform());
+            pf.setCompensateTime(src.getCompensateTime());
+            pf.setTaskid(newSubtask.getTaskid());
+            pf.setSubid(newSubtask.getId());
+            pf.setSendstate("1");
+            pf.setOrgid(newSubtask.getOrgid());
+            pf.setCreateTime(new Date());
+            serviceSubtaskPreachformMapper.insertServiceSubtaskPreachform(pf);
+        }
+    }
+
+    /**
+     * 鎸� schedule_time_unit 鎺ㄨ繘鏃堕棿锛�1-澶� 2-鍛� 3-鏈� 4-骞�
+     */
+    private Date advance(Date base, int num, int unit) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(base);
+        switch (unit) {
+            case 2:
+                c.add(Calendar.WEEK_OF_YEAR, num);
+                break;
+            case 3:
+                c.add(Calendar.MONTH, num);
+                break;
+            case 4:
+                c.add(Calendar.YEAR, num);
+                break;
+            case 1:
+            default:
+                c.add(Calendar.DAY_OF_MONTH, num);
+        }
+        return c.getTime();
+    }
+}

--
Gitblit v1.9.3