فهرست منبع

Merge remote-tracking branch 'refs/remotes/origin/master' into cqtyt_master

xgb 2 هفته پیش
والد
کامیت
84f1798674
100فایلهای تغییر یافته به همراه3062 افزوده شده و 545 حذف شده
  1. 0 2
      README.md
  2. 33 8
      fs-admin/src/main/java/com/fs/api/controller/StatisticManageController.java
  3. 48 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyConfigController.java
  4. 14 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyDeductController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyRechargeController.java
  6. 572 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java
  7. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  8. 5 1
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  9. 16 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  10. 4 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  11. 21 0
      fs-admin/src/main/java/com/fs/course/params/FsUserCourseConfigParam.java
  12. 93 2
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  13. 18 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java
  14. 20 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  15. 23 0
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  16. 49 9
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  17. 31 4
      fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java
  18. 1 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  19. 10 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java
  20. 138 0
      fs-admin/src/main/java/com/fs/qw/controller/QwDeptController.java
  21. 309 5
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  22. 51 25
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  23. 9 0
      fs-admin/src/main/java/com/fs/task/SgTestController.java
  24. 2 2
      fs-admin/src/main/resources/application.yml
  25. 1 0
      fs-admin/src/main/resources/logback.xml
  26. 6 5
      fs-common/pom.xml
  27. 1 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  28. 37 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  29. 43 0
      fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java
  30. 29 11
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  31. 74 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  32. 1 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  33. 4 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagController.java
  34. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  35. 104 0
      fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java
  36. 3 2
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  37. 2 2
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  38. 29 25
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  39. 1 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  40. 9 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  41. 99 3
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  42. 5 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingServiceImpl.java
  43. 1 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  44. 1 1
      fs-qw-voice/pom.xml
  45. 15 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  46. 13 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  47. 13 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  48. 14 0
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  49. 4 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  50. 31 18
      fs-service/src/main/java/com/fs/company/domain/CompanyDeduct.java
  51. 14 4
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptTreeSelect.java
  52. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java
  53. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeductMapper.java
  54. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  55. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  56. 19 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  57. 73 3
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  58. 2 1
      fs-service/src/main/java/com/fs/company/service/ICompanyRechargeService.java
  59. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  60. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  61. 25 2
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  62. 9 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyRechargeServiceImpl.java
  63. 252 31
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  64. 11 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  65. 95 63
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  66. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyDeductVO.java
  67. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java
  68. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  69. 8 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  70. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourse.java
  71. 0 27
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  72. 17 17
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  73. 11 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  74. 8 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  75. 3 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  76. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  77. 7 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  78. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  79. 4 3
      fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java
  80. 19 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  81. 2 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  82. 8 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  83. 183 182
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  84. 18 1
      fs-service/src/main/java/com/fs/course/vo/FsCourseTrafficLogListVO.java
  85. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java
  86. 2 2
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java
  87. 0 39
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java
  88. 12 0
      fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java
  89. 1 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  90. 1 1
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  91. 23 0
      fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java
  92. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralOrderMapper.java
  93. 4 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralOrderService.java
  94. 15 0
      fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java
  95. 9 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  96. 52 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  97. 45 0
      fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java
  98. 44 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  99. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  100. 1 1
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java

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

+ 48 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyConfigController.java

@@ -0,0 +1,48 @@
+package com.fs.company.controller;
+
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.service.ICompanyConfigService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.service.ISysConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 系统配置
+ *
+
+ */
+@RestController
+@RequestMapping("/company/companyConfig")
+public class CompanyConfigController extends BaseController
+{
+
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+
+
+    /**
+     * 根据参数键名查询总后台参数值
+     * @param configKey
+     * @return
+     */
+    @GetMapping(value = "/getConfigByKey/{configKey}")
+    public AjaxResult getConfigByKey(@PathVariable String configKey)
+    {
+        SysConfig config=configService.selectConfigByConfigKey(configKey);
+        return AjaxResult.success(config);
+    }
+
+
+
+}

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

+ 572 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java

