Kaynağa Gözat

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

# Conflicts:
#	fs-admin/src/main/resources/application.yml
#	fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
#	fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
#	fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
#	fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
#	fs-service/src/main/java/com/fs/hisStore/config/StoreConfig.java
#	fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
#	fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
#	fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
#	fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
#	fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java
#	fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java
#	fs-service/src/main/java/com/fs/tag/service/FsTagUpdateService.java
#	fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
#	fs-service/src/main/resources/mapper/his/FsUserMapper.xml
#	fs-user-app/src/main/java/com/fs/app/controller/store/CompanyUserScrmController.java
xw 1 hafta önce
ebeveyn
işleme
e6852e5e49
100 değiştirilmiş dosya ile 2387 ekleme ve 210 silme
  1. 24 0
      README.md
  2. 3 1
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  3. 62 0
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  4. 14 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyDeductController.java
  5. 11 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyRechargeController.java
  6. 4 1
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  7. 34 2
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  8. 42 33
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  9. 96 1
      fs-admin/src/main/java/com/fs/his/task/Task.java
  10. 99 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsUserPromoterApplyController.java
  11. 28 0
      fs-admin/src/main/java/com/fs/task/ComprehensiveStatisticsTask.java
  12. 54 2
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  13. 9 0
      fs-admin/src/main/java/com/fs/task/SgTestController.java
  14. 1 0
      fs-admin/src/main/resources/logback.xml
  15. 37 0
      fs-admin/src/test/java/com/fs/api/controller/IndexStatisticsControllerTest.java
  16. 6 5
      fs-common/pom.xml
  17. 52 0
      fs-common/src/main/java/com/fs/common/enums/DimensionEnum.java
  18. 3 1
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  19. 5 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  20. 1 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java
  21. 118 5
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempParentController.java
  22. 10 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseVideoController.java
  23. 19 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwDeptController.java
  24. 47 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  25. 104 0
      fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java
  26. 5 0
      fs-ipad-task/pom.xml
  27. 10 10
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  28. 52 0
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  29. 2 2
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  30. 39 6
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  31. 1 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  32. 1 1
      fs-qw-voice/pom.xml
  33. 2 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  34. 48 0
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptUserInfo.java
  35. 50 0
      fs-service/src/main/java/com/fs/company/domain/ComprehensiveDailyStats.java
  36. 57 0
      fs-service/src/main/java/com/fs/company/dto/CompanyDeptUserInfoDTO.java
  37. 42 0
      fs-service/src/main/java/com/fs/company/dto/ComprehensiveStatisticsDTO.java
  38. 7 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  39. 6 4
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  40. 12 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  41. 108 0
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  42. 3 0
      fs-service/src/main/java/com/fs/company/param/companyUserAddPrintParam.java
  43. 9 0
      fs-service/src/main/java/com/fs/company/service/ICompanyRechargeService.java
  44. 6 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  45. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  46. 44 0
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  47. 67 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRechargeServiceImpl.java
  48. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  49. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  50. 183 0
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  51. 1 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  52. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java
  53. 4 4
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  54. 19 5
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  55. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  56. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsVideoResourceMapper.java
  57. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  58. 3 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java
  59. 5 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  60. 7 26
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  61. 2 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  62. 68 17
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  63. 5 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  64. 58 0
      fs-service/src/main/java/com/fs/his/dto/FsUserDTO.java
  65. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsPackageOrderMapper.java
  66. 8 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  67. 16 1
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  68. 45 26
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  69. 5 1
      fs-service/src/main/java/com/fs/his/param/FsPackageCateUParam.java
  70. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsPackageOrderService.java
  71. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  72. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  73. 7 2
      fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java
  74. 2 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  75. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  76. 21 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  77. 162 4
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  78. 7 5
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  79. 6 4
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  80. 1 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  81. 1 1
      fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
  82. 47 0
      fs-service/src/main/java/com/fs/qw/domain/QwDeptTreeSelect.java
  83. 3 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  84. 0 1
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  85. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  86. 10 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  87. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  88. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java
  89. 18 2
      fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java
  90. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwDeptService.java
  91. 1 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  92. 69 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwDeptServiceImpl.java
  93. 25 15
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  94. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  95. 1 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  96. 12 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  97. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  98. 2 2
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopLogsServiceImpl.java
  99. 18 3
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java
  100. 40 0
      fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java

+ 24 - 0
README.md

@@ -50,3 +50,27 @@ COALESCE(JSON_UNQUOTE(JSON_EXTRACT(remark_mobiles, '$[0]')), ''),
 -- 创建索引
 
 CREATE INDEX idx_search_mobile ON qw_external_contact(search_mobile);
+
+
+-- 新加统计表
+CREATE TABLE IF NOT EXISTS user_daily_stats (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID',
+    company_id BIGINT NOT NULL COMMENT '公司ID',
+    company_name VARCHAR(255) NOT NULL COMMENT '公司名称',
+    dept_id BIGINT NOT NULL COMMENT '部门ID',
+    dept_name VARCHAR(255) NOT NULL COMMENT '部门名称',
+    user_id BIGINT COMMENT '用户ID',
+    user_name VARCHAR(100) COMMENT '用户名',
+    nick_name VARCHAR(100) COMMENT '用户昵称',
+    statistics_time DATE NOT NULL COMMENT '统计日期',
+    line_num INT NOT NULL DEFAULT 0 COMMENT 't1统计数(线路数)',
+    active_num INT NOT NULL DEFAULT 0 COMMENT 't2统计数(活跃数)',
+    complete_num INT NOT NULL DEFAULT 0 COMMENT '完成数(t4)',
+    answer_num INT NOT NULL DEFAULT 0 COMMENT '答题数(t5)',
+    red_packet_num INT NOT NULL DEFAULT 0 COMMENT '红包数量(t6)',
+    red_packet_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '红包金额',
+    create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
+    update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
+    KEY idx_company_date (company_id, statistics_time) COMMENT '公司+日期索引',
+    KEY idx_dept_date (dept_id, create_time) COMMENT '部门+日期索引'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户每日统计数据表';

+ 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);
     }
 

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

@@ -0,0 +1,62 @@
+package com.fs.api.controller;
+
+import com.fs.common.core.domain.R;
+import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+
+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;
+
+    /**
+     * 获取统计信息
+     * @param param
+     * @return
+     */
+    @PostMapping("/statisticMain")
+    public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        return R.ok().put("data", statisticManageService.statisticMain(param));
+    }
+
+    /**
+     * 获取公司下拉
+     * @return
+     */
+    @GetMapping("/getSearchCompanyInfo")
+    public R getSearchCompanyInfo(){
+        return R.ok().put("data", statisticManageService.getSearchCompanyInfo());
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchDeptInfo")
+    public R getSearchDeptInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchDeptInfo(id));
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchUserInfo")
+    public R getSearchUserInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchUserInfo(id));
+    }
+}

+ 14 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyDeductController.java

@@ -14,6 +14,7 @@ import com.fs.company.domain.CompanyDeduct;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.service.ICompanyDeductService;
 import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.service.ICompanyRechargeService;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.CompanyDeductExportVO;
 import com.fs.company.vo.CompanyDeductVO;
@@ -45,6 +46,10 @@ public class CompanyDeductController extends BaseController
     private ICompanyService companyService;
     @Autowired
     private ICompanyMoneyLogsService moneyLogsService;
+
+    @Autowired
+    private ICompanyRechargeService companyRechargeService;
+
     /**
      * 查询扣款列表
      */
@@ -128,7 +133,15 @@ public class CompanyDeductController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(deduct.getIsAudit()==1){
             Company company=companyService.selectCompanyByIdForUpdate(deduct.getCompanyId());
-            company.setMoney(company.getMoney().subtract(deduct.getMoney()));
+
+            // 同步redis缓存
+            R r = companyRechargeService.syncUpdateRedisCompanyRecharge(company, deduct.getMoney(), 2);
+            if(!"200".equals(r.get("code").toString())){
+                return r;
+            }
+            // 充值后,需要同步更新余额到数据库,否则余额与缓存中的不一致
+            String newMoney = r.get("newMoney").toString();
+            company.setMoney(new BigDecimal(newMoney));
             companyService.updateCompany(company);
             CompanyMoneyLogs log=new CompanyMoneyLogs();
             log.setCompanyId(deduct.getCompanyId());

+ 11 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyRechargeController.java

@@ -24,6 +24,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 
@@ -121,7 +122,16 @@ public class CompanyRechargeController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(companyRecharge.getIsAudit()==1){
             Company company=companyService.selectCompanyById(companyRecharge.getCompanyId());
-            company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
+
+            // 同步redis缓存
+            // 注意:在进行充值审核之前,需要先执行一下定时任务同步缓存数据到数据库,再进行后续操作,否则金额不正确
+            R r = companyRechargeService.syncUpdateRedisCompanyRecharge(company, companyRecharge.getMoney(), 1);
+            if(!"200".equals(r.get("code").toString())){
+                return r;
+            }
+            // 充值后,需要同步更新余额到数据库,否则余额与缓存中的不一致
+            String newMoney = r.get("newMoney").toString();
+            company.setMoney(new BigDecimal(newMoney));
             companyService.updateCompany(company);
             CompanyMoneyLogs log=new CompanyMoneyLogs();
             log.setCompanyId(companyRecharge.getCompanyId());

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

@@ -48,11 +48,14 @@ public class FsCoursePlaySourceConfigController extends BaseController {
                               @RequestParam(required = false) String appid,
                               @RequestParam(required = false) Integer isMall,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
-                              @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+                              @RequestParam(required = false, defaultValue = "10") Integer pageSize,
+                              @RequestParam(required = false) Long companyId
+    ) {
         Map<String, Object> params = new HashMap<>();
         params.put("name", name);
         params.put("appid", appid);
         params.put("isMall", isMall);
+        params.put("companyId", companyId);
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);

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

@@ -28,6 +28,7 @@ import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PutMapping;
@@ -50,8 +51,7 @@ import com.fs.common.core.page.TableDataInfo;
  */
 @RestController
 @RequestMapping("/his/company")
-public class FsCompanyController extends BaseController
-{
+public class FsCompanyController extends BaseController {
     @Autowired
     private ICompanyService companyService;
     @Autowired
@@ -263,4 +263,36 @@ public class FsCompanyController extends BaseController
         return companyDivConfigService.setDiv(param);
     }
 
+    @PostMapping("/exitMiniProgram")
+    @Log(title = "批量修改小程序", businessType = BusinessType.UPDATE)
+    public R exitMiniProgram(@RequestBody Company company) {
+        // 1. 前置校验:核心参数非空校验
+        if (CollectionUtils.isEmpty(company.getIds())) {
+            return R.error("请选择需要修改的企业ID");
+        }
+        if (isNNull(company.getCourseMiniAppId())&&isListNull(company.getMiniAppMaster()) && isListNull(company.getMiniAppServer())) {
+            return R.error("请选择要修改的小程序");
+        } else {
+            for (Long id : company.getIds()) {
+                Company c = companyService.selectCompanyById(id);
+                c.setMoney(null);
+                c.setUpdateMiniApp(true);
+                if (company.getCourseMiniAppId()!= null&&!company.getCourseMiniAppId().isEmpty()) c.setCourseMiniAppId(company.getCourseMiniAppId());
+                if (company.getMiniAppMaster()!=null&&!company.getMiniAppMaster().isEmpty()) c.setMiniAppMaster(company.getMiniAppMaster());
+                if (company.getMiniAppServer()!=null&&!company.getMiniAppServer().isEmpty()) c.setMiniAppServer(company.getMiniAppServer());
+                companyService.updateCompany(c);
+            }
+            return R.ok();
+        }
+    }
+   public boolean isNNull(String value){
+        if (value==null)return true;
+        if (value.isEmpty())return true;
+        return false;
+    }
+    public boolean isListNull(List<String> value){
+        if (value==null)return true;
+        if (value.isEmpty())return true;
+        return false;
+    }
 }

+ 42 - 33
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -18,6 +18,7 @@ import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.his.domain.FsUserAddress;
+import com.fs.his.dto.FsUserDTO;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
@@ -121,6 +122,47 @@ public class FsUserController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 导出用户列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:user:export')")
+    @Log(title = "用户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserParam fsUser)
+    {
+        logger.info("导出用户列表:"+ SecurityUtils.getUserId());
+        if (fsUserService.isEntityNull(fsUser)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        Long count = fsUserService.selectFsUserExportListVOCount(fsUser);
+        if (count>10000){
+            return AjaxResult.error("导出数据不可超过1w条");
+        }
+        List<FsUserVO> list = fsUserService.selectFsUserListVO(fsUser);
+        SysRole sysRole = isCheckPermission();
+        List<FsUserDTO> listDTO = Lists.newArrayList();
+        for (FsUserVO fsUserVO : list) {
+            if(fsUserVO.getPhone() != null&&fsUserVO.getPhone()!=""){
+                if (!(sysRole.getIsCheckPhone()==1)){
+                    if (fsUserVO.getPhone().length()>11){
+                        fsUserVO.setPhone(decryptPhoneMk(fsUserVO.getPhone()));
+                    }else {
+                        fsUserVO.setPhone(fsUserVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    }
+                } else {
+                    if (fsUserVO.getPhone().length()>11) {
+                        fsUserVO.setPhone(decryptPhone(fsUserVO.getPhone()));
+                    }
+                }
+            }
+            FsUserDTO fsUserDTO = new FsUserDTO();
+            BeanUtils.copyProperties(fsUserVO,fsUserDTO);
+            listDTO.add(fsUserDTO);
+        }
+        ExcelUtil<FsUserDTO> util = new ExcelUtil<FsUserDTO>(FsUserDTO.class);
+        return util.exportExcel(listDTO, "用户数据");
+    }
+
     @Autowired
     private ISysRoleService sysRoleService;
     private SysRole isCheckPermission() {
@@ -196,37 +238,6 @@ public class FsUserController extends BaseController
         return util.exportExcel(list, "项目会员数据");
     }
 
-    /**
-     * 导出用户列表
-     */
-    @PreAuthorize("@ss.hasPermi('his:user:export')")
-    @Log(title = "用户", businessType = BusinessType.EXPORT)
-    @GetMapping("/export")
-    public AjaxResult export(FsUserParam fsUser)
-    {
-        logger.info("导出用户列表:"+ SecurityUtils.getUserId());
-        if (fsUserService.isEntityNull(fsUser)){
-            return AjaxResult.error("请筛选数据导出");
-        }
-        Long count = fsUserService.selectFsUserExportListVOCount(fsUser);
-        if (count>10000){
-            return AjaxResult.error("导出数据不可超过1w条");
-        }
-        List<FsUserExportListVO> list = fsUserService.selectFsUserExportListVO(fsUser);
-        SysRole sysRole = isCheckPermission();
-        for (FsUserExportListVO vo : list) {
-            if (vo.getMobile()!=null && !(sysRole.getIsCheckPhone()==1)){
-                vo.setMobile(vo.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
-            } else {
-                if (vo.getMobile().length()>11){
-                    vo.setMobile(decryptPhone(vo.getMobile()));
-                }
-            }
-        }
-        ExcelUtil<FsUserExportListVO> util = new ExcelUtil<FsUserExportListVO>(FsUserExportListVO.class);
-        return util.exportExcel(list, "用户数据");
-    }
-
     /**
      * 获取用户详细信息
      */
@@ -238,8 +249,6 @@ public class FsUserController extends BaseController
         return AjaxResult.success(fsUser);
     }
 
-
-
     @GetMapping(value = "/getUserAddr/{userId}")
     public AjaxResult getUserAddr(@PathVariable("userId") Long userId)
     {

+ 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;
@@ -66,6 +68,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;
@@ -74,6 +77,7 @@ 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;
@@ -81,6 +85,7 @@ import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -142,7 +147,7 @@ public class Task {
     @Autowired
     private CompanyVoiceLogsMapper companyVoiceLogsMapper;
     @Autowired
-    FsPackageOrderServiceImpl packageOrderService;
+    IFsPackageOrderService packageOrderService;
     @Autowired
     private IFsStoreOrderLogsService fsStoreOrderLogsService;
     org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
@@ -202,6 +207,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消耗统计
@@ -1539,5 +1554,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(); // 等待所有任务完成
+    }
+
 
 }

+ 99 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsUserPromoterApplyController.java

@@ -0,0 +1,99 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsUserPromoterApplyScrm;
+import com.fs.hisStore.param.FsUsePromoterApplyParam;
+import com.fs.hisStore.service.IFsUserPromoterApplyScrmService;
+import com.fs.hisStore.vo.FsUserPromoterApplyVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 推广员申请Controller
+ *
+ * @author fs
+ * @date 2023-02-28
+ */
+@RestController
+@RequestMapping("/store/userPromoterApply")
+public class FsUserPromoterApplyController extends BaseController
+{
+    @Autowired
+    private IFsUserPromoterApplyScrmService fsUserPromoterApplyService;
+
+    /**
+     * 查询推广员申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUsePromoterApplyParam fsUserPromoterApply)
+    {
+        startPage();
+        List<FsUserPromoterApplyVO> list = fsUserPromoterApplyService.selectFsUserPromoterApplyListVO(fsUserPromoterApply);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出推广员申请列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:export')")
+    @Log(title = "推广员申请", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        List<FsUserPromoterApplyScrm> list = fsUserPromoterApplyService.selectFsUserPromoterApplyList(fsUserPromoterApply);
+        ExcelUtil<FsUserPromoterApplyScrm> util = new ExcelUtil<FsUserPromoterApplyScrm>(FsUserPromoterApplyScrm.class);
+        return util.exportExcel(list, "userPromoterApply");
+    }
+
+    /**
+     * 获取推广员申请详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:query')")
+    @GetMapping(value = "/{applyId}")
+    public AjaxResult getInfo(@PathVariable("applyId") Long applyId)
+    {
+        return AjaxResult.success(fsUserPromoterApplyService.selectFsUserPromoterApplyById(applyId));
+    }
+
+    /**
+     * 新增推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:add')")
+    @Log(title = "推广员申请", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.insertFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    /**
+     * 修改推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:edit')")
+    @Log(title = "推广员申请", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserPromoterApplyScrm fsUserPromoterApply)
+    {
+        return toAjax(fsUserPromoterApplyService.updateFsUserPromoterApply(fsUserPromoterApply));
+    }
+
+    /**
+     * 删除推广员申请
+     */
+    @PreAuthorize("@ss.hasPermi('store:userPromoterApply:remove')")
+    @Log(title = "推广员申请", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{applyIds}")
+    public AjaxResult remove(@PathVariable Long[] applyIds)
+    {
+        return toAjax(fsUserPromoterApplyService.deleteFsUserPromoterApplyByIds(applyIds));
+    }
+}

+ 28 - 0
fs-admin/src/main/java/com/fs/task/ComprehensiveStatisticsTask.java

@@ -0,0 +1,28 @@
+package com.fs.task;
+
+import com.fs.company.service.IStatisticManageService;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午4:12
+ */
+@AllArgsConstructor
+@Component("comprehensiveStatisticsTask")
+public class ComprehensiveStatisticsTask {
+
+    @Resource
+    private final IStatisticManageService iStatisticManageService;
+
+    /**
+     * 每隔一小时执行一次
+     */
+    public void execute() {
+        iStatisticManageService.executeTask();
+    }
+
+}

+ 54 - 2
fs-admin/src/main/java/com/fs/task/FsCompanyTask.java

@@ -1,21 +1,34 @@
 package com.fs.task;
 
+import com.fs.common.constant.FsConstants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.company.domain.Company;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 @AllArgsConstructor
 @Component("companyTask")
+@Slf4j
 public class FsCompanyTask {
 
+    private final RedisCache redisCache;
     private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
     private ICompanyService companyService;
 
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
     public void refreshCompanyMoney() {
         LocalDateTime now = LocalDateTime.now();
         // 获取上一个小时的开始时间
@@ -31,4 +44,43 @@ public class FsCompanyTask {
             companyService.subtractCompanyMoneyHourse(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId(), startTime.toLocalTime(), endTime.toLocalTime());
         }
     }
-}
+
+    /**
+     * 同步公司缓存余额到公司数据表
+     */
+    public void syncRedisCompanyMoneyToDB(){
+            // 获取所有的公司余额key
+            String companyMoneyKeyAll = FsConstants.COMPANY_MONEY_KEY + "*";
+            Collection<String> keys = redisCache.keys(companyMoneyKeyAll);
+
+            if (keys != null && !keys.isEmpty()) {
+                log.info("同步缓存余额到公司表,keys:{}", keys);
+                List<Object> values = redisTemplate.opsForValue().multiGet(keys);
+                Iterator<String> keyIterator = keys.iterator();
+                if(values != null && !values.isEmpty()){
+                    Iterator<Object> valueIterator = values.iterator();
+                    List<Company> companies = companyService.selectCompanyList(new Company());
+
+                    Map<Long, BigDecimal> moneyMap = new HashMap<>();
+                    while (keyIterator.hasNext() && valueIterator.hasNext()) {
+                        String next = keyIterator.next();
+                        String[] keySplit = next.split(":");
+                        Long companyId = Long.parseLong(keySplit[2]);
+                        String value = valueIterator.next().toString();
+                        moneyMap.put(companyId, new BigDecimal(value));
+                    }
+
+                    // 使用Stream进行匹配赋值
+                    List<Company> collect = companies.stream()
+                            .filter(company -> moneyMap.containsKey(company.getCompanyId()))
+                            .peek(company -> company.setMoney(moneyMap.get(company.getCompanyId()))).collect(Collectors.toList());
+
+                    // 保存公司余额
+                    if(!collect.isEmpty()){
+                        companyService.batchUpdateCompany(collect);
+                    }
+                }
+            }
+
+    }
+}

+ 9 - 0
fs-admin/src/main/java/com/fs/task/SgTestController.java

@@ -3,6 +3,8 @@ package com.fs.task;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+ import com.fs.company.service.IStatisticManageService;
+
 import javax.annotation.Resource;
 
 /**
@@ -17,11 +19,18 @@ public class SgTestController {
     @Resource
     private SyncTuLinStudentInfoTask syncTuLinStudentInfoTask;
 
+    @Resource
+    private IStatisticManageService iStatisticManageService;
+
 
     @RequestMapping("/execute")
     public void execute(){
         syncTuLinStudentInfoTask.execute();
     }
 
+    @RequestMapping("/statistic")
+    public void executeTask(){
+        iStatisticManageService.executeTask();
+    }
 
 }

+ 1 - 0
fs-admin/src/main/resources/logback.xml

@@ -78,6 +78,7 @@
 
     <!-- log4j2.xml -->
     <Logger name="com.fs.his.mapper" level="debug"/>
+    <Logger name="com.fs.company.mapper" level="debug"/>
     <Logger name="org.apache.ibatis" level="debug"/>
 
 

+ 37 - 0
fs-admin/src/test/java/com/fs/api/controller/IndexStatisticsControllerTest.java

@@ -0,0 +1,37 @@
+package com.fs.api.controller;
+
+import com.fs.FSApplication;
+import com.fs.common.core.domain.R;
+import com.fs.statis.dto.AnalysisPreviewQueryDTO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.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 static org.junit.jupiter.api.Assertions.*;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FSApplication.class)
+@RequiredArgsConstructor
+@Slf4j
+public class IndexStatisticsControllerTest {
+
+    @Autowired
+    private IndexStatisticsController indexStatisticsController;
+
+    @Test
+    public void analysisPreview() {
+        AnalysisPreviewQueryDTO dto = new AnalysisPreviewQueryDTO();
+        dto.setCompanyId(null);
+        dto.setDeptId(1L);
+        dto.setEndTime("2025-10-31 23:59:59");
+        dto.setStartTime("2025-10-01 00:00:00");
+        dto.setType(4);
+        dto.setUserType(1);
+        R r = indexStatisticsController.analysisPreview(dto);
+        log.info("r: {}",r);
+    }
+}

+ 6 - 5
fs-common/pom.xml

@@ -138,23 +138,24 @@
             <artifactId>spring-expression</artifactId>
             <version>6.2.0</version>
         </dependency>
-
-
         <!-- https://mvnrepository.com/artifact/com.baidu.dev2/baiduads-sdk -->
         <dependency>
             <groupId>com.baidu.dev2</groupId>
             <artifactId>baiduads-sdk</artifactId>
             <version>2023.1.0</version>
         </dependency>
-
-
         <dependency>
             <groupId>com.nuonuo</groupId>
             <artifactId>open-sdk</artifactId>
             <version>1.0.5.2</version>
         </dependency>
 
-
+        <!-- Apache Commons Lang 2.x 版本(包含 org.apache.commons.lang.exception 包) -->
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version> <!-- 稳定版本,兼容大部分场景 -->
+        </dependency>
     </dependencies>
 
 </project>

+ 52 - 0
fs-common/src/main/java/com/fs/common/enums/DimensionEnum.java

@@ -0,0 +1,52 @@
+package com.fs.common.enums;
+
+/**
+ * @description: 统计维度枚举
+ * @author: Guos
+ * @time: 2025/11/3 上午11:26
+ */
+public enum DimensionEnum {
+
+    /**
+     * 个人维度
+     */
+    PERSONAL(1, "个人"),
+
+    /**
+     * 公司维度
+     */
+    COMPANY(2, "公司"),
+
+    /**
+     * 部门维度
+     */
+    DEPARTMENT(3, "部门");
+
+    private final Integer value;
+    private final String description;
+
+    DimensionEnum(Integer value, String description) {
+        this.value = value;
+        this.description = description;
+    }
+
+    public Integer getValue() {
+        return value;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * 根据值获取枚举
+     */
+    public static DimensionEnum fromValue(Integer value) {
+        for (DimensionEnum dimension : DimensionEnum.values()) {
+            if (dimension.getValue().equals(value)) {
+                return dimension;
+            }
+        }
+        return null;
+    }
+}

+ 3 - 1
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -270,8 +270,10 @@ public class FsUserCourseVideoController extends AppBaseController {
     public ResponseResult<PageInfo<FsUserCourseVideoPageListVO>> todayCourseList(@RequestParam(defaultValue = "1") Integer pageNum,
                                                                                  @RequestParam(defaultValue = "10") Integer pageSize, String keyword) {
         Long companyId = getCompanyId();
+        log.info("销售小程序-今日课程,获取公司id:{}", companyId);
         if (Objects.isNull(companyId)) {
-            ResponseResult.fail(400, "未获取到公司ID,请重新登录后再试");
+            log.error("未获取到公司id,进入提示逻辑。。。。。。。。");
+            return ResponseResult.fail(400, "未获取到公司ID,请重新登录后再试");
         }
 
         Map<String, Object> params = new HashMap<>();

+ 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));
+    }
+
 }

+ 47 - 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 && qwUser.getCorpId()!=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
@@ -173,6 +203,23 @@ public class QwUserController extends BaseController
         if (!deptList.isEmpty()){
             combinedList.addAll(deptList);
         }
+
+        // 添加企微部门查询条件
+        Long qwDeptId = qwUser.getDeptId();
+        if(qwDeptId!=null && qwUser.getCorpId()!=null){
+            List<Long> qwDeptIdList = new ArrayList<>();
+            if (qwDeptId!=null){
+                qwDeptIdList.add(qwDeptId);
+            }
+            // 本部门的下级部门
+            List<Long> qwDeptList = qwUserService.selectDeptByParentId(qwDeptId,qwUser.getCorpId());
+            if (!qwDeptList.isEmpty()){
+                qwDeptIdList.addAll(qwDeptList);
+            }
+            qwUser.setQwDeptIdList(qwDeptIdList);
+        }
+
+
         qwUser.setCuDeptIdList(combinedList);
         qwUser.setUserType(loginUser.getUser().getUserType());
 

+ 104 - 0
fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java

@@ -0,0 +1,104 @@
+package com.fs.company.controller.tag;
+
+import java.util.List;
+
+import com.fs.tag.domain.FsVideoCourseTag;
+import com.fs.tag.service.IFsVideoCourseTagService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 视频小节看课标签关联Controller
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@RestController
+@RequestMapping("/shop/tag")
+public class FsVideoCourseTagController extends BaseController
+{
+    @Autowired
+    private IFsVideoCourseTagService fsVideoCourseTagService;
+
+    /**
+     * 查询视频小节看课标签关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsVideoCourseTag fsVideoCourseTag)
+    {
+        startPage();
+        List<FsVideoCourseTag> list = fsVideoCourseTagService.selectFsVideoCourseTagList(fsVideoCourseTag);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出视频小节看课标签关联列表
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:export')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsVideoCourseTag fsVideoCourseTag)
+    {
+        List<FsVideoCourseTag> list = fsVideoCourseTagService.selectFsVideoCourseTagList(fsVideoCourseTag);
+        ExcelUtil<FsVideoCourseTag> util = new ExcelUtil<FsVideoCourseTag>(FsVideoCourseTag.class);
+        return util.exportExcel(list, "视频小节看课标签关联数据");
+    }
+
+    /**
+     * 获取视频小节看课标签关联详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsVideoCourseTagService.selectFsVideoCourseTagById(id));
+    }
+
+    /**
+     * 新增视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:add')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsVideoCourseTag fsVideoCourseTag)
+    {
+        return toAjax(fsVideoCourseTagService.insertFsVideoCourseTag(fsVideoCourseTag));
+    }
+
+    /**
+     * 修改视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:edit')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsVideoCourseTag fsVideoCourseTag)
+    {
+        return toAjax(fsVideoCourseTagService.updateFsVideoCourseTag(fsVideoCourseTag));
+    }
+
+    /**
+     * 删除视频小节看课标签关联
+     */
+    @PreAuthorize("@ss.hasPermi('shop:tag:remove')")
+    @Log(title = "视频小节看课标签关联", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsVideoCourseTagService.deleteFsVideoCourseTagByIds(ids));
+    }
+}

+ 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>

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

@@ -268,16 +268,16 @@ public class SendMsg {
                 }
             }
             // 推送 APP
-//            if (!setting.getSetting().isEmpty()) {
-//                new Thread(() -> {
-//                    try {
-//                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
-//                        asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
-//                    } catch (Exception e) {
-//                        log.error("推送APP失败", e);
-//                    }
-//                }).start();
-//            }
+            if (!setting.getSetting().isEmpty()) {
+                new Thread(() -> {
+                    try {
+                        List<QwSopTempSetting.Content.Setting> settings = JSON.parseArray(JSON.toJSONString(setting.getSetting()), QwSopTempSetting.Content.Setting.class).stream().filter(e -> "9".equals(e.getContentType())).collect(Collectors.toList());
+                        asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(settings, qwSopLogs.getCorpId(), user.getCompanyUserId(), qwSopLogs.getFsUserId());
+                    } catch (Exception e) {
+                        log.error("推送APP失败", e);
+                    }
+                }).start();
+            }
             qwSopLogs.setSend(true);
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             QwSopLogs updateQwSop = new QwSopLogs();

+ 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(194L,195L);
+
+        for(Long logId : testLogIds){
+            FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
+            fsTagUpdateService.onCourseWatchFinishedBatch(Collections.singletonList(fsCourseWatchLog));
+        }
+    }
+
+    @Test
+    public void handleData(){
+        fsTagUpdateService.handleData();
+    }
+}

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

