ソースを参照

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 日 前
コミット
9d9b844336
38 ファイル変更1146 行追加95 行削除
  1. 16 0
      fs-admin/src/main/java/com/fs/course/task/VideoTask.java
  2. 26 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  3. 68 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferController.java
  4. 28 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferLogController.java
  5. 7 2
      fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java
  6. 1 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  7. 12 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseTrafficLog.java
  8. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  9. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  10. 68 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  11. 3 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  12. 1 1
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  13. 8 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  14. 78 0
      fs-service/src/main/java/com/fs/qw/domain/QwGroupChatTransferLog.java
  15. 5 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  16. 7 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
  17. 18 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatTransferLogMapper.java
  18. 25 0
      fs-service/src/main/java/com/fs/qw/param/ChatParam.java
  19. 6 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java
  20. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwGroupChatParam.java
  21. 15 0
      fs-service/src/main/java/com/fs/qw/param/QwGroupChatTransferLogParam.java
  22. 1 0
      fs-service/src/main/java/com/fs/qw/param/ResignedTransferParam.java
  23. 29 0
      fs-service/src/main/java/com/fs/qw/param/TransferChatParam.java
  24. 1 0
      fs-service/src/main/java/com/fs/qw/param/TransferParam.java
  25. 21 0
      fs-service/src/main/java/com/fs/qw/result/ResultMessage.java
  26. 22 0
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java
  27. 18 0
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatTransferLogService.java
  28. 156 83
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  29. 29 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatTransferLogServiceImpl.java
  30. 51 0
      fs-service/src/main/java/com/fs/qw/vo/QwGroupChatTransferLogVO.java
  31. 47 0
      fs-service/src/main/java/com/fs/qw/vo/QwGroupChatTransferVO.java
  32. 14 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  33. 2 2
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  34. 79 0
      fs-service/src/main/resources/application-config-druid-xfk.yml
  35. 150 0
      fs-service/src/main/resources/application-druid-xfk.yml
  36. 49 1
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  37. 32 0
      fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml
  38. 33 0
      fs-service/src/main/resources/mapper/qw/QwGroupChatTransferLogMapper.xml

+ 16 - 0
fs-admin/src/main/java/com/fs/course/task/VideoTask.java

@@ -5,6 +5,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
+import com.fs.course.service.IFsCourseTrafficLogService;
 import com.fs.course.service.IFsUserVideoCommentService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
@@ -18,6 +19,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
@@ -55,6 +57,9 @@ public class VideoTask {
     @Autowired
     private FsCourseRedPacketLogMapper redPacketLogMapper;
 
+    @Autowired
+    private IFsCourseTrafficLogService iFsCourseTrafficLogService;
+
     public void autoNotShowVideo() {
         List<Long> list = videoMapper.selectNotAuditVideo();
         for (Long videoId : list){
@@ -238,4 +243,15 @@ public class VideoTask {
 
         }
     }
+
+    /**
+     * 存储课程流量日志
+     * @throws Exception
+     */
+//    @Scheduled(fixedRate = 1000)
+    public void saveCourseTrafficLog() throws Exception
+    {
+        iFsCourseTrafficLogService.saveCourseTrafficLog();
+    }
+
 }

+ 26 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -30,6 +31,7 @@ import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.collections.CollectionUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -39,6 +41,7 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
@@ -233,7 +236,18 @@ public class QwExternalContactController extends BaseController
     @PutMapping("/resignedTransfer")
     public R resignedTransfer(@RequestBody ResignedTransferParam param)
     {
-
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
 
         return qwExternalContactService.resignedTransfer(param);
     }
@@ -246,7 +260,17 @@ public class QwExternalContactController extends BaseController
     @PutMapping("/transfer")
     public R transfer(@RequestBody TransferParam param)
     {
-
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
         return qwExternalContactService.transfer(param);
     }
     /**

+ 68 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferController.java

@@ -0,0 +1,68 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.domain.CompanyUser;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
+import com.fs.qw.service.IQwGroupChatService;
+import com.fs.qw.vo.QwGroupChatTransferVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/groupChatTransfer")
+@AllArgsConstructor
+public class QwGroupChatTransferController extends BaseController {
+
+    private final IQwGroupChatService qwGroupChatService;
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:listOnJob')")
+    @GetMapping("/listOnJob")
+    public TableDataInfo listOnJob(QwGroupChatParam qwGroupChat) {
+        startPage();
+        qwGroupChat.setStatusList(Arrays.asList("0", "3"));
+        List<QwGroupChatTransferVO> list = qwGroupChatService.selectQwGroupChatTransferList(qwGroupChat);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:transferOnJob')")
+    @PostMapping("/transferOnJob")
+    public AjaxResult transferOnJob(@RequestBody @Valid TransferChatParam param) {
+        if (param.getChatIds().size() > 100) {
+            return AjaxResult.error("单批次最大转移数为100个");
+        }
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        ResultMessage message = qwGroupChatService.processTransfer(param, user, false);
+        return AjaxResult.success(message.buildResultMessage());
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatParam qwGroupChat) {
+        startPage();
+        qwGroupChat.setStatusList(Collections.singletonList("1"));
+        List<QwGroupChatTransferVO> list = qwGroupChatService.selectQwGroupChatTransferList(qwGroupChat);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:transfer')")
+    @PostMapping("/transfer")
+    public AjaxResult transfer(@RequestBody @Valid TransferChatParam param) {
+        if (param.getChatIds().size() > 100) {
+            return AjaxResult.error("单批次最大转移数为100个");
+        }
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        ResultMessage message = qwGroupChatService.processTransfer(param, user, true);
+        return AjaxResult.success(message.buildResultMessage());
+    }
+}

+ 28 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferLogController.java

@@ -0,0 +1,28 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.service.IQwGroupChatTransferLogService;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/groupChatTransferLog")
+@AllArgsConstructor
+public class QwGroupChatTransferLogController extends BaseController {
+
+    private final IQwGroupChatTransferLogService groupChatTransferLogService;
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransferLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatTransferLogParam param) {
+        startPage();
+        List<QwGroupChatTransferLogVO> list = groupChatTransferLogService.selectQwGroupChatTransferVOList(param);
+        return getDataTable(list);
+    }
+}

+ 7 - 2
fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -33,13 +33,18 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
-
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
+    public DataSource clickhouseDataSource() {
+        return new DruidDataSource();
+    }
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 

+ 1 - 1
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -487,7 +487,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             }
             List<QwSopTempSetting.Content> contents = getDay(tempSettings, day);
             if (contents == null || contents.isEmpty()) {
-                log.error("SOP ID {} 的 TempSetting 内容为空,跳过处理。", logVo.getSopId());
+                log.error("SOP ID {} 的 TempSetting 内容为空,跳过处理。天数 {}", logVo.getSopId(),day);
                 return;
             }
 

+ 12 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseTrafficLog.java

@@ -56,6 +56,18 @@ public class FsCourseTrafficLog extends BaseEntity
     @Excel(name = "课程id")
     private Long courseId;
 
+    /**
+     * 项目
+     */
+    private Long project;
+
+    /**
+     * 营期id
+     */
+    private Long periodId;
+
+
+
 //    @JsonFormat(pattern = "yyyy-MM-dd")
 //    private Date time;
 

+ 10 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -1,11 +1,15 @@
 package com.fs.course.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
 
 /**
  * 短链课程流量记录Mapper接口
@@ -13,6 +17,7 @@ import org.apache.ibatis.annotations.Select;
  * @author fs
  * @date 2024-10-31
  */
+@Repository
 public interface FsCourseTrafficLogMapper
 {
     /**
@@ -97,5 +102,10 @@ public interface FsCourseTrafficLogMapper
     Long getYesterdayTrafficLog();
 
     Long getMonthTrafficLog();
+    @Select("select count(1) from fs_course_traffic_log l WHERE l.create_time < DATE_SUB(NOW(), INTERVAL 2 DAY)")
+    Integer saveCourseTrafficLogByTwoDaysLaterCount();
 
+    List<FsCourseTrafficLog> selectCourseTrafficLogByTwoDaysLater(@Param("offset")Integer offset,@Param("limit")Integer limit);
+    @DataSource(DataSourceType.CLICKHOUSE)
+    void insertCourseTrafficLogByTwoDaysLaterBatch(@Param("list") List<FsCourseTrafficLog> redPacketLogs);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java

@@ -63,4 +63,9 @@ public interface IFsCourseTrafficLogService
 
 
     List<FsCourseTrafficLogListVO> selectTrafficByCompany(FsCourseTrafficLogParam param);
+
+    /**
+     * 存储课程流量日志
+     */
+    void saveCourseTrafficLog();
 }

+ 68 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -1,10 +1,13 @@
 package com.fs.course.service.impl;
 
+import java.text.SimpleDateFormat;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import com.fs.common.utils.DateUtils;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
@@ -18,6 +21,7 @@ import com.fs.course.service.IFsCourseTrafficLogService;
  * @date 2024-10-31
  */
 @Service
+@Slf4j
 public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
 {
     @Autowired
@@ -100,4 +104,68 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
     public List<FsCourseTrafficLogListVO> selectTrafficByCompany(FsCourseTrafficLogParam param) {
         return fsCourseTrafficLogMapper.selectTrafficByCompany(param);
     }
+
+
+    @Override
+    public void saveCourseTrafficLog() {
+        Integer count = fsCourseTrafficLogMapper.saveCourseTrafficLogByTwoDaysLaterCount();
+        Integer limit = 1000;
+        log.info("总数: {}", count);
+
+        // Start timing
+        long startTime = System.currentTimeMillis();
+        log.info("开始处理,时间: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+
+        // 计算需要查询的次数
+        int totalPages = (count + limit - 1) / limit; // 向上取整
+        for (int page = 0; page < totalPages; page++) {
+            int offset = page * limit;
+
+            log.info("开始查询");
+            List<FsCourseTrafficLog> redPacketLogs = fsCourseTrafficLogMapper.selectCourseTrafficLogByTwoDaysLater(0,limit);
+            log.info("查询结果: {}", redPacketLogs);
+            // 处理当前批次的1000条数据
+            if (redPacketLogs==null ||redPacketLogs.isEmpty()) {
+                log.info("没有数据");
+                continue;
+            }
+            fsCourseTrafficLogMapper.insertCourseTrafficLogByTwoDaysLaterBatch(redPacketLogs);
+            Long[] delArray = redPacketLogs.stream()
+                    .map(FsCourseTrafficLog::getLogId)
+                    .toArray(Long[]::new);
+            fsCourseTrafficLogMapper.deleteFsCourseTrafficLogByLogIds(delArray);
+
+            // 打印进度
+            log.info("已处理: {}/{} ({:.2f}%)",
+                    Math.min(offset + limit, count),
+                    count,
+                    (double)(offset + limit) / count * 100);
+
+            // Calculate estimated remaining time
+            long currentTime = System.currentTimeMillis();
+            long elapsedTime = currentTime - startTime;
+            double timePerBatch = (double)elapsedTime / (page + 1);
+            long estimatedRemaining = (long)(timePerBatch * (totalPages - page - 1));
+
+            log.info("当前批次耗时: {:.2f}s, 预计剩余时间: {}",
+                    timePerBatch / 1000,
+                    formatDuration(estimatedRemaining));
+        }
+
+        // End timing
+        long endTime = System.currentTimeMillis();
+        log.info("处理完成,时间: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+        log.info("总耗时: {}", formatDuration(endTime - startTime));
+    }
+
+    private static String formatDuration(long millis) {
+        long seconds = millis / 1000;
+        long minutes = seconds / 60;
+        long hours = minutes / 60;
+
+        return String.format("%02d:%02d:%02d",
+                hours,
+                minutes % 60,
+                seconds % 60);
+    }
 }

+ 3 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -836,14 +836,15 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         packetParam.setOpenId(user.getMpOpenId());
         // 来源是小程序切换openId
         if (param.getSource() == 2) {
-            packetParam.setOpenId(user.getMaOpenId());
+            packetParam.setOpenId(user.getCourseMaOpenId());
         }
         packetParam.setAmount(amount);
         packetParam.setSource(param.getSource());
         packetParam.setRedPacketMode(config.getRedPacketMode());
         packetParam.setCompanyId(param.getCompanyId());
 
-
+        System.out.println("红包商户号"+amount);
+        System.out.println("红包商户号"+packetParam);
         // 发送红包
         R sendRedPacket = paymentService.sendRedPacket(packetParam);
         if (sendRedPacket.get("code").equals(200)) {

+ 1 - 1
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java

@@ -82,7 +82,7 @@ public interface FsStoreOrderMapper
             " LEFT JOIN fs_doctor d ON so.doctor_id= d.doctor_id  " +
             " LEFT JOIN company c on c.company_id =so.company_id " +
             " LEFT JOIN company_user cu on cu.user_id=so.company_user_id  " +
-            " WHERE so.is_del=0 "+
+            " WHERE 1=1 "+
             "<if test=\"maps.packageSecondName != null and maps.packageSecondName != '' \"> and so.package_second_name like concat('%', #{maps.packageSecondName}, '%')</if>"+
             "<if test=\"maps.storeId != null \"> and so.store_id = #{maps.storeId}</if>\n" +
             "<if test=\"maps.orderCode != null  and maps.orderCode != ''\"> and so.order_code = #{maps.orderCode}</if>\n" +

+ 8 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -456,6 +456,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                 config = JSONUtil.toBean(json, RedPacketConfig.class);
                 break;
         }
+        System.out.println("最终传参"+config);
         //组合返回参数
         R result = new R();
         // 根据 isNew 判断使用哪种发红包方式
@@ -479,7 +480,12 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         TransferService transferService = wxPayService.getTransferService();
 
         TransferBillsRequest request = new TransferBillsRequest();
-        request.setAppid(config.getAppId());
+        if(param.getSource()==2){
+            request.setAppid(config.getMiniappId());
+        }else {
+            request.setAppid(config.getAppId());
+        }
+
         request.setOpenid(param.getOpenId());
 
         String code = String.valueOf(IdUtil.getSnowflake(0, 0).nextId());
@@ -505,6 +511,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         transferSceneReportInfos.add(info2);
         request.setTransferSceneReportInfos(transferSceneReportInfos);
 
+
         try {
             TransferBillsResult transferBillsResult = transferService.transferBills(request);
             logger.info("商家转账支付完成:[msg:{}]", transferBillsResult);

+ 78 - 0
fs-service/src/main/java/com/fs/qw/domain/QwGroupChatTransferLog.java

@@ -0,0 +1,78 @@
+package com.fs.qw.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("qw_group_chat_transfer_log")
+public class QwGroupChatTransferLog {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 企微主体ID
+     */
+    private String corpId;
+    /**
+     * 转移类型 0:在职继承 1:离职继承
+     */
+    private Integer transferType;
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+    /**
+     * 销售ID
+     */
+    private Long companyUserId;
+    /**
+     * 原归属群主ID
+     */
+    private String oldOwner;
+    /**
+     * 原归属销售ID
+     */
+    private Long oldCompanyUserId;
+    /**
+     * 原归属企微用户ID
+     */
+    private Long oldQwUserId;
+    /**
+     * 原归属企微用户名称
+     */
+    private String oldQwUserName;
+    /**
+     * 接替群主ID
+     */
+    private String newOwner;
+    /**
+     * 接替销售ID
+     */
+    private Long newCompanyUserId;
+    /**
+     * 接替企微用户ID
+     */
+    private Long newQwUserId;
+    /**
+     * 接替企微用户名称
+     */
+    private String newQwUserName;
+    /**
+     * 客户群ID
+     */
+    private String chatId;
+    /**
+     * 客户群名称
+     */
+    private String chatName;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -213,6 +213,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "select ec.*,qu.qw_user_name,qd.dept_name as departmentName from qw_external_contact ec " +
             "left join qw_user qu on ec.user_id=qu.qw_user_id and qu.corp_id=ec.corp_id " +
             "left join qw_dept qd on qd.dept_id=qu.department and qd.corp_id=qu.corp_id " +
+            "left join company_user cu on ec.company_user_id=cu.user_id " +
             "<where>  \n" +
             "            <if test=\"userId != null  and userId != ''\"> and ec.user_id   like concat( #{userId}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name   like concat( #{qwUserName}, '%') </if>\n" +
@@ -250,6 +251,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "            <if test=\"delTime != null \"> and DATE(ec.del_time) = DATE(#{delTime})</if>\n" +
             "            <if test=\"sTime != null \">  and DATE(ec.create_time) &gt;= DATE(#{sTime})</if>\n" +
             "            <if test=\"eTime != null \">  and DATE(ec.create_time) &lt;= DATE(#{eTime})</if>\n" +
+
+            "<if test ='companyUser!=null'> " +
+                "and (cu.nick_name like concat('%', #{companyUser}, '%') or cu.phonenumber= #{companyUser})"+
+            "</if> " +
             "        </where>"+
             "order by ec.id desc"+
             "</script>"})

+ 7 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java

@@ -3,6 +3,7 @@ package com.fs.qw.mapper;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.param.QwGroupChatParam;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
@@ -115,4 +116,10 @@ public interface QwGroupChatMapper
 
     List<QwGroupChat> selectQwGroupChatByChatIds(@Param("ids") String[] ids);
 
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat   参数
+     * @return  list
+     */
+    List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatTransferLogMapper.java

@@ -0,0 +1,18 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+
+import java.util.List;
+
+public interface QwGroupChatTransferLogMapper extends BaseMapper<QwGroupChatTransferLog> {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return  list
+     */
+    List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/qw/param/ChatParam.java

@@ -0,0 +1,25 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class ChatParam {
+    /**
+     * 群聊ID
+     */
+    @NotBlank(message = "客户群不能为空")
+    private String chatId;
+    /**
+     * 群主企微ID
+     */
+    @NotNull(message = "群主企微账户不能为空")
+    private Long qwId;
+    /**
+     * 群主ID
+     */
+    @NotBlank(message = "群主ID不能为空")
+    private String owner;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java

@@ -27,6 +27,12 @@ public class QwExternalContactParam {
 
     private Long companyUserId;
 
+    /**
+     * 销售员工信息(昵称,手机号)
+     */
+    private String companyUser;
+
+
     private Long customerId;
 
     /** 名称 */

+ 5 - 0
fs-service/src/main/java/com/fs/qw/param/QwGroupChatParam.java

@@ -3,6 +3,8 @@ package com.fs.qw.param;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 public class QwGroupChatParam {
     private static final long serialVersionUID = 1L;
@@ -45,4 +47,7 @@ public class QwGroupChatParam {
 
     private String qwUserList;
 
+    /** 状态集合 */
+    private List<String> statusList;
+
 }

+ 15 - 0
fs-service/src/main/java/com/fs/qw/param/QwGroupChatTransferLogParam.java

@@ -0,0 +1,15 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class QwGroupChatTransferLogParam {
+    /**
+     * 企微主体ID
+     */
+    private String corpId;
+    /**
+     * 群名
+     */
+    private String name;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/qw/param/ResignedTransferParam.java

@@ -9,4 +9,5 @@ public class ResignedTransferParam {
     List<Long> ids;
     Long  userId;
     String corpId;
+    String  qwUserName;
 }

+ 29 - 0
fs-service/src/main/java/com/fs/qw/param/TransferChatParam.java

@@ -0,0 +1,29 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class TransferChatParam {
+    /**
+     * 客户群ID集合
+     */
+    @Valid
+    @NotEmpty(message = "转移客户群不能为空")
+    private List<ChatParam> chatIds;
+    /**
+     * 接替企微用户
+     */
+    @NotNull(message = "接替企微用户不能为空")
+    private Long qwId;
+    /**
+     * 企微主体ID
+     */
+    @NotBlank(message = "企微主体不能为空")
+    private String corpId;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/qw/param/TransferParam.java

@@ -8,6 +8,7 @@ import java.util.List;
 public class TransferParam {
     List<Long> ids;
     Long  userId;
+    String  qwUserName;
     String corpId;
     String content;
 }

+ 21 - 0
fs-service/src/main/java/com/fs/qw/result/ResultMessage.java

@@ -0,0 +1,21 @@
+package com.fs.qw.result;
+
+
+public class ResultMessage {
+    private int successNum = 0;
+    private int failureNum = 0;
+    private final StringBuilder errorMsg = new StringBuilder();
+
+    public void addSuccess() {
+        successNum++;
+    }
+
+    public void addFailure(String title, String error) {
+        failureNum++;
+        errorMsg.append("<br/>").append(title).append(" 失败:").append(error);
+    }
+
+    public String buildResultMessage() {
+        return "成功" + successNum + " 条,失败" + failureNum + "条。" + errorMsg;
+    }
+}

+ 22 - 0
fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java

@@ -1,11 +1,16 @@
 package com.fs.qw.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.company.domain.CompanyUser;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 
+import javax.validation.Valid;
 import java.util.List;
 
 /**
@@ -65,4 +70,21 @@ public interface IQwGroupChatService
     List<QwGroupChatOptionsVO> listAllByQwUserList(String qwUserIds, String corpId);
 
     List<QwGroupChat> selectQwGroupChatByChatIds(String[] ids);
+
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat 参数
+     * @return  list
+     */
+    List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat);
+
+    /**
+     * 分配客户群
+     *
+     * @param param         参数
+     * @param user          当前登录销售
+     * @param isResigned    是否在职人员客户群
+     * @return message
+     */
+    ResultMessage processTransfer(TransferChatParam param, CompanyUser user, boolean isResigned);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/service/IQwGroupChatTransferLogService.java

@@ -0,0 +1,18 @@
+package com.fs.qw.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+
+import java.util.List;
+
+public interface IQwGroupChatTransferLogService extends IService<QwGroupChatTransferLog> {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return list
+     */
+    List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param);
+}

+ 156 - 83
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java

@@ -1,39 +1,33 @@
 package com.fs.qw.service.impl;
 
 import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
-import com.fs.company.service.ICompanyConfigService;
-import com.fs.course.service.IFsCourseLinkService;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwGroupChat;
-import com.fs.qw.domain.QwGroupChatUser;
+import com.fs.company.domain.CompanyUser;
+import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
+import com.fs.qw.param.ChatParam;
 import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.Result.QwGroupChatListResult;
+import com.fs.qwApi.domain.QwGroupChatTransferResult;
+import com.fs.qwApi.param.QwGroupChatTransferParam;
 import com.fs.qwApi.service.QwApiService;
-import com.fs.sop.mapper.QwSopMapper;
-import com.fs.sop.mapper.SopUserLogsInfoMapper;
-import com.fs.sop.service.IQwSopLogsService;
-import com.fs.sop.service.IQwSopService;
-import com.fs.sop.service.ISopUserLogsInfoService;
-import com.fs.sop.service.ISopUserLogsService;
-import com.fs.sop.service.impl.QwSopLogsServiceImpl;
-import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.voice.utils.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 客户群详情Service业务层处理
@@ -44,80 +38,18 @@ import java.util.List;
 @Service
 public class QwGroupChatServiceImpl implements IQwGroupChatService
 {
-    private static final Logger log = LoggerFactory.getLogger(QwGroupChatServiceImpl.class);
     @Autowired
     private QwGroupChatMapper qwGroupChatMapper;
-
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
-
     @Autowired
     private QwApiService qwApiService;
-
-    @Autowired
-    private ICompanyConfigService companyConfigService;
-
-    @Autowired
-    private IFsCourseLinkService iFsCourseLinkService;
-
-    @Autowired
-    IQwExternalContactService qwExternalContactService;
-
-    @Autowired
-    IQwCompanyService iQwCompanyService;
-
-    @Autowired
-    private ISopUserLogsInfoService sopUserLogsInfoService;
-
-    @Autowired
-    private IQwSopService iQwSopService;
-
     @Autowired
-    private IQwSopLogsService iQwSopLogsService;
-
-    @Autowired
-    private IQwGroupMsgService iQwGroupMsgService;
-
-    @Autowired
-    private QwSopLogsServiceImpl qwSopLogsService;
-
-    @Autowired
-    private QwSopServiceImpl qwSopService;
-
-    @Autowired
-    QwAutoTagsMapper qwAutoTagsMapper;
-
-    @Autowired
-    QwCompanyMapper qwCompanyMapper;
-
-    @Autowired
-    private QwSopMapper qwSopMapper;
-
+    private QwCompanyMapper qwCompanyMapper;
     @Autowired
     private QwUserMapper qwUserMapper;
-
-    @Autowired
-    private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
-    private ISopUserLogsService sopUserLogsService;
-
-    @Autowired
-    private ISopUserLogsInfoService iSopUserLogsInfoService;
-
-    @Autowired
-    private QwAppContactWayMapper  qwAppContactWayMapper;
-
-    @Autowired
-    private QwAppContactWayLogsMapper qwAppContactWayLogsMapper;
-
-    @Autowired
-    private IQwExternalErrRetryService errRetryService;
-
-    @Autowired
-    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
-
-//    @Autowired
-//    private IQwGetJsapiTicketService qwGetJsapiTicketService;
+    private IQwGroupChatTransferLogService qwGroupChatTransferLogService;
 
     /**
      * 查询客户群详情
@@ -416,6 +348,15 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
                     });
 
                 }
+                // 处理离职查询不到详情的情况
+                else {
+                    if (list.getStatus() == 1) {
+                        QwGroupChat qwGroupChatResigned = qwGroupChatMapper.selectQwGroupChatByChatId(list.getChat_id());
+                        qwGroupChatResigned.setUpdateTime(new Date());
+                        qwGroupChatResigned.setStatus(String.valueOf(list.getStatus()));
+                        qwGroupChatMapper.updateQwGroupChat(qwGroupChatResigned);
+                    }
+                }
             }
 
             if (!StringUtil.strIsNullOrEmpty(qwGroupChatListResult.getNext_cursor())){
@@ -440,4 +381,136 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
         return qwGroupChatMapper.selectQwGroupChatByChatIds(ids);
     }
 
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat 参数
+     * @return  list
+     */
+    @Override
+    public List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat) {
+        return qwGroupChatMapper.selectQwGroupChatTransferList(qwGroupChat);
+    }
+
+    /**
+     * 客户群转移
+     * @param param         参数
+     * @param user          当前销售
+     * @param isResigned    是否转移离职员工客户群
+     * @return  message
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public ResultMessage processTransfer(TransferChatParam param, CompanyUser user, boolean isResigned) {
+        ResultMessage resultMessage = new ResultMessage();
+
+        QwUser qwUser = qwUserMapper.selectQwUserById(param.getQwId());
+        if (Objects.isNull(qwUser)) {
+            throw new CustomException("接替群主不存在");
+        }
+
+        // 准备转移参数
+        QwGroupChatTransferParam transferParam = new QwGroupChatTransferParam();
+        transferParam.setChat_id_list(param.getChatIds().stream()
+                .map(ChatParam::getChatId)
+                .collect(Collectors.toList()));
+        transferParam.setNew_owner(qwUser.getQwUserId());
+
+        // 调用不同的API
+        QwGroupChatTransferResult result = isResigned ?
+                qwApiService.resignedGroupChatTransfer(transferParam, param.getCorpId()) :
+                qwApiService.groupChatTransfer(transferParam, param.getCorpId());
+
+        if (result.getErrcode() != 0) {
+            throw new CustomException(result.getErrmsg());
+        }
+
+        // 处理失败结果
+        result.getFailed_chat_list().forEach(failed -> resultMessage.addFailure(failed.getChat_id(), failed.getErrmsg()));
+
+        // 过滤掉转移失败的群聊
+        List<ChatParam> successChatParams = filterSuccessChats(param.getChatIds(), result.getFailed_chat_list());
+
+        // 处理成功的转移记录
+        processSuccessTransfers(successChatParams, user, qwUser, resultMessage, isResigned);
+
+        return resultMessage;
+    }
+
+    /**
+     * 过滤转移失败客户群
+     * @param chatParams    入参
+     * @param failedChats   失败列表
+     * @return  客户群
+     */
+    private List<ChatParam> filterSuccessChats(List<ChatParam> chatParams, List<QwGroupChatTransferResult.FailedChat> failedChats) {
+        Set<String> failedChatIds = failedChats.stream()
+                .map(QwGroupChatTransferResult.FailedChat::getChat_id)
+                .collect(Collectors.toSet());
+
+        return chatParams.stream()
+                .filter(c -> !failedChatIds.contains(c.getChatId()))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 处理转移成功客户群
+     *
+     * @param successChatParams 转移成功客户群
+     * @param user              当前销售账户
+     * @param newOwner          新群主
+     * @param resultMessage     消息
+     * @param isResigned        是否转移离职员工客户群
+     */
+    private void processSuccessTransfers(List<ChatParam> successChatParams, CompanyUser user, QwUser newOwner, ResultMessage resultMessage, boolean isResigned) {
+        List<QwGroupChatTransferLog> transferLogs = successChatParams.stream()
+                .map(chatParam -> createTransferLog(chatParam, user, newOwner, isResigned))
+                .collect(Collectors.toList());
+
+        if (!transferLogs.isEmpty()) {
+            qwGroupChatTransferLogService.saveBatch(transferLogs);
+            transferLogs.forEach(log -> resultMessage.addSuccess());
+        }
+    }
+
+    /**
+     * 保存转移记录修改群主
+     * @param chatParam  客户群
+     * @param user       当前销售
+     * @param newOwner   新群主
+     * @param isResigned 是否转移离职员工客户群
+     * @return  log
+     */
+    private QwGroupChatTransferLog createTransferLog(ChatParam chatParam, CompanyUser user, QwUser newOwner, boolean isResigned) {
+        QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(chatParam.getChatId());
+        QwUser oldOwner = qwUserMapper.selectQwUserById(chatParam.getQwId());
+
+        // 更新群主信息
+        qwGroupChat.setOwner(newOwner.getQwUserId());
+        qwGroupChat.setUpdateTime(new Date());
+        if (isResigned) {
+            qwGroupChat.setStatus("3");
+        }
+        qwGroupChatMapper.updateQwGroupChat(qwGroupChat);
+
+        // 创建转移日志
+        QwGroupChatTransferLog transferLog = new QwGroupChatTransferLog();
+        transferLog.setChatName(qwGroupChat.getName());
+        transferLog.setCorpId(qwGroupChat.getCorpId());
+        transferLog.setTransferType(isResigned ? 1 : 0);
+        transferLog.setCompanyId(user.getCompanyId());
+        transferLog.setCompanyUserId(user.getUserId());
+        transferLog.setOldOwner(oldOwner.getQwUserId());
+        transferLog.setOldCompanyUserId(oldOwner.getCompanyUserId());
+        transferLog.setOldQwUserName(oldOwner.getQwUserName());
+        transferLog.setOldQwUserId(oldOwner.getId());
+        transferLog.setNewOwner(newOwner.getQwUserId());
+        transferLog.setNewCompanyUserId(newOwner.getCompanyUserId());
+        transferLog.setNewQwUserName(newOwner.getQwUserName());
+        transferLog.setNewQwUserId(newOwner.getId());
+        transferLog.setChatId(chatParam.getChatId());
+        transferLog.setCreateTime(LocalDateTime.now());
+
+        return transferLog;
+    }
+
 }

+ 29 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatTransferLogServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.qw.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.mapper.QwGroupChatTransferLogMapper;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.service.IQwGroupChatTransferLogService;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+@Service
+@Slf4j
+public class QwGroupChatTransferLogServiceImpl extends ServiceImpl<QwGroupChatTransferLogMapper, QwGroupChatTransferLog>
+        implements IQwGroupChatTransferLogService {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return list
+     */
+    @Override
+    public List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param) {
+        return baseMapper.selectQwGroupChatTransferVOList(param);
+    }
+}

+ 51 - 0
fs-service/src/main/java/com/fs/qw/vo/QwGroupChatTransferLogVO.java

@@ -0,0 +1,51 @@
+package com.fs.qw.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class QwGroupChatTransferLogVO {
+    /**
+     * 主键ID
+     */
+    private Long id;
+    /**
+     * 企微主体
+     */
+    private String corpId;
+    /**
+     * 转移类型 0:在职继承 1:离职继承
+     */
+    private Integer transferType;
+    /**
+     * 群聊名称
+     */
+    private String name;
+    /**
+     * 原群主(后台)
+     */
+    private String oldCompanyUserName;
+    /**
+     * 原群主(企微)
+     */
+    private String oldQwUserName;
+    /**
+     * 新群主(后台)
+     */
+    private String newCompanyUserName;
+    /**
+     * 新群主(企微)
+     */
+    private String newQwUserName;
+    /**
+     * 操作人
+     */
+    private String companyUserName;
+    /**
+     * 转移时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+}

+ 47 - 0
fs-service/src/main/java/com/fs/qw/vo/QwGroupChatTransferVO.java

@@ -0,0 +1,47 @@
+package com.fs.qw.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class QwGroupChatTransferVO {
+    /**
+     * 客户群ID
+     */
+    private String chatId;
+    /**
+     * 客户群名称
+     */
+    private String name;
+    /**
+     * 群主ID
+     */
+    private String owner;
+    /**
+     * 群主销售名称
+     */
+    private String ownerName;
+    /**
+     * 群主企微ID
+     */
+    private String qwId;
+    /**
+     * 群主企微名称
+     */
+    private String qwUserName;
+    /**
+     * 公告
+     */
+    private String notice;
+    /**
+     * 企微主体
+     */
+    private String corpId;
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -179,6 +179,20 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     @Override
     public int deleteSopUserLogsInfoByIds(String[] ids) {
+
+        List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(ids);
+        List<Map<String, Object>> result = sopUserLogsInfos.stream()
+                .map(info -> {
+                    Map<String, Object> fields = new HashMap<>();
+                    fields.put("sopId", info.getSopId());
+                    fields.put("exterId", info.getExternalContactId());
+                    fields.put("qwUserId", info.getQwUserId());
+                    fields.put("cropId", info.getCorpId());
+                    return fields;
+                })
+                .collect(Collectors.toList());
+        log.info("删除客户营期-"+result);
+
         return sopUserLogsInfoMapper.deleteSopUserLogsInfoByIds(ids);
     }
 

+ 2 - 2
fs-service/src/main/resources/application-config-druid-jzzx.yml

@@ -75,5 +75,5 @@ headerImg:
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
 wx_miniapp_temp:
-  pay_order_temp_id:
-  inquiry_temp_id:
+  pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek
+  inquiry_temp_id: 9POPYeqhI48LOPvq-Rfoklze7H-9SlunJKh10Qt4_2I

+ 79 - 0
fs-service/src/main/resources/application-config-druid-xfk.yml

@@ -0,0 +1,79 @@
+baidu:
+  token: 12313231232
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  cp:
+    corpId: wwb2a1055fb6c9a7c2
+    appConfigs:
+      - agentId: 1000005
+        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85f8d62769119 #微信公众号或者小程序等的appid
+    mchId: 1611402045 #微信支付商户号
+    mchKey: 8cab128997a3547c1363b0898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx5d516e5b575bae3c # 第一个公众号的appid  //公众号名称:成都九州在线互联网医院
+        secret: c911f6671909a81337c6a15cf45e57fc # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+  #  account: tcloud
+  #  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.16:8010
+  h5CommonApi: http://119.29.195.254:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: xfk-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: xfk
+tmp_secret_config:
+  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
+  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
+  bucket: xfk-1319721001
+  app_id: 1319721001
+  region: ap-chongqing
+  proxy: fs
+cloud_host:
+  company_name: 小访客
+headerImg:
+  imgUrl: https://xiaofangke-1360933944.cos.ap-nanjing.myqcloud.com/xiaofangke/20250610/9c3fb587d224492e8b61f5dece0b8b7b.png
+ipad:
+  ipadUrl: http://ipad.cdwjyyh.com
+wx_miniapp_temp:
+  pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek
+  inquiry_temp_id: 9POPYeqhI48LOPvq-Rfoklze7H-9SlunJKh10Qt4_2I

+ 150 - 0
fs-service/src/main/resources/application-druid-xfk.yml

@@ -0,0 +1,150 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-xfk,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 10.206.16.10
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: Ylrz_1q2w3e4r5t6y
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://10.206.16.9:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://10.206.16.9:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    consumer:
+        group: test-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey

+ 49 - 1
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -16,10 +16,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyId"    column="company_id"    />
         <result property="courseId"    column="course_id"    />
         <result property="uuId"    column="uu_id"    />
+        <result property="periodId"    column="period_id"    />
+        <result property="project"    column="project"    />
     </resultMap>
 
     <sql id="selectFsCourseTrafficLogVo">
-        select log_id, uu_id,user_id, video_id, create_time, qw_external_contact_id, internet_traffic, qw_user_id, company_user_id, company_id, course_id from fs_course_traffic_log
+        select log_id,
+               user_id,
+               video_id,
+               create_time,
+               qw_external_contact_id,
+               internet_traffic,
+               qw_user_id,
+               company_user_id,
+               company_id,
+               course_id,
+               uu_id,
+               project,
+               period_id from fs_course_traffic_log
     </sql>
 
     <select id="selectFsCourseTrafficLogList" parameterType="FsCourseTrafficLog" resultMap="FsCourseTrafficLogResult">
@@ -190,4 +204,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{logId}
         </foreach>
     </delete>
+
+
+
+    <select id="selectCourseTrafficLogByTwoDaysLater" resultMap="FsCourseTrafficLogResult">
+        <![CDATA[
+        SELECT
+            l.log_id,
+            l.user_id,
+            l.video_id,
+            l.create_time,
+            l.qw_external_contact_id,
+            l.internet_traffic,
+            l.qw_user_id,
+            l.company_user_id,
+            l.company_id,
+            l.course_id,
+            l.uu_id,
+            l.project, l.period_id
+        FROM
+            fs_course_traffic_log l
+        WHERE
+            l.create_time < DATE_SUB( NOW( ), INTERVAL 2 DAY )
+        ORDER BY l.log_id  limit #{offset},#{limit} ]]>
+    </select>
+
+
+    <insert id="insertCourseTrafficLogByTwoDaysLaterBatch" useGeneratedKeys="false">
+        insert into fs_course_traffic_log (
+        log_id, user_id, video_id, create_time, qw_external_contact_id, internet_traffic, qw_user_id, company_user_id, company_id, course_id, uu_id, project, period_id ) values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.logId},#{item.userId},#{item.videoId},#{item.createTime},#{item.qwExternalContactId},#{item.internetTraffic},#{item.qwUserId}
+            ,#{item.companyUserId},#{item.companyId},#{item.courseId},#{item.uuId},#{item.project},#{item.periodId})
+        </foreach>
+    </insert>
 </mapper>

+ 32 - 0
fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml

@@ -52,6 +52,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectQwGroupChatByChatIds" resultType="com.fs.qw.domain.QwGroupChat">
         select * from qw_group_chat where chat_id in <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
     </select>
+
+    <select id="selectQwGroupChatTransferList" resultType="com.fs.qw.vo.QwGroupChatTransferVO">
+        select
+            gc.*,
+            qu.id qwId,
+            qu.qw_user_name,
+            cu.nick_name ownerName
+        from qw_group_chat gc
+        left join qw_user qu on qu.qw_user_id = gc.owner and qu.is_del = 0 and qu.corp_id = gc.corp_id
+        left join company_user cu on cu.user_id = qu.company_user_id
+        <where>
+            <if test="corpId != null and corpId != ''">
+                and gc.corp_id = #{corpId}
+            </if>
+            <if test="name != null and name != ''">
+                and gc.name like concat('%', #{name}, '%')
+            </if>
+            <if test="ownerName != null and ownerName != ''">
+                and cu.nick_name like concat('%', #{ownerName}, '%')
+            </if>
+            <if test="qwName != null and qwName != ''">
+                and qu.qw_user_name like concat('%', #{qwName}, '%')
+            </if>
+            <if test="statusList != null and statusList.size() > 0">
+                and gc.status in
+                <foreach collection="statusList" separator="," item="item" open="(" close=")">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        order by gc.update_time desc
+    </select>
     <insert id="insertOrUpdateQwGroupChat" parameterType="QwGroupChat">
         insert into qw_group_chat
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 33 - 0
fs-service/src/main/resources/mapper/qw/QwGroupChatTransferLogMapper.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.qw.mapper.QwGroupChatTransferLogMapper">
+
+    <select id="selectQwGroupChatTransferVOList" resultType="com.fs.qw.vo.QwGroupChatTransferLogVO">
+        select
+            tctl.id,
+            tctl.chat_name name,
+            old.nick_name oldCompanyUserName,
+            tctl.old_qw_user_name,
+            new.nick_name newCompanyUserName,
+            tctl.new_qw_user_name,
+            tctl.transfer_type,
+            user.nick_name companyUserName,
+            tctl.corp_id,
+            tctl.create_time
+        from qw_group_chat_transfer_log tctl
+        left join company_user old on old.user_id = tctl.old_company_user_id
+        left join company_user new on new.user_id = tctl.new_company_user_id
+        left join company_user user on user.user_id = tctl.company_user_id
+        <where>
+            <if test="corpId != null and corpId != ''">
+                and tctl.corp_id = #{corpId}
+            </if>
+            <if test="name != null and name != ''">
+                and tctl.chat_name like concat('%', #{name}, '%')
+            </if>
+        </where>
+        order by tctl.create_time desc
+    </select>
+</mapper>