@@ -0,0 +1,572 @@
+package com.fs.company.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONUtil;
+import com.baidu.dev2.thirdparty.jackson.databind.ObjectMapper;
+import com.fs.common.annotation.Log;
+import com.fs.common.constant.UserConstants;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.PatternUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.*;
+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.vo.BatchUserRolesVO;
+import com.fs.company.vo.CompanyUserImportVO;
+import com.fs.company.vo.CompanyUserQwListVO;
+import com.fs.company.vo.CompanyUserVO;
+import com.fs.course.config.CourseConfig;
+import com.fs.framework.web.service.TokenService;
+import com.fs.his.utils.qrcode.QRCodeUtils;
+import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.FsStoreProductExportVO;
+import com.fs.im.config.IMConfig;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.service.IQwCompanyService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.CompanyUserQwVO;
+import com.fs.qw.vo.QwUserVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.utils.DomainUtil;
+import com.fs.utils.QwStatusEnum;
+import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.Assert;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+
+/**
+ * 用户信息
+ */
+@RestController
+@RequestMapping("/company/CompanyUserAll")
+public class CompanyUserAllController extends BaseController {
+
+    @Autowired
+    private ICompanyRoleService roleService;
+
+    @Autowired
+    private ICompanyPostService postService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private ICompanyUserDelayTimeService companyUserDelayTimeService;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private OpenIMService openIMService;
+
+    @Autowired
+    IQwCompanyService iQwCompanyService;
+
+    @Autowired
+    private IQwUserService qwUserService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
+
+
+    @GetMapping("/getList")
+    public TableDataInfo getList(CompanyUser user)
+    {
+        startPage();
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(user);
+        return getDataTable(list);
+    }
+    @GetMapping("/qwList")
+    public TableDataInfo qwList(CompanyUserQwParam user) {
+        List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
+        if (!list.isEmpty()){
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+            Long sendDelayTime = config.getSendDelayTime();
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (CompanyUserQwListVO companyUserQwListVO : list) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+                    if (ObjectUtil.isEmpty(companyUserDelayTime)) {
+                        companyUserQwListVO.setSendDelayTime(sendDelayTime);
+                    } else {
+                        companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
+                    }
+                    //是否绑定
+                    if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
+                        if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                            Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
+                                    .map(Long::parseLong)
+                                    .toArray(Long[]::new);
+                            List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
+                            companyUserQwListVO.setQwUsers(qwUserVOS);
+                        }
+                    }
+                });
+                futures.add(future);
+            }
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+        }
+        return getDataTable(list);
+    }
+
+    @Log(title = "用户管理导出", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('company:user:export')")
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUser user)
+    {
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(user);
+        ExcelUtil<CompanyUser> util = new ExcelUtil<CompanyUser>(CompanyUser.class);
+        return util.exportExcel(list, "用户数据");
+    }
+
+
+    @Log(title = "销售信息导入", businessType = BusinessType.IMPORT,isStoreLog = true,logParam = {"销售","信息导入"})
+    @PreAuthorize("@ss.hasPermi('company:user:import')")
+    @PostMapping("/importCompanyUser")
+    public AjaxResult importData(@RequestParam("file") MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<CompanyUserImportVO> util = new ExcelUtil<>(CompanyUserImportVO.class);
+        List<CompanyUserImportVO> list = util.importExcel(file.getInputStream());
+        String message = companyUserService.importCompanyUser(list, updateSupport);
+        return AjaxResult.success(message);
+    }
+
+
+    @GetMapping("/importTemplate")
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<CompanyUserImportVO> util = new ExcelUtil<>(CompanyUserImportVO.class);
+        return util.importTemplateExcel("销售数据");
+    }
+
+
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:query')")
+    @GetMapping(value = { "/", "/{userId}" })
+    public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+        CompanyRole companyRoleMap=new CompanyRole();
+
+        CompanyUser userById = companyUserService.selectCompanyUserById(userId);
+        companyRoleMap.setCompanyId(userById.getCompanyId());
+        List<CompanyRole> roles = roleService.selectCompanyRoleList(companyRoleMap);
+
+        ajax.put("roles", CompanyUser.isAdmin(userById.getUserType()) ? roles : roles.stream().filter(r -> r.getRoleKey()!="admin").collect(Collectors.toList()));
+
+        CompanyPost postMap=new CompanyPost();
+        postMap.setCompanyId(userById.getCompanyId());
+        ajax.put("posts", postService.selectCompanyPostList(postMap));
+        if (StringUtils.isNotNull(userId))
+        {
+            CompanyUser companyUser=companyUserService.selectCompanyUserById(userId);
+            ajax.put(AjaxResult.DATA_TAG, companyUser);
+            ajax.put("postIds", postService.selectPostListByUserId(userId));
+            ajax.put("roleIds", roleService.selectRoleListByUserId(userId));
+
+
+        }
+        return ajax;
+    }
+
+    /**
+     * 根据用户编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:query')")
+    @GetMapping("/addInfo/{companyId}")
+    public AjaxResult addInfo(@PathVariable(value = "companyId", required = true) Long companyId)
+    {
+        AjaxResult ajax = AjaxResult.success();
+
+        CompanyRole companyRoleMap=new CompanyRole();
+        companyRoleMap.setCompanyId(companyId);
+        List<CompanyRole> roles = roleService.selectCompanyRoleList(companyRoleMap);
+
+        ajax.put("roles", CompanyUser.isAdmin("01") ? roles : roles.stream().filter(r -> r.getRoleKey()!="admin").collect(Collectors.toList()));
+
+        CompanyPost postMap=new CompanyPost();
+        postMap.setCompanyId(companyId);
+        ajax.put("posts", postService.selectCompanyPostList(postMap));
+
+        return ajax;
+    }
+
+    /**
+     * 新增用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:add')")
+    @Log(title = "用户管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody CompanyUser user)
+    {
+        if (!PatternUtils.checkPassword(user.getPassword())) {
+            return AjaxResult.error("密码格式不正确,需包含字母、数字和特殊字符,长度为 8-20 位");
+        }
+
+        //判断用户数量是否已达到上线
+
+        Integer count=companyUserService.selectCompanyUserCountByCompanyId(user.getCompanyId());
+        Company company=companyService.selectCompanyById(user.getCompanyId());
+
+        if(count>company.getLimitUserCount()){
+            return AjaxResult.error("用户数量已达到上限");
+        }
+        user.setCompanyId(user.getCompanyId());
+        if (UserConstants.NOT_UNIQUE.equals(String.valueOf(companyUserService.checkUserName(user.getUserName()))))
+        {
+            return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
+        }
+        if (!StringUtil.strIsNullOrEmpty(user.getDomain())){
+            user.setDomain(user.getDomain().replaceAll("^[\\s\\u2005]+", ""));
+        }
+
+        user.setCreateBy(SecurityUtils.getUsername());
+        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
+        user.setCreateTime(new Date());
+        user.setUserType("01");//一般用户
+        user.setIsAudit(1);
+        return toAjax(companyUserService.insertUser(user));
+    }
+
+    /**
+     * 生成创建销售的二维码
+     */
+
+    @PreAuthorize("@ss.hasPermi('company:user:addCodeUrl')")
+    @Log(title = "生成创建销售的二维码", businessType = BusinessType.INSERT)
+    @PostMapping("/addCodeUrl")
+    public R addCodeUrl( @RequestBody CompanyUserCodeParam user) throws Exception {
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(user.getCorpId());
+        if (qwCompany == null || qwCompany.getCorpId() == null){
+            return R.error("企业信息不存在");
+        }
+
+        //部门
+        Long deptId = user.getDeptId();
+
+        //角色组
+        Long[] roleIds = user.getRoleIds();
+
+        //区域
+        String addressId = user.getAddressId();
+
+
+        Map<String, Object> stateMap = new HashMap<>();
+        stateMap.put("companyId", user.getCompanyId());
+        stateMap.put("deptId", deptId);
+        stateMap.put("roleIds", roleIds); // 数组可以直接存放
+        stateMap.put("addressId", addressId);
+
+        // 使用JSON库将Map转换为字符串
+        ObjectMapper objectMapper = new ObjectMapper();
+        String status = objectMapper.writeValueAsString(stateMap);
+
+        // 如果要将status作为URL参数传递,记得进行URL编码!
+        String encodedStatus = URLEncoder.encode(status, StandardCharsets.UTF_8.toString());
+
+        String url="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+user.getCorpId()+"&redirect_uri=" +
+                "http://"+qwCompany.getRealmNameUrl()+"/loginqw/pages/companyLogin/index?corpId="+user.getCorpId() +
+                "&response_type=code&scope=snsapi_base&state="+encodedStatus+"&agentid="+qwCompany.getServerAgentId()+"#wechat_redirect";
+
+        R andUpload = QRCodeUtils.createAndUpload(url);
+        return  R.ok().put("data",andUpload);
+    }
+    /**
+     * 修改用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody CompanyUser user)
+    {
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        user.setUpdateBy(SecurityUtils.getUsername());
+
+        if (!StringUtil.strIsNullOrEmpty(user.getDomain())){
+            user.setDomain(user.getDomain().replaceAll("^[\\s\\u2005]+", ""));
+        }
+
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(user.getUserId());
+
+        if (!companyUser.getUserName().equals(user.getUserName())){
+            if (UserConstants.NOT_UNIQUE.equals(String.valueOf(companyUserService.checkUserName(user.getUserName()))))
+            {
+                return AjaxResult.error("修改员工账号'" + user.getUserName() + "'失败,员工账号已存在");
+            }
+        }
+
+
+        logger.info("用户管理-修改用户:"+user.getUserName()+"/登陆人:"+loginUser.getUser().getUserId()+"/名字:"+loginUser.getUser().getUserName()+"/修改的角色:"+ Arrays.toString(user.getRoleIds()));
+        return toAjax(companyUserService.updateUser(user));
+    }
+
+    /**
+     * 删除用户
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:remove')")
+    @Log(title = "用户管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{userIds}")
+    public AjaxResult remove(@PathVariable Long[] userIds)
+    {
+        for (Long userId : userIds) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserById(userId);
+            CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUser.getCompanyId(),userId);
+            if (ObjectUtil.isNotEmpty(companyUserDelayTime)){
+                companyUserDelayTimeService.deleteCompanyUserDelayTimeById(companyUserDelayTime.getId());
+            }
+        }
+        return toAjax(companyUserService.deleteCompanyUserByIds(userIds));
+    }
+
+    /**
+     * 重置密码
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/resetPwd")
+    public AjaxResult resetPwd(@RequestBody CompanyUser user)
+    {
+        if (!PatternUtils.checkPassword(user.getPassword())) {
+            return AjaxResult.error("密码格式不正确,需包含字母、数字和特殊字符,长度为 8-20 位");
+        }
+
+        String newPassword = SecurityUtils.encryptPassword(user.getPassword());
+        int i = companyUserService.resetUserPwdByUserId(user.getUserId(), newPassword);
+
+        if (i > 0) {
+            // 更新缓存用户密码
+            redisCache.deleteObject("newCompanyUser:" + user.getCompanyId() + ":" + user.getUserName());
+        }
+
+        return toAjax(i);
+    }
+
+    /**
+     * 状态修改
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody CompanyUser user)
+    {
+        //管理员的状态不能修改
+        CompanyUser companyUser=companyUserService.selectCompanyUserById(user.getUserId());
+        if(companyUser.isAdmin()){
+            return AjaxResult.error("不能修改管理员状态");
+        }
+        user.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(companyUserService.updateCompanyUser(user));
+    }
+
+
+
+
+    /**
+     * 获取区域
+     */
+    @GetMapping("/getCitysAreaList")
+    public R getCitysAreaList(){
+        return R.ok().put("data",companyUserService.getCitysAreaList());
+    }
+
+    /**
+     * 批量修改 销售的所属区域(临时的)
+     */
+    @PostMapping("/updateCompanyUserAreaList")
+    public R updateCompanyUserAreaList(@RequestBody CompanyUserAreaParam param)
+    {
+        return companyUserService.updateCompanyUserAreaList(param);
+    }
+
+
+    @GetMapping("/generateSubDomain")
+    public R generateSubDomain(CompanyUser user){
+        //获取后台配置
+        String json= configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        // 生成二级域名
+        String subDomain = "http://" + DomainUtil.generateSubDomain(config.getCourseDomainName(), 6, String.valueOf(SecurityUtils.getLoginUser().getUser().getUserId()));
+        return  R.ok().put("data",subDomain);
+    }
+
+    @Log(title = "设置是否需要单独注册会员", businessType = BusinessType.UPDATE)
+    @PutMapping("/setRegister")
+    public AjaxResult setIsRegisterMember(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
+        Boolean r = companyUserService.setIsRegisterMember(status, userIds);
+        if (r) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("操作失败");
+        }
+    }
+
+    @Log(title = "是否允许所有方式注册会员", businessType = BusinessType.UPDATE)
+    @PutMapping("/allowedAllRegister")
+    public AjaxResult isAllowedAllRegister(@RequestParam Boolean status, @RequestBody List<Long> userIds) {
+        Boolean r = companyUserService.isAllowedAllRegister(status, userIds);
+        if (r) {
+            return AjaxResult.success();
+        } else {
+            return AjaxResult.error("操作失败");
+        }
+    }
+    @PostMapping("/common/uploadOSS")
+    public R uploadOSS(@RequestParam("file") MultipartFile file,
+                       @RequestParam("userId") String userId) throws Exception {
+        String url = companyUserService.uploadQrCode(file, userId);
+        return R.ok().put("url", url);
+    }
+
+
+    /**
+     * 销售解除绑定医生
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUser:unBindDoctorId')")
+    @Log(title = "销售解除绑定医生", businessType = BusinessType.UPDATE)
+    @GetMapping("/unBindDoctorId/{userId}")
+    public R bindDoctorId(@PathVariable("userId") Long userId){
+        return companyUserService.unBindDoctor(userId);
+    }
+
+    /**
+     * 销售绑定医生
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUser:bindDoctorId')")
+    @Log(title = "销售绑定医生", businessType = BusinessType.UPDATE)
+    @PostMapping("/bindDoctorId")
+    public R unBindDoctorId(@RequestBody CompanyUser companyUser){
+        return companyUserService.bindDoctor(companyUser);
+    }
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "批量修改角色", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateBatchUserRoles")
+    public R updateBatchUserRoles(@RequestBody BatchUserRolesVO batchUserRolesVO){
+        Assert.notEmpty(batchUserRolesVO.getRoleIds(), "角色不能为空");
+        Assert.notEmpty(batchUserRolesVO.getUserIds(), "用户不能为空");
+        return companyUserService.updateBatchUserRoles(batchUserRolesVO);
+    }
+
+
+    @ApiOperation("校验客服是否注册新的im")
+    @PostMapping("/accountCheck")
+    public R accountCheck(@RequestBody Map<String, String> userIdMap){
+        //获取管理员token
+        String userId = userIdMap.get("userId");
+        String adminToken = openIMService.getAdminToken();
+        JSONObject requestBody = new JSONObject();
+        // 解析响应
+        if (StringUtils.isNotEmpty(adminToken)) {
+            //查询用户是否注册
+            ArrayList<String> userIds = new ArrayList<>();
+            requestBody = new JSONObject();
+            userIds.add(userId);
+            requestBody.put("checkUserIDs", userIds);
+            String body = HttpRequest.post(IMConfig.URL+"/user/account_check")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+            JSONObject jsonObject = new JSONObject(body);
+            JSONArray results = jsonObject.getJSONObject("data").getJSONArray("results");
+            if (results != null && results.length() > 0) {
+                JSONObject resultObj = results.getJSONObject(0);
+                int accountStatus = resultObj.getInt("accountStatus");
+                //未注册自动注册
+                if (accountStatus==0){
+                    String s = userId.replaceFirst("^C", "");
+                    CompanyUser companyUser = companyUserService.selectCompanyUserById(Long.parseLong(s));
+                    if (null==companyUser){
+                        return R.error("用户不存在");
+                    }
+                    ArrayList<Object> users = new ArrayList<>();
+                    HashMap<String, String> map = new HashMap<>();
+                    map.put("userID",userId);
+                    map.put("nickname",companyUser.getNickName());
+                    map.put("faceURL",companyUser.getAvatar());
+                    users.add(map);
+                    requestBody = new JSONObject();
+                    //userIds.add(userId);
+                    requestBody.put("users", users);
+                    String registerBody = HttpRequest.post(IMConfig.URL+"/user/user_register")
+                            .header("operationID", String.valueOf(System.currentTimeMillis()))
+                            .header("token", adminToken).body(requestBody.toString()).execute().body();
+                }
+            } else {
+                return R.error("返回结果为空");
+            }
+           /* HashMap<String, String> tokenMap = new HashMap<>();
+            tokenMap.put("platformID","1");
+            tokenMap.put("userID",userId);*/
+            requestBody = new JSONObject();
+            requestBody.put("platformID",5);
+            requestBody.put("userID",userId);
+            String body1 = HttpRequest.post(IMConfig.URL+"/auth/get_user_token")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString()).execute().body();
+            JSONObject userJson = new JSONObject(body1);
+            JSONObject userData = userJson.getJSONObject("data");
+            String userToken = userData.getString("token");
+            return R.ok().put("token", userToken);
+        } else {
+            return R.error("获取管理员token失败");
+        }
+    }
+    @ApiOperation("添加好友")
+    @PostMapping("/importFriend")
+    public R importFriend(@RequestBody HashMap<String,Object> map){
+        String ownerUserID = (String) map.get("ownerUserID");
+        List<String> friendUserIDs = (List<String>)map.get("friendUserIDs");
+        OpenImResponseDTO openImResponseDTO = openIMService.importFriend(ownerUserID, friendUserIDs);
+        return R.ok().put("data",openImResponseDTO);
+    }
+}

