ソースを参照

Merge branch 'master' into 企微聊天

# Conflicts:
#	fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
#	fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
#	fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
#	fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
#	fs-service/src/main/resources/application-dev.yml
Long 2 週間 前
コミット
108adc394f
95 ファイル変更2232 行追加178 行削除
  1. 3 1
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 30 0
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  3. 96 1
      fs-admin/src/main/java/com/fs/his/task/Task.java
  4. 5 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  5. 1 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java
  6. 118 5
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempParentController.java
  7. 10 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseVideoController.java
  8. 19 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwDeptController.java
  9. 30 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  10. 5 0
      fs-ipad-task/pom.xml
  11. 1 1
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  12. 4 0
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  13. 52 0
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  14. 2 2
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  15. 39 6
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  16. 1 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  17. 18 12
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  18. 48 0
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java
  19. 57 0
      fs-service/src/main/java/com/fs/company/dto/CompanyDeptUserInfoDTO.java
  20. 1 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  21. 12 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  22. 22 0
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  23. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  24. 17 0
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  25. 2 1
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  26. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  27. 114 0
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  28. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java
  29. 26 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  30. 5 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  31. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsVideoResourceMapper.java
  32. 3 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java
  33. 41 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  34. 32 6
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  35. 39 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java
  36. 2 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  37. 66 15
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  38. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsPackageOrderMapper.java
  39. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  40. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  41. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsPackageOrderService.java
  42. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  43. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  44. 7 2
      fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java
  45. 2 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  46. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  47. 21 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  48. 59 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  49. 1 1
      fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java
  50. 78 53
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  51. 1 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  52. 2 0
      fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
  53. 15 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java
  54. 47 0
      fs-service/src/main/java/com/fs/qw/domain/QwDeptTreeSelect.java
  55. 3 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  56. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  57. 14 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  58. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  59. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java
  60. 18 2
      fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java
  61. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwDeptService.java
  62. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  63. 69 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwDeptServiceImpl.java
  64. 14 13
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  65. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  66. 1 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  67. 12 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  68. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  69. 18 3
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java
  70. 2 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  71. 115 0
      fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java
  72. 162 0
      fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java
  73. 29 0
      fs-service/src/main/java/com/fs/tag/service/FsTagUpdateService.java
  74. 318 0
      fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
  75. 6 0
      fs-service/src/main/resources/application-config-druid-cqtyt.yml
  76. 6 0
      fs-service/src/main/resources/application-config-druid-fby.yml
  77. 1 1
      fs-service/src/main/resources/application-config-druid-heyantang.yml
  78. 7 0
      fs-service/src/main/resources/application-config-druid-jnlzjk.yml
  79. 1 1
      fs-service/src/main/resources/application-dev.yml
  80. 4 2
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  81. 65 0
      fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml
  82. 13 6
      fs-service/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml
  83. 6 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  84. 14 0
      fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml
  85. 3 0
      fs-service/src/main/resources/mapper/his/FsPackageOrderMapper.xml
  86. 4 0
      fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml
  87. 2 0
      fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml
  88. 29 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  89. 3 0
      fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml
  90. 14 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml
  91. 0 17
      fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java
  92. 2 10
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  93. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java
  94. 13 2
      fs-user-app/src/main/java/com/fs/app/controller/store/CompanyUserScrmController.java
  95. 57 3
      fs-user-app/src/main/java/com/fs/app/param/FsUserEditParam.java

+ 3 - 1
fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -174,8 +174,10 @@ public class IndexStatisticsController {
         if(ObjectUtils.isNull(redPacketCompanyMoney)){
             redPacketCompanyMoney = BigDecimal.ZERO;
         }
-        consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
+        if (consumptionBalanceDataDTO != null){
+            consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
 
+        }
         return R.ok().put("data", consumptionBalanceDataDTO);
     }
 

+ 30 - 0
fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java

@@ -0,0 +1,30 @@
+package com.fs.api.controller;
+
+
+import com.fs.common.core.domain.R;
+import com.fs.company.service.IStatisticManageService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * @description: 统计管理
+ * @author: Guos
+ * @time: 2025/10/30 上午9:16
+ */
+@Slf4j
+@RestController
+@RequestMapping("/statistic/manage")
+public class StatisticManageController {
+
+    @Resource
+    private IStatisticManageService statisticManageService;
+
+    @PostMapping("/statisticMain")
+    public R statisticMain() {
+        return R.ok().put("data", statisticManageService.statisticMain());
+    }
+}

+ 96 - 1
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -21,6 +21,7 @@ import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.dto.BatchSendCourseAllDTO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.IFsUserCourseOrderService;
 import com.fs.course.service.ITencentCloudCosService;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrder;
@@ -49,6 +50,7 @@ import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.enums.FsStoreOrderStatusEnum;
 import com.fs.his.mapper.*;
 import com.fs.his.param.FsInquiryOrderFinishParam;
+import com.fs.his.param.FsPackageOrderCancelParam;
 import com.fs.his.service.*;
 import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
@@ -65,6 +67,7 @@ import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.system.service.ISysConfigService;
 import com.google.gson.Gson;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -73,12 +76,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -140,7 +145,7 @@ public class Task {
     @Autowired
     private CompanyVoiceLogsMapper companyVoiceLogsMapper;
     @Autowired
-    FsPackageOrderServiceImpl packageOrderService;
+    IFsPackageOrderService packageOrderService;
     @Autowired
     private IFsStoreOrderLogsService fsStoreOrderLogsService;
     org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
@@ -197,6 +202,16 @@ public class Task {
     private FsUserOperationLogMapper fsUserOperationLogMapper;
 
     public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
+    @Autowired
+    private IFsStorePaymentService fsStorePaymentService;
+
+    @Autowired
+    private IFsStoreOrderService orderService;
+    @Autowired
+    private ISysConfigService sysConfigService;
+
+    @Autowired
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
     /**
      * sop任务token消耗统计
@@ -1531,5 +1546,85 @@ public class Task {
         log.info("定时删除行为轨迹记录 {} 条", deleteCount);
     }
 
+    //同步支付状态
+    public void synchronizePayStatus(){
+        fsStorePaymentService.synchronizePayStatus();
+    }
+
+    /**
+     * 超时取消订单
+     */
+    public void cancelOrder(){
+        //查询超时订单
+        SysConfig sysConfig= sysConfigService.selectConfigByConfigKey("his.store");
+        StoreConfig config= JSONUtil.toBean(sysConfig.getConfigValue(),StoreConfig.class);
+        Integer unPayTime = config.getUnPayTime(); //分钟
+        if (unPayTime == null){
+            return ;
+        }
+        //1.处方订单
+        //查询超时未支付订单
+        List<FsStoreOrder> orderList = orderService.selectOutTimeOrderList(unPayTime);
+        //取消订单
+        List<CompletableFuture<Void>> orderFutures = cancelOrdersAsync(orderList, order -> {
+            orderService.cancelOrder(order.getOrderId());
+        });
+
+//        //2.课程订单
+//        //查询超时未支付订单
+//        List<FsUserCourseOrder> courseOrderlist = userCourseOrderService.selectOutTimeOrderList(unPayTime);
+//        //取消订单
+//        courseOrderlist.forEach(order->{
+//            userCourseOrderService.cancelOrder(order.getOrderId());
+//        });
+        //3.服务包订单
+        //查询超时未支付订单
+        List<FsPackageOrder> packageOrderList = packageOrderService.selectOutTimeOrderList(unPayTime);
+        //取消订单
+        List<CompletableFuture<Void>> packageOrderFutures = cancelOrdersAsync(packageOrderList, order -> {
+            FsPackageOrderCancelParam param = new FsPackageOrderCancelParam();
+            param.setOrderId(order.getOrderId());
+            packageOrderService.cancel(param);
+        });
+
+        // 等待所有任务完成
+        waitForAllTasksToComplete(orderFutures);
+        waitForAllTasksToComplete(packageOrderFutures);
+    }
+
+    /**
+     * 异步取消订单
+     * @param orders 订单列表
+     * @param cancelAction 取消订单的逻辑
+     * @param <T> 订单类型
+     * @return CompletableFuture列表
+     */
+    private <T> List<CompletableFuture<Void>> cancelOrdersAsync(List<T> orders, Consumer<T> cancelAction) {
+        List<CompletableFuture<Void>> futures = new ArrayList<>();
+        for (T order : orders) {
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    cancelAction.accept(order);
+                } catch (Exception e) {
+                    // 记录异常日志
+                    System.err.println("Failed to cancel order: " + order + ", Error: " + e.getMessage());
+                }
+            }, threadPoolTaskExecutor);
+            futures.add(future);
+        }
+        return futures;
+    }
+
+    /**
+     * 等待所有任务完成
+     * @param futures CompletableFuture列表
+     */
+    private void waitForAllTasksToComplete(List<CompletableFuture<Void>> futures) {
+        CompletableFuture<Void> allFutures = CompletableFuture.allOf(
+                futures.toArray(new CompletableFuture[0])
+        );
+        allFutures.join(); // 等待所有任务完成
+    }
+
 
 }

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

