Ver Fonte

Merge remote-tracking branch 'refs/remotes/origin/master' into 企微聊天

# Conflicts:
#	fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
#	fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
#	fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
#	fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
#	fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
#	fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
#	fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml
ct há 2 semanas atrás
pai
commit
eac3d85c98
97 ficheiros alterados com 1846 adições e 576 exclusões
  1. 0 2
      README.md
  2. 33 8
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  3. 14 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyDeductController.java
  4. 1 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyRechargeController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  6. 5 1
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  7. 35 0
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  8. 20 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  9. 16 4
      fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java
  10. 10 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java
  11. 51 25
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  12. 9 0
      fs-admin/src/main/java/com/fs/task/SgTestController.java
  13. 2 2
      fs-admin/src/main/resources/application.yml
  14. 1 0
      fs-admin/src/main/resources/logback.xml
  15. 6 5
      fs-common/pom.xml
  16. 1 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  17. 43 0
      fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java
  18. 23 10
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  19. 1 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  20. 3 2
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  21. 9 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  22. 99 3
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  23. 5 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingServiceImpl.java
  24. 1 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  25. 1 1
      fs-qw-voice/pom.xml
  26. 15 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  27. 13 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  28. 13 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  29. 14 0
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  30. 2 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  31. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java
  32. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  33. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  34. 73 3
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  35. 2 1
      fs-service/src/main/java/com/fs/company/service/ICompanyRechargeService.java
  36. 6 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  37. 25 2
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  38. 9 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRechargeServiceImpl.java
  39. 187 27
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  40. 95 63
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  41. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java
  42. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  43. 8 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  44. 17 17
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  45. 2 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  46. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  47. 4 3
      fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java
  48. 6 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  49. 182 154
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  50. 18 1
      fs-service/src/main/java/com/fs/course/vo/FsCourseTrafficLogListVO.java
  51. 23 0
      fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java
  52. 15 0
      fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java
  53. 45 0
      fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java
  54. 29 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  55. 3 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  56. 161 0
      fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java
  57. 3 0
      fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java
  58. 3 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  59. 7 0
      fs-service/src/main/java/com/fs/qw/domain/QwGroupChat.java
  60. 5 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  61. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
  62. 9 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  63. 59 2
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  64. 1 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  65. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  66. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java
  67. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  68. 8 0
      fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java
  69. 6 1
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java
  70. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  71. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  72. 4 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  73. 80 5
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  74. 3 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java
  75. 10 0
      fs-service/src/main/java/com/fs/sop/domain/QwSop.java
  76. 7 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopMapper.java
  77. 3 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  78. 1 0
      fs-service/src/main/java/com/fs/sop/vo/SopUserLogsVo.java
  79. 10 1
      fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
  80. 17 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxCreateRoomMsgDTO.java
  81. 11 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxCreateRoomRespDTO.java
  82. 1 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatIdToRoomIdDTO.java
  83. 11 0
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceNew.java
  84. 7 0
      fs-service/src/main/resources/application-config-dev-jnlzjk.yml
  85. 5 3
      fs-service/src/main/resources/application-config-druid-hst.yml
  86. 0 7
      fs-service/src/main/resources/application-config-druid-jnlzjk.yml
  87. 7 1
      fs-service/src/main/resources/application-druid-jnlzjk.yml
  88. 8 0
      fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml
  89. 2 1
      fs-service/src/main/resources/mapper/company/CompanyMapper.xml
  90. 11 8
      fs-service/src/main/resources/mapper/company/CompanyRechargeMapper.xml
  91. 56 181
      fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml
  92. 3 2
      fs-service/src/main/resources/mapper/course/FsCourseLinkMapper.xml
  93. 35 2
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  94. 4 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  95. 14 0
      fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml
  96. 2 2
      fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml
  97. 35 0
      fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

+ 0 - 2
README.md

@@ -53,7 +53,6 @@ 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',
@@ -72,7 +71,6 @@ CREATE TABLE IF NOT EXISTS user_daily_stats (
     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 '记录更新时间',
-    UNIQUE KEY uk_user_date (user_id, statistics_time) 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 '用户每日统计数据表';

+ 33 - 8
fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java

@@ -1,15 +1,11 @@
 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.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 
@@ -26,12 +22,41 @@ public class StatisticManageController {
     @Resource
     private IStatisticManageService statisticManageService;
 
+    /**
+     * 获取统计信息
+     * @param param
+     * @return
+     */
     @PostMapping("/statisticMain")
     public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
         Assert.notNull(param.getDimension(), "请选择统计维度");
-        statisticManageService.executeTask();
-//        return R.ok().put("data", statisticManageService.statisticMain(param));
-        return R.ok("success");
+        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());

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

@@ -125,7 +125,7 @@ public class CompanyRechargeController extends BaseController
 
             // 同步redis缓存
             // 注意:在进行充值审核之前,需要先执行一下定时任务同步缓存数据到数据库,再进行后续操作,否则金额不正确
-            R r = companyRechargeService.syncUpdateRedisCompanyRecharge(company, companyRecharge.getMoney());
+            R r = companyRechargeService.syncUpdateRedisCompanyRecharge(company, companyRecharge.getMoney(), 1);
             if(!"200".equals(r.get("code").toString())){
                 return r;
             }

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

@@ -42,7 +42,7 @@ public class FsCoursePlaySourceConfigController extends BaseController {
     private final TokenService tokenService;
     private final ISysConfigService configService;
 
-    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:list')")
+//    @PreAuthorize("@ss.hasPermi('course:playSourceConfig:list')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required = false) String name,
                               @RequestParam(required = false) String appid,

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

@@ -70,7 +70,11 @@ public class FsCourseTrafficLogController extends BaseController
             param.setYear(Integer.parseInt(parts[0]));
             param.setMonth(Integer.parseInt(parts[1]));
         }
-        List<FsCourseTrafficLogListVO> list = fsCourseTrafficLogService.selectTrafficByCompany(param);
+        List<FsCourseTrafficLogListVO> list = fsCourseTrafficLogService.selectTrafficNew(param);
+        //格式化每个记录的流量为 GB 字符串
+        for (FsCourseTrafficLogListVO vo : list) {
+            vo.formatTraffic();
+        }
         ExcelUtil<FsCourseTrafficLogListVO> util = new ExcelUtil<FsCourseTrafficLogListVO>(FsCourseTrafficLogListVO.class);
         return util.exportExcel(list, "短链课程流量记录数据");
     }

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

@@ -217,6 +217,41 @@ public class FsCompanyController extends BaseController {
 
     }
 
+
+    /**
+     * @Description: 红包充值功能 因公司余额的变动关联的地方太多,现在把充值红包的金额单独提出来 不合计到公司余额中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 11:08
+     */
+    @PreAuthorize("@ss.hasPermi('his:company:redRecharge')")
+    @Log(title = "红包充值", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/redRecharge")
+    @Transactional
+    @RepeatSubmit
+    public R redRecharge(@RequestBody CompanyRechargeParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyRecharge redRecharge=new CompanyRecharge();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        redRecharge.setRechargeNo(orderSn);
+        redRecharge.setCompanyId(param.getCompanyId());
+        redRecharge.setMoney(param.getMoney());
+        redRecharge.setCreateUserId(loginUser.getUser().getUserId());
+        redRecharge.setIsAudit(0);
+        redRecharge.setStatus(0);
+        redRecharge.setRemark(param.getRemark());
+        redRecharge.setPayType(3);
+        redRecharge.setBusinessType(1);// 红包充值
+        rechargeService.insertCompanyRecharge(redRecharge);
+        return R.ok("提交成功,等待审核");
+
+    }
+
     @PreAuthorize("@ss.hasPermi('his:company:deduct')")
     @Log(title = "企业扣款", businessType = BusinessType.INSERT)
     @PostMapping(value = "/deduct")

+ 20 - 13
fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java

@@ -137,21 +137,28 @@ public class FsCompanyRechargeController extends BaseController
         companyRecharge.setRemark(param.getRemark());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(companyRecharge.getIsAudit()==1){
-            Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
-            company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
-            companyRecharge.setBalance(company.getMoney());
-            companyService.updateCompany(company);
-            //生成流水
-            CompanyMoneyLogs log=new CompanyMoneyLogs();
-            log.setCompanyId(companyRecharge.getCompanyId());
-            log.setMoney(companyRecharge.getMoney());
-            log.setRemark(companyRecharge.getRemark());
-            log.setLogsType(1);
-            log.setBalance(company.getMoney());
-            log.setCreateTime(new Date());
-            moneyLogsService.insertCompanyMoneyLogs(log);
+            if(1==companyRecharge.getBusinessType()){// 红包充值
+                // 更新红包充值余额redis字段
+                companyService.redPacketTopUpCompany(companyRecharge.getCompanyId(),companyRecharge.getMoney());
+            }else {
+                Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
+                company.setMoney(company.getMoney().add(companyRecharge.getMoney()));
+                companyRecharge.setBalance(company.getMoney());
+                companyService.updateCompany(company);
+                //生成流水
+                CompanyMoneyLogs log=new CompanyMoneyLogs();
+                log.setCompanyId(companyRecharge.getCompanyId());
+                log.setMoney(companyRecharge.getMoney());
+                log.setRemark(companyRecharge.getRemark());
+                log.setLogsType(1);
+                log.setBalance(company.getMoney());
+                log.setCreateTime(new Date());
+                moneyLogsService.insertCompanyMoneyLogs(log);
+
+            }
             companyRecharge.setPayTime(new Date());
             companyRecharge.setStatus(1);
+
         }
 
         companyRecharge.setAuditTime(new Date());

+ 16 - 4
fs-admin/src/main/java/com/fs/course/task/CompanyBalanceTask.java → fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java

@@ -1,14 +1,13 @@
-package com.fs.course.task;
+package com.fs.his.task;
 
 import com.fs.company.service.ICompanyService;
 import com.fs.course.service.BalanceRollbackErrorService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.context.event.ApplicationReadyEvent;
-import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 /**
- * @description: 公司余额同步定时任务
+ * @description: 公司余额同步定时任务 (红包余额)
  * @author: Xgb
  * @createDate: 2025/10/22
  * @version: 1.0
@@ -59,6 +58,19 @@ public class CompanyBalanceTask {
 
     }
 
+    /**
+     * @Description: 每天晚上0点定时获取获取公司余额,记录到数据库中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 10:43
+     */
+    public void recordRedPacketBalance() {
+        companyService.recordRedPacketBalance();
+    }
+
+
+
 
 
 }

+ 10 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java

@@ -11,6 +11,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -192,7 +193,15 @@ public class FsStorePaymentScrmController extends BaseController
         }
 
         if(payment.getPayTypeCode().equals("weixin")){
-            String json = configService.selectConfigByKey("store.pay");
+            String json;
+            if (CloudHostUtils.hasCloudHostName("康年堂")){
+                json = configService.selectConfigByKey("his.pay");
+            } else {
+                json = configService.selectConfigByKey("store.pay");
+            }
+            if (StringUtils.isBlank(json)) {
+                return R.error("缺少支付相关配置");
+            }
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             if (payment.getPayMode()!=null&&payment.getPayMode().equals("hf")){
                 String huifuId="";

+ 51 - 25
fs-admin/src/main/java/com/fs/task/FsCompanyTask.java

@@ -3,6 +3,8 @@ 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.domain.CompanyMoneyLogs;
+import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -29,6 +31,9 @@ public class FsCompanyTask {
     @Autowired
     private RedisTemplate<String, Object> redisTemplate;
 
+    @Autowired
+    private CompanyMoneyLogsMapper moneyLogsMapper;
+
     public void refreshCompanyMoney() {
         LocalDateTime now = LocalDateTime.now();
         // 获取上一个小时的开始时间
@@ -49,38 +54,59 @@ public class FsCompanyTask {
      * 同步公司缓存余额到公司数据表
      */
     public void syncRedisCompanyMoneyToDB(){
-            // 获取所有的公司余额key
-            String companyMoneyKeyAll = FsConstants.COMPANY_MONEY_KEY + "*";
-            Collection<String> keys = redisCache.keys(companyMoneyKeyAll);
+        // 获取所有的公司余额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());
+        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));
+                }
 
-                    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));
+                // 新增账户流水
+                // 直接循环保存,由于是定时任务执行,暂时不优化
+                for (Company company : companies) {
+                    if(moneyMap.containsKey(company.getCompanyId()) && !(moneyMap.get(company.getCompanyId()).compareTo(company.getMoney()) == 0)){
+                        CompanyMoneyLogs log = new CompanyMoneyLogs();
+                        log.setCompanyId(company.getCompanyId());
+                        log.setRemark("扣除红包金额-同步");
+                        // 让减出来的金额为负数
+                        if(moneyMap.get(company.getCompanyId()).compareTo(company.getMoney()) <= 0 ){
+                            log.setMoney(moneyMap.get(company.getCompanyId()).subtract(company.getMoney()));
+                        } else {
+                            log.setMoney(company.getMoney().subtract(moneyMap.get(company.getCompanyId())));
+                        }
+                        log.setLogsType(15);
+                        log.setBalance(moneyMap.get(company.getCompanyId()));
+                        log.setCreateTime(new Date());
+                        moneyLogsMapper.insertCompanyMoneyLogs(log);
                     }
+                }
 
-                    // 使用Stream进行匹配赋值
-                    List<Company> collect = companies.stream()
-                            .filter(company -> moneyMap.containsKey(company.getCompanyId()))
-                            .peek(company -> company.setMoney(moneyMap.get(company.getCompanyId()))).collect(Collectors.toList());
+                // 使用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);
-                    }
+                // 保存公司余额
+                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();
+    }
 
 }

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

@@ -4,10 +4,10 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: dev
+    active: dev
 #    active: druid-hdt
 #    active: druid-yzt
-    active: druid-sxjz-test
+#    active: druid-sxjz-test
 #    active: druid-sft
 #    active: druid-fby
 #    active: dev

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

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

+ 1 - 0
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -14,6 +14,7 @@ public interface FsConstants {
 
     // 公司余额redis key "company:money:" + company.getCompanyId()
     String COMPANY_MONEY_KEY = "company:money:";
+    String COMPANY_MONEY_DATE_KEY = "company:money:date:";
     // 公司余额redis 锁
     String COMPANY_MONEY_LOCK = "company_money_lock:";
     // 看客统计  按公司分组 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;

+ 43 - 0
fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java

@@ -0,0 +1,43 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsRedPacket;
+import com.fs.his.service.IFsRedPacketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: 红包信息
+ * @author: Xgb
+ * @createDate: 2025/11/4
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/his/redPacket")
+public class FsRedPacketController extends BaseController {
+
+    @Autowired
+    private IFsRedPacketService fsRedPacketService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @GetMapping("/info")
+    public R info() {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        Company company = loginUser.getCompany();
+        FsRedPacket redPacket=fsRedPacketService.getInfo(company.getCompanyId());
+        return  R.ok().put("data",redPacket);
+    }
+
+
+
+}

+ 23 - 10
fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java

@@ -22,6 +22,7 @@ import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -46,6 +47,9 @@ public class FsQwCourseWatchLogController extends BaseController
     private SopUserLogsMapper sopUserLogsMapper;
     @Autowired
     private IQwWatchLogService qwWatchLogService;
+
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
     /**
      * 查询短链课程看课记录列表
      */
@@ -99,25 +103,34 @@ public class FsQwCourseWatchLogController extends BaseController
     }
 
     @GetMapping("/qwWatchLogStatisticsList")
-    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
-    {
+    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId( loginUser.getCompany().getCompanyId());
-        if (param.getSTime()==null||param.getETime()==null){
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (param.getSTime() == null || param.getETime() == null) {
             return getDataTable(new ArrayList<>());
         }
-        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+        //济南联志健康 数据排除转接用户
+        if ("济南联志健康".equals(signProjectName)) {
+            return qwWatchLogService.selectQwWatchLogStatisticsListVOExcludeTransfer(param);
+        } else {
+            return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+        }
     }
+
     @GetMapping("/myQwWatchLogStatisticsList")
-    public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
-    {
+    public TableDataInfo myQwWatchLogStatisticsList(QwWatchLogStatisticsListParam param) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
         param.setCompanyUserId(loginUser.getUser().getUserId());
-        if (param.getSTime()==null||param.getETime()==null){
+        if (param.getSTime() == null || param.getETime() == null) {
             return getDataTable(new ArrayList<>());
         }
-        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+        //济南联志健康 数据排除转接用户
+        if ("济南联志健康".equals(signProjectName)) {
+            return qwWatchLogService.selectQwWatchLogStatisticsListVOExcludeTransfer(param);
+        } else {
+            return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+        }
     }
 
 

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

@@ -241,6 +241,7 @@ public class QwSopTempController extends BaseController
     }
 
     @PreAuthorize("@ss.hasPermi('qw:sopTemp:edit') or @ss.hasPermi('qw:sopTemp:myEdit') or @ss.hasPermi('qw:sopTemp:deptEdit')")
+    @Log(title = "SOP模板天数调整", businessType = BusinessType.UPDATE)
     @PostMapping("/sortDay")
     public AjaxResult sortDay(@RequestBody List<SortDayVo> list){
         qwSopTempService.sortDay(list);

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

@@ -1,5 +1,6 @@
 package com.fs.app.service;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BaseException;
@@ -295,7 +296,7 @@ public class IpadSendServer {
             return false;
         }
 
-        if (qwSopLogs.getSendType() != 12 && noSop) {
+        if (qwSopLogs.getSendType() != 12 && qwSopLogs.getSendType() != 6 && noSop) {
             // 客户的信息
 //            QwExternalContactHParam contactHParam = new QwExternalContactHParam();
 //            contactHParam.setUserId(qwUser.getQwUserId().trim());
@@ -381,7 +382,7 @@ public class IpadSendServer {
                     break;
             }
         } catch (Exception e) {
-            log.error("ID:" + qwSopLogs.getId() + "-发送失败QW_SOP_ID:" + qwSopLogs.getId(), e);
+            log.error("ID:{}-发送失败QW_SOP_ID:{},content:{},vo:{}", qwSopLogs.getId(), qwSopLogs.getId(), JSON.toJSONString(content), JSON.toJSONString(vo), e);
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + e.getMessage());
         }

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

@@ -40,6 +40,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import com.fs.app.task.qwTask;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -77,6 +78,8 @@ public class CommonController {
 
     @Autowired
     private IQwSopLogsService qwSopLogsService;
+    @Autowired
+    private qwTask qwTask1;
 
     @Autowired
     private QwSopMapper qwSopMapper;
@@ -382,4 +385,10 @@ public class CommonController {
         return R.ok();
     }
 
+    @GetMapping("/autoPullGroup")
+    public R autoPullGroup(){
+        qwTask1.autoPullGroup();
+        return R.ok();
+    }
+
 }

+ 99 - 3
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -1,10 +1,15 @@
 package com.fs.app.task;
 
 import com.fs.app.taskService.*;
-import com.fs.qw.service.IQwExternalErrRetryService;
-import com.fs.qw.service.IQwGroupMsgService;
-import com.fs.qw.service.IQwWorkUserService;
+import com.fs.common.utils.PubFun;
+import com.fs.ipad.IpadSendUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwGroupChat;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.*;
+import com.fs.sop.domain.QwSop;
 import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.IQwSopTagService;
 import com.fs.sop.service.ISopUserLogsService;
@@ -20,7 +25,15 @@ import org.springframework.stereotype.Component;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static com.fs.qw.service.impl.AsyncChatSopService.MAX_GROUP_USER_NUM;
+import static com.fs.qw.service.impl.AsyncChatSopService.MAX_GROUP_NUM;
 
 /**
  * 企业微信SOP定时任务管理类
@@ -33,6 +46,20 @@ import java.util.List;
 @Slf4j
 public class qwTask {
 
+    @Autowired
+    private QwSopMapper qwSopMapper;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwUserService qwUserService;
+    @Autowired
+    private IQwGroupChatService qwGroupChatService;
+
+    @Autowired
+    private IpadSendUtils ipadSendUtils;
+
     @Autowired
     private QwSopServiceImpl qwSopService;
 
@@ -385,4 +412,73 @@ public class qwTask {
         syncQwExternalContactService.syncQwExternalContactUnionid();
 
     }
+
+    /**
+     * 定时拉人进群
+     */
+    @Scheduled(cron = "0 0 16 * * ?")
+    public void autoPullGroup(){
+        //  拉群 ,①保持群号 ②每日拉群 ③创建建群记录
+        // 计算每个人最大拉人数量
+        long maxNum = (long) MAX_GROUP_NUM * MAX_GROUP_USER_NUM;
+        // 获取当前时间
+        LocalDate now = LocalDate.now();
+        // 获取需要自动拉群的SOP任务
+        List<QwSop> list = qwSopMapper.selectGroup(now);
+        if(list == null || list.isEmpty()) return;
+        list.forEach(sop -> {
+            // 获取这个SOP下面的企微ID
+            List<Long> qwUserIdList = Arrays.stream(sop.getQwUserIds().split(",")).map(Long::parseLong).distinct().collect(Collectors.toList());
+            // 获取企微ID下面的所有用户
+            List<QwExternalContact> qwExternalContactList = qwExternalContactService.selectQwUserAndLevel(qwUserIdList, Arrays.asList(sop.getAutoGroupLevel().split(",")), sop.getAutoUserReg() == 1);
+            // 根据企微ID进行分组
+            Map<Long, List<QwExternalContact>> qwUserMap = PubFun.listToMapByGroupList(qwExternalContactList, QwExternalContact::getQwUserId);
+            // 获取企微列表
+            List<QwUser> qwUserList = qwUserService.selectQwUserByIds(qwUserIdList);
+            try {
+                // 每个企微都拉人
+                qwUserList.stream().filter(qwUser -> qwUserMap.containsKey(qwUser.getId())).forEach(qwUser -> {
+                    List<QwExternalContact> userList = qwUserMap.get(qwUser.getId()).stream().limit(maxNum).collect(Collectors.toList());
+                    // 创建群 如果没人或者人数没达到满群的要求,不进行建群
+                    if(userList.isEmpty() || userList.size() < MAX_GROUP_USER_NUM) return;
+                    List<QwGroupChat> chatList = qwGroupChatService.selectSopAndQwUser(qwUser.getQwUserId(), sop.getId());
+                    int groupNum = 0;
+                    if (chatList != null && !chatList.isEmpty()) {
+                        groupNum = extractLastNumber(chatList.get(0).getName())  == null ? 0 : extractLastNumber(chatList.get(0).getName());
+                    }
+                    try {
+                        // 建群
+                        ipadSendUtils.createRoom(sop, sop.getGroupName(), qwUser, userList, MAX_GROUP_NUM, MAX_GROUP_USER_NUM,groupNum);
+                    }catch (Exception e){
+                        log.error("群聊拉人进群错误:{},企微ID:{},企微名称:{},外部联系人:{}", e.getMessage(), qwUser.getId(), qwUser.getQwUserName(), PubFun.listToNewList(userList, QwExternalContact::getId));
+                        log.error("群聊拉人进群错误", e);
+                    }
+                });
+            }catch (Exception e){
+                log.error("SOP拉人进群错误", e);
+            }
+        });
+    }
+    /**
+     * 提取字符串中最后的数字
+     * @param str 待处理的字符串
+     * @return 提取到的数字,若没有数字则返回null
+     */
+    public static Integer extractLastNumber(String str) {
+        if (str == null || str.isEmpty()) {
+            return null;
+        }
+
+        // 正则表达式:匹配字符串末尾的一个或多个数字
+        Pattern pattern = Pattern.compile("\\d+$");
+        Matcher matcher = pattern.matcher(str);
+
+        if (matcher.find()) {
+            String numberStr = matcher.group();
+            return Integer.parseInt(numberStr);
+        }
+
+        // 没有找到数字
+        return null;
+    }
 }

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

@@ -13,6 +13,7 @@ import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.sop.params.QwRatingConfig;
 import com.fs.sop.service.IQwSopTempDayService;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.vo.QwRatingVO;
 import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
@@ -61,6 +62,9 @@ public class QwExternalContactRatingServiceImpl implements QwExternalContactRati
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
     @Autowired
     private ExecutorService sopRatingExecutor;  // 自定义线程池
 
@@ -247,6 +251,7 @@ public class QwExternalContactRatingServiceImpl implements QwExternalContactRati
             CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                 try {
                     qwExternalContactMapper.batchUpdateQwExternalContact(batchList);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByLevel(batchList);
                     log.info("成功更新评级数据,起始索引: {}, 数量: {}", finalI, batchList.size());
                 } catch (Exception e) {
                     log.error("批量更新异常,批次起始索引: {}", finalI, e);

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

@@ -298,6 +298,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             log.info("没有需要处理的 SOP 用户日志。");
             return;
         }
+        sopUserLogsVos = sopUserLogsVos.stream().filter(e -> e.getFilterMode() == 1 || (e.getFilterMode() == 2 && StringUtils.isNotEmpty(e.getChatId()))).collect(Collectors.toList());
 
         String[] array = sopUserLogsVos.stream().map(SopUserLogsVo::getChatId).filter(StringUtils::isNotEmpty).toArray(String[]::new);
         Map<String, QwGroupChat> groupChatMap = new HashMap<>();

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

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

+ 15 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -192,6 +192,21 @@ public class ApisFsUserCourseVideoController extends BaseController {
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+
         return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 

+ 13 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -101,6 +101,19 @@ public class FsUserCourseVideoController {
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
         return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 

+ 13 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -160,6 +160,19 @@ public class ApisFsUserCourseVideoController extends BaseController {
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
         return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 

+ 14 - 0
fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -165,6 +165,20 @@ public class FsUserCourseVideoController {
         if (qwUser==null||qwUser.getCompanyId()==null){
             return R.error("无权限");
         }
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
         return fsUserCourseVideoService.createRoomMiniLink(param);
     }
 

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

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

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java

@@ -75,4 +75,7 @@ public class CompanyRecharge extends BaseEntity
 
     private String remark;
     private String imgs;
+
+    /** 业务类型 1普通 2-红包充值 */
+    private Integer businessType;
 }

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

@@ -118,4 +118,6 @@ public interface CompanyDeptMapper
     CompanyDept selectDeptNameBydeptName(@Param("deptName") String deptName);
 
     void deleteCompanyDeptByCompanyIds(Long[] companyIds);
+
+    List<CompanyDept> selectCompanyDeptByIds(@Param("depts")List<Long> deptIds);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java

@@ -150,6 +150,9 @@ public interface CompanyRechargeMapper
             "<if test = 'maps.payType != null  '> " +
             "and r.pay_type = #{maps.payType}" +
             "</if>" +
+            "<if test = 'maps.businessType != null  '> " +
+            "and r.business_type = #{maps.businessType}" +
+            "</if>" +
 
             "<if test = 'maps.createTime != null  '> " +
             "and   DATE(r.create_time) = DATE(#{maps.createTime})" +

+ 73 - 3
fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java

@@ -16,15 +16,56 @@ import java.util.List;
  */
 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);
 
     /**
@@ -34,5 +75,34 @@ public interface StatisticManageMapper {
      * @param endTime
      * @param userIds
      */
-    List<ComprehensiveStatisticsDTO> getStatisticNumByPersonal(@Param("dimension")Integer dimension, @Param("startTime") Date startTime, @Param("endTime") Date endTime, @Param("userIds") Long... 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);
+
 }

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

@@ -55,9 +55,10 @@ public interface ICompanyRechargeService
      * 同步到缓存余额
      * @param company 公司
      * @param rechargeMoney 充值金额
+     * @param type 同步类型,1-充值,2-扣款
      * @return 是否成功
      */
-    R syncUpdateRedisCompanyRecharge(Company company, BigDecimal rechargeMoney);
+    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

@@ -170,6 +170,12 @@ public interface ICompanyService
 
     void syncCompanyBalance();
 
+    void redPacketTopUpCompany(Long companyId, BigDecimal money);
+
+    void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark);
+
+    void recordRedPacketBalance();
+
     /**
      * 批量更新公司余额
      * @param list 公司列表

+ 25 - 2
fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java

@@ -1,9 +1,9 @@
 package com.fs.company.service;
 
+import com.fs.company.domain.CompanyDeptUserInfo;
 import com.fs.statis.param.ComprehensiveStatisticsParam;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * @description:
@@ -12,10 +12,33 @@ import java.util.Map;
  */
 public interface IStatisticManageService {
 
-    List<Map<String, Object>> statisticMain(ComprehensiveStatisticsParam param);
+    /**
+     * 统计主页
+     * @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();
+
 }

+ 9 - 4
fs-service/src/main/java/com/fs/company/service/impl/CompanyRechargeServiceImpl.java

@@ -109,9 +109,9 @@ public class CompanyRechargeServiceImpl implements ICompanyRechargeService
     }
 
     @Override
-    public R syncUpdateRedisCompanyRecharge(Company company, BigDecimal rechargeMoney) {
+    public R syncUpdateRedisCompanyRecharge(Company company, BigDecimal rechargeMoney, Integer type) {
         if(company.getCompanyId() == null){
-            log.error("公司充值-审核-同步更新到缓存,参数错误,公司id:{}, 充值余额:{}", company.getCompanyId(), rechargeMoney);
+            log.error("公司充值/扣款-审核-同步更新到缓存,参数错误,公司id:{}, 充值/扣款余额:{},类型:{}", null, rechargeMoney, type);
             return R.error("公司id为空");
         }
 
@@ -136,13 +136,18 @@ public class CompanyRechargeServiceImpl implements ICompanyRechargeService
                     redisMoney = company.getMoney();
                 }
 
-                newMoney = redisMoney.add(rechargeMoney);
+                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);
+            log.error("公司充值/扣款-审核-同步更新到缓存,参数错误,请求异常,异常信息:{}", e.getMessage(), e);
             return R.error("审核失败,请重试");
         } finally {
             if (lockAcquired && lock.isHeldByCurrentThread()) {

+ 187 - 27
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.company.service.impl;
 import java.math.BigDecimal;
 import java.time.LocalTime;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.util.ObjectUtil;
@@ -42,6 +43,8 @@ import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -104,9 +107,15 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Autowired
     private TransactionTemplate transactionTemplate;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Override
     public List<OptionsVO> selectAllCompanyList(Long deptId) {
         return companyMapper.selectAllCompanyList(deptId);
@@ -672,7 +681,16 @@ public class CompanyServiceImpl implements ICompanyService
 
     @Override
     public List<CompanyVO> selectCompanyListVO(Company company) {
-        return companyMapper.selectCompanyListVO(company);
+
+        List<CompanyVO> companyVOList=companyMapper.selectCompanyListVO(company);
+        companyVOList.forEach(companyVO -> {
+            String redPackageMoney = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+companyVO.getCompanyId());
+            if(redPackageMoney!=null){
+                companyVO.setRedPackageMoney(new BigDecimal(redPackageMoney));
+
+            }
+        });
+        return companyVOList;
     }
 
     @Override
@@ -1245,17 +1263,48 @@ public class CompanyServiceImpl implements ICompanyService
         if(companyId!=null&&companyId>0){
             Company company=companyMapper.selectCompanyByIdForUpdate(companyId);
             if(company!=null){
-                logger.info("退回红包金额:"+money);
-                company.setMoney(company.getMoney().add(money));
-                companyMapper.updateCompany(company);
-                CompanyMoneyLogs log=new CompanyMoneyLogs();
-                log.setCompanyId(company.getCompanyId());
-                log.setRemark("退回红包金额");
-                log.setMoney(money);
-                log.setLogsType(16);
-                log.setBalance(company.getMoney());
-                log.setCreateTime(new Date());
-                moneyLogsMapper.insertCompanyMoneyLogs(log);
+                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + company.getCompanyId();
+                // 加锁,与看课发放红包的加锁保持一致
+                RLock lock = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + company.getCompanyId());
+                boolean lockAcquired = false;
+                try {
+                    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();
+                        }
+                        BigDecimal newMoney = redisMoney.add(money);
+                        logger.info("退回红包金额:"+money);
+                        company.setMoney(newMoney);
+                        companyMapper.updateCompany(company);
+                        CompanyMoneyLogs log=new CompanyMoneyLogs();
+                        log.setCompanyId(company.getCompanyId());
+                        log.setRemark("退回红包金额");
+                        log.setMoney(money);
+                        log.setLogsType(16);
+                        log.setBalance(newMoney);
+                        log.setCreateTime(new Date());
+                        moneyLogsMapper.insertCompanyMoneyLogs(log);
+
+                        redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+                    }
+                } catch (Exception e) {
+                    logger.error("退回的红包同步增加到缓存和数据表,参数错误,请求异常,异常信息:{}", e.getMessage(), e);
+                } finally {
+                    if (lockAcquired && lock.isHeldByCurrentThread()) {
+                        try {
+                            lock.unlock();
+                        } catch (IllegalMonitorStateException e) {
+                            logger.warn("尝试释放非当前线程持有的锁: companyId:{}", company.getCompanyId());
+                        }
+                    }
+                }
+
             }
         }
     }
@@ -1318,12 +1367,12 @@ public class CompanyServiceImpl implements ICompanyService
                             String moneyStr = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY + company.getCompanyId());
                             if (StringUtils.isNotEmpty(moneyStr)) {
                                 BigDecimal redisMoney = new BigDecimal(moneyStr);
-                                BigDecimal dbMoney = company.getMoney();
+                                BigDecimal dbMoney = company.getRedPackageMoney();
                                 BigDecimal amount = redisMoney.subtract(dbMoney); // 计算差额
 
                                 // 只有当余额不一致时才更新
                                 if (amount.compareTo(BigDecimal.ZERO) != 0) {
-                                    company.setMoney(redisMoney);
+                                    company.setRedPackageMoney(redisMoney);
                                     int updateResult = companyMapper.updateCompany(company);
                                     if(updateResult != 1){
                                         logger.error("更新公司余额失败,公司ID: {}", company.getCompanyId());
@@ -1331,19 +1380,9 @@ public class CompanyServiceImpl implements ICompanyService
                                     }
 
                                     // 记录余额变更日志
-                                    CompanyMoneyLogs log = new CompanyMoneyLogs();
-                                    log.setCompanyId(company.getCompanyId());
-                                    log.setRemark("同步公司余额,差额: " + amount);
-                                    log.setMoney(amount);
-                                    log.setLogsType(15);
-                                    log.setBalance(redisMoney);
-                                    log.setCreateTime(new Date());
-
-                                    int logResult = moneyLogsMapper.insertCompanyMoneyLogs(log);
-                                    if(logResult != 1){
-                                        logger.error("添加公司余额日志失败,公司ID: {}", company.getCompanyId());
-                                        throw new RuntimeException("添加公司余额日志失败");
-                                    }
+                                    String remark = "同步公司余额,差额: " + amount+"(正数为增加,负数为扣减)";
+                                    // 实际不发生交易只是从缓存同步金额到数据库中 交易金额登记为0,备注清楚同步的金额
+                                    asyncRecordBalanceLog(company.getCompanyId(),new BigDecimal(0),17,redisMoney,remark);
                                 }
                             }
                             return null;
@@ -1354,6 +1393,127 @@ public class CompanyServiceImpl implements ICompanyService
                 }));
     }
 
+    /**
+     * @Description: 红包充值
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 14:01
+     */
+    @Override
+    public void redPacketTopUpCompany(Long companyId, BigDecimal money) {
+
+        String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + companyId;
+
+        // 加锁:增加余额
+        RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + companyId);
+        boolean lockAcquired = false;
+        try {
+            if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
+                lockAcquired = true;
+                BigDecimal originalMoney;
+                // 获取当前余额
+                String moneyStr = redisCache.getCacheObject(companyMoneyKey);
+                if (StringUtils.isNotEmpty(moneyStr)) {
+                    originalMoney = new BigDecimal(moneyStr);
+                }else {
+                    // 缓存没有值,重启系统恢复redis数据 保证数据正确性
+                    logger.error("红包余额充值获取redis余额缓存异常,异常请求参数companyId:{},money:{}",companyId,money);
+                    throw new RuntimeException("红包余额充值获取redis余额缓存异常");
+                }
+
+                // 增加余额
+                BigDecimal newMoney = originalMoney.add(money);
+                redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+
+                // 异步登记余额添加日志
+                asyncRecordBalanceLog(companyId,money,16,newMoney,"红包充值");
+
+            } else {
+                logger.error("获取redis锁失败,异常请求参数companyId:{},money:{}",companyId,money);
+                throw new RuntimeException("服务繁忙,请稍后重试");
+            }
+        } catch (Exception e) {
+            logger.error("增加余额失败: 异常请求参数companyId:{},money:{}",companyId,money, e);
+            throw new RuntimeException("系统异常,请稍后重试");
+        }finally {
+            // 只有在成功获取锁的情况下才释放锁
+            if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                try {
+                    lock1.unlock();
+                } catch (IllegalMonitorStateException e) {
+                    logger.error("尝试释放非当前线程持有的锁: 异常请求参数companyId:{},money:{}",companyId,money);
+                }
+            }
+        }
+
+
+    }
+
+
+    /**
+     * 异步登记余额添加日志  xgb
+     * @param companyId 公司ID
+     * @param money 变更金额
+     * @param balance 当前余额
+     * @param remark 备注信息
+     * @param logType 16-红包余额充值 15-红包余额扣除 17-同步公司余额
+     */
+    @Async
+    @Override
+    public void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark) {
+        try {
+            CompanyMoneyLogs log = new CompanyMoneyLogs();
+            log.setCompanyId(companyId);
+            log.setRemark(remark);
+            log.setMoney(money);
+            log.setLogsType(logType); // 同步余额
+            log.setBalance(balance);
+            log.setCreateTime(new Date());
+            moneyLogsMapper.insertCompanyMoneyLogs(log);
+            logger.info("异步登记余额日志成功 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
+                    companyId, money, balance, remark);
+        } catch (Exception e) {
+            logger.error("异步登记余额日志失败 - 公司ID: {}, 金额: {}, 余额: {}, 备注: {}",
+                    companyId, money, balance, remark, e);
+        }
+    }
+
+    /**
+     * @Description: 每天晚上0点定时获取获取公司余额,记录到数据库中
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 10:51
+     */
+    @Override
+    public void recordRedPacketBalance() {
+        Company query = new Company();
+        query.setIsDel(0);
+        // 查询公司列表 同步公司余额
+        List<Company> companyList = companyMapper.selectCompanyList(query);
+        Optional.ofNullable(companyList).ifPresent(list -> list.forEach(company -> {
+            transactionTemplate.execute(status -> {
+                String time=DateUtils.getTime();
+                String moneyStr = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY + company.getCompanyId());
+                if (StringUtils.isNotEmpty(moneyStr)) {
+                    // 存到缓存中,有效期25小时
+                    redisCache.setCacheObject(FsConstants.COMPANY_MONEY_DATE_KEY + company.getCompanyId()+":"+DateUtils.getDate(), moneyStr, 25, TimeUnit.HOURS);
+                    // 实际不发生交易只是从缓存获取当天余额报错25小时 交易金额登记为0,备注清楚同步的金额
+                    String remark = "时间:" + time +",当前公司余额,金额: " + moneyStr;
+                    BigDecimal money = new BigDecimal(moneyStr);
+                    asyncRecordBalanceLog(company.getCompanyId(),new BigDecimal(0),18,money,remark);
+                }
+                return null;
+            });
+
+
+        }));
+
+
+    }
+
+
     @Override
     public void batchUpdateCompany(List<Company> list) {
         companyMapper.batchUpdateCompany(list);

+ 95 - 63
fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java

@@ -1,18 +1,15 @@
 package com.fs.company.service.impl;
 
-import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.date.StopWatch;
 import com.fs.common.enums.DimensionEnum;
-import com.fs.common.utils.bean.BeanUtils;
 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 com.fs.company.mapper.StatisticManageMapper;
 import com.fs.company.service.IStatisticManageService;
 import com.fs.statis.param.ComprehensiveStatisticsParam;
-import com.google.common.collect.Maps;
 import lombok.extern.slf4j.Slf4j;
-import com.google.common.collect.Lists;
+import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
@@ -20,10 +17,6 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
 /**
  * @description:
  * @author: Guos
@@ -42,77 +35,116 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
      * @return
      */
     @Override
-    public List<Map<String, Object>> statisticMain(ComprehensiveStatisticsParam param) {
-        List<Map<String, Object>> result = Lists.newArrayList();
-
-        if (param.getDimension() == DimensionEnum.PERSONAL.getValue()){
-            Assert.notNull(param.getId(), "按个人展示查询条件不能为空!");
-            List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
-            //从所有数据中去匹配userId = param.getId的
-            Optional<CompanyDeptUserInfo> first = companyDeptdUserList.stream()
-                    .filter(e -> e.getUserId().equals(param.getId()))
-                    .findFirst();
-            if (!first.isPresent()) {
-                return result;
-            }
-            CompanyDeptUserInfo companyDeptUserInfo = first.get();
-            Map<String, Object> map = Maps.newHashMap();
-            map.put("id", companyDeptUserInfo.getUserId());
-            map.put("name", companyDeptUserInfo.getNickName());
-            List<ComprehensiveStatisticsDTO> statisticNumByPersonal =
-                    getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(), param.getId());
-            map.put("list", statisticNumByPersonal);
-            result.add(map);
-            return result;
-        }
+    public Object statisticMain(ComprehensiveStatisticsParam param) {
         if(param.getDimension() == DimensionEnum.COMPANY.getValue()){
-            //按照公司统计,如果id为空就要展示全部公司,如果不为空就展示id查询的公司
-            if(ObjectUtil.isEmpty(param.getId())){
-                //得到所有公司信息
-                List<CompanyDeptUserInfo> companyInfo = statisticManageMapper.getCompanyInfo();
-                for (CompanyDeptUserInfo companyDeptUserInfo : companyInfo){
-                    getStatisticNumByPersonal(DimensionEnum.COMPANY.getValue(), param.getStartTime(), param.getEndTime(),
-                            companyDeptUserInfo.getCompanyId());
-                }
-            }
+            Assert.notNull(param.getId(), "按公司展示查询条件不能为空!");
+            return getStatisticNumByCompanyId(param.getStartTime(), param.getEndTime(), param.getId());
         }
         if(param.getDimension() == DimensionEnum.DEPARTMENT.getValue()){
-            Assert.notNull(param.getId(), "按部门展示公司不能为空!");
-            List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(param.getId());
-            //按照部门分组
-            Map<Long, List<CompanyDeptUserInfo>> deptInfos = companyDeptdUserList.stream()
-                    .collect(Collectors.groupingBy(CompanyDeptUserInfo::getDeptId));
-            deptInfos.forEach((deptId, companyDeptUserInfos) -> {
-                Long[] userIds = companyDeptUserInfos.stream().map(CompanyDeptUserInfo::getUserId).toArray(Long[]::new);
-                getStatisticNumByPersonal(DimensionEnum.PERSONAL.getValue(), param.getStartTime(), param.getEndTime(),userIds);
-            });
+            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 void executeTask() {
-        List<CompanyDeptUserInfo> companyDeptdUserList = statisticManageMapper.getCompanyAndDeptAndDeptUserList(null);
-        companyDeptdUserList.forEach(companyDeptUserInfo -> {
-            CompanyDeptUserInfoDTO statisticNum =  new CompanyDeptUserInfoDTO();
-            if(null != companyDeptUserInfo.getUserId()){
-                statisticNum = statisticManageMapper.getStatisticNum(companyDeptUserInfo.getUserId());
-            }
-            ComprehensiveDailyStats comprehensiveDailyStats = component(companyDeptUserInfo, statisticNum);
-            statisticManageMapper.insert(comprehensiveDailyStats);
-        });
+    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<ComprehensiveStatisticsDTO> getStatisticNumByPersonal(Integer dimension, Date startTime, Date endTime, Long... userIds) {
-       return statisticManageMapper.getStatisticNumByPersonal(dimension, startTime, endTime, userIds);
+    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){

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

@@ -85,6 +85,9 @@ public class CompanyRechargeVO implements Serializable {
     @Excel(name = "备注")
     private String remark;
 
+    /** 业务类型 0-普通 1-红包充值 */
+    private Integer businessType;
+
 
 
 }

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

@@ -95,4 +95,6 @@ public class CompanyVO implements Serializable
     private Integer usedNum;
     /** 所属部门id */
     private Long deptId;
+
+    private BigDecimal redPackageMoney;
 }

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

@@ -79,11 +79,19 @@ public class CourseConfig implements Serializable {
      */
     private boolean oneCompanyCourse;
 
+    //
+    private boolean moreCompanyCourse;
+
     /**
      * 是否开启IM
      */
     private Boolean isOpenIM;
 
+    /**
+     * 是否红包余额抵扣 1是 0否
+     */
+    private String isRedPackageBalanceDeduction;
+
 
     @Data
     public static class DisabledTimeVo{

+ 17 - 17
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -71,23 +71,23 @@ public interface FsCourseTrafficLogMapper
      */
     public int deleteFsCourseTrafficLogByLogIds(Long[] logIds);
 
-    @Select({"<script> " +
-            "select c.company_name, SUM(l.internet_traffic) AS total_internet_traffic" +
-            ",DATE_FORMAT(l.create_time, '%Y-%m') AS `month`  from fs_course_traffic_log l " +
-            "left join company c on c.company_id = l.company_id " +
-            "where 1 = 1 " +
-            "<if test= 'maps.year != null '>" +
-            "and YEAR(l.create_time) = #{maps.year} " +
-            "</if>" +
-            "<if test= 'maps.month != null '>"+
-            "and MONTH(l.create_time) = #{maps.month} " +
-            "</if>" +
-            "<if test = ' maps.companyId !=null '> " +
-            "and l.company_id = #{maps.companyId} " +
-            "</if>" +
-            "group by l.company_id,`month` "+
-            "</script>"})
-    List<FsCourseTrafficLogListVO> selectTrafficByCompany(@Param("maps") FsCourseTrafficLogParam param);
+//    @Select({"<script> " +
+//            "select c.company_name, SUM(l.internet_traffic) AS total_internet_traffic" +
+//            ",DATE_FORMAT(l.create_time, '%Y-%m') AS `month`  from fs_course_traffic_log l " +
+//            "left join company c on c.company_id = l.company_id " +
+//            "where 1 = 1 " +
+//            "<if test= 'maps.year != null '>" +
+//            "and YEAR(l.create_time) = #{maps.year} " +
+//            "</if>" +
+//            "<if test= 'maps.month != null '>"+
+//            "and MONTH(l.create_time) = #{maps.month} " +
+//            "</if>" +
+//            "<if test = ' maps.companyId !=null '> " +
+//            "and l.company_id = #{maps.companyId} " +
+//            "</if>" +
+//            "group by l.company_id,`month` "+
+//            "</script>"})
+    List<FsCourseTrafficLogListVO> selectTrafficByCompany(FsCourseTrafficLogParam param);
 
     @Select("select * from fs_course_traffic_log where uu_id = #{uuId}")
     FsCourseTrafficLog selectFsCourseTrafficLogByuuId(@Param("uuId") String uuId);

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

@@ -347,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} " +

+ 1 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -103,5 +103,6 @@ public class FsCourseWatchLogListParam implements Serializable {
      */
     private String qwUserName;
     private Long deptId;
+    private List<Long> deptIds;
     private String ids;
 }

+ 4 - 3
fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java

@@ -89,8 +89,7 @@ public class BalanceRollbackErrorServiceImpl extends ServiceImpl<BalanceRollback
     }
 
     @Override
-    // todo
-//    @EventListener(ApplicationReadyEvent.class)
+    @EventListener(ApplicationReadyEvent.class)
     public void initCompanyBalance() {
 
         // 查询公司表 Company
@@ -104,7 +103,9 @@ public class BalanceRollbackErrorServiceImpl extends ServiceImpl<BalanceRollback
             String moneyStr = redisCache.getCacheObject(companyMoneyKey);
             if (StringUtils.isEmpty(moneyStr)) {
                 // 初始化余额
-                redisCache.setCacheObject(companyMoneyKey, company.getMoney().toString());
+                if(company.getRedPackageMoney() != null){
+                    redisCache.setCacheObject(companyMoneyKey, company.getRedPackageMoney().toString());
+                }
             }
         }
     }

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

@@ -643,6 +643,12 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
+
+        // 因为selectFsCourseWatchLogListVO 这个方法中查询了看课日志记录表 需要限制创建时间必传
+        if(StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())){
+            throw new RuntimeException("请输入创建时间");
+        }
+
         // 待看课-未注册
         if(ObjectUtil.equal(param.getLogType(),5)){
             param.setLogType(3);

+ 182 - 154
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -15,6 +15,7 @@ import com.fs.common.core.domain.entity.SysDictData;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.enums.BizResponseEnum;
 import com.fs.common.exception.CustomException;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
@@ -555,6 +556,19 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 return R.error(500, "该用户("+fsUser.getUserId() + ")已成为其他销售会员");
             }
         }
+
+        // 重粉问题,关闭单销售重粉检测 打开公司销售重粉检测 允许不同公司销售重粉
+        if(!oneCompanyCourse && config.isMoreCompanyCourse()){
+            QwExternalContact query=new QwExternalContact();
+            query.setFsUserId(param.getUserId());
+            query.setCompanyId(param.getCompanyId());
+            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactList(query);
+            if(qwExternalContacts != null && !qwExternalContacts.isEmpty() && !Objects.equals(qwExternalContacts.get(0).getCompanyUserId(), param.getCompanyUserId())){
+                String msgInfo= "该用户("+fsUser.getUserId() + ")已在公司"+param.getCompanyId()+"成为其他销售会员";
+                return R.error(500, msgInfo);
+            }
+        }
+
         Integer isRoom = param.getIsRoom();
 
         // 处理逻辑
@@ -1606,174 +1620,183 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         //2025.6.19 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){
 
-            Company company = companyMapper.selectCompanyById(param.getCompanyId());
-            BigDecimal money = company.getMoney();
-            if (money.compareTo(BigDecimal.ZERO)<=0) {
-                return R.error("服务商余额不足,请联系群主服务器充值!");
-            }
-
-            // 发送红包
-            R sendRedPacket = paymentService.sendRedPacket(packetParam);
-            if (sendRedPacket.get("code").equals(200)) {
-                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-                TransferBillsResult transferBillsResult;
-                if (sendRedPacket.get("isNew").equals(1)){
-                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
-                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-                    redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
-                }else {
-                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+            // 打开红包扣减功能
+            if("1".equals(config.getIsRedPackageBalanceDeduction())){
+                // 先注释 20251024 redis 余额 充值没有考虑 其余扣减没有考虑
+                // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
+                // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
+                // 2 另起定时任务 同步缓存余额到redis中
+                // 3 注意!!!!! 启动系统时查询公司账户余额(这个时候要保证余额正确)启动会自动保存到redis缓存中
+                // 注意!!!!! 打开这个开关前记得检测redis缓存余额是否正确 若不正确 修改数据库字段red_package_money,删除redis缓存,重启系统,
+
+
+                // 预设值异常对象
+
+                BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
+                balanceRollbackError.setCompanyId(packetParam.getCompanyId());
+                balanceRollbackError.setUserId(user.getUserId());
+                balanceRollbackError.setLogId(log.getLogId());
+                balanceRollbackError.setVideoId(log.getVideoId());
+                balanceRollbackError.setStatus(0);
+                balanceRollbackError.setMoney(amount);
+
+                if(packetParam.getCompanyId()== null){
+                    logger.error("发送红包参数错误,公司不能为空,异常请求参数{}",packetParam);
+                    return R.error("发送红包失败,请联系管理员");
                 }
-                // 添加红包记录
-                redPacketLog.setCourseId(param.getCourseId());
-                redPacketLog.setCompanyId(param.getCompanyId());
-                redPacketLog.setUserId(param.getUserId());
-                redPacketLog.setVideoId(param.getVideoId());
-                redPacketLog.setStatus(0);
-                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-                redPacketLog.setCompanyUserId(param.getCompanyUserId());
-                redPacketLog.setCreateTime(new Date());
-                redPacketLog.setAmount(amount);
-                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-                redPacketLog.setPeriodId(param.getPeriodId());
-                redPacketLog.setAppId(param.getAppId());
-
-                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-                // 更新观看记录的奖励类型
-                log.setRewardType(config.getRewardType());
-                courseWatchLogMapper.updateFsCourseWatchLog(log);
-
-                return sendRedPacket;
-            } else {
-                return R.error("奖励发送失败,请联系客服");
-            }
-
-            // 先注释 20251024 redis 余额 充值没有考虑 其余扣减没有考虑
-            // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
-            // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
-            // 2 另起定时任务 同步缓存余额到redis中
-            // 3 启动系统时查询公司账户余额(这个时候要保证余额正确)保存到redis缓存中
-
-
-            // 预设值异常对象
-            /*
-            BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
-            balanceRollbackError.setCompanyId(packetParam.getCompanyId());
-            balanceRollbackError.setUserId(user.getUserId());
-            balanceRollbackError.setLogId(log.getLogId());
-            balanceRollbackError.setVideoId(log.getVideoId());
-            balanceRollbackError.setStatus(0);
-            balanceRollbackError.setMoney(amount);
-
-            if(packetParam.getCompanyId()== null){
-                logger.error("发送红包参数错误,公司不能为空,异常请求参数{}",packetParam);
-                return R.error("发送红包失败,请联系管理员");
-            }
-            String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
-
-            // 第一次加锁:预扣减余额
-            RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
-            boolean lockAcquired = false;
-            try {
-                if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
-                    lockAcquired = true;
-                    BigDecimal originalMoney;
-                    // 获取当前余额
-                    String moneyStr = redisCache.getCacheObject(companyMoneyKey);
-                    if (StringUtils.isNotEmpty(moneyStr)) {
-                        originalMoney = new BigDecimal(moneyStr);
-                    }else {
-                        // 缓存没有值,重启系统恢复redis数据 保证数据正确性
-                        logger.error("发送红包获取redis余额缓存异常,异常请求参数{}",packetParam);
-                        return R.error("系统异常,请稍后重试");
-                    }
+                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
 
-                    if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
-                        logger.error("服务商余额不足,异常请求参数{}",packetParam);
-                        return R.error("服务商余额不足,请联系群主服务器充值!");
+                // 第一次加锁:预扣减余额
+                RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
+                boolean lockAcquired = false;
+                BigDecimal newMoney;
+                try {
+                    if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
+                        lockAcquired = true;
+                        BigDecimal originalMoney;
+                        // 获取当前余额
+                        String moneyStr = redisCache.getCacheObject(companyMoneyKey);
+                        if (StringUtils.isNotEmpty(moneyStr)) {
+                            originalMoney = new BigDecimal(moneyStr);
+                        }else {
+                            // 缓存没有值,重启系统恢复redis数据 保证数据正确性
+                            logger.error("发送红包获取redis余额缓存异常,异常请求参数{}",packetParam);
+                            return R.error("系统异常,请稍后重试");
+                        }
+
+                        if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
+                            logger.error("服务商余额不足,异常请求参数{}",packetParam);
+                            return R.error("服务商余额不足,请联系群主服务器充值!");
+                        }
+
+                        // 预扣减金额
+                        newMoney = originalMoney.subtract(amount);
+                        redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+                    } else {
+                        logger.error("获取redis锁失败,异常请求参数{}",packetParam);
+                        return R.error("系统繁忙,请稍后重试");
                     }
-
-                    // 预扣减金额
-                    BigDecimal newMoney = originalMoney.subtract(amount);
-                    redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
-                } else {
-                    logger.error("获取redis锁失败,异常请求参数{}",packetParam);
-                    return R.error("系统繁忙,请稍后重试");
-                }
-            } catch (Exception e) {
-                logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
-                return R.error("系统异常,请稍后重试");
-            }finally {
-                // 只有在成功获取锁的情况下才释放锁
-                if (lockAcquired && lock1.isHeldByCurrentThread()) {
-                    try {
-                        lock1.unlock();
-                    } catch (IllegalMonitorStateException e) {
-                        logger.warn("尝试释放非当前线程持有的锁: companyId={}", packetParam.getCompanyId());
+                } catch (Exception e) {
+                    logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
+                    return R.error("系统异常,请稍后重试");
+                }finally {
+                    // 只有在成功获取锁的情况下才释放锁
+                    if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                        try {
+                            lock1.unlock();
+                        } catch (IllegalMonitorStateException e) {
+                            logger.warn("尝试释放非当前线程持有的锁: companyId={}", packetParam.getCompanyId());
+                        }
                     }
                 }
-            }
 
 
 
-            // 调用第三方接口(锁外操作)
-            R sendRedPacket;
-            try {
-                sendRedPacket= paymentService.sendRedPacket(packetParam);
-            } catch (Exception e) {
-                logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
-                // 异常时回滚余额
+                // 调用第三方接口(锁外操作)
+                R sendRedPacket;
+                try {
+                    sendRedPacket= paymentService.sendRedPacket(packetParam);
+                } catch (Exception e) {
+                    logger.error("红包发送异常: 异常请求参数{}",packetParam, e);
+                    // 异常时回滚余额
 
-//                rollbackBalance(balanceRollbackError);
-                return R.error("奖励发送失败,请联系客服");
-            }
+                    rollbackBalance(balanceRollbackError);
+                    return R.error("奖励发送失败,请联系客服");
+                }
 
-            // 红包发送成功处理
-            if (sendRedPacket.get("code").equals(200)) {
-                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-                TransferBillsResult transferBillsResult;
-                if (sendRedPacket.get("isNew").equals(1)){
-                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
-                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-                    redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
-                }else {
-                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                // 红包发送成功处理
+                if (sendRedPacket.get("code").equals(200)) {
+                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                    TransferBillsResult transferBillsResult;
+                    if (sendRedPacket.get("isNew").equals(1)){
+                        transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                        redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                        redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                        redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                    }else {
+                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                        redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                    }
+                    // 添加红包记录
+                    redPacketLog.setCourseId(param.getCourseId());
+                    redPacketLog.setCompanyId(param.getCompanyId());
+                    redPacketLog.setUserId(param.getUserId());
+                    redPacketLog.setVideoId(param.getVideoId());
+                    redPacketLog.setStatus(0);
+                    redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                    redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                    redPacketLog.setCreateTime(new Date());
+                    redPacketLog.setAmount(amount);
+                    redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                    redPacketLog.setPeriodId(param.getPeriodId());
+                    redPacketLog.setAppId(param.getAppId());
+
+                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                    // 更新观看记录的奖励类型
+                    log.setRewardType(config.getRewardType());
+                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                    // 异步登记余额扣减日志
+                    BigDecimal money=amount.multiply(BigDecimal.valueOf(-1));
+                    companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
+                    // 发送成功,记录日志等操作
+                    return sendRedPacket;
+
+
+                } else {
+                    // 发送失败,回滚余额
+                    rollbackBalance(balanceRollbackError);
+                    return R.error("奖励发送失败,请联系客服");
                 }
-                // 添加红包记录
-                redPacketLog.setCourseId(param.getCourseId());
-                redPacketLog.setCompanyId(param.getCompanyId());
-                redPacketLog.setUserId(param.getUserId());
-                redPacketLog.setVideoId(param.getVideoId());
-                redPacketLog.setStatus(0);
-                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-                redPacketLog.setCompanyUserId(param.getCompanyUserId());
-                redPacketLog.setCreateTime(new Date());
-                redPacketLog.setAmount(amount);
-                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-                redPacketLog.setPeriodId(param.getPeriodId());
-                redPacketLog.setAppId(param.getAppId());
-
-                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-                // 更新观看记录的奖励类型
-                log.setRewardType(config.getRewardType());
-                courseWatchLogMapper.updateFsCourseWatchLog(log);
-                // 发送成功,记录日志等操作
-                return sendRedPacket;
 
+                // ===================== 本次修改目的为了实时扣减公司余额=====================
+            }else {
+                Company company = companyMapper.selectCompanyById(param.getCompanyId());
+                BigDecimal money = company.getMoney();
+                if (money.compareTo(BigDecimal.ZERO)<=0) {
+                    return R.error("服务商余额不足,请联系群主服务器充值!");
+                }
 
-            } else {
-                // 发送失败,回滚余额
-//                rollbackBalance(balanceRollbackError);
-                return R.error("奖励发送失败,请联系客服");
+                // 发送红包
+                R sendRedPacket = paymentService.sendRedPacket(packetParam);
+                if (sendRedPacket.get("code").equals(200)) {
+                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                    TransferBillsResult transferBillsResult;
+                    if (sendRedPacket.get("isNew").equals(1)){
+                        transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                        redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                        redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                        redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                    }else {
+                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                        redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                    }
+                    // 添加红包记录
+                    redPacketLog.setCourseId(param.getCourseId());
+                    redPacketLog.setCompanyId(param.getCompanyId());
+                    redPacketLog.setUserId(param.getUserId());
+                    redPacketLog.setVideoId(param.getVideoId());
+                    redPacketLog.setStatus(0);
+                    redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                    redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                    redPacketLog.setCreateTime(new Date());
+                    redPacketLog.setAmount(amount);
+                    redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                    redPacketLog.setPeriodId(param.getPeriodId());
+                    redPacketLog.setAppId(param.getAppId());
+
+                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                    // 更新观看记录的奖励类型
+                    log.setRewardType(config.getRewardType());
+                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                    return sendRedPacket;
+                } else {
+                    return R.error("奖励发送失败,请联系客服");
+                }
             }
-             */
-            // ===================== 本次修改目的为了实时扣减公司余额=====================
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             // 添加红包记录
@@ -2660,6 +2683,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     }
 
+    public static void main(String[] args) {
+        BigDecimal a=new BigDecimal(22.01);
+        System.out.println(a.multiply(new BigDecimal(-1)));
+    }
+
 
     //插入观看记录
     private void addWatchLogIfNeeded(Long videoId, Long courseId,

+ 18 - 1
fs-service/src/main/java/com/fs/course/vo/FsCourseTrafficLogListVO.java

@@ -15,15 +15,32 @@ import java.io.Serializable;
 @Data
 public class FsCourseTrafficLogListVO implements Serializable
 {
+    @Excel(name = "公司名称")
     private String companyName;
     private Long companyId;
+    //@Excel(name = "课程名称")
     private String courseName;
     private Long courseId;
+    //@Excel(name = "项目名称")
     private String projectName;
     private Long project;
-
+    // 原始字节数(不用于导出)
     private Long totalInternetTraffic;
 
+    @Excel(name = "总流量 (GB)")
+    private String formattedTotalTraffic;
+
+    @Excel(name = "统计月份")
     private String month;
 
+    // 工具方法:根据 totalInternetTraffic 自动计算 formattedTotalTraffic
+    public void formatTraffic() {
+        if (this.totalInternetTraffic == null) {
+            this.formattedTotalTraffic = "0.0000 GB";
+        } else {
+            double gb = this.totalInternetTraffic.doubleValue() / (1024 * 1024 * 1024);
+            this.formattedTotalTraffic = String.format("%.4f GB", gb);
+        }
+    }
+
 }

+ 23 - 0
fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java

@@ -0,0 +1,23 @@
+package com.fs.his.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @description: 红包
+ * @author: Xgb
+ * @createDate: 2025/11/4
+ * @version: 1.0
+ */
+@Data
+public class FsRedPacket extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    // 红包余额
+    private BigDecimal redBalance;
+    // 今日消耗
+    private BigDecimal redTodayComsumption;
+
+}

+ 15 - 0
fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java

@@ -0,0 +1,15 @@
+package com.fs.his.service;
+
+import com.fs.his.domain.FsRedPacket;
+
+/**
+ * 红包Service接口
+ *
+ * @author fs
+ * @date 2023-10-23
+ */
+public interface IFsRedPacketService
+{
+
+    FsRedPacket getInfo(Long companyId);
+}

+ 45 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java

@@ -0,0 +1,45 @@
+package com.fs.his.service.impl;
+
+import com.fs.common.constant.FsConstants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsRedPacket;
+import com.fs.his.service.IFsRedPacketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+
+/**
+ * 红包Service业务层处理
+ *
+ * @author fs
+ * @date 2023-10-23
+ */
+@Service
+public class FsRedPacketServiceImpl implements IFsRedPacketService
+{
+
+    @Autowired
+    private RedisCache redisCache;
+    /**
+     * @Description: 获取红包余额相关信息
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 11:52
+     */
+    @Override
+    public FsRedPacket getInfo(Long companyId) {
+       String yesterdayBalance =redisCache.getCacheObject(FsConstants.COMPANY_MONEY_DATE_KEY+companyId+":"+ DateUtils.getDate());
+       String balance = redisCache.getCacheObject(FsConstants.COMPANY_MONEY_KEY+companyId);
+       FsRedPacket fsRedPacket = new FsRedPacket();
+       if(balance!=null){
+           fsRedPacket.setRedBalance(new BigDecimal( balance));
+           if(yesterdayBalance!=null){
+               fsRedPacket.setRedTodayComsumption(fsRedPacket.getRedBalance().subtract(new BigDecimal(yesterdayBalance)));
+           }
+       }
+       return fsRedPacket;
+    }
+}

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

@@ -749,7 +749,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if(ObjectUtil.isEmpty(config.getOrderAttribution())
                     ||!config.getOrderAttribution().equals(1)){
                 if(param.getCompanyUserId()!=null){
-                    if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&fsuser.getCompanyUserId()!=param.getCompanyUserId()){
+                    if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&!fsuser.getCompanyUserId().equals(param.getCompanyUserId())){
                         CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
                         return R.error(String.format("请联系%s销售进行购买商品!",companyUser.getNickName()));
                     }else {
@@ -3843,6 +3843,31 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     return R.error("导入失败,订单号为" + dto.getOrderNumber() + "物流编码异常,请核对后再导入!");
                 }
                 list.add(dto);
+
+
+                //批量导入物流单号 存在快递鸟时订阅下方参数,进行调整
+                if(CloudHostUtils.hasCloudHostName("内蒙古一贴")){
+                    FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(dto.getOrderNumber());
+                    //订阅物流回调
+                    String lastFourNumber = "";
+                    if (dto.getDeliverySn().equals(ShipperCodeEnum.SF.getValue())) {
+                        lastFourNumber = order.getUserPhone();
+                        if (lastFourNumber.length() == 11) {
+                            lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                        }
+                    }
+                    expressService.subscribeEspress(order.getOrderCode(), order.getDeliverySn(), order.getDeliveryId(), lastFourNumber);
+
+                    TemplateBean templateBean = TemplateBean.builder()
+                            .orderId(order.getId().toString())
+                            .orderCode(order.getOrderCode().toString())
+                            .deliveryId(order.getDeliveryId())
+                            .deliveryName(order.getDeliveryName())
+                            .userId(order.getUserId())
+                            .templateType(TemplateListenEnum.TYPE_2.getValue())
+                            .build();
+                    publisher.publishEvent(new TemplateEvent(this, templateBean));
+                }
             }
 
             //分批次处理
@@ -3863,6 +3888,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    public static void main(String[] args) {
+        String lastFourNumber = "15808582581";
+    }
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
     public R pay(FsStoreOrderPayParam param) {

+ 3 - 3
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -349,9 +349,9 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     case 1://非处方
                         break;
                     case 2://处方
-                        if("".equals(store.getDrugLicense()) ||  LocalDate.now().isBefore(store.getDrugLicenseExpiryEnd())){
-                            return R.error("店铺药品资质为空或已过期,请完善后再添加");
-                        }
+//                        if("".equals(store.getDrugLicense()) ||  LocalDate.now().isBefore(store.getDrugLicenseExpiryEnd())){
+//                            return R.error("店铺药品资质为空或已过期,请完善后再添加");
+//                        }
                         break;
                     case 3://食品
                         if("".equals(store.getFoodLicense()) ||  LocalDate.now().isBefore(store.getFoodLicenseExpiryEnd())){

+ 161 - 0
fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java

@@ -2,28 +2,45 @@ package com.fs.ipad;
 
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.ipad.param.WxGetSessionRoomListParam;
 import com.fs.ipad.param.WxRoomUserListParam;
 import com.fs.ipad.param.WxSendAtMsgParam;
 import com.fs.ipad.vo.*;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwGroupChat;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwGroupChatMapper;
+import com.fs.sop.domain.QwSop;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkServiceNew;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Component
@@ -32,6 +49,10 @@ public class IpadSendUtils {
 
     private final WxWorkServiceNew wxWorkService;
     private final RedisCacheT<String> redisCache;
+    private final QwGroupChatMapper qwGroupChatMapper;
+    private final QwSopMapper qwSopMapper;
+    private final QwExternalContactMapper qwExternalContactMapper;
+    private final SopUserLogsMapper sopUserLogsMapper;
     private final RedisCache redisCacheUrl;
     private final String FILE_KEY = "ipad:upload:";
 
@@ -379,4 +400,144 @@ public class IpadSendUtils {
         log.info("发送返回数据:{}", result);
         if(result.getErrcode() != 0) throw new BaseException("发送消息错误:" + result.getErrmsg());
     }
+
+    /**
+     * 创建群聊
+     * @param qwSop           SOP
+     * @param groupName       群聊名称
+     * @param qwUser          创建企微账号
+     * @param list            客户列表
+     * @param maxGroupNum     最大拉群数量
+     * @param maxGroupUserNum 单个群聊最大拉取用户数量
+     */
+    public String createRoom(QwSop qwSop, String groupName, QwUser qwUser, List<QwExternalContact> list, Integer maxGroupNum, Integer maxGroupUserNum, int currentNum) {
+        // 最大支持的客户数量
+        int maxNum = maxGroupNum * maxGroupUserNum;
+        // 根据最大客户数量限制客户
+        List<QwExternalContact> joinList = list.stream().limit(maxNum).collect(Collectors.toList());
+        // 修改加群状态微 已加群
+        joinList.forEach(e -> e.setJoinGroup(1));
+        // 用外部联系人ID去换取企微对应的ID
+        List<Long> userIds = changeUserIds(qwUser, joinList);
+        // 添加的群ID
+        List<String> chatIds = new ArrayList<>();
+        // 筛选出来的客户可以创建多少个群聊
+        int groupNum = BigDecimal.valueOf((double) userIds.size() / maxGroupUserNum).setScale(0, RoundingMode.UP).intValue();
+        List<SopUserLogs> logsList = new ArrayList<>();
+        // 获取当前日期
+        Date currentDate = new Date();
+        // 创建格式化器,指定格式为"yyyy年MM月dd日"
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        // 格式化日期
+        String today = sdf.format(currentDate);
+        // 循环创建群聊添加客户
+        for (int i = 0; i < groupNum; i++) {
+            List<Long> collect = userIds.stream().skip((long) i * maxGroupUserNum).limit(maxGroupUserNum).collect(Collectors.toList());
+            // 如果人数为空 或者 人数不足最大群聊人数
+            if(collect.isEmpty() || collect.size() < maxGroupUserNum) continue;
+            // 设置新的群聊名称
+            String newGroupName = groupName + (i + 1 + currentNum);
+            // 创建群聊对象
+            WxCreateRoomMsgDTO dto = new WxCreateRoomMsgDTO();
+            // 设置UUID
+            dto.setUuid(qwUser.getUid());
+            // 设置群聊名称
+            dto.setRoomName(newGroupName);
+            // 客户ID列表,会自动拉取到群聊里面
+            dto.setVids(collect);
+            // 请求创建群聊方法
+            WxWorkResponseDTO<WxCreateRoomRespDTO> room = wxWorkService.createRoom(dto, qwUser.getServerId());
+            // 通过创建群聊接口返回的ID换取系统需要的群聊ID
+            String chatId = toChatIds(qwUser.getUid(), room.getData().getRoomid(), qwUser.getServerId());
+            // 群聊对象
+            QwGroupChat qwGroupChat = new QwGroupChat();
+            // 群聊ID
+            qwGroupChat.setChatId(chatId);
+            // 群聊名称
+            qwGroupChat.setName(newGroupName);
+            // 对应的SOPID
+            qwGroupChat.setSopId(qwSop.getId());
+            // 所属的企微ID
+            qwGroupChat.setQwUserId(qwUser.getId());
+            // 微信对应的群聊ID
+            qwGroupChat.setRoomid(room.getData().getRoomid());
+            // 创建活修改群聊
+            qwGroupChatMapper.insertOrUpdateQwGroupChat(qwGroupChat);
+            // 群聊ID添加
+            chatIds.add(chatId);
+            SopUserLogs sopUserLogs = new SopUserLogs();
+            sopUserLogs.setSopId(qwSop.getId());
+            sopUserLogs.setSopTempId(qwSop.getTempId());
+            sopUserLogs.setQwUserId(qwUser.getQwUserId());
+            sopUserLogs.setCorpId(qwSop.getCorpId());
+            sopUserLogs.setStartTime(today);
+            sopUserLogs.setStatus(1);
+            sopUserLogs.setUserId(qwUser.getId() + "|" + qwUser.getCompanyUserId() + "|" + qwUser.getCompanyId());
+            sopUserLogs.setChatId(chatId);
+            logsList.add(sopUserLogs);
+        }
+        // 更新客户信息
+        qwExternalContactMapper.updateJoinGroup(PubFun.listToNewList(joinList, QwExternalContact::getId));
+        // 组装群聊ID
+        String chatId = String.join(",", chatIds);
+        // 修改SOP对象
+        qwSopMapper.updateSopGroupIds(qwSop.getId(), chatId);
+        sopUserLogsMapper.batchInsertSopUserLogs(logsList);
+        return chatId;
+    }
+    private String toChatIds(String uuid, Long roomid, Long serverId) {
+        WxWorkChatIdToRoomIdDTO tdo = new WxWorkChatIdToRoomIdDTO();
+        tdo.setRoom_id(roomid);
+        tdo.setUuid(uuid);
+        WxWorkResponseDTO<WxWorkChatIdToRoomIdResp> result = wxWorkService.RoomIdToChatId(tdo, serverId);
+        if(result.getErrcode() != 0){
+            throw new BaseException(result.getErrmsg());
+        }
+        WxWorkChatIdToRoomIdResp data = result.getData();
+        if(data == null || data.getRoom_id() == null) {
+            log.error("未找到群聊数据,请求数据:{},返回数据:{}", JSON.toJSONString(tdo), JSON.toJSONString(result));
+            throw new BaseException("未找到群聊:" + roomid);
+        }
+        return data.getChatid();
+    }
+
+    private List<Long> changeUserIds(QwUser qwUser, List<QwExternalContact> joinList) {
+        BaseVo vo = new BaseVo();
+        vo.setId(qwUser.getId());
+        vo.setRoom(false);
+        vo.setUuid(qwUser.getUid());
+        vo.setExIdList(joinList.stream().map(QwExternalContact::getExternalUserId).collect(Collectors.toList()));
+        vo.setServerId(qwUser.getServerId());
+        return userIdList(vo);
+    }
+    private List<Long> userIdList(BaseVo vo){
+        if(vo.isRoom()){
+            return Collections.emptyList();
+        }
+        WxWorkUserId2VidDTO wxWorkUserId2VidDTO = new WxWorkUserId2VidDTO();
+        wxWorkUserId2VidDTO.setOpenid(vo.getExIdList());
+        wxWorkUserId2VidDTO.setCorpid(vo.getCorpId());
+        wxWorkUserId2VidDTO.setScorpid(vo.getCorpCode());
+        wxWorkUserId2VidDTO.setUuid(vo.getUuid());
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> resutl = wxWorkService.UserId2Vid(wxWorkUserId2VidDTO, vo.getServerId());
+        List<?> rawData = resutl.getData(); // 先用一个泛型不确定的列表接收
+        if(rawData.isEmpty()) {
+            log.error("未找到用户数据,基础数据:{},请求数据:{},返回数据:{}", vo, JSON.toJSONString(wxWorkUserId2VidDTO), JSON.toJSONString(resutl));
+            throw new BaseException("未找到用户:" + vo.getId());
+        }
+        List<WxWorkVid2UserIdRespDTO> data = new ArrayList<>();
+        for (Object item : rawData) {
+            if (item instanceof WxWorkVid2UserIdRespDTO) {
+                data.add((WxWorkVid2UserIdRespDTO) item);
+            } else if (item instanceof JSONObject) {
+                // 如果是JSONObject,则需要将其转换为目标类型
+                WxWorkVid2UserIdRespDTO dto = ((JSONObject) item).toJavaObject(WxWorkVid2UserIdRespDTO.class);
+                data.add(dto);
+            } else {
+                log.warn("未知类型: {}", item.getClass());
+                // 根据实际情况处理,例如跳过或抛出更明确的异常
+            }
+        }
+        return data.stream().map(WxWorkVid2UserIdRespDTO::getUser_id).collect(Collectors.toList());
+    }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java

@@ -2,12 +2,15 @@ package com.fs.ipad.vo;
 
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 public class BaseVo{
 
     private Long id;
     private String uuid;
     private Long serverId;
+    private List<String> exIdList;
     private String exId;
     private String corpId;
     private String corpCode;

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

@@ -148,4 +148,7 @@ public class QwExternalContact extends BaseEntity
     //用户是否回复  0未回复  1已回复
     private Integer isReply;
 
+    // 是否被邀请进群0否1是
+    private Integer joinGroup;
+
 }

+ 7 - 0
fs-service/src/main/java/com/fs/qw/domain/QwGroupChat.java

@@ -75,4 +75,11 @@ public class QwGroupChat extends BaseEntity
     private Long allOutGroup;
     @TableField(exist = false)
     private List<QwGroupChatUser> chatUserList;
+
+
+    // 是否自动拉人0否1是
+    private Long autoAdduser;
+    private String sopId;
+    private Long qwUserId;
+    private Long roomid;
 }

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

@@ -1,6 +1,8 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.fastGpt.domain.FastgptChatArtificialWords;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUserDelLossLog;
@@ -12,6 +14,7 @@ import com.fs.qw.result.QwExternalContactVo;
 import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactNumVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -551,4 +554,6 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
      * 查询群组指定企微关联外部联系人
      */
     List<QwExternalContact> selectGroupContactByChatIdAndQwUserId(@Param("chatId") String chatId, @Param("qwUserId") Long qwUserId);
+
+    void updateJoinGroup(List<Long> longs);
 }

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

@@ -134,4 +134,6 @@ public interface QwGroupChatMapper
      * 根据企微用户ID查询群组列表
      */
     List<QwContactVO> getGroupListByQwUserId(@Param("qwUserId") Long qwUserId, @Param("name") String name);
+
+    List<QwGroupChat> selectSopAndQwUser(@Param("qwUserId") String qwUserId, @Param("sopId") String sopId);
 }

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

@@ -463,4 +463,13 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     @Select("select * from qw_user where vid = #{vid} order by id desc limit 1")
     QwUser selectQwUserByVid(@Param("vid") Long vid);
+
+    @Select("<script>" +
+            "select * from qw_user where qw_user_id in " +
+            "<foreach collection='qwUserIdList' item='item' open='(' separator=',' close=')'> " +
+            "#{item} " +
+            "</foreach> " +
+            "</script>")
+    List<QwUser> selectQwUserByIds(@Param("qwUserIdList") List<Long> qwUserIdList);
+
 }

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

@@ -93,7 +93,13 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "<if test ='nickName !=null and nickName!=\"\"'>\n" +
             "   and qu.qw_user_name like concat( #{nickName}, '%')\n" +
             "</if>" +
-            "<if test ='deptId !=null and deptId!=\"\"'>\n" +
+            "<if test ='deptIds !=null and deptIds.size > 0'>\n" +
+            "and cu.dept_id in" +
+            "<foreach collection=\"deptIds\" item=\"deptId\" index=\"index\"  separator=\",\" open=\"(\" close=\")\">\n" +
+            "     #{deptId}\n" +
+            "</foreach>" +
+            "</if>" +
+            "<if test ='(deptIds ==null or deptIds.size = 0) and deptId !=null and deptId!=\"\"'>\n" +
             "   and cu.dept_id = #{deptId}\n" +
             "</if>" +
             "<if test ='ids !=null and ids!=\"\"'>\n" +
@@ -120,7 +126,8 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "    COUNT(CASE WHEN qec.`level` = 5 THEN 1 END) AS E,\n" +
             "    COUNT(CASE WHEN qec.fs_user_id IS NOT NULL THEN 1 END) AS sign,\n" +
             "    COUNT(CASE WHEN qec.`status` =3 THEN 1 END) AS los,\n" +
-            "    COUNT(CASE WHEN qec.`status` IN (4, 5,6) THEN 1 END) AS del\n" +
+            "    COUNT(CASE WHEN qec.`status` IN (4, 5,6) THEN 1 END) AS del,\n" +
+            "    COUNT(CASE WHEN qec.fs_user_id IS NOT NULL and qec.fs_user_id != 0 THEN 1 END) AS reg_num\n"+
             "FROM\n" +
             "    qw_external_contact qec\n" +
             "JOIN\n" +
@@ -128,6 +135,13 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "left join company_user cu on qec.company_user_id = cu.user_id "+
             "WHERE\n" +
             "    DATE(qec.create_time) &gt;= DATE(#{sTime}) and  DATE(qec.create_time) &lt;= DATE(#{eTime}) and qec.company_id =#{companyId} " +
+            " and NOT EXISTS (\n" +
+            " SELECT 1 \n" +
+            " FROM qw_external_contact_transfer_log t " +
+            " WHERE t.external_contact_id = qec.id " +
+            " AND DATE(t.create_time) &gt;= DATE(#{sTime}) \n" +
+            " AND DATE(t.create_time) &lt;= DATE(#{eTime})\n" +
+            " )" +
             "<if test ='nickName !=null and nickName!=\"\"'>\n" +
             "   and qu.qw_user_name like concat( #{nickName}, '%')\n" +
             "</if>" +
@@ -142,6 +156,49 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "ORDER BY\n" +
             "    DATE(qec.create_time) "+
             "</script>"})
+    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAndExcludeTransfer(QwWatchLogStatisticsListParam param);
+    @Select({"<script> " +
+            "SELECT\n" +
+            "    qec.qw_user_id id,\n" +
+            "    qu.qw_user_name AS qw_user_name, \n" +
+            "    DATE(qec.create_time) AS create_time, \n" +
+            "    COUNT(1) AS line,\n" +
+            "    COUNT(CASE WHEN qec.is_interact = 1 THEN 1 END) AS interact,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 1 THEN 1 END) AS A,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 2 THEN 1 END) AS B,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 3 THEN 1 END) AS C,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 4 THEN 1 END) AS D,\n" +
+            "    COUNT(CASE WHEN qec.`level` = 5 THEN 1 END) AS E,\n" +
+            "    COUNT(CASE WHEN qec.fs_user_id IS NOT NULL THEN 1 END) AS sign,\n" +
+            "    COUNT(CASE WHEN qec.`status` =3 THEN 1 END) AS los,\n" +
+            "    COUNT(CASE WHEN qec.`status` IN (4, 5,6) THEN 1 END) AS del\n" +
+            "FROM\n" +
+            "    qw_external_contact qec\n" +
+            "JOIN\n" +
+            "    qw_user qu ON qec.qw_user_id = qu.id \n" +
+            "left join company_user cu on qec.company_user_id = cu.user_id "+
+            "WHERE\n" +
+            "    DATE(qec.create_time) &gt;= DATE(#{sTime}) and  DATE(qec.create_time) &lt;= DATE(#{eTime}) and qec.company_id =#{companyId} " +
+            "<if test ='nickName !=null and nickName!=\"\"'>\n" +
+            "   and qu.qw_user_name like concat( #{nickName}, '%')\n" +
+            "</if>" +
+            "<if test ='deptIds !=null and deptIds.size > 0'>\n" +
+            "and cu.dept_id in" +
+            "<foreach collection=\"deptIds\" item=\"deptId\" index=\"index\"  separator=\",\" open=\"(\" close=\")\">\n" +
+            "     #{deptId}\n" +
+            "</foreach>" +
+            "</if>" +
+            "<if test ='(deptIds == null or deptIds.size == 0) and deptId !=null and deptId!=\"\"'>\n" +
+            "   and cu.dept_id = #{deptId}\n" +
+            "</if>" +
+            "<if test ='ids !=null and ids!=\"\"'>\n" +
+            "   and qec.qw_user_id in (${ids})\n" +
+            "</if>" +
+            "GROUP BY\n" +
+            "    qec.qw_user_id, DATE(qec.create_time) \n" +
+            "ORDER BY\n" +
+            "    DATE(qec.create_time) "+
+            "</script>"})
     List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAndOther(FsCourseWatchLogListParam param);
     @Select("select \n" +
             "COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,\n" +

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

@@ -36,6 +36,7 @@ public class QwWatchLogStatisticsListParam {
     private Long courseId;
     private Long videoId;
     private Long deptId;
+    private List<Long> deptIds; //多选
 
     private Long pageNum;
     private Long pageSize;

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

@@ -281,4 +281,6 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      * 外部联系人删除标签
      */
     void delQwExternalContactTag(Long id, List<String> delTags);
+
+    List<QwExternalContact> selectQwUserAndLevel(List<Long> qwUserIdList, List<String> levelList, boolean isReg);
 }

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

@@ -86,4 +86,6 @@ public interface IQwGroupChatService
      * @return message
      */
     ResultMessage processTransfer(TransferChatParam param, CompanyUser user, boolean isResigned);
+
+    List<QwGroupChat> selectSopAndQwUser(String qwUserId, String id);
 }

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

@@ -210,4 +210,6 @@ public interface IQwUserService
      * 根据Vid查询企微用户
      */
     QwUser selectQwUserByVid(Long vid);
+
+    List<QwUser> selectQwUserByIds(List<Long> qwUserIdList);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/qw/service/IQwWatchLogService.java

@@ -66,6 +66,14 @@ public interface IQwWatchLogService extends IService<QwWatchLog>{
     int deleteQwWatchLogById(Long id);
 
     TableDataInfo selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param);
+
+    /**
+     * 查询进线统计,排除掉转接的数据
+     * @param param
+     * @return
+     */
+    TableDataInfo selectQwWatchLogStatisticsListVOExcludeTransfer(QwWatchLogStatisticsListParam param);
+
     List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVO(QwWatchLogStatisticsListParam param);
 
     TableDataInfo selectQwWatchLogAllStatisticsListVONew(QwWatchLogStatisticsListParam param);

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

@@ -29,6 +29,11 @@ import java.util.stream.Collectors;
 @AllArgsConstructor
 public class AsyncChatSopService {
 
+    // 每个人一次性最大创建群聊次数
+    public final static Integer MAX_GROUP_NUM = 3;
+    // 一个群一天最大拉群人数量
+    public final static Integer MAX_GROUP_USER_NUM = 40;
+
     private final QwSopMapper qwSopMapper;
     private final QwSopTempMapper qwSopTempMapper;
     private final SopUserLogsMapper sopUserLogsMapper;
@@ -49,7 +54,7 @@ public class AsyncChatSopService {
             Map<String, QwUserVO> qwUserMap = PubFun.listToMapByGroupObject(qwUserVOList, QwUserVO::getQwUserId);
             List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(ruleTimeVOList.stream().flatMap(e -> Arrays.stream(e.getChatId().split(","))).toArray(String[]::new));
             Map<String, QwGroupChat> groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
-            ruleTimeVOList.forEach(ruleTimeVO -> {
+            ruleTimeVOList.stream().filter(e -> StringUtils.isNotEmpty(e.getChatId())).forEach(ruleTimeVO -> {
                 QwSopTemp qwSopTemp = tempMap.get(ruleTimeVO.getTempId());
                 if (!qwSopTemp.getStatus().equals("0")) {
                     processInternal(ruleTimeVO, qwSopTemp, qwUserMap, groupChatMap);

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

@@ -5920,6 +5920,20 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         qwExternalContactMapper.updateQwExternalContactStatusById(qwExternalContact);
     }
 
+    @Override
+    public List<QwExternalContact> selectQwUserAndLevel(List<Long> qwUserIdList, List<String> levelList, boolean isReg) {
+        QueryWrapper<QwExternalContact> queryCondition = new QueryWrapper<QwExternalContact>().isNotNull(isReg, "fs_user_id").eq("status", 0).eq("join_group", 0)
+                        .in("qw_user_id", qwUserIdList).and(qw->{
+                        qw.in("level", levelList);
+                        if(levelList.contains("-1")) {
+                                qw.or().isNull("level");
+                            }
+                        return qw;
+                    });
+
+        return list(queryCondition);
+    }
+
     @Override
     public R getRepeat(RepeatParam param) {
         List<QwExternalContact> list = qwExternalContactMapper.selectList(new QueryWrapper<QwExternalContact>().eq("external_user_id", param.getExternalUserId()));

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

@@ -564,4 +564,9 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
         return transferLog;
     }
 
+    @Override
+    public List<QwGroupChat> selectSopAndQwUser(String qwUserId, String sopId) {
+        return qwGroupChatMapper.selectSopAndQwUser(qwUserId, sopId);
+    }
+
 }

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

@@ -1546,6 +1546,10 @@ public class QwUserServiceImpl implements IQwUserService
         qwUserMapper.updateById(qwUser);
     }
 
+    @Override
+    public List<QwUser> selectQwUserByIds(List<Long> qwUserIdList) {
+        return qwUserMapper.selectQwUserByIds(qwUserIdList);
+    }
     /**
      * 根据销售公司和企微ID查询企微用户
      */

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

@@ -148,10 +148,26 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
 
     @Override
     public List<QwWatchLogStatisticsListVO> selectQwWatchLogStatisticsListVOExport(FsCourseWatchLogListParam param) {
-        CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
-        if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
-            param.setDeptId(null);
+        List<Long> deptIds = param.getDeptIds();
+        if (deptIds !=null && !deptIds.isEmpty()){
+            List<CompanyDept> companyDeptList  = companyDeptMapper.selectCompanyDeptByIds(deptIds);
+            if (companyDeptList!=null && !companyDeptList.isEmpty()){
+                for (CompanyDept c : companyDeptList) {
+                    if (c.getParentId() == 0L) {
+                        param.setDeptId(null);
+                        param.setDeptIds(null);
+                        break;
+                    }
+                }
+            }
+        } else {
+            CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
+            if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
+                param.setDeptId(null);
+            }
         }
+
+
         if (param.getCompanyUserId()!=null){
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
         }
@@ -170,6 +186,66 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
 
     @Override
     public TableDataInfo selectQwWatchLogStatisticsListVO(QwWatchLogStatisticsListParam param) {
+        List<Long> deptIds = param.getDeptIds();
+        if (deptIds !=null && !deptIds.isEmpty()){
+            List<CompanyDept> companyDeptList  = companyDeptMapper.selectCompanyDeptByIds(deptIds);
+            if (companyDeptList!=null && !companyDeptList.isEmpty()){
+                for (CompanyDept c : companyDeptList) {
+                    if (c.getParentId() == 0L) {
+                        param.setDeptId(null);
+                        param.setDeptIds(null);
+                        break;
+                    }
+                }
+            }
+        } else {
+            CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
+            if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
+                param.setDeptId(null);
+            }
+        }
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(new ArrayList<>());
+        rspData.setTotal(0L);
+
+        Date sTime = param.getSTime();
+        Date eTime = param.getETime();
+        List<Date> datesBetween = getDatesBetween(sTime, eTime);
+        if (datesBetween.size() > 7) {
+            return rspData;
+        }
+        if (param.getCompanyUserId()!=null){
+            param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
+        }
+        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAnd(param);
+        for (QwWatchLogStatisticsListVO vo : vos) {
+            Long id = vo.getId();
+            Date createTime = vo.getCreateTime();
+            QwWatchLogStatisticsListVO stat = qwWatchLogMapper.selectQwWatchLogByQwUserId(id, createTime);
+            vo.setD1Online(stat.getD1Online());
+            vo.setD1Over(stat.getD1Over());
+            vo.setFirstOnline(stat.getFirstOnline());
+            vo.setFirstOver(stat.getFirstOver());
+        }
+        if (StringUtils.isNotBlank(param.getIds())) {
+            String replace = param.getIds().replace("(", "").replace(")", "");
+            param.setIdsList(Arrays.asList(replace.split(",")));
+        }
+        Long total = qwWatchLogMapper.selectQwExtCountByDayAndCount(param);
+        rspData.setRows(vos);
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 查询进线统计,排除掉转接的数据
+     * @param param
+     * @return
+     */
+    @Override
+    public TableDataInfo selectQwWatchLogStatisticsListVOExcludeTransfer(QwWatchLogStatisticsListParam param){
         CompanyDept companyDept = companyDeptMapper.selectCompanyDeptById(param.getDeptId());
         if (ObjectUtils.isNotEmpty(companyDept)&&companyDept.getParentId()==0L){
             param.setDeptId(null);
@@ -189,7 +265,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.selectQwExtCountByDayAndExcludeTransfer(param);
         for (QwWatchLogStatisticsListVO vo : vos) {
             Long id = vo.getId();
             Date createTime = vo.getCreateTime();
@@ -208,7 +284,6 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
         rspData.setTotal(total);
         return rspData;
     }
-
     @Override
     public List<QwWatchLogAllStatisticsListVO> selectQwWatchLogAllStatisticsListVO(QwWatchLogStatisticsListParam param) {
         Date sTime = param.getSTime();

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

@@ -139,6 +139,9 @@ public class QwSopTempSetting implements Serializable{
             //链接过期时间
             private Integer expiresDays;
 
+            //视频ID
+            private Long videoId;
+
             @Override
             public Setting clone() {
                 try {

+ 10 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -138,10 +138,20 @@ public class QwSop implements Serializable
     private List<Long> qwUserIdList;
 
     @Excel(name = "开启评论或者弹幕,1-开启评论;2-开启弹幕;3-都关闭")
+    @TableField(exist = false)
     private Integer openCommentStatus;
 
     /**
      * 是否按照营期 发送官方群发 1是 2否(否的时候按单链发)
      */
     private Integer isSampSend;
+    private Integer isLiveMgs;
+    private Long liveTempId;
+    private String liveTempSendTime;
+    private Integer autoGroup;
+    private String autoGroupLevel;
+    private String groupName;
+    private Integer autoUserReg;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String pullTime;
 }

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

@@ -22,6 +22,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
+import java.time.LocalDate;
 import java.util.List;
 import java.util.Set;
 
@@ -427,4 +428,10 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
 
     @DataSource(DataSourceType.SOP)
     List<QwSop> selectAllQwSopInfo(QwSop qwSop);
+
+    @DataSource(DataSourceType.SOP)
+    List<QwSop> selectGroup(@Param("now")LocalDate now);
+
+    @DataSource(DataSourceType.SOP)
+    void updateSopGroupIds(@Param("id") String id, @Param("chatId") String chatId);
 }

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

@@ -198,6 +198,9 @@ public class QwSopServiceImpl implements IQwSopService
         qwSop.setCreateTime(sdf.format(new Date()));
         QwSopTemp qwSopTemp = qwSopTempMapper.selectById(qwSop.getTempId());
         qwSop.setProject(qwSopTemp.getProject());
+        if(qwSop.getAutoGroup() == 1){
+            qwSop.setPullTime(qwSop.getStartTime());
+        }
         return qwSopMapper.insert(qwSop);
     }
 

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

@@ -21,6 +21,7 @@ public class SopUserLogsVo  {
     private Integer minSend;
     private Long externalId;
     private Integer maxSend;
+    private Integer filterMode;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String startTime;

+ 10 - 1
fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java

@@ -131,8 +131,12 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             task.setRetryCount(0);
             task.setQwExternalContactId(item.getQwExternalContactId());
             task.setQwUserId(item.getQwUserId());
-
             FsUserCourseVideo fsUserCourseVideo = courseVideoMap.get(task.getVideoId());
+            if(ObjectUtil.isNull(item.getQwUserId())){
+                log.error("qwUserId is null 已跳过");
+                continue;
+            }
+
             String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
             if(StringUtils.isNotNull(corpId)){
                 task.setCorpId(corpId);
@@ -190,6 +194,11 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
             task.setQwExternalContactId(item.getQwExternalContactId());
             task.setQwUserId(item.getQwUserId());
             task.setLogType(1);
+            if(ObjectUtil.isNull(item.getQwUserId())){
+                log.error("qwUserId is null 已跳过");
+                continue;
+            }
+
             String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
             if(StringUtils.isNotNull(corpId)){
                 task.setCorpId(corpId);

+ 17 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxCreateRoomMsgDTO.java

@@ -0,0 +1,17 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WxCreateRoomMsgDTO {
+    /**
+     * 消息的唯一标识符 (UUID)
+     */
+    private String uuid;
+    private String roomName;
+    private Long roomid;
+    private List<Long> vids;
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxCreateRoomRespDTO.java

@@ -0,0 +1,11 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxCreateRoomRespDTO {
+    private String roomname;
+    private Long createTime;
+    private Long createid;
+    private Long roomid;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatIdToRoomIdDTO.java

@@ -7,4 +7,5 @@ public class WxWorkChatIdToRoomIdDTO {
     private String uuid;
     private String corpid;
     private String chatid;
+    private Long room_id;
 }

+ 11 - 0
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceNew.java

@@ -221,4 +221,15 @@ public class WxWorkServiceNew {
         String url = getUrl(serverId) + "/SendTextAtMsg";
         return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxSendAtMsgVo>>() {});
     }
+
+    public WxWorkResponseDTO<WxCreateRoomRespDTO> createRoom(WxCreateRoomMsgDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/CreateRoomWx";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxCreateRoomRespDTO>>() {});
+    }
+
+
+    public WxWorkResponseDTO<WxWorkChatIdToRoomIdResp> RoomIdToChatId(WxWorkChatIdToRoomIdDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/RoomIdToChatId";
+        return WxWorkHttpUtilNew.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxWorkChatIdToRoomIdResp>>() {}, serverId);
+    }
 }

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

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

+ 5 - 3
fs-service/src/main/resources/application-config-druid-hst.yml

@@ -88,12 +88,14 @@ headerImg:
   imgUrl:
 ipad:
   ipadUrl: http://ipad.schstyl.cn
-  aiApi:
-  voiceApi:
-  commonApi:
+  aiApi: http://49.232.181.28:3000/api
+  voiceApi: http://123.207.48.104:8009
+  commonApi: http://123.207.48.104:7771
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:
 
 # 0 代表关闭 1代表开启(润天老商户号的扣款限制)
 enableRedPackAccount: 1
+
+

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

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

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

@@ -155,5 +155,11 @@ im:
     type: NONE
 #是否为新商户,新商户不走mpOpenId
 isNewWxMerchant: false
-
+qw:
+    enableAutoTag: 1
+tag:
+    thread:
+        num: 5
+    rate:
+        limit: 30
 

+ 8 - 0
fs-service/src/main/resources/mapper/company/CompanyDeptMapper.xml

@@ -170,5 +170,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectChildrenDeptById" parameterType="Long" resultMap="CompanyDeptResult">
         select * from company_dept where find_in_set(#{deptId}, ancestors)
     </select>
+    <select id="selectCompanyDeptByIds" resultType="com.fs.company.domain.CompanyDept">
+        <include refid="selectCompanyDeptVo"/>
+        where dept_id in
+        <foreach collection="depts" item="deptId" index="index"
+                 separator="," open="(" close=")">
+            #{deptId}
+        </foreach>
+    </select>
 
 </mapper>

+ 2 - 1
fs-service/src/main/resources/mapper/company/CompanyMapper.xml

@@ -200,6 +200,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="groupName != null">group_name = #{groupName},</if>
             <if test="maxPadNum != null">max_pad_num = #{maxPadNum},</if>
             <if test="deptId != null">dept_id = #{deptId},</if>
+            <if test="redPackageMoney != null">red_package_money = #{redPackageMoney},</if>
         </trim>
         where company_id = #{companyId}
     </update>
@@ -264,7 +265,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectCompanyMoneyAllList" resultType="com.fs.company.domain.Company">
-        select company_id,company_name,money from company where is_del= 0
+        select company_id,company_name,money,red_package_money from company where is_del= 0
     </select>
 
     <update id="batchUpdateCompany" parameterType="java.util.List">

+ 11 - 8
fs-service/src/main/resources/mapper/company/CompanyRechargeMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.company.mapper.CompanyRechargeMapper">
-    
+
     <resultMap type="CompanyRecharge" id="CompanyRechargeResult">
         <result property="rechargeId"    column="recharge_id"    />
         <result property="companyId"    column="company_id"    />
@@ -20,15 +20,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="auditUserId"    column="audit_user_id"    />
         <result property="auditTime"    column="audit_time"    />
         <result property="remark"    column="remark"    />
+        <result property="businessType"    column="business_type"    />
     </resultMap>
 
     <sql id="selectCompanyRechargeVo">
-        select recharge_id, company_id, recharge_no, money, create_time, pay_time, status, pay_type, trade_no, balance, create_user_id, is_audit, audit_user_id, audit_time,remark from company_recharge
+        select recharge_id, company_id, recharge_no, money, create_time, pay_time, status, pay_type, trade_no, balance, create_user_id, is_audit, audit_user_id, audit_time,remark, business_type from company_recharge
     </sql>
 
     <select id="selectCompanyRechargeList" parameterType="CompanyRecharge" resultMap="CompanyRechargeResult">
         <include refid="selectCompanyRechargeVo"/>
-        <where>  
+        <where>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="rechargeNo != null  and rechargeNo != ''"> and recharge_no = #{rechargeNo}</if>
             <if test="money != null "> and money = #{money}</if>
@@ -43,12 +44,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditTime != null "> and audit_time = #{auditTime}</if>
         </where>
     </select>
-    
+
     <select id="selectCompanyRechargeById" parameterType="Long" resultMap="CompanyRechargeResult">
         <include refid="selectCompanyRechargeVo"/>
         where recharge_id = #{rechargeId}
     </select>
-        
+
     <insert id="insertCompanyRecharge" parameterType="CompanyRecharge" useGeneratedKeys="true" keyProperty="rechargeId">
         insert into company_recharge
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -66,6 +67,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditUserId != null">audit_user_id,</if>
             <if test="auditTime != null">audit_time,</if>
             <if test="remark != null">remark,</if>
+            <if test="businessType != null">business_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyId != null">#{companyId},</if>
@@ -82,6 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditUserId != null">#{auditUserId},</if>
             <if test="auditTime != null">#{auditTime},</if>
             <if test="remark != null">#{remark},</if>
+            <if test="businessType != null">#{businessType},</if>
          </trim>
     </insert>
 
@@ -111,10 +114,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteCompanyRechargeByIds" parameterType="String">
-        delete from company_recharge where recharge_id in 
+        delete from company_recharge where recharge_id in
         <foreach item="rechargeId" collection="array" open="(" separator="," close=")">
             #{rechargeId}
         </foreach>
     </delete>
-    
-</mapper>
+
+</mapper>

+ 56 - 181
fs-service/src/main/resources/mapper/company/StatisticManageMapper.xml

@@ -40,7 +40,7 @@
                 qw_external_contact AS qec
             WHERE
                 date_format( qec.create_time, '%y%m%d' ) = date_format(now(), '%y%m%d' )
-              AND qec.company_user_id = 327
+              AND qec.company_user_id = #{userIds}
         ),
         t2 AS (
                  SELECT
@@ -50,181 +50,14 @@
                  WHERE
                      date_format( qec.create_time, '%y%m%d' ) = date_format(now(), '%y%m%d' )
                    AND qec.fs_user_id IS NOT NULL
-                   AND qec.company_user_id = 327
+                   AND qec.company_user_id = #{userIds}
              ),
-             t3 AS ( SELECT count(*) AS completeNum FROM fs_course_watch_log AS fcwl WHERE date_format(fcwl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d') and fcwl.log_type = 2 AND fcwl.company_user_id = 327 ),
-             t4 AS ( SELECT count(*) AS answerNum FROM fs_course_answer_logs AS fcal WHERE date_format(fcal.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d')  and fcal.company_user_id = 327 ),
-             t5 AS ( SELECT count(*) AS redPacketNum FROM fs_course_red_packet_log AS fcrpl WHERE date_format(fcrpl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d' )  and  fcrpl.company_user_id = 327 )
+             t3 AS ( SELECT count(*) AS completeNum FROM fs_course_watch_log AS fcwl WHERE date_format(fcwl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d') and fcwl.log_type = 2 AND fcwl.company_user_id = #{userIds} ),
+             t4 AS ( SELECT count(*) AS answerNum FROM fs_course_answer_logs AS fcal WHERE date_format(fcal.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d')  and fcal.company_user_id = #{userIds} ),
+             t5 AS ( SELECT count(*) AS redPacketNum FROM fs_course_red_packet_log AS fcrpl WHERE date_format(fcrpl.create_time, '%y%m%d' ) = date_format( now(), '%y%m%d' )  and  fcrpl.company_user_id = #{userIds} )
         SELECT * FROM t1,t2,t3,t4,t5
     </select>
 
-    <select id="getStatisticNumByPersonal" resultType="com.fs.company.dto.ComprehensiveStatisticsDTO">
-        WITH RECURSIVE date_range AS (
-            SELECT #{startTime} AS dt
-            UNION ALL
-            SELECT DATE_ADD(dt, INTERVAL 1 DAY)
-            FROM date_range
-            WHERE dt &lt; #{endTime}
-        ),
-        t1 AS (
-            SELECT
-            COUNT(qec.id) AS t1_count,
-            d.dt AS create_time
-            FROM date_range d
-            LEFT JOIN qw_external_contact qec
-            ON DATE(qec.create_time) = d.dt
-        <if test="userIds != null">
-            <if test="dimension == 1">
-                <choose>
-                    <when test="userIds.length > 1 ">
-                        AND qec.company_user_id IN (
-                        <foreach collection="userIds" item="i" separator=",">
-                            #{i}
-                        </foreach>
-                        )
-                    </when>
-                    <otherwise>
-                        AND qec.company_user_id = #{userIds[0]}
-                    </otherwise>
-                </choose>
-            </if>
-            <if test="dimension == 2">
-                AND qec.company_id = #{userIds[0]}
-            </if>
-        </if>
-            GROUP BY d.dt
-        ),
-        t2 AS (
-            SELECT
-            COUNT(qec.id) AS t2_count,
-            d.dt AS create_time
-            FROM date_range d
-            LEFT JOIN qw_external_contact qec
-            ON DATE(qec.create_time) = d.dt
-        <if test="userIds != null">
-            <if test="dimension == 1">
-                <choose>
-                    <when test="userIds.length > 1 ">
-                        AND qec.company_user_id IN (
-                        <foreach collection="userIds" item="i" separator=",">
-                            #{i}
-                        </foreach>
-                        )
-                    </when>
-                    <otherwise>
-                        AND qec.company_user_id = #{userIds[0]}
-                    </otherwise>
-                </choose>
-            </if>
-            <if test="dimension == 2">
-                AND qec.company_id = #{userIds[0]}
-            </if>
-        </if>
-            AND qec.fs_user_id IS NOT NULL
-            GROUP BY d.dt
-        ),
-        t4 AS (
-            SELECT
-            d.dt AS create_time,
-            COUNT(fcwl.qw_external_contact_id) AS completeNum
-            FROM
-            date_range d
-            LEFT JOIN fs_course_watch_log AS fcwl
-            ON DATE(fcwl.create_time) = d.dt
-            AND fcwl.log_type = 2
-        <if test="userIds != null">
-            <if test="dimension == 1">
-                <choose>
-                    <when test="userIds.length > 1 ">
-                        AND fcwl.company_user_id IN (
-                        <foreach collection="userIds" item="i" separator=",">
-                            #{i}
-                        </foreach>
-                        )
-                    </when>
-                    <otherwise>
-                        AND fcwl.company_user_id = #{userIds[0]}
-                    </otherwise>
-                </choose>
-            </if>
-            <if test="dimension == 2">
-                AND fcwl.company_id = #{userIds[0]}
-            </if>
-        </if>
-            GROUP BY d.dt
-        ),
-        t5 AS (
-            SELECT
-            d.dt AS create_time,
-            COUNT(fcal.log_id) AS answerNum
-            FROM
-            date_range d
-            LEFT JOIN fs_course_answer_logs AS fcal
-            ON DATE(fcal.create_time) = d.dt
-        <if test="userIds != null">
-            <if test="dimension == 1">
-                <choose>
-                    <when test="userIds.length > 1 ">
-                        AND fcal.company_user_id IN (
-                        <foreach collection="userIds" item="i" separator=",">
-                            #{i}
-                        </foreach>
-                        )
-                    </when>
-                    <otherwise>
-                        AND fcal.company_user_id = #{userIds[0]}
-                    </otherwise>
-                </choose>
-            </if>
-            <if test="dimension == 2">
-                AND fcal.company_id = #{userIds[0]}
-            </if>
-        </if>
-            GROUP BY d.dt
-        ),
-        t6 AS (
-            SELECT
-            d.dt AS create_time,
-            COUNT(fcrpl.log_id) AS redPacketNum
-            FROM
-            date_range d
-            LEFT JOIN fs_course_red_packet_log AS fcrpl
-            ON DATE(fcrpl.create_time) = d.dt
-        <if test="userIds != null">
-            <if test="dimension == 1">
-                <choose>
-                    <when test="userIds.length > 1 ">
-                        AND fcrpl.company_user_id IN (
-                        <foreach collection="userIds" item="i" separator=",">
-                            #{i}
-                        </foreach>
-                        )
-                    </when>
-                    <otherwise>
-                        AND fcrpl.company_user_id = #{userIds[0]}
-                    </otherwise>
-                </choose>
-            </if>
-            <if test="dimension == 2">
-                AND fcrpl.company_id = #{userIds[0]}
-            </if>
-        </if>
-            GROUP BY d.dt
-        )
-        SELECT
-            t1.create_time as dateStr,
-            t1.t1_count as lineNum,
-            t2.t2_count as activeNum,
-            t4.completeNum,
-            t5.answerNum,
-            t6.redPacketNum
-        FROM t1
-        INNER JOIN t2 ON t1.create_time = t2.create_time
-        INNER JOIN t4 ON t1.create_time = t4.create_time
-        INNER JOIN t5 ON t1.create_time = t5.create_time
-        INNER JOIN t6 ON t1.create_time = t6.create_time
-        ORDER BY t1.create_time
-    </select>
 
     <select id="getCompanyInfo" resultType="com.fs.company.domain.CompanyDeptUserInfo">
         SELECT
@@ -244,6 +77,33 @@
         red_packet_num, red_packet_amount, create_time, update_time
     </sql>
 
+    <select id="getStatisticNumByPersonal" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        select <include refid="Base_Column_List"/> from user_daily_stats as uds
+        <where>
+            uds.user_id = #{userIds[0]}
+            and statistics_time >=date_format(#{startTime},'%y%m%d')
+            and statistics_time &lt;= date_format(#{endTime},'%y%m%d')
+        </where>
+    </select>
+
+    <select id="getStatisticNumByDeptId" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        select <include refid="Base_Column_List"/> from user_daily_stats as uds
+        <where>
+            uds.dept_id = #{deptIds[0]}
+            and statistics_time >=date_format(#{startTime},'%y%m%d')
+            and statistics_time &lt;= date_format(#{endTime},'%y%m%d')
+        </where>
+    </select>
+
+    <select id="getStatisticNumByCompanyId" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        select <include refid="Base_Column_List"/> from user_daily_stats as uds
+        <where>
+            uds.company_id = #{companyIds[0]}
+            and statistics_time >=date_format(#{startTime},'%y%m%d')
+            and statistics_time &lt;= date_format(#{endTime},'%y%m%d')
+        </where>
+    </select>
+
     <!-- 1. 插入数据(全字段插入) -->
     <insert id="insert" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
         INSERT INTO user_daily_stats (
@@ -259,7 +119,7 @@
         )
     </insert>
 
-    <!-- 2. 插入或更新(根据唯一索引uk_user_date,存在则更新,不存在则插入) -->
+    <!-- 2. 插入或更新(根据唯一索引uk_user_date,存在则更新,不存在则插入) 已经删除了索引,有时候用户id是空的 -->
     <insert id="insertOrUpdate" parameterType="com.fs.company.domain.ComprehensiveDailyStats">
         INSERT INTO user_daily_stats (
             company_id, company_name, dept_id, dept_name,
@@ -320,7 +180,7 @@
             red_packet_num = #{redPacketNum},
             red_packet_amount = #{redPacketAmount},
             update_time = NOW()
-        WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+        WHERE user_id = #{userId} AND statistics_time = date_format(#{statisticsTime},'%y%m%d')
     </update>
 
     <!-- 5. 根据ID查询 -->
@@ -332,16 +192,22 @@
     <select id="selectByUserAndDate" resultType="com.fs.company.domain.ComprehensiveDailyStats">
         SELECT <include refid="Base_Column_List"/>
         FROM user_daily_stats
-        WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+        WHERE user_id = #{userId} AND statistics_time =date_format(#{statisticsTime},'%y%m%d')
     </select>
 
-    <!-- 7. 根据公司ID和日期范围查询 -->
-    <select id="selectByCompanyAndDateRange" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+    <!-- 6. 根据用户ID和统计日期查询 -->
+    <select id="countByUserAndDate" resultType="java.lang.Integer">
+        SELECT count(id)
+        FROM user_daily_stats
+        WHERE user_id = #{userId} AND statistics_time = date_format(#{statisticsTime},'%y%m%d')
+    </select>
+
+    <!-- 8. 根据部门ID和日期查询 -->
+    <select id="selectByDeptAndDate" resultType="com.fs.company.domain.ComprehensiveDailyStats">
         SELECT <include refid="Base_Column_List"/>
         FROM user_daily_stats
-        WHERE company_id = #{companyId}
-        AND statistics_time BETWEEN #{startTime} AND #{endTime}
-        ORDER BY statistics_time ASC
+        WHERE dept_id = #{deptId}
+        AND statistics_time = date_format(#{statisticsTime},'%y%m%d')
     </select>
 
     <!-- 8. 根据部门ID和日期范围查询 -->
@@ -353,6 +219,15 @@
         ORDER BY statistics_time ASC
     </select>
 
+    <!-- 7. 根据公司ID和日期范围查询 -->
+    <select id="selectByCompanyAndDateRange" resultType="com.fs.company.domain.ComprehensiveDailyStats">
+        SELECT <include refid="Base_Column_List"/>
+        FROM user_daily_stats
+        WHERE company_id = #{companyId}
+        AND statistics_time BETWEEN #{startTime} AND #{endTime}
+        ORDER BY statistics_time ASC
+    </select>
+
     <!-- 9. 删除数据(根据ID) -->
     <delete id="deleteById" parameterType="java.lang.Long">
         DELETE FROM user_daily_stats WHERE id = #{id}
@@ -360,7 +235,7 @@
 
     <!-- 10. 删除数据(根据用户ID和统计日期) -->
     <delete id="deleteByUserAndDate">
-        DELETE FROM user_daily_stats WHERE user_id = #{userId} AND statistics_time = #{statisticsTime}
+        DELETE FROM user_daily_stats WHERE user_id = #{userId} AND statistics_time = date_format(#{statisticsTime},'%y%m%d')
     </delete>
 
 </mapper>

+ 3 - 2
fs-service/src/main/resources/mapper/course/FsCourseLinkMapper.xml

@@ -135,7 +135,7 @@
         link, real_link, create_time, update_time,
         company_id, company_user_id, qw_user_id, video_id,
         corp_id, course_id, qw_external_id, link_type,
-        is_room
+        is_room,chat_id
         )
         VALUES
         <foreach collection="courseLinks" item="item" separator=",">
@@ -152,7 +152,8 @@
             #{item.courseId,jdbcType=BIGINT},
             #{item.qwExternalId,jdbcType=BIGINT},
             #{item.linkType,jdbcType=BIGINT},
-            #{item.isRoom,jdbcType=VARCHAR}
+            #{item.isRoom,jdbcType=VARCHAR},
+            #{item.chatId,jdbcType=VARCHAR}
             )
         </foreach>
     </insert>

+ 35 - 2
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -72,8 +72,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM
         fs_course_traffic_log
         <where>
-            create_time  &gt;= CURDATE()
-            AND create_time &lt; CURDATE() + INTERVAL 1 DAY
+            create_time  &gt;= CURDATE() - INTERVAL 1 DAY
+            AND create_time &lt; CURDATE()
             <if test="companyId != null">
                 AND company_id = ${companyId}
             </if>
@@ -281,4 +281,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="selectTrafficByCompany" parameterType="com.fs.course.param.FsCourseTrafficLogParam"
+            resultType="com.fs.course.vo.FsCourseTrafficLogListVO">
+        SELECT
+        c.company_name AS companyName,
+        l.company_id AS companyId,
+        '' AS courseName,
+        0 AS courseId,
+        '' AS projectName,
+        0 AS project,
+        SUM(l.internet_traffic) AS totalInternetTraffic,
+        DATE_FORMAT(l.create_time, '%Y-%m') AS month
+        FROM fs_course_traffic_log l
+        LEFT JOIN company c ON c.company_id = l.company_id
+        <where>
+            <if test="startDate != null and startDate != ''">
+                AND l.create_time >= STR_TO_DATE(#{startDate}, '%Y-%m-%d %H:%i:%s')
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND l.create_time &lt;= STR_TO_DATE(#{endDate}, '%Y-%m-%d %H:%i:%s')
+            </if>
+            <if test="companyId != null">
+                AND l.company_id = #{companyId}
+            </if>
+            <if test="common == null">
+                AND l.company_id IS NOT NULL
+            </if>
+            <if test="common != null">
+                AND l.company_id IS NULL
+            </if>
+        </where>
+        GROUP BY l.company_id, DATE_FORMAT(l.create_time, '%Y-%m')
+    </select>
+
 </mapper>

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

@@ -826,4 +826,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where qgc.chat_id = #{chatId} and qec.qw_user_id = #{qwUserId}
     </select>
 
+
+    <update id="updateJoinGroup">
+        update qw_external_contact set join_group = 1 where id in <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
+    </update>
 </mapper>

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

@@ -102,6 +102,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="todayJoin != null">today_join,</if>
             <if test="todayOut != null">today_out,</if>
             <if test="allOutGroup != null">all_out_group,</if>
+            <if test="sopId != null">sop_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="roomid != null">roomid,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="chatId != null">#{chatId},</if>
@@ -119,6 +122,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="todayJoin != null">#{todayJoin},</if>
             <if test="todayOut != null">#{todayOut},</if>
             <if test="allOutGroup != null">#{allOutGroup},</if>
+            <if test="sopId != null">#{sopId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="roomid != null">#{roomid},</if>
         </trim>
         on duplicate key update
         <trim suffixOverrides=",">
@@ -136,6 +142,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="todayJoin != null">today_join = #{todayJoin},</if>
             <if test="todayOut != null">today_out = #{todayOut},</if>
             <if test="allOutGroup != null">all_out_group = #{allOutGroup},</if>
+            <if test="sopId != null">sop_id = #{sopId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="roomid != null">roomid = #{roomid},</if>
         </trim>
     </insert>
 
@@ -232,4 +241,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and qgc.name like concat('%', #{name}, '%')
         </if>
     </select>
+
+    <select id="selectSopAndQwUser" resultType="com.fs.qw.domain.QwGroupChat">
+       select * from qw_group_chat where owner = #{qwUserId} and sop_id = #{sopId}
+            order by create_time desc limit 1
+    </select>
 </mapper>

+ 2 - 2
fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml

@@ -212,8 +212,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         JOIN
         qw_user qu ON qec.qw_user_id = qu.id
         WHERE
-        DATE(qec.create_time) &gt;= DATE(#{sTime})
-        AND DATE(qec.create_time) &lt;= DATE(#{eTime})
+        qec.create_time &gt;= #{sTime}
+        AND qec.create_time &lt; DATE_ADD(#{eTime}, INTERVAL 1 DAY)
         AND qec.company_id = #{companyId}
         <if test='nickName != null and nickName != ""'>
             AND qu.qw_user_name LIKE CONCAT(#{nickName}, '%')

+ 35 - 0
fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -35,6 +35,12 @@
         <result property="chatId"    column="chat_id"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
         <result property="isSampSend"    column="is_samp_send"    />
+        <result property="isLiveMgs"    column="is_live_mgs"    />
+        <result property="liveTempId"    column="live_temp_id"    />
+        <result property="liveTempSendTime"    column="live_temp_send_time"    />
+        <result property="autoGroup"    column="auto_group"    />
+        <result property="autoGroupLevel"    column="auto_group_level"    />
+        <result property="groupName"    column="group_name"    />
     </resultMap>
 
     <sql id="selectQwSopVo">
@@ -282,6 +288,12 @@
             <if test="data.isRating != null">is_rating,</if>
             <if test="data.courseDay != null">course_day,</if>
             <if test="data.openCommentStatus != null">open_comment_status,</if>
+            <if test="data.isLiveMgs != null">is_live_mgs,</if>
+            <if test="data.liveTempId != null">live_temp_id,</if>
+            <if test="data.liveTempSendTime != null">live_temp_send_time,</if>
+            <if test="data.autoGroup != null">auto_group,</if>
+            <if test="data.autoGroupLevel != null">auto_group_level,</if>
+            <if test="data.groupName != null">group_name,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="data.name != null">#{data.name},</if>
@@ -308,6 +320,12 @@
             <if test="data.isRating != null">#{data.isRating},</if>
             <if test="data.courseDay != null">#{data.courseDay},</if>
             <if test="data.openCommentStatus != null">#{data.openCommentStatus},</if>
+            <if test="data.isLiveMgs != null">#{data.isLiveMgs},</if>
+            <if test="data.liveTempId != null">#{data.liveTempId},</if>
+            <if test="data.liveTempSendTime != null">#{data.liveTempSendTime},</if>
+            <if test="data.autoGroup != null">#{data.autoGroup},</if>
+            <if test="data.autoGroupLevel != null">#{data.autoGroupLevel},</if>
+            <if test="data.groupName != null">#{data.groupName},</if>
         </trim>
     </insert>
 
@@ -388,6 +406,10 @@
             <if test="minSend != null "> and min_send = #{minSend}</if>
             <if test="maxSend != null "> and max_send = #{maxSend}</if>
             <if test="stopTime != null "> and stop_time = #{stopTime}</if>
+            <if test="isLiveMgs != null "> and is_live_mgs = #{isLiveMgs}</if>
+            <if test="liveTempId != null "> and live_temp_id = #{liveTempId}</if>
+            <if test="liveTempSendTime != null "> and live_temp_send_time = #{liveTempSendTime}</if>
+            <if test="groupName != null "> and group_name = #{groupName}</if>
             <!-- 加入固定条件 -->
             and status != 6
         </where>
@@ -477,6 +499,12 @@
             <if test="data.openCommentStatus != null">open_comment_status = #{data.openCommentStatus},</if>
             <if test="data.chatId != null">chat_id = #{data.chatId},</if>
             <if test="data.isSampSend != null">is_samp_send = #{data.isSampSend},</if>
+            <if test="data.isLiveMgs != null">is_live_mgs = #{data.isLiveMgs},</if>
+            <if test="data.liveTempId != null">live_temp_id = #{data.liveTempId},</if>
+            <if test="data.liveTempSendTime != null">live_temp_send_time = #{data.liveTempSendTime},</if>
+            <if test="data.autoGroup != null">auto_group = #{data.autoGroup},</if>
+            <if test="data.autoGroupLevel != null">auto_group_level = #{data.autoGroupLevel},</if>
+            <if test="data.groupName != null">group_name = #{data.groupName},</if>
         </trim>
         where id = #{data.id}
     </update>
@@ -568,5 +596,12 @@
         </where>
         order by create_time desc
     </select>
+    <select id="selectGroup" resultMap="QwSopResult">
+        select * from qw_sop where auto_group = 1
+    </select>
+
+    <update id="updateSopGroupIds">
+        update qw_sop set chat_id = #{chatId},pull_time = now() where id = #{id}
+    </update>
 
 </mapper>