+ 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, "短链课程流量记录数据");
     }

+ 16 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.params.FsUserCourseConfigParam;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.FsUserCourseListPVO;
 import com.fs.framework.web.service.TokenService;
@@ -341,4 +342,19 @@ public class FsUserCourseController extends BaseController
         redisCacheUtil.delRedisKey("getCourseList");
         return toAjax(1);
     }
+
+    /**
+     * 修改配置
+     **/
+    @PreAuthorize("@ss.hasPermi('course:userCourse:editConfig')")
+    @Log(title = "课程配置", businessType = BusinessType.UPDATE)
+    @PostMapping("/editConfig")
+    public R editConfig(@RequestBody FsUserCourseConfigParam params){
+        fsUserCourseService.editConfig(params.getId(), params.getConfigJson());
+        redisCacheUtil.delRedisKey("getCourseList");
+        redisCacheUtil.delRedisKey("h5user:course:video:list:all");
+        redisCacheUtil.delRedisKey("h5user:course:list:all");
+        redisCacheUtil.delRedisKey("cache:video");
+        return R.ok();
+    }
 }

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

+ 21 - 0
fs-admin/src/main/java/com/fs/course/params/FsUserCourseConfigParam.java

@@ -0,0 +1,21 @@
+package com.fs.course.params;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@ApiModel("过程页配置参数实体")
+@Data
+public class FsUserCourseConfigParam {
+
+    @NotNull(message = "课程ID不能为空")
+    @ApiModelProperty("课程ID")
+    private Long id;
+
+    @NotBlank(message = "配置信息不能为空")
+    @ApiModelProperty("配置信息")
+    private String configJson;
+}

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

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

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

+ 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(),"1");
+            }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());

+ 23 - 0
fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java

@@ -26,6 +26,9 @@ import com.fs.his.service.IFsDoctorService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
+import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
+import static com.fs.his.utils.PhoneUtil.encryptPhone;
+
 /**
  * 医生管理Controller
  *
@@ -291,4 +294,24 @@ public class FsDoctorController extends BaseController
         return R.ok().put("data", new PageInfo<>(list));
     }
 
+
+    @GetMapping("/getDocVoList")
+    public TableDataInfo getDocVoList(FsDoctorParam param) {
+        startPage();
+        List<FsDoctorVO> list = fsDoctorService.selectDocVOByNameAndPhone(param);
+        if (list == null || list.isEmpty()) {
+            param.setMobile(encryptPhone(param.getMobile()));
+            list = fsDoctorService.selectDocVOByNameAndPhone(param);
+        }
+
+        if (list != null) {
+            list.forEach( item -> {
+                if (item.getMobile() != null)
+                    item.setMobile(decryptAutoPhoneMk(item.getMobile()));
+            });
+        }
+        return getDataTable(list);
+    }
+
+
 }

+ 49 - 9
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -1,13 +1,13 @@
 package com.fs.his.controller;
 
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralOrder;
@@ -17,7 +17,8 @@ import com.fs.his.enums.ShipperCodeEnum;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsIntegralOrderService;
-import com.fs.his.utils.PhoneUtil;
+import com.fs.his.service.IFsStoreOrderService;
+import com.fs.utils.OrderContextHolder;
 import com.fs.his.vo.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -44,6 +45,9 @@ public class FsIntegralOrderController extends BaseController
     private IFsIntegralOrderService fsIntegralOrderService;
     @Autowired
     private IFsExpressService expressService;
+
+    @Autowired
+    private IFsStoreOrderService fsStoreOrderService;
     /**
      * 查询积分商品订单列表
      */