@@ -376,8 +376,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;

+ 1 - 1
fs-qw-voice/pom.xml

@@ -10,7 +10,7 @@
     <modelVersion>4.0.0</modelVersion>
     <artifactId>fs-qw-voice</artifactId>
     <description>
-       投流接口
+       语音接口
     </description>
     <dependencies>
 

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

@@ -136,4 +136,6 @@ public class Company extends BaseEntity
     /** 所属部门id */
     private Long deptId;
 
+    @TableField(exist = false)
+    private List<Long> ids;
 }

+ 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 Long companyId;
+
+   /**
+    * 公司名称
+    */
+   private String companyName;
+
+   /**
+    * 部门id
+    */
+   private Long deptId;
+
+   /**
+    * 部门名称
+    */
+   private String deptName;
+
+   /**
+    * 员工id
+    */
+   private Long userId;
+
+   /**
+    * 员工名称
+    */
+   private String userName;
+
+   /**
+    * 员工昵称
+    */
+   private String nickName;
+
+}

+ 50 - 0
fs-service/src/main/java/com/fs/company/domain/ComprehensiveDailyStats.java

@@ -0,0 +1,50 @@
+package com.fs.company.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午4:58
+ */
+@Data
+public class ComprehensiveDailyStats {
+
+    private Long id;                     // 记录ID
+
+    private Long companyId;              // 公司ID
+
+    private String companyName;          // 公司名称
+
+    private Long deptId;                 // 部门ID
+
+    private String deptName;             // 部门名称
+
+    private Long userId;                 // 用户ID
+
+    private String userName;             // 用户名
+
+    private String nickName;             // 用户昵称
+
+    private Date statisticsTime;    // 统计日期
+
+    private Integer lineNum;             // t1统计数(线路数)
+
+    private Integer activeNum;           // t2统计数(活跃数)
+
+    private Integer completeNum;         // 完成数(t4)
+
+    private Integer answerNum;           // 答题数(t5)
+
+    private Integer redPacketNum;        // 红包数量(t6)
+
+    private BigDecimal redPacketAmount;  // 红包金额
+
+    private Date createTime;    // 记录创建时间
+
+    private Date updateTime;    // 记录更新时间
+
+}

+ 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 Long companyId;
+
+    /**
+     * 公司名称
+     */
+    private String companyName;
+
+    /**
+     * 部门id
+     */
+    private Long deptId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 进线数量
+     */
+    private Integer lineNum;
+
+    /**
+     * 激活数
+     */
+    private Integer activeNum;
+
+    /**
+     * 完课数量
+     */
+    private Integer completeNum;
+
+    /**
+     * 答题数量
+     */
+    private Integer answerNum;
+
+    /**
+     * 红包数量
+     */
+    private Integer redPacketNum;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/company/dto/ComprehensiveStatisticsDTO.java

@@ -0,0 +1,42 @@
+package com.fs.company.dto;
+
+import lombok.Data;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/3 下午3:24
+ */
+@Data
+public class ComprehensiveStatisticsDTO {
+
+    /**
+     * 日期
+     */
+    private String dateStr;
+
+    /**
+     * 进线数量
+     */
+    private Long lineNum;
+
+    /**
+     * 激活数
+     */
+    private Long activeNum;
+
+    /**
+     * 完课数量
+     */
+    private Long completeNum;
+
+    /**
+     * 答题数量
+     */
+    private Long answerNum;
+
+    /**
+     * 红包数量
+     */
+    private Long redPacketNum;
+}

+ 7 - 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" +
@@ -205,4 +205,10 @@ public interface CompanyMapper
     List<OptionsVO> getCompanyListByCorpId(@Param("corpId") String corpId);
 
     List<Company> selectCompanyMoneyAllList();
+
+    /**
+     * 批量修改公司余额
+     * @param list
+     */
+    void batchUpdateCompany(@Param("list") List<Company> list);
 }

+ 6 - 4
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -84,11 +84,13 @@ public interface CompanyRechargeMapper
             "<if test = 'maps.payType != null  '> " +
             "and r.pay_type = #{maps.payType}" +
             "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\" '> " +
-            "and date_format(r.pay_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test= 'maps.params != null and maps.params !=\"\"'>"+
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\" '> " +
+            "and date_format(r.pay_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\" '> " +
-            "and date_format(r.pay_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\" '> " +
+            "and date_format(r.pay_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
+            "</if>"+
             "</if>" +
             "order by r.recharge_id desc " +
             "</script>"})

+ 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);
+
 }

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

@@ -0,0 +1,108 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.domain.ComprehensiveDailyStats;
+import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.company.dto.ComprehensiveStatisticsDTO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:25
+ */
+public interface StatisticManageMapper {
+
+    /**
+     * 插入数据
+     * @param comprehensiveDailyStats
+     */
+    Integer insert(ComprehensiveDailyStats comprehensiveDailyStats);
+
+    /**
+     * 根据id更新数据
+     * @param comprehensiveDailyStats
+     */
+    Integer updateById(ComprehensiveDailyStats comprehensiveDailyStats);
+
+    /**
+     * 根据userId和日期更新数据
+     * @param comprehensiveDailyStats
+     */
+    Integer updateByUserAndDate(ComprehensiveDailyStats comprehensiveDailyStats);
+
+    /**
+     * 根据deptId和日期查询数据
+     * @param deptId
+     * @param statisticsTime
+     */
+    ComprehensiveDailyStats selectByDeptAndDate(@Param("deptId") Long deptId, @Param("statisticsTime") Date statisticsTime);
+
+    /**
+     * 根据用户id和日期查询是否有数据
+     * @param userId
+     * @param statisticsTime
+     */
+    ComprehensiveDailyStats selectByUserAndDate(@Param("userId") Long userId, @Param("statisticsTime") Date statisticsTime);
+
+    /**
+     * 根据用户id和日期查询是否有数据
+     * @param userId
+     * @param statisticsTime
+     */
+    Integer countByUserAndDate(@Param("userId") Long userId, @Param("statisticsTime") Date statisticsTime);
+
+    /**
+     * 根据id删除数据
+     * @param id
+     */
+    Integer deleteById(@Param("id") Long id);
+
+    //获取公司、部门、员工信息
+    List<CompanyDeptUserInfo> getCompanyAndDeptAndDeptUserList(@Param("companyId") Long companyId);
+
+    List<CompanyDeptUserInfo> getCompanyInfo();
+
+    CompanyDeptUserInfoDTO getStatisticNum(@Param("userIds") Long userIds);
+
+    /**
+     * 按照个人统计
+     * @param dimension 维度
+     * @param startTime
+     * @param endTime
+     * @param userIds
+     */
+    List<ComprehensiveDailyStats> getStatisticNumByPersonal(@Param("dimension")Integer dimension, @Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("userIds") Long... userIds);
+
+    /**
+     * 按照部门统计
+     * @param startTime
+     * @param endTime
+     * @param deptIds
+     */
+    List<ComprehensiveDailyStats> getStatisticNumByDeptId(@Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("deptIds") Long... deptIds);
+
+    /**
+     * 按照公司统计
+     * @param startTime
+     * @param endTime
+     * @param companyIds
+     */
+    List<ComprehensiveDailyStats> getStatisticNumByCompanyId(@Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("companyIds") Long... companyIds);
+
+    /**
+     * 根据公司id获取部门下拉搜索信息
+     * @param id
+     */
+    List<CompanyDeptUserInfo> getSearchDeptInfo(@Param("id") Long id);
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @param id
+     */
+    List<CompanyDeptUserInfo> getSearchUserInfo(@Param("id") Long id);
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/company/param/companyUserAddPrintParam.java

@@ -1,9 +1,12 @@
 package com.fs.company.param;
 
+import io.swagger.models.auth.In;
 import lombok.Data;
 
 @Data
 public class companyUserAddPrintParam
 {
     String  voicePrintUrl;
+    String  token;
+    Long companyUserId;
 }

+ 9 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyRechargeService.java

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
 import java.util.List;
 
 import com.fs.common.core.domain.R;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyRecharge;
 import com.fs.company.domain.CompanyRechargeOrder;
 import com.fs.company.dto.RechargeDTO;
@@ -50,6 +51,14 @@ public interface ICompanyRechargeService
      */
     public int updateCompanyRecharge(CompanyRecharge companyRecharge);
 
+    /**
+     * 同步到缓存余额
+     * @param company 公司
+     * @param rechargeMoney 充值金额
+     * @param type 同步类型,1-充值,2-扣款
+     * @return 是否成功
+     */
+    R syncUpdateRedisCompanyRecharge(Company company, BigDecimal rechargeMoney, Integer type);
 
     List<CompanyRechargeVO> selectCompanyRechargeListVO(CompanyRechargeVO companyRecharge);
     /**

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

@@ -169,4 +169,10 @@ public interface ICompanyService
     void subtractCompanyMoneyHourse(BigDecimal money, Long companyId, LocalTime start, LocalTime end);
 
     void syncCompanyBalance();
+
+    /**
+     * 批量更新公司余额
+     * @param list 公司列表
+     */
+    void batchUpdateCompany(List<Company> list);
 }

+ 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);
 }

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

@@ -0,0 +1,44 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:21
+ */
+public interface IStatisticManageService {
+
+    /**
+     * 统计主页
+     * @param param
+     * @return
+     */
+    Object statisticMain(ComprehensiveStatisticsParam param);
+
+    /**
+     * 获取搜索公司信息
+     */
+    List<CompanyDeptUserInfo> getSearchCompanyInfo();
+
+    /**
+     * 根据公司id获取部门下拉搜索信息
+     * @param id
+     */
+    List<CompanyDeptUserInfo> getSearchDeptInfo(Long id);
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @param id
+     */
+    List<CompanyDeptUserInfo> getSearchUserInfo(Long id);
+
+    /**
+     * 用来执行定时任务
+     */
+    void executeTask();
+
+}

+ 67 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyRechargeServiceImpl.java

@@ -1,12 +1,17 @@
 package com.fs.company.service.impl;
 
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import cn.hutool.core.util.ObjectUtil;
+import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.constant.PaymentStatus;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
@@ -19,7 +24,10 @@ import com.fs.company.service.CompanyRechargeOrderService;
 import com.fs.company.vo.CompanyRechargeExportVO;
 import com.fs.company.vo.CompanyRechargeVO;
 import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 import com.fs.company.mapper.CompanyRechargeMapper;
 import com.fs.company.domain.CompanyRecharge;
@@ -45,6 +53,12 @@ public class CompanyRechargeServiceImpl implements ICompanyRechargeService
     private CompanyMapper companyMapper;
     @Autowired
     private CompanyRechargeOrderService companyRechargeOrderService;
+    @Qualifier("redissonClient")
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private RedisCache redisCache;
+
     /**
      * 查询充值
      *
@@ -93,6 +107,59 @@ public class CompanyRechargeServiceImpl implements ICompanyRechargeService
     {
         return companyRechargeMapper.updateCompanyRecharge(companyRecharge);
     }
+
+    @Override
+    public R syncUpdateRedisCompanyRecharge(Company company, BigDecimal rechargeMoney, Integer type) {
+        if(company.getCompanyId() == null){
+            log.error("公司充值/扣款-审核-同步更新到缓存,参数错误,公司id:{}, 充值/扣款余额:{},类型:{}", null, rechargeMoney, type);
+            return R.error("公司id为空");
+        }
+
+        // 公司红包真实余额key
+        String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + company.getCompanyId();
+
+        // 加锁,与看课发放红包的加锁保持一致
+        RLock lock = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + company.getCompanyId());
+        boolean lockAcquired = false;
+        try {
+            BigDecimal newMoney = company.getMoney();
+
+            // 尝试加锁
+            lockAcquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
+            if (lockAcquired) {
+                BigDecimal redisMoney;
+                // 获取当前余额
+                String moneyStr = redisCache.getCacheObject(companyMoneyKey);
+                if (StringUtils.isNotEmpty(moneyStr)) {
+                    redisMoney = new BigDecimal(moneyStr);
+                } else {
+                    redisMoney = company.getMoney();
+                }
+
+                if(type == 1){
+                    // 充值
+                    newMoney = redisMoney.add(rechargeMoney);
+                } else {
+                    newMoney = redisMoney.subtract(rechargeMoney);
+                }
+
+                redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+            }
+            return R.ok().put("newMoney", newMoney);
+        } catch (Exception e) {
+            log.error("公司充值/扣款-审核-同步更新到缓存,参数错误,请求异常,异常信息:{}", e.getMessage(), e);
+            return R.error("审核失败,请重试");
+        } finally {
+            if (lockAcquired && lock.isHeldByCurrentThread()) {
+                try {
+                    lock.unlock();
+                } catch (IllegalMonitorStateException e) {
+                    log.warn("尝试释放非当前线程持有的锁: companyId:{}", company.getCompanyId());
+                }
+            }
+        }
+    }
+
     @Override
     public List<CompanyRechargeVO> selectCompanyRechargeListVO(CompanyRechargeVO companyRecharge) {
         return companyRechargeMapper.selectCompanyRechargeListVO(companyRecharge);

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

@@ -1353,4 +1353,9 @@ public class CompanyServiceImpl implements ICompanyService
                 }));
     }
 
+    @Override
+    public void batchUpdateCompany(List<Company> list) {
+        companyMapper.batchUpdateCompany(list);
+    }
+
 }

+ 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);
+    }
 }

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

@@ -0,0 +1,183 @@
+package com.fs.company.service.impl;
+
+import cn.hutool.core.date.StopWatch;
+import com.fs.common.enums.DimensionEnum;
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.domain.ComprehensiveDailyStats;
+import com.fs.company.dto.CompanyDeptUserInfoDTO;
+import com.fs.company.mapper.StatisticManageMapper;
+import com.fs.company.service.IStatisticManageService;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/30 上午9:21
+ */
+@Slf4j
+@Service
+public class StatisticManageServiceImpl implements IStatisticManageService {
+
+    @Resource
+    private StatisticManageMapper statisticManageMapper;
+
+    /**
+     * 统计
+     * 按照部门分组情况
+     * @return
+     */
+    @Override
+    public Object statisticMain(ComprehensiveStatisticsParam param) {
+        if(param.getDimension() == DimensionEnum.COMPANY.getValue()){
+            Assert.notNull(param.getId(), "按公司展示查询条件不能为空!");
+            return getStatisticNumByCompanyId(param.getStartTime(), param.getEndTime(), param.getId());
+        }
+        if(param.getDimension() == DimensionEnum.DEPARTMENT.getValue()){
+            Assert.notNull(param.getId(), "按部门展示查询条件不能为空!");
+            return getStatisticNumByDeptId(param.getStartTime(), param.getEndTime(), param.getId());
+        }
+        if (param.getDimension() == DimensionEnum.PERSONAL.getValue()){
+            Assert.notNull(param.getId(), "按个人展示查询条件不能为空!");
+            return getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(), param.getId());
+        }
+        return null;
+    }
+
+    /**
+     * 获取搜索公司信息
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchCompanyInfo(){
+        return statisticManageMapper.getCompanyInfo();
+    }
+
+    /**
+     * 根据公司id获取部门下拉搜索信息
+     * @param id
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchDeptInfo(Long id){
+        return statisticManageMapper.getSearchDeptInfo(id);
+    }
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @param id
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchUserInfo(Long id){
+        return statisticManageMapper.getSearchUserInfo(id);
+    }
+
+    /**
+     * 获取个人统计数据
+     * @param dimension 维度
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param userIds 用户id
+     * @return
+     */
+    public List<ComprehensiveDailyStats> getStatisticNumByPersonal(Integer dimension, Date startTime, Date endTime, Long... userIds) {
+        return statisticManageMapper.getStatisticNumByPersonal(dimension, startTime, endTime, userIds);
+    }
+
+    /**
+     * 获取部门统计数据
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param deptId 用户id
+     * @return
+     */
+    public List<ComprehensiveDailyStats> getStatisticNumByDeptId(Date startTime, Date endTime, Long... deptId) {
+        return statisticManageMapper.getStatisticNumByDeptId(startTime, endTime, deptId);
+    }
+
+    /**
+     * 获取部门统计数据
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @param deptId 用户id
+     * @return
+     */
+    public List<ComprehensiveDailyStats> getStatisticNumByCompanyId(Date startTime, Date endTime, Long... deptId) {
+        return statisticManageMapper.getStatisticNumByCompanyId(startTime, endTime, deptId);
+    }
+
+    /**
+     * 执行定时任务
+     * 还需要考虑在统计部门数据时,部门下面没有用户,某个时候这个部门下面又加入新的用户了,这个时候就会出现数据偏差
+     */
+    @Override
+    public void executeTask() {
+        StopWatch stopWatch = new StopWatch();
+        stopWatch.start("gs-执行数据统计任务");
+        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
+        log.info("统计人数列表:{}", companyDeptdUserList.size());
+        companyDeptdUserList.forEach(companyDeptUserInfo -> {
+            CompanyDeptUserInfoDTO statisticNum =  new CompanyDeptUserInfoDTO();
+            boolean empty = null == companyDeptUserInfo.getUserId(); //用户id为空返回true
+            if(!empty){
+                statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
+            }
+            ComprehensiveDailyStats comprehensiveDailyStats = component(companyDeptUserInfo, statisticNum);
+            ComprehensiveDailyStats selectResult = null;
+            if(!empty){
+                //用户id不为空,我们就用用户id去查询这个日期是否有数据
+                selectResult = statisticManageMapper.selectByUserAndDate(comprehensiveDailyStats.getUserId(), comprehensiveDailyStats.getStatisticsTime());
+            }else{
+                //如果用户id为空,说明部门下面没有人(没人情况下,部门id只会在当天的日期中存在一条),先按照部门去查询后删除(或者直接按照id更新就可以了)
+                selectResult = statisticManageMapper.selectByDeptAndDate(companyDeptUserInfo.getDeptId(), comprehensiveDailyStats.getStatisticsTime());
+            }
+            if(!ObjectUtils.isEmpty(selectResult)){
+                comprehensiveDailyStats.setId(selectResult.getId());
+                statisticManageMapper.updateById(comprehensiveDailyStats);
+            }else{
+                statisticManageMapper.insert(comprehensiveDailyStats);
+            }
+        });
+        stopWatch.stop();
+        log.info("gs-执行数据统计任务完成,耗时:{}", stopWatch.getTotalTimeSeconds());
+    }
+
+    private ComprehensiveDailyStats component(CompanyDeptUserInfo source, CompanyDeptUserInfoDTO statisticNum){
+        ComprehensiveDailyStats target = new ComprehensiveDailyStats();
+        target.setCompanyId(source.getCompanyId());
+        target.setCompanyName(source.getCompanyName());
+        target.setDeptId(source.getDeptId());
+        target.setDeptName(source.getDeptName());
+        target.setStatisticsTime(new Date());
+        target.setCreateTime(new Date());
+        target.setUpdateTime(new Date());
+        if(null != source.getUserId()){
+            target.setUserId(source.getUserId());
+            target.setUserName(source.getUserName());
+            target.setNickName(source.getNickName());
+            target.setAnswerNum(statisticNum.getAnswerNum());
+            target.setCompleteNum(statisticNum.getCompleteNum());
+            target.setLineNum(statisticNum.getLineNum());
+            target.setActiveNum(statisticNum.getActiveNum());
+            target.setRedPacketNum(statisticNum.getRedPacketNum());
+            target.setRedPacketAmount(new BigDecimal(0.00));
+        }else{
+            target.setAnswerNum(0);
+            target.setCompleteNum(0);
+            target.setLineNum(0);
+            target.setActiveNum(0);
+            target.setRedPacketNum(0);
+            target.setRedPacketAmount(new BigDecimal(0.00));
+        }
+      return target;
+    }
+
+
+}
+
+

+ 1 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -16,6 +16,7 @@ public class CourseConfig implements Serializable {
     private Integer maxBufferLength;//最大缓冲时长
     private Integer videoIntegral;//每十分钟获取多少积分
     private Integer answerIntegral;//答题获得积分
+    private Integer appAnswerIntegral; //app答题积分
     private Integer defaultLine;//默认看课线路
     private String realLinkDomainName;//真链域名
     private String authDomainName;//网页授权域名

+ 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;
 }

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

@@ -120,12 +120,12 @@ public class FsUserCourseVideo extends BaseEntity
      * 看课中标签ID
      */
     private String watchingTagId;
-    
+
     /**
      * 完课标签ID
      */
     private String watchedTagId;
-    
+
     /**
      * 标签组ID
      */
@@ -135,12 +135,12 @@ public class FsUserCourseVideo extends BaseEntity
      * 标签组表中的ID
      */
     private Long tgId;
-    
+
     /**
      * 看课标签 表中的ID
      */
     private Long watchingTgId;
-    
+
     /**
      * 完课标签 表中的ID
      */

+ 19 - 5
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -246,12 +246,14 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             " ,count(o.log_id) send_number" +
             " ,sum(if((o.user_id is not null or o.user_id>0) and o.log_type=3,1,0)) is_user_wait_number" +
             " ,sum(if((o.user_id is null or o.user_id=0) and o.log_type=3,1,0)) no_user_wait_number" +
-            " ,sum(ifnull(fcr.amount,0)) red_amount" +
+//            " ,sum(ifnull(fcr.amount,0)) red_amount" +
+            ",(SELECT SUM(amount) FROM fs_course_red_packet_log \n" +
+            "     WHERE user_id = o.user_id AND video_id = o.video_id) as red_amount " +
             "</if> " +
             "FROM fs_course_watch_log o " +
             "<if test= 'sendType != 1 '> " +
             " LEFT JOIN qw_user qu on qu.id=o.qw_user_id " +
-            " LEFT JOIN fs_course_red_packet_log fcr on o.user_id = fcr.user_id and fcr.video_id = o.video_id" +
+//            " LEFT JOIN fs_course_red_packet_log fcr on o.user_id = fcr.user_id and fcr.video_id = o.video_id" + //会有笛卡尔积问题
             "</if>\n" +
             "LEFT JOIN fs_user_course_video v on v.video_id=o.video_id \n" +
             "LEFT JOIN fs_user_course uc on uc.course_id=v.course_id\n" +
@@ -345,7 +347,8 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     @Select("SELECT * FROM fs_course_watch_log  WHERE  DATE(create_time) = CURDATE() and video_id in (select video_id from fs_user_course_video WHERE is_first=1 ) ")
     List<FsQwCourseWatchLogVO> selectFsCourseWatchLogByVideoId();
 
-    @Select("SELECT qw_external_contact_id FROM fs_course_watch_log  WHERE log_type=2 and DATE(create_time) = CURDATE() ")
+    @Select("SELECT qw_external_contact_id FROM fs_course_watch_log  WHERE log_type=2 AND create_time >= CURDATE()\n" +
+            "  AND create_time < DATE_ADD(CURDATE(), INTERVAL 1 DAY) ")
     List<Long> selectFsCourseWatchLogByFinish();
     @Select("select * from fs_course_watch_log " +
             "where video_id = #{videoId} " +
@@ -475,8 +478,19 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     void batchUpdateFsUserWatchLog(@Param("list") List<FsCourseWatchLog> list);
 
-    @Select("select * from fs_course_watch_log where user_id = #{userId} and video_id = #{videoId} and send_type = 1 order by log_id desc limit 1")
-    FsCourseWatchLog getCourseWatchLogByUser(@Param("userId") Long userId, @Param("videoId") Long videoId);
+    @Select("<script>" +
+            "select * from fs_course_watch_log " +
+            "<where>" +
+            "   <if test='periodId != null'>and period_id = #{periodId}</if>" +
+            "   and send_type = 1" +
+            "   and user_id = #{userId}" +
+            "   and video_id = #{videoId}" +
+            "</where>" +
+            "order by log_id desc limit 1" +
+            "</script>")
+    FsCourseWatchLog getCourseWatchLogByUser(@Param("userId") Long userId,
+                                             @Param("videoId") Long videoId,
+                                             @Param("periodId") Long periodId);
 
     /**
      * 根据条件查询条数

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java

@@ -120,7 +120,7 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
 
     Long selectFsUserCoursePeriodDaysCount(FsUserCoursePeriodDays fsUserCoursePeriodDays);
 
-    @Select("SELECT distinct period_id from fs_user_course_period_days  where start_date_time >=#{periodSTime} and end_date_time <=#{periodETime} ")
+    @Select("SELECT distinct period_id from fs_user_course_period_days  where day_date >=#{periodSTime} and day_date <=#{periodETime} ")
     List<Long> selectFsUserCoursePeriodDaysByTime(@Param("periodSTime") String periodSTime,@Param("periodETime") String periodETime);
 
     @Select("<script>" +

+ 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);
 }

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

@@ -208,4 +208,6 @@ public interface IFsUserCourseVideoService
      * 查询选择使用的视频列表
      */
     List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
+
+    R sendAppReward(FsCourseSendRewardUParam param);
 }

+ 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());

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

@@ -51,11 +51,13 @@ import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.vo.DictVO;
 import com.fs.tag.service.FsTagUpdateService;
+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;
@@ -93,7 +95,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
-    RedisCache redisCache;
+    private RedisCache redisCache;
     @Autowired
     private IQwExternalContactCacheService qwExternalContactCacheService;
     @Autowired
@@ -497,7 +499,6 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         if(CollectionUtils.isNotEmpty(watchingLogs)){
             fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
         }
-
     }
 
     @Override
@@ -578,7 +579,6 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
         batchUpdateFsCourseWatchLogIsOpen(logs,100);
 
-
         // 完课打标签
         if(CollectionUtils.isNotEmpty(finishedLogs)){
             fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
@@ -1167,14 +1167,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         for (int i = 0; i < logs.size(); i += batchSize) {
             int end = Math.min(i + batchSize, logs.size());
             List<FsCourseWatchLog> batchList = logs.subList(i, end);
-            
+
             // 筛选出完课记录
             for(FsCourseWatchLog log : batchList) {
                 if(log.getLogType() != null && log.getLogType() == 2) {
                     finishedLogs.add(log);
                 }
             }
-            
+
             // 执行批量更新
             try {
                 fsCourseWatchLogMapper.batchUpdateWatchLog(batchList);

+ 7 - 26
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -45,6 +45,7 @@ import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
 import com.fs.his.utils.ConfigUtil;
@@ -258,6 +259,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private QwTagMapper qwTagMapper;
 
 
+    @Autowired
+    private IFsUserIntegralLogsService iFsUserIntegralLogsService;
 
 
 
@@ -923,32 +926,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.error(400,msg).put("qrcode",contactWay);
     }
 
+
     @Override
     public List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam 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;
+        return fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
     }
 
     @Override
@@ -1567,11 +1548,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         if(fsUserCoursePeriodDays != null && !fsUserCoursePeriodDays.isEmpty()){
             periodDays = fsUserCoursePeriodDays.get(0);
         }
-        
+
         // 检查是否开启自由模式
         FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
         boolean isFreeMode = (period != null && period.getFreeMode() != null && period.getFreeMode() == 1);
-        
+
         // 只有在非自由模式下才检查红包领取时间限制
         if(!isFreeMode && periodDays != null && periodDays.getLastJoinTime() !=null && LocalDateTime.now().isAfter(periodDays.getLastJoinTime())) {
             return R.error(403,"已超过领取红包时间");

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

@@ -20,6 +20,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);

+ 68 - 17
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -459,9 +459,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);
@@ -1937,8 +1937,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);
 
 
@@ -1952,6 +1953,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){
@@ -1978,19 +1998,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();
-
-                FastGptChatSession chatSession = new FastGptChatSession();
-                chatSession.setLastTime(expireTime);
-                chatSession.setIsArtificial(1);
-                chatSession.setSessionId(fastGptChatSession.getSessionId());
-
-                fastGptChatSessionMapper.updateFastGptChatSession(chatSession);
+        }
+        return R.ok();
+    }
+
+    @Override
+    public R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid) {
+        QwUser sendUser = qwUserMapper.selectQwUserById(qwUserID);
+
+
+        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 {
+
+                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();

+ 5 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -122,6 +122,11 @@ public class FsUser extends BaseEntity
     private Date  vipStartDate;
     private Date  vipEndDate;
     private Integer vipLevel;
+
+    /**
+     * 会员等级
+     */
+    private Integer level;
     private Integer vipStatus;
 
     private Integer sex;

+ 58 - 0
fs-service/src/main/java/com/fs/his/dto/FsUserDTO.java

@@ -0,0 +1,58 @@
+package com.fs.his.dto;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/29 上午9:40
+ */
+@Data
+public class FsUserDTO {
+
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @Excel(name = "手机号码")
+    private String phone;
+
+    @Excel(name = "用户积分")
+    private Long integral;
+
+    @Excel(name = "用户状态:1为正常,0为禁止")
+    private Integer status;
+
+    @Excel(name = "用户备注")
+    private String remark;
+
+    @Excel(name = "上级昵称")
+    private String tuiName;
+
+    @Excel(name = "app来源")
+    private String source;
+
+    @Excel(name = "登陆设备")
+    private String loginDevice;
+
+    @Excel(name = "上级手机号码")
+    private String tuiPhone;
+
+    @Excel(name = "下级人数")
+    private Integer tuiUserCount;
+
+    @Excel(name = "最后一次登录ip")
+    private String lastIp;
+
+    @Excel(name = "余额")
+    private BigDecimal balance;
+
+    @Excel(name = "会员注册时间", dateFormat = "yyyy-MM-dd HH:mm:ss" , sort = 6)
+    private Date createTime;
+}

+ 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);
 }

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

@@ -219,6 +219,12 @@ public interface FsStoreOrderMapper
             "            <if test=\"maps.packageSecondName != null and maps.packageSecondName != '' \"> and so.package_second_name like concat('%', #{maps.packageSecondName}, '%')</if>"+
             "            <if test=\"maps.storeId != null \"> and so.store_id = #{maps.storeId}</if>\n" +
             "            <if test=\"maps.orderCode != null  and maps.orderCode != ''\"> and so.order_code = #{maps.orderCode}</if>\n" +
+            "            <if test=\"maps.orderCodes != null  and maps.orderCodes.size > 0\">\n" +
+            "                and so.order_code in\n" +
+            "                <foreach collection=\"maps.orderCodes\" item=\"orderCode\" open=\"(\" close=\")\" separator=\",\">\n" +
+            "                    #{orderCode}\n" +
+            "                </foreach>\n" +
+            "            </if>" +
             "            <if test=\"maps.prescribeCode != null  and maps.prescribeCode != ''\"> and p.prescribe_code = #{maps.prescribeCode}</if>\n" +
             "            <if test=\"maps.userName != null  and maps.userName != ''\"> and so.user_name like concat('%', #{maps.userName}, '%')</if>\n" +
             "            <if test=\"maps.userPhone != null  and maps.userPhone != ''\"> and so.user_phone = #{maps.userPhone}</if>\n" +
@@ -1188,4 +1194,6 @@ public interface FsStoreOrderMapper
     List<Report> selectOrderByCustomerIds(@Param("map") ReportParam param);
 
     FsStoreOrderAmountStatsVo selectFsStoreOrderAmountStats(FsStoreOrderAmountStatsQueryDto queryDto);
+
+    List<FsStoreOrder> selectOutTimeOrderList(@Param("unPayTime")Integer unPayTime);
 }

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

@@ -181,7 +181,22 @@ public interface FsStorePaymentMapper
     @Select("select * from fs_store_payment where business_type=#{type} and  business_id=#{businessId} and (status=1 or starus=-1)")
     List<FsStorePayment> selectFsStorePaymentByPayOrRefund(int i, Long orderId);
     @Select({"<script> " +
-            " SELECT CONCAT('package-', sp.pay_code) AS pay_code,sp.*,u.nick_name,u.phone,s.store_name,c.company_name,cu.nick_name as companyUserName,fso.delivery_name,fso.package_name,fso.package_second_name ,csc.name miniProgramName " +
+            " SELECT " +
+            " CASE " +
+            "   WHEN sp.status = -1 THEN CONCAT('refund-', sp.pay_code) " +
+            "   ELSE " +
+            "     CASE " +
+            "       WHEN sp.business_type = 1 THEN CONCAT('inquiry-', sp.pay_code) " +
+            "       WHEN sp.business_type = 2 THEN CONCAT('store-', sp.pay_code) " +
+            "       WHEN sp.business_type = 3 THEN CONCAT('package-', sp.pay_code) " +
+            "       WHEN sp.business_type = 4 THEN CONCAT('course-', sp.pay_code) " +
+            "       WHEN sp.business_type = 5 THEN CONCAT('appvip-', sp.pay_code) " +
+            "       WHEN sp.business_type = 6 THEN CONCAT('integral-', sp.pay_code) " +
+            "       WHEN sp.business_type = 7 THEN CONCAT('payment-', sp.pay_code) " +
+            "       ELSE sp.pay_code " +
+            "     END " +
+            " END AS pay_code, " +
+            "sp.*,u.nick_name,u.phone,s.store_name,c.company_name,cu.nick_name as companyUserName,fso.delivery_name,fso.package_name,fso.package_second_name ,csc.name miniProgramName " +
             " FROM fs_store_payment sp " +
             " LEFT JOIN  fs_user u ON u.user_id=sp.user_id " +
             " LEFT JOIN fs_store s ON s.store_id=sp.store_id " +

+ 45 - 26
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -105,6 +105,32 @@ public interface FsUserMapper
     })
     List<FsUserVO> selectFsUserListVO(FsUserParam fsUser);
 
+    @Select({"<script> " +
+            "SELECT  \n" +
+            "    fp.create_time ,\n" +
+            "    cu.nick_name ,\n" +
+            "    fu.user_id ,\n" +
+            "    fp.patient_name ,\n" +
+            "    fp.mobile ,\n" +
+            "    fu.is_buy \n" +
+            "FROM fs_user fu\n" +
+            "LEFT JOIN company_user_user cuu ON fu.user_id = cuu.user_id\n" +
+            "LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id\n" +
+            "LEFT JOIN fs_patient fp ON fu.user_id = fp.user_id\n" +
+            "WHERE fp.create_time IS NOT NULL and fu.is_del = 0 \n" +
+            "  <if test=\"companyUserId != null \"> and cuu.company_user_id=#{companyUserId} </if>\n" +
+            "  <if test=\"companyId != null \"> and cuu.company_id=#{companyId} </if>\n" +
+            "  <if test=\"nickName != null  and nickName != ''\"> and fu.nick_name like concat( #{nickName}, '%')</if>\n" +
+            "   <if test=\"phone != null  and phone != ''\"> and fu.phone like concat( #{phone}, '%')</if>\n" +
+            "   <if test=\"status != null \"> and fu.status = #{status}</if>\n" +
+            "            <if test=\"isBuy != null \"> and fu.is_buy = #{isBuy}</if>\n" +
+            "            <if test=\"userId != null \"> and fu.user_id = #{userId}</if>\n" +
+            "            <if test=\"sTime != null \">  and DATE(fu.create_time) &gt;= DATE(#{sTime})</if>\n" +
+            "            <if test=\"eTime != null \">  and DATE(fu.create_time) &lt;= DATE(#{eTime})</if>\n" +
+            "ORDER BY fu.user_id DESC "+
+            "</script>"})
+    List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser);
+
     @Update("update fs_user set is_del=1 where user_id=#{userId}")
     int updateFsUserByUserId(Long userId);
 
@@ -144,32 +170,6 @@ public interface FsUserMapper
             "</script>"})
     List<FsUserVO> selectFsUserListVOByComponentsUser(FsUserParam fsUser);
 
-    @Select({"<script> " +
-            "SELECT  \n" +
-            "    fp.create_time ,\n" +
-            "    cu.nick_name ,\n" +
-            "    fu.user_id ,\n" +
-            "    fp.patient_name ,\n" +
-            "    fp.mobile ,\n" +
-            "    fu.is_buy \n" +
-            "FROM fs_user fu\n" +
-            "LEFT JOIN company_user_user cuu ON fu.user_id = cuu.user_id\n" +
-            "LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id\n" +
-            "LEFT JOIN fs_patient fp ON fu.user_id = fp.user_id\n" +
-            "WHERE fp.create_time IS NOT NULL and fu.is_del = 0 \n" +
-            "  <if test=\"companyUserId != null \"> and cuu.company_user_id=#{companyUserId} </if>\n" +
-            "  <if test=\"companyId != null \"> and cuu.company_id=#{companyId} </if>\n" +
-            "  <if test=\"nickName != null  and nickName != ''\"> and fu.nick_name like concat( #{nickName}, '%')</if>\n" +
-            "   <if test=\"phone != null  and phone != ''\"> and fu.phone like concat( #{phone}, '%')</if>\n" +
-            "   <if test=\"status != null \"> and fu.status = #{status}</if>\n" +
-            "            <if test=\"isBuy != null \"> and fu.is_buy = #{isBuy}</if>\n" +
-            "            <if test=\"userId != null \"> and fu.user_id = #{userId}</if>\n" +
-            "            <if test=\"sTime != null \">  and DATE(fu.create_time) &gt;= DATE(#{sTime})</if>\n" +
-            "            <if test=\"eTime != null \">  and DATE(fu.create_time) &lt;= DATE(#{eTime})</if>\n" +
-            "ORDER BY fu.user_id DESC "+
-            "</script>"})
-    List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser);
-
     @Select("SELECT user_id\n" +
             "FROM fs_user_integral_logs\n" +
             "WHERE business_type = 2\n" +
@@ -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);
@@ -410,6 +415,20 @@ public interface FsUserMapper
     Map<String, Object> countUserStats(
             UserStatisticsCommonParam param);
 
+    /**
+     * 查询观看和完成人数
+     */
+    Map<String, Object> countUserWatchStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户答题数据(答题次数、正确次数)
+     */
+    Map<String, Object> countUserAnswerStats(UserStatisticsCommonParam param);
+
+
+    /**查询红包数量和红包金额 */
+    Map<String, Object> countUserRedPacketStats(UserStatisticsCommonParam param);
+
     /**
      * 统计用户领取红包数据(红包个数、红包金额)
      * */

+ 5 - 1
fs-service/src/main/java/com/fs/his/param/FsPackageCateUParam.java

@@ -1,5 +1,7 @@
 package com.fs.his.param;
 
+import com.fs.common.param.BaseQueryParam;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -12,7 +14,7 @@ import java.util.List;
  * @date 2024-05-08
  */
 @Data
-public class FsPackageCateUParam implements Serializable
+public class FsPackageCateUParam extends BaseQueryParam implements Serializable
 {
 
     /** ID */
@@ -43,5 +45,7 @@ public class FsPackageCateUParam implements Serializable
 
     private Long companyUserId;
 
+    private Integer pageNum;
+    private Integer pageSize;
 
 }

+ 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

@@ -277,4 +277,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 payment(FsStorePaymentPayParam payParam);
     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) {
@@ -488,7 +488,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){
@@ -1818,4 +1818,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

@@ -4408,4 +4408,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;
@@ -1625,6 +1626,26 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
     }
 
 
+    @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());

+ 162 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -270,11 +270,26 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.updateFsUserByUserId(userId);
     }
 
+    /**
+     * 列表查询
+     * @param fsUser
+     * @return
+     */
     @Override
     public List<FsUserVO> selectFsUserListVO(FsUserParam fsUser) {
         return fsUserMapper.selectFsUserListVO(fsUser);
     }
 
+    /**
+     * 导出用户列表
+     * @param fsUser
+     * @return
+     */
+    @Override
+    public List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser) {
+        return fsUserMapper.selectFsUserExportListVO(fsUser);
+    }
+
     @Override
     public FsUser selectFsUserByOpenId(String openId) {
         return fsUserMapper.selectFsUserByOpenId(openId);
@@ -312,10 +327,6 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.selectFsUserListVOByComponentsUser(fsUser);
     }
 
-    @Override
-    public List<FsUserExportListVO> selectFsUserExportListVO(FsUserParam fsUser) {
-        return fsUserMapper.selectFsUserExportListVO(fsUser);
-    }
     @Autowired
     private FsUserIntegralLogsMapper integralLogsMapper;
 
@@ -834,8 +845,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);
@@ -843,9 +898,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;
     }
 
@@ -1189,7 +1259,95 @@ public class FsUserServiceImpl implements IFsUserService {
         return fsUserMapper.selectFsUserListByJointUserNameKey(userNameKey);
     }
 
+
+    /**
+     * @Description: 该方法原方法是 getUserStatisticsOld
+     * 目的为了优化sql查询 fsUserMapper.countUserStats 现拆分成三次异步查询
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/29 15:34
+     */
     private FsUserStatisticsVO getUserStatistics(UserStatisticsCommonParam param) {
+
+        FsUserStatisticsVO fsUserStatisticsVO = new FsUserStatisticsVO();
+
+        // 判断是否是管理员
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getUserId());
+        if (companyUser != null && companyUser.isAdmin()){
+            param.setUserId(0L);
+            param.setCompanyId(companyUser.getCompanyId());
+        }
+
+        // 异步调用三个查询方法
+        CompletableFuture<Map<String, Object>> watchStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserWatchStats(param));
+
+        CompletableFuture<Map<String, Object>> answerStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserAnswerStats(param));
+
+        CompletableFuture<Map<String, Object>> redPacketStatsFuture = CompletableFuture.supplyAsync(() ->
+                fsUserMapper.countUserRedPacketStats(param));
+
+        try {
+            // 等待所有查询完成
+            Map<String, Object> watchMap = watchStatsFuture.get();
+            Map<String, Object> answerMap = answerStatsFuture.get();
+            Map<String, Object> redPacketMap = redPacketStatsFuture.get();
+
+            // 处理观看和完成人数统计
+            if (watchMap != null) {
+                fsUserStatisticsVO.setCourseWatchNum(Integer.valueOf(watchMap.getOrDefault("courseWatchNum", 0L).toString()))
+                        .setCourseCompleteNum(Integer.valueOf(watchMap.getOrDefault("courseCompleteNum", 0L).toString()));
+
+                Long completeNum = (Long) watchMap.get("courseCompleteNum");
+                Long watchNum = (Long) watchMap.get("courseWatchNum");
+                if (completeNum != null && watchNum != null && watchNum > 0) {
+                    int courseCompleteRate = BigDecimal.valueOf(completeNum)
+                            .divide(BigDecimal.valueOf(watchNum), 2, RoundingMode.HALF_UP)
+                            .multiply(BigDecimal.valueOf(100))
+                            .intValue();
+                    fsUserStatisticsVO.setCourseCompleteRate(courseCompleteRate);
+                }
+            }
+
+            // 处理答题统计
+            if (answerMap != null) {
+                Long answerRightNum = (Long) answerMap.get("answerRightNum");
+                Long answerNum = (Long) answerMap.get("answerNum");
+
+                fsUserStatisticsVO.setAnswerNum(answerNum.intValue()).setAnswerRightNum(answerRightNum.intValue());
+
+                if (answerRightNum != null && answerNum != null && answerNum > 0) {
+                    int answerCompleteRate = BigDecimal.valueOf(answerRightNum)
+                            .divide(BigDecimal.valueOf(answerNum), 2, RoundingMode.HALF_UP)
+                            .multiply(BigDecimal.valueOf(100))
+                            .intValue();
+                    fsUserStatisticsVO.setAnswerRightRate(answerCompleteRate);
+                }
+            }
+
+            // 处理红包统计
+            if (redPacketMap != null) {
+                Object numObj = redPacketMap.get("redPacketNum");
+                Object amtObj = redPacketMap.get("redPacketAmount");
+                if (numObj != null && amtObj != null) {
+                    fsUserStatisticsVO.setRedPacketNum(Integer.parseInt(numObj.toString()))
+                            .setRedPacketAmount(new BigDecimal(amtObj.toString()));
+                }
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            // 异常处理
+            logger.error("异步查询用户统计数据失败", e);
+        }
+
+        return fsUserStatisticsVO;
+    }
+
+
+
+
+    private FsUserStatisticsVO getUserStatisticsNew(UserStatisticsCommonParam param) {
         FsUserStatisticsVO fsUserStatisticsVO = new FsUserStatisticsVO();
 
         // 判断是否是管理员

+ 7 - 5
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -119,15 +119,17 @@ public interface FsStoreAfterSalesScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\"   '> " +
-            " AND date_format(s.create_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test = 'maps.params != null and maps.params != \"\"   '> " +
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\"   '> " +
+            " AND date_format(s.create_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
+            "</if>" +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\"   '> " +
+            " AND date_format(s.create_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
+            "</if>" +
             "</if>" +
             "<if test = 'maps.consigneePhone != null and  maps.consigneePhone !=\"\"     '> " +
             "and o.user_phone like CONCAT('%',#{maps.consigneePhone},'%') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\"   '> " +
-            " AND date_format(s.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
-            "</if>" +
             "<if test = 'maps.deptId != null    '> " +
             "  AND (o.dept_id = #{maps.deptId} OR o.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{maps.deptId}, ancestors) )) " +
             "</if>" +

+ 6 - 4
fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java

@@ -105,11 +105,13 @@ public interface FsStorePaymentScrmMapper
 //            "<if test = 'maps.createTime != null    '> " +
 //            "and DATE_FORMAT(p.create_time,'%Y-%m-%d') = DATE_FORMAT(#{maps.createTime},'%Y-%m-%d')  " +
 //            "</if>" +
-            "<if test = 'maps.beginTime != null and maps.beginTime != \"\"   '> " +
-            " AND date_format(p.pay_time,'%y%m%d') &gt;= date_format(#{maps.beginTime},'%y%m%d') " +
+            "<if test = 'maps.params != null and maps.params != \"\"   '> " +
+            "<if test = 'maps.params.beginTime != null and maps.params.beginTime != \"\"   '> " +
+            " AND date_format(p.pay_time,'%y%m%d') &gt;= date_format(#{maps.params.beginTime},'%y%m%d') " +
+            "</if>" +
+            "<if test = 'maps.params.endTime != null and maps.params.endTime != \"\"   '> " +
+            " AND date_format(p.pay_time,'%y%m%d') &lt;= date_format(#{maps.params.endTime},'%y%m%d') " +
             "</if>" +
-            "<if test = 'maps.endTime != null and maps.endTime != \"\"   '> " +
-            " AND date_format(p.pay_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d') " +
             "</if>" +
 
             "<if test = 'maps.refundBeginTime != null  and maps.refundBeginTime != \"\"    '> " +

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

@@ -895,7 +895,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.setPayMode("hf");
         storePayment.setBusinessType(1);

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

@@ -2,6 +2,6 @@ package com.fs.qw.cache;
 
 public interface IQwUserCacheService {
     String queryQwUserNameByUserId(String userId);
-
     String queryCorpIdByQwUserId(Long 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

@@ -400,8 +400,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);
 

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

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

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

@@ -86,7 +86,7 @@ public interface QwTagMapper
     QwTagVO selectQwTagByName(@Param("trimTag") String trimTag, @Param("corpId")String corpId );
 
     @Select("<script>" +
-            "SELECT name FROM qw_tag WHERE tag_id IN " +
+            "SELECT distinct name FROM qw_tag WHERE tag_id IN " +
             "<foreach collection='date.tagIds' item='tagId' open='(' separator=',' close=')'>" +
             "#{tagId}" +
             "</foreach>" +

+ 10 - 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 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);
@@ -444,5 +451,8 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     @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);
 }

+ 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);
 }

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

@@ -199,4 +199,5 @@ public interface IQwUserService
 
     List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
 
+    List<Long> selectDeptByParentId(Long deptId,String cropId);
 }

+ 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;
+    }
+
+
+
+
 }

+ 25 - 15
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<>();
 
@@ -3929,9 +3930,10 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                         qw.setDescription(followUser.getDescription()); // 设置描述信息
                         List<Tag> tags = followUser.getTags();
 
+                        List<String> tagArr = new ArrayList<>();
+
                         if (tags != null && tags.size() > 0) {
 
-                            List<String> tagArr = new ArrayList<>();
                             for (Tag tag : tags) {
                                 if (tag != null && tag.getTag_id() != null) {
                                     tagArr.add(tag.getTag_id());
@@ -3944,10 +3946,18 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
                             //新客对话
 //                            processTagsAllByAiChat(qwExternalContact1,corpId,tagArr);
+                        }else {
 
-                            qw.setTagIds(JSON.toJSONString(tagArr)); // 设置标签ID
+                            //客户自带标签空干净了
+                            logger.info("客户自带标签空干净了:"+qwExternalContact1.getName()+"|公司"+qwExternalContact1.getCorpId()+"|员工"+qwExternalContact1.getUserId()+"|总标签"+tagArr+"|原标签"+qwExternalContact1.getTagIds());
+
+                            //销售删除客户-找这个销售的sop任务中所有的营期里有没有这个客户,删了
+                            sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(userID,corpId, externalUserID);
+                            sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactIdByChat(userID,corpId, externalUserID);
 
                         }
+
+                        qw.setTagIds(JSON.toJSONString(tagArr)); // 设置标签ID
                         if (followUser.getRemark_mobiles() != null && followUser.getRemark_mobiles().size() > 0) {
                             List<String> remarkMobiles = followUser.getRemark_mobiles();
                             qw.setRemarkMobiles(JSON.toJSONString(remarkMobiles));

+ 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);
+    }
+
 
     /**
      * 构建查询条件

+ 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();

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

@@ -1099,8 +1099,8 @@ public class QwSopLogsServiceImpl extends ServiceImpl<QwSopLogsMapper, QwSopLogs
 
                                 //有app的异步推送消息
                                 if (!setting.isEmpty()) {
-                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
-                                    //asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
+//                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormal(setting, param.getExternalId());
+                                    asyncSopTestService.asyncSendMsgBySopAppLinkNormalIM(setting, param.getCorpId(),qwUser.getCompanyUserId(),log.getFsUserId());
                                 }
 
                                 if (log.getExpiryTime() == null) {

+ 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模板
      *
@@ -477,10 +483,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);

+ 40 - 0
fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java

@@ -0,0 +1,40 @@
+package com.fs.statis.param;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @description: 综合统计入参
+ * @author: Guos
+ * @time: 2025/11/3 上午11:22
+ */
+@Data
+public class ComprehensiveStatisticsParam {
+
+    /**
+     * 统计维度
+     */
+    private Integer dimension;
+
+    /**
+     * 开始时间
+     */
+    private Date startTime;
+
+    /**
+     * 结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * id 在不同的维度下,id代表的意义不同
+     */
+    private Long id;
+}
+

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor