Просмотр исходного кода

Merge remote-tracking branch 'origin/bjcz_his_scrm' into bjcz_his_scrm

yjwang 2 дней назад
Родитель
Сommit
afaf9234e2
29 измененных файлов с 581 добавлено и 126 удалено
  1. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  2. 1 1
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  3. 24 24
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  4. 3 4
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsInfoController.java
  5. 41 14
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java
  6. 1 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingServiceImpl.java
  7. 2 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java
  8. 5 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  9. 14 2
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/service/IUserProjectRepeatMaintainService.java
  11. 52 0
      fs-service/src/main/java/com/fs/course/service/impl/UserProjectRepeatMaintainServiceImpl.java
  12. 6 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  13. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
  14. 10 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatUserMapper.java
  15. 6 0
      fs-service/src/main/java/com/fs/qw/param/ResignedTransferParam.java
  16. 6 0
      fs-service/src/main/java/com/fs/qw/param/TransferParam.java
  17. 6 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  18. 221 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  19. 64 54
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactTransferLogServiceImpl.java
  20. 5 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  21. 4 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatUserServiceImpl.java
  22. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceAsyncHelperImpl.java
  23. 18 12
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  24. 2 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  25. 18 3
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  26. 5 1
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  27. 1 1
      fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml
  28. 53 0
      fs-service/src/main/resources/mapper/qw/QwGroupChatUserMapper.xml
  29. 1 1
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

+ 1 - 1
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -256,7 +256,7 @@ public class FsUserVideoController extends BaseController
         String[] command = {
                 "ffmpeg",
                 "-i", videoPath,
-                "-ss", "00:00:03.000", // 定位到第3秒
+//                "-ss", "00:00:03.000", // 定位到第3秒
                 "-frames:v", "1",        // 只取1帧
                 "-f", "image2",
                 "-update", "1",

+ 1 - 1
fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -168,7 +168,7 @@ public class FsCompanyController extends BaseController {
     {
         company.setMoney(null);
         company.setUpdateMiniApp(true);
-        return toAjax(companyService.updateCompany(company));
+        return toAjax(companyService.updateFsCompany(company));
     }
 
     /**

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

@@ -518,25 +518,20 @@ public class QwExternalContactController extends BaseController
 
 
 
-
+    /**
+     * 离职继承 分配客户
+     */
     @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
     @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
     @PutMapping("/resignedTransfer")
     public R resignedTransfer(@RequestBody ResignedTransferParam param)
     {
-        if (ObjectUtil.isNotEmpty(param.getQwUserName())&&ObjectUtil.isNotEmpty(param.getType())&&param.getType().equals("1")){
-            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);
-            }
+        if (param.isFilter()) {
+            param.setIds(getList(param.getAddType(), param.getParam()));
+        }
+        if (param.getIds() == null || param.getIds().isEmpty()) {
+            return R.error("请选择需要分配的客户");
         }
-
         return qwExternalContactService.resignedTransfer(param);
     }
 
@@ -548,16 +543,11 @@ public class QwExternalContactController extends BaseController
     @PutMapping("/transfer")
     public R transfer(@RequestBody TransferParam param)
     {
-        if (ObjectUtil.isNotEmpty(param.getQwUserName())&&ObjectUtil.isNotEmpty(param.getType())&&param.getType().equals("1")){
-            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);
-            }
+        if (param.isFilter()) {
+            param.setIds(getList(param.getAddType(), param.getParam()));
+        }
+        if (param.getIds() == null || param.getIds().isEmpty()) {
+            return R.error("请选择需要分配的客户");
         }
         return qwExternalContactService.transfer(param);
     }
@@ -839,10 +829,20 @@ public class QwExternalContactController extends BaseController
         return new ArrayList<>(set);
     }
 
+    private void prepareQueryParam(QwExternalContactParam param) {
+        if (param == null) {
+            return;
+        }
+        if (StringUtils.isNotEmpty(param.getStatuses())) {
+            param.setStatusCondition(param.getStatuses().split(","));
+        }
+    }
+
     private List<Long> getList(Integer addType, QwExternalContactParam param){
-        if(addType == null){
+        if(addType == null || param == null){
             return Collections.emptyList();
         }
+        prepareQueryParam(param);
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(addType == 0){
             param.setCompanyId(loginUser.getCompany().getCompanyId());

+ 3 - 4
fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsInfoController.java

@@ -83,6 +83,7 @@ public class SopUserLogsInfoController extends BaseController
         //startPage();
         if (sopUserLogsInfo.getFilterMode() == 1) {
             List<SopUserLogsInfo> list = sopUserLogsInfoService.selectSopUserLogsInfoList(sopUserLogsInfo);
+            list.forEach(item -> item.setLevel(item.getGrade()));
 
             if (!list.isEmpty()) {
                 // 使用并行流提取externalId
@@ -98,7 +99,7 @@ public class SopUserLogsInfoController extends BaseController
                 Map<Long, SopExternalContactInfo> externalContactInfoMap = qwExternalContactVOTimes.stream()
                         .collect(Collectors.toMap(
                                 QwExternalContactVOTime::getId,
-                                item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIds(), item.getRemark(),item.getLevel())
+                                item -> new SopExternalContactInfo(item.getCreateTime(), item.getTagIds(), item.getRemark(), null)
                         ));
 
                 List<String> tagIds = qwExternalContactVOTimes.stream().map(QwExternalContactVOTime::getTagIds).filter(StringUtils::isNotEmpty).flatMap(e -> JSON.parseArray(e, String.class).stream()).collect(Collectors.toList());
@@ -117,12 +118,10 @@ public class SopUserLogsInfoController extends BaseController
                 list.forEach(item -> {
                     SopExternalContactInfo info = externalContactInfoMap.getOrDefault(
                             item.getExternalId(),
-                            new SopExternalContactInfo("无进线时间", "无标签", "无备注",0));
+                            new SopExternalContactInfo("无进线时间", "无标签", "无备注", null));
                     item.setInComTime(info.getCreateTime());
                     item.setTagIds(info.getTagIds());
                     item.setRemark(info.getRemark());
-                    item.setLevel(info.getLevel());
-
                 });
             }
 

+ 41 - 14
fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java

@@ -27,6 +27,7 @@ import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.*;
@@ -209,13 +210,8 @@ public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExtern
 
             // 如果连续3天没有看课记录,标记为E级
             if (ratingVOS == null || ratingVOS.isEmpty()) {
-                log.info("连续{}天没有看课记录,标记为E级,externalId: {}", config.getNotStudyDays(), externalId);
-                QwExternalContact externalContact = new QwExternalContact();
-                externalContact.setId(externalId);
-                externalContact.setLevel(5);  // E级
-                externalContact.setLevelType(2);  // 降级(从ABCD任何等级变为E级都是降级)
-                externalContact.setIsDaysNotStudy(1);
-                return externalContact;
+                return buildELevelContactIfEligible(logsInfo, config,
+                        "连续{}天没有看课记录,标记为E级,externalId: {}");
             }
 
             // 判断连续3天的总观看时长是否大于0
@@ -223,18 +219,14 @@ public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExtern
 
             if (!hasWatchDuration) {
                 // 有记录但观看时长为0,也认为连续3天未看课,标记为E级
-                log.info("连续{}天看课时长为0,标记为E级,externalId: {}", config.getNotStudyDays(), externalId);
-                QwExternalContact externalContact = new QwExternalContact();
-                externalContact.setId(externalId);
-                externalContact.setLevel(5);  // E级
-                externalContact.setLevelType(2);  // E级类型
-                externalContact.setIsDaysNotStudy(1);
-                return externalContact;
+                return buildELevelContactIfEligible(logsInfo, config,
+                        "连续{}天看课时长为0,标记为E级,externalId: {}");
             } else {
                 // 有看课记录且时长>0,不是E级,恢复为ABCD评级
                 log.info("最近{}天有看课记录,不是E级,externalId: {}", config.getNotStudyDays(), externalId);
                 QwExternalContact externalContact = new QwExternalContact();
                 externalContact.setId(externalId);
+                externalContact.setSopUserLogsInfoId(logsInfo.getId());
                 // 检查当前等级是否为E级,如果是则需要重新计算
                 Integer currentLevel = ratingVOS.get(0).getLevel();
                 if (currentLevel != null && currentLevel == 5) {
@@ -342,6 +334,41 @@ public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExtern
     }
 
 
+    /**
+     * 转接客户(is_transfer=1)需 create_time 超过3天才参与E级评定,其他客户不受影响。
+     */
+    private boolean canRateTransferContactAsELevel(Long externalId) {
+        QwExternalContact contact = qwExternalContactMapper.selectQwExternalContactById(externalId);
+        if (contact == null) {
+            return true;
+        }
+        if (contact.getIsTransfer() == null || contact.getIsTransfer() != 1) {
+            return true;
+        }
+        Date createTime = contact.getCreateTime();
+        if (createTime == null) {
+            return true;
+        }
+        long daysSinceCreate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - createTime.getTime());
+        return daysSinceCreate > 3;
+    }
+
+    private QwExternalContact buildELevelContactIfEligible(SopUserLogsInfo logsInfo, QwRatingConfig config, String logPattern) {
+        Long externalId = logsInfo.getExternalId();
+        if (!canRateTransferContactAsELevel(externalId)) {
+            log.info("转接客户创建未满3天,跳过E级评定,externalId: {}", externalId);
+            return null;
+        }
+        log.info(logPattern, config.getNotStudyDays(), externalId);
+        QwExternalContact externalContact = new QwExternalContact();
+        externalContact.setId(externalId);
+        externalContact.setSopUserLogsInfoId(logsInfo.getId());
+        externalContact.setLevel(5);
+        externalContact.setLevelType(2);
+        externalContact.setIsDaysNotStudy(1);
+        return externalContact;
+    }
+
     //查 E级
     public boolean getScoreMoreStudyLevel(List<QwRatingVO> qwRatingVOS) {
 

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

@@ -229,6 +229,7 @@ public class QwExternalContactRatingServiceImpl implements QwExternalContactRati
 
             QwExternalContact externalContact = new QwExternalContact();
             externalContact.setId(externalId);
+            externalContact.setSopUserLogsInfoId(logsInfo.getId());
             externalContact.setLevel(scoreLevel);
             externalContact.setLastWatchTime(latestTime);
             externalContact.setLevelType(levelUpFall);

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

@@ -246,10 +246,11 @@ public class SopUserLogsInfoByIsDaysNotStudyImpl implements SopUserLogsInfoByIsD
             if (sumDuration!=null && sumDuration>0) {
                 QwExternalContact externalContact = new QwExternalContact();
                 externalContact.setId(externalId);
+                externalContact.setSopUserLogsInfoId(logsInfo.getId());
                 externalContact.setLevel(4);  // 从E级恢复到D级
                 externalContact.setLevelType(1);  // 升级
                 externalContact.setIsDaysNotStudy(0);  // 标记为非连续未看课
-                log.info("客户从E级恢复到D级,externalId: {}", externalId);
+                log.info("客户从E级恢复到D级,externalId: {}, logsInfoId: {}", externalId, logsInfo.getId());
                 return externalContact;
             }
 

+ 5 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -66,6 +66,11 @@ public interface ICompanyService
      */
     public int updateCompany(Company company);
 
+    /**
+     * 修改销售公司(强制评级变更时同步SOP)
+     */
+    int updateFsCompany(Company company);
+
     /**
      * 批量删除企业
      *

+ 14 - 2
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -602,8 +602,20 @@ public class CompanyServiceImpl implements ICompanyService
         if(company.isUpdateMiniApp()){
             bindMiniApp(company);
         }
-        int rows = companyMapper.updateCompany(company);
-        if (rows > 0 && company.getLevel() != null && company.getCompanyId() != null) {
+        return companyMapper.updateCompany(company);
+    }
+
+    @Override
+    public int updateFsCompany(Company company)
+    {
+        Integer oldLevel = null;
+        if (company.getCompanyId() != null && company.getLevel() != null) {
+            Company oldCompany = companyMapper.selectCompanyById(company.getCompanyId());
+            oldLevel = oldCompany != null ? oldCompany.getLevel() : null;
+        }
+        int rows = updateCompany(company);
+        if (rows > 0 && company.getLevel() != null && company.getCompanyId() != null
+                && !Objects.equals(oldLevel, company.getLevel())) {
             qwSopMapper.updateIsRatingByCompanyId(company.getCompanyId(), company.getLevel());
         }
         return rows;

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

@@ -25,4 +25,9 @@ public interface IUserProjectRepeatMaintainService {
      * 判断用户在某项目下绑定指定企微销售后是否会成为重粉(写入前判重,走中间表)。
      */
     boolean wouldBeRepeatAfterBind(Long fsUserId, Long projectId, Long qwUserId);
+
+    /**
+     * 转接完成:将重粉中间表里原销售的 qw_user_id 迁移到新销售,并刷新重粉标记。
+     */
+    void migrateQwUserOnTransfer(Long fsUserId, Long oldQwUserId, Long newQwUserId, Long companyId);
 }

+ 52 - 0
fs-service/src/main/java/com/fs/course/service/impl/UserProjectRepeatMaintainServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.course.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.course.domain.FsUserProjectQw;
 import com.fs.course.domain.FsUserProjectRepeat;
 import com.fs.course.mapper.FsUserProjectQwMapper;
 import com.fs.course.mapper.FsUserProjectRepeatMapper;
@@ -10,7 +11,10 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 @Slf4j
 @Service
@@ -89,4 +93,52 @@ public class UserProjectRepeatMaintainServiceImpl implements IUserProjectRepeatM
         }
         return fsUserProjectQwMapper.existsQwUserInProjects(fsUserId, qwUserId, equivalentProjectIds) == 0;
     }