@@ -98,18 +102,33 @@ public class FsIntegralOrderController extends BaseController
     @GetMapping(value = "/getExpress/{id}")
     public R getExpress(@PathVariable("id") Long id)
     {
+        /**
+         * selectFsIntegralOrderByOrderId查询的数据表:fs_integral_order
+         * 需要执行添加字段的DDL语句
+         * ALTER TABLE `fs_integral_order`
+         * ADD COLUMN `login_account` VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '代服物流查询账号';
+         * */
         FsIntegralOrder fsIntegralOrder = fsIntegralOrderService.selectFsIntegralOrderByOrderId(id);
         ExpressInfoDTO expressInfoDTO=null;
-        if(StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())){
-            String lastFourNumber = "";
-            if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+        //代服管家 查询自己的物流
+        if (CloudHostUtils.hasCloudHostName("金牛明医")) {
+            FsStoreOrder fsStoreOrder = new FsStoreOrder();
+            fsStoreOrder.setOrderCode(fsIntegralOrder.getOrderCode());
+            //线程携带订单信息
+            OrderContextHolder.setIntegralOrder(fsIntegralOrder);
+            expressInfoDTO = fsStoreOrderService.getDfExpressInfoDTO(fsStoreOrder);
+        }else {
+            if (StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())) {
+                String lastFourNumber = "";
+                if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
 
-                lastFourNumber = fsIntegralOrder.getUserPhone();
-                if (lastFourNumber.length() == 11) {
-                    lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                    lastFourNumber = fsIntegralOrder.getUserPhone();
+                    if (lastFourNumber.length() == 11) {
+                        lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                    }
                 }
+                expressInfoDTO = expressService.getExpressInfo(fsIntegralOrder.getOrderCode(), fsIntegralOrder.getDeliveryCode(), fsIntegralOrder.getDeliverySn(), lastFourNumber);
             }
-            expressInfoDTO=expressService.getExpressInfo(fsIntegralOrder.getOrderCode(),fsIntegralOrder.getDeliveryCode(),fsIntegralOrder.getDeliverySn(),lastFourNumber);
         }
         return R.ok().put("data",expressInfoDTO);
     }
@@ -178,4 +197,25 @@ public class FsIntegralOrderController extends BaseController
         String orderCode = requestBody.get("orderCode");
         return toAjax(fsIntegralOrderService.cancelOrder(orderCode));
     }
+    /**
+     * 申请退款
+     * 接口不校验是否发货
+     * */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:cancel')")
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @PostMapping("/mandatoryRefunds")
+    public AjaxResult mandatoryRefunds(@RequestBody Map<String, String> requestBody){
+        String orderCode = requestBody.get("orderCode");
+        return toAjax(fsIntegralOrderService.mandatoryRefunds(orderCode));
+    }
+
+    /**
+     * 确认收货
+     * todo:权限标识符待定
+     */
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @GetMapping("/finishOrder/{orderCode}")
+    public AjaxResult finishOrder(@PathVariable("orderCode") String orderCode) {
+        return toAjax(fsIntegralOrderService.finishOrder(orderCode));
+    }
 }

+ 31 - 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,16 @@
-package com.fs.course.task;
+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.boot.context.event.ApplicationReadyEvent;
-import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
+
 /**
- * @description: 公司余额同步定时任务
+ * @description: 公司余额同步定时任务 (红包余额)
  * @author: Xgb
  * @createDate: 2025/10/22
  * @version: 1.0
@@ -59,6 +61,31 @@ 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:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/4 10:43
+     */
+    public void recordRedPacketBalance() {
+        companyService.recordRedPacketBalance();
+    }
+
+
+
 
 
 }

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

+ 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="";

+ 138 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwDeptController.java

@@ -0,0 +1,138 @@
+package com.fs.qw.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.QwDept;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.service.IQwDeptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 企业微信部门Controller
+ *
+ * @author fs
+ * @date 2024-08-27
+ */
+@RestController
+@RequestMapping("/qw/qwDept")
+public class QwDeptController extends BaseController
+{
+    @Autowired
+    private IQwDeptService qwDeptService;
+
+
+    /**
+     * 查询企业微信部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwDept qwDept)
+    {
+        startPage();
+        List<QwDept> list = qwDeptService.selectQwDeptList(qwDept);
+        return getDataTable(list);
+    }
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+
+    /**
+     * 同步企业微信 部门信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:list')")
+    @GetMapping("/syncDept/{companyId}")
+    public R syncDept(@PathVariable("companyId") Long companyId){
+
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(companyId);
+        for (String string : strings) {
+            qwDeptService.insertOrUpdateQwDept(string);
+        }
+
+        return  R.ok();
+    }
+
+    /**
+     * 导出企业微信部门列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:export')")
+    @Log(title = "企业微信部门", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwDept qwDept)
+    {
+        List<QwDept> list = qwDeptService.selectQwDeptList(qwDept);
+        ExcelUtil<QwDept> util = new ExcelUtil<QwDept>(QwDept.class);
+        return util.exportExcel(list, "企业微信部门数据");
+    }
+
+    /**
+     * 获取企业微信部门详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwDeptService.selectQwDeptById(id));
+    }
+
+    /**
+     * 新增企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:add')")
+    @Log(title = "企业微信部门", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwDept qwDept)
+    {
+        return toAjax(qwDeptService.insertQwDept(qwDept));
+    }
+
+    /**
+     * 修改企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:edit')")
+    @Log(title = "企业微信部门", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwDept qwDept)
+    {
+        return toAjax(qwDeptService.updateQwDept(qwDept));
+    }
+
+    /**
+     * 删除企业微信部门
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwDept:remove')")
+    @Log(title = "企业微信部门", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwDeptService.deleteQwDeptByIds(ids));
+    }
+
+    /**
+     * @Description: 获取企微部门 按Treeselect返回 每一个企微主体有自己的部门,按企微主体查询
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/30 9:33
+     */
+    @GetMapping("/treeselect")
+    public AjaxResult treeselect(QwDept qwDept)
+    {
+        if(StringUtils.isEmpty(qwDept.getCorpId())){
+            return AjaxResult.error("请选择企微主体");
+        }
+        List<QwDept> depts = qwDeptService.selectQwDeptList(qwDept);
+        return AjaxResult.success(qwDeptService.buildDeptTreeSelect(depts));
+    }
+
+}

+ 309 - 5
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -1,22 +1,48 @@
 package com.fs.qw.controller;
 
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactTransferCompanyAudit;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.param.QwFsUserParam;
+import com.fs.qw.param.QwUserBingParam;
+import com.fs.qw.param.QwUserListParam;
+import com.fs.qw.service.IQwDeptService;
 import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.QwUserVO;
+import com.fs.qwApi.domain.QwExternalContactAllListResult;
+import com.fs.qwApi.domain.inner.ExternalContact;
+import com.fs.qwApi.domain.inner.ExternalContactInfo;
+import com.fs.qwApi.domain.inner.FollowInfo;
+import com.fs.qwApi.param.QwExternalListParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.voice.utils.StringUtil;
 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.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 企微用户Controller
@@ -29,8 +55,28 @@ import java.util.List;
 public class QwUserController extends BaseController {
     @Autowired
     private IQwUserService qwUserService;
+
     @Autowired
     private IQwExternalContactTransferCompanyAuditService auditService;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
+
+    @Autowired
+    private IQwDeptService qwDeptService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
     @GetMapping("/getQwUserAll")
     public AjaxResult getQwUserAll(){
         return AjaxResult.success(qwUserService.getQwUserAll());
@@ -47,7 +93,265 @@ public class QwUserController extends BaseController {
    @GetMapping("/getMyQwCompanyList")
     public R getMyQwCompanyList()
     {
-        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOBySys();
+        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOAll();
         return  R.ok().put("data",list);
     }
+
+    /**
+     * 绑定企微用户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:bind')")
+    @Log(title = "企微用户", businessType = BusinessType.UPDATE)
+    @PutMapping("/bindQwUser")
+    @RepeatSubmit
+    public R bindQwUser(@RequestBody QwUserBingParam qwUserParam)
+    {
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(qwUserParam.getCompanyUserId());
+
+        String qwUserIdCompanyStr = companyUser.getQwUserId();
+        Set<String> qwUserIdCompanySet = new HashSet<>();
+        if (!StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr)) {
+            String[] qwUserId = qwUserIdCompanyStr.split(",");
+            qwUserIdCompanySet = Arrays.stream(qwUserId)
+                    .filter(id -> !id.isEmpty())
+                    .collect(Collectors.toSet());
+        }
+
+        if (companyUser!=null){
+
+            //选择的企业微信账号为“”
+            if (StringUtil.strIsNullOrEmpty(qwUserParam.getId())&&qwUserParam.getCompanyUserId()!=null){
+                //制空企业微信的绑定
+                qwUserService.updateUserByUserId(qwUserParam.getCompanyUserId());
+
+                //制空销售的绑定
+                companyUserMapper.updateCompanyUserByNullQwUserID(companyUser.getUserId());
+
+            }else {
+                String idParam = qwUserParam.getId();
+                String[] splitParam = idParam.split(",");
+                Set<String> splitParamSet = Arrays.stream(splitParam)
+                        .filter(s -> !s.isEmpty())
+                        .collect(Collectors.toSet());
+
+                // 找出在 qwUserIdCompanySet 中存在但在 splitParamSet 中不存在的元素
+                Set<String> difference = new HashSet<>(qwUserIdCompanySet);
+                difference.removeAll(splitParamSet);
+
+                //制空绑定
+                if (!difference.isEmpty()){
+                    difference.forEach(id->{
+                        qwUserService.updateUnBindUserById(id);
+                    });
+
+                }
+
+
+                for (String paramId : splitParamSet) {
+                    QwUser qu= qwUserService.selectQwUserById(Long.parseLong(paramId));
+                    if (qu.getCompanyUserId()!=null&&!qu.getCompanyUserId().equals(qwUserParam.getCompanyUserId())){
+                        return R.error( qu.getQwUserName()+"已经被其他人绑定,请先解绑");
+                    }
+
+
+                    CompanyUser user = new CompanyUser();
+                    user.setQwUserId(qwUserParam.getId());
+                    user.setUserId(companyUser.getUserId());
+                    user.setQwStatus(1);
+                    companyUserMapper.updateCompanyUser(user);
+
+
+
+                    qu.setCompanyUserId(qwUserParam.getCompanyUserId());
+                    qu.setStatus(1);
+                    qu.setCompanyId(companyUser.getCompanyId());
+                    qwUserService.updateQwUser(qu);
+
+                }
+
+                //同步最后单独跑/先不异步了pp
+                for (String paramId : splitParamSet){
+
+                    //如果销售没绑定过,全刷,否则只同步新的增加的
+                    if (StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr)){
+                        updateAndSyncQwUser(paramId, companyUser);
+
+                    }
+                    else if (!StringUtil.strIsNullOrEmpty(qwUserIdCompanyStr) && !qwUserIdCompanySet.contains(paramId)){
+                        updateAndSyncQwUser(paramId, companyUser);
+                    }
+                }
+            }
+
+
+            return R.ok();
+        }
+        return R.error("绑定失败");
+
+    }
+
+
+    private void updateAndSyncQwUser(String paramId, CompanyUser companyUser) {
+
+        QwUser qu= qwUserService.selectQwUserById(Long.parseLong(paramId));
+
+        qwExternalContactMapper.updateBindUserByQwUser(qu.getCorpId(),qu.getQwUserId(),companyUser.getCompanyId(),companyUser.getUserId());
+
+        //根据企微账号和公司id 同步企业微信账号(游标初始为空)
+        syncMyQwExternalContact(qu,null);
+    }
+
+    public R  syncMyQwExternalContact(QwUser qwUser,String getNextCursor )  {
+
+        String qwUserId = qwUser.getQwUserId();
+        String corpId = qwUser.getCorpId();
+        Long companyId = qwUser.getCompanyId();
+
+        QwExternalListParam param = new QwExternalListParam();
+        param.setLimit(100);
+        param.setUserid_list(Arrays.asList(qwUserId));
+        param.setCursor(getNextCursor);
+
+        QwExternalContactAllListResult list = qwApiService.getAllExternalcontactList(param, corpId);
+
+        logger.info("批量获取客户详情" + list);
+
+        if (list.getErrcode() == 0) {
+            List<ExternalContactInfo> externalContactList = list.getExternal_contact_list();
+            for (ExternalContactInfo externalContactInfo : externalContactList) {
+
+                ExternalContact externalContact = externalContactInfo.getExternal_contact();
+                FollowInfo followInfo = externalContactInfo.getFollow_info();
+                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalContact.getExternal_userid(), qwUserId, corpId);
+                logger.info("客户详情" + qwExternalContact);
+
+                if (qwExternalContact == null) {
+                    qwExternalContact = new QwExternalContact();
+                    qwExternalContact.setUserId(qwUserId); // 设置属于用户ID
+                    qwExternalContact.setExternalUserId(externalContact.getExternal_userid()); // 设置外部联系人ID
+
+
+                    qwExternalContact.setCorpId(corpId); // 设置企业ID
+                    qwExternalContact.setCompanyId(companyId); // 设置公司ID
+                }
+
+
+                // 设置公共属性
+                qwExternalContact.setCompanyUserId(qwUser.getCompanyUserId());
+                qwExternalContact.setQwUserId(qwUser.getId());
+                qwExternalContact.setName(externalContact.getName()); // 设置名称
+                qwExternalContact.setAvatar(externalContact.getAvatar()); // 设置头像
+                qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
+                qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
+                qwExternalContact.setDescription(followInfo.getDescription()); // 设置描述信息
+                qwExternalContact.setRemark(followInfo.getRemark());
+
+//                        if (followInfo.getTag_id() != null && !followInfo.getTag_id().isEmpty()) {
+                qwExternalContact.setTagIds(JSON.toJSONString(followInfo.getTag_id())); // 设置标签ID
+//                        }
+
+//                        if (followInfo.getRemark_mobiles() != null && !followInfo.getRemark_mobiles().isEmpty()) {
+                qwExternalContact.setRemarkMobiles(JSON.toJSONString(followInfo.getRemark_mobiles())); // 设置备注电话号码
+//                        }
+
+                qwExternalContact.setState(followInfo.getState());
+
+                if (followInfo.getState() != null && !followInfo.getState().isEmpty()) {
+                    String s = "way:" + corpId + ":";
+                    if (followInfo.getState().contains(s)) {
+                        String wayId = followInfo.getState().substring(followInfo.getState().indexOf(s) + s.length());
+                        qwExternalContact.setWayId(Long.parseLong(wayId));
+                    }
+                }
+
+                qwExternalContact.setRemarkCorpName(followInfo.getRemark_corp_name()); // 设置备注企业名称
+                qwExternalContact.setAddWay(followInfo.getAdd_way()); // 设置来源
+                qwExternalContact.setOperUserid(followInfo.getOper_userid()); // 设置oper用户ID
+
+                // 根据是否存在记录进行更新或插入
+                if (qwExternalContact.getId() != null) {
+                    qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                } else {
+                    qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
+                }
+            }
+
+        }else {
+            return R.error("同步失败:"+list.getErrmsg());
+        }
+
+        if (!StringUtil.strIsNullOrEmpty(list.getNext_cursor())){
+            syncMyQwExternalContact(qwUser,list.getNext_cursor());
+        }
+        return R.ok();
+    }
+
+
+    /**
+     * 同步企微用户
+     */
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+    @PostMapping("sync/{corpId}")
+    public R sync(@PathVariable("corpId") String corpId)
+    {
+
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
+        for (String string : strings) {
+
+            if (string.equals(corpId)){
+                qwUserService.syncQwUser(string);
+                qwDeptService.insertOrUpdateQwDept(string);
+                logger.info("同步完成");
+            }
+        }
+        return R.ok();
+    }
+
+    /**
+     * 获取企微用户详细信息
+     */
+
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOById(id));
+    }
+    /**
+     * 批量查询 企微用户详细信息
+     */
+    @GetMapping(value = "/getInfo/{ids}")
+    public AjaxResult getInfoByIds(@PathVariable("ids") Long[] ids)
+    {
+        return AjaxResult.success(qwUserService.selectQwUserVOByIds(ids));
+    }
+
+    @RepeatSubmit
+    @PreAuthorize("@ss.hasPermi('qw:user:sync')")
+    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
+    @PostMapping("syncName/{corpId}")
+    public R syncName(@PathVariable("corpId") String corpId)
+    {
+        List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByAll();
+        for (String string : strings) {
+            if (string.equals(corpId)){
+                qwUserService.syncQwUserName(string);
+            }
+        }
+        return R.ok();
+    }
+
+
+    /**
+     * 查询企微用户列表
+     */
+
+    @GetMapping("/userList")
+    public TableDataInfo userList(QwUserListParam qwUser)
+    {
+        startPage();
+        List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
+        return getDataTable(list);
+    }
 }

+ 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-上月;

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

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

+ 29 - 11
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);
+        }
     }
 
 
@@ -212,8 +225,13 @@ public class FsQwCourseWatchLogController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
+        List<QwWatchLogStatisticsListVO> list = new ArrayList<>();
 
-        List<QwWatchLogStatisticsListVO> list = qwWatchLogService.selectQwWatchLogStatisticsListVOExport(param);
+        if ("济南联志健康".equals(signProjectName)) {
+            list = qwWatchLogService.selectQwWatchLogStatisticsListVOExportExcludeTransfer(param);
+        }else{
+            list = qwWatchLogService.selectQwWatchLogStatisticsListVOExport(param);
+        }
 
         ExcelUtil<QwWatchLogStatisticsListVO> util = new ExcelUtil<QwWatchLogStatisticsListVO>(QwWatchLogStatisticsListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");

+ 74 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -13,9 +13,10 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
+import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsUserCourseListUParam;
-import com.fs.course.service.IFsUserCompanyBindService;
-import com.fs.course.service.IFsUserCourseStudyService;
+import com.fs.course.service.*;
+import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsUserCourseStudyListUVO;
 import com.fs.course.vo.UserWatchLogListVo;
 import com.fs.crm.param.CrmMyCustomerListQueryParam;
@@ -718,7 +719,78 @@ public class QwExternalContactController extends BaseController
         return R.ok("正在批量修改备注中");
     }
 
+    /**
+     * copy批量修改备注方法,该方法提供给我的看课记录里面使用
+     * @param param
+     * @return
+     * @throws JSONException
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:edit')")
+    @Log(title = "批量修改备注", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchUpdateExternalContactNotesByWatchLog")
+    public R batchUpdateExternalContactNotesByWatchLog(@RequestBody QwExternalContactUpdateNoteParam param) throws JSONException {
+        if(param.isFilter()){
+            param.setWatchLogIds(getWatchLogIds(param.getFromMyList(), param.getWatchLogParam()));
+        }
+        //查询
+        if(null != param.getWatchLogIds() && !param.getWatchLogIds().isEmpty()){
+            param.setUserIds(getUserIdsByWatchLogIds(param.getWatchLogIds()));
+        }
+
+        if(param.getUserIds() == null || param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
+        qwUserServiceAsyncHelper.batchUpdateExternalContactNotes(param);
+        return R.ok("正在批量修改备注中");
+    }
+
+    @Autowired
+    private IFsUserCoursePeriodService userCoursePeriodService;
 
+    @Autowired
+    private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    /**
+     * 用于适配看课批量修改标签【根据过滤条件版】
+     * @param fromMyList
+     * @param watchLogParam
+     * @return
+     */
+    private List<Long> getWatchLogIds(Integer fromMyList,FsCourseWatchLogListParam watchLogParam){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if(Integer.valueOf(0).equals(fromMyList)){
+            watchLogParam.setCompanyId( loginUser.getCompany().getCompanyId());
+        }else{
+            watchLogParam.setCompanyUserId( loginUser.getUser().getUserId());
+        }
+
+        if (watchLogParam.getSendType()==1&& watchLogParam.getPeriodETime()!=null && watchLogParam.getPeriodSTime()!=null) {
+            List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(watchLogParam.getPeriodSTime(), watchLogParam.getPeriodETime());
+
+            if (!periodIds.isEmpty()){
+                List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, loginUser.getCompany().getCompanyId());
+                if (!longs.isEmpty()){
+                    watchLogParam.setPeriodIds(longs);
+                }else {
+                    return new ArrayList<>();
+                }
+            }else {
+                return new ArrayList<>();
+            }
+
+        }
+
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(watchLogParam);
+        List<Long> collect = list.stream().map(FsCourseWatchLogListVO::getLogId).collect(Collectors.toList());
+        return collect;
+    }
+    private List<Long> getUserIdsByWatchLogIds(List<Long> watchLogIds){
+        List<Long> exContactIdsIdsByWatchLogIds = fsCourseWatchLogService.getExContactIdsIdsByWatchLogIds(watchLogIds);
+        Set<Long> set = new HashSet<>(exContactIdsIdsByWatchLogIds);
+        return new ArrayList<>(set);
+    }
 
     private List<Long> getList(Integer addType, QwExternalContactParam param){
         if(addType == null){

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

+ 4 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwTagController.java

@@ -5,7 +5,9 @@ import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.qw.domain.QwTag;
 import com.fs.qw.param.QwTagParam;
@@ -45,6 +47,8 @@ public class QwTagController extends BaseController
     @PostMapping("/searchTags")
     public TableDataInfo  searchTags(@RequestBody QwTagParam tagParam)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        tagParam.setCompanyId(loginUser.getCompany().getCompanyId());
         return getDataTable(qwTagService.searchTags(tagParam));
     }
     /**

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

@@ -621,7 +621,7 @@ public class QwUserController extends BaseController
     }
     @RepeatSubmit
     @PreAuthorize("@ss.hasPermi('qw:user:sync')")
-    @Log(title = "企微用户", businessType = BusinessType.INSERT)
+    @Log(title = "同步企微用户名称", businessType = BusinessType.INSERT)
     @PostMapping("syncName/{corpId}")
     public R syncName(@PathVariable String corpId)
     {

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

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

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

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

@@ -37,11 +37,11 @@ public class SendMsgTest {
 
     @Test
     public void testLogWrite(){
-        List<Long> testLogIds = Arrays.asList(177L,180L,182L,183L,184L,185L);
+        List<Long> testLogIds = Arrays.asList(194L,195L);
 
         for(Long logId : testLogIds){
             FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
-            fsTagUpdateService.onCourseWatchingBatch(Collections.singletonList(fsCourseWatchLog));
+            fsTagUpdateService.onCourseWatchFinishedBatch(Collections.singletonList(fsCourseWatchLog));
         }
     }
 

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

@@ -308,7 +308,7 @@ public class QwMsgController {
                 if (wxWorkMessageDTO.getReferid()!=0){
                     break;
                 }
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
@@ -347,30 +347,6 @@ public class QwMsgController {
                         content = wxWorkMessageDTO.getUrl();
                         log.info("id:{}, 用户发送表情"+content, id);
                     }//视频号
-                    else if (wxWorkMessageDTO.getMsgtype()==141){
-                        QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
-                        if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
-                            QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
-                            if(qwUserVideo == null){
-                                QwUserVideo userVideo=new QwUserVideo();
-                                userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
-                                userVideo.setAppKey(qwUserByAppKey.getAppKey());
-                                userVideo.setNickName(wxWorkMessageDTO.getNickname());
-                                userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
-                                userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
-                                userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
-                                userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
-                                userVideo.setDesc(wxWorkMessageDTO.getDesc());
-                                userVideo.setUrl(wxWorkMessageDTO.getUrl());
-                                userVideo.setExtras(wxWorkMessageDTO.getExtras());
-                                userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
-                                userVideo.setQwUserId(qwUserByAppKey.getId());
-                                userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
-                                userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
-                                qwUserVideoService.insertQwUserVideo(userVideo);
-                            }
-                        }
-                    }
 
                     if (2000000000000000L-receiver>0){
                         log.info("id:{}, 客户发送", id);
@@ -381,6 +357,34 @@ public class QwMsgController {
                     }
 
                 }
+                //视频号
+                if (wxWorkMessageDTO.getMsgtype()==141){
+                    QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
+                    log.info("进入到视频号:{}",qwUserByAppKey);
+
+                    if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
+                        QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
+                        if(qwUserVideo == null){
+                            QwUserVideo userVideo=new QwUserVideo();
+                            userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
+                            userVideo.setAppKey(qwUserByAppKey.getAppKey());
+                            userVideo.setNickName(wxWorkMessageDTO.getNickname());
+                            userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
+                            userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
+                            userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
+                            userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
+                            userVideo.setDesc(wxWorkMessageDTO.getDesc());
+                            userVideo.setUrl(wxWorkMessageDTO.getUrl());
+                            userVideo.setExtras(wxWorkMessageDTO.getExtras());
+                            userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
+                            userVideo.setQwUserId(qwUserByAppKey.getId());
+                            userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
+                            userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
+                            qwUserVideoService.insertQwUserVideo(userVideo);
+                            log.info("存储完成:userVideo={}",userVideo);
+                        }
+                    }
+                }
                 //语音通话
                 if (wxWorkMessageDTO.getMsgtype()==40){
                     if (wxWorkMessageDTO.getRecordtype()==null){

+ 1 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -207,6 +207,7 @@ public class QwDataCallbackService {
                             if(WelcomeCodeList.getLength() > 0) {
                                 WelcomeCode = WelcomeCodeList.item(0).getTextContent();
                             }
+
                             String userId = root.getElementsByTagName("UserID").item(0).getTextContent();
                             String externalUserId = root.getElementsByTagName("ExternalUserID").item(0).getTextContent();
                             String cacheKey = "qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId;

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

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

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

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

+ 14 - 4
fs-service/src/main/java/com/fs/company/domain/CompanyDeptTreeSelect.java

@@ -14,21 +14,31 @@ public class CompanyDeptTreeSelect {
     /** 节点名称 */
     private String label;
 
+    /**
+    * 销售公司
+    */
+    private Long companyId;
+
     /** 子节点 */
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<CompanyDeptTreeSelect> children;
 
-    public CompanyDeptTreeSelect()
-    {
-
-    }
     public CompanyDeptTreeSelect(CompanyDept dept) {
         this.id = dept.getDeptId();
         this.label = dept.getDeptName();
+        this.companyId = dept.getCompanyId();
         this.children = dept.getChildren().stream().map(CompanyDeptTreeSelect::new).collect(Collectors.toList());
 
     }
 
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
     public Long getId()
     {
         return id;

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

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

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

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

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

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

+ 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);
     /**

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

@@ -170,9 +170,17 @@ public interface ICompanyService
 
     void syncCompanyBalance();
 
+    void redPacketTopUpCompany(Long companyId, BigDecimal money,String type);
+
+    void asyncRecordBalanceLog(Long companyId, BigDecimal money,Integer logType, BigDecimal balance, String remark);
+
+    void recordRedPacketBalance();
+
     /**
      * 批量更新公司余额
      * @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);
 

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

+ 252 - 31
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;
@@ -20,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;
@@ -42,6 +41,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,6 +105,12 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+
     @Autowired
     private TransactionTemplate transactionTemplate;
 
@@ -672,7 +679,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 +1261,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 +1365,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 +1378,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,9 +1391,193 @@ public class CompanyServiceImpl implements ICompanyService
                 }));
     }
 
+    /**
+     * @Description: 红包充值
+     * @Param: type 充值类型 1 充值 2 扣款
+     * @Return:
+     * @Author xgb
+     * @Date 2025/11/3 14:01
+     */
+    @Override
+    public void redPacketTopUpCompany(Long companyId, BigDecimal money,String type) {
+
+        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:{},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,"红包充值(负数为扣款)");
+
+            } else {
+                logger.error("获取redis锁失败,异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
+                throw new RuntimeException("服务繁忙,请稍后重试");
+            }
+        } catch (Exception e) {
+            logger.error("增加余额失败: 异常请求参数companyId:{},money:{},type:{}",companyId,money, type, e);
+            throw new RuntimeException("系统异常,请稍后重试");
+        }finally {
+            // 只有在成功获取锁的情况下才释放锁
+            if (lockAcquired && lock1.isHeldByCurrentThread()) {
+                try {
+                    lock1.unlock();
+                } catch (IllegalMonitorStateException e) {
+                    logger.error("尝试释放非当前线程持有的锁: 异常请求参数companyId:{},money:{},type:{}",companyId,money, type);
+                }
+            }
+        }
+
+
+    }
+
+
+    /**
+     * 异步登记余额添加日志  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);
     }
 
+    /**
+     * @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) {

+ 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/CompanyDeductVO.java

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

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

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

@@ -152,4 +152,9 @@ public class FsUserCourse extends BaseEntity
     @TableField(exist = false)
     private Long[] companyIdsList;
 
+    /**
+     * 课堂配置
+     */
+    private String configJson;
+
 }

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

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

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

+ 11 - 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} " +
@@ -569,4 +570,13 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     // 统计当天各公司的观看人数和完播人数, 存到redis中,定时任务每 ? 分钟执行一次
     List<WatchCourseStatisticsResultDTO> watchCourseStatisticsGroupByCompany(@Param("params") Map<String, Object> params);
+
+    @Select({"<script>" +
+            " select qw_external_contact_id from fs_course_watch_log where log_id in  " +
+            "        <foreach collection=\"watchLogIds\" item=\"id\" open=\"(\" separator=\",\" close=\")\">\n" +
+               "    #{id} " +
+               "</foreach>" +
+            "</script>"
+    })
+    List<Long> getExContactIdsIdsByWatchLogIds(@Param("watchLogIds")List<Long> watchLogIds);
 }

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

@@ -193,7 +193,7 @@ public interface FsUserCourseMapper
 
 
     @Select("select v.video_id,v.title,v.course_id,v.video_url,v.question_bank_id," +
-            "SEC_TO_TIME(c.duration) as total_duration,c.views,c.course_name,c.description,c.img_url " +
+            "SEC_TO_TIME(c.duration) as total_duration,c.views,c.course_name,c.description,c.img_url,c.config_json  " +
             " from fs_user_course_video v " +
             "left join fs_user_course c on v.course_id = c.course_id " +
             "where v.video_id = #{videoId}")
@@ -328,4 +328,11 @@ public interface FsUserCourseMapper
 
     @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where course_id = #{courseId} and is_del = 0")
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(@Param("courseId") Long courseId);
+
+
+    /**
+     * 修改课堂配置
+     */
+    @Update("update fs_user_course set config_json = #{configJson} where course_id = #{id}")
+    void editConfig(@Param("id") Long id, @Param("configJson") String configJson);
 }

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

@@ -67,7 +67,9 @@ public interface FsUserCourseVideoRedPackageMapper
      * @return 结果
      */
     public int deleteFsUserCourseVideoRedPackageByIds(Long[] ids);
-    public int deleteFsUserCourseVideoRedPackageByVedioIds(Long[] ids);
+    public int deleteFsUserCourseVideoRedPackageByVedioIds(
+            @Param("videoIds") Long[] videoIds,
+            @Param("periodIds") Long[] periodIds);
     @Update("INSERT INTO fs_user_course_video_red_package (company_id, video_id, red_packet_money) " +
             "VALUES (#{companyId}, #{videoId}, #{redPacketMoney}) " +
             "ON DUPLICATE KEY UPDATE red_packet_money = VALUES(red_packet_money);")

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

+ 7 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -139,4 +139,11 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * 看课统计
      * */
     List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
+
+    /**
+     * 根据看课记录id获取所有的外部联系人ids
+     * @param watchLogIds
+     * @return
+     */
+    List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds);
 }

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

@@ -13,6 +13,7 @@ import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.his.vo.OptionsVO;
 
+import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 
 /**
@@ -134,4 +135,9 @@ public interface IFsUserCourseService
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId);
 
     R createAppCourseSortLink(FsCourseLinkCreateParam fsCourseLinkCreateParam);
+
+    /**
+     * 修改课堂配置
+     */
+    void editConfig(Long id, String configJson);
 }

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

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

@@ -643,6 +643,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
+
+        // 因为selectFsCourseWatchLogListVO 这个方法中查询了看课日志记录表 需要限制创建时间必传
+        if((StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())) &&
+                (StringUtils.isEmpty(param.getUpSTime()) || StringUtils.isEmpty(param.getUpETime())) &&
+                (StringUtils.isEmpty(param.getScheduleEndTime()) || StringUtils.isEmpty(param.getScheduleStartTime()))){
+            throw new RuntimeException("请输入创建时间或营期时间或更新时间其中一个");
+        }
+
         // 待看课-未注册
         if(ObjectUtil.equal(param.getLogType(),5)){
             param.setLogType(3);
@@ -1281,5 +1289,16 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectQwFsCourseWatchLogStatisticsListVO(param);
     }
 
+    /**
+     * 根据看课记录id获取所有的外部联系人ids
+     * @param watchLogIds
+     * @return
+     */
+    @Override
+    public List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds){
+        return fsCourseWatchLogMapper.getExContactIdsIdsByWatchLogIds(watchLogIds);
+    }
+
+
 
 }

+ 2 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java

@@ -129,11 +129,12 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         int flag = 0;
         List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectBatchIds(Arrays.asList(ids));
         List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
+        List<Long> getPeriodIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getPeriodId).collect(Collectors.toList());
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             flag = fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
             //删除红包记录
-            fsUserCourseVideoRedPackageMapper.deleteFsUserCourseVideoRedPackageByVedioIds(videoIds.toArray(new Long[0]));
+            fsUserCourseVideoRedPackageMapper.deleteFsUserCourseVideoRedPackageByVedioIds(videoIds.toArray(new Long[0]),getPeriodIds.toArray(new Long[0]));
         }
         return flag;
     }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -762,6 +762,14 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return R.error("生成链接失败!");
     }
 
+    /**
+     * 修改课堂配置
+     */
+    @Override
+    public void editConfig(Long id, String configJson) {
+        fsUserCourseMapper.editConfig(id, configJson);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

+ 183 - 182
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;
@@ -544,6 +545,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();
 
         // 处理逻辑
@@ -928,30 +942,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Override
     public List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam fsUserCourseVideo) {
-        List<FsUserCourseVideoVO> fsUserCourseVideoVOS = fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
-        for (FsUserCourseVideoVO item : fsUserCourseVideoVOS) {
-            if(ObjectUtils.isNotNull(item.getTgId())){
-                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupById(item.getTgId());
-                if(ObjectUtils.isNotNull(qwTagGroup)){
-                    item.setTagGroupName(qwTagGroup.getName());
-                }
-            }
-
-            if(ObjectUtils.isNotNull(item.getWatchingTgId())){
-                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchingTgId());
-                if(ObjectUtils.isNotNull(qwTag)){
-                    item.setWatchingTagName(qwTag.getName());
-                }
-            }
-
-            if(ObjectUtils.isNotNull(item.getWatchedTgId())) {
-                QwTag qwTag = qwTagMapper.selectQwTagById(item.getWatchedTgId());
-                if(ObjectUtils.isNotNull(qwTag)){
-                    item.setWatchedTagName(qwTag.getName());
-                }
-            }
-        }
-        return fsUserCourseVideoVOS;
+        return fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
     }
 
     @Override
@@ -1595,174 +1586,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();
             // 添加红包记录
@@ -2649,6 +2649,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,
@@ -2702,10 +2707,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }else {
             link.setLinkType(3);
         }
-        link.setProjectCode(cloudHostProper.getProjectCode());
-
-        link.setProjectCode(cloudHostProper.getProjectCode());
-
         String randomString = generateRandomStringWithLock();
         if (StringUtil.strIsNullOrEmpty(randomString)){
             link.setLink(UUID.randomUUID().toString().replace("-", ""));

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

+ 5 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java

@@ -52,4 +52,9 @@ public class FsUserCourseListPVO extends BaseEntity
      * 项目名称
      */
     private String projectName;
+
+    /**
+     * 课堂配置
+     */
+    private String configJson;
 }

+ 2 - 2
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java

@@ -35,7 +35,7 @@ public class FsUserCourseVideoH5VO extends BaseEntity
     /** 总播放量 */
     private Long views;
 
-
-
+    /** 课堂配置 **/
+    private String configJson;
 
 }

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

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

+ 12 - 0
fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java

@@ -47,6 +47,7 @@ import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.utils.OrderContextHolder;
 import com.hc.openapi.tool.util.StringUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.util.Asserts;
@@ -255,9 +256,20 @@ public class DfOrderServiceImpl implements IErpOrderService {
         String orderCode = request.getCode();
         if (StrUtil.isNotBlank(orderCode)) {
             FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(orderCode);
+            if (OrderContextHolder.hasIntegralOrder())
+                order = new FsStoreOrder();
             if (order != null) {
                 String mailNumber = order.getDeliverySn();
                 Long dfAccountId = getSFAccountIndex(order.getOrderId());
+                //积分商城逻辑
+                if (OrderContextHolder.hasIntegralOrder()){
+                    FsIntegralOrder integralOrder = OrderContextHolder.getIntegralOrder();
+                    mailNumber = integralOrder.getDeliverySn();
+                    FsDfAccount fsDfAccount = fsDfAccountMapper.selectFsDfAccountByAccount(integralOrder.getLoginAccount());
+                    if (fsDfAccount != null)
+                        dfAccountId = fsDfAccount.getId();
+                    OrderContextHolder.clear();//清理threadlocalmap引用。
+                }
                 if (StringUtils.isNotBlank(mailNumber) && dfAccountId != null) {
                     try {
                         Map<String, Object> map = new HashMap<>();

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

+ 1 - 1
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -114,6 +114,6 @@ public class FsIntegralOrder extends BaseEntity
     @Excel(name = "销售公司ID")
     private Long companyId;
 
-
+    private String loginAccount;
 
 }

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

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

@@ -108,4 +108,6 @@ public interface FsIntegralOrderMapper
     FsIntegralOrderPVO selectFsIntegralOrderPVO(Long orderId);
 
     int cancelOrder(@Param("orderId") Long orderId);
+
+    int finishOrder(@Param("orderId") Long orderId,@Param("oldStatus") Integer status);
 }

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

@@ -105,4 +105,8 @@ public interface IFsIntegralOrderService
     AjaxResult export(FsIntegralOrder fsIntegralOrder);
 
     int cancelOrder(String orderCode);
+
+    int mandatoryRefunds(String orderCode);
+
+    int finishOrder(String orderCode);
 }

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

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

@@ -103,6 +103,15 @@ public interface IFsStorePaymentService
 
     String transferNotify(String notifyData, HttpServletRequest request);
 
+    /**
+     * 分公司配置回调地址
+     * @param companyId
+     * @param notifyData
+     * @param request
+     * @return
+     */
+    String TransferNotifyWithCompanyId(Long companyId,String notifyData, HttpServletRequest request);
+
     Boolean isEntityNull(FsStorePaymentParam fsStorePayment);
 
     Long selectFsStorePaymentExcelVOCount(FsStorePaymentParam fsStorePayment);

+ 52 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -44,6 +44,7 @@ import com.fs.ybPay.dto.OrderQueryDTO;
 import com.fs.ybPay.service.IPayService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
+import org.redisson.api.RObjectAsync;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -192,7 +193,8 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         o1.setDeliveryName(fsIntegralOrder.getDeliveryName());
         o1.setDeliverySn(fsIntegralOrder.getDeliverySn());
         o1.setDeliveryTime(DateUtils.getNowDate());
-
+        //增加字段,表示代服物流的账号,后续会使用该账号信息来查询物流。
+        o1.setLoginAccount(fsIntegralOrder.getLoginAccount());
         return fsIntegralOrderMapper.updateFsIntegralOrder(o1);
     }
 
@@ -652,6 +654,55 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         return i;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int mandatoryRefunds(String orderCode) {
+        //查询订单是否存在且是否未发货
+        FsIntegralOrder fsIntegralOrder = fsIntegralOrderMapper.selectFsIntegralOrderByOrderCode(orderCode);
+        if (null==fsIntegralOrder){
+            throw new ServiceException("订单不存在");
+        }
+        //用户说自己去对接物流,不必校验是否发货。
+//        if (fsIntegralOrder.getStatus()!=1){
+//            throw new ServiceException("订单已发货或已完成");
+//        }
+        int i = 0;
+        //修改订单状态
+        i = fsIntegralOrderMapper.cancelOrder(fsIntegralOrder.getOrderId());
+        if (i>0){
+            //原路退回积分
+            FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsIntegralOrder.getUserId());
+            fsUser.setIntegral(fsUser.getIntegral()+Long.parseLong(fsIntegralOrder.getIntegral()));
+            i = fsUserMapper.updateFsUser(fsUser);
+            //新增积分记录
+            FsUserIntegralLogs fsUserIntegralLogs = new FsUserIntegralLogs();
+            fsUserIntegralLogs.setBalance(fsUser.getIntegral());
+            fsUserIntegralLogs.setBusinessId(fsIntegralOrder.getOrderId().toString());
+            fsUserIntegralLogs.setUserId(fsIntegralOrder.getUserId());
+            fsUserIntegralLogs.setLogType(20);
+            fsUserIntegralLogs.setIntegral(Long.parseLong(fsIntegralOrder.getIntegral()));
+            fsUserIntegralLogs.setBusinessType(2);
+            fsUserIntegralLogs.setStatus(0);
+            i = fsUserIntegralLogsMapper.insertFsUserIntegralLogs(fsUserIntegralLogs);
+            //todo:库存是否需要退还,待定
+        }
+        return i;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int finishOrder(String orderCode) {
+        //查询订单是否存在且是否未发货
+        FsIntegralOrder fsIntegralOrder = fsIntegralOrderMapper.selectFsIntegralOrderByOrderCode(orderCode);
+        if (null==fsIntegralOrder){
+            throw new ServiceException("订单不存在");
+        }
+        if (fsIntegralOrder.getStatus()!=2){
+            throw new ServiceException("订单未发货或者未支付,无法完成订单");
+        }
+        return fsIntegralOrderMapper.finishOrder(fsIntegralOrder.getOrderId(), fsIntegralOrder.getStatus());
+    }
+
     /**
      * 处理手机号脱敏
      */

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

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

@@ -940,6 +940,50 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         }
     }
 
+    /**
+     * 分公司配置回调地址
+     * @param companyId
+     * @param notifyData
+     * @param request
+     * @return
+     */
+    @Override
+    public String TransferNotifyWithCompanyId(Long companyId,String notifyData, HttpServletRequest request) {
+        logger.info("分公司回调::companyId:{}",companyId);
+        logger.info("zyp \n【收到转账回调::分公司】:{}",notifyData);
+        try {
+            String json = companyConfigMapper.selectRedPacketConfigByKey(companyId);
+//            String json = configService.selectConfigByKey("redPacket.config");
+            RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+            //创建微信订单
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config,payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            SignatureHeader signatureHeader = new SignatureHeader();
+            signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
+            signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
+            signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
+            signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
+            WxPayTransferBatchesNotifyV3Result result = wxPayService.parseTransferBatchesNotifyV3Result(notifyData,signatureHeader);
+            logger.info("到零钱回调::分公司:{}",result.getResult());
+            if (result.getResult().getBatchStatus().equals("FINISHED") && result.getResult().getFailNum()==0) {
+                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBatchNo(),result.getResult().getBatchId());
+                logger.info("result:{}",r);
+                if (r.get("code").equals(200)){
+                    return WxPayNotifyResponse.success("处理成功");
+                }else {
+                    return WxPayNotifyResponse.fail("");
+                }
+            }else {
+                return WxPayNotifyResponse.fail("");
+            }
+        } catch (WxPayException e) {
+            logger.error("zyp \n【转账回调异常】:{}", e.getReturnMsg());
+            return WxPayNotifyResponse.fail(e.getMessage());
+        }
+    }
+
     @Override
     public Boolean isEntityNull(FsStorePaymentParam param) {
         if (param == null) {

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

@@ -243,6 +243,11 @@ public class FsUserServiceImpl implements IFsUserService {
         } else {
             fsUser.setPhone(null);
         }
+        if (ObjectUtils.isNotEmpty(fsUser.getLevel())&&fsUser.getLevel().equals(1)){
+            fsUser.setIsShow(1);
+        }else {
+            fsUser.setIsShow(0);
+        }
         return fsUserMapper.updateFsUser(fsUser);
     }
 

+ 1 - 1
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java

@@ -75,5 +75,5 @@ public class FsIntegralOrderPVO extends BaseEntity
 
     private String phone;
 
-
+    private String loginAccount;
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است