Browse Source

Merge branch 'master' into bjcz_his_scrm

# Conflicts:
#	fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
#	fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
#	fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
#	fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
#	fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
#	fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
#	fs-user-app/src/main/resources/application.yml
吴树波 3 days ago
parent
commit
c37196cd27
97 changed files with 2513 additions and 371 deletions
  1. 11 6
      README.md
  2. 27 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java
  3. 41 0
      fs-admin/src/main/java/com/fs/course/controller/CourseRedPacketStatisticsController.java
  4. 3 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  5. 37 0
      fs-admin/src/main/java/com/fs/course/task/RedPacketLogsTask.java
  6. 200 156
      fs-admin/src/main/java/com/fs/his/task/Task.java
  7. 1 1
      fs-admin/src/main/resources/application.yml
  8. 43 9
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  9. 27 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  10. 7 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  11. 29 0
      fs-company/src/main/java/com/fs/company/utils/QwStatusEnum.java
  12. 2 1
      fs-company/src/main/resources/application.yml
  13. 1 1
      fs-doctor-app/src/main/resources/application.yml
  14. 29 1
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  15. 25 5
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  16. 11 0
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  17. 10 0
      fs-qw-task/src/main/java/com/fs/app/taskService/SyncQwExternalContactService.java
  18. 10 3
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  19. 78 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SyncQwExternalContactServiceImpl.java
  20. 1 1
      fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  21. 6 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  22. 59 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  23. 21 0
      fs-service/src/main/java/com/fs/company/vo/BatchUserRolesVO.java
  24. 4 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  25. 36 0
      fs-service/src/main/java/com/fs/course/dto/CourseRedPacketStatisticsDTO.java
  26. 4 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  27. 7 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  28. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  29. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  30. 38 0
      fs-service/src/main/java/com/fs/course/param/CourseRedPacketStatisticsParam.java
  31. 11 0
      fs-service/src/main/java/com/fs/course/service/CourseRedPacketStatisticsService.java
  32. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java
  33. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  34. 28 0
      fs-service/src/main/java/com/fs/course/service/impl/CourseRedPacketStatisticsServiceImpl.java
  35. 5 6
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  36. 71 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  37. 0 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  38. 55 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  39. 96 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  40. 7 3
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  41. 2 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  42. 28 0
      fs-service/src/main/java/com/fs/his/dto/FsStoreOrderAmountScrmStatsQueryDto.java
  43. 28 0
      fs-service/src/main/java/com/fs/his/dto/FsStoreOrderAmountStatsQueryDto.java
  44. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  45. 6 4
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  46. 5 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  47. 4 4
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  48. 198 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  49. 156 61
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  50. 44 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAmountScrmStatsVo.java
  51. 44 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAmountStatsVo.java
  52. 5 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  53. 9 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  54. 88 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  55. 4 0
      fs-service/src/main/java/com/fs/qw/domain/QwContactWay.java
  56. 6 0
      fs-service/src/main/java/com/fs/qw/domain/QwFriendWelcome.java
  57. 13 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  58. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  59. 2 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserVideoMapper.java
  60. 7 0
      fs-service/src/main/java/com/fs/qw/param/QwFriendWelcomeParam.java
  61. 7 0
      fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java
  62. 2 0
      fs-service/src/main/java/com/fs/qw/param/QwWatchLogStatisticsListParam.java
  63. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserVideoService.java
  64. 3 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwContactWayServiceImpl.java
  65. 34 12
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  66. 4 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVideoServiceImpl.java
  67. 4 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  68. 12 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  69. 6 1
      fs-service/src/main/java/com/fs/qw/vo/QwFriendWelcomeVO.java
  70. 3 2
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java
  71. 8 6
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  72. 1 0
      fs-service/src/main/java/com/fs/sop/vo/SopUserLogsVo.java
  73. 1 1
      fs-service/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java
  74. 10 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java
  75. 94 0
      fs-service/src/main/resources/application-config-dev-jnlzjk.yml
  76. 1 1
      fs-service/src/main/resources/application-config-druid-fby.yml
  77. 2 2
      fs-service/src/main/resources/application-config-druid-hcl.yml
  78. 3 3
      fs-service/src/main/resources/application-config-druid-jnlzjk.yml
  79. 94 0
      fs-service/src/main/resources/application-config-druid-jnsyj.yml
  80. 153 0
      fs-service/src/main/resources/application-dev-jnlzjk.yml
  81. 1 1
      fs-service/src/main/resources/application-druid-cqtyt-test.yml
  82. 10 13
      fs-service/src/main/resources/application-druid-cqtyt.yml
  83. 1 1
      fs-service/src/main/resources/application-druid-hcl.yml
  84. 1 1
      fs-service/src/main/resources/application-druid-hdt.yml
  85. 1 1
      fs-service/src/main/resources/application-druid-jnmy-test.yml
  86. 162 0
      fs-service/src/main/resources/application-druid-jnsyj.yml
  87. 3 1
      fs-service/src/main/resources/application-druid-sxjz.yml
  88. 38 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  89. 30 0
      fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml
  90. 4 3
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  91. 34 0
      fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml
  92. 28 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  93. 33 31
      fs-service/src/main/resources/mapper/hisStore/FsUserScrmMapper.xml
  94. 5 2
      fs-service/src/main/resources/mapper/qw/QwContactWayMapper.xml
  95. 6 1
      fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml
  96. 2 2
      fs-service/src/main/resources/mapper/qw/QwWatchLogMapper.xml
  97. 3 1
      fs-user-app/src/main/resources/application.yml

+ 11 - 6
README.md

@@ -3,21 +3,26 @@
 #### 介绍
 问诊平台
 
-#### 软件架构
-软件架构说明
+#### 软件模块说明
+
+| 模块名称         | 模块描述         | 对应前端项目                  |
+|--------------|--------------|-------------------------|
+| fs-admin     | 总后台服务        | ylrz_his_scrm_adminUI   |
+| fs-company   | 销售端          | ylrz_his_scrm_companyUI |
+| fs-user-app  | 微信小程序端       | 对应某个微信小程序(前端蒲瑶清楚)       |
+| fs-framework | 主要依赖包,核心包    | /                       |
+| fs-service   | 所有的链接配置文件都在里面 | /                       |
 
 
 #### 安装教程
 
 1.  xxxx
 2.  xxxx
-3.  xxxx
 
 #### 使用说明
 
-1.  xxxx
-2.  xxxx
-3.  xxxx
+1.  注意调整Memory的大小,以及堆内存大小
+2.  对于maven仓库缺少的jar包引用,需要拷贝现有的文件(拷贝后依然出现错误,直接删除错误包下的_remote.repositories文件)。
 
 #### 参与贡献
 

+ 27 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java

@@ -19,9 +19,14 @@ import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.service.ICrmCustomerVisitService;
 import com.fs.crm.vo.CrmCustomerStatisticsVO;
 import com.fs.crm.vo.CrmCustomerVisitStatisticsVO;
+import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
+import com.fs.his.dto.FsStoreOrderAmountStatsQueryDto;
 import com.fs.his.service.IFsStoreAfterSalesService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
+import com.fs.his.vo.FsStoreOrderAmountStatsVo;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -67,6 +72,10 @@ public class CompanyStatisticsController extends BaseController
     private ICrmCustomerService crmCustomerService;
     @Autowired
     private ICrmCustomerVisitService crmCustomerVisitService;
+
+    //app商城订单接口Service
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
     @GetMapping("/storeOrder")
     public R storeOrder(FsStoreStatisticsParam param)
     {
@@ -724,4 +733,22 @@ public class CompanyStatisticsController extends BaseController
         return util.exportExcel(qwIpadTotalVos, "visit");
     }
 
+    /**
+     * 获取互联网医院订单统计数据
+     * */
+    @GetMapping("/hisOrderCountStats")
+    public AjaxResult getHisOrderCount(FsStoreOrderAmountStatsQueryDto statsQueryDto){
+        FsStoreOrderAmountStatsVo fsStoreOrderAmountStatsVo = storeOrderService.selectFsStoreOrderAmountStats(statsQueryDto);
+        return AjaxResult.success(fsStoreOrderAmountStatsVo);
+    }
+
+    /**
+     * 获取App商城订单统计数据
+     * */
+    @GetMapping("/appOrderCountStats")
+    public AjaxResult getAppOrderCount(FsStoreOrderAmountScrmStatsQueryDto statsQueryDto){
+        FsStoreOrderAmountScrmStatsVo scrmStatsVo = fsStoreOrderScrmService.selectFsStoreOrderAmountScrmStats(statsQueryDto);
+        return AjaxResult.success(scrmStatsVo);
+    }
+
 }

+ 41 - 0
fs-admin/src/main/java/com/fs/course/controller/CourseRedPacketStatisticsController.java

@@ -0,0 +1,41 @@
+package com.fs.course.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+import com.fs.course.service.CourseRedPacketStatisticsService;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiModelProperty;
+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;
+
+import java.util.List;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/course/courseRedPacketStatistics")
+public class CourseRedPacketStatisticsController extends BaseController {
+
+    @Autowired
+    private CourseRedPacketStatisticsService courseRedPacketStatisticsService;
+
+    @ApiModelProperty("看客红包发送统计")
+    @GetMapping("/list")
+    public R list(CourseRedPacketStatisticsParam param) {
+
+        startPage();
+        // 看客红包发送统计
+        List<CourseRedPacketStatisticsDTO> list = courseRedPacketStatisticsService.statistics(param);
+
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+}

+ 3 - 6
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -103,13 +103,10 @@ public class FsCourseWatchLogController extends BaseController
         if (param.getSTime()==null||param.getETime()==null){
             throw new CustomException("必须选择开始时间和结束时间!");
         }
+        startPage();
         List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONew(param);
-        TableDataInfo rspData = new TableDataInfo();
-        rspData.setCode(HttpStatus.SUCCESS);
-        rspData.setMsg("查询成功");
-        rspData.setRows(list);
-        rspData.setTotal(fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONewCount(param));
-        return rspData;
+
+        return getDataTable(list);
     }
 
     /**

+ 37 - 0
fs-admin/src/main/java/com/fs/course/task/RedPacketLogsTask.java

@@ -0,0 +1,37 @@
+package com.fs.course.task;
+
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.course.service.IFsCourseRedPacketLogService;
+import com.fs.course.service.impl.FsCourseRedPacketLogServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * @description: 微信红包转账回调部分没有接收到,主动自己去查询
+ * @author: Xgb
+ * @createDate: 2025/10/15
+ * @version: 1.0
+ */
+@Component("redPacketLogsTask")
+public class RedPacketLogsTask {
+
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    /**
+     * @Description: 查询微信红包转账结果 每10分钟查询上一个10分钟区间的红包转账结果
+     * (定时调取失败 可以启动fs-qw-task 中的 CommonController queryRedPacketResult 手动调取) 仅给内部人员使用
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/15 16:32
+     */
+    public void queryRedPacketResult() {
+
+        // 查询RedPacketLog表,
+        fsCourseRedPacketLogService.queryRedPacketResult(null, null);
+    }
+
+}

+ 200 - 156
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -29,9 +29,13 @@ import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
 import com.fs.fastGpt.domain.FastgptEventLogTotal;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
+import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
+import com.fs.fastgptApi.util.AudioUtils;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
@@ -54,6 +58,8 @@ import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.google.gson.Gson;
@@ -65,9 +71,12 @@ import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Component("task")
@@ -90,7 +99,7 @@ public class Task {
     private IFsFollowService fsFollowService;
     @Autowired
     private IFsStoreAfterSalesService fsStoreAfterSalesService;
-//    @Autowired
+    //    @Autowired
 //    IErpOrderService erpOrderService;
     @Autowired
     FsIntegralOrderMapper integralOrderMapper;
@@ -131,7 +140,7 @@ public class Task {
     FsPackageOrderServiceImpl packageOrderService;
     @Autowired
     private IFsStoreOrderLogsService fsStoreOrderLogsService;
-    org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
+    org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
     IFsDoctorService doctorService;
     @Autowired
@@ -177,6 +186,51 @@ public class Task {
     @Autowired
     private IFastgptEventLogTotalService fastgptEventLogTotalService;
 
+    @Autowired
+    private FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    @Autowired
+    private IQwSopTempVoiceService qwSopTempVoiceService;
+
+    public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
+
+    /**
+     * 一键生成语音定时任务
+     */
+    public void ConsumerSopTempVoice() {
+        try {
+            Long newCompanyUserId = redisCache.popVoiceKey(SOP_TEMP_VOICE_KEY);
+            if (newCompanyUserId != null) {
+                List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + newCompanyUserId);
+                if (sopTempVoices != null && !sopTempVoices.isEmpty()) {
+                    try {
+                        for (QwSopTempVoice qwSopTempVoice : sopTempVoices) {
+                            try {
+                                AudioVO audioVO = new AudioVO();
+                                List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+                                audioVO = AudioUtils.createUserUrlAndUrl(homos, qwSopTempVoice.getCompanyUserId(), qwSopTempVoice.getVoiceTxt());
+                                if (audioVO != null && audioVO.getWavUrl() != null && audioVO.getUrl() != null) {
+                                    qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                                    qwSopTempVoice.setUserVoiceUrl(audioVO.getWavUrl());
+                                    qwSopTempVoice.setDuration(audioVO.getDuration());
+                                    qwSopTempVoice.setRecordType(1);
+                                    qwSopTempVoiceService.updateQwSopTempVoice(qwSopTempVoice);
+                                }
+                            } catch (Exception e) {
+
+                            }
+                        }
+                    } finally {
+                        redisCache.deleteObject(SOP_TEMP_VOICE_KEY + ":" + newCompanyUserId);
+                    }
+                }
+            } else {
+                log.info("没有需要生成的语音");
+            }
+        } catch (Exception e) {
+            log.error("生成语音定时任务执行异常", e);
+        }
+    }
+
     //统计ai事件埋点
     public void eventLogTotals() {
         // 判断是否是凌晨 00:00 - 00:59
@@ -241,7 +295,7 @@ public class Task {
                         FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
                         eventLogTotal.setId(info.getId());
                         eventLogTotal.setCount(newCount);
-                        if(!processedKeys.contains(uniqueKey)) {
+                        if (!processedKeys.contains(uniqueKey)) {
                             toUpdateList.add(eventLogTotal);
                             // 标记为已处理
                             processedKeys.add(uniqueKey);
@@ -249,7 +303,7 @@ public class Task {
                     }
                 } else {
                     total.setStatTime(dateTime);
-                    if(!processedKeys.contains(uniqueKey)) {
+                    if (!processedKeys.contains(uniqueKey)) {
                         toInsertList.add(total);
                         // 标记为已处理
                         processedKeys.add(uniqueKey);
@@ -352,7 +406,7 @@ public class Task {
                         FastgptEventLogTotal eventLogTotalNew = new FastgptEventLogTotal();
                         eventLogTotalNew.setId(info.getId());
                         eventLogTotalNew.setCount(totalCount);
-                        if(!processedKeys.contains(uniqueKey)){
+                        if (!processedKeys.contains(uniqueKey)) {
                             toUpdateList.add(eventLogTotalNew);
                             // 标记为已处理
                             processedKeys.add(uniqueKey);
@@ -369,7 +423,7 @@ public class Task {
                     eventLogTotal.setQwUserId(tokenLog.getQwUserId());
                     eventLogTotal.setStatTime(dateTime);
 
-                    if(!processedKeys.contains(uniqueKey)) {
+                    if (!processedKeys.contains(uniqueKey)) {
                         toInsertList.add(eventLogTotal);
                         // 标记为已处理
                         processedKeys.add(uniqueKey);
@@ -394,23 +448,23 @@ public class Task {
     }
 
     //定时查询ipad主机使用情况,建议每天凌晨1点执行一次
-    public void totalIpadTask(){
+    public void totalIpadTask() {
         String dateTime = DateUtils.addDateDays(-1); // 昨天
         List<QwIpadTotalVo> qwIpadTotalVos = userService.selectCompanyByIpadStatusCount();
-        if(qwIpadTotalVos != null && !qwIpadTotalVos.isEmpty()){
+        if (qwIpadTotalVos != null && !qwIpadTotalVos.isEmpty()) {
             qwIpadTotalVos.forEach(qwIpadTotalVo ->
                     qwIpadTotalVo.setStatTime(dateTime)
             );
             int a = userService.insertQwIpadTotal(qwIpadTotalVos);
-            if(a == 0){
+            if (a == 0) {
                 log.error("插入ipad主机失败");
             }
-        }else{
+        } else {
             log.error("查询没有数据");
         }
     }
 
-    public void addQwUserName(){
+    public void addQwUserName() {
         QwCompany qwCompany = new QwCompany();
         List<QwCompany> companyList = qwCompanyService.selectQwCompanyList(qwCompany);
         for (QwCompany company : companyList) {
@@ -418,50 +472,56 @@ public class Task {
         }
     }
 
-    public void videoTranscode() throws Exception
-    {
+    public void videoTranscode() throws Exception {
 
         tencentCloudCosService.videoTranscode();
     }
 
-    public void updateUrl() throws Exception
-    {
+    public void updateUrl() throws Exception {
 
         tencentCloudCosService.updateUrl();
     }
-    public void addPrescribeImg() throws Exception
-    {
-       List<Long> ids= fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();
+
+    public void addPrescribeImg() throws Exception {
+        List<Long> ids = fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();
         for (Long id : ids) {
             System.out.println(id);
             fsPrescribeService.PrescribeStoreImg(id);
         }
     }
 
-    public void addQwWatchLog() throws Exception
-    {
+    public void addQwWatchLog() throws Exception {
+        LocalDateTime now = LocalDateTime.now().withSecond(0);
         fsCourseWatchLogService.addCourseWatchLogDay();
     }
 
 
-    public void transferLog() throws Exception
-    {
+    public void addQwWatchLogMinute(Integer minute) throws Exception{
+        log.info("定时更新看课111");
+        if(minute == null) minute = 30;
+        LocalDateTime now = LocalDateTime.now().withSecond(0);
+        LocalDateTime start = now.minusMinutes(minute);
+        LocalDateTime end = now.minusSeconds(1);
+        fsCourseWatchLogService.addCourseWatchLogDayMinute(start, end);
+    }
+
+    public void transferLog() throws Exception {
         qwExternalContactTransferLogService.updateQwExternalContactTransferLogByStatus();
 
 
     }
 
-    public void isArtificial() throws Exception
-    {
+    public void isArtificial() throws Exception {
         fastGptChatSessionMapper.updateFastGptChatSessionByIsReply();
 
     }
-    public void expirationQwAppCountWay(){
+
+    public void expirationQwAppCountWay() {
         qwAppContactWayService.expirationQwAppCountWay();
 
     }
-    public void sendOrderMsg() throws Exception
-    {
+
+    public void sendOrderMsg() throws Exception {
         List<FsStoreOrder> fsStoreOrders = fsStoreOrderMapper.selectStoreOrderIdByFollow();
         for (FsStoreOrder fsStoreOrder : fsStoreOrders) {
 
@@ -469,81 +529,77 @@ public class Task {
         }
 
     }
-    public void redPacketSubMoney() throws Exception
-    {
+
+    public void redPacketSubMoney() throws Exception {
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByCompany();
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
-            companyService.subtractCompanyMoney(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId());
+            companyService.subtractCompanyMoney(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId());
         }
     }
 
 
-    public void redPacketAddMoney() throws Exception
-    {
+    public void redPacketAddMoney() throws Exception {
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseAddRedPacketLogByCompany();
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
-            companyService.addRedPacketCompanyMoney(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId());
+            companyService.addRedPacketCompanyMoney(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId());
         }
     }
 
-    public void updateCompanyUserStatus()
-    {
+    public void updateCompanyUserStatus() {
         CompanyUser user = new CompanyUser();
         user.setStatus("0");
         user.setDelFlag("0");
         List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserList(user);
         for (CompanyUser companyUser : companyUsers) {
-            if(SecurityUtils.matchesPassword("123456", companyUser.getPassword())){
+            if (SecurityUtils.matchesPassword("123456", companyUser.getPassword())) {
                 companyUser.setStatus("1");
                 companyUserMapper.updateCompanyUser(companyUser);
-                logger.info("密码为123456 停用账号:"+companyUser.getUserId()+":"+companyUser.getNickName());
+                logger.info("密码为123456 停用账号:" + companyUser.getUserId() + ":" + companyUser.getNickName());
             }
         }
     }
-    public void couponStatus()
-    {
+
+    public void couponStatus() {
         fsUserCouponMapper.updateFsUserCouponStatusByLimtTime();
     }
 
     //每10秒执行一次
-    public void auditPrescribe()
-    {
+    public void auditPrescribe() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.inquiryConfig");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        boolean isAudit = (boolean)config.get("isAutoPrescribeAudit");
-        if (isAudit){
+        boolean isAudit = (boolean) config.get("isAutoPrescribeAudit");
+        if (isAudit) {
             fsPrescribeService.auditPrescribe();
         }
     }
 
-    public void deliveryOp()
-    {
+    public void deliveryOp() {
         IErpOrderService erpOrderService = getErpService();
         List<FsStoreOrder> orders = null;
-        if (erpOrderService == gyOrderService){
+        if (erpOrderService == gyOrderService) {
             orders = fsStoreOrderMapper.selectOmsOrderdeliveryOp();
-        } else if (erpOrderService == wdtOrderService || erpOrderService == dfOrderService || erpOrderService == jSTOrderService){
+        } else if (erpOrderService == wdtOrderService || erpOrderService == dfOrderService || erpOrderService == jSTOrderService) {
             orders = fsStoreOrderMapper.selectWdtOmsOrderdeliveryOp();
         }
 
 
-        for(FsStoreOrder order:orders){
+        for (FsStoreOrder order : orders) {
 
-            ErpOrderQueryRequert request=new ErpOrderQueryRequert();
+            ErpOrderQueryRequert request = new ErpOrderQueryRequert();
 
             request.setCode(order.getExtendOrderId());
-            if (erpOrderService != null){
-                ErpOrderQueryResponse response=erpOrderService.getOrder(request);
-                if (erpOrderService != dfOrderService){
-                    if(response.getOrders()!=null&&response.getOrders().size()>0){
-                        for(ErpOrderQuery orderQuery : response.getOrders()){
-                            if(orderQuery.getDeliverys()!=null&&orderQuery.getDeliverys().size()>0){
-                                for(ErpDeliverys delivery:orderQuery.getDeliverys()){
-                                    if(delivery.getDelivery()&& StringUtils.isNotEmpty(delivery.getMail_no())){
+            if (erpOrderService != null) {
+                ErpOrderQueryResponse response = erpOrderService.getOrder(request);
+                if (erpOrderService != dfOrderService) {
+                    if (response.getOrders() != null && response.getOrders().size() > 0) {
+                        for (ErpOrderQuery orderQuery : response.getOrders()) {
+                            if (orderQuery.getDeliverys() != null && orderQuery.getDeliverys().size() > 0) {
+                                for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                                    if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
                                         //更新商订单状态 删除REDIS
-                                        fsStoreOrderService.deliveryOrder(order.getOrderCode(),delivery.getMail_no(),delivery.getExpress_code(),delivery.getExpress_name());
-                                        redisCache.deleteObject("delivery"+":"+order.getExtendOrderId());
+                                        fsStoreOrderService.deliveryOrder(order.getOrderCode(), delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
+                                        redisCache.deleteObject("delivery" + ":" + order.getExtendOrderId());
                                     }
                                 }
 
@@ -556,19 +612,17 @@ public class Task {
         }
 
 
-
     }
 
 
-    public void getOrderDeliveryStatus()
-    {
+    public void getOrderDeliveryStatus() {
         IErpOrderService erpOrderService = getErpService();
         List<FsStoreOrder> orders = null;
-        if (erpOrderService !=null && erpOrderService == dfOrderService){
+        if (erpOrderService != null && erpOrderService == dfOrderService) {
             orders = fsStoreOrderMapper.selectShippedOrder();
-            if(orders!=null&& !orders.isEmpty()){
+            if (orders != null && !orders.isEmpty()) {
                 List<CompletableFuture<Void>> futures = new ArrayList<>();
-                for(FsStoreOrder order:orders){
+                for (FsStoreOrder order : orders) {
                     CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                         erpOrderService.getOrderDeliveryStatus(order);
                     });
@@ -579,15 +633,14 @@ public class Task {
         }
     }
 
-    public void CreateOmsAndHis()
-    {
+    public void CreateOmsAndHis() {
         List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
-        logger.info("推送订单id====>{}",omsList);
+        logger.info("推送订单id====>{}", omsList);
         for (Long l : omsList) {
             try {
                 fsStoreOrderService.createOmsOrder(l);
             } catch (Exception e) {
-                logger.error("推送订单异常:",e);
+                logger.error("推送订单异常:", e);
             }
         }
 //        List<Long> tuiOrderList = fsStoreOrderMapper.selectFsStoreOrderNoTuiOrder();
@@ -600,31 +653,30 @@ public class Task {
 //        }
     }
 
-    public void createFollow()
-    {
+    public void createFollow() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectStoreOrderIdByFollow();
         for (FsStoreOrder order : orders) {
             try {
 
                 fsStoreOrderService.addFsFollowByStoreOrder(order);
-            }catch (Exception e){
-                logger.info("创建随访错误:"+order);
+            } catch (Exception e) {
+                logger.info("创建随访错误:" + order);
             }
         }
     }
+
     @Autowired
     IFsStoreSubOrderService fsStoreSubOrderService;
 
 
-    public void puSubStoreOrder()
-    {
+    public void puSubStoreOrder() {
         List<Long> longs = fsStoreSubOrderService.selectFsStoreSubOrderByNoPush();
-        int i=0;
+        int i = 0;
         for (Long aLong : longs) {
             FsSubOrderResultVO fsSubOrderResultVO = fsStoreSubOrderService.TuiFsStoreSubOrderByStoreOrder(aLong);
-            if (fsSubOrderResultVO!=null&&fsSubOrderResultVO.getCode()!=null&&fsSubOrderResultVO.getCode()==1){
+            if (fsSubOrderResultVO != null && fsSubOrderResultVO.getCode() != null && fsSubOrderResultVO.getCode() == 1) {
                 i++;
-                if (i>65){
+                if (i > 65) {
                     return;
                 }
             }
@@ -632,14 +684,13 @@ public class Task {
         }
     }
 
-    public void refundOp()
-    {
-        List<FsStoreAfterSales> list=fsStoreAfterSalesService.selectFsStoreAfterSalesByDoAudit();
-        if(list!=null){
-            for(FsStoreAfterSales afterSales:list){
+    public void refundOp() {
+        List<FsStoreAfterSales> list = fsStoreAfterSalesService.selectFsStoreAfterSalesByDoAudit();
+        if (list != null) {
+            for (FsStoreAfterSales afterSales : list) {
                 try {
                     fsStoreAfterSalesService.auditing(afterSales);
-                }catch (Exception e){
+                } catch (Exception e) {
 
                 }
 
@@ -647,16 +698,17 @@ public class Task {
         }
 
     }
+
     public void isAfterSales() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        Integer storeAfterSalesDay = (Integer)config.get("storeAfterSalesDay");
+        Integer storeAfterSalesDay = (Integer) config.get("storeAfterSalesDay");
         List<FsStoreOrder> fsStoreOrders = fsStoreOrderMapper.selectFsStoreOrderNoIsAfterSales(storeAfterSalesDay);
         for (FsStoreOrder fsStoreOrder : fsStoreOrders) {
             try {
                 fsStoreOrderService.addIntegralAndShareByStoreOrder(fsStoreOrder);
-            }catch (Exception e) {
+            } catch (Exception e) {
                 logger.info("分账错误: " + fsStoreOrder.getOrderCode());
             }
         }
@@ -664,42 +716,41 @@ public class Task {
     }
 
 
-    public void integralOrderStatus(){
+    public void integralOrderStatus() {
         integralOrderMapper.updatePackageOrderStatusByDeliveryTime();
     }
 
-    public void packageStatus()
-    {
+    public void packageStatus() {
         fsPackageOrderMapper.updatePackageOrderStatusByFinishTime();
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        Integer unPayTime = (Integer)config.get("unPayTime");
+        Integer unPayTime = (Integer) config.get("unPayTime");
         fsPackageOrderMapper.updatePackageOrderStatusByStatus(unPayTime);
     }
 
-    public void endFollow(){
+    public void endFollow() {
         fsStoreOrderService.endFollow();
     }
 
 
-    public void erDelivery(){
+    public void erDelivery() {
         fsStoreOrderService.endDeliveryOrder();
     }
 
 
-
     @Autowired
     private ICompanyService companyService;
-    public void addCompanyMoney(){
+
+    public void addCompanyMoney() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectOrderIds();
-        for (FsStoreOrder order : orders){
+        for (FsStoreOrder order : orders) {
             CompanyMoneyLogs moneyLog1 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(), 5);
-            CompanyMoneyLogs moneyLog2 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(),3);
-            if (moneyLog1==null){
+            CompanyMoneyLogs moneyLog2 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(), 3);
+            if (moneyLog1 == null) {
                 companyService.subtractCompanyMoney(order);
             }
-            if (moneyLog2==null){
+            if (moneyLog2 == null) {
                 companyService.addCompanyMoney(order);
                 FsStoreOrder orderMap = new FsStoreOrder();
                 orderMap.setOrderId(order.getOrderId());
@@ -710,28 +761,26 @@ public class Task {
     }
 
     //每天执行一次
-    public void syncExpress()
-    {
-        List<Long> ids =fsStoreOrderMapper.selectSyncExpressIds();
+    public void syncExpress() {
+        List<Long> ids = fsStoreOrderMapper.selectSyncExpressIds();
         for (Long id : ids) {
             fsStoreOrderService.syncExpress(id);
         }
 
     }
 
-    public void refundCompanyMoney(){
+    public void refundCompanyMoney() {
         List<FsStoreOrder> list = fsStoreOrderMapper.selectOrders();
-        for (FsStoreOrder order : list ){
+        for (FsStoreOrder order : list) {
             companyService.refundCompanyMoney(order);
         }
     }
 
-    public void subIntegral()
-    {
+    public void subIntegral() {
         fsUserIntegralLogsService.subFsUserIntegralLogsByOrder5();
     }
 
-    public void finishInquiry(){
+    public void finishInquiry() {
         List<FsInquiryOrder> orders = inquiryOrderMapper.selectFsInquiryOrderByFinish();
         for (FsInquiryOrder order : orders) {
             // 订单已超过48小时,执行关闭操作
@@ -740,20 +789,20 @@ public class Task {
             param.setDoctorId(order.getDoctorId());
             try {
                 iFsInquiryOrderService.autoFinishOrder(param);
-            }catch (Exception e){
-                logger.info("订单已超过48小时关闭异常"+param);
+            } catch (Exception e) {
+                logger.info("订单已超过48小时关闭异常" + param);
             }
 
         }
     }
 
 
-    public void finishStoreOrderByXN(){
+    public void finishStoreOrderByXN() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectFinishStoreOrderByXN();
-        if (orders!=null&&orders.size()>0){
+        if (orders != null && orders.size() > 0) {
             for (FsStoreOrder o : orders) {
                 FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(o.getOrderId());
-                if (order.getStatus()!= 2) {
+                if (order.getStatus() != 2) {
                     continue;
                 }
                 FsStoreOrder o1 = new FsStoreOrder();
@@ -764,20 +813,20 @@ public class Task {
                 int i = fsStoreOrderMapper.updateFsStoreOrder(o1);
                 fsStoreOrderLogsService.create(order.getOrderId(), FsStoreOrderLogEnum.FINISH_ORDER.getValue(),
                         FsStoreOrderLogEnum.FINISH_ORDER.getDesc());
-                if (order.getCompanyId()!=null&&order.getTuiMoneyStatus()==0&&order.getPayType()==1){
+                if (order.getCompanyId() != null && order.getTuiMoneyStatus() == 0 && order.getPayType() == 1) {
                     companyService.addCompanyMoney(order);
                 }
             }
         }
     }
 
-    public void inquirySendSms(){
+    public void inquirySendSms() {
         List<FsInquiryOrder> orders = inquiryOrderMapper.selectFsInquiryOrderBySendSms();
-        for (FsInquiryOrder order : orders){
-            FsInquiryOrderPatientDTO patientDTO = JSON.parseObject(order.getPatientJson(),FsInquiryOrderPatientDTO.class);
-            if (patientDTO!=null&&patientDTO.getPatientName()!=null){
+        for (FsInquiryOrder order : orders) {
+            FsInquiryOrderPatientDTO patientDTO = JSON.parseObject(order.getPatientJson(), FsInquiryOrderPatientDTO.class);
+            if (patientDTO != null && patientDTO.getPatientName() != null) {
                 FsUser fsUser = fsUserMapper.selectFsUserByUserId(order.getUserId());
-                if (fsUser!=null&&fsUser.getPhone()!=null){
+                if (fsUser != null && fsUser.getPhone() != null) {
                     smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "2");
                     order.setIsSendSms(1);
                     inquiryOrderMapper.updateFsInquiryOrder(order);
@@ -788,9 +837,9 @@ public class Task {
     }
 
     //处理30天问题件
-    public void clearProblemOrder(){
+    public void clearProblemOrder() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectFsStoreOrderByProblemOrder();
-        for (FsStoreOrder order : orders){
+        for (FsStoreOrder order : orders) {
             FsStoreOrder map = new FsStoreOrder();
             map.setStatus(4);
             map.setOrderId(order.getOrderId());
@@ -801,14 +850,14 @@ public class Task {
 
 
     //30天无通话记录回收坐席
-    public void recoverCompanyCaller(){
+    public void recoverCompanyCaller() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         StoreConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), StoreConfig.class);
         Integer days = fsPayConfig.getStoreCall();
         List<CompanyVoiceCaller> list = companyVoiceCallerMapper.selectCompanyVoiceCallerByRecover(days);
-        for (CompanyVoiceCaller caller : list){
-            Long count = companyVoiceLogsMapper.selectCompanyVoiceLogsCountByCallerNo(caller.getCallerNo(),caller.getBindTime());
-            if (count==0){
+        for (CompanyVoiceCaller caller : list) {
+            Long count = companyVoiceLogsMapper.selectCompanyVoiceLogsCountByCallerNo(caller.getCallerNo(), caller.getBindTime());
+            if (count == 0) {
                 caller.setCompanyId(0l);
                 caller.setCompanyUserId(0l);
                 caller.setMobile("");
@@ -821,32 +870,27 @@ public class Task {
 
 
     public void tb() {
-        packageOrderService.payConfirm("", "1780763211956486144", "1075999515888117190", "14", 1,null,null);
+        packageOrderService.payConfirm("", "1780763211956486144", "1075999515888117190", "14", 1, null, null);
     }
 
 
-
-
-
-
-
-    public void addSend(){
-       String userId="4048905872";
-       String doctorId="147";
-        String storeOrderId="470920";
-        String followId="1062986";
+    public void addSend() {
+        String userId = "4048905872";
+        String doctorId = "147";
+        String storeOrderId = "470920";
+        String followId = "1062986";
         //发送给用户
-        MsgDTO msgDTO=new MsgDTO();
-        MsgCustomDTO customDTO=new MsgCustomDTO();
+        MsgDTO msgDTO = new MsgDTO();
+        MsgCustomDTO customDTO = new MsgCustomDTO();
         customDTO.setType("startDrugReport");
         customDTO.setImType(2);
         customDTO.setOrderId(storeOrderId);
         customDTO.setFollowId(followId);
         msgDTO.setCloudCustomData(JSONUtil.toJsonStr(customDTO));
-        msgDTO.setFrom_Account("U-"+userId);
-        msgDTO.setTo_Account("D-"+doctorId);
-        List<MsgDataDTO> msgs=new ArrayList<>();
-        MsgDataDTO msg=new MsgDataDTO();
+        msgDTO.setFrom_Account("U-" + userId);
+        msgDTO.setTo_Account("D-" + doctorId);
+        List<MsgDataDTO> msgs = new ArrayList<>();
+        MsgDataDTO msg = new MsgDataDTO();
         msg.setMsgType("TIMTextElem");
         msg.setMsgContent(new MsgDataFormatDTO("您好"));
         msgs.add(msg);
@@ -1125,7 +1169,7 @@ public class Task {
 //                iFsPackageOrderService.updateFsPackageOrder(order);
 //                num++;
 //            }
-////
+    /// /
 //        }
 //
 //        logger.info("所有订单同步完成:"+num);
@@ -1201,7 +1245,7 @@ public class Task {
 //}
 
 
-        //同步流水
+    //同步流水
 //    public void task(){
 //        CompanyMoneyLogs item = moneyLogsMapper.selectCompanyMoneyLogsById(248884L);
 //        String logsId=item.getLogsId().toString();
@@ -1304,27 +1348,27 @@ public class Task {
     private IErpOrderService getErpService() {
         FsSysConfig sysConfig = configUtil.getSysConfig();
         Integer erpOpen = sysConfig.getErpOpen();
-        if (erpOpen != null && erpOpen == 1){
+        if (erpOpen != null && erpOpen == 1) {
             //判断erp类型
             Integer erpType = sysConfig.getErpType();
-            if (erpType != null){
+            if (erpType != null) {
                 IErpOrderService erpOrderService = null;
-                if (erpType == 1){
+                if (erpType == 1) {
                     //管易
-                    erpOrderService =  gyOrderService;
-                } else if (erpType == 2){
+                    erpOrderService = gyOrderService;
+                } else if (erpType == 2) {
                     //旺店通
-                    erpOrderService =  wdtOrderService;
-                } else if (erpType == 3){
+                    erpOrderService = wdtOrderService;
+                } else if (erpType == 3) {
                     //
-                    erpOrderService =  hzOMSErpOrderService;
-                } else if (erpType == 4){
+                    erpOrderService = hzOMSErpOrderService;
+                } else if (erpType == 4) {
                     //代服
-                    erpOrderService =  dfOrderService;
-                }else if(erpType == 5){
-                    erpOrderService=jSTOrderService;
-                }else if(erpType == 6){
-                    erpOrderService=k9OrderService;
+                    erpOrderService = dfOrderService;
+                } else if (erpType == 5) {
+                    erpOrderService = jSTOrderService;
+                } else if (erpType == 6) {
+                    erpOrderService = k9OrderService;
                 }
                 return erpOrderService;
 

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

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

+ 43 - 9
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -22,6 +22,8 @@ import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.service.*;
 import com.fs.company.utils.DomainUtil;
+import com.fs.company.utils.QwStatusEnum;
+import com.fs.company.vo.BatchUserRolesVO;
 import com.fs.company.vo.CompanyUserImportVO;
 import com.fs.company.vo.CompanyUserQwListVO;
 import com.fs.company.vo.CompanyUserVO;
@@ -35,6 +37,7 @@ import com.fs.hisStore.vo.FsStoreProductExportVO;
 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;
@@ -46,6 +49,7 @@ 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;
@@ -58,13 +62,10 @@ import java.util.stream.Collectors;
 
 /**
  * 用户信息
- *
-
  */
 @RestController
 @RequestMapping("/company/user")
-public class CompanyUserController extends BaseController
-{
+public class CompanyUserController extends BaseController {
 
     @Autowired
     private ICompanyRoleService roleService;
@@ -77,19 +78,28 @@ public class CompanyUserController extends BaseController
 
     @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;
+
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
 
     /**
@@ -123,24 +133,34 @@ public class CompanyUserController extends BaseController
         return getDataTable(list);
     }
     @GetMapping("/qwList")
-    public TableDataInfo qwList(CompanyUserQwParam user)
-    {
+    public TableDataInfo qwList(CompanyUserQwParam user) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         user.setCompanyId(loginUser.getCompany().getCompanyId());
         startPage();
         List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
         for (CompanyUserQwListVO companyUserQwListVO : list) {
-             CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(),companyUserQwListVO.getUserId());
-            if (ObjectUtil.isEmpty(companyUserDelayTime)){
+            CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+            if (ObjectUtil.isEmpty(companyUserDelayTime)) {
                 String json = configService.selectConfigByKey("course.config");
                 CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
                 companyUserQwListVO.setSendDelayTime(config.getSendDelayTime());
-            }else {
+            } 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);
+                }
+            }
         }
         return getDataTable(list);
     }
+
     @Log(title = "用户管理导出", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('company:user:export')")
     @GetMapping("/export")
@@ -565,6 +585,20 @@ public class CompanyUserController extends BaseController
         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){

+ 27 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -21,6 +21,7 @@ import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.vo.CrmMyCustomerListQueryVO;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.qw.domain.QwContactWay;
 import com.fs.qw.domain.QwExternalContact;
@@ -36,6 +37,7 @@ import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -56,6 +58,7 @@ import static com.fs.his.utils.PhoneUtil.encryptPhone;
  * @author fs
  * @date 2024-06-20
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/externalContact")
 public class QwExternalContactController extends BaseController
@@ -272,10 +275,34 @@ public class QwExternalContactController extends BaseController
             if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
                 item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
             }
+            //获取用户下单次数
+            try {
+                fillOrderCount(item);
+            }catch (Exception e){
+                log.error("获取用户下单次数异常:{}",e.getMessage());
+            }
+
         });
 
         return getDataTable(list);
     }
+    /**
+     * 根据用户ID查询并设置下单次数(若用户不存在或ID无效,则设为0)
+     */
+    private void fillOrderCount(QwExternalContactVO item){
+        Long fsUserId = item.getFsUserId();
+        if (fsUserId==null){
+            item.setOrderCount(0L);
+            return;
+        }
+        FsUser fsUser = fsUserService.selectFsUserById(fsUserId);
+        if (fsUser == null) {
+            item.setOrderCount(0L);
+            return;
+        }
+        Long orderCount = fsUser.getOrderCount();
+        item.setOrderCount(orderCount != null ? orderCount : 0);
+    }
 
 
     @Log(title = "同步我的企业微信客户", businessType = BusinessType.INSERT)

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

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
@@ -392,6 +393,12 @@ public class QwUserController extends BaseController
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("1")){
+            qwUser.setCompanyUserId(loginUser.getUser().getUserId());
+        }else if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("2")){
+            qwUser.setDeptId(loginUser.getUser().getDeptId());
+            qwUser.setCorpId(null);
+        }
 
         List<QwUserVO> list = qwUserService.selectQwUserListVO(qwUser);
         return getDataTable(list);

+ 29 - 0
fs-company/src/main/java/com/fs/company/utils/QwStatusEnum.java

@@ -0,0 +1,29 @@
+package com.fs.company.utils;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/16 下午3:00
+ */
+public enum QwStatusEnum {
+
+    BOUND(1, "已绑定"),
+    UNBOUND(0, "未绑定");
+
+    private Integer code;
+
+    private String detail;
+
+    QwStatusEnum(Integer code, String detail) {
+        this.code = code;
+        this.detail = detail;
+    }
+
+    public int getCode(){
+        return this.code;
+    }
+
+    public String getDetail() {
+        return this.detail;
+    }
+}

+ 2 - 1
fs-company/src/main/resources/application.yml

@@ -4,11 +4,12 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: druid-jnmy-test
+#    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft
+    active: dev-jnlzjk
 #    active: dev-yjb

+ 1 - 1
fs-doctor-app/src/main/resources/application.yml

@@ -6,4 +6,4 @@ server:
 spring:
   profiles:
 #    active: dev
-    active: druid-jnmy
+    active: dev-yjb

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

@@ -16,10 +16,12 @@ import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwUserVideo;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwUserVideoService;
 import com.fs.qw.service.IQwUserVoiceLogService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
@@ -57,6 +59,8 @@ public class QwMsgController {
     @Autowired
     IQwUserService qwUserService;
     @Autowired
+    IQwUserVideoService qwUserVideoService;
+    @Autowired
     IQwUserVoiceLogService qwUserVoiceLogService;
     @Autowired
     IFsStoreOrderService fsStoreOrderService;
@@ -304,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){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
@@ -342,6 +346,30 @@ public class QwMsgController {
                     else if (wxWorkMessageDTO.getMsgtype() == 104){
                         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){

+ 25 - 5
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -2,12 +2,11 @@ package com.fs.app.controller;
 
 
 import cn.hutool.core.date.DateUtil;
-import com.fs.app.taskService.QwExternalContactRatingService;
-import com.fs.app.taskService.SopLogsChatTaskService;
-import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.app.taskService.SopWxLogsService;
+import com.fs.app.taskService.*;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -18,10 +17,12 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.utils.qrcode.QRCodeUtils;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwMaterialService;
+import com.fs.qwApi.domain.QwExternalContactResult;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
@@ -40,6 +41,7 @@ import org.springframework.web.bind.annotation.RestController;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -67,6 +69,8 @@ public class CommonController {
     private IFsCourseWatchLogService watchLogService;
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
 
     @Autowired
     private IQwSopLogsService qwSopLogsService;
@@ -112,10 +116,13 @@ public class CommonController {
     @Autowired
     private IQwMaterialService iQwMaterialService;
 
-
     @Autowired
     private IFsCourseLinkService iFsCourseLinkService;
 
+    @Autowired
+    private SyncQwExternalContactService syncQwExternalContactService;
+
+
     /**
     * 发官方通连
     */
@@ -315,4 +322,17 @@ public class CommonController {
         }
         return R.ok();
     }
+
+    @GetMapping("/syncQwExternalContactUnionid")
+    public R syncQwExternalContactUnionid(){
+        return syncQwExternalContactService.syncQwExternalContactUnionid();
+    }
+
+
+    @GetMapping("/queryRedPacketResult")
+    public R queryRedPacketResult(String startTime , String  endTime) {
+        fsCourseRedPacketLogService.queryRedPacketResult(startTime, endTime);
+        return R.ok();
+    }
+
 }

+ 11 - 0
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -78,6 +78,9 @@ public class qwTask {
     @Autowired
     private QwExternalContactRatingMoreSevenDaysService qwExternalContactRatingMoreSevenDaysService;
 
+    @Autowired
+    private SyncQwExternalContactService syncQwExternalContactService;
+
     /**
      * 定时任务:检查SOP规则时间
      * 执行时间:每天凌晨 1:10:00
@@ -374,4 +377,12 @@ public class qwTask {
         long endTimeMillis = System.currentTimeMillis();
         log.info("====== 更新掉所有前一天的所有待发送,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }
+
+    @Scheduled(cron = "0 1 0 */2 * ?")
+    public void updateQwExternalContactUnionid() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 同步外部联系人的UnionId ======");
+        syncQwExternalContactService.syncQwExternalContactUnionid();
+
+    }
 }

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/SyncQwExternalContactService.java

@@ -0,0 +1,10 @@
+package com.fs.app.taskService;
+
+import com.fs.common.core.domain.R;
+
+public interface SyncQwExternalContactService {
+
+    R syncQwExternalContactUnionid();
+
+
+}

+ 10 - 3
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -899,6 +899,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                      Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
                     Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType,
                                      List<Company> companies) {
+        QwExternalContact contact = null;
+        if(logVo.getExternalId() != null){
+            contact = qwExternalContactMapper.selectById(logVo.getExternalId());
+        }
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -968,6 +972,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 } else {
                                     setting.setValue(currentValue
                                             .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
+                                            .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
                                             + "\n" + link);
                                 }
                             }
@@ -978,7 +983,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     } else {
                         if ("1".equals(setting.getContentType())) {
                             setting.setValue(setting.getValue()
-                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText));
+                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
+                                    .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus()));
                         }
                     }
                     break;
@@ -988,7 +994,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
-                            qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
+                            qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId(), isGroupChat ? groupChat.getChatId() : null);
 
                     if(sopLogs.getSendType()==1){
                         setting.setMiniprogramAppid(miniAppId);
@@ -1323,7 +1329,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                        Long courseId, Long videoId, String qwUserId,
-                                       String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId) {
+                                       String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId, String chatId) {
         // 获取缓存的配置
         CourseConfig config;
         synchronized(configLock) {
@@ -1351,6 +1357,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCourseId(courseId.longValue());
         link.setQwExternalId(Long.parseLong(externalId));
         link.setProjectCode(cloudHostProper.getProjectCode());
+        link.setChatId(chatId);
 
         if (StringUtil.strIsNullOrEmpty(isOfficial)){
             link.setLinkType(3);

+ 78 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SyncQwExternalContactServiceImpl.java

@@ -0,0 +1,78 @@
+package com.fs.app.taskService.impl;
+
+import com.fs.app.taskService.SyncQwExternalContactService;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qwApi.domain.QwExternalContactResult;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@Slf4j
+public class SyncQwExternalContactServiceImpl implements SyncQwExternalContactService {
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private QwApiService qwApiService;
+    @Override
+    public R syncQwExternalContactUnionid() {
+        // 测试环境需要在sql加上:and corp_id='ww51717e2b71d5e2d3'
+        // 查询这次同步的最大id
+        Long maxId = qwExternalContactMapper.selectSyncMaxId();
+        log.info("同步最大id值:"+maxId);
+        if (maxId == null) {
+            return R.ok("无需同步");
+        }
+        Long recordId = 0L;
+        String recordIdStr = redisCache.getCacheObject("syncQwExternalContactUnionId");
+        if (StringUtils.isNotEmpty(recordIdStr)) {
+            try {
+                recordId = Long.parseLong(recordIdStr);
+            } catch (NumberFormatException e) {
+                log.info("Failed to parse recordId from redis: {}", recordIdStr);
+                recordId = 0L;
+            }
+        }
+        log.info("开始同步的recordId值:"+recordId);
+        // 循环同步直到recordId等于maxId
+        while (recordId < maxId) {
+            // 每次查询500条数据
+            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectSyncData(recordId, maxId);
+            if (qwExternalContacts.isEmpty()) {
+                break;
+            }
+            List<QwExternalContact> batchList = new ArrayList<>();
+            // 调用接口
+            for (QwExternalContact info : qwExternalContacts) {
+                QwExternalContactResult externalcontact = qwApiService.getExternalcontact(info.getExternalUserId(), info.getCorpId());
+                if (null!=externalcontact && null!=externalcontact.getExternal_contact() && null!=externalcontact.getExternal_contact().getUnionid() ) {
+                    info.setUnionid(externalcontact.getExternal_contact().getUnionid());
+                    batchList.add(info);
+                }
+            }
+            if (!batchList.isEmpty()) {
+                for (QwExternalContact qwExternalContact : batchList) {
+                    qwExternalContactMapper.batchUpdateUnionId(qwExternalContact);
+                }
+            }else{
+                log.info("集合为空:{recordId->"+recordId+";syncId->"+qwExternalContacts.get(qwExternalContacts.size() - 1).getId()+"}");
+            }
+            // 更新recordId为本次处理的最后一条记录的id
+            recordId = qwExternalContacts.get(qwExternalContacts.size() - 1).getId();
+            // 更新redis中的记录值
+            redisCache.setCacheObject("syncQwExternalContactUnionId", recordId.toString());
+        }
+        log.info("同步成功,同步完之后的recordId:"+recordId);
+        return R.ok();
+    }
+}

+ 1 - 1
fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -20,7 +20,7 @@ import java.util.function.Function;
 @Slf4j
 @Service
 @AllArgsConstructor
-@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
+@RocketMQMessageListener(topic = "repeat-upload", consumerGroup = "common-group")
 public class RocketMQConsumerService implements RocketMQListener<String> {
 
     private final RepeatService repeatService;

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

@@ -238,4 +238,10 @@ public interface ICompanyUserService {
     R unBindDoctor(Long userId);
 
     R getBindInfo(Long companyUserId);
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO 批量修改角色参数
+     */
+    R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO);
 }

+ 59 - 4
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.company.service.impl;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.QRutils;
 import com.fs.common.annotation.DataScope;
@@ -22,8 +23,7 @@ import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
-import com.fs.company.service.ICompanyService;
-import com.fs.company.service.ICompanyUserService;
+import com.fs.company.service.*;
 import com.fs.company.vo.*;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.his.mapper.FsUserMapper;
@@ -43,8 +43,13 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysRoleService;
+import com.fs.system.service.ISysUserService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import org.slf4j.Logger;
@@ -72,6 +77,7 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 {
     @Autowired
     private CompanyUserMapper companyUserMapper;
+
     @Autowired
     private CompanyRoleMapper roleMapper;
 
@@ -99,6 +105,7 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 
     @Autowired
     private FsUserMapper fsUserMapper;
+
     @Autowired
     private IFsUserCompanyUserService userCompanyUserService;
 
@@ -108,10 +115,19 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Autowired
     private ICompanyService companyService;
 
-
     @Autowired
     private IQwUserService qwUserService;
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
+//    @Autowired
+//    private ICompanyUserRoleService userRoleService;
+
+
     /**
      * 查询物业公司管理员信息
      *
@@ -649,7 +665,20 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Override
     @DataScope(deptAlias = "u", userAlias = "u")
     public List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user) {
-        return companyUserMapper.selectCompanyUserQwListVO(user);
+//        CompanyConfig companyConfig = companyConfigService.selectCompanyConfigByKey(user.getCompanyId(), "company:admin:show");
+        boolean  isAdminShow = false;
+//        if(!StringUtils.isEmpty(companyConfig.getConfigValue())){
+//            isAdminShow = Boolean.parseBoolean(companyConfig.getConfigValue());
+//        }
+        List<CompanyUserQwListVO> companyUserQwListVOS = companyUserMapper.selectCompanyUserQwListVO(user);
+        if(!isAdminShow){
+            Company company = companyService.selectCompanyById(user.getCompanyId());
+            Long userId = company.getUserId();
+            companyUserQwListVOS = companyUserQwListVOS.stream()
+                    .filter(vo -> !vo.getUserId().equals(userId))
+                    .collect(Collectors.toList());
+        }
+        return companyUserQwListVOS;
     }
 
     @Override
@@ -1006,4 +1035,30 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         }
         return R.error();
     }
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO 批量修改角色参数
+     */
+    @Override
+    public R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO) {
+        Long[] roleIds = batchUserRolesVO.getRoleIds();
+        Long[] userIds = batchUserRolesVO.getUserIds();
+        //先删除之前关联的角色
+        userRoleMapper.deleteCompanyUserRoleByIds(userIds);
+        for (Long userId : userIds){
+            try {
+                CompanyUser companyUser = selectCompanyUserById(userId);
+                companyUser.setRoleIds(roleIds);
+                //updateUser(companyUser) 直接使用这个方法后屏蔽下方代码也可以
+                insertUserRole(companyUser);
+                userPostMapper.deleteUserPostByUserId(userId);
+                insertUserPost(companyUser);
+                companyUserMapper.updateCompanyUser(companyUser);
+            }catch (Exception exception){
+                throw new CustomException("修改失败");
+            }
+        }
+        return R.ok("修改成功");
+    }
 }

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

@@ -0,0 +1,21 @@
+package com.fs.company.vo;
+
+import lombok.*;
+
+/**
+ * @description: 批量修改用户角色入参
+ * @author: Guos
+ * @time: 2025/10/16 上午9:23
+ */
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+public class BatchUserRolesVO {
+
+    private Long[] roleIds;
+
+    private Long[] userIds;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyRole;
+import com.fs.qw.vo.QwUserVO;
 import lombok.Data;
 
 import java.util.Date;
@@ -133,4 +134,7 @@ public class CompanyUserQwListVO extends BaseEntity {
     /** 医生id */
     private Long doctorId;
 
+    /** 企微用户列表 */
+    List<QwUserVO> qwUsers;
+
 }

+ 36 - 0
fs-service/src/main/java/com/fs/course/dto/CourseRedPacketStatisticsDTO.java

@@ -0,0 +1,36 @@
+package com.fs.course.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Data
+public class CourseRedPacketStatisticsDTO {
+
+    // 公司名称
+    private String companyName;
+
+    // 员工姓名
+    private String nickName;
+
+    // 员工id
+    private Long companyUserId;
+
+    // 红包数
+    private Long redPacketNum;
+
+   // 红包总金额
+    private BigDecimal redPacketTotalMoney;
+
+
+
+
+}

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

@@ -117,8 +117,11 @@ public interface FsCourseAnswerLogsMapper
             "<if test = 'qwUserId !=null '> " +
             "and qw_user_id = #{qwUserId} " +
             "</if>" +
+            "<if test = 'project !=null '> " +
+            "and project = #{project} " +
+            "</if>" +
             "</script>"})
-    int selectErrorCountByCourseVideo(@Param("videoId") Long videoId,@Param("userId") Long userId,@Param("qwUserId") String qwUserId);
+    int selectErrorCountByCourseVideo(@Param("videoId") Long videoId, @Param("userId") Long userId, @Param("qwUserId") String qwUserId,@Param("project") Long project);
 
     Long selectRedStatus(@Param("userId") Long userId, @Param("videoId") Long videoId, @Param("periodId") Long periodId);
 

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

@@ -3,9 +3,12 @@ package com.fs.course.mapper;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.domain.FsCourseRedPacketLog;
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
 import com.fs.course.param.FsCourseRedPacketLogParam;
 import com.fs.course.param.FsUserCourseOrderParam;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
@@ -171,4 +174,8 @@ public interface FsCourseRedPacketLogMapper
     List<FsCourseRedPacketLog> selectFail(@Param("userId") Long userId);
 
     List<RedPacketMoneyVO> selectFsCourseRedPacketLogHourseByCompany(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
+
+    List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param);
+
+    List<FsCourseRedPacketLog> selectFsCourseRedPacketLogListBySending(@Param("maps") Map<String, Object> map);
 }

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

@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -311,6 +312,11 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             " WHERE  DATE(l.create_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY) and l.video_id =#{videoId}")
     List<FsQwCourseWatchLogVO> selectFsCourseWatchLogByNoDayAndVoidId(Long videoId);
 
+    @Select("SELECT l.qw_external_contact_id,l.log_type,l.qw_user_id,l.create_time ,u.first_time,u.create_time lineTime FROM fs_course_watch_log  l " +
+            "LEFT JOIN qw_external_contact u ON u.id=l.qw_external_contact_id  " +
+            " WHERE  l.create_time between #{start} and #{end} and l.video_id =#{videoId}")
+    List<FsQwCourseWatchLogVO> selectFsCourseWatchLogByNoDayAndVoidIdByTime(@Param("videoId") Long videoId, @Param("start") String start, @Param("end") String end);
+
     @Select("SELECT l.qw_external_contact_id,l.log_type,l.qw_user_id,l.create_time ,u.first_time,u.create_time lineTime FROM fs_course_watch_log  l " +
             "LEFT JOIN qw_external_contact u ON u.id=l.qw_external_contact_id  " +
             " WHERE  DATE(l.create_time) = DATE_SUB(CURDATE(), INTERVAL 2 DAY) and l.video_id =#{videoId}")

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

@@ -137,7 +137,7 @@ public interface FsUserCourseVideoMapper
     Long selectFsUserCourseVideoByCourseSort(@Param("courseId")Long courseId, @Param("courseSort")Long courseSort);
 
 
-    @Select("select video_id dict_value, title dict_label  from fs_user_course_video where course_id=#{id} and is_del = 0 ")
+    @Select("select video_id dict_value, title dict_label  from fs_user_course_video where course_id=#{id} and is_del = 0 order by course_sort")
     List<OptionsVO> selectFsUserCourseVodeAllList(Long id);
 
     @Select({"<script> " +

+ 38 - 0
fs-service/src/main/java/com/fs/course/param/CourseRedPacketStatisticsParam.java

@@ -0,0 +1,38 @@
+package com.fs.course.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Data
+public class CourseRedPacketStatisticsParam {
+
+    // 公司id
+    private Long companyId;
+
+    // 公司用户id
+    private Long companyUserId;
+
+    // 状态 状态 0 发送中  1  已发送  2余额不足待发送
+    private Integer status;
+
+    // 开始时间
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date startTime;
+
+    // 结束时间
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date endTime;
+
+
+
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/course/service/CourseRedPacketStatisticsService.java

@@ -0,0 +1,11 @@
+package com.fs.course.service;
+
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+
+import java.util.List;
+
+
+public interface CourseRedPacketStatisticsService {
+    List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param);
+}

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

@@ -85,4 +85,6 @@ public interface IFsCourseRedPacketLogService
     R retryCourseRedPacketLog(Long[] logIds);
 
     void sendRedPacketBf();
+
+    void queryRedPacketResult(String startTime, String endTime);
 }

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

@@ -10,6 +10,8 @@ import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.List;
 import java.util.Map;
 
@@ -100,6 +102,7 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     List<FsCourseOverVO> selectFsCourseWatchLogOverStatisticsListVO(FsCourseOverParam param);
 
     void addCourseWatchLogDay();
+    void addCourseWatchLogDayMinute(LocalDateTime start, LocalDateTime end);
 
     void addCourseWatchLogDay2();
 

+ 28 - 0
fs-service/src/main/java/com/fs/course/service/impl/CourseRedPacketStatisticsServiceImpl.java

@@ -0,0 +1,28 @@
+package com.fs.course.service.impl;
+
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+import com.fs.course.service.CourseRedPacketStatisticsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @description: 看客红包统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Service
+public class CourseRedPacketStatisticsServiceImpl implements CourseRedPacketStatisticsService {
+
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+
+    @Override
+    public List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param) {
+        return fsCourseRedPacketLogMapper.statistics(param);
+    }
+}

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

@@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.validation.constraints.Size;
 import java.util.*;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
@@ -177,7 +176,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                 return R.ok("答题成功");
             }
         }
-        errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+        errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(),log.getProject());
 
 
 
@@ -263,7 +262,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog!=null){
                 return R.error("该课程已答题完成,不可重复答题");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null);
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null, null);
 
         }else {
             FsCourseWatchLog log;
@@ -284,7 +283,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog != null) {
                 return R.ok("答题成功");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(), log.getProject());
         }
 
 
@@ -368,7 +367,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog!=null){
                 return R.error("该课程已答题完成,不可重复答题");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null);
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null, null);
 
         }else {
             FsCourseWatchLog log;
@@ -399,7 +398,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                     return R.ok("答题成功");
                 }
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(), log.getProject());
         }
 
 

+ 71 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -1,18 +1,24 @@
 package com.fs.course.service.impl;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.config.RedPacketConfig;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
@@ -22,9 +28,16 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.TransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -300,6 +313,64 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
         }
     }
 
+    @Override
+    public void queryRedPacketResult(String startTime, String endTime) {
+        LocalDateTime tenMinutesAgo;
+        LocalDateTime twentyMinutesAgo;
+        if(StringUtils.isEmpty(startTime) || StringUtils.isEmpty(endTime)){
+            // 获取前十分钟时间 和 前二十分钟时间
+            tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
+            twentyMinutesAgo = tenMinutesAgo.minusMinutes(10);
+        }else {
+            tenMinutesAgo = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            twentyMinutesAgo = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        }
+
+        // 获取前分钟时间
+        Map<String, Object> params = new HashMap<>();
+        params.put("startTime", twentyMinutesAgo);
+        params.put("endTime",tenMinutesAgo);
+        // 获取前十分钟红包记录状态为发送中的记录
+        List<FsCourseRedPacketLog> redPacketLogs = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogListBySending(params);
+        if(redPacketLogs!=null && !redPacketLogs.isEmpty()){
+            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);
+            TransferService transferService=wxPayService.getTransferService();
+            for(FsCourseRedPacketLog redPacket:redPacketLogs){
+                // 获取批次号
+                // redPacket.getResult() {"msg":"发送红包成功","code":200,"mchId":"1703311381","data":{"createTime":"2025-06-26T18:00:48+08:00","outBillNo":"fsCourse1938175604536901632","packageInfo":"ABBQO+oYAAABAAAAAABRil0NtaWxBS5JURpdaBAAAADnGpepZahT9IkJjn90+1qgtzWOmCRNZJfek1QMbZ9ktG8idrj37//0xOSt0T67XUFE+PGeXO8qZoNKHYlU3RicIHExIjZr342xE+QjrpjaHIFYoPg=","state":"WAIT_USER_CONFIRM","transferBillNo":"1330007292140242506260028904279364"},"isNew":1}
+                // 获取 transferBillNo
+                String batchId;
+                try {
+                    batchId = StringUtils.isNotEmpty(redPacket.getBatchId())?redPacket.getBatchId():JSON.parseObject(redPacket.getResult()).getJSONObject("data").getString("transferBillNo");
+                }catch (Exception e){
+                    logger.error("【红包处理】获取批次号失败,FsCourseRedPacketLog-log_id:{}",redPacket.getLogId());
+                    continue;
+                }
+
+                try {
+                    TransferBillsGetResult queryRedPacketResult = transferService.getBillsByTransferBillNo(batchId);
+                    logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",redPacket.getLogId(),queryRedPacketResult);
+                    if(queryRedPacketResult!=null && "SUCCESS".equals(queryRedPacketResult.getState())){
+                        FsCourseRedPacketLog fsCourseRedPacketLog=new FsCourseRedPacketLog();
+                        fsCourseRedPacketLog.setLogId(redPacket.getLogId());
+                        fsCourseRedPacketLog.setStatus(1); // 已发送
+                        fsCourseRedPacketLog.setUpdateTime(new Date());
+//                        updateFsCourseRedPacketLog(fsCourseRedPacketLog);
+                    }
+                } catch (WxPayException e) {
+                    logger.error(e.getMessage());
+                }
+            }
+        }
+    }
+
+
     private void processRedPacket(FsCourseRedPacketLog redPacket, CourseConfig config) {
         // 获取用户信息
         FsUser user = fsUserMapper.selectFsUserByUserId(redPacket.getUserId());

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

@@ -185,8 +185,6 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         }
         if (ObjectUtils.isNotEmpty(param.getTabType())&&param.getTabType().equals("common")){
             param.setCommon(param.getTabType());
-        }else if(ObjectUtils.isNotNull(param.getCompanyId())){
-            param.setCommon("company");
         }
         List<FsCourseTrafficLogListVO> fsCourseTrafficLogListVOS = fsCourseTrafficLogMapper.selectTrafficNew(param);
         for (FsCourseTrafficLogListVO log : fsCourseTrafficLogListVOS) {

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

@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.company.cache.ICompanyCacheService;
 import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.Company;
@@ -878,6 +879,60 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectFsCourseWatchLogOverStatisticsListVO(param);
     }
 
+    @Override
+    public void addCourseWatchLogDayMinute(LocalDateTime start, LocalDateTime end) {
+        List<FsUserCourse> courses = fsUserCourseMapper.selectFsUserCourseAllCourse();
+        for (FsUserCourse course : courses) {
+            Long project = course.getProject();
+            List<FsUserCourseVideo> fsUserCourseVideos = fsUserCourseVideoMapper.selectVideoByCourseId(course.getCourseId());
+            for (FsUserCourseVideo fsUserCourseVideo : fsUserCourseVideos) {
+                ArrayList<QwWatchLog> QwWatchLogs = new ArrayList<>();
+                List<FsQwCourseWatchLogVO> watchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByNoDayAndVoidIdByTime(fsUserCourseVideo.getVideoId(), DateUtil.formatLocalDateTime(start), DateUtil.formatLocalDateTime(end));
+                for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
+                    Date firstTime = fsCourseWatchLog.getFirstTime();
+                    Long day=1L;
+                    if (fsCourseWatchLog.getLineTime()==null){
+                        continue;
+                    }
+                    //不是第一次 看课程
+                    if (firstTime!=null){
+                        LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                        LocalDate currentDate = LocalDate.now();
+                        day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
+                    }else {
+                        //是先导课
+                        if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
+                            int count = qwWatchLogMapper.selectQwWatchLogIsFirst(fsCourseWatchLog.getQwExternalContactId());
+                            if (count>0){
+                                continue;
+                            }
+                            day=0L;
+                            //不是先导课
+                        }else {
+                            day=1L;
+                            QwExternalContact qwExternalContact = new QwExternalContact();
+                            qwExternalContact.setId(fsCourseWatchLog.getQwExternalContactId());
+                            qwExternalContact.setFirstTime(fsCourseWatchLog.getCreateTime());
+                            qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                        }
+                    }
+                    QwWatchLog qwWatchLog = new QwWatchLog();
+                    qwWatchLog.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                    qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
+                    qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
+                    qwWatchLog.setDay(day);
+                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
+                    qwWatchLog.setProject(project);
+                    qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
+                    QwWatchLogs.add(qwWatchLog);
+                }
+                if (!QwWatchLogs.isEmpty()){
+                    qwWatchLogMapper.insertQwWatchLogBatch(QwWatchLogs);
+                }
+
+            }
+        }
+    }
     @Override
     public void addCourseWatchLogDay() {
 

+ 96 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.BeanCopyUtils;
@@ -55,6 +56,7 @@ import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qwApi.Result.QwAddContactWayResult;
+import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
@@ -482,6 +484,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         Integer isRoom = param.getIsRoom();
 
         // 处理逻辑
+        if(StringUtils.isNotEmpty(param.getChatId())){
+            return handleQwRoom(param, fsUser);
+        }
         if (isRoom == null || isRoom == 0) {
             // 当 isRoom 为 null 或 0 时走 handleExt
             return handleExt(param,msg, oneCompanyCourse);
@@ -495,7 +500,88 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
 
     }
+    private R handleQwRoom(FsUserCourseVideoAddKfUParam param,FsUser user) {
+        FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLink(param.getLink());
+        String msg = "<div style=\"color: red;margin-bottom: 15px;font-weight: bold;\">本课程为群会员独享<br>请长按二维码</div>\n" +
+                "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
+        QwGroupChatDetailsResult result = qwApiService.groupChatDetails(courseLink.getChatId(), param.getCorpId());
+        log.info("群聊参数:{},链接参数:{}, 企微返回:{}", JSON.toJSONString(param), JSON.toJSONString(courseLink), JSON.toJSONString(result));
+        if(result.getErrCode() != 0){
+            return R.error("企微接口请求失败,请联系管理员:" +result.getErrMsg());
+        }
+//            List<QwGroupChatDetailsResult.Member> collect = result.getGroupChat().getMemberList().stream().filter(e -> e.getType() == 2).collect(Collectors.toList());
+//            if(collect.isEmpty()){
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//            Optional<QwGroupChatDetailsResult.Member> optional = collect.stream().filter(e -> e.getName().equals(fsUser.getNickName()) || e.getName().equals(param.getNickName())).findFirst();
+//            if(!optional.isPresent()){
+//                return addCustomerService(param.getQwUserId(),msg);
+//            }
+//            QwGroupChatDetailsResult.Member member = optional.get();
+//            QwExternalContact qwExternalContact =
+//                    qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>()
+//                    .eq("user_id", result.getGroupChat().getOwner())
+//                    .eq("external_user_id", member.getUserId())
+//                    );
+        QwExternalContact qwExternalContact =
+                qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>()
+                        .eq("user_id", result.getGroupChat().getOwner())
+                        .eq("fs_user_id", param.getUserId())
+                        .eq("corp_id", param.getCorpId())
+                        .eq("status",0));
+        if(qwExternalContact==null){
+            return addCustomerService(param.getQwUserId(),msg);
+        }
+        log.info("外部联系人数据:{}", qwExternalContact);
+        if(result.getGroupChat().getMemberList().stream().noneMatch(e -> e.getUserId().equals(qwExternalContact.getExternalUserId()))){
+            return addCustomerService(param.getQwUserId(),msg);
+        }
+        Long qwExternalId = qwExternalContact.getId();
+//        addCompanyCompanyFsUser(param);
+        FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(qwExternalId, param.getVideoId(),param.getQwUserId());
+        if (log==null ){
+            return addCustomerService(param.getQwUserId(),msg);
+        }
+        //判断外部联系人有没有绑定userId
+        if (qwExternalContact.getFsUserId()!=null){
+            //有客户有小程序id  但 登录的小程序id和根据外部联系人id查出来的小程序id不一致
+            if (!qwExternalContact.getFsUserId().equals(param.getUserId())) {
+                return addCustomerService(param.getQwUserId(),msg);
+            }
+            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByMiniUserId(param.getUserId());
+            //匹配客户公司id
+            if (qwExternalContacts.stream().noneMatch(contact -> contact.getCorpId().equals(param.getCorpId()))){
+                return addCustomerService(param.getQwUserId(),msg);
+            }
+
+            //看课记录中userId为0绑定userId
+            if (log.getUserId()==null||log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())){
+                log.setUserId(param.getUserId());
+            }
+
+            log.setUpdateTime(new Date());
+//            fsUserCompanyBindService.bindFsUser(fsUser.getUserId(), qwExternalId, log.getLogId());
+            courseWatchLogMapper.updateFsCourseWatchLog(log);
 
+        }else {
+            //没绑定fsUser直接绑定fsUser
+            QwExternalContact contact = new QwExternalContact();
+            contact.setId(qwExternalId);
+            contact.setFsUserId(param.getUserId());
+            qwExternalContactMapper.updateQwExternalContact(contact);
+            FsUser fsUser = new FsUser();
+            fsUser.setUserId(user.getUserId());
+            fsUser.setIsAddQw(1);
+            fsUserMapper.updateFsUser(fsUser);
+            //绑定上之后 更新观看记录
+            //看课记录中userId为0绑定userId
+            log.setUserId(param.getUserId());
+            log.setUpdateTime(new Date());
+            courseWatchLogMapper.updateFsCourseWatchLog(log);
+        }
+//        fsUserCompanyBindService.bindFsUser(fsUser.getUserId(), qwExternalId, log.getLogId());
+        return R.error(567,"群聊通用链接").put("qwExternalId", qwExternalContact.getId());
+    }
     private R handleRoom(FsUserCourseVideoAddKfUParam param,FsUser user) {
         //查询客户列表
         List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactListVOByfsUserId(user.getUserId());
@@ -1145,7 +1231,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 return sendIntegralReward(param,user, log, config);
             // 红包+积分
             case 3:
-                R sendRed = sendRedPacketReward(param, user, log, video, config);
+                R sendRed = sendRedPacketRewardFsUser(param, user, log, video, config);
                 if (!Objects.equals(sendRed.get("code"), 200)) {
                     return sendRed;
                 }
@@ -1345,9 +1431,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         BigDecimal amount = BigDecimal.ZERO;
         FsUserCourseVideoRedPackage redPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(param.getVideoId(), param.getCompanyId(), param.getPeriodId());
 
-        if (redPackage != null) {
+        if (redPackage != null && redPackage.getRedPacketMoney() != null) {
             amount = redPackage.getRedPacketMoney();
-        } else if (video != null) {
+        } else if (video != null && video.getRedPacketMoney() != null) {
             amount = video.getRedPacketMoney();
         }
 
@@ -1402,7 +1488,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
             Company company = companyMapper.selectCompanyById(param.getCompanyId());
             BigDecimal money = company.getMoney();
-            if (money.compareTo(BigDecimal.ZERO)<0) {
+            if (money.compareTo(BigDecimal.ZERO)<=0) {
                 return R.error("服务商余额不足,请联系群主服务器充值!");
             }
 
@@ -1415,6 +1501,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                     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());
@@ -1586,6 +1673,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         courseWatchLogMapper.updateFsCourseWatchLog(log);
         logger.info("发放奖励====================》看课记录,{}",log);
 
+        // 红德堂不要积分转红包
+        if (CloudHostUtils.hasCloudHostName("弘德堂")) {
+            return R.ok("奖励发放成功").put("rewardType",config.getRewardType());
+        }
+
         //转换红包
         FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
         redPacketLog.setCourseId(param.getCourseId());

+ 7 - 3
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -75,6 +75,13 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @Excel(name = "所属销售")
     private String companyUserName;
 
+    @Excel(name = "所属sop任务")
+    private String sopId;
+
+    @Excel(name = "营期时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date campPeriodTime;
+
     /** 所属团队 */
 //    @Excel(name = "所属团队")
     private String companyName;
@@ -105,9 +112,6 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date finishTime;
 
-    @Excel(name = "营期时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    private Date campPeriodTime;
 
     @Excel(name = "进线时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

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

@@ -148,6 +148,8 @@ public class FsUser extends BaseEntity
 
     private Long qwExtId;
 
+    /** 下单次数 */
+    private Long orderCount;
     /**
      * 企微销售ID
      * **/

+ 28 - 0
fs-service/src/main/java/com/fs/his/dto/FsStoreOrderAmountScrmStatsQueryDto.java

@@ -0,0 +1,28 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * app商城订单金额统计queryDto对象
+ * */
+@Data
+public class FsStoreOrderAmountScrmStatsQueryDto implements Serializable {
+    /**
+     * 销售公司id
+     * */
+    private Long companyId;
+    /**
+     * 销售人员id
+     * */
+    private Long companyUserId;
+    /**
+     * 订单开始时间
+     * */
+    private String startTime;
+    /**
+     * 订单结束时间
+     * */
+    private String endTime;
+}

+ 28 - 0
fs-service/src/main/java/com/fs/his/dto/FsStoreOrderAmountStatsQueryDto.java

@@ -0,0 +1,28 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 互联网医院订单金额统计queryDto对象
+ * */
+@Data
+public class FsStoreOrderAmountStatsQueryDto  implements Serializable {
+    /**
+     * 销售公司id
+     * */
+    private Long companyId;
+    /**
+     * 销售人员id
+     * */
+    private Long companyUserId;
+    /**
+     * 订单开始时间
+     * */
+    private String startTime;
+    /**
+     * 订单结束时间
+     * */
+    private String endTime;
+}

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

@@ -14,6 +14,7 @@ import com.fs.his.domain.FsInquiryOrderMsg;
 import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.domain.FsStoreOrderItem;
 import com.fs.his.domain.FsStoreOrderLogs;
+import com.fs.his.dto.FsStoreOrderAmountStatsQueryDto;
 import com.fs.his.param.*;
 import com.fs.his.vo.*;
 import org.apache.ibatis.annotations.Param;
@@ -1177,4 +1178,6 @@ public interface FsStoreOrderMapper
 
 
     List<Report> selectOrderByCustomerIds(@Param("map") ReportParam param);
+
+    FsStoreOrderAmountStatsVo selectFsStoreOrderAmountStats(FsStoreOrderAmountStatsQueryDto queryDto);
 }

+ 6 - 4
fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java

@@ -15,10 +15,7 @@ import com.fs.erp.dto.df.BspOrderResponse;
 import com.fs.erp.dto.df.DFConfigVo;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.domain.*;
-import com.fs.his.dto.ExpressInfoDTO;
-import com.fs.his.dto.ExpressNotifyDTO;
-import com.fs.his.dto.ExpressResultDTO;
-import com.fs.his.dto.StoreOrderExpressExportDTO;
+import com.fs.his.dto.*;
 import com.fs.his.param.*;
 import com.fs.his.vo.*;
 import com.fs.huifuPay.domain.HuiFuResult;
@@ -268,4 +265,9 @@ public interface IFsStoreOrderService
     String selectFsStoreOrderProductStatistics(FsStoreOrderParam fsStoreOrder);
 
     int afterSalesByProduct(FsStoreOrderSalesParam fsStoreOrder);
+
+    /**
+     * 查询互联网医院订单金额统计信息
+     * */
+    FsStoreOrderAmountStatsVo selectFsStoreOrderAmountStats(FsStoreOrderAmountStatsQueryDto queryDto);
 }

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

@@ -95,6 +95,10 @@ public interface IFsStorePaymentService
 
     R sendRedPacketV3(WxSendRedPacketParam param);
 
+    R sendRedPacketLimit(WxSendRedPacketParam param);
+
+    R sendRedPacketDeduction(WxSendRedPacketParam param);
+
     String transferNotify(String notifyData, HttpServletRequest request);
 
     Boolean isEntityNull(FsStorePaymentParam fsStorePayment);
@@ -119,5 +123,6 @@ public interface IFsStorePaymentService
 
     R getWxaCodeByPayment(FsStorePaymentGetWxaCodeParam param);
 
+
     R payment(FsStorePaymentPayParam payParam);
 }

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

@@ -125,8 +125,8 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     private JpushService jpushService;
     @Autowired
     IPayService payService;
-    @Autowired
-    SmsServiceImpl smsService;
+//    @Autowired
+//    SmsServiceImpl smsService;
     @Autowired
     private FsInquiryOrderMapper fsInquiryOrderMapper;
     @Autowired
@@ -932,7 +932,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
                 FsUser fsUser = fsUserService.selectFsUserByUserId(order.getUserId());
                 if (fsUser!=null&&fsUser.getPhone()!=null){
                     logger.info("医生接单发送短信:"+fsUser.getPhone()+patientDTO.getPatientName());
-                    smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "3");
+//                    smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "3");
                 }
 
             }
@@ -1095,7 +1095,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
                 FsUser fsUser = fsUserService.selectFsUserByUserId(order.getUserId());
                 if (fsUser!=null&&fsUser.getPhone()!=null){
                     logger.info("医生接单发送短信:"+fsUser.getPhone()+patientDTO.getPatientName());
-                    smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "3");
+//                    smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "3");
                 }
 
             }

+ 198 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -103,6 +103,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
 import org.springframework.util.CollectionUtils;
 
+import javax.annotation.PostConstruct;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
@@ -278,7 +279,8 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
     private FsStoreOrderDfMapper fsStoreOrderDfMapper;
     @Autowired
     private FsUserWxMapper fsUserWxMapper;
-
+    @Autowired
+    private FsUserMapper fsUserMapper;
     @Autowired
     private IFsUserWxService userWxService;
 
@@ -288,6 +290,19 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
     @Autowired
     private com.fs.gtPush.service.uniPush2Service uniPush2Service;
 
+    //ERP 类型到服务的映射
+    private Map<Integer, IErpOrderService> erpServiceMap;
+    @PostConstruct
+    public void initErpServiceMap() {
+        erpServiceMap = new HashMap<>();
+        erpServiceMap.put(1, gyOrderService);      // 管易
+        erpServiceMap.put(2, wdtOrderService);     // 旺店通
+        erpServiceMap.put(3, hzOMSOrderService);   // 瀚智OMS
+        erpServiceMap.put(4, dfOrderService);      // 代服
+        erpServiceMap.put(5, jSTOrderService);     // 聚水潭
+        erpServiceMap.put(6, k9OrderService);      // K9
+    }
+
     /**
      * 查询订单
      *
@@ -331,8 +346,151 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
     @Override
     public int updateFsStoreOrder(FsStoreOrder fsStoreOrder) {
         fsStoreOrder.setUpdateTime(DateUtils.getNowDate());
+        //推送修改的互联网医院订单地址到聚水潭ERP
+        try {
+            pushOrderAddressToErp(fsStoreOrder);
+        }catch (Exception e){
+            log.error("修改互联网医院订单地址推送到聚水潭ERP失败,orderId: {}", fsStoreOrder.getOrderId(), e);
+        }
         return fsStoreOrderMapper.updateFsStoreOrder(fsStoreOrder);
     }
+    /**
+     * 推送修改订单的最新地址到 ERP
+     *
+     * @param partialOrder 前端传入的部分订单对象,必须包含 id、userAddress
+     */
+    public void pushOrderAddressToErp(FsStoreOrder partialOrder){
+        if (partialOrder == null || partialOrder.getOrderId() == null) {
+            log.info("传入订单为空或ID缺失,跳过ERP同步");
+            return;
+        }
+
+        // 1. 检查 ERP 是否开启
+        FsSysConfig sysConfig = configUtil.getSysConfig();
+        Integer erpOpen = sysConfig.getErpOpen();
+        if (erpOpen == null || erpOpen == 0) {
+            log.info("ERP未开启,跳过同步");
+            return;
+        }
+        Integer erpType = sysConfig.getErpType();
+        if (erpType == null || !erpServiceMap.containsKey(erpType)) {
+            log.info("ERP类型配置无效: {}", erpType);
+            return;
+        }
+        //目前只针对聚水潭ERP推送最新的修改地址
+        if (erpType != 5){
+            return;
+        }
+        // 2. 从数据库获取完整订单(用于补全必要字段;当前是修改商城订单接口,查询fs_store_order_scrm表)
+        FsStoreOrder dbOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderId(partialOrder.getOrderId());
+        if (dbOrder == null) {
+            log.error("数据库表fs_store_order中订单不存在,orderId: {}", partialOrder.getOrderId());
+            return;
+        }
+        //判断当前是否满足推送条件(erp订单号不为空且订单状态为待发货时可推送)
+        if (StringUtils.isBlank(dbOrder.getExtendOrderId()) || dbOrder.getStatus() != 1) {
+            log.info("修改订单地址不满足推送到ERP条件: extendOrderId={}, status={}", dbOrder.getExtendOrderId(), dbOrder.getStatus());
+            return;
+        }
+        // 3. 构造用于ERP推送的订单对象:订单地址用传入的新值
+        if (StrUtil.isBlank(partialOrder.getUserAddress())){
+            log.error("修改互联网医院订单地址为空,orderId: {}", partialOrder.getOrderId());
+            return;
+        }
+        dbOrder.setUserAddress(partialOrder.getUserAddress());
+        // 4. 构建 ERP 订单对象
+        try {
+        ErpOrder erpOrder = buildErpOrder(dbOrder,sysConfig);
+
+        // 5. 调用对应 ERP 服务(当前是聚水潭ERP)
+        IErpOrderService erpService = erpServiceMap.get(erpType);
+
+        //执行互联网医院订单推送逻辑
+        ErpOrderResponse response = erpService.addOrder(erpOrder);
+        log.info("ERP地址推送结果 - 互联网医院订单: {}, ERP类型: {}, 成功: {}, 外部单号: {}",
+                dbOrder.getOrderCode(), erpType, response.getSuccess(), response.getCode());
+        } catch (Exception e) {
+            log.error("推送修改互联网医院订单地址到ERP失败,orderId: {}", partialOrder.getOrderId(), e);
+        }
+    }
+
+    /**
+     * 构建 ErpOrder 对象(仅用于修改互联网医院订单地址推送到聚水潭ERP)
+     */
+    private ErpOrder buildErpOrder(FsStoreOrder order, FsSysConfig sysConfig) {
+        ErpOrder erpOrder = new ErpOrder();
+
+        //订单号
+        erpOrder.setPlatform_code(order.getOrderCode());
+
+        // 店铺编码
+        erpOrder.setShop_code(sysConfig.getErpJstShopCode());
+        erpOrder.setBuyer_account(order.getUserName());
+        //成交时间
+        erpOrder.setDeal_datetime(order.getBeginTime());
+        //支付信息
+        List<ErpOrderPayment> payments = new ArrayList<>();
+        ErpOrderPayment payment = new ErpOrderPayment();
+        payment.setPay_type_code("weixin");
+        erpOrder.setPayments(payments);
+        //卖家留言
+        erpOrder.setBuyer_memo("");
+        //推送聚水潭erp不需要推送处方图片,这里就注释代码
+        //String s = fsPrescribeService.PrescribeImg(order.getPrescribeId());
+        //erpOrder.setSeller_memo(erpOrder.getSeller_memo() + "||" + s + "||");
+        // 收货人姓名(过滤非法字符)
+        erpOrder.setReceiver_name(order.getUserName().replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", ""));
+        // 电话处理
+        String phone = null;
+        if (StrUtil.isNotBlank(order.getErpPhone())) {
+            phone = order.getErpPhone();
+        } else if (StrUtil.isNotBlank(order.getUserPhone())) {
+            // 如果手机号被加密(长度>11),需解密
+            phone = order.getUserPhone().length() > 11 ? decryptPhone(order.getUserPhone()) : order.getUserPhone();
+        }
+
+        if (StrUtil.isNotBlank(phone)) {
+            if (phone.length() > 11) {
+                erpOrder.setReceiver_phone(phone);     // 固话
+            } else {
+                erpOrder.setReceiver_mobile(phone);    // 手机
+            }
+        }
+
+        // 地址解析
+        String userAddress = order.getUserAddress();
+        try {
+            String[] parts = userAddress.split(" ");
+            if (parts.length < 3) {
+                // 调用地址解析服务(如快递鸟)
+                String kdnResult = fsUserAddressService.getKdnAddress(userAddress);
+                Map<String, Object> result = JSON.parseObject(kdnResult);
+                Map<String, String> addr = (Map<String, String>) ((Map) result.get("Data")).get("result");
+                erpOrder.setReceiver_province(addr.get("ProvinceName"));
+                erpOrder.setReceiver_city(addr.get("CityName"));
+                erpOrder.setReceiver_district(addr.get("ExpAreaName"));
+                erpOrder.setReceiver_address(addr.get("StreetName") + addr.get("Address"));
+            } else {
+                erpOrder.setReceiver_province(parts[0]);
+                erpOrder.setReceiver_city(parts[1]);
+                erpOrder.setReceiver_district(parts[2]);
+                erpOrder.setReceiver_address(parts.length > 3 ? String.join("", Arrays.copyOfRange(parts, 3, parts.length)) : parts[2]);
+            }
+
+            // 清理地址中的特殊字符
+            if (erpOrder.getReceiver_address() != null) {
+                erpOrder.setReceiver_address(
+                        erpOrder.getReceiver_address()
+                                .replace("+", "加")
+                                .replace("\n", "")
+                                .replace("\r", "")
+                );
+            }
+        } catch (Exception e) {
+            log.error("修改订单地址解析异常,订单: {}", order.getOrderCode(), e);
+        }
+        return erpOrder;
+    }
 
     /**
      * 修改订单状态-收货信息
@@ -1495,6 +1653,19 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             }
             storeOrder.setPayTime(new Date());
             fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+            try {
+                //更新用户下单次数(获取阈值,当订单总价大于等于阈值,则下单次数+1)
+                BigDecimal minThreshold = toBigDecimal(config.get("minimumThreshold"), BigDecimal.ZERO);
+                if (order.getTotalPrice().compareTo(minThreshold) >= 0) {
+                    //根据用户id获取fs_user表对应的下单次数并更新
+                    FsUser user = fsUserMapper.selectFsUserById(order.getUserId());
+                    user.setOrderCount(user.getOrderCount() + 1);
+                    fsUserMapper.updateFsUser(user);
+                }
+            } catch (Exception ex) {
+                // 仅记录日志,这个更新逻辑如果异常不回滚其他逻辑代码
+                log.error("更新用户下单次数失败,订单: {}, 用户: {}", order.getOrderCode(), order.getUserId(), ex);
+            }
             //更新优惠券状态
             if (order.getUserCouponId() != null && order.getUserCouponId() > 0) {
                 FsUserCoupon userCoupon = userCouponService.selectFsUserCouponById(order.getUserCouponId());
@@ -1520,7 +1691,27 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
             return R.error();
         }
     }
-
+    public  BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof String) {
+            String str = ((String) value).trim();
+            return str.isEmpty() ? defaultValue : new BigDecimal(str);
+        }
+        if (value instanceof Number) {
+            return new BigDecimal(((Number) value).toString());
+        }
+        // 兜底:toString 转换
+        try {
+            return new BigDecimal(value.toString().trim());
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
     @Override
     public Long selectFsStoreOrderTotalCount(int type, Long companyId) {
         return fsStoreOrderMapper.selectFsStoreOrderTotalCount(type, companyId);
@@ -3913,4 +4104,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         return 1;
     }
 
+    @Override
+    public FsStoreOrderAmountStatsVo selectFsStoreOrderAmountStats(FsStoreOrderAmountStatsQueryDto queryDto) {
+        return fsStoreOrderMapper.selectFsStoreOrderAmountStats(queryDto);
+    }
+
 }

+ 156 - 61
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -3,9 +3,10 @@ package com.fs.his.service.impl;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
@@ -37,6 +38,7 @@ import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.RedPacketConfig;
+import com.fs.course.domain.FsCourseRedPacketLog;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.course.service.IFsUserCourseOrderService;
 import com.fs.course.service.IFsUserVipOrderService;
@@ -48,7 +50,6 @@ import com.fs.his.mapper.FsStorePaymentMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.mapper.FsUserWxMapper;
 import com.fs.his.mapper.*;
-import com.fs.his.param.FsStoreOrderParam;
 import com.fs.his.param.FsStorePaymentParam;
 import com.fs.his.param.PayOrderParam;
 import com.fs.his.param.WxSendRedPacketParam;
@@ -83,45 +84,35 @@ import com.fs.ybPay.dto.OrderQueryDTO;
 import com.fs.ybPay.dto.RefundDTO;
 import com.fs.ybPay.dto.WxJspayDTO;
 import com.fs.ybPay.service.IPayService;
-import com.github.binarywang.wxpay.bean.merchanttransfer.TransferCreateRequest;
-import com.github.binarywang.wxpay.bean.merchanttransfer.TransferCreateResult;
 import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
 import com.github.binarywang.wxpay.bean.notify.WxPayTransferBatchesNotifyV3Result;
 import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
-import com.github.binarywang.wxpay.bean.request.WxPaySendRedpackRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
 import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryResult;
 import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
 import com.github.binarywang.wxpay.bean.transfer.*;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
-import com.github.binarywang.wxpay.service.MerchantTransferService;
-import com.github.binarywang.wxpay.service.RedpackService;
 import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
-import com.github.binarywang.wxpay.service.impl.RedpackServiceImpl;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 import com.google.gson.Gson;
 import com.hc.openapi.tool.fastjson.JSON;
-import com.wechat.pay.java.service.transferbatch.TransferBatchService;
-import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest;
-import me.chanjar.weixin.common.bean.WxJsapiSignature;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.mp.api.WxMpService;
 import org.apache.commons.lang.exception.ExceptionUtils;
-import org.apache.hc.core5.concurrent.CompletedFuture;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -139,6 +130,7 @@ import java.util.*;
  * @date 2023-08-11
  */
 @Service
+@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
 public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
     Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
@@ -211,10 +203,23 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
     @Value("${enableRedPackAccount:0}")
     private String ENABLE_RED_PACK_ACCOUNT;
 
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Autowired
+    private RedPacketLogMapper redPacketLogMapper;
+
     /**
      * 红包账户锁
      */
     private static final String REDPACKET_POOL_LOCK = "redpacket_pool_lock";
+    /**
+     * 红包锁
+     */
+    private static final String REDPACKET_LOCK = "redpacket_lock:%d";
 
     /**
      * 公司红包金额
@@ -226,6 +231,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
      */
     private static final String REDPACKET_USER_LIMIT = "redpacket_user_limit:%s:%d";
 
+
+
     /**
      * 查询支付明细
      *
@@ -529,18 +536,111 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
     }
 
-    @Autowired
-    private ICompanyConfigService companyConfigService;
-
-    @Autowired
-    private RedissonClient redissonClient;
 
-    @Autowired
-    private RedPacketLogMapper redPacketLogMapper;
     @Override
     @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
     public R sendRedPacket(WxSendRedPacketParam param) {
+        IFsStorePaymentService service = (IFsStorePaymentService) AopContext.currentProxy();
+
+        if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
+            return service.sendRedPacketDeduction(param);
+        } else {
+            return service.sendRedPacketLimit(param);
+        }
+    }
+
+
+    @Override
+    public R sendRedPacketLimit(WxSendRedPacketParam param){
+        FsUser user = param.getUser();
+
+        if(user == null) {
+            throw new IllegalArgumentException("[发送红包] 用户id为必传参数!");
+        }
+        Long userId = user.getUserId();
+
+        String key = String.format(REDPACKET_LOCK,userId);
+        RLock lock = redissonClient.getLock(key);
+
+        try{
+            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
+
+//            if (!locked) {
+//                logger.error("获取锁失败");
+//                return R.error("[红包领取] 系统繁忙,请重试!");
+//            }
+
+
+            // 判断当前用户是否限流
+            String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
+            String userLimitKey = String.format(REDPACKET_USER_LIMIT, today, userId);
+            Integer userCount =  redisTemplateInteger.opsForValue().get(userLimitKey);
+
+
+            // 首次领取
+            if(userCount == null) {
+                userCount = 0;
+                long expireSeconds = getExpireSeconds();
+                redisTemplateInteger.opsForValue().set(userLimitKey, userCount, expireSeconds, TimeUnit.SECONDS);
+            }
+
+            if(userCount >= RED_PACKET_LIMIT_COUNT){
+                logger.info("[红包领取] 用户{} 领取红包已经达到最大限制!",userId);
+                return R.error("[红包领取] 当前用户当前已经领取红包已经达到限制!");
+            }
+
+            String json;
+            RedPacketConfig config = new RedPacketConfig();
+            // 根据红包模式获取配置
+            switch (param.getRedPacketMode()){
+                case 1:
+                    json = configService.selectConfigByKey("redPacket.config");
+                    config = JSONUtil.toBean(json, RedPacketConfig.class);
+                    break;
+                case 2:
+                    json = companyConfigService.selectRedPacketConfigByKey(param.getCompanyId());
+                    config = JSONUtil.toBean(json, RedPacketConfig.class);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("当前红包模式不支持!");
+            }
+            //H5的用公众号的appid发,小程序的用小程序的appid来发
+            if (param.getSource()==2){
+                // 传参appId为空时,仍然使用配置里面的
+                String appId = StringUtils.isBlank(param.getAppId()) ? config.getMiniappId() : param.getAppId();
+                config.setAppId(appId);
+            }
+            logger.info("最终传参 {}",config);
+            //组合返回参数
+            R result = new R();
+            // 根据 isNew 判断使用哪种发红包方式
+            if (config.getIsNew() != null && config.getIsNew() == 1) {
+                result = sendRedPacketV3Internal(param, config);
+            } else {
+                result= sendRedPacketLegacyInternal(param, config);
+            }
+            result.put("mchId", config.getMchId());
+            result.put("isNew",config.getIsNew());
+            logger.info("红包返回:{}",result);
 
+            // 用户领取红包次数+1
+            redisTemplateInteger.opsForValue().increment(userLimitKey, 1);
+            return result;
+        }catch (Exception e){
+            logger.error("领取红包失败原因:{}", ExceptionUtils.getFullStackTrace(e),e);
+            throw new RuntimeException(e);
+        }finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 开启红包账户扣减-发红包
+     */
+    @Override
+    public R sendRedPacketDeduction(WxSendRedPacketParam param){
         //---------------发红包前先判断润天账户余额是否足够---------
         RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
         RedPacketLog redPacketLog = null;
@@ -562,8 +662,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             Long userId = user.getUserId();
 
             // 判断当前用户是否限流
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-            String today = sdf.format(new Date());
+            String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
             String userLimitKey = String.format(REDPACKET_USER_LIMIT, today, userId);
             Integer userCount =  redisTemplateInteger.opsForValue().get(userLimitKey);
 
@@ -581,37 +680,33 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             }
 
 
-            BigDecimal companyMoney = null;
+            BigDecimal companyMoney = redisTemplate.opsForValue().get(REDPACKET_COMPANY_MONEY);
 
-            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                companyMoney = redisTemplate.opsForValue().get(REDPACKET_COMPANY_MONEY);
-
-                if(ObjectUtils.isNull(companyMoney)){
-                    SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("company.money");
-                    if(ObjectUtils.isNull(sysConfig)){
-                        throw new IllegalArgumentException("润天公司账户余额不能为空!请检查配置!");
-                    }
-                    String configValue = sysConfig.getConfigValue();
-                    companyMoney = new BigDecimal(configValue);
-                    logger.info("缓存公司余额为空,从数据库读取 companyMoney: {}",companyMoney);
-                }
-
-                if (companyMoney.compareTo(BigDecimal.ZERO) <= 0) {
-                    logger.info("润天账户余额: {} 不足!", companyMoney);
-                    return R.error("[红包领取] 账户余额不足,请联系管理员!");
+            if(ObjectUtils.isNull(companyMoney)){
+                SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("company.money");
+                if(ObjectUtils.isNull(sysConfig)){
+                    throw new IllegalArgumentException("润天公司账户余额不能为空!请检查配置!");
                 }
+                String configValue = sysConfig.getConfigValue();
+                companyMoney = new BigDecimal(configValue);
+                logger.info("缓存公司余额为空,从数据库读取 companyMoney: {}",companyMoney);
+            }
 
-                redPacketLog = new RedPacketLog();
-                redPacketLog.setRedPacketMode(param.getRedPacketMode());
-                redPacketLog.setAmount(param.getAmount());
-                redPacketLog.setAppId(param.getAppId());
-                redPacketLog.setCompanyId(param.getCompanyId());
-                redPacketLog.setCreateTime(LocalDateTime.now());
-                redPacketLog.setUserId(userId);
-                redPacketLog.setAccBalanceBefore(companyMoney);
-                redPacketLog.setSource(param.getSource());
+            if (companyMoney.compareTo(BigDecimal.ZERO) <= 0) {
+                logger.info("润天账户余额: {} 不足!", companyMoney);
+                return R.error("[红包领取] 账户余额不足,请联系管理员!");
             }
 
+            redPacketLog = new RedPacketLog();
+            redPacketLog.setRedPacketMode(param.getRedPacketMode());
+            redPacketLog.setAmount(param.getAmount());
+            redPacketLog.setAppId(param.getAppId());
+            redPacketLog.setCompanyId(param.getCompanyId());
+            redPacketLog.setCreateTime(LocalDateTime.now());
+            redPacketLog.setUserId(userId);
+            redPacketLog.setAccBalanceBefore(companyMoney);
+            redPacketLog.setSource(param.getSource());
+
             String json;
             RedPacketConfig config = new RedPacketConfig();
             // 根据红包模式获取配置
@@ -624,6 +719,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                     json = companyConfigService.selectRedPacketConfigByKey(param.getCompanyId());
                     config = JSONUtil.toBean(json, RedPacketConfig.class);
                     break;
+                default:
+                    throw new UnsupportedOperationException("当前红包模式不支持!");
             }
             //H5的用公众号的appid发,小程序的用小程序的appid来发
             if (param.getSource()==2){
@@ -644,19 +741,16 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             result.put("isNew",config.getIsNew());
             logger.info("红包返回:{}",result);
 
+            // 更新账户余额
+            logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
 
-            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                // 更新账户余额
-                logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
-
-                companyMoney = companyMoney.subtract(amount);
-                redisTemplate.opsForValue().set(REDPACKET_COMPANY_MONEY,companyMoney);
+            companyMoney = companyMoney.subtract(amount);
+            redisTemplate.opsForValue().set(REDPACKET_COMPANY_MONEY,companyMoney);
 
+            redPacketLog.setAccBalanceAfter(companyMoney);
+            redPacketLog.setUpdateTime(LocalDateTime.now());
+            redPacketLog.setStatus(1);
 
-                redPacketLog.setAccBalanceAfter(companyMoney);
-                redPacketLog.setUpdateTime(LocalDateTime.now());
-                redPacketLog.setStatus(1);
-            }
 
             // 用户领取红包次数+1
             redisTemplateInteger.opsForValue().increment(userLimitKey, 1);
@@ -916,7 +1010,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
 
     @Override
     public String v3TransferNotify(String notifyData, HttpServletRequest request) {
-        logger.info("zyp \n【收到转账回调】:{}",notifyData);
+        logger.info("zyp \n【收到转账回调V3】:{}",notifyData);
         try {
             String json = configService.selectConfigByKey("redPacket.config");
             RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
@@ -931,7 +1025,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
             signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
             TransferBillsNotifyResult result = wxPayService.parseTransferBillsNotifyV3Result(notifyData,signatureHeader);
-            logger.info("到零钱回调:{}",result.getResult());
+            logger.info("到零钱回调1:{}",result.getResult());
             if (result.getResult().getState().equals("SUCCESS")) {
                 R r = redPacketLogService.syncRedPacket(result.getResult().getOutBillNo(),result.getResult().getTransferBillNo());
                 logger.info("result:{}",r);
@@ -944,6 +1038,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                 return WxPayNotifyResponse.fail("");
             }
         } catch (WxPayException e) {
+            e.printStackTrace();
             logger.error("zyp \n【转账回调异常】:{}", e.getReturnMsg());
             return WxPayNotifyResponse.fail(e.getMessage());
         }

+ 44 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAmountScrmStatsVo.java

@@ -0,0 +1,44 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+/**
+ *  app商城订单金额统计Vo对象
+ * */
+@Data
+public class FsStoreOrderAmountScrmStatsVo {
+    /**
+     * 订单总数
+     * */
+    private Integer totalOrderCount;
+    /**
+     * 全款支付订单数
+     * */
+    private Integer fullPayOrderCount;
+    /**
+     * 物流代收支付订单数
+     * */
+    private Integer codOrderCount;
+
+    /**
+     * 付定金的物流代收订单数
+     * */
+    private Integer depositCodOrderCount;
+    /**
+     * 0定金的物流代收订单数
+     * */
+    private Integer noDepositCodOrderCount;
+    /**
+     * 订单总金额
+     * */
+    private BigDecimal totalOrderAmount;
+    /**
+     * 定金总金额
+     * */
+    private BigDecimal depositAmount;
+    /**
+     * 物流代收总金额
+     * */
+    private BigDecimal codAmount;
+}

+ 44 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAmountStatsVo.java

@@ -0,0 +1,44 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+/**
+ *  互联网医院订单金额统计Vo对象
+ * */
+@Data
+public class FsStoreOrderAmountStatsVo {
+    /**
+     * 订单总数
+     * */
+    private Integer totalOrderCount;
+    /**
+     * 全款支付订单数
+     * */
+    private Integer fullPayOrderCount;
+    /**
+     * 物流代收支付订单数
+     * */
+    private Integer codOrderCount;
+
+    /**
+     * 付定金的物流代收订单数
+     * */
+    private Integer depositCodOrderCount;
+    /**
+     * 0定金的物流代收订单数
+     * */
+    private Integer noDepositCodOrderCount;
+    /**
+     * 订单总金额
+     * */
+    private BigDecimal totalOrderAmount;
+    /**
+     * 定金总金额
+     * */
+    private BigDecimal depositAmount;
+    /**
+     * 物流代收总金额
+     * */
+    private BigDecimal codAmount;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -5,6 +5,9 @@ import com.fs.api.param.OrderListParam;
 import com.fs.api.vo.OrderListVO;
 import com.fs.company.param.CompanyStatisticsParam;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
+import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
 import com.fs.his.vo.FsStoreOrderExcelVO;
 import com.fs.hisStore.domain.FsStoreOrderItemScrm;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
@@ -1222,6 +1225,8 @@ public interface FsStoreOrderScrmMapper
             "</script>"})
     int updateFsStoreOrderByOrderCode(FsStoreOrderScrm fsStoreOrder);
 
+    FsStoreOrderAmountScrmStatsVo selectFsStoreOrderAmountScrmStats(FsStoreOrderAmountScrmStatsQueryDto queryDto);
+
     /**
      * 获取订单用户信息
      * @param list 订单号

+ 9 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -7,7 +7,10 @@ import com.fs.common.core.domain.R;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
 import com.fs.erp.domain.ErpOrder;
+import com.fs.his.domain.FsStorePayment;
+import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
 import com.fs.his.param.FsStoreOrderSalesParam;
+import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
 import com.fs.his.vo.FsPrescribeVO;
 import com.fs.his.vo.FsStoreOrderExcelVO;
 import com.fs.hisStore.domain.FsStoreOrderItemScrm;
@@ -294,6 +297,12 @@ public interface IFsStoreOrderScrmService
     /**
      * 获取确认收货类型
      * **/
+    R importDeliveryNoteExpress(List<FsOrderDeliveryNoteDTO> dtoList);
+
+    /**
+     * 查询app商城订单金额统计信息
+     * */
+    FsStoreOrderAmountScrmStatsVo selectFsStoreOrderAmountScrmStats(FsStoreOrderAmountScrmStatsQueryDto queryDto);
     R queryReceiptType();
 
     /**

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

@@ -55,6 +55,7 @@ import com.fs.his.domain.FsPrescribe;
 import com.fs.his.domain.FsPrescribeDrug;
 import com.fs.his.dto.FsPrescribeUsageDTO;
 import com.fs.his.dto.FsProdItemDTO;
+import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
 import com.fs.his.dto.FsStoreOrderItemDTO;
 import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.enums.FsStoreOrderStatusEnum;
@@ -68,6 +69,7 @@ import com.fs.his.service.IFsUserWatchService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsInquiryOrderVO;
 import com.fs.his.vo.FsPrescribeVO;
+import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
 import com.fs.his.vo.FsStoreOrderExcelVO;
 import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.config.StoreConfig;
@@ -118,6 +120,7 @@ import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
 
+import javax.annotation.PostConstruct;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.nio.charset.Charset;
@@ -337,6 +340,18 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private IFsUserProjectTagScrmService userProjectTagService;
 
+    //ERP 类型到服务的映射
+    private Map<Integer, IErpOrderService> erpServiceMap;
+    @PostConstruct
+    public void initErpServiceMap() {
+        erpServiceMap = new HashMap<>();
+        erpServiceMap.put(1, gyOrderService);      // 管易
+        erpServiceMap.put(2, wdtOrderService);     // 旺店通
+        erpServiceMap.put(3, hzOMSOrderService);   // 瀚智OMS
+        erpServiceMap.put(4, dfOrderService);      // 代服
+        erpServiceMap.put(5, jSTOrderService);     // 聚水潭
+        erpServiceMap.put(6, k9OrderService);      // K9
+    }
     /**
      * 查询订单
      *
@@ -380,9 +395,75 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Override
     public int updateFsStoreOrder(FsStoreOrderScrm fsStoreOrder) {
         fsStoreOrder.setUpdateTime(DateUtils.getNowDate());
+        //推送修改的商城订单地址到聚水潭ERP
+        try {
+            pushOrderAddressToErp(fsStoreOrder);
+        }catch (Exception e){
+            log.error("修改商城订单地址推送到聚水潭ERP失败,orderId: {}", fsStoreOrder.getId(), e);
+        }
+
         return fsStoreOrderMapper.updateFsStoreOrder(fsStoreOrder);
     }
 
+    /**
+     * 推送修改订单的最新地址到 ERP
+     *
+     * @param partialOrder 前端传入的部分订单对象,必须包含 id、userAddress
+     */
+    public void pushOrderAddressToErp(FsStoreOrderScrm partialOrder) {
+        if (partialOrder == null || partialOrder.getId() == null) {
+            log.info("传入订单为空或ID缺失,跳过ERP同步");
+            return;
+        }
+
+        // 1. 检查 ERP 是否开启
+        FsSysConfig sysConfig = configUtil.getSysConfig();
+        Integer erpOpen = sysConfig.getErpOpen();
+        if (erpOpen == null || erpOpen == 0) {
+            log.info("ERP未开启,跳过同步");
+            return;
+        }
+        Integer erpType = sysConfig.getErpType();
+        if (erpType == null || !erpServiceMap.containsKey(erpType)) {
+            log.info("ERP类型配置无效: {}", erpType);
+            return;
+        }
+        //目前只针对聚水潭ERP推送最新的修改地址
+        if (erpType != 5){
+            return;
+        }
+        // 2. 从数据库获取完整订单(用于补全必要字段;当前是修改商城订单接口,查询fs_store_order_scrm表)
+        FsStoreOrderScrm dbOrder = fsStoreOrderMapper.selectFsStoreOrderById(partialOrder.getId());
+        if (dbOrder == null) {
+            log.error("数据库表fs_store_order_scrm中订单不存在,orderId: {}", partialOrder.getId());
+            return;
+        }
+        //判断当前是否满足推送条件(erp订单号不为空且订单状态为待发货时可推送)
+        if (StringUtils.isBlank(dbOrder.getExtendOrderId()) || dbOrder.getStatus() != 1) {
+            log.info("不满足修改订单地址推送到ERP条件: extendOrderId={}, status={}", dbOrder.getExtendOrderId(), dbOrder.getStatus());
+            return;
+        }
+        // 3. 构造用于ERP推送的订单对象:订单地址用传入的新值
+        if (StrUtil.isBlank(partialOrder.getUserAddress())){
+            log.error("修改商城订单地址为空,orderId: {}", partialOrder.getId());
+            return;
+        }
+        dbOrder.setUserAddress(partialOrder.getUserAddress());
+        try {
+        // 4. 构建 ERP 订单对象
+        ErpOrder erpOrder = getErpOrder(dbOrder);
+
+        // 5. 调用对应 ERP 服务(当前是聚水潭ERP)
+        IErpOrderService erpService = erpServiceMap.get(erpType);
+        //执行商城订单推送逻辑
+        ErpOrderResponse response = erpService.addOrderScrm(erpOrder);
+        log.info("ERP地址推送结果 - 商城订单: {}, ERP类型: {}, 成功: {}, 外部单号: {}",
+                dbOrder.getOrderCode(), erpType, response.getSuccess(), response.getCode());
+        } catch (Exception e) {
+            log.error("修改商城订单地址推送到ERP失败,orderId: {}", partialOrder.getId(), e);
+        }
+    }
+
     /**
      * 批量删除订单
      *
@@ -3992,6 +4073,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         return map;
     }
 
+
+    @Override
+    public FsStoreOrderAmountScrmStatsVo selectFsStoreOrderAmountScrmStats(FsStoreOrderAmountScrmStatsQueryDto queryDto) {
+        return fsStoreOrderMapper.selectFsStoreOrderAmountScrmStats(queryDto);
+    }
+
     @Override
     public void calculateAgentPayment(FsStoreOrderScrm order, Integer rate) {
         if (order == null || rate == null || order.getId() == null) {
@@ -4183,4 +4270,4 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         return start < len ? str.substring(start) : "";
     }
-}
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/domain/QwContactWay.java

@@ -141,4 +141,8 @@ public class QwContactWay extends BaseEntity
     private String welcomeJson;
 
     private Long informationId;
+
+    /** 序号 */
+    private Integer sort;
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/domain/QwFriendWelcome.java

@@ -45,6 +45,12 @@ public class QwFriendWelcome extends BaseEntity
     @Excel(name = "开启分时段之后的存储")
     private String daypartingItemlist;
 
+    /**
+     * 欢迎语标题
+     */
+    @Excel(name = "欢迎语标题")
+    private String welcomeTitle;
+
     /** 公司id */
     @Excel(name = "公司id")
     private Long companyId;

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

@@ -227,10 +227,12 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     public int deleteQwExternalContactByIds(Long[] ids);
 
     @Select({"<script> " +
-            "select ec.*,qu.qw_user_name,qd.dept_name as departmentName from qw_external_contact ec " +
+            "select ec.*,qu.qw_user_name,qd.dept_name as departmentName,cw.name way_name,wg.group_name way_group_name from qw_external_contact ec " +
             "left join qw_user qu on ec.user_id=qu.qw_user_id and qu.corp_id=ec.corp_id " +
             "left join qw_dept qd on qd.dept_id=qu.department and qd.corp_id=qu.corp_id " +
             "left join company_user cu on ec.company_user_id=cu.user_id " +
+            "left join qw_contact_way cw on cw.id = ec.way_id " +
+            "left join qw_contact_way_group wg on wg.id=cw.group_id" +
             "<where>  \n" +
             "            <if test=\"userId != null  and userId != ''\"> and ec.user_id   like concat( #{userId}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name   like concat( #{qwUserName}, '%') </if>\n" +
@@ -506,4 +508,14 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     List<QwExternalContact> selectQwExternalContactByFsUserIdAndCompany(@Param("userId")Long userId,@Param("companyUserId") Long companyUserId);
 
     void updateQwExternalContactIsRePlyById(@Param("id")Long id);
+
+    @Select("SELECT max(id) FROM qw_external_contact where status <>'4' and unionid is NULL")
+    Long selectSyncMaxId();
+
+    @Select("SELECT * FROM qw_external_contact where id >#{recordId} and id <=#{maxId} and status <>'4' and unionid is NULL order by id asc limit 500")
+    List<QwExternalContact> selectSyncData(@Param("recordId")Long recordId,@Param("maxId")Long maxId);
+
+    @Update("update qw_external_contact set unionid = #{item.unionid} where id = #{item.id}")
+    int batchUpdateUnionId(@Param("item") QwExternalContact item);
+
 }

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

@@ -206,6 +206,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_uame like concat( #{qwUserName}, '%') </if>\n" +
             "            <if test=\"companyUserId != null \"> and qu.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"corpId != null \"> and qu.corp_id = #{corpId}</if>\n" +
+            "            <if test=\"deptId != null \"> and qd.dept_id = #{deptId}</if>\n" +
             "            <if test=\"status != null \"> and qu.status = #{status}</if>\n" +
             "            <if test=\"type != null and sendType !=null and type==2 and (sendType==2 or sendType==4 or sendType==11) \"> and qu.app_key IS NOT NULL  </if>\n" +
             "</script>"})

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

@@ -72,4 +72,6 @@ public interface QwUserVideoMapper
      * @return 结果
      */
     public int deleteQwUserVideoByIds(Long[] ids);
+
+    QwUserVideo selectByObjectId(@Param("objectId") String objectId, @Param("id") Long id);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/qw/param/QwFriendWelcomeParam.java

@@ -72,4 +72,11 @@ public class QwFriendWelcomeParam extends BaseEntity {
      */
     private String userType;
 
+    /**
+     * 欢迎语标题
+     * ALTER TABLE qw_friend_welcome ADD welcome_title varchar(100) NULL COMMENT '标题';
+     */
+    private String welcomeTitle;
+
+
 }

+ 7 - 0
fs-service/src/main/java/com/fs/qw/param/QwUserListParam.java

@@ -62,4 +62,11 @@ public class QwUserListParam {
      */
     private String userType;
 
+    /**
+     * 0:好友欢迎语
+     * 1:我的欢迎语
+     * 2、部门欢迎语
+     */
+    private String isRemark;
+
 }

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

@@ -5,6 +5,7 @@ import com.fs.common.utils.DateUtils;
 import lombok.Data;
 
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class QwWatchLogStatisticsListParam {
@@ -30,6 +31,7 @@ public class QwWatchLogStatisticsListParam {
     private Long companyUserId;
     private Long id;
     private String ids;
+    private List<String> idsList;
     private Long project;
     private Long courseId;
     private Long videoId;

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

@@ -62,4 +62,7 @@ public interface IQwUserVideoService
      * @return 结果
      */
     public int deleteQwUserVideoById(Long id);
+
+
+    QwUserVideo selectByObjectId(String objectId, Long id);
 }

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

@@ -104,6 +104,9 @@ public class QwContactWayServiceImpl implements IQwContactWayService
         log.info("新增企微活码corpId"+corpId);
         qwContactWay.setType(type);
         qwContactWay.setCreateTime(DateUtils.getNowDate());
+        if (null==qwContactWay.getSort()) {
+            qwContactWay.setSort(0);
+        }
         qwContactWayMapper.insertQwContactWay(qwContactWay);
         Long id = qwContactWay.getId();
         qwAddContactWayParam.setType(type);

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

@@ -50,6 +50,7 @@ import com.fs.qwApi.domain.*;
 import com.fs.qwApi.domain.inner.*;
 import com.fs.qwApi.param.*;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -1057,6 +1058,8 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                     } else {
                         qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
                     }
+
+                    logger.info("成功同步一个用户");
                 }
 
             }else {
@@ -2175,7 +2178,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     @Override
     public QwUser getQwUserByRedisForId(String qwUserId) {
-
+        if(StringUtils.isEmpty(qwUserId)){
+            return null;
+        }
         String redisKey = "qwUserRdById:" + qwUserId;
 
         // 直接从Redis获取JSON字符串
@@ -2249,6 +2254,21 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 //            logger.error("重粉提交mq失败", e);
 //        }
 //        iAdHtmlClickLogService.upload(state, AdUploadType.ADD_WX, e -> finalQwExternalContact.setUploadAddWxStatus(1));
+        //重粉问题
+        try {
+            new Thread(() -> {
+                try {
+                    Thread.sleep(3000);
+                } catch (InterruptedException e) {
+                    logger.error("添加等待时长错误", e);
+                }
+                rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(0).externalUserId(externalUserID).build()));
+            }).start();
+        }catch (Exception e){
+            logger.error("重粉提交mq失败", e);
+        }
+
+
 
         //先入一次库
         if (isNewQwExternalContact) {
@@ -2430,6 +2450,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
                     if (wayLogs==null){
 
+
                         QwAppContactWayLogs qwAppContactWayLogs = new QwAppContactWayLogs();
                         qwAppContactWayLogs.setAppWayId(qwAppContactWay.getId());
                         qwAppContactWayLogs.setUserId(qwAppContactWay.getUserId());
@@ -2604,8 +2625,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                 //渠道活码的标签
                                 List<String> wayTags = JSON.parseArray(wayId.getTags(), String.class);
                                 //总添加标签
-                                combinedTagsSet.addAll(wayTags);
-                                logger.info("渠道活码标签,{}===============", wayTags);
+//                                combinedTagsSet.addAll(wayTags);
+                                combinedTagsSet = new HashSet<>(wayTags);
+                                logger.info("渠道活码标签,{}===============,打的标签={}", wayTags,combinedTagsSet);
                             }
 
                             //总标签 转换回列表
@@ -2676,18 +2698,18 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                     }
                     if (wayId.getIsRemark() == 1) {
                         if (wayId.getRemarkStatus() == 1) {
-                            if (!StringUtil.strIsNullOrEmpty(tagRemark)){
-                                qwExternalContact.setRemark(externalContact.getName() + "-" + wayId.getRemark()+"-"+tagRemark);
-                            }else {
+//                            if (!StringUtil.strIsNullOrEmpty(tagRemark)){
+//                                qwExternalContact.setRemark(externalContact.getName() + "-" + wayId.getRemark()+"-"+tagRemark);
+//                            }else {
                                 qwExternalContact.setRemark(externalContact.getName() + "-" + wayId.getRemark());
-                            }
+//                            }
 
                         } else {
-                            if (!StringUtil.strIsNullOrEmpty(tagRemark)){
-                                qwExternalContact.setRemark(tagRemark+"-"+wayId.getRemark() + "-" + externalContact.getName());
-                            }else {
+//                            if (!StringUtil.strIsNullOrEmpty(tagRemark)){
+//                                qwExternalContact.setRemark(tagRemark+"-"+wayId.getRemark() + "-" + externalContact.getName());
+//                            }else {
                                 qwExternalContact.setRemark(wayId.getRemark() + "-" + externalContact.getName());
-                            }
+//                            }
 
                         }
                     }
@@ -4871,7 +4893,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         for (QwOptionsVO qwOptionsVO : qwOptionsVOS) {
             String corpId = qwOptionsVO.getCorpId();
 //            System.out.println("同步的"+corpId);
-//            logger.info("同步的"+corpId);
+            logger.info("同步的"+corpId);
             executor.execute(() ->  qwExternalContactSyncByCorpId(corpId));
         }
         executor.shutdown();

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

@@ -100,4 +100,8 @@ public class QwUserVideoServiceImpl implements IQwUserVideoService
     {
         return qwUserVideoMapper.deleteQwUserVideoById(id);
     }
+    @Override
+    public QwUserVideo selectByObjectId(String objectId, Long id) {
+        return qwUserVideoMapper.selectByObjectId(objectId, id);
+    }
 }

+ 4 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java

@@ -175,7 +175,10 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
             vo.setFirstOnline(stat.getFirstOnline());
             vo.setFirstOver(stat.getFirstOver());
         }
-
+        if (StringUtils.isNotBlank(param.getIds())) {
+            String replace = param.getIds().replace("(", "").replace(")", "");
+            param.setIdsList(Arrays.asList(replace.split(",")));
+        }
         Long total = qwWatchLogMapper.selectQwExtCountByDayAndCount(param);
         rspData.setRows(vos);
         rspData.setTotal(total);

+ 12 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -120,6 +120,8 @@ public class QwExternalContactVO {
     private Long fsUserId;
     @Excel(name = "客户等级")
     private Long level;
+    //下单次数
+    private Long orderCount;
     @Excel(name = "等级升降")
     private Long levelType;
 
@@ -127,4 +129,14 @@ public class QwExternalContactVO {
      * 是否回复
      */
     private Integer isReply;
+
+    /**
+     * 联系我名称
+     */
+    private String wayName;
+
+    /**
+     * 联系我分组名称
+     */
+    private String wayGroupName;
 }

+ 6 - 1
fs-service/src/main/java/com/fs/qw/vo/QwFriendWelcomeVO.java

@@ -10,7 +10,7 @@ import java.util.List;
 
 /**
  * 好友欢迎语对象 qw_friend_welcome
- * 
+ *
  * @author fs
  * @date 2024-07-20
  */
@@ -60,6 +60,11 @@ public class QwFriendWelcomeVO extends BaseEntity
     /** 分时段欢迎语 */
     private String daypartingItemlist;
 
+    /**
+     * 欢迎语标题
+     */
+    private String welcomeTitle;
+
     public List<QwUserVO> userSelectList;
 
 }

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

@@ -10,6 +10,7 @@ import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.CompanyQwUserByIdsVo;
 import com.fs.fastgptApi.util.AudioUtils;
@@ -46,7 +47,7 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
     private final IQwSopTempContentService qwSopTempContentService;
     private final QwSopMapper qwSopMapper;
     private final QwSopTempDayMapper qwSopTempDayMapper;
-    private final ICompanyUserService companyUserService;
+    private final CompanyUserMapper companyUserMapper;
     private final QwSopTempVoiceMapper qwSopTempVoiceMapper;
     /**
      * 查询模板对应的销售语音文件
@@ -177,7 +178,7 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
             return;
         }
         List<Long> qwUserIdList = sopList.stream().flatMap(e -> Arrays.stream(e.getQwUserIds().split(","))).distinct().filter(StringUtils::isNotEmpty).map(Long::parseLong).collect(Collectors.toList());
-        List<CompanyQwUserByIdsVo> companyUserList = companyUserService.selectCompanyQwUserByIds(qwUserIdList);
+        List<CompanyQwUserByIdsVo> companyUserList = companyUserMapper.selectCompanyQwUserByIds(qwUserIdList);
         if (companyUserList.isEmpty()) {
             log.info("sop任务里面的销售为空跳过生成");
             qwSopTempDayMapper.updateVoice(dayId);

+ 8 - 6
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -545,6 +545,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSort(30000001);
                     sopLogs.setSendType(6);
                     sopLogs.setExternalUserName(groupUser.getName());
+                    sopLogs.setQwUserKey(qwUser.getId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
                     String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
@@ -608,7 +609,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //小程序单独
                             case "4":
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
-                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
+                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, qwGroupChat.getChatId());
 
                                 if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
                                     log.error("配置中无小程序id,采用默认的");
@@ -655,6 +656,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSort(2);
                     sopLogs.setSendType(12);
                     sopLogs.setExternalUserName(groupChat.getName());
+                    sopLogs.setQwUserKey(qwUser.getId());
 
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
 
@@ -712,7 +714,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //小程序单独
                             case "4":
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
-                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
+                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, groupChat.getChatId());
 
                                 if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
                                     log.error("配置中无小程序id,采用默认的");
@@ -869,7 +871,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),item.getStartTime(),createTime );
 
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                    Long.valueOf(qwUserId), companyUserId, companyId, item.getExternalId(), config);
+                                    Long.valueOf(qwUserId), companyUserId, companyId, item.getExternalId(), config, null);
 
                             String miniAppId = null;
 
@@ -1304,7 +1306,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             item.getExternalId(),item.getStartTime(),dataTime );
 
                     String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
-                            qwUser.getId(), companyUserId, companyId, item.getExternalId(), config);
+                            qwUser.getId(), companyUserId, companyId, item.getExternalId(), config, null);
 
 
                     String miniAppId = null;
@@ -1480,10 +1482,10 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
                                      Integer courseId, Integer videoId, Long qwUserId,
-                                     String companyUserId, String companyId, Long externalId,CourseConfig config) {
+                                     String companyUserId, String companyId, Long externalId,CourseConfig config, String chatId) {
 
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
-                                                companyUserId, companyId, externalId,3,null);
+                                                companyUserId, companyId, externalId,3,chatId);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link,courseMap);

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

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

+ 1 - 1
fs-service/src/main/java/com/fs/statis/mapper/FsStatisSalerWatchMapper.java

@@ -79,7 +79,7 @@ public interface FsStatisSalerWatchMapper {
     @Delete("DELETE FROM fs_statis_saler_watch WHERE id = #{id}")
     int deleteById(@Param("id") Integer id);
 
-    @DataSource(DataSourceType.SLAVE)
+//    @DataSource(DataSourceType.SLAVE)
     List<FsStatisSalerWatch> queryList(StatsWatchLogPageListDTO param);
 
     void batchSave(@Param("list") List<FsStatisSalerWatch> writeData);

+ 10 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java

@@ -33,4 +33,14 @@ public class WxWorkMessageDTO {
     // 语音
     private String voice_id;
     private Integer voice_size;
+
+    // 视频号
+    String objectId;//视频号ID
+    String nickname;//标题
+    String thumb_url;//视频号封面
+    String cover_url;//视频号切图
+    String avatar;//视频号头像
+    String desc;//视频号描述
+    String extras;//视频号扩展字段
+    String objectNonceId;//视频号扩展字段
 }

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

@@ -0,0 +1,94 @@
+baidu:
+  token: 12313231232
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+      - appid: wx94951f52d3ac5e25   #北京存在文化
+        secret: bfe27b20c6e3c4232a1d4ef36228e84b #北京存在文化
+        token: Ncbnd7lJvkripxxna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
+  cp:
+    corpId: wwa46ffb9ff6ac35b8 #企业ID北京存在文化
+    appConfigs:
+      - agentId: 1000070       #北京存在文化
+        secret: pu2EFz6gY2Fo2K-aRUxLPaAkKIaMJJRp8ES9JdpHkp4 #北京存在文化
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId:  #微信公众号或者小程序等的appid
+    mchId:  #微信支付商户号
+    mchKey:  #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wxd6905bed94e45ef0 # 第一个公众号的appid  //公众号名称:济南联智健康
+        secret: a7a59d1536e9eae16108a5ad627fa4a3 # 公众号的appsecret--济南联智健康
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+  #  account: tcloud
+  #  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://127.0.0.1:7771
+  h5CommonApi: http://127.0.0.1:7771
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: jnlzjk-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: jnlzjk
+tmp_secret_config:
+  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
+  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
+  bucket: fs-1319721001
+  app_id: 1319721001
+  region: ap-chongqing
+  proxy: fs
+cloud_host:
+  company_name: 济南联志健康
+  projectCode: LZJK
+headerImg:
+  imgUrl:
+
+ipad:
+  ipadUrl: http://ipadjnlzjk.ylrztop.com
+  aiApi: http://49.232.181.28:3000/
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+
+

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

@@ -54,7 +54,7 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wx961fadab9bcb792b # 第一个公众号的appid  //公众号名称:云联
+      - appId: wx961fadab9bcb792b # 第一个公众号的appid  //公众号名称:壹道正气
         secret: 8adb2a7533921449ef6e60814c2ff075
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值

+ 2 - 2
fs-service/src/main/resources/application-config-druid-hcl.yml

@@ -42,8 +42,8 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId: wx2f17bb412271cfe6 # 第一个公众号的appid  //公众号名称:
-        secret: 47b6d65c45719e3e40392e19996587b3 # 公众号的appsecret
+      - appId: wx5d3e8c6791f0676d # 第一个公众号的appid  //公众号名称:善德佑
+        secret: d336ec63efd88b47f89b6a329a41c98d # 公众号的appsecret
         token: PPKOdAlCoMO # 接口配置里的Token值
         aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接

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

@@ -84,9 +84,9 @@ headerImg:
 
 ipad:
   ipadUrl: http://ipadjnlzjk.ylrztop.com
-  aiApi: http://49.232.181.28:3000/
-  voiceApi:
-  commonApi:
+  aiApi: http://49.232.181.28:3000/api
+  voiceApi: http://129.28.187.88:8667
+  commonApi: http://129.28.187.88:7771
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:

+ 94 - 0
fs-service/src/main/resources/application-config-druid-jnsyj.yml

@@ -0,0 +1,94 @@
+baidu:
+  token: 1231323123
+  back-domain: https://www.xxxx.com
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  miniapp:
+    configs:
+#      - appid: wx4115995705bb0ea0   #中康智慧
+#        secret: 58910ae743005c396012b029c7def579
+#        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+#        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+#        msgDataFormat: JSON
+#      - appid: wxedde588767b358b1   #中康未来智慧药房
+#        secret: 928d2961c81610d8f64b019597212fcd
+#        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+#        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+#        msgDataFormat: JSON
+  cp:
+    corpId: wwb2a10556ca7c2
+    appConfigs:
+      - agentId: 10005
+        secret: ec7okROXJqkNafq66aKNv0asTQIG0CYrj3vyBbo
+        token: PPKOdAoMO
+        aesKey: PKvaxtpSvNGpfTDmVUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85fd6119 #微信公众号或者小程序等的appid
+    mchId: 161145 #微信支付商户号
+    mchKey: 8cab128997a347c10898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://usepp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx961e754dd68694f7 # 第一个公众号的appid  //公众号名称:顺亿景服务中心
+        secret: 60ed7cf5a079664b3513a1b9eafab2d4 # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值抖音免领及代运营        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+  open:
+    appId: wx0943a6f6d040ca3a
+    secret: 602e2a9ea639b6cd18eb80e715c4b22e
+aifabu:  #爱链接
+  appKey: 7b471be905ab17ef358c610d117601d008
+
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.29:8010
+  h5CommonApi: http://119.29.195.254:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: jnsyj-1323137866
+  app_id: 1323137866
+  region: ap-chongqing
+  proxy: jnsyj
+cloud_host:
+  company_name: 济南顺亿景
+  projectCode: JNSYJ
+#看课授权时显示的头像
+headerImg:
+  imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+ipad:
+  ipadUrl: http://ipad.hebeihdt.com
+#  aiApi: http://152.136.202.157:3000/api
+  aiApi: http://49.232.181.28:3000/api
+  voiceApi:
+  commonApi:
+wx_miniapp_temp:
+  pay_order_temp_id:
+  inquiry_temp_id:
+

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

@@ -0,0 +1,153 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-dev-jnlzjk,common
+    # redis 配置
+    redis:
+        host: localhost
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password:
+        # 连接超时时间
+        timeout: 30s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 100
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+#        clickhouse:
+#            type: com.alibaba.druid.pool.DruidDataSource
+#            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+#            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+#            username: rt_2024
+#            password: Yzx_19860213
+#            initialSize: 10
+#            maxActive: 100
+#            minIdle: 10
+#            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                  url: jdbc:mysql://nj-cdb-22nvjajz.sql.tencentcdb.com:29237/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                  username: root
+                  password: Ylrz_1q2w3e4r5t6y
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://nj-cdb-22nvjajz.sql.tencentcdb.com:29237/fs_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_1q2w3e4r5t6y
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-16xj8o92zp.rocketmq.cd.qcloud.tencenttdmq.com:8080
+    producer:
+        group: my-producer-group
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
+    consumer:
+        group: common-group
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
+openIM:
+    secret: openIM123
+    userID: imAdmin
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: true

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

@@ -11,7 +11,7 @@ spring:
         # 数据库索引
         database: 0
         # 密码
-#        password: Ylrz_c123232014^$
+        password:
         # 连接超时时间
         timeout: 20s
         lettuce:

+ 10 - 13
fs-service/src/main/resources/application-druid-cqtyt.yml

@@ -8,12 +8,10 @@ spring:
         host: r-2zexagt5g4z7arviu5.redis.rds.aliyuncs.com
         # 端口,默认为6379
         port: 6379
-        # 数据库索引
-        database: 0
         # 密码
         password: Ylrz_c123232014^$
         # 连接超时时间
-        timeout: 20s
+        timeout: 30s
         lettuce:
             pool:
                 # 连接池中的最小空闲连接
@@ -24,17 +22,8 @@ spring:
                 max-active: 8
                 # #连接池最大阻塞等待时间(使用负值表示没有限制)
                 max-wait: -1ms
+        database: 0
     datasource:
-        #        clickhouse:
-        #            type: com.alibaba.druid.pool.DruidDataSource
-        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-        #            username: rt_2024
-        #            password: Yzx_19860213
-        #            initialSize: 10
-        #            maxActive: 100
-        #            minIdle: 10
-        #            maxWait: 6000
         mysql:
             type: com.alibaba.druid.pool.DruidDataSource
             driverClassName: com.mysql.cj.jdbc.Driver
@@ -142,6 +131,14 @@ spring:
                     wall:
                         config:
                             multi-statement-allow: true
+redisson:
+    config: |
+        singleServerConfig:
+          address: "redis://r-2zexagt5g4z7arviu5.redis.rds.aliyuncs.com:6379"
+          password: "Ylrz_c123232014^$"
+          database: 0
+        checkKeysEventsConfig: false
+
 rocketmq:
     name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
     producer:

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

@@ -152,4 +152,4 @@ openIM:
     secret: op
     userID: im
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
+isNewWxMerchant: false

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

@@ -154,4 +154,4 @@ openIM:
     secret: openIM123
     userID: imAdmin
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
+isNewWxMerchant: false

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

@@ -41,7 +41,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://120.46.174.121:2345/fs_his_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://120.46.174.121:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrztek250218!3@.
                 # 从库数据源

+ 162 - 0
fs-service/src/main/resources/application-druid-jnsyj.yml

@@ -0,0 +1,162 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-jnsyj,common
+    # redis 配置
+    redis:
+        # 地址  localhost
+        host: 172.16.0.17
+#        host: localhost
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password: Ylrz_c123232014^$
+#        password:
+        # 连接超时时间
+        timeout: 10s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.26:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 从库数据源
+                slave:
+                    url: jdbc:mysql://172.16.0.33:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                    # 从数据源开关/默认关闭
+                    enabled: true
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 1000
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://172.16.0.26:3306/fs_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                read:
+
+                    url: jdbc:mysql://172.16.0.33:3306/fs_his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrz_c123232014^$
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 200
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: 1:8100 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group   # 生产者组名(必须唯一)
+        access-key: default
+        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
+    consumer:
+        group: common-group
+        access-key: default
+        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
+openIM:
+    secret: openIM123
+    userID: imAdmin
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false

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

@@ -165,4 +165,6 @@ openIM:
     secret: openIM123
     userID: imAdmin
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
+isNewWxMerchant: false
+
+enableRedPackAccount: 0

+ 38 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -54,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyId" column="company_id"/>
         <result property="status" column="status"/>
         <result property="qwStatus" column="qw_status"/>
+        <result property="qwUserId" column="qw_user_id"/>
         <result property="phonenumber" column="phonenumber"/>
         <result property="createTime" column="create_time"/>
         <result property="deptId" column="dept_id"/>
@@ -91,6 +92,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         u.qw_status, u.phonenumber, u.create_time, u.dept_id,
         u.qr_code_weixin, u.user_type, u.qr_code_wecom, u.jpush_id,
         u.avatar,
+        u.qw_user_id,
         d.dept_name,
         d.leader,
         cr.role_name as role_name
@@ -302,6 +304,42 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where user_id = #{userId}
     </update>
 
+    <update id="updateAllCompanyUser" parameterType="CompanyUser">
+        update company_user
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="deptId != null">dept_id = #{deptId},</if>
+            <if test="userName != null and userName != ''">user_name = #{userName},</if>
+            <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
+            <if test="email != null">email = #{email},</if>
+            <if test="phonenumber != null">phonenumber = #{phonenumber},</if>
+            <if test="sex != null">sex = #{sex},</if>
+            <if test="avatar != null">avatar = #{avatar},</if>
+            <if test="idCard != null">id_card = #{idCard},</if>
+            <if test="password != null and password != '' ">password = #{password},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="delFlag != null">del_flag = #{delFlag},</if>
+            <if test="loginIp != null">login_ip = #{loginIp},</if>
+            <if test="loginDate != null">login_date = #{loginDate},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="token != null">token = #{token},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="qrCodeWeixin != null">qr_code_weixin = #{qrCodeWeixin},</if>
+            <if test="qrCodeWecom != null">qr_code_wecom = #{qrCodeWecom},</if>
+            <if test="jpushId != null">jpush_id = #{jpushId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="qwStatus != null">qw_status = #{qwStatus},</if>
+            <if test="domain != null">`domain` = #{domain},</if>
+            <if test="isAudit != null">`is_audit` = #{isAudit},</if>
+            <if test="addressId != null">`address_id` = #{addressId},</if>
+            <if test="maOpenId != null">`ma_open_id` = #{maOpenId},</if>
+        </trim>
+        where company_id = #{companyId}
+    </update>
+
     <delete id="deleteCompanyUserById" parameterType="Long">
         delete from company_user where user_id = #{userId}
     </delete>

+ 30 - 0
fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml

@@ -182,4 +182,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsCourseRedPacketLogHourseByCompany" resultType="com.fs.company.vo.RedPacketMoneyVO">
         SELECT a.company_id, SUM(amount) as money  FROM fs_course_red_packet_log a WHERE a.create_time &gt;= #{startTime} AND a.create_time &lt;= #{endTime} GROUP BY a.company_id
     </select>
+    <!-- 看客红包统计   -->
+    <select id="statistics" resultType="com.fs.course.dto.CourseRedPacketStatisticsDTO">
+       select c.company_name,cu.nick_name,pl.company_user_id,cu.company_id,sum(pl.amount) as redPacketTotalMoney,count(pl.log_id) as redPacketNum from fs_course_red_packet_log pl
+           left join  company_user cu on pl.company_user_id = cu.user_id
+       left join company c on cu.company_id=c.company_id
+       <where>
+           <if test="companyId != null">
+               and cu.company_id = #{companyId}
+           </if>
+           <if test="companyUserId != null">
+               and pl.company_user_id = #{companyUserId}
+           </if>
+            <if test="status != null">
+               and pl.status = #{status}
+           </if>
+           <if test="startTime != null">
+               and date_format(pl.create_time,'%Y-%m-%d') &gt;= date_format(#{startTime},'%Y-%m-%d')
+           </if>
+            <if test="endTime != null">
+               and date_format(pl.create_time,'%Y-%m-%d') &lt;= date_format(#{endTime},'%Y-%m-%d')
+           </if>
+       </where>
+       group by pl.company_user_id order by c.company_name
+    </select>
+    <!-- 获取当前时间前十分钟到前二十分钟红包记录状态为发送中的记录   -->
+    <select id="selectFsCourseRedPacketLogListBySending"
+            resultType="com.fs.course.domain.FsCourseRedPacketLog">
+        <include refid="selectFsCourseRedPacketLogVo"/> where status != 1 and create_time &gt;= #{maps.startTime} and create_time &lt;= #{maps.endTime}
+
+    </select>
 </mapper>

+ 4 - 3
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -467,10 +467,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id=#{companyId}
             </if>
             <if test= 'sTime != null '>
-                and o.create_time &gt;= #{sTime}
+                and DATE(o.create_time) &gt;= #{sTime}
             </if>
             <if test='eTime != null '>
-                and o.create_time &lt;= #{eTime}
+                and DATE(o.create_time) &lt;= #{eTime}
             </if>
             <if test ='courseId !=null'>
                 and o.course_id = #{courseId}
@@ -492,7 +492,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY o.video_id,o.user_id,DATE(o.create_time),o.project,o.course_id
         ORDER BY o.video_id ,DATE(o.create_time)
 
-        limit ${(pageNum-1)*pageSize},${pageSize}
+<!--        limit ${(pageNum-1)*pageSize},${pageSize}-->
     </select>
     <select id="selectFsCourseWatchLogStatisticsListVONewCount" resultType="java.lang.Long">
         SELECT COUNT(*)
@@ -764,6 +764,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         l.video_id,
         l.qw_user_id,
         l.qw_external_contact_id,
+        l.sop_id,
         qec.create_time as qec_create_time
         FROM
         fs_course_watch_log l LEFT JOIN qw_external_contact qec on l.qw_external_contact_id = qec.id

+ 34 - 0
fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml

@@ -2100,5 +2100,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND pay_time &lt; DATE_ADD(STR_TO_DATE(#{map.payTime}, '%Y-%m-%d'), INTERVAL 1 DAY)
         </if>
     </select>
+    <select id="selectFsStoreOrderAmountStats" resultType="com.fs.his.vo.FsStoreOrderAmountStatsVo">
+        SELECT
+        COUNT(*) AS totalOrderCount,
+        COUNT(CASE WHEN pay_type = '1' THEN 1 END) AS fullPayOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') THEN 1 END) AS codOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') AND COALESCE(pay_money, 0) > 0 THEN 1 END) AS depositCodOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') AND COALESCE(pay_money, 0) =0 THEN 1 END) AS noDepositCodOrderCount,
+        SUM(pay_price) AS totalOrderAmount,
+        SUM(
+        CASE
+        WHEN pay_type IN ('2', '3')
+        THEN COALESCE(pay_money, 0)
+        ELSE 0
+        END
+        ) AS depositAmount,
+
+        SUM(COALESCE(pay_remain, 0)) AS codAmount
+
+        FROM fs_store_order
+        WHERE is_del = 0 AND status > 1
+        <if test="startTime != null">
+            AND create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            AND create_time &lt;= #{endTime}
+        </if>
+
+        <if test="companyId != null">
+            AND company_id = #{companyId}
+        </if>
+        <if test="companyUserId != null">
+            AND company_user_id = #{companyUserId}
+        </if>
+    </select>
 
 </mapper>

+ 28 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -1109,4 +1109,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           AND o.paid = 1
           AND o.is_del = 0
     </select>
+    <select id="selectFsStoreOrderAmountScrmStats" resultType="com.fs.his.vo.FsStoreOrderAmountScrmStatsVo">
+        SELECT
+        COUNT(*) AS totalOrderCount,
+        COUNT(CASE WHEN pay_type = '1' THEN 1 END) AS fullPayOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') THEN 1 END) AS codOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') AND COALESCE(pay_money, 0) > 0 THEN 1 END) AS depositCodOrderCount,
+        COUNT(CASE WHEN pay_type IN ('2', '3') AND COALESCE(pay_money, 0) = 0 THEN 1 END) AS noDepositCodOrderCount,
+        SUM(pay_price) AS totalOrderAmount,
+        SUM(CASE WHEN pay_type IN ('2', '3') THEN COALESCE(pay_money, 0) ELSE 0 END) AS depositAmount,
+
+        SUM(CASE WHEN pay_type IN ('2', '3') THEN COALESCE(pay_price, 0) ELSE 0 END)
+        -
+        SUM(CASE WHEN pay_type IN ('2', '3') THEN COALESCE(pay_money, 0) ELSE 0 END) AS codAmount
+        FROM fs_store_order_scrm
+        WHERE is_del = 0 AND status > 0
+        <if test="startTime != null">
+            AND create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null">
+            AND create_time &lt;= #{endTime}
+        </if>
+        <if test="companyId != null">
+            AND company_id = #{companyId}
+        </if>
+        <if test="companyUserId != null">
+            AND company_user_id = #{companyUserId}
+        </if>
+    </select>
 </mapper>

+ 33 - 31
fs-service/src/main/resources/mapper/hisStore/FsUserScrmMapper.xml

@@ -69,10 +69,11 @@
         <result property="qwExtId"    column="qw_ext_id"    />
         <result property="rank"    column="rank"    />
         <result property="react"    column="react"    />
+        <result property="orderCount"    column="order_count"    />
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id, username, nick_name, avatar, phone, integral, status, `level`, spread_user_id, spread_time, user_type, is_promoter, pay_count, spread_count, addres, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, nickname, create_time, update_time, last_ip, now_money, brokerage_price, balance, sign_num, integral_status, is_buy, password, real_name, birthday, id_card, jpush_id, is_vip, vip_start_date, vip_end_date, vip_level, vip_status, sex, store_open_id, is_official_account_auth, is_push, is_individuation_push, is_weixin_auth, company_id, company_user_id, register_date, register_code, test, login_device, source, is_add_qw, qw_user_id, is_show, parent_id, course_ma_open_id, history_app, qw_ext_id, `rank`, react from fs_user
+        select user_id, username, nick_name, avatar, phone, integral, status, `level`, spread_user_id, spread_time, user_type, is_promoter, pay_count, spread_count, addres, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, nickname, create_time, update_time, last_ip, now_money, brokerage_price, balance, sign_num, integral_status, is_buy, password, real_name, birthday, id_card, jpush_id, is_vip, vip_start_date, vip_end_date, vip_level, vip_status, sex, store_open_id, is_official_account_auth, is_push, is_individuation_push, is_weixin_auth, company_id, company_user_id, register_date, register_code, test, login_device, source, is_add_qw, qw_user_id, is_show, parent_id, course_ma_open_id, history_app, qw_ext_id, `rank`, react,order_count from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUserScrm" resultMap="FsUserResult">
@@ -444,6 +445,7 @@
             <if test="qwExtId != null">qw_ext_id,</if>
             <if test="rank != null">rank,</if>
             <if test="react != null">react,</if>
+            <if test="orderCount != null">order_count,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="username != null">#{username},</if>
@@ -509,55 +511,55 @@
             <if test="qwExtId != null">#{qwExtId},</if>
             <if test="rank != null">#{rank},</if>
             <if test="react != null">#{react},</if>
+            <if test="orderCount != null">#{orderCount},</if>
         </trim>
     </insert>
 
     <update id="updateFsUser" parameterType="FsUserScrm">
         update fs_user
         <trim prefix="SET" suffixOverrides=",">
-            <if test="username != null">username = #{username},</if>
-            <if test="password != null">password = #{password},</if>
-            <if test="realName != null">real_name = #{realName},</if>
-            <if test="birthday != null">birthday = #{birthday},</if>
-            <if test="idCard != null">id_card = #{idCard},</if>
-            <if test="remark != null">remark = #{remark},</if>
+            <if test="nickName != null">nick_name = #{nickName},</if>
             <if test="nickname != null">nickname = #{nickname},</if>
             <if test="avatar != null">avatar = #{avatar},</if>
             <if test="phone != null">phone = #{phone},</if>
-            <if test="createTime != null">create_time = #{createTime},</if>
-            <if test="updateTime != null">update_time = #{updateTime},</if>
-            <if test="lastIp != null">last_ip = #{lastIp},</if>
-            <if test="nowMoney != null">now_money = #{nowMoney},</if>
-            <if test="brokeragePrice != null">brokerage_price = #{brokeragePrice},</if>
             <if test="integral != null">integral = #{integral},</if>
             <if test="signNum != null">sign_num = #{signNum},</if>
             <if test="status != null">status = #{status},</if>
-            <if test="level != null">level = #{level},</if>
-            <if test="spreadUserId != null">spread_user_id = #{spreadUserId},</if>
-            <if test="spreadTime != null">spread_time = #{spreadTime},</if>
-            <if test="userType != null and userType != ''">user_type = #{userType},</if>
-            <if test="isPromoter != null">is_promoter = #{isPromoter},</if>
-            <if test="payCount != null">pay_count = #{payCount},</if>
-            <if test="spreadCount != null">spread_count = #{spreadCount},</if>
-            <if test="addres != null and addres != ''">addres = #{addres},</if>
+            <if test="tuiUserId != null">tui_user_id = #{tuiUserId},</if>
+            <if test="tuiTime != null">tui_time = #{tuiTime},</if>
+            <if test="tuiUserCount != null">tui_user_count = #{tuiUserCount},</if>
             <if test="maOpenId != null">ma_open_id = #{maOpenId},</if>
             <if test="mpOpenId != null">mp_open_id = #{mpOpenId},</if>
             <if test="unionId != null">union_id = #{unionId},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
+            <if test="userCode != null">user_code = #{userCode},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="lastIp != null">last_ip = #{lastIp},</if>
+            <if test="balance != null">balance = #{balance},</if>
+            <if test="integralStatus != null">integral_status = #{integralStatus},</if>
+            <if test="isBuy != null">is_buy = #{isBuy},</if>
+            <if test="password != null">password = #{password},</if>
+            <if test="jpushId != null">jpush_id = #{jpushId},</if>
+            <if test="isVip != null">is_vip = #{isVip},</if>
+            <if test="vipStartDate != null">vip_start_date = #{vipStartDate},</if>
+            <if test="vipEndDate != null">vip_end_date = #{vipEndDate},</if>
+            <if test="vipLevel != null">vip_level = #{vipLevel},</if>
+            <if test="vipStatus != null">vip_status = #{vipStatus},</if>
+            <if test="sex != null">sex = #{sex},</if>
+            <if test="storeOpenId != null">store_open_id = #{storeOpenId},</if>
+            <if test="isPush != null">is_push = #{isPush},</if>
+            <if test="isIndividuationPush != null">is_individuation_push = #{isIndividuationPush},</if>
             <if test="isWeixinAuth != null">is_weixin_auth = #{isWeixinAuth},</if>
-            <if test="companyId != null">company_id = #{companyId},</if>
-            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
-            <if test="registerDate != null">register_date = #{registerDate},</if>
-            <if test="registerCode != null">register_code = #{registerCode},</if>
+            <if test="loginDevice != null">login_device = #{loginDevice},</if>
             <if test="source != null">source = #{source},</if>
-            <if test="userCode != null">user_code = #{userCode},</if>
-            <if test="isShow != null">is_show = #{isShow},</if>
-            <if test="qwExtId != null">qw_ext_id = #{qwExtId},</if>
             <if test="isAddQw != null">is_add_qw = #{isAddQw},</if>
-            <!--<if test="qwRepeat != null">qw_repeat = #{qwRepeat},</if>
-            <if test="userRepeat != null">user_repeat = #{userRepeat},</if>
-            <if test="payOrder != null">pay_order = #{payOrder},</if>
-            <if test="isBecomeMember != null">is_become_member = #{isBecomeMember},</if>-->
+            <if test="courseMaOpenId != null">course_ma_open_id = #{courseMaOpenId},</if>
+            <if test="parentId != null">parent_id = #{parentId},</if>
+            <if test="qwExtId != null">qw_ext_id = #{qwExtId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
         </trim>
         where user_id = #{userId}
     </update>

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

@@ -42,10 +42,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="welcomeJson"    column="welcome_json"    />
         <result property="corpId"    column="corp_id"    />
         <result property="informationId"    column="information_id"    />
+        <result property="sort"    column="sort"    />
     </resultMap>
 
     <sql id="selectQwContactWayVo">
-        select id, type,corp_id,information_id, name,welcome_json, remark, skip_verify, state, user_ids, add_num, delete_num, num, qr_code, config_id, is_del, create_time, company_id, is_welcome, text_content, image_pic_url, is_user_limit, is_span_welcome, is_close_welcome, close_welcome_word, is_tag, tags, is_remark, remark_status, description, is_description, spare_user_ids, group_id, user_limit_json, user_time_json, user_type from qw_contact_way
+        select id, type,corp_id,information_id, name,welcome_json, remark, skip_verify, state, user_ids, add_num, delete_num, num, qr_code, config_id, is_del, create_time, company_id, is_welcome, text_content, image_pic_url, is_user_limit, is_span_welcome, is_close_welcome, close_welcome_word, is_tag, tags, is_remark, remark_status, description, is_description, spare_user_ids, group_id, user_limit_json, user_time_json, user_type,sort
+        from qw_contact_way
     </sql>
 
     <select id="selectQwContactWayList" parameterType="QwContactWay" resultMap="QwContactWayResult">
@@ -82,7 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userLimitJson != null  and userLimitJson != ''"> and user_limit_json = #{userLimitJson}</if>
             <if test="userTimeJson != null  and userTimeJson != ''"> and user_time_json = #{userTimeJson}</if>
             <if test="userType != null "> and user_type = #{userType}</if>
-        </where> order by id desc
+        </where> order by sort asc,create_time desc
     </select>
 
     <select id="selectQwContactWayById" parameterType="Long" resultMap="QwContactWayResult">
@@ -206,6 +208,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="welcomeJson != null">welcome_json = #{welcomeJson},</if>
             <if test="corpId != null">corp_id = #{corpId},</if>
             <if test="informationId != null">information_id = #{informationId},</if>
+            <if test="sort != null">sort = #{sort},</if>
         </trim>
         where id = #{id}
     </update>

+ 6 - 1
fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml

@@ -16,10 +16,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="updateTime"    column="update_time"    />
         <result property="corpId"    column="corp_id"    />
+        <result property="welcomeTitle"    column="welcome_title"    />
     </resultMap>
 
     <sql id="selectQwFriendWelcomeVo">
-        select id, qw_user_ids,corp_id, is_send_msg, welcome_text, attachments, is_dayparting, dayparting_ItemList, company_id, create_time, update_time from qw_friend_welcome
+        select id, qw_user_ids,corp_id,welcome_title, is_send_msg, welcome_text, attachments, is_dayparting, dayparting_ItemList, company_id, create_time, update_time from qw_friend_welcome
     </sql>
 
     <select id="selectQwFriendWelcomeListVO" parameterType="QwFriendWelcome" resultMap="QwFriendWelcomeResult">
@@ -33,6 +34,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null "> and create_time = #{createTime}</if>
             <if test="corpId != null "> and corp_id = #{corpId}</if>
             <if test="updateTime != null "> and update_time = #{updateTime}</if>
+            <if test="welcomeTitle != null "> and welcome_title = #{welcomeTitle}</if>
         </where>
     </select>
 
@@ -77,6 +79,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <insert id="insertQwFriendWelcomeVO" parameterType="QwFriendWelcome" useGeneratedKeys="true" keyProperty="id">
         insert into qw_friend_welcome
         <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="welcomeTitle != null">welcome_title,</if>
             <if test="qwUserIds != null">qw_user_ids,</if>
             <if test="isSendMsg != null">is_send_msg,</if>
             <if test="welcomeText != null">welcome_text,</if>
@@ -91,6 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="corpId != null">corp_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="welcomeTitle != null">#{welcomeTitle},</if>
             <if test="qwUserIds != null">#{qwUserIds},</if>
             <if test="isSendMsg != null">#{isSendMsg},</if>
             <if test="welcomeText != null">#{welcomeText},</if>
@@ -109,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <update id="updateQwFriendWelcome" parameterType="QwFriendWelcome">
         update qw_friend_welcome
         <trim prefix="SET" suffixOverrides=",">
+            <if test="welcomeTitle != null">welcome_title = #{welcomeTitle},</if>
             <if test="qwUserIds != null">qw_user_ids = #{qwUserIds},</if>
             <if test="isSendMsg != null">is_send_msg = #{isSendMsg},</if>
             <if test="welcomeText != null">welcome_text = #{welcomeText},</if>

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

@@ -218,9 +218,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test='nickName != null and nickName != ""'>
             AND qu.qw_user_name LIKE CONCAT(#{nickName}, '%')
         </if>
-        <if test='ids != null and ids != ""'>
+        <if test='idsList != null and !idsList.isEmpty()'>
             AND qec.qw_user_id IN
-            <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
+            <foreach item="item" index="index" collection="idsList" open="(" separator="," close=")">
                 #{item}
             </foreach>
         </if>

+ 3 - 1
fs-user-app/src/main/resources/application.yml

@@ -11,5 +11,7 @@ spring:
 #    active: druid-yzt
 #    active: druid-hdt
 #    active: druid-sxjz
+#    active: druid-qdtst
 #    active: druid-yzt
-    active: druid-bjczwh-test
+#    active: druid-bjczwh-test
+    active: dev-jnlzjk