@@ -516,7 +516,11 @@ public class CompanyUserController extends BaseController {
                                         @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String,Object> params = new HashMap<>();
         params.put("nickName", name);
-
+        //查询多条数据传入公司
+        if (pageSize>=200){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            params.put("companyId", loginUser.getCompany().getCompanyId());
+        }
         PageHelper.startPage(pageNum, pageSize);
         List<OptionsVO> companyUserList = companyUserService.selectCompanyUserListByMap(params);
         return R.ok().put("data", new PageInfo<>(companyUserList));

+ 1 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java

@@ -82,6 +82,7 @@ public class FsCourseFinishTempController extends BaseController
     {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         fsCourseFinishTemp.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTemp.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         return toAjax(fsCourseFinishTempService.insertFsCourseFinishTemp(fsCourseFinishTemp));
     }
 

+ 118 - 5
fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempParentController.java

@@ -1,7 +1,11 @@
 package com.fs.company.controller.course;
 
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
+import com.fs.company.service.impl.CompanyDeptServiceImpl;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -25,7 +29,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 完课模板Controller
- * 
+ *
  * @author 吴树波
  * @date 2025-05-22
  */
@@ -36,6 +40,12 @@ public class FsCourseFinishTempParentController extends BaseController
     @Autowired
     private IFsCourseFinishTempParentService fsCourseFinishTempParentService;
 
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
+
     /**
      * 查询完课模板列表
      */
@@ -50,6 +60,55 @@ public class FsCourseFinishTempParentController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询我创建的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myList')")
+    @GetMapping("/myList")
+    public TableDataInfo myList(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        startPage();
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询部门的创建的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:deptList')")
+    @GetMapping("/deptList")
+    public TableDataInfo deptList(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        List<Long> combinedList = new ArrayList<>();
+        //本部门
+        Long deptId = loginUser.getUser().getDeptId();
+        if (deptId!=null){
+            combinedList.add(deptId);
+        }
+        //本部门的下级部门
+        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+        if (!deptList.isEmpty()){
+            combinedList.addAll(deptList);
+        }
+
+        List<Long> userIds = companyUserService.selectCompanyQwUserByDept(deptList, loginUser.getUser().getUserType());
+        if (userIds.isEmpty()){
+            return getDataTable(new ArrayList<>());
+        }
+
+        fsCourseFinishTempParent.setUserIds(userIds);
+
+        startPage();
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
     /**
      * 导出完课模板列表
      */
@@ -58,15 +117,67 @@ public class FsCourseFinishTempParentController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsCourseFinishTempParent fsCourseFinishTempParent)
     {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+    /**
+     * 导出我的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myExport')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
         ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
         return util.exportExcel(list, "完课模板数据");
     }
 
+    /**
+     * 导出部门完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myExport')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/deptExport")
+    public AjaxResult deptExport(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+
+        List<Long> combinedList = new ArrayList<>();
+        //本部门
+        Long deptId = loginUser.getUser().getDeptId();
+        if (deptId!=null){
+            combinedList.add(deptId);
+        }
+        //本部门的下级部门
+        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+        if (!deptList.isEmpty()){
+            combinedList.addAll(deptList);
+        }
+
+        List<Long> userIds = companyUserService.selectCompanyQwUserByDept(deptList, loginUser.getUser().getUserType());
+        if (userIds.isEmpty()){
+            return AjaxResult.error();
+        }
+
+        fsCourseFinishTempParent.setUserIds(userIds);
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+
     /**
      * 获取完课模板详细信息
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query') || @ss.hasPermi('course:courseFinishTempParent:myQuery') || @ss.hasPermi('course:courseFinishTempParent:deptQuery')")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id)
     {
@@ -76,20 +187,22 @@ public class FsCourseFinishTempParentController extends BaseController
     /**
      * 新增完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add') || @ss.hasPermi('course:courseFinishTempParent:myAdd') || @ss.hasPermi('course:courseFinishTempParent:deptAdd')")
     @Log(title = "完课模板", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent){
 
         LoginUser loginUser = SecurityUtils.getLoginUser();
         fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateTime(new Date());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         return toAjax(fsCourseFinishTempParentService.insertFsCourseFinishTempParent(fsCourseFinishTempParent));
     }
 
     /**
      * 修改完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit') || @ss.hasPermi('course:courseFinishTempParent:myEdit') || @ss.hasPermi('course:courseFinishTempParent:deptEdit')")
     @Log(title = "完课模板", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent)
@@ -102,7 +215,7 @@ public class FsCourseFinishTempParentController extends BaseController
     /**
      * 删除完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove') || @ss.hasPermi('course:courseFinishTempParent:myRemove') || @ss.hasPermi('course:courseFinishTempParent:deptRemove')")
     @Log(title = "完课模板", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseVideoController.java

@@ -86,7 +86,17 @@ public class FsUserCourseVideoController extends BaseController
         }
         return toAjax(fsUserCourseVideoService.insertFsUserCourseVideo(fsUserCourseVideo));
     }
+    /**
+     * 更新课堂视频
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseVideo:update')")
+    @Log(title = "更新课堂视频", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    public AjaxResult update(@RequestBody FsUserCourseVideo fsUserCourseVideo)
+    {
 
+        return toAjax(fsUserCourseVideoService.updateFsUserCourseVideo(fsUserCourseVideo));
+    }
     /**
      * 修改课堂视频
      */

+ 19 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwDeptController.java

@@ -7,6 +7,7 @@ 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.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
@@ -120,4 +121,22 @@ public class QwDeptController extends BaseController
     {
         return toAjax(qwDeptService.deleteQwDeptByIds(ids));
     }
+
+    /**
+     * @Description: 获取企微部门 按Treeselect返回 每一个企微主体有自己的部门,按企微主体查询
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/30 9:33
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(QwDept qwDept)
+    {
+        if(StringUtils.isEmpty(qwDept.getCorpId())){
+            return AjaxResult.error("请选择企微主体");
+        }
+        List<QwDept> depts = qwDeptService.selectQwDeptList(qwDept);
+        return AjaxResult.success(qwDeptService.buildDeptTreeSelect(depts));
+    }
+
 }

+ 30 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -113,13 +113,43 @@ public class QwUserController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:user:staffList')")
     @GetMapping("/staffList")
     public TableDataInfo staffList(QwUserListParam qwUser) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        // 添加部门查询条件
+        //本部门
+        Long deptId = qwUser.getDeptId();
+        if(deptId!=null){
+            List<Long> qwDeptIdList = new ArrayList<>();
+            if (deptId!=null){
+                qwDeptIdList.add(deptId);
+            }
+            // 本部门的下级部门
+            List<Long> deptList = qwUserService.selectDeptByParentId(deptId,qwUser.getCorpId());
+            if (!deptList.isEmpty()){
+                qwDeptIdList.addAll(deptList);
+            }
+            qwUser.setQwDeptIdList(qwDeptIdList);
+        }
+        startPage();
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询我的企微员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:myStaffList')")
+    @GetMapping("/myStaffList")
+    public TableDataInfo myStaffList(QwUserListParam qwUser) {
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        qwUser.setCompanyUserId(loginUser.getUser().getUserId());
         List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
         return getDataTable(list);
     }
 
+
     /**
      * 导出企微员工列表
      * @param qwUser

+ 5 - 0
fs-ipad-task/pom.xml

@@ -105,6 +105,11 @@
             <groupId>com.fs</groupId>
             <artifactId>fs-service</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 1 - 1
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -334,7 +334,7 @@ public class IpadSendServer {
     public void send(QwSopCourseFinishTempSetting.Setting content, QwUser qwUser, QwSopLogs qwSopLogs, Map<String, FsCoursePlaySourceConfig> miniMap, BaseVo parentVo) {
         BaseVo vo = new BaseVo();
         vo.setId(Long.parseLong(qwSopLogs.getId()));
-        vo.setRoom(qwSopLogs.getSendType() == 12);
+        vo.setRoom(qwSopLogs.getSendType() == 6 || qwSopLogs.getSendType() == 12);
         vo.setUuid(qwUser.getUid());
         vo.setExId(qwSopLogs.getExternalUserId());
         vo.setServerId(qwUser.getServerId());

+ 4 - 0
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -90,12 +90,14 @@ public class SendMsg {
         if (qwUserList.isEmpty()) {
             List<QwIpadServer> serverList = qwIpadServerMapper.selectList(new QueryWrapper<QwIpadServer>().eq("group_no", groupNo));
             if (serverList.isEmpty()) {
+                log.info("没找到可用的服务器 {} ",serverList);
                 return new ArrayList<>();
             }
             List<Long> serverIds = PubFun.listToNewList(serverList, QwIpadServer::getId);
             List<QwUser> qwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().eq("send_msg_type", 1).eq("server_status", 1).eq("ipad_status", 1).in("server_id", serverIds));
             qwUserList.addAll(qwUsers);
         }
+        log.info("getQwUserList {}",JSON.toJSONString(qwUserList));
         return qwUserList;
     }
 
@@ -169,6 +171,7 @@ public class SendMsg {
         // 获取当前企微待发送记录
         List<QwSopLogs> qwSopLogList = qwSopLogsMapper.selectByQwUserId(qwUser.getId());
         if (qwSopLogList.isEmpty()) {
+            log.info("获取当前企微待发送记录为空");
             return;
         }
         // 获取企微用户
@@ -178,6 +181,7 @@ public class SendMsg {
         long end1 = System.currentTimeMillis();
         // 判断这个企微是否需要发送
         if (!sendServer.isSend(user, parentVo)) {
+            log.info("当前这个企微不需要发送 数据{}",user);
             return;
         }
         log.info("销售:{}, 消息:{}, 耗时: {}, 时间:{}", user.getQwUserName(), qwSopLogList.size(), end1 - start1, qwMap.get(qwUser.getId()));

+ 52 - 0
fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java

@@ -0,0 +1,52 @@
+package com.fs.app.task;
+
+import com.fs.FsIpadTaskApplication;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.tag.service.FsTagUpdateService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FsIpadTaskApplication.class)
+@RequiredArgsConstructor
+@Slf4j
+public class SendMsgTest {
+    @Autowired
+    private SendMsg sendMsg;
+
+    @Autowired
+    private FsTagUpdateService fsTagUpdateService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Test
+    public void sendMsg2() {
+        sendMsg.sendMsg2();
+    }
+
+    @Test
+    public void testLogWrite(){
+        List<Long> testLogIds = Arrays.asList(177L,180L,182L,183L,184L,185L);
+
+        for(Long logId : testLogIds){
+            FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
+            fsTagUpdateService.onCourseWatchingBatch(Collections.singletonList(fsCourseWatchLog));
+        }
+    }
+
+    @Test
+    public void handleData(){
+        fsTagUpdateService.handleData();
+    }
+}

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -400,8 +400,8 @@ public class QwMsgController {
                         log.info("id:{}, 客户发送", id);
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
                     }else {
-                        log.info("id:{}, 销售发送", id);
-                        aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
+                        log.info("销售发送");
+                        aiHookService.qwHookNotifyAddMsgNew(id,receiver,content,wxWorkMsgResp.getUuid(),1);
                     }
 
                 }

+ 39 - 6
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -26,6 +26,8 @@ import com.google.gson.JsonParser;
 import com.tencent.wework.Finance;
 import lombok.extern.slf4j.Slf4j;
 import org.json.JSONObject;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
@@ -73,6 +75,9 @@ public class QwDataCallbackService {
     @Autowired
     IQwAutoTagsService qwAutoTagsService;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Autowired
     IQwAutoTagsLogsService qwAutoTagsLogsService;
 
@@ -202,13 +207,41 @@ public class QwDataCallbackService {
                             if(WelcomeCodeList.getLength() > 0) {
                                 WelcomeCode = WelcomeCodeList.item(0).getTextContent();
                             }
-
-                            String qwApiExternal=redisCache.getCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent());
-                            if (StringUtil.strIsNullOrEmpty(qwApiExternal)){
-                                redisCache.setCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent() ,"1",10, TimeUnit.MINUTES);
-                                qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
-
+                            String userId = root.getElementsByTagName("UserID").item(0).getTextContent();
+                            String externalUserId = root.getElementsByTagName("ExternalUserID").item(0).getTextContent();
+                            String cacheKey = "qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId;
+                            String lockKey = "lock:qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId; // 锁Key(Hash类型,加前缀lock:)
+
+                            // 2. 获取 Redisson 分布式锁
+                            RLock lock = redissonClient.getLock(lockKey);
+                            boolean isLocked = false;
+                            try {
+                                // 3. 尝试加锁:最多等待 5 秒,锁自动释放时间 15 分钟
+                                isLocked = lock.tryLock(5, 15, TimeUnit.MINUTES);
+                                if (isLocked) {
+                                    // 4. 加锁成功后,再次检查缓存(避免多线程竞争时重复执行业务)
+                                    String qwApiExternal = redisCache.getCacheObject(cacheKey);
+                                    if (StringUtil.strIsNullOrEmpty(qwApiExternal)) {
+                                        try {
+                                            // 5. 新增用户
+                                            qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
+                                            // 6. 业务逻辑执行成功后,写入 Redis 缓存(有效期 10 分钟)
+                                            redisCache.setCacheObject(cacheKey, "1", 10, TimeUnit.MINUTES);
+                                        } catch (Exception e) {
+                                            // 7. 业务逻辑失败时,删除缓存
+                                            redisCache.deleteObject(cacheKey);
+                                        }
+                                    }
+                                }
+                            } catch (InterruptedException e) {
+                                logger.error("中断异常");
+                            } finally {
+                                // 4. 确保锁最终被释放(只有加锁成功的线程才需要释放)
+                                if (isLocked && lock.isHeldByCurrentThread()) {
+                                    lock.unlock();
+                                }
                             }
+
                             break;
                         case "edit_external_contact":
                             qwExternalContactService.updateQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),corpId);

+ 1 - 0
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 
 import cn.hutool.core.date.DateUtil;
+import com.alibaba.fastjson.JSON;
 import com.fs.app.taskService.*;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;

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

@@ -714,23 +714,29 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             ruleTimeVO.setSendType(6);
             ruleTimeVO.setType(2);
-            if (content.getIndex() == 0) {
+            if (groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()) {
                 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, miniMap, null, sendMsgType,companies);
-            } else {
-                if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
-                    groupChat.getChatUserList().forEach(user -> {
-                        ruleTimeVO.setSendType(2);
-                        ruleTimeVO.setRemark("客户群催课");
-                        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, miniMap, null, sendMsgType,companies);
-                    });
-                }
             }
+//            if (content.getIndex() == 0) {
+//                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, miniMap, null, sendMsgType,companies);
+//            } else {
+//                if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
+//                    groupChat.getChatUserList().forEach(user -> {
+//                        ruleTimeVO.setSendType(2);
+//                        ruleTimeVO.setRemark("客户群催课");
+//                        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, miniMap, null, sendMsgType,companies);
+//                    });
+//                }
+//            }
         } else {
             // 处理每个 externalContactId
             sopUserLogsInfos.forEach(contactId -> {

+ 48 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java

@@ -0,0 +1,48 @@
+package com.fs.company.domain;
+
+import lombok.Data;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:40
+ */
+@Data
+public class CompanyDeptUserInfo {
+
+   /**
+    * 公司id
+    */
+   private Integer companyId;
+
+   /**
+    * 公司名称
+    */
+   private String companyName;
+
+   /**
+    * 部门id
+    */
+   private Long deptId;
+
+   /**
+    * 部门名称
+    */
+   private String deptName;
+
+   /**
+    * 员工id
+    */
+   private Long userId;
+
+   /**
+    * 员工名称
+    */
+   private String userName;
+
+   /**
+    * 员工昵称
+    */
+   private String nickName;
+
+}

+ 57 - 0
fs-service/src/main/java/com/fs/company/dto/CompanyDeptUserInfoDTO.java

@@ -0,0 +1,57 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午10:38
+ */
+@Data
+public class CompanyDeptUserInfoDTO {
+
+    /**
+     * 公司id
+     */
+    private Integer companyId;
+
+    /**
+     * 公司名称
+     */
+    private String companyName;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 进线数量
+     */
+    private Long lineNum;
+
+    /**
+     * 激活数
+     */
+    private Long activeNum;
+
+    /**
+     * 完课数量
+     */
+    private Long completeNum;
+
+    /**
+     * 答题数量
+     */
+    private Long answerNum;
+
+    /**
+     * 红包数量
+     */
+    private Long redPacketNum;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -155,7 +155,7 @@ public interface CompanyMapper
 
     @Select({"<script> " +
             "select c.*,cu.user_name,qu.used_num FROM company c LEFT JOIN company_user cu ON c.user_id =cu.user_id  " +
-            "LEFT JOIN (select company_id, count(id) as used_num from qw_user where server_id is not null group by company_id) qu ON qu.company_id = c.company_id " +
+            "LEFT JOIN (select company_id, count(id) as used_num from qw_user where server_id is not null and server_status = 1 group by company_id) qu ON qu.company_id = c.company_id " +
             "where c.is_del=0 " +
             "            <if test=\"companyName != null  and companyName != ''\"> and c.company_name like concat('%', #{companyName}, '%')</if>\n" +
             "            <if test=\"companyMobile != null  and companyMobile != ''\"> and c.company_mobile = #{companyMobile}</if>\n" +

+ 12 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -284,4 +284,16 @@ public interface CompanyUserMapper
 
 
     List<String> selectCompanyUserNameByIdsList(@Param("companyUserIDs")List<Long> companyUserID);
+
+    @Select("<script>" +
+            "SELECT user_id FROM company_user WHERE 1=1 " +
+            "<if test=\"companyUserIDs != null and companyUserIDs.size() > 0 and userType != '00'\">" +
+            "   AND dept_id IN " +
+            "   <foreach collection='companyUserIDs' item='item' open='(' separator=',' close=')'>" +
+            "       #{item}" +
+            "   </foreach>" +
+            "</if>" +
+            "</script>")
+    List<Long> selectCompanyQwUserByDept(@Param("companyUserIDs") List<Long> companyUserIDs, @Param("userType") String userType);
+
 }

+ 22 - 0
fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java

@@ -0,0 +1,22 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:25
+ */
+public interface StatisticManageMapper {
+
+
+    //获取公司、部门、员工信息
+    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList();
+
+
+    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long[] userIds);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -244,4 +244,6 @@ public interface ICompanyUserService {
      * @param batchUserRolesVO 批量修改角色参数
      */
     R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO);
+
+    List<Long> selectCompanyQwUserByDept(List<Long> deptList,String userType);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java

@@ -0,0 +1,17 @@
+package com.fs.company.service;
+
+import com.fs.company.dto.CompanyDeptUserInfoDTO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:21
+ */
+public interface IStatisticManageService {
+
+    List<Map<String, Object>> statisticMain();
+
+}

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

@@ -434,7 +434,8 @@ public class CompanyServiceImpl implements ICompanyService
                 orderMap.setOrderId(order.getId());
                 orderMap.setTuiMoneyStatus(1);
                 storeOrderMapper.updateFsStoreOrder(orderMap);
-                BigDecimal money = order.getPayMoney().add(order.getPayRemain());
+                // order.getPayRemain() 数据库实际没有这个字段了 直接使用 应付金额
+                BigDecimal money = order.getPayPrice();
                 company.setMoney(company.getMoney().add(money));
                 companyMapper.updateCompany(company);
                 CompanyMoneyLogs log=new CompanyMoneyLogs();

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

@@ -1061,4 +1061,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         }
         return R.ok("修改成功");
     }
+
+    @Override
+    public List<Long> selectCompanyQwUserByDept(List<Long> deptList,String userType) {
+        return companyUserMapper.selectCompanyQwUserByDept(deptList,userType);
+    }
 }

+ 114 - 0
fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java

@@ -0,0 +1,114 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.company.mapper.StatisticManageMapper;
+import com.fs.company.service.IStatisticManageService;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import com.google.common.collect.Lists;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:21
+ */
+@Slf4j
+@Service
+public class StatisticManageServiceImpl implements IStatisticManageService {
+
+    @Resource
+    private StatisticManageMapper statisticManageMapper;
+
+
+    /**
+     * 统计
+     * 按照部门分组情况
+     * @return
+     */
+    @Override
+    public List<Map<String, Object>> statisticMain() {
+        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList();
+        if(CollectionUtils.isEmpty(companyDeptdUserList)){return null;}
+        //按照部门分组
+        Map<Long, List<CompanyDeptUserInfo>> deptInfos = companyDeptdUserList.stream()
+            .collect(Collectors.groupingBy(CompanyDeptUserInfo::getDeptId));
+        //创建一个新的List来接收
+        List<CompanyDeptUserInfoDTO> companyDeptUserInfoDTOS = Lists.newArrayList();
+        deptInfos.forEach((deptId, companyDeptUserInfos) -> {
+            //目前没看见没部门的情况,第一个判断是size为1的情况并且userId为null的情况或者0L的情况,就不用走数据去查询,直接都是0
+            int size = companyDeptUserInfos.size();
+            if(size == 1 && (companyDeptUserInfos.get(0).getUserId() == null || 0L == companyDeptUserInfos.get(0).getUserId())) {
+                CompanyDeptUserInfo companyDeptUserInfo = companyDeptUserInfos.get(0);
+                companyDeptUserInfoDTOS.add(component(companyDeptUserInfo));
+            }else{
+                //将所有userId统计为一个数组
+                Long[] userIds = companyDeptUserInfos.stream().map(CompanyDeptUserInfo::getUserId).toArray(Long[]::new);
+                CompanyDeptUserInfo companyDeptUserInfo = companyDeptUserInfos.get(0);
+                CompanyDeptUserInfoDTO statisticNum = statisticManageMapper.getStatisticNum(userIds);
+                companyDeptUserInfoDTOS.add(component(companyDeptUserInfo, statisticNum));
+            }
+        });
+        //在这里按照公司分组
+//        return companyDeptUserInfoDTOS;
+       return groupByCompanyAndCompanyName(companyDeptUserInfoDTOS);
+    }
+
+    /**
+     * 组装数据(主要针对userId是空或者是0L情况)
+     * @param companyDeptUserInfo
+     * @return
+     */
+    private static CompanyDeptUserInfoDTO component(CompanyDeptUserInfo companyDeptUserInfo) {
+        CompanyDeptUserInfoDTO companyDeptUserInfoDTO = new CompanyDeptUserInfoDTO();
+        BeanUtils.copyProperties(companyDeptUserInfo, companyDeptUserInfoDTO);
+        companyDeptUserInfoDTO.setLineNum(0L);
+        companyDeptUserInfoDTO.setActiveNum(0L);
+        companyDeptUserInfoDTO.setCompleteNum(0L);
+        companyDeptUserInfoDTO.setAnswerNum(0L);
+        companyDeptUserInfoDTO.setRedPacketNum(0L);
+        return companyDeptUserInfoDTO;
+    }
+
+    /**
+     * 组装数据
+     * @param source
+     * @param target
+     * @return
+     */
+    private static CompanyDeptUserInfoDTO component(CompanyDeptUserInfo source, CompanyDeptUserInfoDTO target) {
+        target.setCompanyId(source.getCompanyId());
+        target.setCompanyName(source.getCompanyName());
+        target.setDeptId(source.getDeptId());
+        target.setDeptName(source.getDeptName());
+        return target;
+    }
+
+    private List<Map<String, Object>> groupByCompanyAndCompanyName(List<CompanyDeptUserInfoDTO> source){
+        //按照companyId和companyName分组
+        Map<Integer, List<CompanyDeptUserInfoDTO>> companyInfos = source.stream()
+                .collect(Collectors.groupingBy(CompanyDeptUserInfoDTO::getCompanyId));
+        List<Map<String, Object>> resultList = Lists.newArrayList();
+        companyInfos.forEach((companyId, companyDeptUserInfoDTOS) -> {
+            CompanyDeptUserInfoDTO companyDeptUserInfoDTO = companyDeptUserInfoDTOS.get(0);
+            Map<String, Object> companyInfoMap = Maps.newHashMap();
+            companyInfoMap.put("companyId", companyId);
+            companyInfoMap.put("companyName", companyDeptUserInfoDTO.getCompanyName());
+            companyInfoMap.put("deptInfos", companyDeptUserInfoDTOS);
+            resultList.add(companyInfoMap);
+        });
+        return resultList;
+    }
+
+
+}
+
+

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

@@ -8,6 +8,8 @@ import lombok.Data;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.EqualsAndHashCode;
 
+import java.util.List;
+
 /**
  * 完课模板对象 fs_course_finish_temp_parent
  *
@@ -45,4 +47,7 @@ public class FsCourseFinishTempParent extends BaseEntity{
     private String companyUserIds;
     @TableField(exist = false)
     private Integer isAllCompanyUser;
+
+    @TableField(exist = false)
+    private List<Long> userIds;
 }

+ 26 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -113,4 +113,30 @@ public class FsUserCourseVideo extends BaseEntity
     private Long listingEndTime;//商品结束售卖时间
 
 
+    /**
+     * 看课中标签ID
+     */
+    private String watchingTagId;
+    /**
+     * 完课标签ID
+     */
+    private String watchedTagId;
+    /**
+     * 标签组ID
+     */
+    private String tagGroupId;
+
+    /**
+     * 标签组表中的ID
+     */
+    private Long tgId;
+    /**
+     * 看课标签 表中的ID
+     */
+    private Long watchingTgId;
+    /**
+     * 完课标签 表中的ID
+     */
+    private Long watchedTgId;
+
 }

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

@@ -10,6 +10,7 @@ import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -255,4 +256,8 @@ public interface FsUserCourseVideoMapper
     List<FsUserCourseVideoAppletVO> getFsUserCourseVideoAppletVOListByIds(@Param("videoIds") List<Long> videoIds);
 
     FsUserCourseVO selectFsUserCourseVideoVoByVideoIdAndCourdeId(@Param("videoId") Long videoId,@Param("courseId") Long courseId);
+
+    @Select("select video_id,is_first,course_sort,tg_id,watching_tg_id,watched_tg_id,watching_tag_id,watched_tag_id,tag_group_id from fs_user_course_video")
+    @MapKey("videoId")
+    Map<Long, FsUserCourseVideo> selectAllMap();
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/mapper/FsVideoResourceMapper.java

@@ -27,4 +27,6 @@ public interface FsVideoResourceMapper extends BaseMapper<FsVideoResource> {
 
     @Select("select * from fs_video_resource where file_key = #{fileKey} limit 1")
     FsVideoResource selectByFileKey(String fileKey);
+
+    List<FsVideoResource> selectByIds(@Param("ids") long[] ids);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -79,6 +80,8 @@ public class FsCourseFinishTempParentServiceImpl extends ServiceImpl<FsCourseFin
             temp.setCompanyId(fsCourseFinishTempParent.getCompanyId());
             temp.setCourseId(e.getCourseId());
             temp.setVideoId(e.getVideoId());
+            temp.setCreateBy(fsCourseFinishTempParent.getCreateBy());
+            temp.setCreateTime(new Date());
             temp.setCompanyUserIds(fsCourseFinishTempParent.getCompanyUserIds());
             temp.setIsAllCompanyUser(fsCourseFinishTempParent.getIsAllCompanyUser());
             temp.setParentId(fsCourseFinishTempParent.getId());

+ 41 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -48,10 +48,13 @@ import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.tag.service.FsTagUpdateService;
 import com.hc.openapi.tool.util.StringUtils;
+import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -89,7 +92,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
-    RedisCache redisCache;
+    private RedisCache redisCache;
     @Autowired
     private IQwExternalContactCacheService qwExternalContactCacheService;
     @Autowired
@@ -130,6 +133,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
 
+    @Autowired
+    private FsTagUpdateService fsTagUpdateService;
+
     /**
      * 查询短链课程看课记录
      *
@@ -367,6 +373,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             String[] parts = key.split(":");
@@ -405,12 +412,17 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
             logs.add(watchLog);
         }
         batchUpdateFsUserCourseWatchLog(logs,100);
+
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
     public Long getFsUserVideoDuration(Long videoId){
         //将视频时长也存到redis
@@ -439,6 +451,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         Collection<String> keys = redisCache.keys("h5wxuser:watch:heartbeat:*");
         LocalDateTime now = LocalDateTime.now();
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             String[] parts = key.split(":");
@@ -463,11 +476,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 redisCache.deleteObject(key);
             }else {
                 watchLog.setLogType(1);
+                watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
         batchUpdateFsUserCourseWatchLog(logs,100);
-
+        if(CollectionUtils.isNotEmpty(watchingLogs)){
+            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+        }
     }
 
     @Override
@@ -490,6 +506,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             Long videoId=null;
@@ -538,6 +555,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
@@ -545,6 +564,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
 
         batchUpdateFsCourseWatchLogIsOpen(logs,100);
+
+        // 完课打标签
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
 
     public void batchUpdateFsCourseWatchLogIsOpen(List<FsCourseWatchLog> logs, int batchSize) {
@@ -766,6 +790,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             Long qwUserId=null;
@@ -824,6 +849,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
@@ -831,6 +858,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
 
         batchUpdateFsCourseWatchLog(logs,100);
+
+        // 完课打标签
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
 
     @Override
@@ -840,6 +872,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         Collection<String> keys = redisCache.keys("h5user:watch:heartbeat:*");
         LocalDateTime now = LocalDateTime.now();
         List<FsCourseWatchLog> logs = new ArrayList<>();
+
+        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             //取key中数据
@@ -874,10 +908,15 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 redisCache.deleteObject(key);
             }else {
                 watchLog.setLogType(1);
+                watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
         batchUpdateFsCourseWatchLog(logs,100);
+
+        if(CollectionUtils.isNotEmpty(watchingLogs)){
+            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+        }
     }
 
     @Override

+ 32 - 6
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -63,6 +63,7 @@ import com.fs.qw.mapper.QwGroupChatMapper;
 import com.fs.qw.mapper.QwGroupChatUserMapper;
 import com.fs.qw.mapper.QwSessionMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.*;
 import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
@@ -263,7 +264,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Autowired
     private IFsUserIntegralLogsService iFsUserIntegralLogsService;
-
+    @Autowired
+    private QwTagGroupMapper qwTagGroupMapper;
+    @Autowired
+    private QwTagMapper qwTagMapper;
 
 
 
@@ -929,9 +933,33 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.error(400,msg).put("qrcode",contactWay);
     }
 
+
     @Override
     public List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam fsUserCourseVideo) {
-        return fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
+        List<FsUserCourseVideoVO> fsUserCourseVideoVOS = fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
+        for (FsUserCourseVideoVO item : fsUserCourseVideoVOS) {
+            if(ObjectUtils.isNotNull(item.getTgId())){
+                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(item.getTgId());
+                if(ObjectUtils.isNotNull(qwTagGroup)){
+                    item.setTagGroupName(qwTagGroup.getName());
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getWatchingTgId())){
+                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchingTgId());
+                if(ObjectUtils.isNotNull(qwTag)){
+                    item.setWatchingTagName(qwTag.getName());
+                }
+            }
+
+            if(ObjectUtils.isNotNull(item.getWatchedTgId())) {
+                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchedTgId());
+                if(ObjectUtils.isNotNull(qwTag)){
+                    item.setWatchedTagName(qwTag.getName());
+                }
+            }
+        }
+        return fsUserCourseVideoVOS;
     }
 
     @Override
@@ -2478,10 +2506,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Override
     public void batchSaveVideo(BatchVideoSvae vo) {
-        List<FsVideoResource> videoResourceList = fsVideoResourceMapper.selectBatchIds(vo.getIds());
-        videoResourceList = videoResourceList.stream()
-                .sorted(Comparator.comparing(FsVideoResource::getSort).thenComparing(FsVideoResource::getId))
-                .collect(Collectors.toList());
+        long[] idArray = vo.getIds().stream().mapToLong(Long::longValue).toArray();
+        List<FsVideoResource> videoResourceList = fsVideoResourceMapper.selectByIds(idArray);
         FsUserCourseVideo param = new FsUserCourseVideo();
         param.setCourseId(vo.getCourseId());
         List<FsUserCourseVideo> videoList = selectFsUserCourseVideoList(param);

+ 39 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java

@@ -68,4 +68,43 @@ public class FsUserCourseVideoVO extends BaseEntity {
     private String redPacketMoney;
 
     private String companyRedPacketMoney;
+    /**
+     * 标签组表中的ID
+     */
+    private Long tgId;
+    /**
+     * 看课标签 表中的ID
+     */
+    private Long watchingTgId;
+    /**
+     * 完课标签 表中的ID
+     */
+    private Long watchedTgId;
+
+    /**
+     * 看课中标签ID
+     */
+    private String watchingTagId;
+    /**
+     * 完课标签ID
+     */
+    private String watchedTagId;
+    /**
+     * 标签组ID
+     */
+    private String tagGroupId;
+
+    /**
+     * 标签组名称
+     */
+    private String tagGroupName;
+    /**
+     * 看课标签
+     */
+    private String watchingTagName;
+    /**
+     * 完课标签
+     */
+    private String watchedTagName;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -21,6 +21,8 @@ public interface AiHookService {
 
     R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid);
 
+    R qwHookNotifyAddMsgNew(Long qwUserID, Long sender,String count,String uid,Integer type);
+
     void expireAiMsg();
 
     WxWorkResponseDTO<String> getFileUrl(String uuid, String fileId, String aesKey, String authKey, String fileName, Integer fileSize, Long serverId);

+ 66 - 15
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -470,9 +470,9 @@ public class AiHookServiceImpl implements AiHookService {
                 return R.ok();
             }
 
-            //对用户处理的内容做处理
-            String maskedContent = processContent(qwContent);
-            String contentEmj = replaceWxEmo(maskedContent);
+            //对用户处理的内容做处理,去除手机号替换
+            //String maskedContent = processContent(qwContent);
+            String contentEmj = replaceWxEmo(qwContent);
             if(!contentEmj.contains("表情包")){
                 if(!contentEmj.isEmpty()){
                     addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
@@ -1948,8 +1948,9 @@ public class AiHookServiceImpl implements AiHookService {
         sendAIParam.setKey(user.getAppKey());
         redisTemplate.opsForList().leftPush("AiMsg:"+user.getAppKey(), JSON.toJSONString(sendAIParam));
     }
+
     @Override
-    public R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid) {
+    public R qwHookNotifyAddMsgNew(Long qwUserID, Long sender,String count,String uid,Integer type) {
         QwUser sendUser = qwUserMapper.selectQwUserById(qwUserID);
 
 
@@ -1963,6 +1964,25 @@ public class AiHookServiceImpl implements AiHookService {
             FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
             if (fastGptChatSession!=null){
                 saveQwUserMsg(fastGptChatSession,2,count);
+                // 客服进行回复后就转人工10分钟
+                if(type == 1){
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.add(Calendar.MINUTE, -5);
+                    Date lastTime = calendar.getTime();
+                    if(lastTime.after(fastGptChatSession.getCreateTime())){
+                        Calendar calendar1 = Calendar.getInstance();
+                        //定时任务会处理10分钟以内的,所以设置20分钟
+                        calendar1.add(Calendar.MINUTE, 10);
+                        Date expireTime = calendar1.getTime();
+
+                        FastGptChatSession chatSession = new FastGptChatSession();
+                        chatSession.setLastTime(expireTime);
+                        chatSession.setIsArtificial(1);
+                        chatSession.setSessionId(fastGptChatSession.getSessionId());
+
+                        fastGptChatSessionMapper.updateFastGptChatSession(chatSession);
+                    }
+                }
             }else {
 
                 if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
@@ -1989,19 +2009,50 @@ public class AiHookServiceImpl implements AiHookService {
                     }
                 }
             }
-            // 客服进行回复后就转人工10分钟
-            if(fastGptChatSession != null){
-                Calendar calendar = Calendar.getInstance();
-                //定时任务会处理10分钟以内的,所以设置20分钟
-                calendar.add(Calendar.MINUTE, 30);
-                Date expireTime = calendar.getTime();
+        }
+        return R.ok();
+    }
+
+    @Override
+    public R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid) {
+        QwUser sendUser = qwUserMapper.selectQwUserById(qwUserID);
+
 
-                FastGptChatSession chatSession = new FastGptChatSession();
-                chatSession.setLastTime(expireTime);
-                chatSession.setIsArtificial(1);
-                chatSession.setSessionId(fastGptChatSession.getSessionId());
+        if (sendUser!=null){
+
+            String extId = getExtId(sender, uid, sendUser.getServerId());
+            QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(extId, sendUser.getCorpId(),sendUser.getQwUserId());
+            if (qwExternalContacts==null){
+                return R.ok();
+            }
+            FastGptChatSession fastGptChatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(qwExternalContacts.getId(), sendUser.getId());
+            if (fastGptChatSession!=null){
+                saveQwUserMsg(fastGptChatSession,2,count);
+            }else {
 
-                fastGptChatSessionMapper.updateFastGptChatSession(chatSession);
+                if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+                    if(sendUser.getFastGptRoleId()!=null){
+                        fastGptChatSession = new FastGptChatSession();
+                        String chatId = UUID.randomUUID().toString();
+                        fastGptChatSession.setChatId(chatId);
+                        fastGptChatSession.setKfId(sendUser.getFastGptRoleId().toString());
+                        fastGptChatSession.setStatus(1);
+                        fastGptChatSession.setRemindCount(0);
+                        fastGptChatSession.setRemindStatus(0);
+                        fastGptChatSession.setCreateTime(new Date());
+                        fastGptChatSession.setQwExtId(qwExternalContacts.getId());
+                        fastGptChatSession.setQwUserId(sendUser.getId());
+                        fastGptChatSession.setIsArtificial(0);
+                        fastGptChatSession.setAvatar(qwExternalContacts.getAvatar());
+                        fastGptChatSession.setNickName(qwExternalContacts.getName());
+                        fastGptChatSession.setCompanyId(sendUser.getCompanyId());
+                        fastGptChatSession.setLastTime(new Date());
+                        fastGptChatSession.setIsReply(0);
+                        fastGptChatSessionMapper.insertFastGptChatSession(fastGptChatSession);
+                        addUserSex(qwExternalContacts);
+                        saveQwUserMsg(fastGptChatSession,2,count);
+                    }
+                }
             }
         }
         return R.ok();

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPackageOrderMapper.java

@@ -239,4 +239,6 @@ public interface FsPackageOrderMapper
     FsPackage selectFsPackageByOrderId(Long packageOrderId);
 
     List<PackageOrderDTO> getNewOrder();
+
+    List<FsPackageOrder> selectOutTimeOrderList(@Param("unPayTime") Integer unPayTime);
 }

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

@@ -1194,4 +1194,6 @@ public interface FsStoreOrderMapper
     List<Report> selectOrderByCustomerIds(@Param("map") ReportParam param);
 
     FsStoreOrderAmountStatsVo selectFsStoreOrderAmountStats(FsStoreOrderAmountStatsQueryDto queryDto);
+
+    List<FsStoreOrder> selectOutTimeOrderList(@Param("unPayTime")Integer unPayTime);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -315,6 +315,11 @@ public interface FsUserMapper
 
     FsUserSummaryCountVO countUserSummary(@Param("userId") Long userId, @Param("companyId") Long companyId);
 
+    Integer getUserTotal(@Param("userId") Long userId, @Param("companyId") Long companyId);
+
+    Integer getTodayNewUser(@Param("userId") Long userId, @Param("companyId") Long companyId);
+
+
     List<FsUserSummaryCountTagVO> countTag(@Param("userId") Long userId, @Param("companyId") Long companyId);
 
     Map<String, Long> countUserCourse(UserStatisticsCommonParam param);

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/IFsPackageOrderService.java

@@ -142,4 +142,6 @@ public interface IFsPackageOrderService
 
 
     R getPackageOrder(String createOrderKey);
+
+    List<FsPackageOrder> selectOutTimeOrderList(Integer unPayTime);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java

@@ -276,4 +276,6 @@ public interface IFsStoreOrderService
     FsStoreOrderScrm selectFsStoreOrderScrmByOrderCode(String soId);
 
     FsStoreOrder confirmOrder(FsPackageOrder packageOrder,Long doctorId);
+
+    List<FsStoreOrder> selectOutTimeOrderList(Integer unPayTime);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java

@@ -126,4 +126,6 @@ public interface IFsStorePaymentService
     R getWxaCodeByPayment(FsStorePaymentGetWxaCodeParam param);
 
     String payConfirm(String payCode,String tradeNo,String bankTransactionId,String bankSerialNo);
+
+    void synchronizePayStatus();
 }

+ 7 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java

@@ -358,7 +358,7 @@ public class FsPackageOrderServiceImpl implements IFsPackageOrderService
         if (param.getUserCouponId() != null && param.getUserCouponId() > 0l) {
             FsUserCoupon userCoupon = userCouponService.selectFsUserCouponById(param.getUserCouponId());
             if (userCoupon != null) {
-                if (userCoupon.getStatus() == 0) {
+                if (Objects.equals(userCoupon.getBusinessId(), param.getOrderId()) || userCoupon.getStatus() == 0) {
                     FsCoupon coupon = couponService.selectFsCouponByCouponId(userCoupon.getCouponId());
                     if (coupon.getCouponType().equals(1)) {
                         if (coupon.getMinPrice().compareTo(orderPrice) <=0) {
@@ -500,7 +500,7 @@ public class FsPackageOrderServiceImpl implements IFsPackageOrderService
         FsPatient patient=null;
         FsDoctor doctor=null;
         FsPackage fsPackage=fsPackageMapper.selectFsPackageByPackageId(param.getPackageId());
-        if(fsPackage.getProductType()==1 || fsPackage.getProductType()==2){
+        if(fsPackage.getProductType()!= null &&(fsPackage.getProductType()==1 || fsPackage.getProductType()==2)){
             if(param.getPatientId()!=null){
                 patient=fsPatientMapper.selectFsPatientByPatientId(param.getPatientId());
                 if (patient==null){
@@ -1830,4 +1830,9 @@ public class FsPackageOrderServiceImpl implements IFsPackageOrderService
         }
         return R.ok().put("package", fsPackage).put("money", money).put("payType", payType);
     }
+
+    @Override
+    public List<FsPackageOrder> selectOutTimeOrderList(Integer unPayTime) {
+        return fsPackageOrderMapper.selectOutTimeOrderList(unPayTime);
+    }
 }

+ 2 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java

@@ -388,7 +388,7 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         return 1;
     }
 
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public int refundMoney(FsStoreAfterSales fsStoreAfterSales) {
         FsStoreAfterSales order = fsStoreAfterSalesMapper.selectFsStoreAfterSalesById(fsStoreAfterSales.getId());
@@ -473,7 +473,7 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         if (payments != null && payments.size() > 0) {
             FsStorePayment payment = payments.get(0);
             if (reMoney.compareTo(payment.getPayMoney()) > 0) {
-                return 0; //退款金额不能大于实际支付金额
+                throw new CustomException("退款金额不能大于实际支付金额"); //退款金额不能大于实际支付金额
             }
             String json = configService.selectConfigByKey("his.pay");
             if (payment.getPayMode().equals("wx")) {

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -4407,4 +4407,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return order;
     }
 
+    @Override
+    public List<FsStoreOrder> selectOutTimeOrderList(Integer unPayTime) {
+        return fsStoreOrderMapper.selectOutTimeOrderList(unPayTime);
+    }
+
 }

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -7,6 +7,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
@@ -1536,6 +1537,26 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return "SUCCESS";
     }
 
+    @Override
+    public void synchronizePayStatus() {
+        FsStorePayment queryParam = new FsStorePayment();
+        queryParam.setStatus(0);//未支付
+        queryParam.setBeginTime(DateUtils.addDateDays(-1));
+        queryParam.setEndTime(DateUtils.getDate());
+        List<FsStorePayment> list = selectFsStorePaymentList(queryParam);
+        if (list != null && !list.isEmpty()) {
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (FsStorePayment fsStorePayment : list) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    updateFsStorePaymentByDecryptForm(fsStorePayment.getPaymentId());
+                    logger.info("定时任务:同步支付状态,payment_id:{}",fsStorePayment.getPaymentId());
+                });
+                futures.add(future);
+            }
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+        }
+    }
+
     @Override
     public R paymentByWxaCode(FsStorePaymentPayParam param) {
         FsUser user = userMapper.selectFsUserById(param.getUserId());

+ 59 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -846,8 +846,52 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.selectFsUserByUserIds(userIds, companyUserId);
     }
 
+
     @Override
     public FsUserSummaryCountVO userSummaryCount(Long userId) {
+        // 判断是否是管理员
+        Long companyId;
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(userId);
+        if (companyUser != null && companyUser.isAdmin()) {
+            userId = 0L;
+            companyId = companyUser.getCompanyId();
+        } else {
+            companyId = null;
+        }
+
+        // 使用 CompletableFuture 异步执行两个查询
+
+        Long finalUserId = userId;
+        CompletableFuture<Integer> userTotalFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.getUserTotal(finalUserId, companyId));
+
+        CompletableFuture<Integer> todayNewUserFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.getTodayNewUser(finalUserId, companyId));
+
+        CompletableFuture<List<FsUserSummaryCountTagVO>> countTagList = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countTag(finalUserId, companyId));
+
+        // 等待查询结果并构建返回对象
+        FsUserSummaryCountVO fsUserSummaryCountVO = new FsUserSummaryCountVO();
+        try {
+            fsUserSummaryCountVO.setUserTotal(userTotalFuture.get());
+            fsUserSummaryCountVO.setTodayNewUser(todayNewUserFuture.get());
+            fsUserSummaryCountVO.setTagList(countTagList.get());
+
+        } catch (InterruptedException | ExecutionException e) {
+            logger.error("异步查询用户统计数据失败", e);
+            // 设置默认值
+            fsUserSummaryCountVO.setUserTotal(0);
+            fsUserSummaryCountVO.setTodayNewUser(0);
+            fsUserSummaryCountVO.setTagList(new ArrayList<>());
+        }
+
+        return fsUserSummaryCountVO;
+    }
+
+    public FsUserSummaryCountVO userSummaryCountOld(Long userId) {
+        // 开始时间
+        long start = System.currentTimeMillis();
         // 判断是否是管理员
         Long companyId = null;
         CompanyUser companyUser = companyUserMapper.selectCompanyUserById(userId);
@@ -855,9 +899,24 @@ public class FsUserServiceImpl implements IFsUserService {
             userId = 0L;
             companyId = companyUser.getCompanyId();
         }
+        long start2 = System.currentTimeMillis();
+        logger.info("countUserSummary 执行耗时: {}ms", start2 - start);
+
+        //     Integer getUserTotal(@Param("userId") Long userId, @Param("companyId") Long companyId);
+        //
+        //    Integer getTodayNewUser(@Param("userId") Long userId, @Param("companyId") Long companyId);
+
+
         FsUserSummaryCountVO fsUserSummaryCountVO = fsUserMapper.countUserSummary(userId, companyId);
+        long start3 =System.currentTimeMillis();
+        logger.info("countTag 执行耗时: {}ms", start3 - start2);
         List<FsUserSummaryCountTagVO> countTagList = fsUserMapper.countTag(userId, companyId);
         fsUserSummaryCountVO.setTagList(countTagList);
+
+        // 结束时间
+        long end = System.currentTimeMillis();
+        logger.info("countUserAnswerStats 执行耗时: {}ms", end - start3);
+
         return fsUserSummaryCountVO;
     }
 

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java

@@ -24,5 +24,5 @@ public class StoreConfig implements Serializable {
     private Integer auditSwitch; // 订单审核开关
     private Integer createSalesOrderType; // 订单改价方式 1 商品改价 2总价改价
     private Boolean isBrushOrders;//是否开启刷单按钮
-
+    private Integer orderAttribution; // 下单归属 1 多销售 2单销售
 }

+ 78 - 53
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -33,13 +33,11 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
-import com.fs.company.domain.Company;
-import com.fs.company.domain.CompanyDept;
-import com.fs.company.domain.CompanyMoneyLogs;
-import com.fs.company.domain.CompanyUser;
+import com.fs.company.domain.*;
 import com.fs.company.mapper.CompanyDeptMapper;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
+import com.fs.company.mapper.CompanyUserUserMapper;
 import com.fs.company.service.ICompanyDeptService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
@@ -170,6 +168,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
     private CompanyMoneyLogsMapper moneyLogsMapper;
+
+    @Autowired
+    private CompanyUserUserMapper companyUserUserMapper;
     @Autowired
     private IFsStoreOrderStatusScrmService orderStatusService;
     @Autowired
@@ -740,23 +741,42 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             storeOrder.setStoreHouseCode("CK01");
             storeOrder.setCompanyId(param.getCompanyId());
             storeOrder.setCompanyUserId(param.getCompanyUserId());
+
+            String json = configService.selectConfigByKey("store.config");
+            StoreConfig config= JSONUtil.toBean(json, StoreConfig.class);
             //绑定销售
-            FsUserScrm fsuser = userService.selectFsUserById(userId);
-            if (param.getCompanyUserId() != null) {
-                if (!CloudHostUtils.hasCloudHostName("鸿森堂") && ObjectUtil.isNotEmpty(fsuser.getCompanyUserId()) && !Objects.equals(fsuser.getCompanyUserId(), param.getCompanyUserId())) {
-                    CompanyUser companyUser = companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
-                    return R.error(String.format("请联系%s销售进行购买商品!", companyUser.getNickName()));
-                } else {
-                    fsuser.setCompanyUserId(param.getCompanyUserId());
-                    userService.updateFsUser(fsuser);
+            FsUserScrm fsuser= userService.selectFsUserById(userId);
+            if(ObjectUtil.isEmpty(config.getOrderAttribution())
+                    ||!config.getOrderAttribution().equals(1)){
+                if(param.getCompanyUserId()!=null){
+                    if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&fsuser.getCompanyUserId()!=param.getCompanyUserId()){
+                        CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
+                        return R.error(String.format("请联系%s销售进行购买商品!",companyUser.getNickName()));
+                    }else {
+                        fsuser.setCompanyUserId(param.getCompanyUserId());
+                        userService.updateFsUser(fsuser);
+                    }
+                    CompanyUser companyUser=companyUserService.selectCompanyUserById(param.getCompanyUserId());
+                    if(companyUser!=null){
+                        storeOrder.setDeptId(companyUser.getDeptId());
+                    }
+                }else {
+                    storeOrder.setCompanyUserId(fsuser.getCompanyUserId());
                 }
-                CompanyUser companyUser = companyUserService.selectCompanyUserById(param.getCompanyUserId());
-                if (companyUser != null) {
-                    storeOrder.setDeptId(companyUser.getDeptId());
+            }
+
+            CompanyUserUser map=new CompanyUserUser();
+            map.setCompanyUserId(param.getCompanyUserId());
+            map.setUserId(userId);
+            List<CompanyUserUser> list= companyUserUserMapper.selectCompanyUserUserList(map);
+            if(list==null||list.size()==0){
+                CompanyUser companyUser=companyUserService.selectCompanyUserById(param.getCompanyUserId());
+                if(companyUser!=null&&companyUser.getStatus().equals("0")){
+                    map.setCompanyId(companyUser.getCompanyId());
+                    companyUserUserMapper.insertCompanyUserUser(map);
                 }
-            } else {
-                storeOrder.setCompanyUserId(fsuser.getCompanyUserId());
             }
+
             storeOrder.setUserId(userId);
             storeOrder.setOrderCode(orderSn);
             storeOrder.setRealName(address.getRealName());
@@ -810,8 +830,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             storeOrder.setShippingType(1);
             storeOrder.setCreateTime(new Date());
 
-            String json = configService.selectConfigByKey("store.config");
-            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+//            String json = configService.selectConfigByKey("store.config");
+//            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
             if (config.getServiceFee() != null) {
                 storeOrder.setServiceFee(config.getServiceFee());
             }
@@ -1728,43 +1748,44 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if (order != null && !order.getPaid().equals(0)) {
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                 return "";
-            } else if (type.equals(2)) {
-                //货到付款
-                order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
-                if (!order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())) {
-                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-                    return "";
-                }
             }
-            //写入公司佣金 当有归属公司时只进行公司分佣
-            if (order.getCompanyId() != null && order.getCompanyId() > 0) {
-                companyService.addCompanyTuiMoney(order);
-                //没有归属公司对个人分佣
-            } else if (order.getIsPackage() != 1 && order.getTuiUserId() != null && order.getTuiUserId() > 0) {
-                //处理佣金 套餐不分佣金
-                FsStoreOrderItemScrm orderItemMap = new FsStoreOrderItemScrm();
-                orderItemMap.setOrderId(order.getId());
-                List<FsStoreOrderItemScrm> items = storeOrderItemService.selectFsStoreOrderItemList(orderItemMap);
-                userService.addTuiMoney(order, items);
-            }
-            //增加用户购买次数
-            userService.incPayCount(order.getUserId());
-            //增加状态
-            orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
-                    OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
-            FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
-            storeOrder.setId(order.getId());
-            storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());
-            storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
-            storeOrder.setPayTime(new Date());
-            fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
-            // 添加订单审核
-            if (storeOrder.getCompanyId() != null) {
-                addOrderAudit(order);
+        }else if (type.equals(2)) {
+            //货到付款
+            order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
+            if (!order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())) {
+                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+                return "";
             }
         }
+        //写入公司佣金 当有归属公司时只进行公司分佣
+        if (order.getCompanyId() != null && order.getCompanyId() > 0) {
+            companyService.addCompanyTuiMoney(order);
+            //没有归属公司对个人分佣
+        } else if (order.getIsPackage() != 1 && order.getTuiUserId() != null && order.getTuiUserId() > 0) {
+            //处理佣金 套餐不分佣金
+            FsStoreOrderItemScrm orderItemMap = new FsStoreOrderItemScrm();
+            orderItemMap.setOrderId(order.getId());
+            List<FsStoreOrderItemScrm> items = storeOrderItemService.selectFsStoreOrderItemList(orderItemMap);
+            userService.addTuiMoney(order, items);
+        }
+        //增加用户购买次数
+        userService.incPayCount(order.getUserId());
+        //增加状态
+        orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
+                OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
+        FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
+        storeOrder.setId(order.getId());
+        storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());
+        storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
+        storeOrder.setPayTime(new Date());
+        fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+        // 添加订单审核
+        if (storeOrder.getCompanyId() != null) {
+            addOrderAudit(order);
+        }
 
-        return "SUCCESS";
+
+    return "SUCCESS";
 
 
         //非处方直接提交OMS
@@ -3876,6 +3897,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     order.setPayType("2");
                     BigDecimal payMoney=order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate())).divide(new BigDecimal(100));
                     payMoney=new BigDecimal(payMoney.setScale(0, BigDecimal.ROUND_HALF_UP).longValue());