+
+    @Override
+    public void migrateQwUserOnTransfer(Long fsUserId, Long oldQwUserId, Long newQwUserId, Long companyId) {
+        if (fsUserId == null || oldQwUserId == null || newQwUserId == null || oldQwUserId.equals(newQwUserId)) {
+            return;
+        }
+        try {
+            List<FsUserProjectQw> oldRows = fsUserProjectQwMapper.selectList(
+                    new QueryWrapper<FsUserProjectQw>()
+                            .eq("fs_user_id", fsUserId)
+                            .eq("qw_user_id", oldQwUserId));
+            if (oldRows.isEmpty()) {
+                return;
+            }
+
+            Set<Long> projectsToRefresh = new HashSet<>();
+            for (FsUserProjectQw row : oldRows) {
+                projectsToRefresh.add(row.getProjectId());
+                FsUserProjectQw existingNew = fsUserProjectQwMapper.selectOne(
+                        new QueryWrapper<FsUserProjectQw>()
+                                .eq("fs_user_id", fsUserId)
+                                .eq("project_id", row.getProjectId())
+                                .eq("qw_user_id", newQwUserId)
+                                .last("LIMIT 1"));
+                if (existingNew != null) {
+                    fsUserProjectQwMapper.deleteById(row.getId());
+                } else {
+                    FsUserProjectQw update = new FsUserProjectQw();
+                    update.setId(row.getId());
+                    update.setQwUserId(newQwUserId);
+                    update.setUpdateTime(new Date());
+                    if (companyId != null) {
+                        update.setCompanyId(companyId);
+                    }
+                    fsUserProjectQwMapper.updateById(update);
+                }
+            }
+
+            for (Long projectId : projectsToRefresh) {
+                maintain(fsUserId, projectId, newQwUserId, companyId);
+            }
+            log.info("转接迁移重粉中间表成功 fsUserId={}, oldQwUserId={}, newQwUserId={}, projects={}",
+                    fsUserId, oldQwUserId, newQwUserId, projectsToRefresh);
+        } catch (Exception e) {
+            log.error("转接迁移重粉中间表失败 fsUserId={}, oldQwUserId={}, newQwUserId={}",
+                    fsUserId, oldQwUserId, newQwUserId, e);
+        }
+    }
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java

@@ -126,6 +126,10 @@ public class QwExternalContact extends BaseEntity
     @TableField(exist = false)
     private Integer isDaysNotStudy;
 
+    /** sop_user_logs_info 主键,营期客户记录按条更新评级/未看课标记 */
+    @TableField(exist = false)
+    private String sopUserLogsInfoId;
+
     private Integer levelType;
 
     @JsonFormat(pattern = "yyyy-MM-dd")
@@ -149,6 +153,8 @@ public class QwExternalContact extends BaseEntity
     private Integer isReply;
     // 是否被邀请进群0否1是
     private Integer joinGroup;
+    // 是否转接客户 0否 1是
+    private Integer isTransfer;
     // 广告链路唯一id
     private String traceId;
 

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

@@ -43,7 +43,7 @@ public interface QwGroupChatMapper
             "    ANY_VALUE(qu.qw_user_name) as qwName, " +
             "    COUNT(CASE WHEN gcu.is_out = 1 AND DATE(gcu.join_time) = CURDATE() THEN 1 END) AS todayJoinByChartUser, " +
             "    COUNT(CASE WHEN gcu.is_out = 2 AND DATE(gcu.out_time) = CURDATE() THEN 1 END) AS todayOutByChartUser, " +
-            "    COUNT(CASE WHEN gcu.is_out = 1 THEN gcu.chat_id END) AS groupSizeByChartUser, " +
+            "    ANY_VALUE(gc.group_size) AS groupSizeByChartUser, " +
             "    gc.* " +
             "FROM " +
             "    qw_group_chat gc " +

+ 10 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatUserMapper.java

@@ -128,6 +128,16 @@ public interface QwGroupChatUserMapper
     public int insertQwGroupChatUser(QwGroupChatUser qwGroupChatUser);
     public int insertOrUpdateQwGroupChatUser(QwGroupChatUser qwGroupChatUser);
 
+    /**
+     * 全量同步前:将该群当前在群/未标记成员先标为退群,再由 API 成员列表回写为在群
+     */
+    int markActiveMembersOutByChatId(@Param("chatId") String chatId, @Param("corpId") String corpId);
+
+    /**
+     * 全量同步成员:强制写入在群状态并清除退群信息
+     */
+    int insertOrUpdateSyncGroupChatUser(QwGroupChatUser qwGroupChatUser);
+
     public QwGroupChatUser selectQwGroupChatUserByExternalUserId(QwGroupChatUser qwGroupChatUser);
 
     /**

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

@@ -13,4 +13,10 @@ public class ResignedTransferParam {
     String type;
     // 是否需要清除标签
     private Integer needClearTag = 0;
+    /** 是否按筛选条件分配(不受分页限制) */
+    private boolean filter;
+    /** 筛选范围:0公司客户 1我的客户 2部门客户 */
+    private Integer addType;
+    /** 列表筛选条件 */
+    private QwExternalContactParam param;
 }

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

@@ -14,4 +14,10 @@ public class TransferParam {
     String type;
     // 是否需要清除标签
     private Integer needClearTag = 0;
+    /** 是否按筛选条件分配(不受分页限制) */
+    private boolean filter;
+    /** 筛选范围:0公司客户 1我的客户 2部门客户 */
+    private Integer addType;
+    /** 列表筛选条件 */
+    private QwExternalContactParam param;
 }

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

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwExternalContactTransferLog;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
@@ -139,6 +140,11 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     void transferFailByExternalUserId(String externalUserID, String userID, String corpId,String failReason);
 
+    /**
+     * 转接完成:设置 is_transfer=1 并继承 fs_user_id
+     */
+    void applyTransferContactInfo(Long newContactId, QwExternalContactTransferLog transferLog, Long oldContactId);
+
     int updateQwExternalContactStatus(QwExternalContact qwExternalContact);
     /**
      *  企业微信客户与企业客户绑定

+ 221 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -31,6 +31,7 @@ import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.IUserProjectRepeatMaintainService;
 import com.fs.course.vo.FsUserCourseVideoQVO;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.mapper.CrmCustomerMapper;
@@ -217,6 +218,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     private FsUserCourseMapper fsUserCourseMapper;
 
+    @Autowired
+    private IUserProjectRepeatMaintainService userProjectRepeatMaintainService;
+
     @Autowired
     private IQwExternalErrRetryService errRetryService;
 
@@ -1190,7 +1194,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                     qwExternalContactTransferLog.setTakeoverUserId(qwUser.getQwUserId());
                                     qwExternalContactTransferLog.setCorpId(param.getCorpId());
                                     qwExternalContactTransferLog.setExternalUserId(qwCustomer.getExternal_userid());
+                                    qwExternalContactTransferLog.setHandoverQwUserId(qwExternalContact.getQwUserId());
                                     qwExternalContactTransferLog.setNeedClearTag(param.getNeedClearTag());
+                                    qwExternalContactTransferLog.setFsUserId(qwExternalContact.getFsUserId());
                                     qwExternalContactTransferLogMapper.insertQwExternalContactTransferLog(qwExternalContactTransferLog);
                                     QwExternalContact qwExternal = new QwExternalContact();
                                     qwExternal.setStatus(2);
@@ -2002,6 +2008,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             contact1.setTransferStatus(1);
             contact1.setExternalUserId(transferLogListByCheck.getExternalUserId());
             qwExternalContactMapper.updateQwExternalContactByUseridTransfer(contact1);
+            applyTransferContactInfo(qwExternalContact.getId(), transferLogListByCheck, null);
         }
 
 
@@ -2795,9 +2802,22 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             qwExternalContactMapper.updateQwExternalContactByUseridTransfer(contact1);
         }
 
+        Long transferOldContactId = null;
+        if (transferLogListByCheck != null) {
+            transferOldContactId = transferLogListByCheck.getExternalContactId();
+        } else if (!auditUserList.isEmpty()) {
+            transferOldContactId = auditUserList.get(0).getExternalId();
+        }
         //上面存过了,这里就更新
         qwExternalContact.setId(contact.getId());
+        if (transferLogListByCheck != null || !auditUserList.isEmpty()) {
+            fillTransferContactInfo(qwExternalContact, transferLogListByCheck, transferOldContactId);
+            applyTransferRemarkAndTags(qwExternalContact, transferLogListByCheck, transferOldContactId, needClearTag);
+        }
         qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+        if (transferLogListByCheck != null || !auditUserList.isEmpty()) {
+            migrateTransferProjectRepeat(transferLogListByCheck, transferOldContactId, qwExternalContact);
+        }
         QwOpenidByExternalcontactParams externalcontactParams = new QwOpenidByExternalcontactParams();
         externalcontactParams.setExternal_userid(externalUserID);
         //录入单独的CRM客户信息  没啥用
@@ -2825,8 +2845,10 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         }
         /*只有ddgy加重粉*/
         if (("叮当国医".equals(cloudHostProper.getCompanyName()))) {
-            if (qwExternalContactMapper.selectCount(new LambdaQueryWrapper<QwExternalContact>().eq(QwExternalContact::getExternalUserId, qwExternalContact.getExternalUserId()))
-            >1){
+            long activeCount = qwExternalContactMapper.selectCount(new LambdaQueryWrapper<QwExternalContact>()
+                    .eq(QwExternalContact::getExternalUserId, qwExternalContact.getExternalUserId())
+                    .ne(QwExternalContact::getStatus, 4));
+            if (activeCount > 1){
                 qwExternalContact.setIsRepeat(1);
                 qwExternalContactMapper.update(null,new LambdaUpdateWrapper<QwExternalContact>().eq(QwExternalContact::getId,qwExternalContact.getId())
                         .set(QwExternalContact::getIsRepeat,1));
@@ -3365,7 +3387,15 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
         //上面存过了,这里就更新
         qwExternalContact.setId(contact.getId());
+        if (transferLogListByCheck != null) {
+            fillTransferContactInfo(qwExternalContact, transferLogListByCheck, transferLogListByCheck.getExternalContactId());
+            boolean needClearTag = transferLogListByCheck.getNeedClearTag() != null && transferLogListByCheck.getNeedClearTag() == 1;
+            applyTransferRemarkAndTags(qwExternalContact, transferLogListByCheck, transferLogListByCheck.getExternalContactId(), needClearTag);
+        }
         qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+        if (transferLogListByCheck != null) {
+            migrateTransferProjectRepeat(transferLogListByCheck, transferLogListByCheck.getExternalContactId(), qwExternalContact);
+        }
         QwOpenidByExternalcontactParams externalcontactParams = new QwOpenidByExternalcontactParams();
         externalcontactParams.setExternal_userid(externalUserID);
         //录入单独的CRM客户信息  没啥用
@@ -4455,6 +4485,195 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     }
 
+    @Override
+    public void applyTransferContactInfo(Long newContactId, QwExternalContactTransferLog transferLog, Long oldContactId) {
+        if (newContactId == null) {
+            return;
+        }
+        QwExternalContact update = new QwExternalContact();
+        update.setId(newContactId);
+        fillTransferContactInfo(update, transferLog, oldContactId);
+        qwExternalContactMapper.updateQwExternalContact(update);
+        QwExternalContact newContact = qwExternalContactMapper.selectQwExternalContactById(newContactId);
+        if (newContact != null) {
+            applyTransferRemarkAndTags(newContact, transferLog, oldContactId);
+            updateTransferRemarkAndTagsInDb(newContactId, newContact);
+            newContact.setFsUserId(update.getFsUserId());
+            newContact.setIsTransfer(update.getIsTransfer());
+            migrateTransferProjectRepeat(transferLog, oldContactId, newContact);
+        }
+    }
+
+    private void migrateTransferProjectRepeat(QwExternalContactTransferLog transferLog, Long oldContactId, QwExternalContact newContact) {
+        if (newContact == null || newContact.getFsUserId() == null || newContact.getQwUserId() == null) {
+            return;
+        }
+        Long oldQwUserId = null;
+        if (transferLog != null && transferLog.getHandoverQwUserId() != null) {
+            oldQwUserId = transferLog.getHandoverQwUserId();
+        }
+        if (oldQwUserId == null && oldContactId != null) {
+            QwExternalContact oldContact = qwExternalContactMapper.selectQwExternalContactById(oldContactId);
+            if (oldContact != null) {
+                oldQwUserId = oldContact.getQwUserId();
+            }
+        }
+        userProjectRepeatMaintainService.migrateQwUserOnTransfer(
+                newContact.getFsUserId(), oldQwUserId, newContact.getQwUserId(), newContact.getCompanyId());
+    }
+
+    private void fillTransferContactInfo(QwExternalContact target, QwExternalContactTransferLog transferLog, Long oldContactId) {
+        target.setIsTransfer(1);
+        Long fsUserId = resolveTransferFsUserId(transferLog, oldContactId);
+        if (fsUserId != null) {
+            target.setFsUserId(fsUserId);
+        }
+    }
+
+    private Long resolveTransferFsUserId(QwExternalContactTransferLog transferLog, Long oldContactId) {
+        if (transferLog != null && transferLog.getFsUserId() != null) {
+            return transferLog.getFsUserId();
+        }
+        QwExternalContact oldContact = resolveOldTransferContact(transferLog, oldContactId);
+        return oldContact == null ? null : oldContact.getFsUserId();
+    }
+
+    private QwExternalContact resolveOldTransferContact(QwExternalContactTransferLog transferLog, Long oldContactId) {
+        Long sourceContactId = oldContactId;
+        if (sourceContactId == null && transferLog != null) {
+            sourceContactId = transferLog.getExternalContactId();
+        }
+        if (sourceContactId == null) {
+            return null;
+        }
+        return qwExternalContactMapper.selectQwExternalContactById(sourceContactId);
+    }
+
+    private void applyTransferRemarkAndTags(QwExternalContact newContact, QwExternalContactTransferLog transferLog, Long oldContactId) {
+        applyTransferRemarkAndTags(newContact, transferLog, oldContactId, null);
+    }
+
+    private void applyTransferRemarkAndTags(QwExternalContact newContact, QwExternalContactTransferLog transferLog,
+                                            Long oldContactId, Boolean needClearTagOverride) {
+        if (newContact == null) {
+            return;
+        }
+        QwExternalContact oldContact = resolveOldTransferContact(transferLog, oldContactId);
+        if (oldContact == null) {
+            return;
+        }
+        boolean needClearTag = needClearTagOverride != null
+                ? needClearTagOverride
+                : (transferLog != null && Integer.valueOf(1).equals(transferLog.getNeedClearTag()));
+
+        String userId = newContact.getUserId();
+        String externalUserId = newContact.getExternalUserId();
+        String corpId = newContact.getCorpId();
+        if (StringUtil.strIsNullOrEmpty(userId) || StringUtil.strIsNullOrEmpty(externalUserId) || StringUtil.strIsNullOrEmpty(corpId)) {
+            logger.warn("转接同步备注标签跳过:新客户缺少必要字段 contactId={}", newContact.getId());
+            return;
+        }
+
+        boolean hasRemark = !StringUtil.strIsNullOrEmpty(oldContact.getRemark());
+        boolean hasDescription = !StringUtil.strIsNullOrEmpty(oldContact.getDescription());
+        boolean hasRemarkCorp = !StringUtil.strIsNullOrEmpty(oldContact.getRemarkCorpName());
+        boolean hasRemarkMobiles = !StringUtil.strIsNullOrEmpty(oldContact.getRemarkMobiles())
+                && !"[]".equals(oldContact.getRemarkMobiles().trim());
+
+        if (hasRemark) {
+            newContact.setRemark(oldContact.getRemark());
+        }
+        if (hasDescription) {
+            newContact.setDescription(oldContact.getDescription());
+        }
+        if (hasRemarkCorp) {
+            newContact.setRemarkCorpName(oldContact.getRemarkCorpName());
+        }
+        if (hasRemarkMobiles) {
+            newContact.setRemarkMobiles(oldContact.getRemarkMobiles());
+        }
+
+        List<String> tagList = null;
+        if (!needClearTag && !StringUtil.strIsNullOrEmpty(oldContact.getTagIds()) && !"[]".equals(oldContact.getTagIds().trim())) {
+            try {
+                tagList = JSON.parseArray(oldContact.getTagIds(), String.class);
+                if (tagList != null) {
+                    tagList = tagList.stream()
+                            .filter(t -> !StringUtil.strIsNullOrEmpty(t))
+                            .distinct()
+                            .collect(Collectors.toList());
+                }
+                if (tagList != null && !tagList.isEmpty()) {
+                    newContact.setTagIds(JSON.toJSONString(tagList));
+                } else {
+                    tagList = null;
+                }
+            } catch (Exception e) {
+                logger.error("转接解析原客户标签失败 contactId={}, tagIds={}", oldContact.getId(), oldContact.getTagIds(), e);
+            }
+        }
+
+        if (hasRemark || hasDescription || hasRemarkCorp || hasRemarkMobiles) {
+            QwExternalContactRemarkParam remarkParam = new QwExternalContactRemarkParam();
+            remarkParam.setUserid(userId);
+            remarkParam.setExternal_userid(externalUserId);
+            if (hasRemark) {
+                remarkParam.setRemark(oldContact.getRemark());
+            }
+            if (hasDescription) {
+                remarkParam.setDescription(oldContact.getDescription());
+            }
+            if (hasRemarkCorp) {
+                remarkParam.setRemark_company(oldContact.getRemarkCorpName());
+            }
+            if (hasRemarkMobiles) {
+                try {
+                    remarkParam.setRemark_mobiles(JSON.parseArray(oldContact.getRemarkMobiles(), String.class));
+                } catch (Exception e) {
+                    logger.error("转接解析原客户备注手机号失败 contactId={}", oldContact.getId(), e);
+                }
+            }
+            QwExternalContactRemarkResult remarkResult = qwApiService.externalcontactRemark(remarkParam, corpId);
+            if (remarkResult.getErrcode() == 45035) {
+                insertQwExternalErrRetryTool(corpId, JSON.toJSONString(remarkParam), 2, remarkResult.getErrmsg());
+                logger.error("转接同步备注失败-已加入补偿机制:{}", remarkParam);
+            } else if (remarkResult.getErrcode() != 0) {
+                logger.error("转接同步备注失败 errcode={}, errmsg={}, contactId={}",
+                        remarkResult.getErrcode(), remarkResult.getErrmsg(), newContact.getId());
+            } else {
+                logger.info("转接同步备注成功 contactId={}, remark={}", newContact.getId(), oldContact.getRemark());
+            }
+        }
+
+        if (tagList != null && !tagList.isEmpty()) {
+            QwEditUserTagParam tagParam = new QwEditUserTagParam();
+            tagParam.setUserid(userId);
+            tagParam.setExternal_userid(externalUserId);
+            tagParam.setAdd_tag(tagList);
+            QwResult tagResult = qwApiService.editUserTag(tagParam, corpId);
+            if (tagResult.getErrcode() == 45035) {
+                insertQwExternalErrRetryTool(corpId, JSON.toJSONString(tagParam), 1, tagResult.getErrmsg());
+                logger.error("转接同步标签失败-已加入补偿机制:{}", tagParam);
+            } else if (tagResult.getErrcode() != 0) {
+                logger.error("转接同步标签失败 errcode={}, errmsg={}, contactId={}",
+                        tagResult.getErrcode(), tagResult.getErrmsg(), newContact.getId());
+            } else {
+                logger.info("转接同步标签成功 contactId={}, tags={}", newContact.getId(), tagList);
+            }
+        }
+    }
+
+    private void updateTransferRemarkAndTagsInDb(Long contactId, QwExternalContact source) {
+        QwExternalContact update = new QwExternalContact();
+        update.setId(contactId);
+        update.setRemark(source.getRemark());
+        update.setDescription(source.getDescription());
+        update.setTagIds(source.getTagIds());
+        update.setRemarkMobiles(source.getRemarkMobiles());
+        update.setRemarkCorpName(source.getRemarkCorpName());
+        qwExternalContactMapper.updateQwExternalContact(update);
+    }
+
     @Override
     public void transferFailByExternalUserId(String externalUserID, String userID, String corpId, String failReason) {
 

+ 64 - 54
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactTransferLogServiceImpl.java

@@ -143,60 +143,8 @@ public class QwExternalContactTransferLogServiceImpl extends ServiceImpl<QwExter
 
                 List<QwExternalContactTransferLog> transferLogListEntry = entry.getValue();
 
-
-                QwGetTransferParam getTransferParam = new QwGetTransferParam();
-                getTransferParam.setTakeover_userid(keyPartsTakeoverUserId);
-                getTransferParam.setHandover_userid(keyPartsHandoverUserId);
-
-
-                // 第一次请求游标为空
-                String cursor = null;
-                List<QwGetTransferResult.Customer> allCustomers = new ArrayList<>();
-
-                int page = 0;
-                do {
-                    page++;
-
-                    // 设置游标(第一次请求可能不需要设置或设为空)
-                    if (cursor != null) {
-                        getTransferParam.setCursor(cursor);
-                    }
-
-                    // 调用接口
-                    QwGetTransferResult transfer = qwApiService.getTransfer(getTransferParam, keyPartsCorpId);
-
-                    // 检查接口返回是否成功
-                    if (transfer.getErrcode() != 0) {
-                        log.error("获取转移记录失败: errcode={}, errmsg={}, takeover_userid={}, handover_userid={}",
-                                transfer.getErrcode(), transfer.getErrmsg(),
-                                keyPartsTakeoverUserId, keyPartsHandoverUserId);
-                        break;
-                    }
-
-                    // 获取当前页数据
-                    List<QwGetTransferResult.Customer> customers = transfer.getCustomer();
-                    if (customers != null && !customers.isEmpty()) {
-                        allCustomers.addAll(customers);
-                        log.info("第 {} 页获取到 {} 条转移记录", page, customers.size());
-                    } else {
-                        log.info("第 {} 页没有数据", page);
-                        // 如果没有数据,也退出循环
-                        break;
-                    }
-
-                    // 更新游标,准备下一次请求
-                    cursor = transfer.getNext_cursor();
-
-                    // 添加短暂延时,避免请求过快
-                    try {
-                        Thread.sleep(200);
-                    } catch (InterruptedException e) {
-                        Thread.currentThread().interrupt();
-                        break;
-                    }
-
-                    // 如果next_cursor为空或没有更多数据,则退出循环
-                } while (cursor != null && !cursor.isEmpty() && !"0".equals(cursor));
+                List<QwGetTransferResult.Customer> allCustomers = fetchAllTransferCustomers(
+                        keyPartsCorpId, keyPartsTakeoverUserId, keyPartsHandoverUserId);
 
                 List<QwExternalContactTransferLog> transferLogList=new ArrayList<>();
 
@@ -225,6 +173,13 @@ public class QwExternalContactTransferLogServiceImpl extends ServiceImpl<QwExter
                             qwExternalContact.setUserId(transferLog.getTakeoverUserId());
                             qwExternalContact.setQwUserId(transferLog.getQwUserId());
                             qwExternalContact.setCompanyUserId(transferLog.getCompanyUserId());
+
+                            QwExternalContact takeoverContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(
+                                    transferLog.getExternalUserId(), transferLog.getTakeoverUserId(), transferLog.getCorpId());
+                            if (takeoverContact != null) {
+                                iQwExternalContactService.applyTransferContactInfo(
+                                        takeoverContact.getId(), transferLog, transferLog.getExternalContactId());
+                            }
                         }
                         contactList.add(qwExternalContact);
                     }
@@ -264,4 +219,59 @@ public class QwExternalContactTransferLogServiceImpl extends ServiceImpl<QwExter
         qwExternalContactTransferLogMapper.updateQwExternalContactByStatus();
         qwExternalContactTransferLogMapper.updateQwExternalContactTransferLogByStatus();
     }
+
+    private List<QwGetTransferResult.Customer> fetchAllTransferCustomers(String corpId, String takeoverUserId, String handoverUserId) {
+        List<QwGetTransferResult.Customer> onJobCustomers = fetchTransferCustomers(corpId, takeoverUserId, handoverUserId, true);
+        if (!onJobCustomers.isEmpty()) {
+            return onJobCustomers;
+        }
+        return fetchTransferCustomers(corpId, takeoverUserId, handoverUserId, false);
+    }
+
+    private List<QwGetTransferResult.Customer> fetchTransferCustomers(String corpId, String takeoverUserId, String handoverUserId, boolean onJobTransfer) {
+        QwGetTransferParam getTransferParam = new QwGetTransferParam();
+        getTransferParam.setTakeover_userid(takeoverUserId);
+        getTransferParam.setHandover_userid(handoverUserId);
+
+        String cursor = null;
+        List<QwGetTransferResult.Customer> allCustomers = new ArrayList<>();
+        int page = 0;
+
+        do {
+            page++;
+            if (cursor != null) {
+                getTransferParam.setCursor(cursor);
+            }
+
+            QwGetTransferResult transfer = onJobTransfer
+                    ? qwApiService.getTransfer(getTransferParam, corpId)
+                    : qwApiService.getResignedTransfer(getTransferParam, corpId);
+
+            if (transfer.getErrcode() != 0) {
+                log.warn("获取{}转移记录失败: errcode={}, errmsg={}, takeover_userid={}, handover_userid={}",
+                        onJobTransfer ? "在职" : "离职",
+                        transfer.getErrcode(), transfer.getErrmsg(),
+                        takeoverUserId, handoverUserId);
+                break;
+            }
+
+            List<QwGetTransferResult.Customer> customers = transfer.getCustomer();
+            if (customers == null || customers.isEmpty()) {
+                break;
+            }
+
+            allCustomers.addAll(customers);
+            log.info("获取{}转移记录第 {} 页: {} 条", onJobTransfer ? "在职" : "离职", page, customers.size());
+
+            cursor = transfer.getNext_cursor();
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                break;
+            }
+        } while (cursor != null && !cursor.isEmpty() && !"0".equals(cursor));
+
+        return allCustomers;
+    }
 }

+ 5 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java

@@ -205,6 +205,7 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
                 qwGroupChatUser.setGroupNickname(groupNickname);
                 qwGroupChatUser.setCompanyId(qwCompany.getId());
                 qwGroupChatUser.setCorpId(corpId);
+                qwGroupChatUser.setIsOut(1L);
                 qwGroupChatUserMapper.insertQwGroupChatUser(qwGroupChatUser);
 
             });
@@ -347,7 +348,9 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
 
                     qwGroupChatMapper.insertOrUpdateQwGroupChat(qwGroupChat);
 
-                    memberList.stream().forEach(item -> {
+                    qwGroupChatUserMapper.markActiveMembersOutByChatId(chatId, corpId);
+
+                    memberList.forEach(item -> {
 
                         //客户名称
                         String name1 = item.getName();
@@ -393,7 +396,7 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
                         qwGroupChatUser.setJoinScene(String.valueOf(joinScene));
                         qwGroupChatUser.setGroupNickname(groupNickname);
                         qwGroupChatUser.setCorpId(corpId);
-                        qwGroupChatUserMapper.insertOrUpdateQwGroupChatUser(qwGroupChatUser);
+                        qwGroupChatUserMapper.insertOrUpdateSyncGroupChatUser(qwGroupChatUser);
 
                     });
 

+ 4 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatUserServiceImpl.java

@@ -134,7 +134,9 @@ public class QwGroupChatUserServiceImpl implements IQwGroupChatUserService
 
             qwGroupChatMapper.insertOrUpdateQwGroupChat(qwGroupChat);
 
-            memberList.stream().forEach(item -> {
+            qwGroupChatUserMapper.markActiveMembersOutByChatId(chatId, corpId);
+
+            memberList.forEach(item -> {
 
                 //客户名称
                 String name1 = item.getName();
@@ -179,7 +181,7 @@ public class QwGroupChatUserServiceImpl implements IQwGroupChatUserService
                 qwGroupChatUser.setJoinScene(String.valueOf(joinScene));
                 qwGroupChatUser.setGroupNickname(groupNickname);
                 qwGroupChatUser.setCorpId(corpId);
-                qwGroupChatUserMapper.insertOrUpdateQwGroupChatUser(qwGroupChatUser);
+                qwGroupChatUserMapper.insertOrUpdateSyncGroupChatUser(qwGroupChatUser);
 
             });
 

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceAsyncHelperImpl.java

@@ -1122,6 +1122,8 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
                                         qwExternalContactTransferLog.setCorpId(param.getCorpId());
                                         qwExternalContactTransferLog.setExternalUserId(qwCustomer.getExternal_userid());
                                         qwExternalContactTransferLog.setHandoverQwUserId(qwExternalContact.getQwUserId());
+                                        qwExternalContactTransferLog.setNeedClearTag(param.getNeedClearTag());
+                                        qwExternalContactTransferLog.setFsUserId(qwExternalContact.getFsUserId());
                                         qwExternalContactTransferLogMapper.insertQwExternalContactTransferLog(qwExternalContactTransferLog);
                                         QwExternalContact qwExternal = new QwExternalContact();
                                         qwExternal.setStatus(2);
@@ -1229,6 +1231,9 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
                                         qwExternalContactTransferLog.setTakeoverUserId(qwUser.getQwUserId());
                                         qwExternalContactTransferLog.setCorpId(param.getCorpId());
                                         qwExternalContactTransferLog.setExternalUserId(qwCustomer.getExternal_userid());
+                                        qwExternalContactTransferLog.setHandoverQwUserId(qwExternalContact.getQwUserId());
+                                        qwExternalContactTransferLog.setNeedClearTag(param.getNeedClearTag());
+                                        qwExternalContactTransferLog.setFsUserId(qwExternalContact.getFsUserId());
                                         qwExternalContactTransferLogMapper.insertQwExternalContactTransferLog(qwExternalContactTransferLog);
                                         QwExternalContact qwExternal = new QwExternalContact();
                                         qwExternal.setStatus(2);

+ 18 - 12
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -161,21 +161,21 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     @Update("<script>" +
             "UPDATE sop_user_logs_info " +
-            "SET is_days_not_study = CASE external_id " +
+            "SET is_days_not_study = CASE id " +
             "<foreach collection='contactList' item='item'> " +
-            "    WHEN #{item.id} THEN #{item.isDaysNotStudy} " +
+            "    WHEN #{item.sopUserLogsInfoId} THEN #{item.isDaysNotStudy} " +
             "</foreach> " +
             "ELSE is_days_not_study " +
             "END, " +
-            "grade = CASE external_id " +
+            "grade = CASE id " +
             "<foreach collection='contactList' item='item'> " +
-            "    WHEN #{item.id} THEN #{item.level} " +
+            "    WHEN #{item.sopUserLogsInfoId} THEN #{item.level} " +
             "</foreach> " +
             "ELSE grade " +
             "END " +
-            "WHERE external_id IN " +
+            "WHERE id IN " +
             "<foreach collection='contactList' item='item' open='(' separator=',' close=')'> " +
-            "    #{item.id} " +
+            "    #{item.sopUserLogsInfoId} " +
             "</foreach>" +
             "</script>")
     void batchUpdateSopUserLogsInfoByMoreStudy(@Param("contactList") List<QwExternalContact> contactList);
@@ -185,9 +185,9 @@ public interface SopUserLogsInfoMapper {
             "UPDATE sop_user_logs_info " +
             "SET is_days_not_study = 0, " +
             "grade = 4 " +
-            "WHERE external_id IN " +
+            "WHERE id IN " +
             "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
-            "    #{item.id} " +
+            "    #{item.sopUserLogsInfoId} " +
             "</foreach>" +
             "</script>")
     void batchUpdateSopUserLogsInfoByIsDaysNotStudy(@Param("contactList") List<QwExternalContact> contactList);
@@ -195,15 +195,21 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     @Update("<script>" +
             "UPDATE sop_user_logs_info " +
-            "SET grade = CASE external_id " +
+            "SET grade = CASE id " +
             "<foreach collection=\"contactList\" item=\"item\">" +
-            "    WHEN #{item.id} THEN #{item.level} " +
+            "    WHEN #{item.sopUserLogsInfoId} THEN #{item.level} " +
             "</foreach>" +
             "    ELSE grade " +
+            "END, " +
+            "is_days_not_study = CASE id " +
+            "<foreach collection=\"contactList\" item=\"item\">" +
+            "    WHEN #{item.sopUserLogsInfoId} THEN CASE WHEN #{item.level} = 5 THEN is_days_not_study ELSE 0 END " +
+            "</foreach>" +
+            "    ELSE is_days_not_study " +
             "END " +
-            "WHERE external_id IN " +
+            "WHERE id IN " +
             "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
-            "    #{item.id} " +
+            "    #{item.sopUserLogsInfoId} " +
             "</foreach>" +
             "</script>")
     void batchUpdateSopUserLogsInfoByLevel(@Param("contactList") List<QwExternalContact> contactList);

+ 2 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -195,6 +195,7 @@ public interface SopUserLogsMapper {
             "            l.sop_id,\n" +
             "            l.sop_temp_id,\n" +
             "            l.qw_user_id,\n" +
+            "            l.actual_qw_user_id,\n" +
             "            l.chat_id,\n" +
             "            l.corp_id,\n" +
             "            l.start_time,\n" +
@@ -291,6 +292,7 @@ public interface SopUserLogsMapper {
         "            l.sop_id,\n" +
         "            l.sop_temp_id,\n" +
         "            l.qw_user_id,\n" +
+        "            l.actual_qw_user_id,\n" +
         "            l.corp_id,\n" +
         "            l.chat_id,\n" +
         "            l.start_time,\n" +

+ 18 - 3
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -259,17 +259,32 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     @Override
     public void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList) {
-        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByMoreStudy(contactList);
+        List<QwExternalContact> validList = contactList.stream()
+                .filter(c -> c.getSopUserLogsInfoId() != null)
+                .collect(Collectors.toList());
+        if (!validList.isEmpty()) {
+            sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByMoreStudy(validList);
+        }
     }
 
     @Override
     public void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList) {
-        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsDaysNotStudy(contactList);
+        List<QwExternalContact> validList = contactList.stream()
+                .filter(c -> c.getSopUserLogsInfoId() != null)
+                .collect(Collectors.toList());
+        if (!validList.isEmpty()) {
+            sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsDaysNotStudy(validList);
+        }
     }
 
     @Override
     public void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList) {
-        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(contactList);
+        List<QwExternalContact> validList = contactList.stream()
+                .filter(c -> c.getSopUserLogsInfoId() != null)
+                .collect(Collectors.toList());
+        if (!validList.isEmpty()) {
+            sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(validList);
+        }
     }
 
     @Override

+ 5 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -44,10 +44,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="lastWatchTime"    column="last_watch_time"    />
         <result property="registerTime"    column="register_time"    />
         <result property="isReply"    column="is_reply"    />
+        <result property="isTransfer"    column="is_transfer"    />
     </resultMap>
 
     <sql id="selectQwExternalContactVo">
-        select id,qw_user_id,register_time,state,way_id,stage_status,first_time,open_id,is_interact,level, unionid, user_id,transfer_time,loss_time,del_time,transfer_num, external_user_id,transfer_status,status,create_time, name, avatar, type, gender, remark, description, tag_ids, remark_mobiles, remark_corp_name, add_way, oper_userid, corp_id, company_id, company_user_id, customer_id, fs_user_id,is_reply from qw_external_contact
+        select id,qw_user_id,register_time,state,way_id,stage_status,first_time,open_id,is_interact,level, unionid, user_id,transfer_time,loss_time,del_time,transfer_num, external_user_id,transfer_status,status,create_time, name, avatar, type, gender, remark, description, tag_ids, remark_mobiles, remark_corp_name, add_way, oper_userid, corp_id, company_id, company_user_id, customer_id, fs_user_id,is_reply,is_transfer from qw_external_contact
     </sql>
 
     <select id="selectQwExternalContactList" parameterType="QwExternalContact" resultMap="QwExternalContactResult">
@@ -282,6 +283,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="levelType != null">level_type,</if>
             <if test="firstTime != null">first_time,</if>
             <if test="registerTime != null">register_time,</if>
+            <if test="isTransfer != null">is_transfer,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="id != null">#{id},</if>
@@ -321,6 +323,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="levelType != null">#{levelType},</if>
             <if test="firstTime != null">#{firstTime},</if>
             <if test="registerTime != null">#{registerTime},</if>
+            <if test="isTransfer != null">#{isTransfer},</if>
          </trim>
     </insert>
 
@@ -363,6 +366,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="levelType != null">level_type = #{levelType},</if>
             <if test="firstTime != null">first_time = #{firstTime},</if>
             <if test="registerTime != null">register_time = #{registerTime},</if>
+            <if test="isTransfer != null">is_transfer = #{isTransfer},</if>
         </trim>
         where id = #{id}
     </update>

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

@@ -232,7 +232,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ANY_VALUE(qu.qw_user_name) as qwName,
         COUNT(CASE WHEN gcu.is_out = 1 AND DATE(gcu.join_time) = CURDATE() THEN 1 END) AS todayJoinByChartUser,
         COUNT(CASE WHEN gcu.is_out = 2 AND DATE(gcu.out_time) = CURDATE() THEN 1 END) AS todayOutByChartUser,
-        COUNT(CASE WHEN gcu.is_out = 1 THEN gcu.chat_id END) AS groupSizeByChartUser,
+        ANY_VALUE(gc.group_size) AS groupSizeByChartUser,
         gc.*
         FROM
         qw_group_chat gc

+ 53 - 0
fs-service/src/main/resources/mapper/qw/QwGroupChatUserMapper.xml

@@ -117,6 +117,59 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </trim>
     </insert>
 
+    <update id="markActiveMembersOutByChatId">
+        update qw_group_chat_user
+        set is_out = 2
+        where chat_id = #{chatId} and corp_id = #{corpId} and (is_out = 1 or is_out is null)
+    </update>
+
+    <insert id="insertOrUpdateSyncGroupChatUser" parameterType="QwGroupChatUser" useGeneratedKeys="true" keyProperty="id">
+        insert into qw_group_chat_user
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="chatId != null">chat_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="type != null">type,</if>
+            <if test="unionid != null">unionid,</if>
+            <if test="joinTime != null">join_time,</if>
+            <if test="joinScene != null">join_scene,</if>
+            <if test="invitor != null">invitor,</if>
+            <if test="groupNickname != null">group_nickname,</if>
+            <if test="name != null">name,</if>
+            <if test="companyId != null">company_id,</if>
+            is_out,
+            <if test="corpId != null">corp_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="chatId != null">#{chatId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="type != null">#{type},</if>
+            <if test="unionid != null">#{unionid},</if>
+            <if test="joinTime != null">#{joinTime},</if>
+            <if test="joinScene != null">#{joinScene},</if>
+            <if test="invitor != null">#{invitor},</if>
+            <if test="groupNickname != null">#{groupNickname},</if>
+            <if test="name != null">#{name},</if>
+            <if test="companyId != null">#{companyId},</if>
+            1,
+            <if test="corpId != null">#{corpId},</if>
+        </trim>
+        on duplicate key update
+        <trim suffixOverrides=",">
+            <if test="type != null">type = #{type},</if>
+            <if test="unionid != null">unionid = #{unionid},</if>
+            <if test="joinTime != null">join_time = #{joinTime},</if>
+            <if test="joinScene != null">join_scene = #{joinScene},</if>
+            <if test="invitor != null">invitor = #{invitor},</if>
+            <if test="groupNickname != null">group_nickname = #{groupNickname},</if>
+            <if test="name != null">name = #{name},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            is_out = 1,
+            out_time = null,
+            quit_scene = null,
+            <if test="corpId != null">corp_id = #{corpId},</if>
+        </trim>
+    </insert>
+
     <insert id="insertQwGroupChatUser" parameterType="QwGroupChatUser" useGeneratedKeys="true" keyProperty="id">
         insert into qw_group_chat_user
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 1 - 1
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -9,7 +9,7 @@
         <result property="sopId"    column="sop_id"    />
         <result property="sopTempId"    column="sop_temp_id"    />
         <result property="qwUserId"    column="qw_user_id"    />
-        <result property="actualQwUserId"    column="actual_user_id"    />
+        <result property="actualQwUserId"    column="actual_qw_user_id"    />
         <result property="corpId"    column="corp_id"    />
         <result property="startTime"    column="start_time"  jdbcType="TIMESTAMP"   />
         <result property="status"    column="status"    />