Explorar o código

Merge remote-tracking branch 'origin/master'

jzp hai 2 días
pai
achega
9942d536cb
Modificáronse 41 ficheiros con 1825 adicións e 308 borrados
  1. 2 1
      fs-admin/src/main/resources/application.yml
  2. 1 0
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  3. 64 0
      fs-company/src/main/java/com/fs/company/controller/store/FsFollowTempController.java
  4. 60 0
      fs-company/src/main/java/com/fs/company/controller/store/FsIcdController.java
  5. 93 0
      fs-company/src/main/java/com/fs/company/controller/store/FsMaterialController.java
  6. 91 0
      fs-company/src/main/java/com/fs/company/controller/store/FsMaterialGroupController.java
  7. 71 0
      fs-company/src/main/java/com/fs/company/controller/store/FsPackageCateController.java
  8. 10 0
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreProductController.java
  9. 39 4
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  10. 10 0
      fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java
  11. 9 0
      fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java
  12. 333 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java
  13. 50 60
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  14. 262 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java
  15. 7 226
      fs-service/src/main/java/com/fs/company/domain/Company.java
  16. 41 0
      fs-service/src/main/java/com/fs/company/domain/CompanyMiniapp.java
  17. 62 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMiniappMapper.java
  18. 70 0
      fs-service/src/main/java/com/fs/company/service/ICompanyMiniappService.java
  19. 141 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyMiniappServiceImpl.java
  20. 18 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  21. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  22. 28 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  23. 4 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderExportVO.java
  24. 4 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  25. 6 0
      fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java
  26. 52 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  27. 7 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  28. 1 0
      fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java
  29. 8 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  30. 3 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java
  31. 84 13
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  32. 5 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  33. 1 1
      fs-service/src/main/java/com/fs/sop/vo/QwRatingVO.java
  34. 1 1
      fs-service/src/main/resources/application-config-druid-hcl.yml
  35. 1 0
      fs-service/src/main/resources/application-config-druid-xzt.yml
  36. 3 0
      fs-service/src/main/resources/application-druid-xzt.yml
  37. 91 0
      fs-service/src/main/resources/mapper/CompanyMiniappMapper.xml
  38. 12 0
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  39. 24 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  40. 12 1
      fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml
  41. 41 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

+ 2 - 1
fs-admin/src/main/resources/application.yml

@@ -9,5 +9,6 @@ spring:
 #    active: druid-yzt
 #    active: druid-sxjz
 #    active: druid-sft
-    active: druid-fby
+#    active: druid-fby
+    active: dev
 

+ 1 - 0
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -209,6 +209,7 @@ public class CompanyUserController extends AppBaseController {
         companyUser.setCreateTime(new Date());
         companyUser.setIsAudit(0);
         companyUser.setParentId(upCompanyUser.getUserId());
+        companyUser.setCompanyId(upCompanyUser.getCompanyId());
 
         // 部门
         CompanyDept dept = companyDeptService.getDefaultCompanyDeptByCompanyId(upCompanyUser.getCompanyId());

+ 64 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsFollowTempController.java

@@ -0,0 +1,64 @@
+package com.fs.company.controller.store;
+
+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.his.domain.FsFollowTemp;
+import com.fs.his.service.IFsFollowTempService;
+import com.fs.his.vo.OptionsVO;
+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 2023-07-14
+ */
+@RestController
+@RequestMapping("/store/followTemp")
+public class FsFollowTempController extends BaseController
+{
+    @Autowired
+    private IFsFollowTempService fsFollowTempService;
+
+    /**
+     * 查询随访模板列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsFollowTemp fsFollowTemp)
+    {
+        startPage();
+        List<FsFollowTemp> list = fsFollowTempService.selectFsFollowTempList(fsFollowTemp);
+        return getDataTable(list);
+    }
+
+
+
+    /**
+     * 获取随访模板详细信息
+     */
+    @GetMapping(value = "/{tempId}")
+    public AjaxResult getInfo(@PathVariable("tempId") Long tempId)
+    {
+        return AjaxResult.success(fsFollowTempService.selectFsFollowTempByTempId(tempId));
+    }
+
+
+
+    /**
+     * 查询模板名称列表
+     */
+    @GetMapping("/allList")
+    public TableDataInfo getAllList()
+    {
+        List<OptionsVO> list = fsFollowTempService.selectAllFsFollowTempList();
+        return getDataTable(list);
+    }
+}

+ 60 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsIcdController.java

@@ -0,0 +1,60 @@
+package com.fs.company.controller.store;
+
+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.his.domain.FsIcd;
+import com.fs.his.service.IFsIcdService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * icd编码库Controller
+ *
+ * @author fs
+ * @date 2024-04-22
+ */
+@RestController
+@RequestMapping("/store/icd")
+public class FsIcdController extends BaseController
+{
+    @Autowired
+    private IFsIcdService fsIcdService;
+
+    /**
+     * 查询icd编码库列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsIcd fsIcd)
+    {
+        startPage();
+        List<FsIcd> list = fsIcdService.selectFsIcdList(fsIcd);
+        return getDataTable(list);
+    }
+
+
+
+    /**
+     * 获取icd编码库详细信息
+     */
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsIcdService.selectFsIcdById(id));
+    }
+
+
+    @GetMapping(value = "/allIcd/{name}")
+    public AjaxResult allIcd(@PathVariable("name") String name)
+    {
+        return AjaxResult.success(fsIcdService.selectFsIcdByName(name));
+    }
+
+}

+ 93 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsMaterialController.java

@@ -0,0 +1,93 @@
+package com.fs.company.controller.store;
+
+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.his.domain.FsMaterial;
+import com.fs.his.service.IFsMaterialService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 素材库Controller
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@RestController
+@RequestMapping("/store/material")
+public class FsMaterialController extends BaseController
+{
+    @Autowired
+    private IFsMaterialService fsMaterialService;
+
+    /**
+     * 查询素材库列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsMaterial fsMaterial)
+    {
+        startPage();
+        fsMaterial.setStoreId(0L);
+        List<FsMaterial> list = fsMaterialService.selectFsMaterialList(fsMaterial);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出素材库列表
+     */
+    @Log(title = "素材库", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsMaterial fsMaterial)
+    {
+        fsMaterial.setStoreId(0L);
+        List<FsMaterial> list = fsMaterialService.selectFsMaterialList(fsMaterial);
+        ExcelUtil<FsMaterial> util = new ExcelUtil<FsMaterial>(FsMaterial.class);
+        return util.exportExcel(list, "material");
+    }
+
+    /**
+     * 获取素材库详细信息
+     */
+    @GetMapping(value = "/{materialId}")
+    public AjaxResult getInfo(@PathVariable("materialId") Long materialId)
+    {
+        return AjaxResult.success(fsMaterialService.selectFsMaterialByMaterialId(materialId));
+    }
+
+    /**
+     * 新增素材库
+     */
+    @Log(title = "素材库", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsMaterial fsMaterial)
+    {
+        fsMaterial.setStoreId(0L);
+        return toAjax(fsMaterialService.insertFsMaterial(fsMaterial));
+    }
+
+    /**
+     * 修改素材库
+     */
+    @Log(title = "素材库", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsMaterial fsMaterial)
+    {
+        return toAjax(fsMaterialService.updateFsMaterial(fsMaterial));
+    }
+
+    /**
+     * 删除素材库
+     */
+    @Log(title = "素材库", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{materialIds}")
+    public AjaxResult remove(@PathVariable Long[] materialIds)
+    {
+        return toAjax(fsMaterialService.deleteFsMaterialByMaterialIds(materialIds));
+    }
+}

+ 91 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsMaterialGroupController.java

@@ -0,0 +1,91 @@
+package com.fs.company.controller.store;
+
+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.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsMaterialGroup;
+import com.fs.his.service.IFsMaterialGroupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 素材分组Controller
+ *
+ * @author fs
+ * @date 2022-03-15
+ */
+@RestController
+@RequestMapping("/store/materialGroup")
+public class FsMaterialGroupController extends BaseController
+{
+    @Autowired
+    private IFsMaterialGroupService fsMaterialGroupService;
+
+    /**
+     * 查询素材分组列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsMaterialGroup fsMaterialGroup)
+    {
+        startPage();
+        fsMaterialGroup.setStoreId(0L);
+        List<FsMaterialGroup> list = fsMaterialGroupService.selectFsMaterialGroupList(fsMaterialGroup);
+        return getDataTable(list);
+    }
+
+
+
+    /**
+     * 获取素材分组详细信息
+     */
+    @GetMapping(value = "/{groupId}")
+    public AjaxResult getInfo(@PathVariable("groupId") Long groupId)
+    {
+        return AjaxResult.success(fsMaterialGroupService.selectFsMaterialGroupByGroupId(groupId));
+    }
+
+    /**
+     * 新增素材分组
+     */
+    @Log(title = "素材分组", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsMaterialGroup fsMaterialGroup)
+    {
+        fsMaterialGroup.setStoreId(0L);
+        return toAjax(fsMaterialGroupService.insertFsMaterialGroup(fsMaterialGroup));
+    }
+
+    /**
+     * 修改素材分组
+     */
+    @Log(title = "素材分组", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsMaterialGroup fsMaterialGroup)
+    {
+        return toAjax(fsMaterialGroupService.updateFsMaterialGroup(fsMaterialGroup));
+    }
+
+    /**
+     * 删除素材分组
+     */
+    @Log(title = "素材分组", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{groupIds}")
+    public AjaxResult remove(@PathVariable Long[] groupIds)
+    {
+        return toAjax(fsMaterialGroupService.deleteFsMaterialGroupByGroupIds(groupIds));
+    }
+
+    @GetMapping("/getAllList")
+    public R getAllList(FsMaterialGroup fsMaterialGroup)
+    {
+        fsMaterialGroup.setStoreId(0L);
+        List<FsMaterialGroup> list = fsMaterialGroupService.selectFsMaterialGroupList(fsMaterialGroup);
+        return R.ok().put("data",list);
+    }
+}

+ 71 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsPackageCateController.java

@@ -0,0 +1,71 @@
+package com.fs.company.controller.store;
+
+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.core.redis.RedisCache;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsPackageCate;
+import com.fs.his.param.FsPackageCateUParam;
+import com.fs.his.service.IFsPackageCateService;
+import com.fs.his.vo.OptionsVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 套餐包分类Controller
+ *
+ * @author fs
+ * @date 2024-05-08
+ */
+@RestController
+@RequestMapping("/store/packageCate")
+public class FsPackageCateController extends BaseController
+{
+    @Autowired
+    private IFsPackageCateService fsPackageCateService;
+    @Autowired
+    RedisCache redisCache;
+    /**
+     * 查询套餐包分类列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsPackageCateUParam fsPackageCate)
+    {
+        startPage();
+        List<FsPackageCate> list = fsPackageCateService.selectFsPackageCateList(fsPackageCate);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取套餐包分类详细信息
+     */
+    @GetMapping(value = "/{cateId}")
+    public AjaxResult getInfo(@PathVariable("cateId") Long cateId)
+    {
+        return AjaxResult.success(fsPackageCateService.selectFsPackageCateByCateId(cateId));
+    }
+
+
+
+    @GetMapping("/allList")
+    public AjaxResult allList()
+    {
+        Map map = fsPackageCateService.selectFsArticleCateAllList();
+        return AjaxResult.success(map);
+    }
+    @GetMapping("/cateList")
+    public TableDataInfo cateList()
+    {
+        List<OptionsVO> list = fsPackageCateService.selectCateList();
+        return getDataTable(list);
+    }
+}

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/store/FsStoreProductController.java

@@ -10,9 +10,11 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsStoreProduct;
 import com.fs.his.domain.FsStoreProductAttr;
 import com.fs.his.domain.FsStoreProductRule;
+import com.fs.his.param.FsProductAttrValueParam;
 import com.fs.his.param.FsStoreProductAddEditParam;
 import com.fs.his.service.IFsStoreProductAttrService;
 import com.fs.his.service.IFsStoreProductService;
+import com.fs.his.vo.FsStoreProductAttrValueVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
 import com.fs.his.vo.FsStoreProductVO;
 import com.fs.his.vo.OptionsVO;
@@ -182,5 +184,13 @@ public class FsStoreProductController extends BaseController
         return fsStoreProductService.addOrEdit(fsStoreProduct);
     }
 
+    @GetMapping("/getStoreProductAttrValueList")
+    public TableDataInfo getStoreProductAttrValueList(FsProductAttrValueParam param)
+    {
+        startPage();
+        List<FsStoreProductAttrValueVO> list=fsStoreProductService.selectFsStoreProductAttrValueListVO(param);
+        return getDataTable(list);
+    }
+
 
 }

+ 39 - 4
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -1,9 +1,6 @@
 package com.fs.app.task;
 
-import com.fs.app.taskService.QwExternalContactRatingService;
-import com.fs.app.taskService.SopLogsChatTaskService;
-import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.app.taskService.SopWxLogsService;
+import com.fs.app.taskService.*;
 import com.fs.qw.service.IQwExternalErrRetryService;
 import com.fs.qw.service.IQwGroupMsgService;
 import com.fs.qw.service.IQwWorkUserService;
@@ -75,6 +72,12 @@ public class qwTask {
     @Autowired
     private IQwSopTagService qwSopTagService;
 
+    @Autowired
+    private SopUserLogsInfoByIsDaysNotStudy logsInfoByIsDaysNotStudy;
+
+    @Autowired
+    private QwExternalContactRatingMoreSevenDaysService qwExternalContactRatingMoreSevenDaysService;
+
     /**
      * 定时任务:检查SOP规则时间
      * 执行时间:每天凌晨 1:10:00
@@ -306,6 +309,22 @@ public class qwTask {
         sopUserLogsService.repairSopUserLogsTimer();
     }
 
+
+    /**
+     * 凌晨 2点35开始,将营期小于7天中标记为 是否7天未看课的(E级) 客户的 但是看课了的恢复一下
+     */
+    @Scheduled(cron = "0 35 2 * * ?")
+    @Async
+    public void processSopUserLogsInfoByIsDaysNotStudy() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 是否7天未看课的(E级) 客户的 恢复一下 ======");
+
+        logsInfoByIsDaysNotStudy.restoreByIsDaysNotStudy();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== 用户E级恢复处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
     /**
      * 定时任务:客户评级处理
      * 执行时间:每天凌晨 3:45:00
@@ -327,6 +346,22 @@ public class qwTask {
         log.info("====== sop营期-用户分级处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }
 
+    /**
+     * 凌晨4点35开始 客户超过7天没有看课的 标记E级
+     */
+    @Scheduled(cron = "0 30 3 * * ?")
+    @Async
+    public void processQwSopExternalContactRatingMoreSevenDaysTimer() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 sop营期-用户超7天的看课情况 ======");
+
+        qwExternalContactRatingMoreSevenDaysService.ratingMoreSevenDaysUserLogs();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== sop营期-用户超7天处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
+
     /**
      * 更新掉所有前一天的所有待发送
      */

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java

@@ -0,0 +1,10 @@
+package com.fs.app.taskService;
+
+import com.fs.common.core.domain.R;
+
+public interface QwExternalContactRatingMoreSevenDaysService {
+    /**
+     * Sop客户超7天评次
+     */
+    public R ratingMoreSevenDaysUserLogs();
+}

+ 9 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java

@@ -0,0 +1,9 @@
+package com.fs.app.taskService;
+
+public interface SopUserLogsInfoByIsDaysNotStudy {
+
+    /**
+     * 将前7天营期中标记为 是否7天未看课的(E级) 客户的 恢复一下,突然有的恢复一下 (复刻版)
+     */
+    public void restoreByIsDaysNotStudy();
+}

+ 333 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java

@@ -0,0 +1,333 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.QwExternalContactRatingMoreSevenDaysService;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.IQwSopTempDayService;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.vo.QwRatingVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import com.google.common.util.concurrent.AtomicDouble;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExternalContactRatingMoreSevenDaysService {
+
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+
+    @Autowired
+    private IQwSopTempDayService qwSopTempDayService;
+
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+        log.info("初始化 {} 个消费者线程", consumerCount);
+    }
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+    @Override
+    public R ratingMoreSevenDaysUserLogs() {
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = sopUserLogsMapper.meetsTheRatingByUserInfoWithPaginationStudyDays(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+        return R.ok();
+    }
+
+
+    private void consumeTasks() {
+
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processSingleTask(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processSingleTask(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> sopUserLogsInfosList = sopUserLogsInfoMapper
+                .selectSopUserLogsInfoListBySopId(item.getSopId(), item.getId());
+
+        if (sopUserLogsInfosList == null || sopUserLogsInfosList.isEmpty()) {
+            log.error("当前营期没有客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> batchQwExternalContact = sopUserLogsInfosList.stream()
+                .map(logsInfo -> processUserLog(logsInfo, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!batchQwExternalContact.isEmpty()) {
+            batchUpdateQwExternalContact(batchQwExternalContact);
+        }
+    }
+
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            List<QwRatingVO> ratingVOS = fsCourseWatchLogMapper
+                    .selectFsCourseWatchLogByExtIdRatingMoreStudyDays(externalId, config.getNotStudyDays());
+
+            if (ratingVOS == null || ratingVOS.isEmpty() || ratingVOS.size() < 6) {
+                log.info("没有记录或不满足条件不评级或看课记录小于6 不评级,externalId: {}", externalId);
+                return null;
+            }
+
+
+            //判断 7天的时长是否大于0
+            boolean scoreMoreStudyLevel = getScoreMoreStudyLevel(ratingVOS);
+
+            if (!scoreMoreStudyLevel) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(5);
+                externalContact.setIsDaysNotStudy(1);
+                return externalContact;
+            }else {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(ratingVOS.get(0).getLevel());
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
+        int batchSize = 300;
+
+        for (int i = 0; i < notInExternalUseridList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, notInExternalUseridList.size());
+            List<QwExternalContact> batchList = notInExternalUseridList.subList(i, endIndex);
+
+            int finalI = i;
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalContactByMoreStudy(batchList);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByMoreStudy(batchList);
+                    log.info("成功更新看课7天数据,起始索引: {}, 数量: {}", finalI, batchList.size());
+                } catch (Exception e) {
+                    log.error("批量更新异常,批次起始索引: {}", finalI, e);
+                }
+
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有 `batchUpdateQwExternalContact` 的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+
+    /**
+     * 每6小时更新一次
+     */
+    @Scheduled(cron = "0 50 0/6 * * ?")
+    public void refreshRatingConfig() {
+
+        synchronized(configLock) {
+            try {
+                String json = configService.selectConfigByKey("qwRating:config");
+                QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+                if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                    qwRatingConfig = config;
+                    log.info("LoadedTime qwRating.config successfully.");
+                } else {
+                    log.error("Failed to load course.config from configService.");
+                }
+            } catch (Exception e) {
+                log.error("Exception while refreshing course.config: {}", e.getMessage(), e);
+            }
+        }
+
+    }
+
+
+    //查 E级
+    public boolean getScoreMoreStudyLevel(List<QwRatingVO> qwRatingVOS) {
+
+        AtomicDouble watchCount= new AtomicDouble();
+
+        qwRatingVOS.forEach(vo -> {
+            watchCount.addAndGet(vo.getWatchDuration());
+        });
+
+        // 判断总 watchDuration 是否 > 0
+        return watchCount.get() > 0;
+    }
+
+}

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

@@ -8,7 +8,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
@@ -150,7 +152,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private IQwGroupChatService qwGroupChatService;
     @Autowired
     private IQwGroupChatUserService qwGroupChatUserService;
-
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
     // Shutdown flags
     private volatile boolean running = true;
     @Autowired
@@ -304,44 +307,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
 
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
 
-        // 查询销售二级域名
-//        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
-//            String[] userKey = s.getUserId().split("\\|");
-//            if (userKey.length < 3) {
-//                return null;
-//            }
-//            return Long.parseLong(userKey[1]);
-//        }).filter(Objects::nonNull).collect(Collectors.toSet());
-//
-//        List<CompanyUser> companyUserList;
-//        if (ids.isEmpty()) {
-//            companyUserList = new ArrayList<>();
-//        } else {
-//            companyUserList = companyUserService.selectCompanyUserByIds(ids);
-//        }
-//
-//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
-//                .peek(s -> {
-//                    String[] userKey = s.getUserId().split("\\|");
-//                    if (userKey.length < 3) {
-//                        return;
-//                    }
-//
-//                    // 销售ID
-//                    Long companyUserId = Long.parseLong(userKey[1]);
-//                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
-//                    if (Objects.nonNull(companyUser)) {
-//                        if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
-//                            s.setDomain(companyUser.getDomain().trim());
-//                        } else {
-//                            s.setDomain(config.getRealLinkDomainName().trim());
-//                        }
-//                    } else {
-//                        s.setDomain(config.getRealLinkDomainName().trim());
-//                    }
-//                })
-//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
 
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
 
@@ -350,7 +319,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config,miniMap);
         }
 
         // 等待所有 SOP 分组处理完成
@@ -371,9 +340,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             backoff = @Backoff(delay = 2000)
     )
     public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
-                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config) {
+                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config);
+            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config,miniMap);
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } finally {
@@ -383,7 +352,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
-            QwGroupChat> groupChatMap,CourseConfig config) throws Exception {
+            QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) throws Exception {
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
 
         if (ruleTimeVO == null) {
@@ -425,7 +394,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         for (SopUserLogsVo logVo : userLogsVos) {
-            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config);
+            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config,miniMap);
         }
 
         // 等待所有用户日志处理完成
@@ -446,9 +415,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     )
     public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
                                     CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
-                                    String miniAppId,CourseConfig config) {
+                                    String miniAppId,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config);
+            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config,miniMap);
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } finally {
@@ -458,7 +427,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
-                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,
+                                CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
 
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
@@ -510,6 +480,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
             String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
             String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
+            Integer sendMsgType = qwUserByRedis.getSendMsgType();
 
             if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
                 log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
@@ -635,7 +606,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                         insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
                                 companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
-                                groupChatMap, miniAppId,config);
+                                groupChatMap, miniAppId,config,miniMap, sendMsgType);
 
                     }
                 } catch (Exception e) {
@@ -678,7 +649,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                    String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
-                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config,
+                                   Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType) {
         String formattedSendTime = sendTime.toInstant()
                 .atZone(ZoneId.systemDefault())
                 .format(DATE_TIME_FORMATTER);
@@ -705,7 +677,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
-                        null, true, miniAppId, groupChat,config);
+                        null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType);
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
@@ -714,7 +686,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                                 type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
-                                null, false, miniAppId, groupChat,config);
+                                null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType);
                     });
                 }
             }
@@ -725,9 +697,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String externalId = contactId.getExternalId().toString();
                     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());
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId, null,config);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId,
+                            null,config, miniMap, grade, sendMsgType);
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
@@ -831,11 +805,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                      Long videoId, int type, String qwUserId,
+                                      SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, int type, String qwUserId,
                                       String companyUserId, String companyId, String externalId, String welcomeText,
                                       String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
-                                      QwGroupChat groupChat,CourseConfig config) {
+                                      QwGroupChat groupChat,CourseConfig config,
+                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
+                                      Integer grade, Integer sendMsgType  ) {
         switch (type) {
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
@@ -843,7 +818,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                         qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId,
-                        isGroupChat, miniAppId, groupChat,config);
+                        isGroupChat, miniAppId, groupChat,config,miniMap, grade, sendMsgType);
                 break;
             case 3:
                 handleOrderMessage(sopLogs, content);
@@ -873,10 +848,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                     SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                     Long videoId, String qwUserId, String companyUserId,
+                                     SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
                                      String companyId, String externalId, String welcomeText, String qwUserName,
-                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config) {
+                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
+                                     Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType) {
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -968,10 +943,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
 
-                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                    if (!miniMap.isEmpty() && sendMsgType==1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (grade == null) ? 5 : grade;
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    setting.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    }else if (!StringUtil.strIsNullOrEmpty(miniAppId)){
                         setting.setMiniprogramAppid(miniAppId);
                     }else {
-                        log.error("公司的小程序id为空:采用了前端传的固定值"+sopLogs.getSopId());
+                        log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                     }
 
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));

+ 262 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java

@@ -0,0 +1,262 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.SopUserLogsInfoByIsDaysNotStudy;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.service.ISopUserLogsService;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class SopUserLogsInfoByIsDaysNotStudyImpl implements SopUserLogsInfoByIsDaysNotStudy {
+
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ISopUserLogsService iSopUserLogsService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+    }
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+
+    @Override
+    public void restoreByIsDaysNotStudy() {
+
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = iSopUserLogsService.meetsTherestoreByIsDaysNotStudy(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+    }
+
+    private void consumeTasks() {
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processRestoreByIsDaysNotStudy(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processRestoreByIsDaysNotStudy(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> infos = iSopUserLogsInfoService.selectRestoreByIsDaysNotStudy(
+                item.getSopId(), item.getId());
+
+        if (infos == null || infos.isEmpty()) {
+            log.error("当前营期没有E级客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> contacts = infos.stream()
+                .map(info -> processUserLog(info, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!contacts.isEmpty()) {
+            batchUpdateQwExternalContact(contacts);
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> contacts) {
+        // 9. 优化分批逻辑
+        int total = contacts.size();
+        for (int i = 0; i < total; i += 300) {
+            List<QwExternalContact> batch = contacts.subList(i, Math.min(i + 300, total));
+
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalByIsDaysNotStudy(batch);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByIsDaysNotStudy(batch);
+                } catch (Exception e) {
+                    log.error("批量更新异常, 批次大小: {}", batch.size(), e);
+                }
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有  的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+    /**
+    * 只计算时长
+    */
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            Integer sumDuration = fsCourseWatchLogMapper.selectFsCourseWatchLogByByIsDaysNotStudy(externalId, config.getNotStudyDays());
+
+            if (sumDuration!=null && sumDuration>0) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+            return null;
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+
+}

+ 7 - 226
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -112,233 +112,14 @@ public class Company extends BaseEntity
     private String courseMiniAppId;
     /** 会员是否默认黑名单,1-是;0-否(用于销售分享成为会员的操作) */
     private Integer fsUserIsDefaultBlack;
+    private Integer repeat;
+    private Integer sendIfType;
+    private Integer ifNum;
+    @TableField(exist = false)
+    private List<String> miniAppMaster;
+    @TableField(exist = false)
+    private List<String> miniAppServer;
 
-//    public String getDoctorIds() {
-//        return doctorIds;
-//    }
-//
-//    public void setDoctorIds(String doctorIds) {
-//        this.doctorIds = doctorIds;
-//    }
-//
-//    public String getFollowDoctorIds() {
-//        return followDoctorIds;
-//    }
-//
-//    public void setFollowDoctorIds(String followDoctorIds) {
-//        this.followDoctorIds = followDoctorIds;
-//    }
-//
-//    public String getManager() {
-//        return manager;
-//    }
-//
-//    public void setManager(String manager) {
-//        this.manager = manager;
-//    }
-//
-//    public String getOmsCode() {
-//        return omsCode;
-//    }
-//
-//    public void setOmsCode(String omsCode) {
-//        this.omsCode = omsCode;
-//    }
-//
-//    public Integer getVoiceCallerNumber() {
-//        return voiceCallerNumber;
-//    }
-//
-//    public void setVoiceCallerNumber(Integer voiceCallerNumber) {
-//        this.voiceCallerNumber = voiceCallerNumber;
-//    }
-//
-//    public Integer getIsDel() {
-//        return isDel;
-//    }
-//
-//    public void setIsDel(Integer isDel) {
-//        this.isDel = isDel;
-//    }
-//
-//    public BigDecimal getTuiMoney() {
-//        return tuiMoney;
-//    }
-//
-//    public void setTuiMoney(BigDecimal tuiMoney) {
-//        this.tuiMoney = tuiMoney;
-//    }
-//
-//    @Override
-//    public String getRemark() {
-//        return remark;
-//    }
-//
-//    @Override
-//    public void setRemark(String remark) {
-//        this.remark = remark;
-//    }
-//
-//    public String getLinkName() {
-//        return linkName;
-//    }
-//
-//    public void setLinkName(String linkName) {
-//        this.linkName = linkName;
-//    }
-//
-//    public Integer getLimitUserCount() {
-//        return limitUserCount;
-//    }
-//
-//    public void setLimitUserCount(Integer limitUserCount) {
-//        this.limitUserCount = limitUserCount;
-//    }
-//
-//    public Date getLimitTime() {
-//        return limitTime;
-//    }
-//
-//    public void setLimitTime(Date limitTime) {
-//        this.limitTime = limitTime;
-//    }
-//
-//    public String getAppId() {
-//        return appId;
-//    }
-//
-//    public void setAppId(String appId) {
-//        this.appId = appId;
-//    }
-//
-//    public String getAppKey() {
-//        return appKey;
-//    }
-//
-//    public void setAppKey(String appKey) {
-//        this.appKey = appKey;
-//    }
-//
-//    public Long getUserId() {
-//        return userId;
-//    }
-//
-//    public void setUserId(Long userId) {
-//        this.userId = userId;
-//    }
-//
-//    public String getAddressId() {
-//        return addressId;
-//    }
-//
-//    public void setAddressId(String addressId) {
-//        this.addressId = addressId;
-//    }
-//
-//    public Integer getCompanyType() {
-//        return companyType;
-//    }
-//
-//    public void setCompanyType(Integer companyType) {
-//        this.companyType = companyType;
-//    }
-//
-//    public static long getSerialVersionUID() {
-//        return serialVersionUID;
-//    }
-//
-//
-//
-//    public String getUserName() {
-//        return userName;
-//    }
-//
-//    public void setUserName(String userName) {
-//        this.userName = userName;
-//    }
-//
-//    public String getPassword() {
-//        return password;
-//    }
-//
-//    public void setPassword(String password) {
-//        this.password = password;
-//    }
-//
-//    public void setCompanyId(Long companyId)
-//    {
-//        this.companyId = companyId;
-//    }
-//
-//    public Long getCompanyId()
-//    {
-//        return companyId;
-//    }
-//    public void setCompanyName(String companyName)
-//    {
-//        this.companyName = companyName;
-//    }
-//
-//    public String getCompanyName()
-//    {
-//        return companyName;
-//    }
-//    public void setCompanyMobile(String companyMobile)
-//    {
-//        this.companyMobile = companyMobile;
-//    }
-//
-//    public String getCompanyMobile()
-//    {
-//        return companyMobile;
-//    }
-//    public void setCompanyAddress(String companyAddress)
-//    {
-//        this.companyAddress = companyAddress;
-//    }
-//
-//    public String getCompanyAddress()
-//    {
-//        return companyAddress;
-//    }
-//    public void setStatus(Integer status)
-//    {
-//        this.status = status;
-//    }
-//
-//    public Integer getStatus()
-//    {
-//        return status;
-//    }
-//    public void setStartTime(Date startTime)
-//    {
-//        this.startTime = startTime;
-//    }
-//
-//    public Date getStartTime()
-//    {
-//        return startTime;
-//    }
-//
-//    public void setMoney(BigDecimal money)
-//    {
-//        this.money = money;
-//    }
-//
-//    public BigDecimal getMoney()
-//    {
-//        return money;
-//    }
-//
-//    public void setVoiceApiId(Long voiceApiId)
-//    {
-//        this.voiceApiId = voiceApiId;
-//    }
-//
-//    public Long getVoiceApiId()
-//    {
-//        return voiceApiId;
-//    }
 
 
 }

+ 41 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyMiniapp.java

@@ -0,0 +1,41 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 客服公司小程序对象 company_miniapp
+ *
+ * @author fs
+ * @date 2025-07-24
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyMiniapp extends BaseEntity{
+
+    /** id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 客服公司ID */
+    @Excel(name = "客服公司ID")
+    private Long companyId;
+
+    /** 小程序appid */
+    @Excel(name = "小程序appid")
+    private String appId;
+
+    /** 主从 0主小程序1备用小程序 */
+    @Excel(name = "主从 0主小程序1备用小程序")
+    private Integer type;
+
+    /** 排序 */
+    @Excel(name = "排序")
+    private Integer sortNum;
+
+
+}

+ 62 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMiniappMapper.java

@@ -0,0 +1,62 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyMiniapp;
+
+import java.util.List;
+
+/**
+ * 客服公司小程序Mapper接口
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+public interface CompanyMiniappMapper extends BaseMapper<CompanyMiniapp>{
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    CompanyMiniapp selectCompanyMiniappById(Long id);
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序集合
+     */
+    List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int insertCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int updateCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 删除客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    int deleteCompanyMiniappById(Long id);
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyMiniappByIds(Long[] ids);
+}

+ 70 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyMiniappService.java

@@ -0,0 +1,70 @@
+package com.fs.company.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.company.domain.CompanyMiniapp;
+import com.fs.company.vo.CompanyVO;
+
+import java.util.List;
+
+/**
+ * 客服公司小程序Service接口
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+public interface ICompanyMiniappService extends IService<CompanyMiniapp>{
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    CompanyMiniapp selectCompanyMiniappById(Long id);
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序集合
+     */
+    List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int insertCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    int updateCompanyMiniapp(CompanyMiniapp companyMiniapp);
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的客服公司小程序主键集合
+     * @return 结果
+     */
+    int deleteCompanyMiniappByIds(Long[] ids);
+
+    /**
+     * 删除客服公司小程序信息
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    int deleteCompanyMiniappById(Long id);
+
+    void insertBatch(List<String> appIds, Long companyId, Integer type);
+
+    void removeByCompanyId(Long companyId);
+
+    void setMiniAppList(List<CompanyVO> companyVOS);
+    List<CompanyMiniapp> getMiniAppListByCompanyList(List<Long> companyIds);
+}

+ 141 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyMiniappServiceImpl.java

@@ -0,0 +1,141 @@
+package com.fs.company.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
+import com.fs.company.domain.CompanyMiniapp;
+import com.fs.company.mapper.CompanyMiniappMapper;
+import com.fs.company.service.ICompanyMiniappService;
+import com.fs.company.vo.CompanyVO;
+import org.springframework.stereotype.Service;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+/**
+ * 客服公司小程序Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-07-24
+ */
+@Service
+public class CompanyMiniappServiceImpl extends ServiceImpl<CompanyMiniappMapper, CompanyMiniapp> implements ICompanyMiniappService {
+
+    public static BiFunction<Integer, List<CompanyMiniapp>, List<String>> GET_MINI_APP_STR = (type, list) -> list.stream().filter(m -> Objects.equals(m.getType(), type)).sorted(Comparator.comparing(CompanyMiniapp::getSortNum)).map(CompanyMiniapp::getAppId).collect(Collectors.toList());
+
+
+    /**
+     * 查询客服公司小程序
+     * 
+     * @param id 客服公司小程序主键
+     * @return 客服公司小程序
+     */
+    @Override
+    public CompanyMiniapp selectCompanyMiniappById(Long id)
+    {
+        return baseMapper.selectCompanyMiniappById(id);
+    }
+
+    /**
+     * 查询客服公司小程序列表
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 客服公司小程序
+     */
+    @Override
+    public List<CompanyMiniapp> selectCompanyMiniappList(CompanyMiniapp companyMiniapp)
+    {
+        return baseMapper.selectCompanyMiniappList(companyMiniapp);
+    }
+
+    /**
+     * 新增客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    @Override
+    public int insertCompanyMiniapp(CompanyMiniapp companyMiniapp)
+    {
+        companyMiniapp.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertCompanyMiniapp(companyMiniapp);
+    }
+
+    /**
+     * 修改客服公司小程序
+     * 
+     * @param companyMiniapp 客服公司小程序
+     * @return 结果
+     */
+    @Override
+    public int updateCompanyMiniapp(CompanyMiniapp companyMiniapp)
+    {
+        companyMiniapp.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateCompanyMiniapp(companyMiniapp);
+    }
+
+    /**
+     * 批量删除客服公司小程序
+     * 
+     * @param ids 需要删除的客服公司小程序主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyMiniappByIds(Long[] ids)
+    {
+        return baseMapper.deleteCompanyMiniappByIds(ids);
+    }
+
+    /**
+     * 删除客服公司小程序信息
+     * 
+     * @param id 客服公司小程序主键
+     * @return 结果
+     */
+    @Override
+    public int deleteCompanyMiniappById(Long id)
+    {
+        return baseMapper.deleteCompanyMiniappById(id);
+    }
+
+    @Override
+    public void insertBatch(List<String> appIds, Long companyId, Integer type) {
+        AtomicInteger i = new AtomicInteger();
+        List<CompanyMiniapp> list = appIds.stream().map(e -> {
+            CompanyMiniapp miniapp = new CompanyMiniapp();
+            miniapp.setCompanyId(companyId);
+            miniapp.setAppId(e);
+            miniapp.setType(type);
+            miniapp.setSortNum(i.getAndIncrement());
+            return miniapp;
+        }).collect(Collectors.toList());
+        super.saveBatch(list);
+    }
+
+    @Override
+    public void removeByCompanyId(Long companyId) {
+        remove(new QueryWrapper<CompanyMiniapp>().eq("company_id",companyId));
+    }
+
+    @Override
+    public void setMiniAppList(List<CompanyVO> companyVOS) {
+        List<CompanyMiniapp> miniAppList = getMiniAppListByCompanyList(PubFun.listToNewList(companyVOS, CompanyVO::getCompanyId));
+        Map<Long, List<CompanyMiniapp>> miniAppMap = PubFun.listToMapByGroupList(miniAppList, CompanyMiniapp::getCompanyId);
+        companyVOS.stream().filter(e -> miniAppMap.containsKey(e.getCompanyId())).forEach(e -> {
+            List<CompanyMiniapp> list = miniAppMap.get(e.getCompanyId());
+            e.setMiniAppMaster(GET_MINI_APP_STR.apply(0, list));
+            e.setMiniAppServer(GET_MINI_APP_STR.apply(1, list));
+        });
+    }
+
+    @Override
+    public List<CompanyMiniapp> getMiniAppListByCompanyList(List<Long> companyIds) {
+        return list(new QueryWrapper<CompanyMiniapp>().in("company_id", companyIds));
+    }
+}

+ 18 - 1
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -13,6 +13,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyParam;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyProfitService;
 import com.fs.company.service.ICompanyRoleService;
 import com.fs.company.vo.CompanyCrmVO;
@@ -54,6 +55,8 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private CompanyMapper companyMapper;
     @Autowired
+    private ICompanyMiniappService companyMiniappService;
+    @Autowired
     private CompanyDeptMapper deptMapper;
     @Autowired
     private ICompanyRoleService roleService;
@@ -232,7 +235,7 @@ public class CompanyServiceImpl implements ICompanyService
             userPostMapper.insertCompanyUserPost(userPost);
             company.setUserId(user.getUserId());
             companyMapper.updateCompany(company);
-
+            bindMiniApp(company);
             return R.ok();
         }
         else
@@ -251,8 +254,22 @@ public class CompanyServiceImpl implements ICompanyService
     public int updateCompany(Company company)
     {
         company.setUpdateTime(DateUtils.getNowDate());
+        bindMiniApp(company);
         return companyMapper.updateCompany(company);
     }
+    // 绑定小程序
+    public void bindMiniApp(Company company){
+        companyMiniappService.removeByCompanyId(company.getCompanyId());
+        List<String> miniAppMaster = company.getMiniAppMaster();
+        if(miniAppMaster != null && !miniAppMaster.isEmpty()){
+            companyMiniappService.insertBatch(miniAppMaster, company.getCompanyId(), 0);
+        }
+        List<String> miniAppServer = company.getMiniAppServer();
+        if(miniAppServer != null && !miniAppServer.isEmpty()){
+            companyMiniappService.insertBatch(miniAppServer, company.getCompanyId(), 1);
+        }
+
+    }
 
     /**
      * 批量删除企业

+ 3 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyVO.java

@@ -7,6 +7,7 @@ import lombok.Data;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.List;
 
 /**
  * 企业账户记录对象 company_money_logs
@@ -86,4 +87,6 @@ public class CompanyVO implements Serializable
     private String followDoctorName;
 
     private String restartTime;
+    private List<String> miniAppMaster;
+    private List<String> miniAppServer;
 }

+ 28 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -165,6 +165,34 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "\tAND wl.create_time < CURDATE() ")
     List<QwRatingVO> selectFsCourseWatchLogByExtIdRating(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
 
+    @Select("SELECT\n" +
+            "\twl.duration AS watchDuration,\n" +
+            "\tcv.duration AS allDuration,\n" +
+            "\twl.finish_time,\n" +
+            "\twl.create_time," +
+            "\tec.`level` \n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "\tLEFT JOIN qw_external_contact ec ON wl.qw_external_contact_id = ec.id\n" +
+            "\tLEFT JOIN fs_user_course_video cv ON wl.video_id = cv.video_id \n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB(CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    List<QwRatingVO> selectFsCourseWatchLogByExtIdRatingMoreStudyDays(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
+
+    @Select("SELECT\n" +
+            "\tCOALESCE(SUM(wl.duration), 0) AS watchDuration\n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB( CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    Integer selectFsCourseWatchLogByByIsDaysNotStudy(@Param("externalId") Long externalId,@Param("dayNum") Integer dayNum);
+
     @Select("select l.*,v.title,c.course_name from fs_course_watch_log l LEFT JOIN fs_user_course_video v ON v.video_id = l.video_id LEFT JOIN fs_user_course c ON c.course_id = l.course_id WHERE l.qw_external_contact_id =#{ExtId} and l.qw_user_id=#{QwUserId} and DATE(l.create_time) =CURDATE() ORDER BY l.create_time  desc LIMIT 1  ")
     FsCourseWatchLogVO selectFsCourseWatchLogByExtIdAndQwUserId(@Param("ExtId")String ExtId,@Param("QwUserId")Long QwUserId);
 

+ 4 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderExportVO.java

@@ -64,6 +64,10 @@ public class FsStoreOrderExportVO implements Serializable
     @Excel(name = "商品金额",cellType= Excel.ColumnType.NUMERIC)
     private BigDecimal totalPrice;
 
+    /** 商品数量 */
+    @Excel(name = "商品数量")
+    private Integer totalNum;
+
 
     /** 实际支付金额 */
     @Excel(name = "应付金额",cellType= Excel.ColumnType.NUMERIC)

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

@@ -78,6 +78,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
                                                   @Param("corpId") String corpId);
 
 
+    public int batchUpdateQwExternalContactByMoreStudy(List<QwExternalContact> qwExternalContact);
+    public int batchUpdateQwExternalByIsDaysNotStudy(List<QwExternalContact> qwExternalContact);
+
+
     @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/sop/domain/SopUserLogsInfo.java

@@ -73,6 +73,12 @@ public class SopUserLogsInfo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String inComingETime;
 
+
+    /**
+     * 评级
+     */
+    private Integer grade;
+
     /**
      * 营期时间
      */

+ 52 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -2,6 +2,7 @@ package com.fs.sop.mapper;
 
 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.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
@@ -148,11 +149,62 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     List<SopUserLogsInfo> selectSopUserLogsInfoListBySopId(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
 
+    @DataSource(DataSourceType.SOP)
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
+
 
 
     @DataSource(DataSourceType.SOP)
     void batchInsertSopUserLogsInfo(@Param("SopUserLogsInfo") List<SopUserLogsInfo> logsToInsert);
 
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = CASE " +
+            "<foreach collection='contactList' item='item'> " +
+            "    WHEN external_id = #{item.id} THEN " +
+            "    CASE WHEN #{item.level} = 5 AND #{item.isDaysNotStudy}=1 THEN 1 ELSE 0 END " +
+            "</foreach> " +
+            "ELSE is_days_not_study " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'> " +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByMoreStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = 0 " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET grade = CASE external_id " +
+            "<foreach collection=\"contactList\" item=\"item\">" +
+            "    WHEN #{item.id} THEN #{item.level} " +
+            "</foreach>" +
+            "    ELSE grade " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByLevel(@Param("contactList") List<QwExternalContact> contactList);
+
+
+
+
     @DataSource(DataSourceType.SOP)
     void batchUpdateSopUserLogsInfoToTime(BatchSopUserLogsInfoParamU paramU);
 

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

@@ -64,6 +64,13 @@ public interface SopUserLogsMapper {
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoWithPagination(@Param("offset") int offset,@Param("pageSize") int pageSize);
 
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTheRatingByUserInfoWithPaginationStudyDays(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoBySopId(@Param("sopId") String sopId);
 

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

@@ -5,6 +5,7 @@ import lombok.Data;
 @Data
 public class QwRatingConfig {
     private Integer levelDay;
+    private Integer notStudyDays;
     private Integer aLevelMin;
     private Integer aLevelMax;
     private Integer bLevelMin;

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

@@ -1,6 +1,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.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
@@ -54,6 +55,11 @@ public interface ISopUserLogsInfoService {
     List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info);
 
     void batchInsertSopUserLogsInfo(List<SopUserLogsInfo> logsToInsert);
+
+    void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList);
+
     void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert);
     /**
      * 查询sopUserLogsInfo
@@ -74,5 +80,7 @@ public interface ISopUserLogsInfoService {
     public R sendUserLogsInfoMsg(SendUserLogsInfoMsgParam param);
     public R sendUserLogsInfoMsgType(SendUserLogsInfoMsgParam param);
 
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId);
+
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java

@@ -61,4 +61,7 @@ public interface ISopUserLogsService {
     void updateLogDate(UpdateSopUserLogDateVo vo);
 
     void addGroupChat(AddSopUserGroupChat vo);
+
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset,int pageSize,Integer notStudyDays);
+
 }

+ 84 - 13
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -3,12 +3,15 @@ package com.fs.sop.service.impl;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseDomainName;
 import com.fs.course.domain.FsCourseLink;
@@ -153,6 +156,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
 
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
+
     @Override
     public void save(SopUserLogsInfo sopUserLogsInfo) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(sopUserLogsInfo);
@@ -218,6 +224,21 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.batchInsertSopUserLogsInfo(logsToInsert);
     }
 
+    @Override
+    public void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByMoreStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsDaysNotStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(contactList);
+    }
+
     @Override
     public void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(logsToInsert);
@@ -698,6 +719,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }).collect(Collectors.toList());
             }
         }else{
+
+
+            // 查询公司关联小程序数据
+            List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+
+            Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+
+
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
@@ -810,12 +839,28 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
 
-                            if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                                log.error("企业未配置小程序-"+param.getCorpId());
-                            }else {
-                                //置换各自的小程序
+                            if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                if (integerListMap != null) {
+
+                                    int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                                    int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                                    List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                    if (miniapps != null && !miniapps.isEmpty()) {
+                                        CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                        if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                            st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                        }
+                                    }
+                                }
+                            } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                                 st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                            } else {
+                                log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                             }
+
+
                             st.setMiniprogramPage(linkByMiniApp);
 
                             break;
@@ -874,6 +919,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return null;
     }
 
+    @Override
+    public List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId) {
+        return sopUserLogsInfoMapper.selectRestoreByIsDaysNotStudy(sopId, userLogsId);
+    }
+
     @Override
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId) {
 
@@ -915,6 +965,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
         List<SopUserLogsInfo> sopUserLogs = sopUserLogsMapper.selectSopUserLogsByIds(param.getIds());
 
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
 
         //排序
         int sort = 0;
@@ -955,7 +1008,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 log.error("员工信息用户不存在:" + key.getKey()+",企业id:"+key.getValue());
             }else {
 
-                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort, finalSendType);
+                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort,
+                        finalSendType,miniMap );
 
                 //批量插入 发送记录
                 if (!sopLogsList.isEmpty()) {
@@ -972,7 +1026,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     private List<QwSopLogs> processInsertSopUserLogsInfo(List<SopUserLogsInfo> sopUserLogsInfos,QwUser qwUser,
                                                          SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
-                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType){
+                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType,
+                                                         Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
 
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
@@ -1026,7 +1081,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
             switch (finalSendType){
                 case 5:
-                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,contact,dataTime, finalDomainName);
+                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,
+                            contact,dataTime, finalDomainName,miniMap);
                     setting.setSetting(list);
                     break;
                 case 9:
@@ -1088,7 +1144,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private List<QwSopCourseFinishTempSetting.Setting> processSetting(SopUserLogsInfo item, QwUser qwUser,
                                                                       SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
-                                                                      QwExternalContact contact,Date dataTime,String domainName){
+                                                                      QwExternalContact contact,Date dataTime,String domainName,
+                                                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
 
         for (QwSopCourseFinishTempSetting.Setting st : list) {
@@ -1158,13 +1215,27 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                    }else {
 //                        st.setMiniprogramAppid(config.getMiniprogramAppid());
 //                    }
-                    if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                        log.error("企业未配置小程序-"+param.getCorpId());
-
-                    }else {
-                        //置换各自的小程序
+                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                         st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                    } else {
+                        log.error("企业未配置小程序-" + param.getCorpId());
                     }
+
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
                 default:

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

@@ -955,6 +955,11 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
         sopUserLogsMapper.batchInsertSopUserLogs(list);
     }
 
+    @Override
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset, int pageSize, Integer notStudyDays) {
+        return sopUserLogsMapper.meetsTherestoreByIsDaysNotStudy(offset,pageSize,notStudyDays);
+    }
+
     //批量更新
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
         // 定义批量插入的大小

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

@@ -16,7 +16,7 @@ public class QwRatingVO {
     */
     private Integer allDuration;
 
-
+    private Integer level;
 
     /**
      * 1升 2降 3未变动

+ 1 - 1
fs-service/src/main/resources/application-config-druid-hcl.yml

@@ -85,7 +85,7 @@ cloud_host:
   company_name: 恒春来
 #看课授权时显示的头像
 headerImg:
-  imgUrl: https://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250803/1754213762409.png
+  imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250808/1754640068227.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-xzt.yml

@@ -82,6 +82,7 @@ headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
+  aiApi: 1212121212
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:

+ 3 - 0
fs-service/src/main/resources/application-druid-xzt.yml

@@ -139,3 +139,6 @@ rocketmq:
         group: test-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 91 - 0
fs-service/src/main/resources/mapper/CompanyMiniappMapper.xml

@@ -0,0 +1,91 @@
+<?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.company.mapper.CompanyMiniappMapper">
+    
+    <resultMap type="CompanyMiniapp" id="CompanyMiniappResult">
+        <result property="id"    column="id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="appId"    column="app_id"    />
+        <result property="type"    column="type"    />
+        <result property="sortNum"    column="sort_num"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectCompanyMiniappVo">
+        select id, company_id, app_id, type, sort_num, create_time, create_by, update_by, update_time, remark from company_miniapp
+    </sql>
+
+    <select id="selectCompanyMiniappList" parameterType="CompanyMiniapp" resultMap="CompanyMiniappResult">
+        <include refid="selectCompanyMiniappVo"/>
+        <where>  
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="appId != null  and appId != ''"> and app_id = #{appId}</if>
+            <if test="type != null "> and type = #{type}</if>
+            <if test="sortNum != null "> and sort_num = #{sortNum}</if>
+        </where>
+    </select>
+    
+    <select id="selectCompanyMiniappById" parameterType="Long" resultMap="CompanyMiniappResult">
+        <include refid="selectCompanyMiniappVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertCompanyMiniapp" parameterType="CompanyMiniapp" useGeneratedKeys="true" keyProperty="id">
+        insert into company_miniapp
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">company_id,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="type != null">type,</if>
+            <if test="sortNum != null">sort_num,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">#{companyId},</if>
+            <if test="appId != null">#{appId},</if>
+            <if test="type != null">#{type},</if>
+            <if test="sortNum != null">#{sortNum},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyMiniapp" parameterType="CompanyMiniapp">
+        update company_miniapp
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="appId != null">app_id = #{appId},</if>
+            <if test="type != null">type = #{type},</if>
+            <if test="sortNum != null">sort_num = #{sortNum},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCompanyMiniappById" parameterType="Long">
+        delete from company_miniapp where id = #{id}
+    </delete>
+
+    <delete id="deleteCompanyMiniappByIds" parameterType="String">
+        delete from company_miniapp where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 12 - 0
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -35,6 +35,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="packageCateIds"    column="package_cate_ids"    />
         <result property="courseMaAppId"    column="course_ma_app_id"    />
         <result property="courseMiniAppId"    column="course_mini_app_id"    />
+        <result property="repeat"    column="repeat"    />
+        <result property="sendIfType"    column="send_if_type"    />
+        <result property="ifNum"    column="if_num"    />
     </resultMap>
 
     <sql id="selectCompanyVo">
@@ -105,6 +108,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="packageCateIds != null">package_cate_ids,</if>
             <if test="courseMaAppId != null">course_ma_app_id,</if>
             <if test="courseMiniAppId != null">course_mini_app_id,</if>
+            <if test="repeat != null">`repeat`,</if>
+            <if test="sendIfType != null">send_if_type,</if>
+            <if test="ifNum != null">if_num,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyName != null">#{companyName},</if>
@@ -134,6 +140,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="packageCateIds != null">#{packageCateIds},</if>
             <if test="courseMaAppId != null">#{courseMaAppId},</if>
             <if test="courseMiniAppId != null">#{courseMiniAppId},</if>
+            <if test="repeat != null">#{repeat},</if>
+            <if test="sendIfType != null">#{sendIfType},</if>
+            <if test="ifNum != null">#{ifNum},</if>
          </trim>
     </insert>
 
@@ -169,6 +178,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseMaAppId != null">course_ma_app_id = #{courseMaAppId},</if>
             <if test="courseMiniAppId != null">course_mini_app_id = #{courseMiniAppId},</if>
             <if test="fsUserIsDefaultBlack != null ">fs_user_is_default_black = #{fsUserIsDefaultBlack},</if>
+            <if test="repeat != null">`repeat` = #{repeat},</if>
+            <if test="sendIfType != null">send_if_type = #{sendIfType},</if>
+            <if test="ifNum != null">if_num = #{ifNum},</if>
         </trim>
         where company_id = #{companyId}
     </update>

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

@@ -201,6 +201,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+    <update id="batchUpdateQwExternalContactByMoreStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET
+        level = CASE id
+        <foreach collection="list" item="item">
+            WHEN #{item.id} THEN #{item.level}
+        </foreach>
+        ELSE level
+        END
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
+    <update id="batchUpdateQwExternalByIsDaysNotStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET level =  NULL
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
 
     <insert id="insertQwExternalContact" parameterType="QwExternalContact" useGeneratedKeys="true" keyProperty="id" >
         insert into qw_external_contact

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

@@ -20,6 +20,7 @@
         <result property="updateTime" column="update_time" jdbcType="VARCHAR" />
         <result property="tagIds" column="tag_ids" jdbcType="VARCHAR" />
         <result property="isDaysNotStudy" column="is_days_not_study"/>
+        <result property="grade" column="grade"/>
     </resultMap>
 
     <sql id="selectSopUserLogsInfoVo">
@@ -185,7 +186,7 @@
 
     <!-- 根据ID查询单条记录 -->
     <select id="selectById" parameterType="String" resultMap="SopUserLogsInfoResult">
-        SELECT 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
+        SELECT 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,grade
         FROM sop_user_logs_info
         WHERE id = #{id}
     </select>
@@ -235,6 +236,16 @@
         from sop_user_logs_info where sop_id = #{sopId} and user_logs_id=#{userLogsId}
     </select>
 
+    <select id="selectRestoreByIsDaysNotStudy" parameterType="String" resultMap="SopUserLogsInfoResult">
+        select
+            id,external_id
+        from sop_user_logs_info
+        where sop_id = #{sopId}
+          and user_logs_id=#{userLogsId}
+          and is_days_not_study=1
+    </select>
+
+
     <!-- 查询所有记录 -->
     <select id="selectAll" resultMap="SopUserLogsInfoResult">
         SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id,

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

@@ -253,6 +253,47 @@
             LIMIT #{offset}, #{pageSize}
     </select>
 
+
+    <select id="meetsTheRatingByUserInfoWithPaginationStudyDays" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id,
+            ul.sop_temp_id,
+            ul.qw_user_id,
+            ul.corp_id,
+            ul.start_time,
+            ul.`status`,
+            ul.user_id,
+            DATEDIFF( CURRENT_DATE, ul.start_time ) AS count_days
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <select id="meetsTherestoreByIsDaysNotStudy" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) &lt; #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+
     <select id="meetsTheRatingByUserInfoBySopId" resultType="String"  resultMap="SopUserLogsResult">
         SELECT
             ul.id,