+                    // 如果小程序需要支付的金额小于0.01元,不能走物流代收,让走货到付款 xgb
+                    if (payMoney.compareTo(new BigDecimal("0.01")) < 0) {
+                        return R.error("物流代收计算支付金额为0,不允许选择物流代收");
+                    }
                     order.setPayDelivery(order.getPayPrice().subtract(payMoney));
                     order.setPayMoney(payMoney);
                 }
@@ -4367,7 +4392,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     order.setPayDelivery(BigDecimal.ZERO);
                 }
                 else if(param.getPayType().equals(2)){
-
+                    // 物流代收
                     order.setPayType("2");
                     BigDecimal payMoney=order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate())).divide(new BigDecimal(100));
                     payMoney=new BigDecimal(payMoney.setScale(0, BigDecimal.ROUND_HALF_UP).longValue());

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -878,7 +878,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         storePayment.setPayMoney(param.getPayMoney());
         storePayment.setCreateTime(new Date());
         storePayment.setPayTypeCode("weixin");
-        storePayment.setBusinessType(7);//微信收款
+        storePayment.setBusinessType(1);//微信收款
         storePayment.setRemark("商城收款订单支付");
         storePayment.setOpenId(user.getMaOpenId());
         storePayment.setUserId(user.getUserId());

+ 2 - 0
fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java

@@ -2,4 +2,6 @@ package com.fs.qw.cache;
 
 public interface IQwUserCacheService {
     String queryQwUserNameByUserId(String userId);
+    String queryCorpIdByQwUserId(Long qwUserId);
+
 }

+ 15 - 0
fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java

@@ -2,6 +2,7 @@ package com.fs.qw.cache.impl;
 
 import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserService;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -14,6 +15,9 @@ import java.util.concurrent.TimeUnit;
 public class QwUserCacheServiceImpl implements IQwUserCacheService {
     @Autowired
     private IQwUserService qwUserService;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
     /**
      * 企微用户名昵称缓存类
      */
@@ -21,6 +25,12 @@ public class QwUserCacheServiceImpl implements IQwUserCacheService {
             .maximumSize(5000)
             .expireAfterWrite(12, TimeUnit.HOURS)
             .build();
+
+    private static final Cache<Long, String> QW_USER_ID_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(5, TimeUnit.HOURS)
+            .build();
+
     @Override
     public String queryQwUserNameByUserId(String userId) {
         return QW_USER_CACHE.get(userId,e-> {
@@ -31,4 +41,9 @@ public class QwUserCacheServiceImpl implements IQwUserCacheService {
             return "-";
         });
     }
+
+    @Override
+    public String queryCorpIdByQwUserId(Long qwUserId) {
+        return QW_USER_ID_CACHE.get(qwUserId,e-> qwUserMapper.selectCorpIdById(qwUserId));
+    }
 }

+ 47 - 0
fs-service/src/main/java/com/fs/qw/domain/QwDeptTreeSelect.java

@@ -0,0 +1,47 @@
+package com.fs.qw.domain;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class QwDeptTreeSelect implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String label;
+    private List<QwDeptTreeSelect> children;
+
+    // 构造方法
+    public QwDeptTreeSelect() {
+    }
+
+    public QwDeptTreeSelect(Long id, String label) {
+        this.id = id;
+        this.label = label;
+    }
+
+    // getter和setter
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public List<QwDeptTreeSelect> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<QwDeptTreeSelect> children) {
+        this.children = children;
+    }
+}

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

@@ -397,8 +397,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     @Select("SELECT id,external_user_id,name,avatar,remark,description,fs_user_id FROM  qw_external_contact " +
             " WHERE user_id = #{map.userId}   " +
-            "AND external_user_id = #{map.externalUserId} " +
             "AND corp_id =#{map.corpId} " +
+            "AND external_user_id = #{map.externalUserId}" +
+            "AND `status` != 4 " +
+            "ORDER BY id desc " +
             "limit 1 ")
     QwExternalContact getQwExternalContactDetails(@Param("map")QwExternalContactHParam param);
 

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

@@ -83,4 +83,5 @@ public interface QwTagGroupMapper
 
     List<QwTagGroupListVO> selectQwTagGroups(QwTagGroup qwTagGroup);
 
+    QwTagGroup selectQwTagGroupByName(@Param("tagGroup") String tagGroup, @Param("corpId") String corpId);
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -19,6 +19,7 @@ import org.apache.ibatis.annotations.Update;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 企微用户Mapper接口
@@ -163,6 +164,12 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "                       #{item} " +
             "                   </foreach> " +
             "            </if>" +
+            "            <if test=\"qwDeptIdList != null and !qwDeptIdList.isEmpty() and  userType != '00' \">" +
+            "               AND qd.dept_id IN " +
+            "                   <foreach collection='qwDeptIdList' item='item' open='(' separator=',' close=')'> " +
+            "                       #{item} " +
+            "                   </foreach> " +
+            "            </if>" +
             "ORDER BY  qu.login_status asc,qu.tool_status desc " +
             "</script>"})
     List<QwUserVO> selectQwUserListStaffVO(QwUserListParam qwUser);
@@ -442,6 +449,13 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
 
+    @Select("select corp_id from qw_user where id=#{id} limit 1")
+    String selectCorpIdById(@Param("id") Long id);
+    List<Long> selectDeptByParentId(@Param("deptId")Long deptId,@Param("corpId") String corpId);
+
+    @Select("  select company_id from qw_user where qw_user_id = #{owner} and corp_id = #{corpId}  limit 1")
+    Long getCompanyIdByCorpIdAndOwner(@Param("corpId")String corpId, @Param("owner")String owner);
+
     /**
      * 根据销售ID查询企微用户列表
      */

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

@@ -142,7 +142,7 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "ORDER BY\n" +
             "    DATE(qec.create_time) "+
             "</script>"})
-    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAnd(FsCourseWatchLogListParam param);
+    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAndOther(FsCourseWatchLogListParam param);
     @Select("select \n" +
             "COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,\n" +
             "COUNT(CASE WHEN day = 0 and status=2 THEN 1 END) AS firstOver,\n" +

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

@@ -57,6 +57,11 @@ public class QwUserListParam {
      * 销售部门
      */
     private List<Long> cuDeptIdList;
+    /**
+     * 企微部门
+     */
+    private List<Long> qwDeptIdList;
+
 
     /**
      * 部门类型 00 管理员 01 员工

+ 18 - 2
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java

@@ -1,6 +1,7 @@
 package com.fs.qw.service;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
@@ -12,9 +13,11 @@ import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.fastGpt.domain.FastGptChatReplaceWords;
 import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwExternalContactInfoMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qw.vo.QwSopTempSetting;
 import com.fs.sop.domain.QwSopLogs;
@@ -67,6 +70,8 @@ public class AsyncQwAiChatSopService {
 
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
 
     @Autowired
     private QwSopLogsMapper qwSopLogsMapper;
@@ -92,6 +97,13 @@ public class AsyncQwAiChatSopService {
                                    QwUser qwUser, String externalUserID, String externalContactName,
                                    Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
 
+
+        QwExternalContact contact;
+        if(externalId != null){
+            contact = qwExternalContactMapper.selectById(externalId);
+        } else {
+            contact = null;
+        }
         //新客对话任务
         List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
         List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
@@ -120,7 +132,6 @@ public class AsyncQwAiChatSopService {
                 List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
 
                 tempContentList.forEach(content->{
-
                     QwSopLogs sopLogs = new QwSopLogs();
                     sopLogs.setQwUserKey(qwUser.getId());
                     sopLogs.setQwUserid(userID);
@@ -162,8 +173,13 @@ public class AsyncQwAiChatSopService {
                         case "3":
 
                             if ("1".equals(setting.getContentType())) {
+                                String defaultName = "同学";
+                                if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
+                                    defaultName = contact.getName();
+                                }
                                 setting.setValue(setting.getValue()
-                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
+                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText())
+                                        .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
                             }
 
 

+ 3 - 0
fs-service/src/main/java/com/fs/qw/service/IQwDeptService.java

@@ -2,6 +2,7 @@ package com.fs.qw.service;
 
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwDept;
+import com.fs.qw.domain.QwDeptTreeSelect;
 
 import java.util.List;
 
@@ -69,4 +70,6 @@ public interface IQwDeptService
      * @return 结果
      */
     public int deleteQwDeptById(Long id);
+
+    List<QwDeptTreeSelect> buildDeptTreeSelect(List<QwDept> depts);
 }

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

@@ -199,6 +199,8 @@ public interface IQwUserService
 
     List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
 
+    List<Long> selectDeptByParentId(Long deptId,String cropId);
+
     /**
      * 根据销售ID查询企微用户列表
      */

+ 69 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwDeptServiceImpl.java

@@ -2,6 +2,7 @@ package com.fs.qw.service.impl;
 
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwDept;
+import com.fs.qw.domain.QwDeptTreeSelect;
 import com.fs.qw.mapper.QwDeptMapper;
 import com.fs.qw.service.IQwDeptService;
 import com.fs.qwApi.domain.QwDeptResult;
@@ -10,7 +11,11 @@ import com.fs.qwApi.service.QwApiService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 企业微信部门Service业务层处理
@@ -129,4 +134,68 @@ public class QwDeptServiceImpl implements IQwDeptService
     {
         return qwDeptMapper.deleteQwDeptById(id);
     }
+
+    /**
+     * @Description: 按TreeSelect结构返回部门
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/30 9:37
+     */
+    @Override
+    /**
+     * 将部门列表转换为TreeSelect树形结构
+     */
+    public List<QwDeptTreeSelect> buildDeptTreeSelect(List<QwDept> deptList) {
+        if (deptList == null || deptList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 按父部门ID分组
+        Map<Long, List<QwDept>> deptMap = deptList.stream()
+                .collect(Collectors.groupingBy(QwDept::getParentid));
+
+        // 获取所有根部门(parentId = 0)
+        List<QwDept> rootDepts = deptMap.getOrDefault(0L, new ArrayList<>());
+
+        // 构建树形结构
+        return rootDepts.stream()
+                .map(dept -> buildTreeSelect(dept, deptMap))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 递归构建TreeSelect节点
+     */
+    private QwDeptTreeSelect buildTreeSelect(QwDept dept, Map<Long, List<QwDept>> deptMap) {
+        return buildTreeSelect(dept, deptMap, 0);
+    }
+
+    /**
+     * 递归构建TreeSelect节点,最多5级
+     */
+    private QwDeptTreeSelect buildTreeSelect(QwDept dept, Map<Long, List<QwDept>> deptMap, int depth) {
+        // 限制最多5级,避免死循环
+        if (depth > 5) {
+            return new QwDeptTreeSelect(dept.getDeptId(), dept.getDeptName());
+        }
+
+        QwDeptTreeSelect treeSelect = new QwDeptTreeSelect(dept.getDeptId(), dept.getDeptName());
+
+        // 获取当前部门的子部门
+        List<QwDept> children = deptMap.get(dept.getDeptId());
+        if (children != null && !children.isEmpty()) {
+            // 递归构建子节点
+            List<QwDeptTreeSelect> childNodes = children.stream()
+                    .map(child -> buildTreeSelect(child, deptMap, depth + 1))
+                    .collect(Collectors.toList());
+            treeSelect.setChildren(childNodes);
+        }
+
+        return treeSelect;
+    }
+
+
+
+
 }

+ 14 - 13
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -2230,7 +2230,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         QwContactWay wayId = null;
         //先入客户
         QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-        boolean isNewQwExternalContact = qwExternalContact == null ? true : false;
+        boolean isNewQwExternalContact = qwExternalContact == null;
         qwExternalContact = qwExternalContact == null ? new QwExternalContact() : qwExternalContact;
         qwExternalContact.setUserId(userID); // 设置属于用户ID
         qwExternalContact.setExternalUserId(externalUserID); // 设置外部联系人ID
@@ -2259,18 +2259,18 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 //        }
 //        iAdHtmlClickLogService.upload(state, AdUploadType.ADD_WX, e -> finalQwExternalContact.setUploadAddWxStatus(1));
         //重粉问题
-        try {
-            new Thread(() -> {
-                try {
-                    Thread.sleep(3000);
-                } catch (InterruptedException e) {
-                    logger.error("添加等待时长错误", e);
-                }
-                rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(0).externalUserId(externalUserID).build()));
-            }).start();
-        }catch (Exception e){
-            logger.error("重粉提交mq失败", e);
-        }
+//        try {
+//            new Thread(() -> {
+//                try {
+//                    Thread.sleep(3000);
+//                } catch (InterruptedException e) {
+//                    logger.error("添加等待时长错误", e);
+//                }
+//                rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(0).externalUserId(externalUserID).build()));
+//            }).start();
+//        }catch (Exception e){
+//            logger.error("重粉提交mq失败", e);
+//        }
 
 
 
