Przeglądaj źródła

Merge branch 'refs/heads/master' into openImAndLive

# Conflicts:
#	fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
#	fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
#	fs-service/src/main/resources/mapper/company/CompanyMapper.xml
caoliqin 1 miesiąc temu
rodzic
commit
38f32afe9b
100 zmienionych plików z 3063 dodań i 493 usunięć
  1. 0 1
      README.md
  2. 33 4
      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. 572 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyUserAllController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  6. 5 1
      fs-admin/src/main/java/com/fs/course/controller/FsCourseTrafficLogController.java
  7. 16 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  8. 4 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  9. 21 0
      fs-admin/src/main/java/com/fs/course/params/FsUserCourseConfigParam.java
  10. 59 0
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  11. 18 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyDeductController.java
  12. 20 13
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyRechargeController.java
  13. 23 0
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  14. 49 9
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  15. 31 4
      fs-admin/src/main/java/com/fs/his/task/CompanyBalanceTask.java
  16. 1 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  17. 10 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStorePaymentScrmController.java
  18. 138 0
      fs-admin/src/main/java/com/fs/qw/controller/QwDeptController.java
  19. 309 5
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  20. 47 47
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  21. 2 2
      fs-admin/src/main/resources/application.yml
  22. 6 5
      fs-common/pom.xml
  23. 1 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  24. 37 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  25. 43 0
      fs-company/src/main/java/com/fs/company/controller/company/FsRedPacketController.java
  26. 29 11
      fs-company/src/main/java/com/fs/company/controller/course/qw/FsQwCourseWatchLogController.java
  27. 74 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  28. 1 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  29. 4 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagController.java
  30. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  31. 104 0
      fs-company/src/main/java/com/fs/company/controller/tag/FsVideoCourseTagController.java
  32. 3 2
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  33. 2 2
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  34. 29 25
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  35. 1 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  36. 9 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  37. 99 3
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  38. 5 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingServiceImpl.java
  39. 1 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  40. 1 1
      fs-qw-voice/pom.xml
  41. 15 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  42. 13 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  43. 13 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  44. 14 0
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  45. 2 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  46. 31 18
      fs-service/src/main/java/com/fs/company/domain/CompanyDeduct.java
  47. 14 4
      fs-service/src/main/java/com/fs/company/domain/CompanyDeptTreeSelect.java
  48. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyRecharge.java
  49. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeductMapper.java
  50. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyDeptMapper.java
  51. 3 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyRechargeMapper.java
  52. 19 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  53. 12 0
      fs-service/src/main/java/com/fs/company/mapper/StatisticManageMapper.java
  54. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  55. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  56. 21 0
      fs-service/src/main/java/com/fs/company/service/IStatisticManageService.java
  57. 207 20
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  58. 11 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  59. 27 3
      fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java
  60. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyDeductVO.java
  61. 3 0
      fs-service/src/main/java/com/fs/company/vo/CompanyRechargeVO.java
  62. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyVO.java
  63. 8 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  64. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourse.java
  65. 0 27
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  66. 17 17
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  67. 9 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  68. 8 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  69. 3 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  70. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  71. 7 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  72. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  73. 4 3
      fs-service/src/main/java/com/fs/course/service/impl/BalanceRollbackErrorServiceImpl.java
  74. 19 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  75. 2 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  76. 8 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  77. 179 184
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  78. 18 1
      fs-service/src/main/java/com/fs/course/vo/FsCourseTrafficLogListVO.java
  79. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseListPVO.java
  80. 2 2
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5VO.java
  81. 0 39
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoVO.java
  82. 12 0
      fs-service/src/main/java/com/fs/erp/service/impl/DfOrderServiceImpl.java
  83. 1 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  84. 1 1
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  85. 23 0
      fs-service/src/main/java/com/fs/his/domain/FsRedPacket.java
  86. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralOrderMapper.java
  87. 4 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralOrderService.java
  88. 15 0
      fs-service/src/main/java/com/fs/his/service/IFsRedPacketService.java
  89. 9 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  90. 52 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  91. 45 0
      fs-service/src/main/java/com/fs/his/service/impl/FsRedPacketServiceImpl.java
  92. 44 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  93. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  94. 1 1
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java
  95. 47 0
      fs-service/src/main/java/com/fs/hisStore/enums/CompanyEnum.java
  96. 42 10
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  97. 5 5
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  98. 161 0
      fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java
  99. 3 0
      fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java
  100. 3 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java

+ 0 - 1
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',

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

@@ -5,10 +5,7 @@ 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;
 
@@ -25,9 +22,41 @@ public class StatisticManageController {
     @Resource
     private IStatisticManageService statisticManageService;
 
+    /**
+     * 获取统计信息
+     * @param param
+     * @return
+     */
     @PostMapping("/statisticMain")
     public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
         Assert.notNull(param.getDimension(), "请选择统计维度");
         return R.ok().put("data", statisticManageService.statisticMain(param));
     }
+
+    /**
+     * 获取公司下拉
+     * @return
+     */
+    @GetMapping("/getSearchCompanyInfo")
+    public R getSearchCompanyInfo(){
+        return R.ok().put("data", statisticManageService.getSearchCompanyInfo());
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchDeptInfo")
+    public R getSearchDeptInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchDeptInfo(id));
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchUserInfo")
+    public R getSearchUserInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", statisticManageService.getSearchUserInfo(id));
+    }
 }

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

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

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

@@ -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")

+ 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

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

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

@@ -29,10 +29,10 @@ public class FsCompanyTask {
     private ICompanyService companyService;
 
     @Autowired
-    private CompanyMoneyLogsMapper moneyLogsMapper;
+    private RedisTemplate<String, Object> redisTemplate;
 
     @Autowired
-    private RedisTemplate<String, Object> redisTemplate;
+    private CompanyMoneyLogsMapper moneyLogsMapper;
 
     public void refreshCompanyMoney() {
         LocalDateTime now = LocalDateTime.now();
@@ -54,59 +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);
-                       }
+                // 新增账户流水
+                // 直接循环保存,由于是定时任务执行,暂时不优化
+                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());
-
-                    // 保存公司余额
-                    if(!collect.isEmpty()){
-                        companyService.batchUpdateCompany(collect);
-                    }
+                // 使用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);
                 }
+
             }
+        }
 
     }
 }

+ 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

+ 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;
@@ -104,6 +105,10 @@ public class CompanyUserController extends BaseController {
     @Autowired
     IQwCompanyService iQwCompanyService;
 
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+
     @Autowired
     private IQwUserService qwUserService;
 
@@ -491,6 +496,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);
     }
 

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

@@ -138,6 +138,8 @@ public class Company extends BaseEntity
     /**经销售归属*/
     private String companyBelongOwner;
 
+    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);
 

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

@@ -93,4 +93,16 @@ public interface StatisticManageMapper {
      */
     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);
+
 }

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

@@ -175,9 +175,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);
 

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

@@ -1,7 +1,10 @@
 package com.fs.company.service;
 
+import com.fs.company.domain.CompanyDeptUserInfo;
 import com.fs.statis.param.ComprehensiveStatisticsParam;
 
+import java.util.List;
+
 /**
  * @description:
  * @author: Guos
@@ -16,8 +19,26 @@ public interface IStatisticManageService {
      */
     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();
+
 }

+ 207 - 20
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -21,10 +21,8 @@ import com.fs.company.param.CompanyParam;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyProfitService;
 import com.fs.company.service.ICompanyRoleService;
-import com.fs.company.vo.CompanyCrmVO;
-import com.fs.company.vo.CompanyNameVO;
-import com.fs.company.vo.CompanyVO;
-import com.fs.company.vo.DeptDataVO;
+import com.fs.company.vo.*;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
 import com.fs.his.domain.FsStoreOrder;
@@ -108,6 +106,12 @@ public class CompanyServiceImpl implements ICompanyService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+
     @Autowired
     private TransactionTemplate transactionTemplate;
 
@@ -679,7 +683,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
@@ -1361,12 +1374,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());
@@ -1374,19 +1387,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;
@@ -1397,9 +1400,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) {

+ 27 - 3
fs-service/src/main/java/com/fs/company/service/impl/StatisticManageServiceImpl.java

@@ -51,6 +51,32 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
         return null;
     }
 
+    /**
+     * 获取搜索公司信息
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchCompanyInfo(){
+        return statisticManageMapper.getCompanyInfo();
+    }
+
+    /**
+     * 根据公司id获取部门下拉搜索信息
+     * @param id
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchDeptInfo(Long id){
+        return statisticManageMapper.getSearchDeptInfo(id);
+    }
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @param id
+     */
+    @Override
+    public List<CompanyDeptUserInfo> getSearchUserInfo(Long id){
+        return statisticManageMapper.getSearchUserInfo(id);
+    }
+
     /**
      * 获取个人统计数据
      * @param dimension 维度
@@ -85,9 +111,6 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
         return statisticManageMapper.getStatisticNumByCompanyId(startTime, endTime, deptId);
     }
 
-
-
-
     /**
      * 执行定时任务
      * 还需要考虑在统计部门数据时,部门下面没有用户,某个时候这个部门下面又加入新的用户了,这个时候就会出现数据偏差
@@ -97,6 +120,7 @@ public class StatisticManageServiceImpl implements IStatisticManageService {
         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

+ 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

@@ -97,4 +97,6 @@ public class CompanyVO implements Serializable
     private Long deptId;
     /**经销售归属*/
     private String companyBelongOwner;
+
+    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);

+ 9 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -570,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

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

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

+ 179 - 184
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,175 +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());
-//                }
-//                // 添加红包记录
-//                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("系统异常,请稍后重试");
-                    }
-
-                    // 预扣减金额
-                    BigDecimal newMoney = originalMoney.subtract(amount);
+            // 打开红包扣减功能
+            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("发送红包失败,请联系管理员");
+                }
+                String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
 
-                    if (originalMoney.compareTo(BigDecimal.ZERO) <= 0 || newMoney.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("系统繁忙,请稍后重试");
                     }
-
-                    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();
             // 添加红包记录
@@ -2703,10 +2702,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

@@ -244,6 +244,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;
 }

+ 47 - 0
fs-service/src/main/java/com/fs/hisStore/enums/CompanyEnum.java

@@ -0,0 +1,47 @@
+package com.fs.hisStore.enums;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 设置单店铺的商城
+ */
+public enum CompanyEnum {
+    YISHANYUAN("益善缘"),
+    KANGNIAN_TANG("康年堂"),
+    SIFU_TANG("四福堂"),
+    NMG_MYT("内蒙古一贴"),
+    CQ_TYT("重庆泰医堂"),
+    CHUNZHENG_TANG("纯正堂");
+
+    private final String companyName;
+
+    CompanyEnum(String companyName) {
+        this.companyName = companyName;
+    }
+
+    public String getCompanyName() {
+        return companyName;
+    }
+
+    /**
+     * 静态集合,避免每次调用都重新创建
+     */
+    private static final Set<String> COMPANY_NAMES = Collections.unmodifiableSet(
+            Arrays.stream(values())
+                    .map(CompanyEnum::getCompanyName)
+                    .collect(Collectors.toSet())
+    );
+
+    /**
+     * 比较是否存在
+     *
+     * @param companyName
+     * @return
+     */
+    public static boolean contains(String companyName) {
+        return COMPANY_NAMES.contains(companyName);
+    }
+}

+ 42 - 10
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -749,7 +749,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if(ObjectUtil.isEmpty(config.getOrderAttribution())
                     ||!config.getOrderAttribution().equals(1)){
                 if(param.getCompanyUserId()!=null){
-                    if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&fsuser.getCompanyUserId()!=param.getCompanyUserId()){
+                    if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&!fsuser.getCompanyUserId().equals(param.getCompanyUserId())){
                         CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
                         return R.error(String.format("请联系%s销售进行购买商品!",companyUser.getNickName()));
                     }else {
@@ -983,17 +983,21 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             redisCache.setCacheObject("orderAmount:" + storeOrder.getId(), storeOrder.getPayMoney(), 24, TimeUnit.HOURS);//物流代收自定义金额
             //删除推荐订单KEY
             String createOrderKey = param.getCreateOrderKey();
-            if (StringUtils.isNotEmpty(createOrderKey)) {
-                if (config.getIsBrushOrders() == null || !(config.getIsBrushOrders() && param.getCompanyUserId() != null)) {//未开启刷单
-                    redisCache.deleteObject("createOrderKey:" + createOrderKey);
-                    redisCache.deleteObject("orderCarts:" + createOrderKey);
-                    redisCache.deleteObject("createOrderMoney:" + createOrderKey);
-                }
-
-                //货到付款自定义金额 key改为id存储
+            if("鸿森堂".equals(cloudHostProper.getCompanyName())){
                 BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + createOrderKey);
-                redisCache.deleteObject("createOrderAmount:" + createOrderKey);
                 redisCache.setCacheObject("orderAmount:" + storeOrder.getId(), amount, 24, TimeUnit.HOURS);//物流代收自定义金额
+            }else {
+                if (StringUtils.isNotEmpty(createOrderKey)) {
+                    if (config.getIsBrushOrders() == null || !(config.getIsBrushOrders() && param.getCompanyUserId() != null)) {//未开启刷单
+                        redisCache.deleteObject("createOrderKey:" + createOrderKey);
+                        redisCache.deleteObject("orderCarts:" + createOrderKey);
+                        redisCache.deleteObject("createOrderMoney:" + createOrderKey);
+                    }
+
+                    //货到付款自定义金额 key改为id存储
+                    BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + createOrderKey);
+                    redisCache.deleteObject("createOrderAmount:" + createOrderKey);
+                }
             }
             return R.ok().put("order", storeOrder).put("payLimitTime", payLimitTime);
         } else {
@@ -3843,6 +3847,31 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     return R.error("导入失败,订单号为" + dto.getOrderNumber() + "物流编码异常,请核对后再导入!");
                 }
                 list.add(dto);
+
+
+                //批量导入物流单号 存在快递鸟时订阅下方参数,进行调整
+                if(CloudHostUtils.hasCloudHostName("内蒙古一贴")){
+                    FsStoreOrderScrm order = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(dto.getOrderNumber());
+                    //订阅物流回调
+                    String lastFourNumber = "";
+                    if (dto.getDeliverySn().equals(ShipperCodeEnum.SF.getValue())) {
+                        lastFourNumber = order.getUserPhone();
+                        if (lastFourNumber.length() == 11) {
+                            lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                        }
+                    }
+                    expressService.subscribeEspress(order.getOrderCode(), order.getDeliverySn(), order.getDeliveryId(), lastFourNumber);
+
+                    TemplateBean templateBean = TemplateBean.builder()
+                            .orderId(order.getId().toString())
+                            .orderCode(order.getOrderCode().toString())
+                            .deliveryId(order.getDeliveryId())
+                            .deliveryName(order.getDeliveryName())
+                            .userId(order.getUserId())
+                            .templateType(TemplateListenEnum.TYPE_2.getValue())
+                            .build();
+                    publisher.publishEvent(new TemplateEvent(this, templateBean));
+                }
             }
 
             //分批次处理
@@ -3863,6 +3892,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    public static void main(String[] args) {
+        String lastFourNumber = "15808582581";
+    }
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
     public R pay(FsStoreOrderPayParam param) {

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

@@ -28,6 +28,7 @@ import com.fs.his.vo.FsStoreProductListSVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.hisStore.domain.*;
+import com.fs.hisStore.enums.CompanyEnum;
 import com.fs.hisStore.mapper.*;
 import com.fs.hisStore.utils.StoreAuditLogUtil;
 import com.fs.statis.dto.ModifyMoreDTO;
@@ -338,7 +339,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         product.setStoreId(param.getStoreId());
         product.setIsDrug(param.getIsDrug().toString());
         //校验店铺资质信息
-        if(!("益善缘".equals(cloudHostProper.getCompanyName())) && !("康年堂".equals(cloudHostProper.getCompanyName())) && !("纯正堂".equals(cloudHostProper.getCompanyName()))){
+        if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
             //获取店铺
             FsStoreScrm store = fsStoreScrmService.selectFsStoreByStoreId(product.getStoreId());
             if(store == null || 1 != store.getStatus()){
@@ -349,9 +350,9 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                     case 1://非处方
                         break;
                     case 2://处方
-                        if("".equals(store.getDrugLicense()) ||  LocalDate.now().isBefore(store.getDrugLicenseExpiryEnd())){
-                            return R.error("店铺药品资质为空或已过期,请完善后再添加");
-                        }
+//                        if("".equals(store.getDrugLicense()) ||  LocalDate.now().isBefore(store.getDrugLicenseExpiryEnd())){
+//                            return R.error("店铺药品资质为空或已过期,请完善后再添加");
+//                        }
                         break;
                     case 3://食品
                         if("".equals(store.getFoodLicense()) ||  LocalDate.now().isBefore(store.getFoodLicenseExpiryEnd())){
@@ -416,7 +417,6 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         return R.ok();
     }
 
-
     private void addProductAttr(Long productId, List<ProductArrtDTO> items, List<FsStoreProductAttrValueScrm> values){
         //清空attr
         fsStoreProductAttrMapper.clear(productId);

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

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

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

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

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

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

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików