소스 검색

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

caoliqin 1 주 전
부모
커밋
1c5c1f5629
31개의 변경된 파일657개의 추가작업 그리고 168개의 파일을 삭제
  1. 4 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  2. 24 0
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  3. 18 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java
  4. 1 1
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  5. 15 0
      fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java
  6. 1 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  7. 0 37
      fs-admin/src/test/java/com/fs/api/controller/IndexStatisticsControllerTest.java
  8. 37 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  9. 31 18
      fs-service/src/main/java/com/fs/company/domain/CompanyDeduct.java
  10. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeductMapper.java
  11. 19 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  12. 3 1
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  13. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  14. 75 11
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  15. 11 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  16. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyDeductVO.java
  17. 1 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  18. 5 2
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  19. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java
  20. 3 3
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  21. 22 6
      fs-service/src/main/java/com/fs/statis/mapper/ConsumptionBalanceMapper.java
  22. 81 4
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java
  23. 79 4
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  24. 6 2
      fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
  25. 10 2
      fs-service/src/main/java/com/fs/tag/service/impl/FsVideoCourseTagServiceImpl.java
  26. 1 1
      fs-service/src/main/resources/application-config-druid-hat.yml
  27. 1 1
      fs-service/src/main/resources/application-druid-knt2.yml
  28. 11 8
      fs-service/src/main/resources/mapper/company/CompanyDeductMapper.xml
  29. 11 5
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  30. 0 6
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  31. 174 42
      fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml

+ 4 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java

@@ -161,14 +161,17 @@ public class FsUserCoursePeriodController extends BaseController {
 
     @PreAuthorize("@ss.hasPermi('course:period:updateCourseTime')")
     @PostMapping("/updateCourseTime")
+    @Log(title = "会员营期修改课程时间", businessType = BusinessType.UPDATE)
     public R updateCourseTime(@RequestBody UpdateCourseTimeVo vo){
         return fsUserCoursePeriodDaysService.updateCourseTime(vo);
     }
     @PostMapping("/updateCourseDate")
+    @Log(title = "会员营期修改CourseDate", businessType = BusinessType.UPDATE)
     public R updateCourseDate(@RequestBody UpdateCourseTimeVo vo){
         return fsUserCoursePeriodDaysService.updateCourseDate(vo);
     }
     @PostMapping("/updateListCourseData")
+    @Log(title = "会员营期修改ListCourseData", businessType = BusinessType.UPDATE)
     public R updateListCourseData(@RequestBody List<FsUserCoursePeriodDays> entity){
         return fsUserCoursePeriodDaysService.updateListCourseData(entity);
     }
@@ -275,6 +278,7 @@ public class FsUserCoursePeriodController extends BaseController {
 
     @PreAuthorize("@ss.hasPermi('course:period:setCompanyRedPacket')")
     @ApiOperation("按公司批量保存设置红包金额")
+    @Log(title = "按公司批量保存设置红包金额", businessType = BusinessType.UPDATE)
     @PostMapping("/batchRedPacket/byCompany")
     public R batchRedPacketByCompany(@Validated @RequestBody BatchCompanyRedPackageParam param) {
         try {

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

@@ -252,6 +252,30 @@ public class FsCompanyController extends BaseController {
 
     }
 
+    @PreAuthorize("@ss.hasPermi('his:company:redDeduct')")
+    @Log(title = "企业扣款", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/redDeduct")
+    @Transactional
+    @RepeatSubmit
+    public R redDeduct(@RequestBody CompanyDeductParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyDeduct deduct=new CompanyDeduct();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        deduct.setDeductNo(orderSn);
+        deduct.setCompanyId(param.getCompanyId());
+        deduct.setMoney(param.getMoney());
+        deduct.setCreateUserId(loginUser.getUser().getUserId());
+        deduct.setIsAudit(0);
+        deduct.setRemark(param.getRemark());
+        deduct.setBusinessType(1); // 红包扣款
+        deductService.insertCompanyDeduct(deduct);
+        return R.ok("提交成功,等待审核");
+    }
+
     @PreAuthorize("@ss.hasPermi('his:company:deduct')")
     @Log(title = "企业扣款", businessType = BusinessType.INSERT)
     @PostMapping(value = "/deduct")

+ 18 - 13
fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java

@@ -139,19 +139,24 @@ public class FsCompanyDeductController extends BaseController
         deduct.setRemark(param.getRemark());
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if(deduct.getIsAudit()==1){
-            Company company=companyService.selectCompanyByIdForUpdate(deduct.getCompanyId());
-            company.setMoney(company.getMoney().subtract(deduct.getMoney()));
-            deduct.setBalance(company.getMoney());
-            companyService.updateCompany(company);
-            //生成流水
-            CompanyMoneyLogs log=new CompanyMoneyLogs();
-            log.setCompanyId(deduct.getCompanyId());
-            log.setMoney(deduct.getMoney().multiply(new BigDecimal(-1)));
-            log.setRemark(deduct.getRemark());
-            log.setLogsType(2);
-            log.setBalance(company.getMoney());
-            log.setCreateTime(new Date());
-            moneyLogsService.insertCompanyMoneyLogs(log);
+            if(1==param.getBusinessType()){// 红包充值
+                // 更新红包充值余额redis字段
+                companyService.redPacketTopUpCompany(deduct.getCompanyId(),deduct.getMoney(),"2");
+            }else {
+                Company company=companyService.selectCompanyByIdForUpdate(deduct.getCompanyId());
+                company.setMoney(company.getMoney().subtract(deduct.getMoney()));
+                deduct.setBalance(company.getMoney());
+                companyService.updateCompany(company);
+                //生成流水
+                CompanyMoneyLogs log=new CompanyMoneyLogs();
+                log.setCompanyId(deduct.getCompanyId());
+                log.setMoney(deduct.getMoney().multiply(new BigDecimal(-1)));
+                log.setRemark(deduct.getRemark());
+                log.setLogsType(2);
+                log.setBalance(company.getMoney());
+                log.setCreateTime(new Date());
+                moneyLogsService.insertCompanyMoneyLogs(log);
+            }
         }
         deduct.setAuditTime(new Date());
         deduct.setAuditUserId(loginUser.getUser().getUserId());

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

@@ -139,7 +139,7 @@ public class FsCompanyRechargeController extends BaseController
         if(companyRecharge.getIsAudit()==1){
             if(1==companyRecharge.getBusinessType()){// 红包充值
                 // 更新红包充值余额redis字段
-                companyService.redPacketTopUpCompany(companyRecharge.getCompanyId(),companyRecharge.getMoney());
+                companyService.redPacketTopUpCompany(companyRecharge.getCompanyId(),companyRecharge.getMoney(),"1");
             }else {
                 Company company=companyService.selectCompanyByIdForUpdate(companyRecharge.getCompanyId());
                 company.setMoney(company.getMoney().add(companyRecharge.getMoney()));

+ 15 - 0
fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java

@@ -1,11 +1,14 @@
 package com.fs.his.task;
 
 import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.service.BalanceRollbackErrorService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
+
 /**
  * @description: 公司余额同步定时任务 (红包余额)
  * @author: Xgb
@@ -58,6 +61,18 @@ public class CompanyBalanceTask {
 
     }
 
+    /**
+     * @Description: 红包余额回滚(回滚的是客户没领取的红包),红包记录表中,两天没领取的记录不会再发送
+     * @Param: 每天0点执行一次
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/7 9:48
+     */
+    public void rollbackRedPacketMoney() throws Exception {
+        // 这个地方真加的是company money字段 xgb 红包余额独立后这个方法弃用
+        companyService.rollbackRedPacketMoney();
+    }
+
     /**
      * @Description: 每天晚上0点定时获取获取公司余额,记录到数据库中
      * @Param:

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

@@ -603,6 +603,7 @@ public class Task {
 
 
     public void redPacketAddMoney() throws Exception {
+        // 这个地方真加的是company money字段 xgb 红包余额独立后这个方法弃用
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseAddRedPacketLogByCompany();
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
             companyService.addRedPacketCompanyMoney(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId());

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

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

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

@@ -21,6 +21,7 @@ import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.service.*;
+import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.company.utils.DomainUtil;
 import com.fs.company.utils.QwStatusEnum;
 import com.fs.company.vo.BatchUserRolesVO;
@@ -100,6 +101,10 @@ public class CompanyUserController extends BaseController {
     @Autowired
     IQwCompanyService iQwCompanyService;
 
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+
     @Autowired
     private IQwUserService qwUserService;
 
@@ -475,6 +480,38 @@ public class CompanyUserController extends BaseController {
         return  R.ok().put("data",list);
     }
 
+    @GetMapping("/getQwMyUserList/{id}")
+    public R getQwMyUserList(@PathVariable("id") String corpId)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        List<QwUserVO> list = companyUserService.selectCompanyQwUserListByMy(corpId,loginUser.getCompany().getCompanyId(),loginUser.getUser().getUserId());
+        return  R.ok().put("data",list);
+    }
+
+    @GetMapping("/getQwDeptUserList/{id}")
+    public R getQwDeptUserList(@PathVariable("id") String corpId)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        List<Long> combinedList = new ArrayList<>();
+        //本部门
+        Long deptId = loginUser.getUser().getDeptId();
+        if (deptId!=null){
+            combinedList.add(deptId);
+        }
+        //本部门的下级部门
+        List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+        if (!deptList.isEmpty()){
+            combinedList.addAll(deptList);
+        }
+        String userType = loginUser.getUser().getUserType();
+
+        List<QwUserVO> list = companyUserService.selectCompanyQwUserListByDept(corpId,loginUser.getCompany().getCompanyId(),combinedList,userType);
+        return  R.ok().put("data",list);
+    }
+
+
+
     /**
      * 根据部门的id获取到企业微信的qwuserid
      */

+ 31 - 18
fs-service/src/main/java/com/fs/company/domain/CompanyDeduct.java

@@ -10,7 +10,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
 
 /**
  * 扣款对象 company_deduct
- * 
+ *
  * @author fs
  * @date 2023-02-27
  */
@@ -57,6 +57,17 @@ public class CompanyDeduct extends BaseEntity
     @Excel(name = "备注")
     private String remark;
 
+    @Excel(name = "业务类型")
+    private Integer businessType;
+
+    public Integer getBusinessType() {
+        return businessType;
+    }
+
+    public void setBusinessType(Integer businessType) {
+        this.businessType = businessType;
+    }
+
     @Override
     public String getRemark() {
         return remark;
@@ -72,79 +83,79 @@ public class CompanyDeduct extends BaseEntity
         this.deductId = deductId;
     }
 
-    public Long getDeductId() 
+    public Long getDeductId()
     {
         return deductId;
     }
-    public void setDeductNo(String deductNo) 
+    public void setDeductNo(String deductNo)
     {
         this.deductNo = deductNo;
     }
 
-    public String getDeductNo() 
+    public String getDeductNo()
     {
         return deductNo;
     }
-    public void setCompanyId(Long companyId) 
+    public void setCompanyId(Long companyId)
     {
         this.companyId = companyId;
     }
 
-    public Long getCompanyId() 
+    public Long getCompanyId()
     {
         return companyId;
     }
-    public void setMoney(BigDecimal money) 
+    public void setMoney(BigDecimal money)
     {
         this.money = money;
     }
 
-    public BigDecimal getMoney() 
+    public BigDecimal getMoney()
     {
         return money;
     }
-    public void setBalance(BigDecimal balance) 
+    public void setBalance(BigDecimal balance)
     {
         this.balance = balance;
     }
 
-    public BigDecimal getBalance() 
+    public BigDecimal getBalance()
     {
         return balance;
     }
-    public void setCreateUserId(Long createUserId) 
+    public void setCreateUserId(Long createUserId)
     {
         this.createUserId = createUserId;
     }
 
-    public Long getCreateUserId() 
+    public Long getCreateUserId()
     {
         return createUserId;
     }
-    public void setIsAudit(Integer isAudit) 
+    public void setIsAudit(Integer isAudit)
     {
         this.isAudit = isAudit;
     }
 
-    public Integer getIsAudit() 
+    public Integer getIsAudit()
     {
         return isAudit;
     }
-    public void setAuditUserId(Long auditUserId) 
+    public void setAuditUserId(Long auditUserId)
     {
         this.auditUserId = auditUserId;
     }
 
-    public Long getAuditUserId() 
+    public Long getAuditUserId()
     {
         return auditUserId;
     }
-    public void setAuditTime(Date auditTime) 
+    public void setAuditTime(Date auditTime)
     {
         this.auditTime = auditTime;
     }
 
-    public Date getAuditTime() 
+    public Date getAuditTime()
     {
         return auditTime;
     }
@@ -162,6 +173,8 @@ public class CompanyDeduct extends BaseEntity
             .append("isAudit", getIsAudit())
             .append("auditUserId", getAuditUserId())
             .append("auditTime", getAuditTime())
+                .append("remark", getRemark())
+                .append("businessType", getBusinessType())
             .toString();
     }
 }

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

@@ -108,6 +108,9 @@ public interface CompanyDeductMapper
             "<if test = 'maps.createTime != null  '> " +
             "and   DATE(d.create_time) = DATE(#{maps.createTime})" +
             "</if>" +
+            "<if test = 'maps.businessType != null  '> " +
+            "and   d.business_type = #{maps.businessType}" +
+            "</if>" +
             "<if test = 'maps.companyName != null  '> " +
             "and c.company_name like concat('%', #{maps.companyName }, '%')" +
             "</if>" +

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

@@ -151,6 +151,25 @@ public interface CompanyUserMapper
     @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId}")
     List<QwUserVO> selectCompanyQwUserList(@Param("corpId") String corpId,@Param("companyId")Long companyId);
 
+    @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId} and company_user_id=#{userId}")
+    List<QwUserVO> selectCompanyQwUserListByMy(@Param("corpId") String corpId,@Param("companyId")Long companyId,@Param("userId")Long userId);
+
+    @Select("<script>" +
+            "select qu.* from qw_user qu " +
+            "left join company_user cu on cu.user_id = qu.company_user_id " +
+            "where 1=1 " +
+            "and qu.corp_id = #{corpId} " +
+            "and qu.company_id = #{companyId} " +
+            "<if test=\"combinedList != null and !combinedList.isEmpty() and  userType != '00' \">" +
+            "and cu.dept_id IN " +
+            "<foreach collection='combinedList' item='deptId' open='(' separator=',' close=')'>" +
+            "#{deptId}" +
+            "</foreach>" +
+            "</if>" +
+            "</script>")
+    List<QwUserVO> selectCompanyQwUserListByDept(@Param("corpId") String corpId,@Param("companyId")Long companyId,
+                                                 @Param("combinedList") List<Long> combinedList,@Param("userType")String userType);
+
 
     List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user);
 

+ 3 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -170,7 +170,7 @@ public interface ICompanyService
 
     void syncCompanyBalance();
 
-    void redPacketTopUpCompany(Long companyId, BigDecimal money);
+    void redPacketTopUpCompany(Long companyId, BigDecimal money,String type);
 
     void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark);
 
@@ -181,4 +181,6 @@ public interface ICompanyService
      * @param list 公司列表
      */
     void batchUpdateCompany(List<Company> list);
+
+    void rollbackRedPacketMoney();
 }

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

@@ -129,6 +129,8 @@ public interface ICompanyUserService {
     List<CitysAreaVO> getCitysAreaList();
 
     List<QwUserVO> selectCompanyQwUserList(String corpId,Long companyId);
+    List<QwUserVO> selectCompanyQwUserListByMy(String corpId,Long companyId,Long userId);
+    List<QwUserVO> selectCompanyQwUserListByDept(String corpId,Long companyId,List<Long> combinedList,String userType);
 
     List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user);
 

+ 75 - 11
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -21,10 +21,8 @@ import com.fs.company.param.CompanyParam;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyProfitService;
 import com.fs.company.service.ICompanyRoleService;
-import com.fs.company.vo.CompanyCrmVO;
-import com.fs.company.vo.CompanyNameVO;
-import com.fs.company.vo.CompanyVO;
-import com.fs.company.vo.DeptDataVO;
+import com.fs.company.vo.*;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
 import com.fs.his.domain.FsStoreOrder;
@@ -110,6 +108,9 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private RedissonClient redissonClient;
 
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+
     @Autowired
     private TransactionTemplate transactionTemplate;
 
@@ -1392,13 +1393,13 @@ public class CompanyServiceImpl implements ICompanyService
 
     /**
      * @Description: 红包充值
-     * @Param:
+     * @Param: type 充值类型 1 充值 2 扣款
      * @Return:
      * @Author xgb
      * @Date 2025/11/3 14:01
      */
     @Override
-    public void redPacketTopUpCompany(Long companyId, BigDecimal money) {
+    public void redPacketTopUpCompany(Long companyId, BigDecimal money,String type) {
 
         String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + companyId;
 
@@ -1415,23 +1416,31 @@ public class CompanyServiceImpl implements ICompanyService
                     originalMoney = new BigDecimal(moneyStr);
                 }else {
                     // 缓存没有值,重启系统恢复redis数据 保证数据正确性
-                    logger.error("红包余额充值获取redis余额缓存异常,异常请求参数companyId:{},money:{}",companyId,money);
+                    logger.error("红包余额充值获取redis余额缓存异常,异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
                     throw new RuntimeException("红包余额充值获取redis余额缓存异常");
                 }
 
+                if(type.equals("1")){// 充值
+
+                }else if(type.equals("2")) {// 扣减
+                    money=money.multiply(new BigDecimal(-1));
+                }else {
+                    logger.error("红包余额充值参数异常,异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
+                    throw new RuntimeException("红包余额充值参数异常");
+                }
                 // 增加余额
                 BigDecimal newMoney = originalMoney.add(money);
                 redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
 
                 // 异步登记余额添加日志
-                asyncRecordBalanceLog(companyId,money,16,newMoney,"红包充值");
+                asyncRecordBalanceLog(companyId,money,16,newMoney,"红包充值(负数为扣款)");
 
             } else {
-                logger.error("获取redis锁失败,异常请求参数companyId:{},money:{}",companyId,money);
+                logger.error("获取redis锁失败,异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
                 throw new RuntimeException("服务繁忙,请稍后重试");
             }
         } catch (Exception e) {
-            logger.error("增加余额失败: 异常请求参数companyId:{},money:{}",companyId,money, e);
+            logger.error("增加余额失败: 异常请求参数companyId:{},money:{},type:{}",companyId,money, type, e);
             throw new RuntimeException("系统异常,请稍后重试");
         }finally {
             // 只有在成功获取锁的情况下才释放锁
@@ -1439,7 +1448,7 @@ public class CompanyServiceImpl implements ICompanyService
                 try {
                     lock1.unlock();
                 } catch (IllegalMonitorStateException e) {
-                    logger.error("尝试释放非当前线程持有的锁: 异常请求参数companyId:{},money:{}",companyId,money);
+                    logger.error("尝试释放非当前线程持有的锁: 异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
                 }
             }
         }
@@ -1516,4 +1525,59 @@ public class CompanyServiceImpl implements ICompanyService
         companyMapper.batchUpdateCompany(list);
     }
 
+    /**
+     * @Description: 红包余额回滚(回滚的是客户没领取的红包),红包记录表中,两天没领取的记录不会再发送
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/7 9:53
+     */
+    @Override
+    public void rollbackRedPacketMoney() {
+        List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseAddRedPacketLogByCompany();
+        for(RedPacketMoneyVO company:redPacketMoneyVOS){
+            logger.info("红包余额回滚开始:{}",company);
+        }
+        Optional.ofNullable(redPacketMoneyVOS).ifPresent(list -> list.forEach(company -> {
+
+            if(company.getCompanyId()==null){
+                logger.error("红包记录表中存在公司id为null的异常数据");
+                return;
+            }
+            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 {
+                        logger.error("缓存公司id:{}的余额不存在,回滚金额{}",company.getCompanyId(),company.getMoney());
+                        return;
+                    }
+                    BigDecimal newMoney = redisMoney.add(company.getMoney());
+                    redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
+
+                    String remark = "执行时间:"+DateUtils.getTime()+",T2天客户未领取红包退回,金额: " + company.getMoney();
+                    asyncRecordBalanceLog(company.getCompanyId(),company.getMoney(),16,newMoney,remark);
+                }
+            } catch (Exception e) {
+                logger.error("退回的红包同步增加到缓存和数据表,参数错误,请求异常,异常信息:{}", e.getMessage(), e);
+            } finally {
+                if (lockAcquired && lock.isHeldByCurrentThread()) {
+                    try {
+                        lock.unlock();
+                    } catch (IllegalMonitorStateException e) {
+                        logger.warn("尝试释放非当前线程持有的锁: companyId:{}", company.getCompanyId());
+                    }
+                }
+            }
+        }));
+    }
+
 }

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

@@ -663,6 +663,17 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public List<QwUserVO> selectCompanyQwUserList(String corpId,Long companyId) {
         return companyUserMapper.selectCompanyQwUserList(corpId,companyId);
     }
+
+    @Override
+    public List<QwUserVO> selectCompanyQwUserListByMy(String corpId, Long companyId, Long userId) {
+        return companyUserMapper.selectCompanyQwUserListByMy(corpId,companyId,userId);
+    }
+
+    @Override
+    public List<QwUserVO> selectCompanyQwUserListByDept(String corpId, Long companyId, List<Long> combinedList, String userType) {
+        return companyUserMapper.selectCompanyQwUserListByDept(corpId,companyId,combinedList,userType);
+    }
+
     @Override
     @DataScope(deptAlias = "u", userAlias = "u")
     public List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user) {

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

@@ -55,6 +55,9 @@ public class CompanyDeductVO implements Serializable {
     @Excel(name = "审核人")
     private String auditUserNickName;
 
+    @Excel(name = "业务类型(0-普通,1-红包扣款)")
+    private Integer businessType;
+
 }
 
 

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

@@ -1511,7 +1511,7 @@ public class AiHookServiceImpl implements AiHookService {
                             }
                             if (value != null) {
                                 str += fieldName + ": " + value.toString() + "\n";
-                            }else {
+                            }else if("交流状态".equals(fieldName) || "学习到的章节".equals(fieldName)){
                                 str += fieldName + ":  \n";
                             }
                         }

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

@@ -251,8 +251,11 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "    </foreach>\n" +
             "    )"+
             "</if> " +
-
-//            "            <if test=\"remarkMobiles != null  and remarkMobiles != ''\"> and ec.remark_mobiles like concat( #{remarkMobiles}, '%')</if>\n" +
+            "<if test='outTagIds !=null'>\n" +
+            "    AND <foreach collection=\"outTagIds\" item=\"item\" open=\"(\" separator=\" AND \" close=\")\">\n" +
+            "        NOT find_in_set(#{item}, REGEXP_REPLACE(ec.tag_ids, '[\\\"\\\\[\\\\]]', ''))\n" +
+            "    </foreach>\n" +
+            "</if>"+
             "<if test=\"remarkMobiles != null and remarkMobiles != ''\">\n" +
             "    AND ec.search_mobile LIKE concat(#{remarkMobiles}, '%')\n" +
             "</if>" +

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

@@ -70,6 +70,11 @@ public class QwExternalContactParam {
     @Excel(name = "标签id")
     private List<String> tagIds;
 
+
+    /** 排除的标签id */
+    @Excel(name = "排除标签id")
+    private List<String> outTagIds;
+
     private String remark;
 
     /** 备注电话号码 */

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

@@ -4337,9 +4337,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Override
     public void deletefollowUserByExternalUserId(String externalUserID, String userID, String corpId) {
 
-        //客户删除销售-找这个销售的sop任务中所有的营期里有没有这个客户,删了(暂时不要删-这种流失的有些一样能发消息)
-//        sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(userID,corpId, externalUserID);
-//        logger.error("客户删除销售-"+"|"+externalUserID+"|"+userID+"|"+corpId);
+        //客户删除销售-找这个销售的sop任务中所有的营期里有没有这个客户-流失客户删除
+        sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(userID,corpId, externalUserID);
+        logger.error("客户删除销售-"+"|"+externalUserID+"|"+userID+"|"+corpId);
 
         QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalUserID, userID, corpId);
         if (qwExternalContact != null) {

+ 22 - 6
fs-service/src/main/java/com/fs/statis/mapper/ConsumptionBalanceMapper.java

@@ -100,12 +100,28 @@ public interface ConsumptionBalanceMapper {
      */
     List<DeaMemberTopTenDTO> deaMemberTopTen(AnalysisPreviewQueryDTO param);
 
-    /**
-     * 课程观看TOP10
-     * @param param 请求参数
-     * @return TOP10
-     */
-    List<CourseStatsDTO> watchCourseTopTen(AnalysisPreviewQueryDTO param);
+    // 按观看人数排序(只查观看数据)
+    List<CourseStatsDTO> watchCourseTopTenByWatch(AnalysisPreviewQueryDTO param);
+
+    // 按完成人数排序(只查观看数据)
+    List<CourseStatsDTO> watchCourseTopTenByComplete(AnalysisPreviewQueryDTO param);
+
+    // 按答题人数排序(需要关联答题表)
+    List<CourseStatsDTO> watchCourseTopTenByAnswer(AnalysisPreviewQueryDTO param);
+
+    // 按正确人数排序(需要关联答题表)
+    List<CourseStatsDTO> watchCourseTopTenByCorrect(AnalysisPreviewQueryDTO param);
+
+    // 批量补充答题数据
+    List<CourseStatsDTO> getAnswerStatsByCourseIds(@Param("courseIds") List<Long> courseIds, @Param("param") AnalysisPreviewQueryDTO param);
+
+
+//    /**
+//     * 课程观看TOP10
+//     * @param param 请求参数
+//     * @return TOP10
+//     */
+//    List<CourseStatsDTO> watchCourseTopTen(AnalysisPreviewQueryDTO param);
 
     /**
      * 奖励金额TOP10

+ 81 - 4
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java

@@ -40,6 +40,8 @@ import java.time.format.DateTimeFormatter;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static com.fs.statis.StatisticsRedisConstant.*;
@@ -983,14 +985,89 @@ public class StatisticsCompanyServiceImpl implements IStatisticsCompanyService {
 
     @Override
     public List<CourseStatsDTO> watchCourseTopTen(AnalysisPreviewQueryDTO param) {
-        List<CourseStatsDTO> courseStatsDTOS = consumptionBalanceMapper.watchCourseTopTen(param);
-        for (CourseStatsDTO courseStatsDTO : courseStatsDTOS) {
+//        List<CourseStatsDTO> courseStatsDTOS = consumptionBalanceMapper.watchCourseTopTen(param);
+//        for (CourseStatsDTO courseStatsDTO : courseStatsDTOS) {
+//            String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(courseStatsDTO.getCourseId());
+//            if(StringUtils.isNotBlank(courseName)){
+//                courseStatsDTO.setCourseName(courseName);
+//            }
+//        }
+//        return courseStatsDTOS;
+        //2025.11.07 优化
+        // 1. 根据统计类型获取基础数据
+        List<CourseStatsDTO> result = getBaseCourseStats(param);
+
+        // 2. 如果查询的是观看或完成数据,需要补充答题数据
+        if (param.getStatisticalType() == 0 || param.getStatisticalType() == 1) {
+            supplementAnswerData(result, param);
+        }
+
+        // 3. 设置课程名称
+        setCourseNames(result);
+
+        return result;
+    }
+
+    /**
+     * 获取基础课程统计数据
+     */
+    private List<CourseStatsDTO> getBaseCourseStats(AnalysisPreviewQueryDTO param) {
+        Integer statisticalType = param.getStatisticalType();
+        switch (statisticalType) {
+            case 0: // 观看人数
+                return consumptionBalanceMapper.watchCourseTopTenByWatch(param);
+            case 1: // 完成人数
+                return consumptionBalanceMapper.watchCourseTopTenByComplete(param);
+            case 2: // 答题人数
+                return consumptionBalanceMapper.watchCourseTopTenByAnswer(param);
+            case 3: // 正确人数
+                return consumptionBalanceMapper.watchCourseTopTenByCorrect(param);
+            default:
+                return consumptionBalanceMapper.watchCourseTopTenByWatch(param);
+        }
+    }
+
+    /**
+     * 补充答题数据(针对观看和完成统计)
+     */
+    private void supplementAnswerData(List<CourseStatsDTO> result, AnalysisPreviewQueryDTO param) {
+        if (result == null || result.isEmpty()) {
+            return;
+        }
+
+        // 提取课程ID列表
+        List<Long> courseIds = result.stream()
+                .map(CourseStatsDTO::getCourseId)
+                .collect(Collectors.toList());
+
+        // 批量查询答题数据
+        List<CourseStatsDTO> answerStats = consumptionBalanceMapper.getAnswerStatsByCourseIds(courseIds, param);
+
+        // 构建课程ID到答题数据的映射
+        Map<Long, CourseStatsDTO> answerMap = answerStats.stream()
+                .collect(Collectors.toMap(CourseStatsDTO::getCourseId, Function.identity()));
+
+        // 合并答题数据到结果中
+        for (CourseStatsDTO dto : result) {
+            CourseStatsDTO answerStat = answerMap.get(dto.getCourseId());
+            if (answerStat != null) {
+                dto.setAnswerUserCount(answerStat.getAnswerUserCount());
+                dto.setCorrectUserCount(answerStat.getCorrectUserCount());
+            }
+            // 如果没有答题数据,保持原来的0值
+        }
+    }
+
+    /**
+     * 设置课程名称
+     */
+    private void setCourseNames(List<CourseStatsDTO> result) {
+        for (CourseStatsDTO courseStatsDTO : result) {
             String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(courseStatsDTO.getCourseId());
-            if(StringUtils.isNotBlank(courseName)){
+            if (StringUtils.isNotBlank(courseName)) {
                 courseStatsDTO.setCourseName(courseName);
             }
         }
-        return courseStatsDTOS;
     }
 
     @Override

+ 79 - 4
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -46,6 +46,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import com.fs.statistics.dto.WatchCourseStatisticsDTO;
 
@@ -897,14 +898,88 @@ public class StatisticsServiceImpl implements IStatisticsService {
 
     @Override
     public List<CourseStatsDTO> watchCourseTopTen(AnalysisPreviewQueryDTO param) {
-        List<CourseStatsDTO> courseStatsDTOS = consumptionBalanceMapper.watchCourseTopTen(param);
-        for (CourseStatsDTO courseStatsDTO : courseStatsDTOS) {
+//        List<CourseStatsDTO> courseStatsDTOS = consumptionBalanceMapper.watchCourseTopTen(param);
+//        for (CourseStatsDTO courseStatsDTO : courseStatsDTOS) {
+//            String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(courseStatsDTO.getCourseId());
+//            if(StringUtils.isNotBlank(courseName)){
+//                courseStatsDTO.setCourseName(courseName);
+//            }
+//        }
+//        return courseStatsDTOS;
+        // 1. 根据统计类型获取基础数据
+        List<CourseStatsDTO> result = getBaseCourseStats(param);
+
+        // 2. 如果查询的是观看或完成数据,需要补充答题数据
+        if (param.getStatisticalType() == 0 || param.getStatisticalType() == 1) {
+            supplementAnswerData(result, param);
+        }
+
+        // 3. 设置课程名称
+        setCourseNames(result);
+
+        return result;
+    }
+
+    /**
+     * 获取基础课程统计数据
+     */
+    private List<CourseStatsDTO> getBaseCourseStats(AnalysisPreviewQueryDTO param) {
+        Integer statisticalType = param.getStatisticalType();
+        switch (statisticalType) {
+            case 0: // 观看人数
+                return consumptionBalanceMapper.watchCourseTopTenByWatch(param);
+            case 1: // 完成人数
+                return consumptionBalanceMapper.watchCourseTopTenByComplete(param);
+            case 2: // 答题人数
+                return consumptionBalanceMapper.watchCourseTopTenByAnswer(param);
+            case 3: // 正确人数
+                return consumptionBalanceMapper.watchCourseTopTenByCorrect(param);
+            default:
+                return consumptionBalanceMapper.watchCourseTopTenByWatch(param);
+        }
+    }
+
+    /**
+     * 补充答题数据(针对观看和完成统计)
+     */
+    private void supplementAnswerData(List<CourseStatsDTO> result, AnalysisPreviewQueryDTO param) {
+        if (result == null || result.isEmpty()) {
+            return;
+        }
+
+        // 提取课程ID列表
+        List<Long> courseIds = result.stream()
+                .map(CourseStatsDTO::getCourseId)
+                .collect(Collectors.toList());
+
+        // 批量查询答题数据
+        List<CourseStatsDTO> answerStats = consumptionBalanceMapper.getAnswerStatsByCourseIds(courseIds, param);
+
+        // 构建课程ID到答题数据的映射
+        Map<Long, CourseStatsDTO> answerMap = answerStats.stream()
+                .collect(Collectors.toMap(CourseStatsDTO::getCourseId, Function.identity()));
+
+        // 合并答题数据到结果中
+        for (CourseStatsDTO dto : result) {
+            CourseStatsDTO answerStat = answerMap.get(dto.getCourseId());
+            if (answerStat != null) {
+                dto.setAnswerUserCount(answerStat.getAnswerUserCount());
+                dto.setCorrectUserCount(answerStat.getCorrectUserCount());
+            }
+            // 如果没有答题数据,保持原来的0值
+        }
+    }
+
+    /**
+     * 设置课程名称
+     */
+    private void setCourseNames(List<CourseStatsDTO> result) {
+        for (CourseStatsDTO courseStatsDTO : result) {
             String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(courseStatsDTO.getCourseId());
-            if(StringUtils.isNotBlank(courseName)){
+            if (StringUtils.isNotBlank(courseName)) {
                 courseStatsDTO.setCourseName(courseName);
             }
         }
-        return courseStatsDTOS;
     }
 
     @Override

+ 6 - 2
fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java

@@ -175,7 +175,9 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
         }
 
 
-        fsTagUpdateQueueMapper.batchInsert(batchData);
+        if(CollectionUtils.isNotEmpty(batchData)){
+            fsTagUpdateQueueMapper.batchInsert(batchData);
+        }
     }
 
 
@@ -257,7 +259,9 @@ public class FsTagUpdateServiceImpl implements FsTagUpdateService {
         }
 
 
-        fsTagUpdateQueueMapper.batchInsert(batchData);
+        if(CollectionUtils.isNotEmpty(batchData)){
+            fsTagUpdateQueueMapper.batchInsert(batchData);
+        }
     }
 
     @Override

+ 10 - 2
fs-service/src/main/java/com/fs/tag/service/impl/FsVideoCourseTagServiceImpl.java

@@ -13,7 +13,9 @@ import com.fs.qw.mapper.QwTagMapper;
 import com.fs.tag.domain.FsVideoCourseTag;
 import com.fs.tag.mapper.FsVideoCourseTagMapper;
 import com.fs.tag.service.IFsVideoCourseTagService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 
 /**
@@ -22,6 +24,7 @@ import org.springframework.stereotype.Service;
  * @author fs
  * @date 2025-11-05
  */
+@Slf4j
 @Service
 public class FsVideoCourseTagServiceImpl extends ServiceImpl<FsVideoCourseTagMapper, FsVideoCourseTag> implements IFsVideoCourseTagService {
 
@@ -94,8 +97,13 @@ public class FsVideoCourseTagServiceImpl extends ServiceImpl<FsVideoCourseTagMap
     @Override
     public int insertFsVideoCourseTag(FsVideoCourseTag fsVideoCourseTag)
     {
-        fsVideoCourseTag.setCreateTime(DateUtils.getNowDate());
-        return baseMapper.insertFsVideoCourseTag(fsVideoCourseTag);
+        try{
+            fsVideoCourseTag.setCreateTime(DateUtils.getNowDate());
+            return baseMapper.insertFsVideoCourseTag(fsVideoCourseTag);
+        }catch (DuplicateKeyException e){
+            log.info("同一主体的小节添加标签不允许重复!");
+            throw new RuntimeException("同一主体的小节添加标签不允许重复!");
+        }
     }
 
     /**

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

@@ -91,7 +91,7 @@ cloud_host:
 headerImg:
   imgUrl: https://hat-1323137866.cos.ap-chongqing.myqcloud.com/fs/20250928/hatlogo.png
 ipad:
-  ipadUrl: http://ipad.****.cn
+  ipadUrl: http://hatipad.ylrzcloud.com
   aiApi: http://62:3000/api
   voiceApi:
   commonApi:

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

@@ -9,7 +9,7 @@ spring:
         # 端口,默认为6379
         port: 6379
         # 数据库索引
-        database: 2
+        database: 1
         # 密码
         password: Ylrztek250218!3@.
         # 连接超时时间

+ 11 - 8
fs-service/src/main/resources/mapper/company/CompanyDeductMapper.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.CompanyDeductMapper">
-    
+
     <resultMap type="CompanyDeduct" id="CompanyDeductResult">
         <result property="deductId"    column="deduct_id"    />
         <result property="deductNo"    column="deduct_no"    />
@@ -16,15 +16,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="selectCompanyDeductVo">
-        select deduct_id, deduct_no, company_id, money, balance, create_time, create_user_id, is_audit, audit_user_id, audit_time,remark from company_deduct
+        select deduct_id, deduct_no, company_id, money, balance, create_time, create_user_id, is_audit, audit_user_id, audit_time,remark, business_type from company_deduct
     </sql>
 
     <select id="selectCompanyDeductList" parameterType="CompanyDeduct" resultMap="CompanyDeductResult">
         <include refid="selectCompanyDeductVo"/>
-        <where>  
+        <where>
             <if test="deductNo != null  and deductNo != ''"> and deduct_no = #{deductNo}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="money != null "> and money = #{money}</if>
@@ -35,12 +36,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="auditTime != null "> and audit_time = #{auditTime}</if>
         </where>
     </select>
-    
+
     <select id="selectCompanyDeductById" parameterType="Long" resultMap="CompanyDeductResult">
         <include refid="selectCompanyDeductVo"/>
         where deduct_id = #{deductId}
     </select>
-        
+
     <insert id="insertCompanyDeduct" parameterType="CompanyDeduct" useGeneratedKeys="true" keyProperty="deductId">
         insert into company_deduct
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -54,6 +55,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="deductNo != null">#{deductNo},</if>
@@ -66,6 +68,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>
 
@@ -91,10 +94,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteCompanyDeductByIds" parameterType="String">
-        delete from company_deduct where deduct_id in 
+        delete from company_deduct where deduct_id in
         <foreach item="deductId" collection="array" open="(" separator="," close=")">
             #{deductId}
         </foreach>
     </delete>
-    
-</mapper>
+
+</mapper>

+ 11 - 5
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -60,7 +60,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM
         fs_course_traffic_log
         <where>
-            DATE(create_time) = CURDATE()
+            <!-- DATE(create_time) = CURDATE() -->
+            create_time >= CURDATE()
+            AND create_time &lt; CURDATE() + INTERVAL 1 DAY
             <if test="companyId != null">
                 AND company_id = ${companyId}
             </if>
@@ -85,8 +87,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM
         fs_course_traffic_log
         <where>
-            YEAR(create_time) = YEAR(CURDATE())
-            AND MONTH(create_time) = MONTH(CURDATE())
+            <!-- YEAR(create_time) = YEAR(CURDATE()) -->
+            <!-- AND MONTH(create_time) = MONTH(CURDATE()) -->
+            create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
+            AND create_time &lt; DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH, '%Y-%m-01')
             <if test="companyId != null">
                 AND company_id = ${companyId}
             </if>
@@ -114,8 +118,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         FROM
             fs_course_traffic_log
         WHERE
-            YEAR(create_time) = YEAR(CURDATE())
-          AND MONTH(create_time) = MONTH(CURDATE())
+        <!-- YEAR(create_time) = YEAR(CURDATE()) -->
+        <!-- AND MONTH(create_time) = MONTH(CURDATE()) -->
+        create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
+        AND create_time &lt; DATE_FORMAT(CURDATE() + INTERVAL 1 MONTH, '%Y-%m-01')
     </select>
 
     <insert id="insertFsCourseTrafficLog" parameterType="FsCourseTrafficLog" useGeneratedKeys="true" keyProperty="logId">

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

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

+ 174 - 42
fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml

@@ -168,14 +168,16 @@
     </select>
     <select id="watchEndPlayTrend" resultType="com.fs.statis.dto.WatchEndPlayTrendDTO">
         SELECT
---             今日/昨日 小时
-            <if test="type == 0 or type == 1">
+        <choose>
+            <!-- 按小时分组 -->
+            <when test="type == 0 or type == 1">
                 DATE_FORMAT(create_time, '%H') AS start_date,
-            </if>
---                 本周/本月/上月 天
-            <if test="type == 2 or type == 3 or type == 4">
+            </when>
+            <!-- 按天分组 -->
+            <when test="type == 2 or type == 3 or type == 4">
                 DATE_FORMAT(create_time, '%Y-%m-%d') AS start_date,
-            </if>
+            </when>
+        </choose>
         COUNT(DISTINCT user_id) AS watch_user_count,
         COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS completed_user_count
         FROM
@@ -192,7 +194,10 @@
             </if>
         </where>
         GROUP BY
-        start_date
+        <choose>
+            <when test="type == 0 or type == 1">DATE_FORMAT(create_time, '%H')</when>
+            <when test="type == 2 or type == 3 or type == 4">DATE_FORMAT(create_time, '%Y-%m-%d')</when>
+        </choose>
         ORDER BY
         start_date
     </select>
@@ -224,50 +229,177 @@
         GROUP BY company_id
         limit 10
     </select>
-    <select id="watchCourseTopTen" resultType="com.fs.statis.dto.CourseStatsDTO">
+<!--    <select id="watchCourseTopTen" resultType="com.fs.statis.dto.CourseStatsDTO">-->
+<!--        SELECT-->
+<!--        w.course_id AS course_id,-->
+<!--        COUNT(DISTINCT w.user_id) AS watch_user_count,-->
+<!--        COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END) AS completed_user_count,-->
+<!--        COUNT(DISTINCT a.user_id) AS answer_user_count,-->
+<!--        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count-->
+<!--        FROM-->
+<!--        fs_course_watch_log w-->
+<!--        LEFT JOIN-->
+<!--        fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id-->
+<!--        <where>-->
+<!--            <if test="startTime != null">-->
+<!--                w.create_time <![CDATA[>=]]> #{startTime}-->
+<!--            </if>-->
+<!--            <if test="endTime != null">-->
+<!--                AND w.create_time <![CDATA[<]]> #{endTime}-->
+<!--            </if>-->
+<!--            <if test="userType != null">-->
+<!--                AND send_type = ${userType}-->
+<!--            </if>-->
+<!--            <if test="companyId != null">-->
+<!--                AND w.company_id = ${companyId}-->
+<!--            </if>-->
+<!--        </where>-->
+<!--        GROUP BY-->
+<!--        w.course_id-->
+<!--        ORDER BY-->
+<!--            &#45;&#45; 观看人数-->
+<!--            <if test="statisticalType == 0">-->
+<!--                COUNT(DISTINCT w.user_id)-->
+<!--            </if>-->
+<!--            <if test="statisticalType == 1">-->
+<!--                COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END)-->
+<!--            </if>-->
+<!--            <if test="statisticalType == 2">-->
+<!--                COUNT(DISTINCT a.user_id)-->
+<!--            </if>-->
+<!--            <if test="statisticalType == 3">-->
+<!--                COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END)-->
+<!--            </if>-->
+<!--        ${sort}-->
+<!--        LIMIT 10-->
+<!--    </select>-->
+
+    <!-- 1. 按观看人数排序 -->
+    <select id="watchCourseTopTenByWatch" resultType="com.fs.statis.dto.CourseStatsDTO">
+        SELECT
+        course_id AS courseId,
+        COUNT(DISTINCT user_id) AS watch_user_count,
+        COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS completed_user_count,
+        0 AS answer_user_count,
+        0 AS correct_user_count
+        FROM fs_course_watch_log
+        <where>
+            <if test="startTime != null">create_time <![CDATA[>=]]> #{startTime}</if>
+            <if test="endTime != null">AND create_time <![CDATA[<]]> #{endTime}</if>
+            <if test="userType != null">AND send_type = #{userType}</if>
+            <if test="companyId != null">AND company_id = #{companyId}</if>
+        </where>
+        GROUP BY course_id
+        ORDER BY watch_user_count
+        <choose>
+            <when test="sort != null and sort == 'DESC'">DESC</when>
+            <otherwise>ASC</otherwise>
+        </choose>
+        LIMIT 10
+    </select>
+
+    <!-- 2. 按完成人数排序 -->
+    <select id="watchCourseTopTenByComplete" resultType="com.fs.statis.dto.CourseStatsDTO">
         SELECT
-        w.course_id AS course_id,
+        course_id AS courseId,
+        COUNT(DISTINCT user_id) AS watch_user_count,
+        COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS completed_user_count,
+        0 AS answer_user_count,
+        0 AS correct_user_count
+        FROM fs_course_watch_log
+        <where>
+            <if test="startTime != null">create_time <![CDATA[>=]]> #{startTime}</if>
+            <if test="endTime != null">AND create_time <![CDATA[<]]> #{endTime}</if>
+            <if test="userType != null">AND send_type = #{userType}</if>
+            <if test="companyId != null">AND company_id = #{companyId}</if>
+        </where>
+        GROUP BY course_id
+        ORDER BY completed_user_count
+        <choose>
+            <when test="sort != null and sort == 'DESC'">DESC</when>
+            <otherwise>ASC</otherwise>
+        </choose>
+        LIMIT 10
+    </select>
+
+    <!-- 3. 按答题人数排序 -->
+    <select id="watchCourseTopTenByAnswer" resultType="com.fs.statis.dto.CourseStatsDTO">
+        SELECT
+        w.course_id AS courseId,
         COUNT(DISTINCT w.user_id) AS watch_user_count,
         COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END) AS completed_user_count,
         COUNT(DISTINCT a.user_id) AS answer_user_count,
         COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
-        FROM
-        fs_course_watch_log w
-        LEFT JOIN
-        fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
+        FROM fs_course_watch_log w
+        LEFT JOIN fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
         <where>
-            <if test="startTime != null">
-                w.create_time <![CDATA[>=]]> #{startTime}
-            </if>
-            <if test="endTime != null">
-                AND w.create_time <![CDATA[<]]> #{endTime}
-            </if>
-            <if test="userType != null">
-                AND send_type = ${userType}
-            </if>
-            <if test="companyId != null">
-                AND w.company_id = ${companyId}
-            </if>
+            <if test="startTime != null">w.create_time <![CDATA[>=]]> #{startTime}</if>
+            <if test="endTime != null">AND w.create_time <![CDATA[<]]> #{endTime}</if>
+            <if test="userType != null">AND w.send_type = #{userType}</if>
+            <if test="companyId != null">AND w.company_id = #{companyId}</if>
+<!--            <if test="startTime != null">AND a.create_time <![CDATA[>=]]> #{startTime}</if> -->
+<!--            <if test="endTime != null">AND a.create_time <![CDATA[<]]> #{endTime}</if> -->
         </where>
-        GROUP BY
-        w.course_id
-        ORDER BY
-            -- 观看人数
-            <if test="statisticalType == 0">
-                COUNT(DISTINCT w.user_id)
-            </if>
-            <if test="statisticalType == 1">
-                COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END)
-            </if>
-            <if test="statisticalType == 2">
-                COUNT(DISTINCT a.user_id)
-            </if>
-            <if test="statisticalType == 3">
-                COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END)
-            </if>
-        ${sort}
+        GROUP BY w.course_id
+        ORDER BY answer_user_count
+        <choose>
+            <when test="sort != null and sort == 'DESC'">DESC</when>
+            <otherwise>ASC</otherwise>
+        </choose>
+        LIMIT 10
+    </select>
+
+    <!-- 4. 按正确人数排序 -->
+    <select id="watchCourseTopTenByCorrect" resultType="com.fs.statis.dto.CourseStatsDTO">
+        SELECT
+        w.course_id AS courseId,
+        COUNT(DISTINCT w.user_id) AS watch_user_count,
+        COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END) AS completed_user_count,
+        COUNT(DISTINCT a.user_id) AS answer_user_count,
+        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
+        FROM fs_course_watch_log w
+        LEFT JOIN fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
+        <where>
+            <if test="startTime != null">w.create_time <![CDATA[>=]]> #{startTime}</if>
+            <if test="endTime != null">AND w.create_time <![CDATA[<]]> #{endTime}</if>
+            <if test="userType != null">AND w.send_type = #{userType}</if>
+            <if test="companyId != null">AND w.company_id = #{companyId}</if>
+            <if test="startTime != null">AND a.create_time <![CDATA[>=]]> #{startTime}</if>
+            <if test="endTime != null">AND a.create_time <![CDATA[<]]> #{endTime}</if>
+        </where>
+        GROUP BY w.course_id
+        ORDER BY correct_user_count
+        <choose>
+            <when test="sort != null and sort == 'DESC'">DESC</when>
+            <otherwise>ASC</otherwise>
+        </choose>
         LIMIT 10
     </select>
+
+    <!-- 5. 批量补充答题数据 -->
+    <select id="getAnswerStatsByCourseIds" resultType="com.fs.statis.dto.CourseStatsDTO">
+        SELECT
+        w.course_id AS courseId,
+        COUNT(DISTINCT a.user_id) AS answer_user_count,
+        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
+        FROM fs_course_answer_logs a
+        INNER JOIN fs_course_watch_log w ON a.video_id = w.video_id AND a.user_id = w.user_id
+        <where>
+            w.course_id IN
+            <foreach collection="courseIds" item="courseId" open="(" close=")" separator=",">
+                #{courseId}
+            </foreach>
+            <if test="param.startTime != null">AND w.create_time <![CDATA[>=]]> #{param.startTime}</if>
+            <if test="param.endTime != null">AND w.create_time <![CDATA[<]]> #{param.endTime}</if>
+            <if test="param.userType != null">AND w.send_type = #{param.userType}</if>
+            <if test="param.companyId != null">AND w.company_id = #{param.companyId}</if>
+            <if test="param.startTime != null">AND a.create_time <![CDATA[>=]]> #{param.startTime}</if>
+            <if test="param.endTime != null">AND a.create_time <![CDATA[<]]> #{param.endTime}</if>
+        </where>
+        GROUP BY w.course_id
+    </select>
+
+
     <select id="rewardMoneyTopTen" resultType="com.fs.statis.dto.RewardMoneyTopTenDTO">
         SELECT
             -- 按公司