Преглед на файлове

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

caoliqin преди 4 дни
родител
ревизия
9aa5c30b57
променени са 57 файла, в които са добавени 1197 реда и са изтрити 129 реда
  1. 4 13
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java
  2. 77 4
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  3. 3 0
      fs-company-app/src/main/java/com/fs/app/controller/UserController.java
  4. 68 6
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  5. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceController.java
  6. 97 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceGroupController.java
  7. 124 38
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  8. 4 4
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  9. 19 1
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java
  10. 19 1
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java
  11. 8 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  12. 5 0
      fs-service/src/main/java/com/fs/course/param/FsCourseListBySidebarParam.java
  13. 8 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  14. 12 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  15. 8 0
      fs-service/src/main/java/com/fs/his/param/FindUserByParam.java
  16. 23 0
      fs-service/src/main/java/com/fs/his/vo/ExchangeDetailVo.java
  17. 24 0
      fs-service/src/main/java/com/fs/qw/domain/QwSopTempVoiceGroup.java
  18. 4 0
      fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java
  19. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  20. 6 0
      fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java
  21. 66 0
      fs-service/src/main/java/com/fs/qw/mapper/QwSopTempVoiceGroupMapper.java
  22. 16 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateLevelParam.java
  23. 63 0
      fs-service/src/main/java/com/fs/qw/service/IQwSopTempVoiceGroupService.java
  24. 3 4
      fs-service/src/main/java/com/fs/qw/service/IQwUserServiceAsyncHelper.java
  25. 95 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwSopTempVoiceGroupServiceImpl.java
  26. 12 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceAsyncHelperImpl.java
  27. 26 10
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  28. 31 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java
  29. 1 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  30. 2 0
      fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskListVO.java
  31. 6 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopLogs.java
  32. 2 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTempVoice.java
  33. 1 1
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java
  34. 20 1
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  35. 1 1
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  36. 4 0
      fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java
  37. 1 1
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java
  38. 5 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  39. 3 2
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java
  40. 10 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  41. 8 0
      fs-service/src/main/resources/db/changelog/dictData/dict_data_update.sql
  42. 17 0
      fs-service/src/main/resources/db/changelog/sop/changes/change_table.sql
  43. 2 0
      fs-service/src/main/resources/db/changelog/sop/db.changelog-master.xml
  44. 12 0
      fs-service/src/main/resources/db/changelog/sop/table/create_table.sql
  45. 8 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  46. 18 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  47. 66 0
      fs-service/src/main/resources/mapper/qw/QwSopTempVoiceGroupMapper.xml
  48. 1 1
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  49. 8 5
      fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml
  50. 15 10
      fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml
  51. 1 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml
  52. 6 6
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  53. 68 9
      fs-user-app/src/main/java/com/fs/app/controller/AppBaseController.java
  54. 2 2
      fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java
  55. 43 2
      fs-user-app/src/main/java/com/fs/app/controller/CommonController.java
  56. 17 0
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  57. 22 2
      fs-user-app/src/main/java/com/fs/app/controller/app/ImController.java

+ 4 - 13
fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java

@@ -46,7 +46,6 @@ import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
-import liquibase.pro.packaged.S;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -160,8 +159,8 @@ public class CompanyUserAllController extends BaseController {
     /**
      * 获取所有 没有 有语音席位的账号
      */
-    @GetMapping("/getAllNotVoiceList")
-    public TableDataInfo getAllNotVoiceList(VcCompanyUserParam param) {
+    @PostMapping("/getAllNotVoiceList")
+    public TableDataInfo getAllNotVoiceList(@RequestBody VcCompanyUserParam param) {
         startPage();
 
         List<VcCompanyUser> vcCompanyUsers = companyUserService.getAllNotVoiceList(param.getCompanyUserId());
@@ -172,8 +171,8 @@ public class CompanyUserAllController extends BaseController {
     /**
     * 获取所有 已经 有语音席位的账号
     */
-    @GetMapping("/getAllVoiceList")
-    public TableDataInfo getAllVoiceList(VcCompanyUserParam param) {
+    @PostMapping("/getAllVoiceList")
+    public TableDataInfo getAllVoiceList(@RequestBody VcCompanyUserParam param) {
         startPage();
 
         List<VcCompanyUser> vcCompanyUsers = companyUserService.selectAllVcCompanyUser(param.getCompanyUserId());
@@ -309,14 +308,6 @@ public class CompanyUserAllController extends BaseController {
         user.setCreateBy(SecurityUtils.getUsername());
         user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
         user.setCreateTime(new Date());
-
-        Date createTime = new Date();
-        Calendar cal = Calendar.getInstance();
-        cal.setTime(createTime);
-        cal.add(Calendar.YEAR, 1); // 加1年
-        Date expirationTime = cal.getTime();
-        user.setExpirationTime(expirationTime);
-
         user.setUserType("01");//一般用户
         user.setIsAudit(1);
         return toAjax(companyUserService.insertUser(user));

+ 77 - 4
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -1,5 +1,7 @@
 package com.fs.qw.controller;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
@@ -7,14 +9,14 @@ import java.util.stream.Collectors;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.ServletUtils;
-import com.fs.qw.param.QwExternalContactParam;
-import com.fs.qw.param.QwTagSearchParam;
-import com.fs.qw.param.ResignedTransferParam;
-import com.fs.qw.param.TransferParam;
+import com.fs.qw.param.*;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwTagService;
+import com.fs.qw.service.IQwUserServiceAsyncHelper;
 import com.fs.qw.vo.QwExternalContactUnionIdExportVO;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.google.gson.Gson;
@@ -51,6 +53,8 @@ public class QwExternalContactController extends BaseController
 {
     private IQwExternalContactService qwExternalContactService;
     private IQwTagService iQwTagService;
+    @Autowired
+    private IQwUserServiceAsyncHelper qwUserServiceAsyncHelper;
 
     @Autowired
     private IQwExternalContactInfoService qwExternalContactInfoService;
@@ -230,4 +234,73 @@ public class QwExternalContactController extends BaseController
         }
         return qwExternalContactService.transfer(param);
     }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:changeLevel')")
+    @Log(title = "批量修改用户等级", businessType = BusinessType.UPDATE)
+    @PostMapping("/changeLevel")
+    public R changeLevel(@RequestBody QwExternalContactUpdateLevelParam param)
+    {
+
+        if(param.isFilter()){
+            param.setUserIds(getList(0, param.getParam()));
+        }
+        if(param.getUserIds() == null || param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
+
+        // 获取所有用户ID
+        List<Long> allUserIds = param.getUserIds();
+        int batchSize = 500;
+        int totalSize = allUserIds.size();
+
+        // 记录处理结果
+        int successCount = 0;
+        List<Long> failedUserIds = new ArrayList<>();
+
+        // 分批处理
+        for (int i = 0; i < totalSize; i += batchSize) {
+            int end = Math.min(i + batchSize, totalSize);
+            List<Long> batchUserIds = allUserIds.subList(i, end);
+
+            try {
+                // 创建新的参数对象,只包含当前批次的数据
+                QwExternalContactUpdateLevelParam batchParam = new QwExternalContactUpdateLevelParam();
+                batchParam.setLevel(param.getLevel());
+                batchParam.setFilter(param.isFilter());
+                batchParam.setUserIds(batchUserIds);
+
+                // 调用异步方法
+                qwUserServiceAsyncHelper.batchUpdateExternalContactLevelById(batchParam);
+
+                successCount += batchUserIds.size();
+
+                // 如果不是最后一批,可以稍微延迟一下,避免压力过大
+                if (end < totalSize) {
+                    Thread.sleep(100); // 100毫秒延迟,可根据需要调整
+                }
+            } catch (Exception e) {
+                // 记录失败的批次
+                logger.error("批次处理失败,用户IDs: {}, 错误: {}", batchUserIds, e.getMessage());
+                failedUserIds.addAll(batchUserIds);
+            }
+        }
+
+        // 返回处理结果
+        if (failedUserIds.isEmpty()) {
+            return R.ok("成功处理 " + successCount + " 个用户");
+        } else {
+            return R.ok("部分成功: 成功 " + successCount + " 个, 失败 " + failedUserIds.size() + " 个");
+        }
+    }
+
+
+    private List<Long> getList(Integer addType, QwExternalContactParam param){
+        if(addType == null){
+            return Collections.emptyList();
+        }
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(param);
+        if(list == null || list.isEmpty()) return Collections.emptyList();
+        return PubFun.listToNewList(list, QwExternalContactVO::getId);
+    }
+
 }

+ 3 - 0
fs-company-app/src/main/java/com/fs/app/controller/UserController.java

@@ -22,6 +22,7 @@ import com.fs.common.utils.PinYinUtil;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.bean.BeanUtils;
 import com.fs.common.utils.http.HttpUtils;
+import com.fs.common.utils.sign.Md5Utils;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUserCard;
@@ -209,10 +210,12 @@ public class UserController extends AppBaseController {
 
             //redisCache.setCacheObject("perms:"+companyUser.getUserId(), JSONUtil.toJsonStr(perms),2592000, TimeUnit.SECONDS);
             redisCache.setCacheObject("perms:" + companyUser.getUserId(), JSONUtil.toJsonStr(perms), 604800, TimeUnit.SECONDS);
+            redisCache.setCacheObject("company-user-token:" + Md5Utils.hash(companyUser.getUserId().toString()), companyUser.getUserId(), 100, TimeUnit.DAYS);
 
             Map<String, Object> result = new HashMap<>();
             result.put("token", token);
             result.put("user", companyUser);
+            result.put("companyUserToken", Md5Utils.hash(companyUser.getUserId().toString()));
             result.put("perms", perms);
             return R.ok("登录成功").put("data", result);
         } catch (Exception e) {

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

@@ -101,6 +101,15 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IFsUserCompanyBindService fsUserCompanyBindService;
 
+    @Autowired
+    private IFsUserCoursePeriodService userCoursePeriodService;
+
+    @Autowired
+    private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
     /**
      * 查询企业微信客户列表
      */
@@ -977,14 +986,67 @@ public class QwExternalContactController extends BaseController
         return R.ok("正在批量修改备注中");
     }
 
-    @Autowired
-    private IFsUserCoursePeriodService userCoursePeriodService;
 
-    @Autowired
-    private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:changeLevel')")
+    @Log(title = "批量修改用户等级", businessType = BusinessType.UPDATE)
+    @PostMapping("/changeLevel")
+    public R changeLevel(@RequestBody QwExternalContactUpdateLevelParam param)
+    {
+
+        if(param.isFilter()){
+            param.setUserIds(getList(0, param.getParam()));
+        }
+        if(param.getUserIds() == null || param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
+
+        // 获取所有用户ID
+        List<Long> allUserIds = param.getUserIds();
+        int batchSize = 500;
+        int totalSize = allUserIds.size();
+
+        // 记录处理结果
+        int successCount = 0;
+        List<Long> failedUserIds = new ArrayList<>();
+
+        // 分批处理
+        for (int i = 0; i < totalSize; i += batchSize) {
+            int end = Math.min(i + batchSize, totalSize);
+            List<Long> batchUserIds = allUserIds.subList(i, end);
+
+            try {
+                // 创建新的参数对象,只包含当前批次的数据
+                QwExternalContactUpdateLevelParam batchParam = new QwExternalContactUpdateLevelParam();
+                batchParam.setLevel(param.getLevel());
+                batchParam.setFilter(param.isFilter());
+                batchParam.setUserIds(batchUserIds);
+
+                // 调用异步方法
+                qwUserServiceAsyncHelper.batchUpdateExternalContactLevelById(batchParam);
+
+                successCount += batchUserIds.size();
+
+                // 如果不是最后一批,可以稍微延迟一下,避免压力过大
+                if (end < totalSize) {
+                    Thread.sleep(100); // 100毫秒延迟,可根据需要调整
+                }
+            } catch (Exception e) {
+                // 记录失败的批次
+                logger.error("批次处理失败,用户IDs: {}, 错误: {}", batchUserIds, e.getMessage());
+                failedUserIds.addAll(batchUserIds);
+            }
+        }
+
+        // 返回处理结果
+        if (failedUserIds.isEmpty()) {
+            return R.ok("成功处理 " + successCount + " 个用户");
+        } else {
+            return R.ok("部分成功: 成功 " + successCount + " 个, 失败 " + failedUserIds.size() + " 个");
+        }
+    }
+
+
 
-    @Autowired
-    private IFsCourseWatchLogService fsCourseWatchLogService;
     /**
      * 用于适配看课批量修改标签【根据过滤条件版】
      * @param fromMyList

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceController.java

@@ -46,7 +46,7 @@ public class QwSopTempVoiceController extends BaseController
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
-        List<QwSopTempVoice> voiceList = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(loginUser.getUser().getUserId(),param.getKeyword());
+        List<QwSopTempVoice> voiceList = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(loginUser.getUser().getUserId(),param.getKeyword(),param.getGroupId());
         return getDataTable(voiceList);
     }
 

+ 97 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempVoiceGroupController.java

@@ -0,0 +1,97 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
+import com.fs.qw.service.IQwSopTempVoiceGroupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 语音分类-主要是区分侧边栏语音类型Controller
+ *
+ * @author fs
+ * @date 2026-07-16
+ */
+@RestController
+@RequestMapping("/qw/QwSopTempVoiceGroup")
+public class QwSopTempVoiceGroupController extends BaseController
+{
+    @Autowired
+    private IQwSopTempVoiceGroupService qwSopTempVoiceGroupService;
+
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        startPage();
+        List<QwSopTempVoiceGroup> list = qwSopTempVoiceGroupService.selectQwSopTempVoiceGroupList(qwSopTempVoiceGroup);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出语音分类-主要是区分侧边栏语音类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:export')")
+    @Log(title = "语音分类-主要是区分侧边栏语音类型", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        List<QwSopTempVoiceGroup> list = qwSopTempVoiceGroupService.selectQwSopTempVoiceGroupList(qwSopTempVoiceGroup);
+        ExcelUtil<QwSopTempVoiceGroup> util = new ExcelUtil<QwSopTempVoiceGroup>(QwSopTempVoiceGroup.class);
+        return util.exportExcel(list, "语音分类-主要是区分侧边栏语音类型数据");
+    }
+
+    /**
+     * 获取语音分类-主要是区分侧边栏语音类型详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwSopTempVoiceGroupService.selectQwSopTempVoiceGroupById(id));
+    }
+
+    /**
+     * 新增语音分类-主要是区分侧边栏语音类型
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:add')")
+    @Log(title = "语音分类-主要是区分侧边栏语音类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        return toAjax(qwSopTempVoiceGroupService.insertQwSopTempVoiceGroup(qwSopTempVoiceGroup));
+    }
+
+    /**
+     * 修改语音分类-主要是区分侧边栏语音类型
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:edit')")
+    @Log(title = "语音分类-主要是区分侧边栏语音类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        return toAjax(qwSopTempVoiceGroupService.updateQwSopTempVoiceGroup(qwSopTempVoiceGroup));
+    }
+
+    /**
+     * 删除语音分类-主要是区分侧边栏语音类型
+     */
+    @PreAuthorize("@ss.hasPermi('qw:QwSopTempVoiceGroup:remove')")
+    @Log(title = "语音分类-主要是区分侧边栏语音类型", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwSopTempVoiceGroupService.deleteQwSopTempVoiceGroupByIds(ids));
+    }
+}

+ 124 - 38
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -28,9 +28,11 @@ import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopTempSetting;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.params.QwRatingConfig;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.service.ISysConfigService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -68,6 +70,7 @@ public class SendMsg {
     private final AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper;
     private final AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper;
     private final QwExternalContactMapper qwExternalContactMapper;
+    private final ISysConfigService sysConfigService;
 
     @Value("${group-no}")
     private String groupNo;
@@ -101,7 +104,7 @@ public class SendMsg {
     @Qualifier("customThreadPool")
     private ThreadPoolTaskExecutor customThreadPool;
 
-    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper, IAiAddwxSopUserNodeService aiAddwxSopUserNodeService, AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper, AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper, QwExternalContactMapper qwExternalContactMapper) {
+    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper, IAiAddwxSopUserNodeService aiAddwxSopUserNodeService, AiAddwxSopUserWorkflowMapper aiAddwxSopUserWorkflowMapper, AiAddwxSopUserNodeRuleMapper aiAddwxSopUserNodeRuleMapper, QwExternalContactMapper qwExternalContactMapper, ISysConfigService sysConfigService) {
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.sendServer = sendServer;
@@ -118,6 +121,7 @@ public class SendMsg {
         this.aiAddwxSopUserWorkflowMapper = aiAddwxSopUserWorkflowMapper;
         this.aiAddwxSopUserNodeRuleMapper = aiAddwxSopUserNodeRuleMapper;
         this.qwExternalContactMapper = qwExternalContactMapper;
+        this.sysConfigService = sysConfigService;
     }
     private List<QwUser> getQwUserList() {
         if (qwUserList.isEmpty()) {
@@ -156,6 +160,10 @@ public class SendMsg {
         // 看课配置文件获取
         SysConfig courseConfig = sysConfigMapper.selectConfigByConfigKey("course.config");
         CourseConfig config = JSON.parseObject(courseConfig.getConfigValue(), CourseConfig.class);
+
+        String ratingJson = sysConfigService.selectConfigByKey("qwRating:config");
+        QwRatingConfig ratingConfig = JSON.parseObject(ratingJson, QwRatingConfig.class);
+
         // 消息发送延迟
         int delayStart;
         int delayEnd;
@@ -190,7 +198,7 @@ public class SendMsg {
             CompletableFuture.runAsync(() -> {
                 try {
                     log.info("开始任务:{}", e.getQwUserName());
-                    processUser(e, delayStart, delayEnd, miniMap, newCtx);
+                    processUser(e, delayStart, delayEnd, miniMap, newCtx,ratingConfig);
                 } catch (Exception exception) {
                     log.error("发送错误:", exception);
                 } finally {
@@ -213,7 +221,7 @@ public class SendMsg {
      * @param miniMap    小程序配置
      * @param ctx        任务上下文(用于取消检查)
      */
-    private void processUser(QwUser qwUser, int delayStart, int delayEnd, Map<String, FsCoursePlaySourceConfig> miniMap, TaskContext ctx) {
+    private void processUser(QwUser qwUser, int delayStart, int delayEnd, Map<String, FsCoursePlaySourceConfig> miniMap, TaskContext ctx,QwRatingConfig ratingConfig) {
         long start1 = System.currentTimeMillis();
         // 获取当前企微待发送记录
         List<QwSopLogs> qwSopLogList = qwSopLogsMapper.selectByQwUserId(qwUser.getId());
@@ -306,51 +314,51 @@ public class SendMsg {
                         }
                     }
                 }
+
                 //查询是否有设置限制客服推送消息次数
 //                    Integer pushCount=pushCountMap.containsKey(String.valueOf(companyId)) ? pushCountMap.get(String.valueOf(companyId)): pushCountMap.getOrDefault(String.valueOf(type), -99);
                 int salesPushCustomerMessageCount = qwRestrictionPushRecordMapper.selectQwRestrictionPushRecord(qwUserId, customerId, type, DateUtils.toStartTime(), DateUtils.toEndTime());
-                if (pushCount != -99 && salesPushCustomerMessageCount >= pushCount) {
-                    content.setSendStatus(2);//设置发送失败状态
-                    content.setSendRemarks("发送次数达到上限");
-                } else {
-                    // 发送
-                    sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
-                    //判断销售推送成功:保存记录
-                    if (content.getSendStatus() != 2) {
-                        QwRestrictionPushRecord qrpr = new QwRestrictionPushRecord();
-                        qrpr.setType(type);
-                        qrpr.setQwUserId(qwUserId);
-                        qrpr.setQwExternalId(customerId);
-                        qrpr.setCompanyId(companyId);
-                        qrpr.setStatus(1);
-                        qrpr.setCreateTime(DateUtils.getTime());
-                        qrpr.setTime(System.currentTimeMillis());
-                        qwRestrictionPushRecordMapper.insert(qrpr);
+
+                if (CloudHostUtils.hasCloudHostName("今正科技")){
+                    //今正要 通过客户等级 限制发送次数 只管 3个类型的
+                    Integer sendType = qwSopLogs.getSendType();
+                    Integer grade = qwSopLogs.getGrade();
+                    boolean shouldSend = true;
+
+                    if (sendType == 2 || sendType == 5 || sendType == 11) {
+                        if (grade != null) {
+                            Integer gradeLimit = getGradeLimit(grade, ratingConfig);
+                            if (gradeLimit != null) {
+                                if (salesPushCustomerMessageCount >= gradeLimit) {
+                                    content.setSendStatus(2);
+                                    content.setSendRemarks(getGradeLabel(grade) + "级客户发送次数达到上限");
+                                    shouldSend = false;
+                                }
+                            }
+                        }
                     }
-                    long end4 = System.currentTimeMillis();
-                    log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - start4);
-                    if(content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks()) || "请求失败:请求频率异常".equals(content.getSendRemarks()))){
-                        QwUser update = new QwUser();
-                        update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
-                        update.setUpdateTime(new Date());
-                        qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
-                        redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
-                        return;
+
+                    if (shouldSend) {
+                        if (!doSendAndPostProcess(content, user, qwSopLogs, miniMap, parentVo,
+                                type, qwUserId, customerId, companyId, start4, ctx)) {
+                            return;
+                        }
+                    }
+                }
+                else {
+                    if (pushCount != -99 && salesPushCustomerMessageCount >= pushCount) {
+                        content.setSendStatus(2);
+                        content.setSendRemarks("发送次数达到上限");
                     }
-                    try {
-                        if (ctx.isCancelled()) {
+                    else {
+                        if (!doSendAndPostProcess(content, user, qwSopLogs, miniMap, parentVo,
+                                type, qwUserId, customerId, companyId, start4, ctx)) {
                             return;
                         }
-                        int delay = ThreadLocalRandom.current().nextInt(300, 1000);
-                        log.debug("pad发送消息等待:{}ms", delay);
-                        Thread.sleep(delay);
-                    } catch (InterruptedException e) {
-                        log.error("线程等待错误!");
-                        Thread.currentThread().interrupt();
-                        return;
                     }
                 }
             }
+
             // 推送 APP
             boolean hasAppSend = false;
             int successCount = 0;
@@ -487,6 +495,84 @@ public class SendMsg {
         long end3 = System.currentTimeMillis();
         log.info("销售执行完成:{}, 耗时:{}", user.getQwUserName(), end3 - start3);
     }
+
+    /**
+     * 发送消息并处理后续逻辑(保存发送记录、频率异常检测、随机等待)
+     * @return true 继续执行,false 需要中断(频率异常或被取消)
+     */
+    private boolean doSendAndPostProcess(QwSopCourseFinishTempSetting.Setting content, QwUser user,
+                                          QwSopLogs qwSopLogs, Map<String, FsCoursePlaySourceConfig> miniMap,
+                                          BaseVo parentVo, Integer type, Long qwUserId, Long customerId,
+                                          Long companyId, long startTime, TaskContext ctx) {
+        sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
+
+        if (content.getSendStatus() != 2) {
+            QwRestrictionPushRecord qrpr = new QwRestrictionPushRecord();
+            qrpr.setType(type);
+            qrpr.setQwUserId(qwUserId);
+            qrpr.setQwExternalId(customerId);
+            qrpr.setCompanyId(companyId);
+            qrpr.setStatus(1);
+            qrpr.setCreateTime(DateUtils.getTime());
+            qrpr.setTime(System.currentTimeMillis());
+            qwRestrictionPushRecordMapper.insert(qrpr);
+        }
+
+        long end4 = System.currentTimeMillis();
+        log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - startTime);
+
+        if (content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks())
+                || "请求失败:请求频率异常".equals(content.getSendRemarks()))) {
+            QwUser update = new QwUser();
+            update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
+            update.setUpdateTime(new Date());
+            qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
+            redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
+            return false;
+        }
+
+        try {
+            if (ctx.isCancelled()) {
+                return false;
+            }
+            int delay = ThreadLocalRandom.current().nextInt(300, 1000);
+            log.debug("pad发送消息等待:{}ms", delay);
+            Thread.sleep(delay);
+        } catch (InterruptedException e) {
+            log.error("线程等待错误!");
+            Thread.currentThread().interrupt();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 根据客户等级获取发送次数上限
+     * @return 上限值,null 表示不限制(非A/B/C/D级客户)
+     */
+    private Integer getGradeLimit(Integer grade, QwRatingConfig ratingConfig) {
+        switch (grade) {
+            case 1: return ratingConfig.getALevelSend();
+            case 2: return ratingConfig.getBLevelSend();
+            case 3: return ratingConfig.getCLevelSend();
+            case 4: return ratingConfig.getDLevelSend();
+            default: return null;
+        }
+    }
+
+    /**
+     * 获取客户等级名称
+     */
+    private String getGradeLabel(Integer grade) {
+        switch (grade) {
+            case 1: return "A";
+            case 2: return "B";
+            case 3: return "C";
+            case 4: return "D";
+            default: return "";
+        }
+    }
+
     /**
      * 今正cid加微后定时发消息
      */

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

@@ -813,7 +813,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 }
                 ruleTimeVO.setType(2);
 
-                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null, null);
+                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null, null,null);
 
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
@@ -845,7 +845,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String externalUserName = contactId.getExternalUserName();
                     Long fsUserId = contactId.getFsUserId();
                     Integer grade = contactId.getGrade();
-                    QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId(), contactId.getIsDaysNotStudy());
+                    QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId(), contactId.getIsDaysNotStudy(),grade);
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                             type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId,
                             null, config, miniMap, grade, sendMsgType, companies, liveId,serverId);
@@ -891,14 +891,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private QwSopLogs createBaseLog(String formattedSendTime, SopUserLogsVo logVo,
                                     QwSopRuleTimeVO ruleTimeVO, String externalContactId,
                                     String externalUserName, Long fsUserId, Integer isOfficial,
-                                    Long externalId, Integer isDaysNotStudy) {
+                                    Long externalId, Integer isDaysNotStudy,Integer grade) {
         QwSopLogs sopLogs = new QwSopLogs();
         sopLogs.setSendTime(formattedSendTime);
         sopLogs.setQwUserid(logVo.getQwUserId());
         sopLogs.setCorpId(logVo.getCorpId());
         sopLogs.setLogType(ruleTimeVO.getType());
         sopLogs.setTakeRecords(0);
-
+        sopLogs.setGrade(grade);
         if (isOfficial != 1 && Integer.valueOf(1).equals(isDaysNotStudy)) {
             sopLogs.setSendStatus(5L);
             sopLogs.setRemark("E级客户不发送");

+ 19 - 1
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java

@@ -5,6 +5,7 @@ import com.fs.common.BeanCopyUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.base.BaseException;
@@ -18,6 +19,7 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
 import com.fs.qw.domain.QwTagGroup;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.ExternalContactDetailsParam;
@@ -26,6 +28,7 @@ import com.fs.qw.param.sidebar.TagGroupListParam;
 import com.fs.qw.param.sidebar.TagGroupUpdateParam;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwSopTempVoiceGroupService;
 import com.fs.qw.service.IQwTagGroupService;
 import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwExternalListByHeavyVO;
@@ -41,6 +44,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -82,6 +86,9 @@ public class ApisQwUserController extends BaseController {
     @Autowired
     private IQwSopTempVoiceService sopTempVoiceService;
 
+    @Autowired
+    private IQwSopTempVoiceGroupService qwSopTempVoiceGroupService;
+
 
     @GetMapping("/details")
     @ApiOperation("会员看课详情")
@@ -246,9 +253,20 @@ public class ApisQwUserController extends BaseController {
         }
 
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
-        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword());
+        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword(),param.getGroupId());
         PageInfo<QwSopTempVoice> result = new PageInfo<>(voiceList);
         return R.ok().put("data", result);
     }
 
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     */
+
+    @PostMapping("/voiceGroupList")
+    public TableDataInfo list(@RequestBody QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        startPage();
+        List<QwSopTempVoiceGroup> list = qwSopTempVoiceGroupService.selectQwSopTempVoiceGroupList(qwSopTempVoiceGroup);
+        return getDataTable(list);
+    }
 }

+ 19 - 1
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java

@@ -5,6 +5,7 @@ import com.fs.common.BeanCopyUtils;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.base.BaseException;
@@ -18,6 +19,7 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
 import com.fs.qw.domain.QwTagGroup;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.ExternalContactDetailsParam;
@@ -27,6 +29,7 @@ import com.fs.qw.param.sidebar.TagGroupListParam;
 import com.fs.qw.param.sidebar.TagGroupUpdateParam;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwSopTempVoiceGroupService;
 import com.fs.qw.service.IQwTagGroupService;
 import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwTagGroupListVO;
@@ -43,6 +46,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -56,6 +60,9 @@ import java.util.Map;
 @RequestMapping("/app/qw/user")
 public class QwUserController extends BaseController {
 
+    @Autowired
+    private IQwSopTempVoiceGroupService qwSopTempVoiceGroupService;
+
     @Autowired
     private IQwExternalContactService qwExternalContactService;
 
@@ -248,9 +255,20 @@ public class QwUserController extends BaseController {
         }
 
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
-        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword());
+        List<QwSopTempVoice> voiceList = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserId(qwUser.getCompanyUserId(),param.getKeyword(),param.getGroupId());
         PageInfo<QwSopTempVoice> result = new PageInfo<>(voiceList);
         return R.ok().put("data", result);
     }
 
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     */
+    @PostMapping("/voiceGroupList")
+    public TableDataInfo list(@RequestBody QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        startPage();
+        List<QwSopTempVoiceGroup> list = qwSopTempVoiceGroupService.selectQwSopTempVoiceGroupList(qwSopTempVoiceGroup);
+        return getDataTable(list);
+    }
+
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -34,6 +34,14 @@ public interface FsUserCourseVideoMapper extends BaseMapper<FsUserCourseVideo> {
      */
     public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId);
 
+    /**
+     * 批量查询课堂视频
+     *
+     * @param videoIds 课堂视频主键集合
+     * @return 课堂视频集合
+     */
+    List<FsUserCourseVideo> selectFsUserCourseVideoByVideoIds(@Param("videoIds") List<Long> videoIds);
+
     @Select("select video_id,course_id from fs_user_course_video " +
             "where is_del = #{data.isDel} " +
             "and is_pause = #{data.isPause} " +

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

@@ -46,4 +46,9 @@ public class FsCourseListBySidebarParam extends BaseEntity {
 
     // 是否上架 0:上架,1:下架
     private Integer isOnPut;
+
+    /**
+    * 分组
+    */
+    private Long groupId;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -40,6 +40,14 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
      */
     public FsUserCourseVideo selectFsUserCourseVideoByVideoId(Long videoId);
 
+    /**
+     * 批量查询课堂视频
+     *
+     * @param videoIds 课堂视频主键集合
+     * @return 课堂视频集合
+     */
+    List<FsUserCourseVideo> selectFsUserCourseVideoByVideoIdS(List<Long> videoIds);
+
     public FsUserCourseVideoTagVO selectFsUserCourseVideoByCourseVideoId(Long videoId);
 
     /**

+ 12 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -333,6 +333,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         return fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
     }
 
+    @Override
+    public List<FsUserCourseVideo> selectFsUserCourseVideoByVideoIdS(List<Long> videoIds) {
+        if (videoIds == null || videoIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoIds(videoIds);
+    }
+
     @Override
     public FsUserCourseVideoTagVO selectFsUserCourseVideoByCourseVideoId(Long videoId) {
         return fsUserCourseVideoMapper.selectFsUserCourseVideoByCourseVideoId(videoId);
@@ -3258,6 +3266,8 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         FsUser userMap = new FsUser();
         userMap.setUserId(user.getUserId());
         userMap.setIntegral(user.getIntegral() + answerIntegral);
+        // 增加可提现积分
+        userMap.setWithdrawIntegral(user.getWithdrawIntegral() + answerIntegral);
         fsUserMapper.updateFsUser(userMap);
 
         // 记录积分日志
@@ -3268,6 +3278,8 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         integralLogs.setLogType(17);
         integralLogs.setBusinessId(StringUtils.isNotEmpty(log.getLogId().toString()) ? log.getLogId().toString() : null);
         integralLogs.setCreateTime(new Date());
+        integralLogs.setNickName(user.getNickName());
+        integralLogs.setPhone(user.getPhone());
         fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
 
         //更新看课记录的奖励类型

+ 8 - 0
fs-service/src/main/java/com/fs/his/param/FindUserByParam.java

@@ -9,4 +9,12 @@ public class FindUserByParam implements Serializable {
     private Integer pageNum;
     private Integer pageSize;
     private String keywords;
+
+    /** 是否仅查询销售/医生:客户搜索时为 true,销售/其他用户不受影响 */
+    private Boolean onlySales;
+    /**
+     * 客户搜索前缀:C=销售,D=医生。
+     * 仅 onlySales=true 时生效,由服务端根据 keywords 解析设置
+     */
+    private String searchPrefix;
 }

+ 23 - 0
fs-service/src/main/java/com/fs/his/vo/ExchangeDetailVo.java

@@ -0,0 +1,23 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 积分兑换佣金明细
+ */
+
+@Data
+public class ExchangeDetailVo {
+    private  Long id;
+    private  Long  userId;
+    private Integer logType;
+    private Long integral;
+    private Long balance;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+    private BigDecimal commission;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/qw/domain/QwSopTempVoiceGroup.java

@@ -0,0 +1,24 @@
+package com.fs.qw.domain;
+
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 语音分类-主要是区分侧边栏语音类型对象 qw_sop_temp_voice_group
+ *
+ * @author fs
+ * @date 2026-07-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwSopTempVoiceGroup extends BaseEntity {
+
+    private Long id;
+
+    private String voiceGroupName;
+
+
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java

@@ -1,5 +1,6 @@
 package com.fs.qw.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
 import io.swagger.models.auth.In;
@@ -82,4 +83,7 @@ public class QwWorkTask extends BaseEntity{
 
     private Long videoId;
 
+    @TableField(exist = false)
+    private String videoName;
+
 }

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

@@ -86,7 +86,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     public int batchUpdateQwExternalContactByMoreStudy(List<QwExternalContact> qwExternalContact);
     public int batchUpdateQwExternalByIsDaysNotStudy(List<QwExternalContact> qwExternalContact);
-
+    public int batchUpdateQwExternalLevelById(QwExternalContactUpdateLevelParam param);
 
     @Select("SELECT * FROM qw_external_contact WHERE external_user_id = #{externalUserId}  AND corp_id=#{corpId} and user_id=#{qwUserId} limit 1")
     public QwExternalContact selectQwExternalContactByExternalUserIdAndQwUserId(@Param("externalUserId") String externalUserId,@Param("corpId") String corpId,@Param("qwUserId") String qwUserId);

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

@@ -37,6 +37,12 @@ public interface QwRestrictionPushRecordMapper extends BaseMapper<QwRestrictionP
                                       @Param("startTime") Long startTime,
                                       @Param("endTime") Long endTime);
 
+    @Select("select count(id) from qw_restriction_push_record where qw_user_id=#{qwUserId} and qw_external_id=#{qwExternalId} and time>#{startTime} and time<=#{endTime}")
+    int selectQwRestrictionPushRecordByExternalId(@Param("qwUserId") Long qwUserId,
+                                      @Param("qwExternalId") Long qwExternalId,
+                                      @Param("startTime") Long startTime,
+                                      @Param("endTime") Long endTime);
+
     @Select("select id from qw_restriction_push_record where time<#{time}")
     List<Long> selectExpirePushRecord(@Param("time") Long time);
 

+ 66 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwSopTempVoiceGroupMapper.java

@@ -0,0 +1,66 @@
+package com.fs.qw.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
+
+import java.util.List;
+
+/**
+ * 语音分类-主要是区分侧边栏语音类型Mapper接口
+ *
+ * @author fs
+ * @date 2026-07-16
+ */
+@DataSource(DataSourceType.SOP)
+public interface QwSopTempVoiceGroupMapper extends BaseMapper<QwSopTempVoiceGroup> {
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 语音分类-主要是区分侧边栏语音类型
+     */
+    QwSopTempVoiceGroup selectQwSopTempVoiceGroupById(Long id);
+
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 语音分类-主要是区分侧边栏语音类型集合
+     */
+    List<QwSopTempVoiceGroup> selectQwSopTempVoiceGroupList(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 新增语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    int insertQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 修改语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    int updateQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 删除语音分类-主要是区分侧边栏语音类型
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 结果
+     */
+    int deleteQwSopTempVoiceGroupById(Long id);
+
+    /**
+     * 批量删除语音分类-主要是区分侧边栏语音类型
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteQwSopTempVoiceGroupByIds(Long[] ids);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateLevelParam.java

@@ -0,0 +1,16 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QwExternalContactUpdateLevelParam {
+
+    private List<Long> userIds;
+    private Integer level;
+
+    private boolean filter;
+    private QwExternalContactParam param;
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/qw/service/IQwSopTempVoiceGroupService.java

@@ -0,0 +1,63 @@
+package com.fs.qw.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
+
+import java.util.List;
+
+/**
+ * 语音分类-主要是区分侧边栏语音类型Service接口
+ *
+ * @author fs
+ * @date 2026-07-16
+ */
+public interface IQwSopTempVoiceGroupService extends IService<QwSopTempVoiceGroup> {
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 语音分类-主要是区分侧边栏语音类型
+     */
+    QwSopTempVoiceGroup selectQwSopTempVoiceGroupById(Long id);
+
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 语音分类-主要是区分侧边栏语音类型集合
+     */
+    List<QwSopTempVoiceGroup> selectQwSopTempVoiceGroupList(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 新增语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    int insertQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 修改语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    int updateQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup);
+
+    /**
+     * 批量删除语音分类-主要是区分侧边栏语音类型
+     *
+     * @param ids 需要删除的语音分类-主要是区分侧边栏语音类型主键集合
+     * @return 结果
+     */
+    int deleteQwSopTempVoiceGroupByIds(Long[] ids);
+
+    /**
+     * 删除语音分类-主要是区分侧边栏语音类型信息
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 结果
+     */
+    int deleteQwSopTempVoiceGroupById(Long id);
+}

+ 3 - 4
fs-service/src/main/java/com/fs/qw/service/IQwUserServiceAsyncHelper.java

@@ -1,10 +1,7 @@
 package com.fs.qw.service;
 
 import com.fs.company.domain.Company;
-import com.fs.qw.param.QwExternalContactAddTagParam;
-import com.fs.qw.param.QwExternalContactUpdateNoteParam;
-import com.fs.qw.param.ResignedTransferParam;
-import com.fs.qw.param.TransferParam;
+import com.fs.qw.param.*;
 import org.codehaus.jettison.json.JSONException;
 
 import java.io.IOException;
@@ -32,6 +29,8 @@ public interface IQwUserServiceAsyncHelper {
      */
     void batchUpdateExternalContactNotes(QwExternalContactUpdateNoteParam param);
 
+    void batchUpdateExternalContactLevelById(QwExternalContactUpdateLevelParam param);
+
     /**
      * 添加标签
      * @param param

+ 95 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwSopTempVoiceGroupServiceImpl.java

@@ -0,0 +1,95 @@
+package com.fs.qw.service.impl;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.qw.domain.QwSopTempVoiceGroup;
+import com.fs.qw.mapper.QwSopTempVoiceGroupMapper;
+import com.fs.qw.service.IQwSopTempVoiceGroupService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 语音分类-主要是区分侧边栏语音类型Service业务层处理
+ *
+ * @author fs
+ * @date 2026-07-16
+ */
+@Service
+public class QwSopTempVoiceGroupServiceImpl extends ServiceImpl<QwSopTempVoiceGroupMapper, QwSopTempVoiceGroup> implements IQwSopTempVoiceGroupService {
+
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 语音分类-主要是区分侧边栏语音类型
+     */
+    @Override
+    public QwSopTempVoiceGroup selectQwSopTempVoiceGroupById(Long id)
+    {
+        return baseMapper.selectQwSopTempVoiceGroupById(id);
+    }
+
+    /**
+     * 查询语音分类-主要是区分侧边栏语音类型列表
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 语音分类-主要是区分侧边栏语音类型
+     */
+    @Override
+    public List<QwSopTempVoiceGroup> selectQwSopTempVoiceGroupList(QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        return baseMapper.selectQwSopTempVoiceGroupList(qwSopTempVoiceGroup);
+    }
+
+    /**
+     * 新增语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    @Override
+    public int insertQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        qwSopTempVoiceGroup.setCreateTime(new Date());
+        return baseMapper.insertQwSopTempVoiceGroup(qwSopTempVoiceGroup);
+    }
+
+    /**
+     * 修改语音分类-主要是区分侧边栏语音类型
+     *
+     * @param qwSopTempVoiceGroup 语音分类-主要是区分侧边栏语音类型
+     * @return 结果
+     */
+    @Override
+    public int updateQwSopTempVoiceGroup(QwSopTempVoiceGroup qwSopTempVoiceGroup)
+    {
+        qwSopTempVoiceGroup.setUpdateTime(new Date());
+        return baseMapper.updateQwSopTempVoiceGroup(qwSopTempVoiceGroup);
+    }
+
+    /**
+     * 批量删除语音分类-主要是区分侧边栏语音类型
+     *
+     * @param ids 需要删除的语音分类-主要是区分侧边栏语音类型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwSopTempVoiceGroupByIds(Long[] ids)
+    {
+        return baseMapper.deleteQwSopTempVoiceGroupByIds(ids);
+    }
+
+    /**
+     * 删除语音分类-主要是区分侧边栏语音类型信息
+     *
+     * @param id 语音分类-主要是区分侧边栏语音类型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwSopTempVoiceGroupById(Long id)
+    {
+        return baseMapper.deleteQwSopTempVoiceGroupById(id);
+    }
+}

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

@@ -28,6 +28,7 @@ import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.params.DeleteQwSopParam;
 import com.fs.sop.params.QwSopAutoByTags;
 import com.fs.sop.params.SopUserLogsParamByDate;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.watch.mapper.WatchCompanyUserMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -102,6 +103,9 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
     @Autowired
     private AsyncQwAiChatSopService asyncQwAiChatSopService;
 
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
     org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
 
     /**
@@ -299,6 +303,14 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
         }
     }
 
+    @Override
+    @Async("qwHandleExec")
+    public void batchUpdateExternalContactLevelById(QwExternalContactUpdateLevelParam param) {
+
+        qwExternalContactMapper.batchUpdateQwExternalLevelById(param);
+        iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByIsLevel(param);
+    }
+
     private String getErrorMsg(Integer code) {
 
         String msg = "";

+ 26 - 10
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -23,6 +23,8 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.config.ai.AiHostProper;
 import com.fs.config.cloud.CloudHostProper;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsDfAccount;
 import com.fs.his.utils.ConfigUtil;
@@ -123,6 +125,9 @@ public class QwUserServiceImpl implements IQwUserService
     @Autowired
     IQwExternalContactService externalContactService;
 
+    @Autowired
+    IFsUserCourseVideoService iFsUserCourseVideoService;
+
     @Autowired
     IpadSendUtils ipadSendUtils;
 
@@ -254,16 +259,27 @@ public class QwUserServiceImpl implements IQwUserService
         }
 
 
-//        for (QwWorkTask qwWorkTask : qwWorkTasks) {
-//            if(qwWorkTask.getExtId() != null) {
-//                QwExternalContact qwExternalContact = externalContactService.selectQwExternalContactById(qwWorkTask.getExtId());
-//                if(qwExternalContact != null) {
-//                    qwWorkTask.setName(qwExternalContact.getName());
-//                    qwWorkTask.setAvatar(qwExternalContact.getAvatar());
-//                    qwWorkTask.setExternalContactId(qwExternalContact.getExternalUserId());
-//                }
-//            }
-//        }
+        // 提取非空的 videoId 列表,批量查询视频标题并填充 videoName
+        List<Long> videoIds = qwWorkTasks.stream()
+                .map(QwWorkTask::getVideoId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!videoIds.isEmpty()) {
+            Map<Long, String> videoTitleMap = iFsUserCourseVideoService.selectFsUserCourseVideoByVideoIdS(videoIds)
+                    .stream()
+                    .collect(Collectors.toMap(
+                            FsUserCourseVideo::getVideoId,
+                            FsUserCourseVideo::getTitle
+                    ));
+
+            qwWorkTasks.forEach(task -> {
+                Optional.ofNullable(task.getVideoId())
+                        .map(videoTitleMap::get)
+                        .ifPresent(task::setVideoName);
+            });
+        }
+
         return qwWorkTasks;
     }
 

+ 31 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java

@@ -2,8 +2,10 @@ package com.fs.qw.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsCourseWatchLogTaskVO;
 import com.fs.course.vo.FsQwCourseWatchLogVO;
 import com.fs.qw.domain.QwExternalContact;
@@ -50,6 +52,10 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
     SopUserLogsInfoMapper sopUserLogsInfoMapper;
     @Autowired
     QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    IFsUserCourseVideoService iFsUserCourseVideoService;
+
     /**
      * 查询企微任务看板
      *
@@ -246,7 +252,31 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
 
     @Override
     public List<QwWorkTaskListVO> selectQwWorkTaskListVO(QwWorkTaskListParam qwWorkTask) {
-        return qwWorkTaskMapper.selectQwWorkTaskListVO(qwWorkTask);
+
+        List<QwWorkTaskListVO> qwWorkTaskListVOS = qwWorkTaskMapper.selectQwWorkTaskListVO(qwWorkTask);
+
+        // 提取非空的 videoId 列表,批量查询视频标题并填充 videoName
+        List<Long> videoIds = qwWorkTaskListVOS.stream()
+                .map(QwWorkTaskListVO::getVideoId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!videoIds.isEmpty()) {
+            Map<Long, String> videoTitleMap = iFsUserCourseVideoService.selectFsUserCourseVideoByVideoIdS(videoIds)
+                    .stream()
+                    .collect(Collectors.toMap(
+                            FsUserCourseVideo::getVideoId,
+                            FsUserCourseVideo::getTitle
+                    ));
+
+            qwWorkTaskListVOS.forEach(task -> {
+                Optional.ofNullable(task.getVideoId())
+                        .map(videoTitleMap::get)
+                        .ifPresent(task::setVideoName);
+            });
+        }
+
+        return qwWorkTaskListVOS;
     }
 
     //看课中断

+ 1 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -166,6 +166,7 @@ public class QwExternalContactVO {
     /**
      * 是否下载APP
      */
+    @Excel(name = "是否下载APP 1是 2否")
     private Integer isDownloadApp;
 
     /**

+ 2 - 0
fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskListVO.java

@@ -50,6 +50,8 @@ public class QwWorkTaskListVO {
 
     /** 用户id */
     private Long companyUserId;
+    private Long videoId;
+    private String videoName;
     /**
      * 销售名称
      */

+ 6 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopLogs.java

@@ -67,6 +67,7 @@ public class QwSopLogs implements Serializable {
     */
     private Integer sendType;
 
+
     /**
     *  qw_sop表主键
     */
@@ -117,6 +118,11 @@ public class QwSopLogs implements Serializable {
      */
     private Long smsLogsId;
 
+    /**
+    * 客户等级
+    */
+    private Integer grade;
+
     // 构造函数
 //    public QwSopLogs() {
 //        this.id = UUID.randomUUID().toString();

+ 2 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTempVoice.java

@@ -66,6 +66,8 @@ public class QwSopTempVoice{
 
     private Long qwUserId;
 
+    private Long groupId;
+
     private String voicePrintUrl;
 
 }

+ 1 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java

@@ -111,7 +111,7 @@ public interface QwSopTempVoiceMapper extends BaseMapper<QwSopTempVoice>{
     int updateBatchWithDataSource(@Param("list") List<QwSopTempVoice> list);
 
     @DataSource(DataSourceType.SOP)
-    List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(@Param("companyUserId") Long companyUserId,@Param("keyword") String keyword);
+    List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(@Param("companyUserId") Long companyUserId,@Param("keyword") String keyword,@Param("groupId") Long groupId);
 
 
 }

+ 20 - 1
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -4,6 +4,7 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
 import com.fs.sop.params.DeleteQwSopParam;
@@ -206,7 +207,25 @@ public interface SopUserLogsInfoMapper {
             "</script>")
     void batchUpdateSopUserLogsInfoByLevel(@Param("contactList") List<QwExternalContact> contactList);
 
-
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET " +
+            "    is_days_not_study = CASE " +
+            "        WHEN #{map.level} = 5 THEN 1 " +
+            "        WHEN #{map.level} = 9 THEN 0 " +
+            "        ELSE 0 " +
+            "    END, " +
+            "    grade = CASE " +
+            "        WHEN #{map.level} = 9 THEN 0 " +
+            "        ELSE #{map.level} " +
+            "    END " +
+            "WHERE external_id IN " +
+            "<foreach collection='map.userIds' item='item' open='(' separator=',' close=')'>" +
+            "    #{item} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByIsLevel(@Param("map") QwExternalContactUpdateLevelParam param);
 
 
     @DataSource(DataSourceType.SOP)

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

@@ -118,7 +118,7 @@ public interface SopUserLogsMapper {
 
     @DataSource(DataSourceType.SOP)
     @Select("<script>" +
-            "select li.sop_id,li.external_contact_id,li.qw_user_id,li.corp_id,li.external_id,li.external_user_name,li.fs_user_id,ul.start_time,li.is_days_not_study " +
+            "select li.sop_id,li.external_contact_id,li.qw_user_id,li.corp_id,li.external_id,li.external_user_name,li.fs_user_id,ul.start_time,li.is_days_not_study,li.grade " +
             "from sop_user_logs ul  " +
             "INNER join  sop_user_logs_info li on ul.id=li.user_logs_id " +
             "        WHERE ul.id IN\n" +

+ 4 - 0
fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java

@@ -8,10 +8,14 @@ public class QwRatingConfig {
     private Integer notStudyDays;
     private Integer aLevelMin;
     private Integer aLevelMax;
+    private Integer aLevelSend;
     private Integer bLevelMin;
     private Integer bLevelMax;
+    private Integer bLevelSend;
     private Integer cLevelMin;
     private Integer cLevelMax;
+    private Integer cLevelSend;
     private Integer dLevelMin;
     private Integer dLevelMax;
+    private Integer dLevelSend;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java

@@ -103,5 +103,5 @@ public interface IQwSopTempVoiceService extends IService<QwSopTempVoice>{
 
     int updateBatchWithDataSource(List<QwSopTempVoice> list);
 
-    List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(Long companyUserId,String keyword);
+    List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(Long companyUserId,String keyword,Long groupId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -3,6 +3,7 @@ package com.fs.sop.service;
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
 import com.fs.sop.params.SendUserLogsInfoMsgParam;
@@ -57,9 +58,13 @@ public interface ISopUserLogsInfoService {
     void batchInsertSopUserLogsInfo(List<SopUserLogsInfo> logsToInsert);
 
     void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList);
+
     void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList);
+
     void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList);
 
+    void batchUpdateSopUserLogsInfoByIsLevel(QwExternalContactUpdateLevelParam param);
+
     void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert);
     /**
      * 查询sopUserLogsInfo

+ 3 - 2
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java

@@ -113,6 +113,7 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
             newVoice.setCompanyId(companyUser.getCompanyId());
             newVoice.setVoiceTxt(tempVoice.getVoiceTxt());
             newVoice.setRecordType(0);
+            newVoice.setGroupId(tempVoice.getGroupId());
             insertList.add(newVoice);
 
             // 分批插入,避免SQL过长
@@ -427,7 +428,7 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
 
     @Override
     @DataSource(DataSourceType.SOP)
-    public List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(Long companyUserId,String keyword) {
-        return qwSopTempVoiceMapper.selectQwSopTempVoiceByCompanyUserId(companyUserId ,keyword);
+    public List<QwSopTempVoice> selectQwSopTempVoiceByCompanyUserId(Long companyUserId,String keyword,Long groupId) {
+        return qwSopTempVoiceMapper.selectQwSopTempVoiceByCompanyUserId(companyUserId ,keyword,groupId);
     }
 }

+ 10 - 2
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -42,6 +42,7 @@ import com.fs.live.mapper.LiveWatchLogMapper;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.qw.param.QwExternalContactVOTime;
 import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.service.IQwCompanyService;
@@ -307,6 +308,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(contactList);
     }
 
+    @Override
+    public void batchUpdateSopUserLogsInfoByIsLevel(QwExternalContactUpdateLevelParam param) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsLevel(param);
+    }
+
     @Override
     public void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(logsToInsert);
@@ -635,7 +641,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     }
                     sopLogs.setExternalUserName(groupUser.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
-
+                    sopLogs.setGrade(null);
                     // 设置实际发送人
                     updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupUser.getChatId());
                     //域名
@@ -1006,6 +1012,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     }
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
+                    sopLogs.setGrade(null);
                     // 设置实际发送人
                     updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupChat.getChatId());
 
@@ -1302,6 +1309,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 sopLogs.setSort(30000000);
                 sopLogs.setSendType(5);
                 sopLogs.setExternalUserName(item.getExternalUserName());
+                sopLogs.setGrade(item.getGrade());
                 if(sendLiveMsgFinal){
                     sopLogs.setSendType(20);
                 }
@@ -2083,7 +2091,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             sopLogs.setSendType(finalSendType);
 
             sopLogs.setExternalUserName(item.getExternalUserName());
-
+            sopLogs.setGrade(item.getGrade());
 
             Long msgNum = Long.valueOf(generateRandomNumberWithLock());
             sopLogs.setSmsLogsId(msgNum);

+ 8 - 0
fs-service/src/main/resources/db/changelog/dictData/dict_data_update.sql

@@ -151,3 +151,11 @@ VALUES
 --rollback DELETE FROM sys_dict_data WHERE dict_type = 'sys_integral_log_type' AND dict_value = '35';
 
 
+--changeset sgw:20250701-sys-integral-log-integral-withdraw
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM sys_dict_data WHERE dict_type = 'sys_integral_log_type' AND dict_value = '36'
+INSERT INTO `sys_dict_data`
+(`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
+VALUES
+    (36, '看课积分提现', '36', 'sys_integral_log_type', NULL, 'default', 'N', '0', 'admin', NOW(), '', NULL, NULL);
+--rollback DELETE FROM sys_dict_data WHERE dict_type = 'sys_integral_log_type' AND dict_value = '36';

+ 17 - 0
fs-service/src/main/resources/db/changelog/sop/changes/change_table.sql

@@ -0,0 +1,17 @@
+--liquibase formatted sql
+
+--changeset sgw:20260716-qw_sop_temp_voice-group_id
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'qw_sop_temp_voice'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'qw_sop_temp_voice' AND column_name = 'group_id'
+ALTER TABLE qw_sop_temp_voice
+    ADD COLUMN group_id BIGINT  DEFAULT NULL COMMENT '分组id,主要是侧边栏语音的';
+--rollback ALTER TABLE qw_sop_temp_voice DROP COLUMN group_id;
+
+--changeset sgw:20260719-qw_sop_logs-grade
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'qw_sop_logs'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'qw_sop_logs' AND column_name = 'grade'
+ALTER TABLE qw_sop_logs
+    ADD COLUMN grade BIGINT  DEFAULT NULL COMMENT '客户评级的等级';
+--rollback ALTER TABLE qw_sop_logs DROP COLUMN grade;

+ 2 - 0
fs-service/src/main/resources/db/changelog/sop/db.changelog-master.xml

@@ -9,6 +9,8 @@
     <!-- SOP 数据源 changelog:仅在此目录下的文件会在 sopDataSource 执行 -->
     <include file="baseline/init-meta.sql" relativeToChangelogFile="true"/>
     <include file="baseline/baseline.sql" relativeToChangelogFile="true"/>
+    <include file="table/create_table.sql" relativeToChangelogFile="true"/>
+    <include file="changes/change_table.sql" relativeToChangelogFile="true"/>
 
     <!-- 新增 SOP 库变更示例:
     <include file="changes/YYYYMMDD-模块-简述.sql" relativeToChangelogFile="true"/>

+ 12 - 0
fs-service/src/main/resources/db/changelog/sop/table/create_table.sql

@@ -0,0 +1,12 @@
+--liquibase formatted sql
+
+--changeset sgw:20260716-qw_sop_temp_voice_group
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'qw_sop_temp_voice_group'
+CREATE TABLE `qw_sop_temp_voice_group` (
+                                           `id` int NOT NULL AUTO_INCREMENT,
+                                           `voice_group_name` varchar(255) DEFAULT NULL,
+                                           `create_time` datetime DEFAULT NULL,
+                                           `update_time` datetime DEFAULT NULL,
+                                           PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='语音分类-主要是区分侧边栏语音类型';

+ 8 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -74,6 +74,14 @@
         where video_id = #{videoId}
     </select>
 
+    <select id="selectFsUserCourseVideoByVideoIds" resultMap="FsUserCourseVideoResult">
+        <include refid="selectFsUserCourseVideoVo"/>
+        where video_id in
+        <foreach collection="videoIds" item="videoId" open="(" separator="," close=")">
+            #{videoId}
+        </foreach>
+    </select>
+
     <insert id="insertFsUserCourseVideo" parameterType="FsUserCourseVideo" useGeneratedKeys="true" keyProperty="videoId">
         insert into fs_user_course_video
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 18 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -242,6 +242,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+    <update id="batchUpdateQwExternalLevelById" parameterType="map">
+        UPDATE qw_external_contact
+        SET level =
+        <choose>
+            <when test="level == 9">
+                NULL
+            </when>
+            <otherwise>
+                #{level}
+            </otherwise>
+        </choose>
+        WHERE id IN
+        <foreach collection="userIds" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+    </update>
+
+
 
     <insert id="insertQwExternalContact" parameterType="QwExternalContact" useGeneratedKeys="true" keyProperty="id" >
         insert into qw_external_contact

+ 66 - 0
fs-service/src/main/resources/mapper/qw/QwSopTempVoiceGroupMapper.xml

@@ -0,0 +1,66 @@
+<?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.QwSopTempVoiceGroupMapper">
+
+    <resultMap type="QwSopTempVoiceGroup" id="QwSopTempVoiceGroupResult">
+        <result property="id"    column="id"    />
+        <result property="voiceGroupName"    column="voice_group_name"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectQwSopTempVoiceGroupVo">
+        select id, voice_group_name, create_time, update_time from qw_sop_temp_voice_group
+    </sql>
+
+    <select id="selectQwSopTempVoiceGroupList" parameterType="QwSopTempVoiceGroup" resultMap="QwSopTempVoiceGroupResult">
+        <include refid="selectQwSopTempVoiceGroupVo"/>
+        <where>
+            <if test="voiceGroupName != null  and voiceGroupName != ''"> and voice_group_name like concat('%', #{voiceGroupName}, '%')</if>
+        </where>
+    </select>
+
+    <select id="selectQwSopTempVoiceGroupById" parameterType="Long" resultMap="QwSopTempVoiceGroupResult">
+        <include refid="selectQwSopTempVoiceGroupVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertQwSopTempVoiceGroup" parameterType="QwSopTempVoiceGroup">
+        insert into qw_sop_temp_voice_group
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="voiceGroupName != null">voice_group_name,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="voiceGroupName != null">#{voiceGroupName},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateQwSopTempVoiceGroup" parameterType="QwSopTempVoiceGroup">
+        update qw_sop_temp_voice_group
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="voiceGroupName != null">voice_group_name = #{voiceGroupName},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteQwSopTempVoiceGroupById" parameterType="Long">
+        delete from qw_sop_temp_voice_group where id = #{id}
+    </delete>
+
+    <delete id="deleteQwSopTempVoiceGroupByIds" parameterType="String">
+        delete from qw_sop_temp_voice_group where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

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

@@ -249,7 +249,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectQwWorkTaskList" resultType="com.fs.qw.domain.QwWorkTask">
         select
             qwt.id,qwt.ext_id, qwt.qw_user_id, qwt.status, qwt.track_type, qwt.type, qwt.title, qwt.remark, qwt.score, qwt.sop_id,
-            qwt.company_id, qwt.company_user_id, qwt.duration, qwt.create_time, qwt.update_time
+            qwt.company_id, qwt.company_user_id, qwt.duration, qwt.create_time, qwt.update_time,qwt.video_id
         from qw_work_task qwt
         where qwt.qw_user_id = #{userId}
           and date(qwt.create_time) = #{date}

+ 8 - 5
fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml

@@ -31,10 +31,11 @@
         <result property="userLogsId"    column="user_logs_id"    />
         <result property="appSendStatus"    column="app_send_status"    />
         <result property="appSendRemark"    column="app_send_remark"    />
+        <result property="grade"    column="grade"    />
     </resultMap>
 
     <sql id="selectQwSopLogsVo">
-        select id,sop_id,customer_id, qw_userid,corp_id,external_user_id,external_id,external_user_name, log_type, content_json, send_status,receiving_status,msg_id, send_time, real_send_time, company_id, send_type,remark,fs_user_id,take_records,qw_user_key,user_logs_id,app_send_status,app_send_remark from qw_sop_logs
+        select id,sop_id,customer_id, qw_userid,corp_id,external_user_id,external_id,external_user_name, log_type, content_json, send_status,receiving_status,msg_id, send_time, real_send_time, company_id, send_type,remark,fs_user_id,take_records,qw_user_key,user_logs_id,app_send_status,app_send_remark,grade from qw_sop_logs
     </sql>
 
     <select id="selectQwSopLogsListVO" parameterType="QwSopLogs" resultMap="QwSopLogsResult">
@@ -600,7 +601,7 @@
         qw_userid, external_user_id,external_id, external_user_name, log_type,
         content_json, send_status, send_time, real_send_time, send_type,
         company_id, receiving_status, msg_id, sop_id, remark,
-        corp_id, customer_id,fs_user_id,sort,qw_user_key,app_send_status,sms_logs_id
+        corp_id, customer_id,fs_user_id,sort,qw_user_key,app_send_status,sms_logs_id,grade
         )
         VALUES
         <foreach collection="qwSopLogs" item="log" separator=",">
@@ -626,7 +627,8 @@
             #{log.sort},
             #{log.qwUserKey},
             #{log.appSendStatus},
-            #{log.smsLogsId}
+            #{log.smsLogsId},
+            #{log.grade}
             )
         </foreach>
     </insert>
@@ -638,7 +640,7 @@
         content_json, send_status, send_time, real_send_time, send_type,
         company_id, receiving_status, msg_id, sop_id, remark,
         corp_id, customer_id, fs_user_id, expiration_time,sort,user_logs_id,take_records,
-         qw_user_key,app_send_status,app_send_remark,sms_logs_id
+         qw_user_key,app_send_status,app_send_remark,sms_logs_id,grade
         )
         VALUES
         <foreach collection="qwSopLogs" item="log" separator=",">
@@ -668,7 +670,8 @@
             #{log.qwUserKey},
             #{log.appSendStatus},
             #{log.appSendRemark},
-            #{log.smsLogsId}
+            #{log.smsLogsId},
+            #{log.grade}
             )
         </foreach>
     </insert>

+ 15 - 10
fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml

@@ -16,10 +16,11 @@
         <result property="userVoiceUrl"    column="user_voice_url"    />
         <result property="recordType"    column="record_type"    />
         <result property="createTime"    column="create_time"    />
+        <result property="groupId"    column="group_id"    />
     </resultMap>
 
     <sql id="selectQwSopTempVoiceVo">
-        select id,company_user_id, temp_id, rule_id, content_id, voice_txt, voice_url, create_time from qw_sop_temp_voice
+        select id,company_user_id, temp_id, rule_id, content_id, voice_txt, voice_url, create_time,group_id from qw_sop_temp_voice
     </sql>
 
     <select id="selectQwSopTempVoiceList" parameterType="QwSopTempVoice" resultMap="QwSopTempVoiceResult">
@@ -34,7 +35,7 @@
     </select>
 
     <select id="selectQwSopTempVoiceById" parameterType="Long" resultMap="QwSopTempVoiceResult">
-        select id,company_user_id,duration, temp_id, rule_id, content_id, voice_txt, voice_url,user_voice_url,record_type, create_time from qw_sop_temp_voice
+        select id,company_user_id,duration, temp_id, rule_id, content_id, voice_txt, voice_url,user_voice_url,record_type, create_time,group_id from qw_sop_temp_voice
         where id = #{id}
     </select>
     <select id="listVoice" resultType="com.fs.sop.domain.QwSopTempVoice">
@@ -62,15 +63,15 @@
     </select>
 
     <select id="selectQwSopTempVoiceByIdAndText" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,company_user_id companyUserId,voice_txt voiceTxt from qw_sop_temp_voice where id = #{id}
+        select id,company_user_id companyUserId,voice_txt voiceTxt,group_id from qw_sop_temp_voice where id = #{id}
                                                                                             and voice_txt = #{voiceTxt} and record_type in (0,1) limit 1
     </select>
     <select id="selectQwSopTempVoiceByIdAndUserVoiceUrl" resultType="com.fs.sop.domain.QwSopTempVoice" >
-        select id,company_user_id companyUserId,voice_txt voiceTxt from qw_sop_temp_voice where id = #{id} and record_type in (0,1,2) limit 1
+        select id,company_user_id companyUserId,voice_txt voiceTxt,group_id from qw_sop_temp_voice where id = #{id} and record_type in (0,1,2) limit 1
     </select>
     <select id="selectQwSopTempVoiceNewList" resultType="com.fs.sop.domain.QwSopTempVoice">
         select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
-        user_voice_url userVoiceUrl from qw_sop_temp_voice
+        user_voice_url userVoiceUrl,group_id from qw_sop_temp_voice
         <where>
             <if test="companyUserId != null"> and company_user_id = #{companyUserId}</if>
             <if test="recordType != null"> and record_type = #{recordType}</if>
@@ -85,18 +86,18 @@
         </foreach>
     </select>
     <select id="selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select qw_user_id qwUserId,voice_url voiceUrl,voice_txt voiceTxt,user_voice_url userVoiceUrl,company_user_id companyUserId,
+        select qw_user_id qwUserId,voice_url voiceUrl,voice_txt voiceTxt,user_voice_url userVoiceUrl,company_user_id companyUserId,group_id,
                record_type recordType,duration from qw_sop_temp_voice where company_user_id = #{companyUserId} and voice_txt = #{voiceTxt}
     </select>
     <select id="selectQwSopTempVoiceByCompanyUserIdAndQwUserId" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,company_user_id companyUserId,voice_url voiceUrl,voice_txt voiceTxt,duration
+        select id,company_user_id companyUserId,voice_url voiceUrl,voice_txt voiceTxt,duration,group_id
         from qw_sop_temp_voice
         where company_user_id = #{companyUserId} and qw_user_id = #{qwUserId}
           and voice_url  is not null
             limit 1
     </select>
     <select id="selectQwSopTempVoiceListLimit" resultType="com.fs.sop.domain.QwSopTempVoice">
-        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,
+        select id,voice_txt voiceTxt,company_user_id companyUserId,voice_url voiceUrl,duration,record_type recordType,group_id,
                user_voice_url userVoiceUrl from qw_sop_temp_voice
         where record_type = #{recordType} and company_user_id is not null
         order by date_format(create_time,'%y%m%d') desc,company_user_id desc limit 10
@@ -214,6 +215,7 @@
         where company_user_id = #{companyUserId}
         and record_type =1
         <if test="keyword != null and keyword!='' ">  and voice_txt like concat('%', #{keyword}, '%') </if>
+        <if test="groupId != null and groupId!='' ">  and group_id =#{groupId} </if>
         order by create_time desc
     </select>
 
@@ -226,6 +228,7 @@
             and temp_id is null
             <if test="data.companyId != null "> and company_id = #{data.companyId}</if>
             <if test="data.recordType != null"> and record_type= #{data.recordType}</if>
+            <if test="data.groupId != null"> and group_id= #{data.groupId}</if>
         </where>
         GROUP BY company_id, voice_txt
     </select>
@@ -243,14 +246,16 @@
         company_user_id,
         company_id,
         voice_txt,
-        record_type
+        record_type,
+        group_id
         ) values
         <foreach collection="list" item="item" separator=",">
             (
             #{item.companyUserId},
             #{item.companyId},
             #{item.voiceTxt},
-            #{item.recordType}
+            #{item.recordType},
+            #{item.groupId}
             )
         </foreach>
         ON DUPLICATE KEY UPDATE

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

@@ -264,6 +264,7 @@
             id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id,
             fs_user_id, external_user_name,create_time,crt_Time,update_time
         from sop_user_logs_info where sop_id = #{sopId} and user_logs_id=#{userLogsId}
+        and (grade != 6 or grade is null)
     </select>
 
     <select id="selectRestoreByIsDaysNotStudy" parameterType="String" resultMap="SopUserLogsInfoResult">

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

@@ -253,7 +253,7 @@
             ul.`status` = '1'
           and qs.is_rating=1
           and qs.`status` in (2,3)
-          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) % 7 = 0
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) % 4 = 0
           AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) &gt; 0
     </select>
 
@@ -274,9 +274,9 @@
             ul.`status` = '1'
           and qs.is_rating=1
           and qs.type=2
-          and qs.send_type=2
+          and qs.send_type in (2,11)
           and qs.`status` in (2,3)
-          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= 7
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= 4
         ORDER BY id ASC
             LIMIT #{offset}, #{pageSize}
     </select>
@@ -298,7 +298,7 @@
         WHERE
             ul.`status` = '1'
           and qs.type=2
-          and qs.send_type=2
+          and qs.send_type in (2,11)
           and qs.`status` in (2,3)
           AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= #{notStudyDays}
         ORDER BY id ASC
@@ -314,7 +314,7 @@
         WHERE
             ul.`status` = '1'
           and qs.type=2
-          and qs.send_type=2
+          and qs.send_type in (2,11)
           and qs.`status` in (2,3)
           AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) &lt; #{notStudyDays}
         ORDER BY id ASC
@@ -342,7 +342,7 @@
           and qs.send_type=2
           and qs.`status` in (2,3)
           and ul.sop_id= #{sopId}
-          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= 7
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= 4
 
     </select>
 

+ 68 - 9
fs-user-app/src/main/java/com/fs/app/controller/AppBaseController.java

@@ -15,6 +15,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.sql.SqlUtil;
 import com.fs.company.domain.CompanyUserUser;
 import com.fs.company.service.ICompanyUserUserService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
@@ -33,6 +35,9 @@ public class AppBaseController {
 	@Autowired
 	private ICompanyUserUserService companyUserUserService;
 
+	@Autowired
+	private IFsUserService userService;
+
 	/**
 	* 用户token
 	*/
@@ -49,18 +54,65 @@ public class AppBaseController {
 	 */
 	public String getUserId()
 	{
-		String headValue =  ServletUtils.getRequest().getHeader("APPToken");
-		if (StringUtils.isNotEmpty(headValue)){
-			Claims claims=jwtUtils.getClaimByToken(headValue);
-			if(claims!=null){
-				String userId = claims.getSubject().toString();
+//		String headValue =  ServletUtils.getRequest().getHeader("APPToken");
+//		if (StringUtils.isNotEmpty(headValue)){
+//			Claims claims=jwtUtils.getClaimByToken(headValue);
+//			if(claims!=null){
+//				String userId = claims.getSubject().toString();
+//				return userId;
+//			}
+//			else{
+//				return null;
+//			}
+//		}
+//		return null;
+		// 1. 获取请求头
+		String headValue = ServletUtils.getRequest().getHeader("APPToken");
+		if (StringUtils.isEmpty(headValue)) {
+			return null;
+		}
+
+		// 2. 解析 Token
+		Claims claims;
+		try {
+			claims = jwtUtils.getClaimByToken(headValue);
+		} catch (Exception e) {
+			return null;
+		}
+		if (claims == null) {
+			return null;
+		}
+
+		// 3. 获取用户ID
+		String userId = claims.getSubject();
+		if (StringUtils.isEmpty(userId)) {
+			return null;
+		}
+
+		// 4. 校验用户状态(带异常保护)
+		try {
+			FsUser user = userService.selectFsUserByUserId(Long.parseLong(userId));
+			if (user != null && user.getStatus() == 1) {
 				return userId;
+			}else {
+				throw new CustomException("用户不存在或被禁用", 403);
 			}
-			else{
-				return null;
-			}
+		} catch (NumberFormatException e) {
+			return null;
 		}
-		return null;
+	}
+
+
+	/**
+	 * 获取销售用户ID;未携带/无效 companyUserToken 时返回 null(不抛异常,用于身份判断)
+	 */
+	public Long getCompanyUserIdOrNull()
+	{
+		String companyUserToken = ServletUtils.getRequest().getHeader("companyUserToken");
+		if (StringUtils.isEmpty(companyUserToken)) {
+			return null;
+		}
+		return redisCache.getCacheObject("company-user-token:" + companyUserToken);
 	}
 
 	/**
@@ -193,6 +245,13 @@ public class AppBaseController {
 	}
 
 
+	protected R denyIfNotCurrentUser(Long userId) {
+		if (!isCurrentUser(userId)) {
+			return R.error("非法操作");
+		}
+		return null;
+	}
+
 	/**
 	* 互医 客服登录(问诊 套餐包订单版)
 	*/

+ 2 - 2
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -605,9 +605,9 @@ public class AppLoginController extends AppBaseController{
                 update.setUserId(user.getUserId());
                 userService.updateFsUser(update);
             }
-            /*if (user.getStatus()==0){
+            if (user.getStatus()==0){
                 return R.error("登录失败,账户被禁用");
-            }*/
+            }
             int isFirstLogin = userNewTaskService.performTaskOne(user.getUserId());
             String token = jwtUtils.generateToken(user.getUserId());
             redisCache.setCacheObject("userToken:" + user.getUserId(), token, 604800, TimeUnit.SECONDS);

+ 43 - 2
fs-user-app/src/main/java/com/fs/app/controller/CommonController.java

@@ -8,6 +8,7 @@ import com.baidubce.appbuilder.model.dataset.DocumentListResponse;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.ai.service.IBaiduAIService;
 import com.fs.ai.vo.BaiduAIMsgResultVO;
+import com.fs.app.annotation.Login;
 import com.fs.app.param.SignParam;
 import com.fs.app.param.TestMoneyParam;
 import com.fs.app.utils.CityTreeUtil;
@@ -98,7 +99,7 @@ import static com.fs.common.utils.SecurityUtils.getUserId;
 @Api("公共接口")
 @RestController
 @RequestMapping(value="/app/common")
-public class CommonController {
+public class CommonController extends AppBaseController {
 	@Autowired
 	private RedisCache redisCache;
 	@Autowired
@@ -714,7 +715,7 @@ public class CommonController {
 	@GetMapping("/getAppContactWay")
 	public R getAppContactWay()
 	{
-		Long userId = getUserId();
+		Long userId = Long.parseLong(getUserId());
 
 		String appContactWayImgCode = qwAppContactWayService.getAppContactWayImgCode(userId);
 		return R.ok().put("data",appContactWayImgCode);
@@ -802,4 +803,44 @@ public class CommonController {
 				.put("storeProductTuiCate",storeProductTuiCate);
 	}
 
+	/**
+	 * 获取用户钱包明细
+	 */
+	@Login
+	@GetMapping("/getWallet/{userId}")
+	public R getWallet(@PathVariable("userId") Long userId){
+		R denied = denyIfNotCurrentUser(userId);
+		if (denied != null) {
+			return denied;
+		}
+		return userService.getWallet(userId);
+	}
+
+	/**
+	 * 用户钱包 积分兑换佣金
+	 */
+	@Login
+	@GetMapping("/integralExchange/{userId}")
+	public R integralExchange(@PathVariable("userId") Long userId){
+		R denied = denyIfNotCurrentUser(userId);
+		if (denied != null) {
+			return denied;
+		}
+		return userService.integralExchange(userId);
+	}
+
+	/**
+	 * 用户钱包 获取兑换明细
+	 */
+	@Login
+	@PostMapping("/exchangDetail")
+	public R exchangDetail(@RequestBody Map<String,Object> params){
+		Long userId = Long.valueOf(params.get("userId").toString());
+		R denied = denyIfNotCurrentUser(userId);
+		if (denied != null) {
+			return denied;
+		}
+		return userService.exchangDetail(params);
+	}
+
 }

+ 17 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -141,6 +141,23 @@ public class UserController extends  AppBaseController {
         if(StringUtils.isBlank(param.getKeywords())) {
             return R.ok().put("data",new ArrayList<>());
         }
+
+        // 有销售身份(companyUserToken)可搜全部;否则按客户限制,仅允许 C/D 前缀
+        if (getCompanyUserIdOrNull() == null) {
+            String keywords = param.getKeywords().trim();
+            if (keywords.length() < 2) {
+                return R.ok().put("data", new ArrayList<>());
+            }
+            char prefix = Character.toUpperCase(keywords.charAt(0));
+            if (prefix != 'C' && prefix != 'D') {
+                // 非 C/D 开头(如 U 或其他)不给展示
+                return R.ok().put("data", new ArrayList<>());
+            }
+            param.setOnlySales(true);
+            param.setSearchPrefix(String.valueOf(prefix));
+            param.setKeywords(keywords.substring(1));
+        }
+
         PageHelper.startPage(param.getPageNum(), param.getPageSize());
 
         if(StringUtils.isNotBlank(param.getKeywords())){

+ 22 - 2
fs-user-app/src/main/java/com/fs/app/controller/app/ImController.java

@@ -1,6 +1,8 @@
 package com.fs.app.controller.app;
 
+import com.fs.app.controller.AppBaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import io.swagger.annotations.Api;
@@ -20,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @RequestMapping(value = "/app/im")
 @Slf4j
-public class ImController {
+public class ImController extends AppBaseController {
 
     @Autowired
     private OpenIMService openIMService;
@@ -33,8 +35,26 @@ public class ImController {
      */
     @GetMapping("/getUserInfo")
     public R getUserInfo(String id) {
+        if (StringUtils.isBlank(id)) {
+            return R.ok().put("data", null);
+        }
+
+        // 有销售身份可查全部;否则按客户限制,仅允许 C/D(纯数字视为 U)
+        if (getCompanyUserIdOrNull() == null) {
+            String checkId = id.trim();
+            char prefix;
+            if (checkId.matches("\\d+")) {
+                prefix = 'U';
+            } else {
+                prefix = Character.toUpperCase(checkId.charAt(0));
+            }
+            if (prefix != 'C' && prefix != 'D') {
+                return R.ok().put("data", null);
+            }
+        }
+
         OpenImResponseDTO responseDTO = openIMService.getUserInfo(id);
-        return R.ok().put("data",responseDTO);
+        return R.ok().put("data", responseDTO);
     }
 
 }