@@ -2526,6 +2526,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                 qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
                 qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
                 qwExternalContact.setDescription(followUser.getDescription()); // 设置描述信息
+                qwExternalContact.setUnionid(externalContact.getUnionid());
                 List<Tag> tags = followUser.getTags();
                 Set<String> combinedTagsSet = new HashSet<>();
 

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

@@ -311,6 +311,11 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
                     qwGroupChat.setCorpId(corpId);
                     qwGroupChat.setNotice(notice);
                     qwGroupChat.setOwner(owner);
+                    //2025-10-31 新增维护 qw_group_chat的company_id
+                    Long findCompanyId = qwUserMapper.getCompanyIdByCorpIdAndOwner(corpId, owner);
+                    if(null != findCompanyId){
+                        qwGroupChat.setCompanyId(findCompanyId);
+                    }
                     qwGroupChat.setCreateAt(String.valueOf(createTime));
                     qwGroupChat.setMemberVersion(groupChat.getMemberVersion());
                     qwGroupChat.setChatId(chatId);

+ 1 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java

@@ -159,6 +159,7 @@ public class QwTagGroupServiceImpl implements IQwTagGroupService {
     public R deleteQwTagGroupByIds(Long[] ids) {
         for (Long id : ids) {
             QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(id);
+            log.info("删除标签组-"+qwTagGroup);
             if (qwTagGroup.getGroupFrom() != null && qwTagGroup.getGroupFrom() == 1) {
                 QwTagParam qwTagParam = new QwTagParam();
                 qwTagParam.setGroup_id(Arrays.asList(qwTagGroup.getGroupId()));

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

@@ -1559,6 +1559,18 @@ public class QwUserServiceImpl implements IQwUserService
         return qwUserMapper.selectQwCompanyListOptionsVOBySys();
     }
 
+    /**
+     * @Description: 根据企微部门查询下级部门id
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/30 14:27
+     */
+    @Override
+    public List<Long> selectDeptByParentId(Long deptId,String cropId) {
+        return qwUserMapper.selectDeptByParentId(deptId,cropId);
+    }
+
     /**
      * 根据销售ID查询企微用户列表
      */

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

@@ -155,7 +155,7 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
         if (param.getCompanyUserId()!=null){
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
         }
-        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAnd(param);
+        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAndOther(param);
         for (QwWatchLogStatisticsListVO vo : vos) {
             Long id = vo.getId();
             Date createTime = vo.getCreateTime();

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

@@ -7,6 +7,7 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.domain.FsUserCourseVideo;
@@ -34,6 +35,8 @@ import lombok.AllArgsConstructor;
 import org.apache.commons.beanutils.ConvertUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
@@ -55,6 +58,10 @@ import java.util.stream.Collectors;
 @AllArgsConstructor
 public class QwSopTempServiceImpl implements IQwSopTempService
 {
+
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     private final QwSopTempMapper qwSopTempMapper;
     private final FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
     private final IQwSopTempRulesService qwSopTempRulesService;
@@ -69,7 +76,6 @@ public class QwSopTempServiceImpl implements IQwSopTempService
     private final IQwSopService qwSopService;
     private final IQwUserService qwUserService;
 
-
     /**
      * 查询sop模板
      *
@@ -460,10 +466,19 @@ public class QwSopTempServiceImpl implements IQwSopTempService
                 QwSopTempSetting2.Content.Setting setting = new QwSopTempSetting2.Content.Setting();
                 setting.setLinkTitle(e.getTitle());
                 setting.setMiniprogramTitle(e.getTitle());
-                setting.setMiniprogramPicUrl(fsUserCourse.getImgUrl());
+
+                //用课节图片做封面
+                if("今正科技".equals(cloudHostProper.getCompanyName())){
+                    setting.setMiniprogramPicUrl(!StringUtil.isNullOrEmpty(e.getThumbnail())?e.getThumbnail():fsUserCourse.getImgUrl());
+                    setting.setLinkImageUrl(!StringUtil.isNullOrEmpty(e.getThumbnail())?e.getThumbnail():fsUserCourse.getImgUrl());
+                }else {
+                    setting.setMiniprogramPicUrl(fsUserCourse.getImgUrl());
+                    setting.setLinkImageUrl(fsUserCourse.getImgUrl());
+
+                }
+
                 setting.setIsBindUrl(1);
                 setting.setLinkDescribe(e.getTitle());
-                setting.setLinkImageUrl(fsUserCourse.getImgUrl());
                 setting.setContentType("4");
                 content.setContent(JSON.toJSONString(setting));
                 content.setIsBindUrl(1);

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

@@ -553,7 +553,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setCorpId(qwGroupChat.getCorpId());
                     sopLogs.setSort(30000001);
-                    sopLogs.setSendType(6);
+                    sopLogs.setSendType(2);
                     sopLogs.setExternalUserName(groupUser.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
 
@@ -692,7 +692,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setCorpId(groupChat.getCorpId());
                     sopLogs.setSort(2);
-                    sopLogs.setSendType(12);
+                    sopLogs.setSendType(6);
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setQwUserKey(qwUser.getId());
                     // 设置实际发送人

+ 115 - 0
fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java

@@ -0,0 +1,115 @@
+package com.fs.tag.domain;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 标签更新对列表
+ */
+@Data
+public class FsTagUpdateQueue {
+    /** id */
+    private Long id;
+
+    /** 看课日志id */
+    private Long courseLogId;
+
+    /** 是否是先导课*/
+    private Integer isFirst;
+
+    /**
+     * 课程排序
+     */
+    private Long sort;
+
+    /** 课程ID */
+    private Long courseId;
+
+    /** 标签id */
+    private String tagId;
+
+    /** 标签名称 */
+    private String tagName;
+
+    /** 操作类型(0 ADD 1 REMOVE) 默认0 */
+    private Integer operationType;
+
+    /** 视频ID */
+    private Long videoId;
+
+    /** 0未处理 1处理中 2成功 3失败 */
+    private Integer status;
+
+    /** 重试次数 */
+    private Integer retryCount;
+
+    /** 企微主体id */
+    private String corpId;
+
+    /** 企微user_id */
+    private Long qwUserId;
+
+    /** 企微外部联系人id */
+    private Long qwExternalContactId;
+    /**
+     * 记录类型
+     * 0 正在看课
+     * 1 已完课
+     */
+    private Integer logType;
+
+    /** 失败原因 */
+    private String failMsg;
+
+    /** 请求参数 */
+    private String payload;
+
+    /** 返回结果 */
+    private String response;
+
+    /** 创建时间 */
+    private LocalDateTime createTime;
+
+    /** 更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 更新人 */
+    private Long updateBy;
+
+    /** 创建人 */
+    private Long createBy;
+
+    /**
+     * 下次执行时间
+     */
+    private LocalDateTime nextExecuteTime;
+
+
+    /**
+     * 看课中标签ID
+     */
+    private String watchingTagId;
+    /**
+     * 完课标签ID
+     */
+    private String watchedTagId;
+    /**
+     * 标签组ID
+     */
+    private String tagGroupId;
+
+    /**
+     * 标签组表中的ID
+     */
+    private Long tgId;
+    /**
+     * 看课标签 表中的ID
+     */
+    private Long watchingTgId;
+    /**
+     * 完课标签 表中的ID
+     */
+    private Long watchedTgId;
+}

+ 162 - 0
fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java

@@ -0,0 +1,162 @@
+package com.fs.tag.mapper;
+
+import com.fs.tag.domain.FsTagUpdateQueue;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 标签更新对列表 Mapper
+ */
+@Mapper
+public interface FsTagUpdateQueueMapper {
+
+    @Select("select * from fs_tag_update_queue where retry_count < 3 and status in (0,3) and (next_execute_time < now() or next_execute_time is null) limit 500")
+    List<FsTagUpdateQueue> selectPending();
+
+    @Select("<script>" +
+            "SELECT * FROM fs_tag_update_queue " +
+            "<where>" +
+            "<if test='id != null'> AND id = #{id} </if>" +
+            "<if test='courseLogId != null'> AND course_log_id = #{courseLogId} </if>" +
+            "<if test='courseId != null'> AND course_id = #{courseId} </if>" +
+            "<if test='tagId != null'> AND tag_id = #{tagId} </if>" +
+            "<if test='tagName != null'> AND tag_name = #{tagName} </if>" +
+            "<if test='operationType != null'> AND operation_type = #{operationType} </if>" +
+            "<if test='videoId != null'> AND video_id = #{videoId} </if>" +
+            "<if test='status != null'> AND status = #{status} </if>" +
+            "<if test='retryCount != null'> AND retry_count = #{retryCount} </if>" +
+            "<if test='corpId != null'> AND corp_id = #{corpId} </if>" +
+            "<if test='qwUserId != null'> AND qw_user_id = #{qwUserId} </if>" +
+            "</where>" +
+            "</script>")
+    List<FsTagUpdateQueue> selectByConditions(FsTagUpdateQueue condition);
+
+    @Insert("<script>" +
+            "INSERT INTO fs_tag_update_queue " +
+            "(course_log_id, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, corp_id, qw_user_id, fail_msg, payload, response, create_time, update_time, update_by, create_by,log_type) " +
+            "VALUES " +
+            "<trim prefix='(' suffix=')' suffixOverrides=','>" +
+            "<if test='courseLogId != null'>course_log_id,</if>" +
+            "<if test='courseId != null'>course_id,</if>" +
+            "<if test='tagId != null'>tag_id,</if>" +
+            "<if test='tagName != null'>tag_name,</if>" +
+            "<if test='operationType != null'>operation_type,</if>" +
+            "<if test='videoId != null'>video_id,</if>" +
+            "<if test='status != null'>status,</if>" +
+            "<if test='retryCount != null'>retry_count,</if>" +
+            "<if test='corpId != null'>corp_id,</if>" +
+            "<if test='qwUserId != null'>qw_user_id,</if>" +
+            "<if test='qwExternalContactId != null'>qw_external_contact_id,</if>" +
+            "<if test='failMsg != null'>fail_msg,</if>" +
+            "<if test='payload != null'>payload,</if>" +
+            "<if test='response != null'>response,</if>" +
+            "<if test='createTime != null'>create_time,</if>" +
+            "<if test='updateTime != null'>update_time,</if>" +
+            "<if test='updateBy != null'>update_by,</if>" +
+            "<if test='createBy != null'>create_by,</if>" +
+            "<if test='logType != null'>log_type,</if>" +
+            "</trim>" +
+            "<trim prefix='VALUES (' suffix=')' suffixOverrides=','>" +
+            "<if test='courseLogId != null'>#{courseLogId},</if>" +
+            "<if test='courseId != null'>#{courseId},</if>" +
+            "<if test='tagId != null'>#{tagId},</if>" +
+            "<if test='tagName != null'>#{tagName},</if>" +
+            "<if test='operationType != null'>#{operationType},</if>" +
+            "<if test='videoId != null'>#{videoId},</if>" +
+            "<if test='status != null'>#{status},</if>" +
+            "<if test='retryCount != null'>#{retryCount},</if>" +
+            "<if test='corpId != null'>#{corpId},</if>" +
+            "<if test='qwExternalContactId != null'>#{qwExternalContactId},</if>" +
+            "<if test='qwUserId != null'>#{qwUserId},</if>" +
+            "<if test='failMsg != null'>#{failMsg},</if>" +
+            "<if test='payload != null'>#{payload},</if>" +
+            "<if test='response != null'>#{response},</if>" +
+            "<if test='createTime != null'>#{createTime},</if>" +
+            "<if test='updateTime != null'>#{updateTime},</if>" +
+            "<if test='updateBy != null'>#{updateBy},</if>" +
+            "<if test='createBy != null'>#{createBy},</if>" +
+            "<if test='log_type != null'>#{logType},</if>" +
+            "</trim>" +
+            "</script>")
+    @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
+    int insertSelective(FsTagUpdateQueue record);
+
+
+
+    @Insert("<script>" +
+            "INSERT IGNORE INTO fs_tag_update_queue (" +
+            "course_log_id, is_first, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, " +
+            "corp_id, qw_user_id, qw_external_contact_id, fail_msg, payload, response, create_time, update_time, update_by, create_by, log_type,tg_id,watching_tg_id,watched_tg_id,watching_tag_id,watched_tag_id,tag_group_id" +
+            ") VALUES " +
+            "<foreach collection='list' item='item' separator=','>" +
+            "(" +
+            "#{item.courseLogId}, #{item.isFirst}, #{item.courseId}, #{item.tagId}, #{item.tagName}, #{item.operationType}, #{item.videoId}, #{item.status}, #{item.retryCount}, " +
+            "#{item.corpId}, #{item.qwUserId}, #{item.qwExternalContactId}, #{item.failMsg}, #{item.payload}, #{item.response}, #{item.createTime}," +
+            " #{item.updateTime}, #{item.updateBy}, #{item.createBy}, #{item.logType},#{item.tgId},#{item.watchingTgId},#{item.watchedTgId},#{item.watchingTagId},#{item.watchedTagId},#{item.tagGroupId}" +
+            ")" +
+            "</foreach>" +
+            "</script>")
+    int batchInsert(@Param("list") List<FsTagUpdateQueue> list);
+
+
+    @Update("<script>" +
+            "UPDATE fs_tag_update_queue " +
+            "<set>" +
+            "<if test='courseLogId != null'>course_log_id = #{courseLogId},</if>" +
+            "<if test='courseId != null'>course_id = #{courseId},</if>" +
+            "<if test='tagId != null'>tag_id = #{tagId},</if>" +
+            "<if test='tagName != null'>tag_name = #{tagName},</if>" +
+            "<if test='operationType != null'>operation_type = #{operationType},</if>" +
+            "<if test='videoId != null'>video_id = #{videoId},</if>" +
+            "<if test='status != null'>status = #{status},</if>" +
+            "<if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "<if test='corpId != null'>corp_id = #{corpId},</if>" +
+            "<if test='qwUserId != null'>qw_user_id = #{qwUserId},</if>" +
+            "<if test='failMsg != null'>fail_msg = #{failMsg},</if>" +
+            "<if test='payload != null'>payload = #{payload},</if>" +
+            "<if test='response != null'>response = #{response},</if>" +
+            "<if test='createTime != null'>create_time = #{createTime},</if>" +
+            "<if test='updateTime != null'>update_time = #{updateTime},</if>" +
+            "<if test='updateBy != null'>update_by = #{updateBy},</if>" +
+            "<if test='createBy != null'>create_by = #{createBy},</if>" +
+            "<if test='logType != null'>log_type = #{logType},</if>" +
+            "</set> " +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateSelective(FsTagUpdateQueue record);
+
+
+
+    @Update("<script>" +
+            "<foreach collection='list' item='item' separator=';'>" +
+            "UPDATE fs_tag_update_queue" +
+            "<set>" +
+            "<if test='item.courseLogId != null'>course_log_id = #{item.courseLogId},</if>" +
+            "<if test='item.isFirst != null'>is_first = #{item.isFirst},</if>" +
+            "<if test='item.courseId != null'>course_id = #{item.courseId},</if>" +
+            "<if test='item.tagId != null'>tag_id = #{item.tagId},</if>" +
+            "<if test='item.tagName != null'>tag_name = #{item.tagName},</if>" +
+            "<if test='item.operationType != null'>operation_type = #{item.operationType},</if>" +
+            "<if test='item.videoId != null'>video_id = #{item.videoId},</if>" +
+            "<if test='item.status != null'>status = #{item.status},</if>" +
+            "<if test='item.retryCount != null'>retry_count = #{item.retryCount},</if>" +
+            "<if test='item.corpId != null'>corp_id = #{item.corpId},</if>" +
+            "<if test='item.qwUserId != null'>qw_user_id = #{item.qwUserId},</if>" +
+            "<if test='item.qwExternalContactId != null'>qw_external_contact_id = #{item.qwExternalContactId},</if>" +
+            "<if test='item.failMsg != null'>fail_msg = #{item.failMsg},</if>" +
+            "<if test='item.payload != null'>payload = #{item.payload},</if>" +
+            "<if test='item.response != null'>response = #{item.response},</if>" +
+            "<if test='item.createTime != null'>create_time = #{item.createTime},</if>" +
+            "<if test='item.updateTime != null'>update_time = #{item.updateTime},</if>" +
+            "<if test='item.updateBy != null'>update_by = #{item.updateBy},</if>" +
+            "<if test='item.createBy != null'>create_by = #{item.createBy},</if>" +
+            "<if test='item.logType != null'>log_type = #{item.logType},</if>" +
+            "<if test='item.nextExecuteTime != null'>next_execute_time = #{item.nextExecuteTime},</if>" +
+            "</set>" +
+            " WHERE id = #{item.id}" +
+            "</foreach>" +
+            "</script>")
+    int batchUpdateSelective(@Param("list") List<FsTagUpdateQueue> list);
+
+}

+ 29 - 0
fs-service/src/main/java/com/fs/tag/service/FsTagUpdateService.java

@@ -0,0 +1,29 @@
+package com.fs.tag.service;
+
+import com.fs.course.domain.FsCourseWatchLog;
+
+import java.util.List;
+
+/**
+ * 完课打备注服务类
+ */
+public interface FsTagUpdateService {
+
+    /**
+     * 正课正在看课
+     * @param logs 日志
+     */
+    void onCourseWatchingBatch(List<FsCourseWatchLog> logs);
+
+    /**
+     * 正课完成看课
+     * @param logs 日志
+     */
+    void onCourseWatchFinishedBatch(List<FsCourseWatchLog> logs);
+
+
+    /**
+     * 处理数据
+     */
+    void handleData();
+}

+ 318 - 0
fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java

@@ -0,0 +1,318 @@
+package com.fs.tag.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsUserCourseMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.qw.cache.IQwUserCacheService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwTag;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwTagGroupMapper;
+import com.fs.qw.mapper.QwTagMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.vo.QwTagGroupAddParam;
+import com.fs.qw.vo.QwTagVO;
+import com.fs.qwApi.domain.QwAddTagResult;
+import com.fs.qwApi.domain.QwResult;
+import com.fs.qwApi.domain.inner.InTag;
+import com.fs.qwApi.domain.inner.TagData;
+import com.fs.qwApi.param.QwAddTagParam;
+import com.fs.qwApi.param.QwEditUserTagParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.tag.domain.FsTagUpdateQueue;
+import com.fs.tag.mapper.FsTagUpdateQueueMapper;
+import com.fs.tag.service.FsTagUpdateService;
+import com.google.common.util.concurrent.RateLimiter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.http.util.Asserts;
+import org.checkerframework.checker.signature.qual.PolySignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Slf4j
+@Service("fsTagUpdateService")
+public class FsTagUpdateServiceImpl implements FsTagUpdateService {
+
+
+    @Autowired
+    private FsTagUpdateQueueMapper fsTagUpdateQueueMapper;
+    @Autowired
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    private IQwUserCacheService qwUserCacheService;
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private QwTagMapper qwTagMapper;
+
+    @Autowired
+    private QwTagGroupMapper qwTagGroupMapper;
+
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
+    @Autowired
+    private FsUserCourseMapper fsUserCourseMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Value("${tag.thread.num:5}")
+    private Integer TAG_THREAD_NUM;
+
+    @Value("${tag.rate.limit:30}")
+    private Integer RATE_LIMIT_NUM;
+    /**
+     * 标签组最大数量
+     */
+    private static final Integer TAG_MAX_NUM = 100;
+
+    /**
+     * 接口限流
+     */
+    private RateLimiter rateLimiter;
+
+    /**
+     * 看课自动打标签开关
+     */
+    @Value("${qw.enableAutoTag:0}")
+    private Integer enableAutoTag;
+
+    @PostConstruct
+    public void init(){
+        this.rateLimiter = RateLimiter.create(RATE_LIMIT_NUM);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void onCourseWatchingBatch(List<FsCourseWatchLog> logs) {
+
+        if(ObjectUtil.equal(enableAutoTag,0)){
+            return;
+        }
+        Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
+        // 用户(这里用户用的是企微外部联系人ID)+videoId+status 唯一
+
+        // 先导课看课记录
+        List<FsTagUpdateQueue> batchData = new ArrayList<>();
+        for (FsCourseWatchLog item : logs) {
+            FsTagUpdateQueue task = new FsTagUpdateQueue();
+            task.setCourseId(item.getCourseId());
+            task.setVideoId(item.getVideoId());
+            task.setCourseLogId(item.getLogId());
+            task.setTagId(null);
+            task.setTagName(null);
+
+            task.setLogType(0);
+            task.setOperationType(0);
+            task.setStatus(0);
+            task.setRetryCount(0);
+            task.setQwExternalContactId(item.getQwExternalContactId());
+            task.setQwUserId(item.getQwUserId());
+
+            FsUserCourseVideo fsUserCourseVideo = courseVideoMap.get(task.getVideoId());
+            String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
+            if(StringUtils.isNotNull(corpId)){
+                task.setCorpId(corpId);
+            }
+            if(ObjectUtils.isNull(fsUserCourseVideo)) {
+                String errorMsg = String.format("该条记录 %d 找不到对应的课堂视频", task.getVideoId());
+                log.error(errorMsg);
+                task.setStatus(3);
+                task.setRetryCount(3);
+                task.setFailMsg(errorMsg);
+                batchData.add(task);
+                continue;
+            }
+            task.setTagGroupId(fsUserCourseVideo.getTagGroupId());
+            task.setTgId(fsUserCourseVideo.getTgId());
+            task.setWatchingTagId(fsUserCourseVideo.getWatchingTagId());
+            task.setWatchedTagId(fsUserCourseVideo.getWatchedTagId());
+            task.setWatchingTgId(fsUserCourseVideo.getWatchingTgId());
+            task.setWatchedTgId(fsUserCourseVideo.getWatchedTgId());
+
+            if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
+                task.setIsFirst(1);
+            } else {
+                task.setIsFirst(0);
+            }
+            task.setSort(fsUserCourseVideo.getCourseSort());
+            batchData.add(task);
+        }
+
+
+        fsTagUpdateQueueMapper.batchInsert(batchData);
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void onCourseWatchFinishedBatch(List<FsCourseWatchLog> logs) {
+        if(ObjectUtil.equal(enableAutoTag,0)){
+            return;
+        }
+        Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
+
+        // 先导课看课记录
+        List<FsTagUpdateQueue> batchData = new ArrayList<>();
+        for (FsCourseWatchLog item : logs) {
+            FsTagUpdateQueue task = new FsTagUpdateQueue();
+            task.setCourseId(item.getCourseId());
+            task.setVideoId(item.getVideoId());
+            task.setCourseLogId(item.getLogId());
+            task.setTagId(null);
+            task.setTagName(null);
+            task.setOperationType(0);
+            task.setStatus(0);
+            task.setRetryCount(0);
+            task.setQwExternalContactId(item.getQwExternalContactId());
+            task.setQwUserId(item.getQwUserId());
+            task.setLogType(1);
+            String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
+            if(StringUtils.isNotNull(corpId)){
+                task.setCorpId(corpId);
+            }
+            FsUserCourseVideo fsUserCourseVideo = courseVideoMap.get(task.getVideoId());
+            if(ObjectUtils.isNull(fsUserCourseVideo)) {
+                String errorMsg = String.format("该条记录 %d 找不到对应的课堂视频", task.getVideoId());
+                log.error(errorMsg);
+                task.setStatus(3);
+                task.setRetryCount(3);
+                task.setFailMsg(errorMsg);
+                batchData.add(task);
+                continue;
+            }
+            task.setTagGroupId(fsUserCourseVideo.getTagGroupId());
+            task.setTgId(fsUserCourseVideo.getTgId());
+            task.setWatchingTagId(fsUserCourseVideo.getWatchingTagId());
+            task.setWatchedTagId(fsUserCourseVideo.getWatchedTagId());
+            task.setWatchingTgId(fsUserCourseVideo.getWatchingTgId());
+            task.setWatchedTgId(fsUserCourseVideo.getWatchedTgId());
+
+            if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
+                task.setIsFirst(1);
+            } else {
+                task.setIsFirst(0);
+            }
+            task.setSort(fsUserCourseVideo.getCourseSort());
+            batchData.add(task);
+        }
+
+
+        fsTagUpdateQueueMapper.batchInsert(batchData);
+    }
+
+    @Override
+    public void handleData() {
+        List<FsTagUpdateQueue> tasks = fsTagUpdateQueueMapper.selectPending();
+        if(CollectionUtils.isEmpty(tasks)){
+            log.info("找不到可处理的任务,已跳过!");
+            return;
+        }
+        ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
+        ExecutorService executor = Executors.newFixedThreadPool(TAG_THREAD_NUM);
+        CountDownLatch latch = new CountDownLatch(tasks.size());
+
+        for (FsTagUpdateQueue task : tasks) {
+            executor.submit(() -> {
+                String lockKey = task.getCourseId() + "_" + task.getVideoId();
+                ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
+                lock.lock();
+                try {
+                    processSingleTask(task);
+                } finally {
+                    lock.unlock();
+                    latch.countDown();
+                }
+            });
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        try {
+            executor.shutdown();
+            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
+                executor.shutdownNow();
+                // 再次等待确保已经关闭
+                if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                    log.warn("线程池未能在预期时间内关闭");
+                }
+            }
+        } catch (InterruptedException e) {
+            executor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+
+        if(CollectionUtils.isNotEmpty(tasks)){
+            fsTagUpdateQueueMapper.batchUpdateSelective(tasks);
+        }
+
+    }
+
+    private void processSingleTask(FsTagUpdateQueue fsTagUpdateQueue) {
+        try {
+            // 调用企微API更新标签
+            QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+            QwExternalContact qwExternalContact = qwExternalContactMapper
+                    .selectQwExternalContactById(fsTagUpdateQueue.getQwExternalContactId());
+            if(qwExternalContact == null) {
+                throw new IllegalArgumentException(String.format("企微外部联系人 %s 未找到!", fsTagUpdateQueue.getQwExternalContactId()));
+            }
+            qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+            qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+            rateLimiter.acquire();
+
+            // 如果是看课中
+            if(ObjectUtil.equal(fsTagUpdateQueue.getLogType(),0)){
+                qwEditUserTagParam.setAdd_tag(Collections.singletonList(fsTagUpdateQueue.getWatchingTagId()));
+            } else {
+                // 已完课
+                qwEditUserTagParam.setAdd_tag(Collections.singletonList(fsTagUpdateQueue.getWatchedTagId()));
+                qwEditUserTagParam.setRemove_tag(Collections.singletonList(fsTagUpdateQueue.getWatchingTagId()));
+            }
+
+            QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, fsTagUpdateQueue.getCorpId());
+            fsTagUpdateQueue.setPayload(JSON.toJSONString(qwEditUserTagParam));
+            fsTagUpdateQueue.setResponse(JSON.toJSONString(qwResult));
+            // 打标签成功
+            if(ObjectUtil.equal(qwResult.getErrcode(),0)) {
+                fsTagUpdateQueue.setStatus(2);
+                fsTagUpdateQueue.setRetryCount(0);
+            } else {
+                throw new RuntimeException(String.format("打标签失败 原因: %s", JSON.toJSONString(qwResult)));
+            }
+        } catch (Exception e){
+            fsTagUpdateQueue.setStatus(3);
+            fsTagUpdateQueue.setRetryCount(fsTagUpdateQueue.getRetryCount()+1);
+            fsTagUpdateQueue.setFailMsg(ExceptionUtils.getFullStackTrace(e));
+            fsTagUpdateQueue.setNextExecuteTime(LocalDateTime.now().plusHours(1));
+        }
+    }
+
+
+}

+ 6 - 0
fs-service/src/main/resources/application-config-druid-cqtyt.yml

@@ -65,6 +65,12 @@ watch:
 fs :
   commonApi: http://172.16.0.9:8010
   h5CommonApi: http://192.168.0.18:7771
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b67cde581c0f9eb1
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
 nuonuo:
   key: 10924508
   secret: A2EB20764D304D16

+ 6 - 0
fs-service/src/main/resources/application-config-druid-fby.yml

@@ -81,6 +81,12 @@ fs :
 nuonuo:
   key: 10924508
   secret: A2EB20764D304D16
+  jwt:
+    # 加密秘钥
+    secret: f4e2e52034348f86b67cde581c0f9eb5
+    # token有效时长,7天,单位秒
+    expire: 31536000
+    header: AppToken
 
 # 存储捅配置
 tencent_cloud_config:

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

@@ -29,7 +29,7 @@ wx:
     subAppId:  #服务商模式下的子商户公众账号ID
     subMchId:  #服务商模式下的子商户号
     keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
-    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+    notifyUrl: https://userapp.yytcdta.com/app/wxpay/wxPayNotify
   mp:
     useRedis: false
     redisConfig:

+ 7 - 0
fs-service/src/main/resources/application-config-druid-jnlzjk.yml

@@ -98,5 +98,12 @@ ipad:
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:
+qw:
+  enableAutoTag: 1
+tag:
+  thread:
+    num: 5
+  rate:
+    limit: 30
 
 

+ 1 - 1
fs-service/src/main/resources/application-dev.yml

@@ -43,7 +43,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
                     username: Rtroot
                     password: Rtroot
                 # 初始连接数

+ 4 - 2
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         u.qw_status, u.phonenumber, u.create_time, u.dept_id,
         u.qr_code_weixin, u.user_type, u.qr_code_wecom, u.jpush_id,
         u.is_need_register_member,u.is_allowed_all_register,
-        u.avatar,
+        u.avatar,u.address_id,
         u.qw_user_id,
         d.dept_name,
         d.leader
@@ -431,7 +431,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <sql id="selectUserVo">
-        select u.user_id,u.company_id,u.qw_user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time,u.id_card, u.remark,u.user_type,u.open_id,u.qr_code_weixin,u.qr_code_wecom,u.jpush_id,u.domain,u.is_audit,u.address_id,
+        select u.user_id,u.company_id,u.qw_user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar,
+               u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by,
+               u.create_time,u.id_card, u.remark,u.user_type,u.open_id,u.qr_code_weixin,u.qr_code_wecom,u.jpush_id,u.domain,u.is_audit,u.address_id,
                d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
                r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,
                u.is_need_register_member, u.is_allowed_all_register,u.doctor_id

+ 65 - 0
fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml

@@ -0,0 +1,65 @@
+<?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.StatisticManageMapper">
+
+    <resultMap id="CompanyDeptUserListMap" type="com.fs.company.domain.CompanyDeptUserInfo">
+        <id column="company_id" property="companyId" />
+        <result column="company_name" property="companyName"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="dept_name" property="deptName"/>
+        <result column="user_id" property="userId"/>
+        <result column="user_name" property="userName"/>
+        <result column="nick_name" property="nickName"/>
+    </resultMap>
+
+    <select id="getCompanyAndDeptAndDeptUserList" resultMap="CompanyDeptUserListMap">
+        SELECT
+            ci.company_id,
+            ci.company_name,
+            cd.dept_id,
+            cd.dept_name,
+            cu.user_id,
+            cu.user_name,
+            cu.nick_name
+        FROM company AS ci
+        LEFT JOIN company_dept AS cd ON ci.company_id = cd.company_id AND cd.STATUS = 0
+        LEFT JOIN company_user AS cu ON cu.dept_id = cd.dept_id AND cu.del_flag = 0
+        WHERE ci.is_del = 0 AND cd.del_flag = 0
+    </select>
+
+    <select id="getStatisticNum" resultType="com.fs.company.dto.CompanyDeptUserInfoDTO">
+        with t1 as (
+            SELECT
+            qec.id,
+            qec.company_user_id,
+            qec.fs_user_id,
+            qec.qw_user_id,
+            qec.user_id
+            FROM
+            qw_external_contact AS qec
+            WHERE
+            date_format(qec.create_time,'%y%m%d') >= date_format(now(),'%y%m%d') and
+            qec.create_time &lt;= now()
+            and qec.company_user_id IN (
+            <foreach collection="userIds" item="i" separator=",">
+                #{i}
+            </foreach>
+            )
+        ),t2 as (
+            select count(t1.id) as lineNum from t1
+        ),t3 as (
+            select count(t1.id) as activeNum from t1 where t1.fs_user_id is not null
+        ),t4 as(
+            select count(fcwl.qw_external_contact_id) as completeNum from t1 inner join fs_course_watch_log as fcwl on t1.id = fcwl.qw_external_contact_id where fcwl.log_type = 2
+        ),t5 as (
+            select count(fcal.log_id) as answerNum  from t1 inner join fs_course_answer_logs as fcal where fcal.user_id = t1.user_id
+        ), t6 as (
+            select count(fcrpl.log_id) as redPacketNum  from t1 inner join fs_course_red_packet_log as fcrpl where fcrpl.user_id = t1.user_id
+        )
+        select t2.lineNum, t3.activeNum, t4.completeNum, t5.answerNum, t6.redPacketNum from t2, t3, t4, t5, t6
+    </select>
+
+
+</mapper>

+ 13 - 6
fs-service/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.course.mapper.FsCourseFinishTempParentMapper">
-    
+
     <resultMap type="FsCourseFinishTempParent" id="FsCourseFinishTempParentResult">
         <result property="id"    column="id"    />
         <result property="name"    column="name"    />
@@ -22,18 +22,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectFsCourseFinishTempParentList" parameterType="FsCourseFinishTempParent" resultMap="FsCourseFinishTempParentResult">
         <include refid="selectFsCourseFinishTempParentVo"/>
-        <where>  
+        <where>
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
             <if test="courseId != null "> and course_id = #{courseId}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="createBy != null "> and create_by = #{createBy}</if>
+            <if test="userIds != null and !userIds.isEmpty() ">
+                AND create_by IN
+                 <foreach collection='userIds' item='item' open='(' separator=',' close=')'>
+                 #{item}
+                </foreach>
+             </if>
         </where>
     </select>
-    
+
     <select id="selectFsCourseFinishTempParentById" parameterType="Long" resultMap="FsCourseFinishTempParentResult">
         <include refid="selectFsCourseFinishTempParentVo"/>
         where id = #{id}
     </select>
-        
+
     <insert id="insertFsCourseFinishTempParent" parameterType="FsCourseFinishTempParent" useGeneratedKeys="true" keyProperty="id">
         insert into fs_course_finish_temp_parent
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -75,9 +82,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteFsCourseFinishTempParentByIds" parameterType="String">
-        delete from fs_course_finish_temp_parent where id in 
+        delete from fs_course_finish_temp_parent where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
-</mapper>
+</mapper>

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

@@ -233,6 +233,12 @@
             <if test="listingEndTime != null">listing_end_time = #{listingEndTime},</if>
             <if test="projectId != null">project_id = #{projectId},</if>
             <if test="isFirst != null">is_first = #{isFirst},</if>
+            <if test="tagGroupId != null">tag_group_id = #{tagGroupId},</if>
+            <if test="watchingTagId != null">watching_tag_id = #{watchingTagId},</if>
+            <if test="watchedTagId != null">watched_tag_id = #{watchedTagId},</if>
+            <if test="tgId != null">tg_id = #{tgId},</if>
+            <if test="watchingTgId != null">watching_tg_id = #{watchingTgId},</if>
+            <if test="watchedTgId != null">watched_tg_id = #{watchedTgId},</if>
         </trim>
         where video_id = #{videoId}
     </update>

+ 14 - 0
fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml

@@ -26,4 +26,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
         order by rr.sort,rr.id desc
     </select>
+
+    <select id="selectByIds" parameterType="String" resultType="com.fs.course.domain.FsVideoResource">
+        SELECT *
+        FROM fs_video_resource
+        WHERE id IN
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+        ORDER BY FIELD(id,
+        <foreach collection="ids" item="id" separator=",">
+            #{id}
+        </foreach>
+        )
+    </select>
 </mapper>

+ 3 - 0
fs-service/src/main/resources/mapper/his/FsPackageOrderMapper.xml

@@ -317,4 +317,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             LEFT JOIN fs_package P ON O.package_id = P.package_id
         where O.is_pay = 1 ORDER BY RAND() desc LIMIT 20
     </select>
+    <select id="selectOutTimeOrderList" resultType="com.fs.his.domain.FsPackageOrder">
+        select * from fs_package_order  where status = 1 AND NOW() &gt; DATE_ADD(create_time, INTERVAL ${unPayTime} MINUTE)
+    </select>
 </mapper>

+ 4 - 0
fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml

@@ -2133,5 +2133,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND company_user_id = #{companyUserId}
         </if>
     </select>
+    <select id="selectOutTimeOrderList" resultType="com.fs.his.domain.FsStoreOrder">
+        <include refid="selectFsStoreOrderVo"/>
+        where is_del = 0 and status = 1 and NOW() &gt; DATE_ADD(create_time, INTERVAL ${unPayTime} MINUTE)
+    </select>
 
 </mapper>

+ 2 - 0
fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml

@@ -58,6 +58,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="storeId != null "> and store_id = #{storeId}</if>
             <if test="businessCode != null "> and business_code = #{businessCode}</if>
             <if test="appId != null and appId !=''"> and app_id = #{appId}</if>
+            <if test="beginTime != null and beginTime !=''"> and create_time >= #{beginTime}</if>
+            <if test="endTime != null and endTime !=''"> and create_time &lt; #{endTime}</if>
         </where>
     </select>
 

+ 29 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -894,6 +894,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         fs_user.user_id
     </select>
 
+    <select id="getUserTotal" resultType="java.lang.Integer">
+        SELECT count(fs_user.user_id) as userTotal
+        FROM fs_user
+        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
+        LEFT JOIN company_user ON ucu.company_user_id = company_user.user_id
+        WHERE fs_user.is_del = 0
+        <if test="userId != null and userId != 0 ">
+            and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
+        </if>
+        <if test="companyId != null ">
+            and ucu.company_id = #{companyId}
+        </if>
+    </select>
+
+    <select id="getTodayNewUser" resultType="java.lang.Integer">
+        SELECT count(fs_user.user_id) as todayNewUser
+        FROM fs_user
+        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
+        LEFT JOIN company_user ON ucu.company_user_id = company_user.user_id
+        WHERE fs_user.is_del = 0
+        <if test="userId != null and userId != 0 ">
+            and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
+        </if>
+        <if test="companyId != null ">
+            and ucu.company_id = #{companyId}
+        </if>
+        AND to_days(ucu.create_time) = to_days(now())
+    </select>
+
     <select id="countUserSummary" resultType="com.fs.store.vo.h5.FsUserSummaryCountVO">
         SELECT (SELECT count(fs_user.user_id)
         FROM fs_user

+ 3 - 0
fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml

@@ -108,4 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      </where>
         )
     </select>
+    <select id="selectQwTagGroupByName" resultType="com.fs.qw.domain.QwTagGroup">
+        select * from qw_tag_group where name=#{tagGroup} and corp_id=#{corpId} limit 1
+    </select>
 </mapper>

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

@@ -294,6 +294,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and qu.qw_user_name like concat('%', #{nickName}, '%')
         </if>
     </select>
+    <!--  递归查询子部门,不包括自己,深度5   -->
+    <select id="selectDeptByParentId" resultType="java.lang.Long">
+        WITH RECURSIVE sub_dept AS (
+        SELECT dept_id, parentid, 1 as depth
+        FROM qw_dept
+        WHERE parentid = #{deptId} and corp_id=#{corpId}
+        UNION ALL
+        SELECT qd.dept_id, qd.parentid, sd.depth + 1
+        FROM qw_dept qd
+        INNER JOIN sub_dept sd ON qd.parentid = sd.dept_id
+        WHERE sd.depth &lt; 5 and qd.corp_id=#{corpId}
+        )
+        SELECT dept_id FROM sub_dept
+    </select>
 
     <select id="selectQwUserVoListByCompanyUserId" resultType="com.fs.qw.vo.QwUserVO">
         select

+ 0 - 17
fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java

@@ -1,17 +0,0 @@
-package com.fs.his.service.impl;
-
-
-import com.fs.tulin.service.ITulinInfoSyncLogService;
-
-import javax.annotation.Resource;
-
-class FsUserServiceImplTest {
-
-    @Resource
-    private ITulinInfoSyncLogService tulinInfoSyncLogService;
-
-
-    void selectFsUserPageListNew() {
-        tulinInfoSyncLogService.syncInfo();
-    }
-}

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

@@ -4,7 +4,6 @@ package com.fs.app.controller;
 import cn.hutool.core.lang.Validator;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.app.annotation.Login;
-import com.fs.app.param.FriendsSearchParam;
 import com.fs.app.param.FsDoctorRegisterParam;
 import com.fs.app.param.FsUserEditParam;
 import com.fs.common.core.domain.R;
@@ -12,10 +11,8 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.sign.Md5Utils;
-import com.fs.company.domain.CompanyUser;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.his.domain.FsDoctor;
-import com.fs.his.domain.FsPackage;
 import com.fs.his.domain.FsUser;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
@@ -26,8 +23,6 @@ import com.fs.his.service.IFsPackageService;
 import com.fs.his.service.IFsUserCouponService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.PhoneUtil;
-import com.fs.his.utils.qrcode.QRCodeUtils;
-import com.fs.his.vo.FsDoctorListUVO;
 import com.fs.his.vo.FsUserCouponCountUVO;
 import com.fs.his.vo.FsUserCouponListUVO;
 import com.fs.his.vo.UserVo;
@@ -36,8 +31,6 @@ import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
-import com.google.zxing.WriterException;
-import io.jsonwebtoken.lang.Assert;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -45,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.parameters.P;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -62,7 +54,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static com.fs.common.utils.SecurityUtils.getUserId;
 import static com.fs.his.utils.PhoneUtil.decryptPhoneMk;
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
 
@@ -219,7 +210,8 @@ public class UserController extends  AppBaseController {
         FsUser user=new FsUser();
         user.setUserId(Long.parseLong(getUserId()));
         user.setAvatar(param.getAvatar());
-        user.setNickName(param.getNickname());
+        // 前端传了nickname 和 nickName 但是只有nickName是正确的
+        user.setNickName(StringUtils.isNotEmpty(param.getNickName())?param.getNickName():param.getNickname());
         if (ObjectUtils.isNotEmpty(param.getIsWeixinAuth())){
             user.setIsWeixinAuth(param.getIsWeixinAuth());
         }

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -95,6 +95,7 @@ public class CourseFsUserController extends AppBaseController {
 
     @ApiOperation("更新看课时长")
     @PostMapping("/updateWatchDuration")
+    @Login
     public R updateWatchDuration(@RequestBody FsUserCourseVideoUParam param)
     {
         param.setUserId(Long.parseLong(getUserId()));

+ 13 - 2
fs-user-app/src/main/java/com/fs/app/controller/store/CompanyUserScrmController.java

@@ -4,6 +4,7 @@ package com.fs.app.controller.store;
 import cn.hutool.core.img.ImgUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.extra.qrcode.QrCodeUtil;
+import cn.hutool.json.JSONUtil;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.app.param.FsBindCompanyUserParam;
@@ -21,6 +22,8 @@ import com.fs.company.service.ICompanyUserCardService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.service.ICompanyUserUserService;
 import com.fs.framework.security.SecurityUtils;
+import com.fs.his.dto.PayConfigDTO;
+import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.io.FileUtils;
@@ -40,11 +43,13 @@ import java.util.concurrent.TimeUnit;
 @RestController
 @RequestMapping(value="/store/app/companyUser")
 public class CompanyUserScrmController extends AppBaseController {
-
+    @Autowired
+    private ISysConfigService configService;
     @Autowired
     private FSConfig fsConfig;
     @Autowired
     RedisCache redisCache;
+
     @Autowired
     private ICompanyUserService companyUserService;
     @Autowired
@@ -201,7 +206,13 @@ public class CompanyUserScrmController extends AppBaseController {
 
     @ApiOperation("获取公司收款码")
     @GetMapping("/getCompanyWxaCodeByPayment")
-    public R getCompanyWxaCodeByPayment(@RequestParam("companyId")Long companyId,@RequestParam("appId")String appId,HttpServletRequest request){
+    public R getCompanyWxaCodeByPayment(@RequestParam("companyId")Long companyId,@RequestParam(value = "appId", required = false) String appId,HttpServletRequest request){
+
+        String json = configService.selectConfigByKey("his.pay");
+        PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+        if (appId == null || appId.trim().isEmpty()) {
+            appId = payConfigDTO.getAppId();
+        }
         //获取用户码
         String WxaCode = redisCache.getCacheObject("company-wxa-code:"+companyId+":"+appId);
         return R.ok().put("data",WxaCode);

+ 57 - 3
fs-user-app/src/main/java/com/fs/app/param/FsUserEditParam.java

@@ -9,17 +9,71 @@ import lombok.Setter;
 import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
-@Data
+
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class FsUserEditParam implements Serializable {
-
     @NotNull(message = "用户昵称不能为空!")
-    @JsonAlias({"nickname", "nickName"})
+    @JsonAlias("nickname")
     private String nickname;
 
     //    @NotBlank(message = "用户头像不能为空!")
     private String avatar;
 
+    @JsonAlias("nickName")
+    private String nickName;
+
     private Long userId;
     private Integer isWeixinAuth;
+
+
+    public @NotNull(message = "用户昵称不能为空!") String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(@NotNull(message = "用户昵称不能为空!") String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Integer getIsWeixinAuth() {
+        return isWeixinAuth;
+    }
+
+    public void setIsWeixinAuth(Integer isWeixinAuth) {
+        this.isWeixinAuth = isWeixinAuth;
+    }
+
+    @Override
+    public String toString() {
+        return "FsUserEditParam{" +
+                "nickname='" + nickname + '\'' +
+                ", avatar='" + avatar + '\'' +
+                ", nickName='" + nickName + '\'' +
+                ", userId=" + userId +
+                ", isWeixinAuth=" + isWeixinAuth +
+                '}';
+    }
 }