Browse Source

Merge branch 'master' into 会员关联项目

# Conflicts:
#	fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
#	fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java
#	fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml
#	fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java
yfh 2 days ago
parent
commit
ce17e7db9c
55 changed files with 1640 additions and 136 deletions
  1. 2 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCoursePeriodController.java
  2. 7 0
      fs-admin/src/main/java/com/fs/course/task/VideoTask.java
  3. 57 0
      fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java
  4. 9 0
      fs-admin/src/test/java/com/fs/course/controller/FsCourseDomainNameControllerTest.java
  5. 1 1
      fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java
  6. 1 0
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  7. 36 5
      fs-company/src/main/java/com/fs/company/controller/IndexStatisticsController.java
  8. 10 0
      fs-service-system/src/main/java/com/fs/company/domain/Company.java
  9. 9 1
      fs-service-system/src/main/java/com/fs/company/mapper/CompanyVoiceLogsMapper.java
  10. 12 6
      fs-service-system/src/main/java/com/fs/company/vo/CompanyVoiceLogsStatisticsVO.java
  11. 15 0
      fs-service-system/src/main/java/com/fs/course/config/CourseMaConfig.java
  12. 2 0
      fs-service-system/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java
  13. 8 1
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  14. 1 1
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  15. 3 0
      fs-service-system/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  16. 5 0
      fs-service-system/src/main/java/com/fs/course/param/FsCourseTrafficLogParam.java
  17. 3 0
      fs-service-system/src/main/java/com/fs/course/param/PeriodCountParam.java
  18. 7 1
      fs-service-system/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java
  19. 20 29
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  20. 2 1
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  21. 5 1
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  22. 152 5
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  23. 52 0
      fs-service-system/src/main/java/com/fs/his/domain/FsUserWx.java
  24. 13 0
      fs-service-system/src/main/java/com/fs/his/mapper/FsUserWxMapper.java
  25. 5 1
      fs-service-system/src/main/java/com/fs/his/param/WxSendRedPacketParam.java
  26. 15 0
      fs-service-system/src/main/java/com/fs/his/service/IFsUserWxService.java
  27. 33 0
      fs-service-system/src/main/java/com/fs/his/service/impl/FsUserWxServiceImpl.java
  28. 2 2
      fs-service-system/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java
  29. 15 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreOrderMapper.java
  30. 3 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserMapper.java
  31. 3 2
      fs-service-system/src/main/java/com/fs/store/param/FsUserEditParam.java
  32. 63 0
      fs-service-system/src/main/java/com/fs/store/param/OrderStatisticsParam.java
  33. 7 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStoreOrderService.java
  34. 3 0
      fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java
  35. 52 23
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java
  36. 122 16
      fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java
  37. 10 4
      fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java
  38. 31 0
      fs-service-system/src/main/java/com/fs/store/vo/OrderCateStatisticsVo.java
  39. 31 0
      fs-service-system/src/main/java/com/fs/store/vo/OrderProductStatisticsVo.java
  40. 150 0
      fs-service-system/src/main/java/com/fs/store/vo/OrderStatisticsVo.java
  41. 5 0
      fs-service-system/src/main/resources/application-config-zkzh.yml
  42. 5 0
      fs-service-system/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml
  43. 15 2
      fs-service-system/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  44. 1 0
      fs-service-system/src/main/resources/mapper/course/FsUserWatchStatisticsMapper.xml
  45. 19 0
      fs-service-system/src/main/resources/mapper/his/FsUserWxMapper.xml
  46. 12 0
      fs-service-system/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml
  47. 217 0
      fs-service-system/src/main/resources/mapper/store/FsStoreOrderMapper.xml
  48. 0 1
      fs-service-system/src/main/resources/mapper/store/FsUserCourseCountMapper.xml
  49. 87 22
      fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml
  50. 16 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  51. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  52. 8 9
      fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java
  53. 266 0
      fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserCopyController.java
  54. 7 2
      fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java
  55. 2 0
      fs-user-app/src/main/java/com/fs/app/param/LoginMaWxParam.java

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

@@ -189,6 +189,7 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok().put("data", periodRedPacketList);
     }
 
+    @Log(title = "按课程批量保存设置红包金额", businessType = BusinessType.UPDATE)
     @PreAuthorize("@ss.hasPermi('course:period:setCourseRedPacket')")
     @ApiOperation("按课程批量保存设置红包金额")
     @PostMapping("/batchRedPacket")
@@ -202,6 +203,7 @@ public class FsUserCoursePeriodController extends BaseController {
         return R.ok();
     }
 
+    @Log(title = "按营期批量保存设置红包金额", businessType = BusinessType.UPDATE)
     @PreAuthorize("@ss.hasPermi('course:period:setRedPacket')")
     @ApiOperation("按营期批量保存设置红包金额")
     @PostMapping("/batchRedPacket/byPeriod")

+ 7 - 0
fs-admin/src/main/java/com/fs/course/task/VideoTask.java

@@ -11,6 +11,7 @@ import com.fs.course.mapper.FsUserVideoMapper;
 import com.fs.course.service.IFsUserVideoCommentService;
 import com.fs.store.domain.FsUser;
 import com.fs.store.mapper.FsUserMapper;
+import com.fs.store.service.IFsStorePaymentService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -171,5 +172,11 @@ public class VideoTask {
         return date1.isEqual(date2);
     }
 
+    @Autowired
+    private IFsStorePaymentService storePaymentService;
+    public void bufa() {
+        storePaymentService.bufaRedPacket();;
+    }
+
 
 }

+ 57 - 0
fs-admin/src/main/java/com/fs/store/controller/FsStoreOrderController.java

@@ -31,6 +31,7 @@ import com.fs.store.param.*;
 import com.fs.store.service.*;
 import com.fs.store.vo.*;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -40,6 +41,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -578,4 +580,59 @@ public class FsStoreOrderController extends BaseController {
         FsStoreOrder order=fsStoreOrderService.selectFsStoreOrderByOrderCode(orderCode);
         return fsStoreOrderService.createOmsOrder(order.getId());
     }
+
+    /**
+     * 订单维度分页查询接口
+     * @param param 查询条件
+     * @return AjaxResult
+     * **/
+    @GetMapping("/orderDimensionStatisticsList")
+    public TableDataInfo orderDimensionStatisticsList(OrderStatisticsParam param){
+        return getDataTable(fsStoreOrderService.selectOrderDimensionStatisticsList(param));
+    }
+
+    /**
+     * 导出订单维度统计接口
+     * @param param 查询条件
+     * @return AjaxResult
+     * **/
+    @Log(title = "订单维度统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/orderDimensionStatisticsExport")
+    public AjaxResult orderDimensionStatisticsExport(OrderStatisticsParam param){
+        List<OrderStatisticsVo> list = fsStoreOrderService.selectOrderDimensionStatisticsList(param);
+//        switch (param.getGroupType()){
+//            case 2:
+//                List<OrderCateStatisticsVo> cateStatisticsVoList = convertTo(list,OrderCateStatisticsVo.class);
+//                ExcelUtil<OrderCateStatisticsVo> cateUtil = new ExcelUtil<>(OrderCateStatisticsVo.class);
+//                return cateUtil.exportExcel(cateStatisticsVoList, "订单标签维度统计");
+//            case 3:
+//                List<OrderProductStatisticsVo> productStatisticsVoList =  convertTo(list,OrderProductStatisticsVo.class);
+//                ExcelUtil<OrderProductStatisticsVo> productUtil = new ExcelUtil<>(OrderProductStatisticsVo.class);
+//                return productUtil.exportExcel(productStatisticsVoList, "订单商品维度统计");
+//        }
+
+        ExcelUtil<OrderStatisticsVo> util = new ExcelUtil<>(OrderStatisticsVo.class);
+        return util.exportExcel(list, "订单员工维度统计");
+    }
+
+    /**
+     * 对象列表类型转换
+     */
+    public static <T> List<T> convertTo(List<?> sourceList, Class<T> targetClass) {
+        if (sourceList == null || sourceList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<T> resultList = new ArrayList<>(sourceList.size());
+        try {
+            for (Object source : sourceList) {
+                T target = targetClass.getDeclaredConstructor().newInstance();
+                BeanUtils.copyProperties(source, target);
+                resultList.add(target);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("数据转换异常", e);
+        }
+        return resultList;
+    }
 }

+ 9 - 0
fs-admin/src/test/java/com/fs/course/controller/FsCourseDomainNameControllerTest.java

@@ -4,6 +4,8 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.json.JSONUtil;
 import com.fs.FSAdminApplication;
 import com.fs.course.config.RedPacketConfig;
+import com.fs.statis.service.IStatisticsCompanyService;
+import com.fs.statis.service.impl.StatisticsCompanyServiceImpl;
 import com.fs.system.service.ISysConfigService;
 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
@@ -32,11 +34,18 @@ import static org.junit.jupiter.api.Assertions.*;
 @Slf4j
 class FsCourseDomainNameControllerTest {
 
+    @Autowired
+    private StatisticsCompanyServiceImpl statisticsCompanyService;
     @Autowired
     private WxPayService wxPayService;
 
     @Autowired
     private ISysConfigService configService;
+
+    @Test
+    public void test(){
+        statisticsCompanyService.rewardMoneyTaskEverydayCompanyUser();
+    }
     @Test
     public void nativePay() throws WxPayException {
 

+ 1 - 1
fs-company-app/src/main/java/com/fs/app/controller/FsUserController.java

@@ -332,7 +332,7 @@ public class FsUserController extends AppBaseController {
         return fsUserService.becomeMember(param);
     }
 
-    //    @Login
+    @Login
     @PostMapping("/userImage")
     @ApiOperation("生成分享会员海报")
     public R createCourseImage(@RequestBody FsUserCourseBeMemberImageParam param) {

+ 1 - 0
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -122,6 +122,7 @@ public class FsUserCourseVideoController extends AppBaseController {
         // type 2 未完播---看课中断
         // 管理员看整个公司 否则看自己的
         CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(Long.parseLong(getUserId()));
+        System.out.println(":进入到接口了");
         if (companyUser.isAdmin()) {
             params.put("companyId", companyUser.getCompanyId());
         } else {

+ 36 - 5
fs-company/src/main/java/com/fs/company/controller/IndexStatisticsController.java

@@ -8,6 +8,7 @@ import com.fs.core.web.service.TokenService;
 import com.fs.statis.StatisticsRedisConstant;
 import com.fs.statis.dto.*;
 import io.jsonwebtoken.lang.Assert;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.http.util.Asserts;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -20,6 +21,7 @@ import static com.fs.statis.StatisticsRedisConstant.*;
 /**
  * 首页-统计
  */
+@Slf4j
 @RestController
 @RequestMapping("/index/statistics")
 public class IndexStatisticsController {
@@ -161,20 +163,32 @@ public class IndexStatisticsController {
      */
     @PostMapping("/rewardMoneyTopTen")
     public R rewardMoneyTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        log.info("开始处理奖励金额top10请求");
+
         Integer type = param.getType();
         Integer dataType = param.getDataType();
         Integer userType = param.getUserType();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
         Long companyUserId = loginUser.getUser().getUserId();
+        log.info("请求参数: type={}, dataType={}, userType={}, companyId={}, companyUserId={}",
+                type, dataType, userType, companyId, companyUserId);
 
         param.setCompanyId(companyId);
         List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS;
+        String cacheKey;
+
         if(loginUser.getUser().isAdmin()) {
-            rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId()));
+            cacheKey = String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId());
+            log.info("管理员用户,使用缓存key: {}", cacheKey);
+
         } else {
-            rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId(),companyUserId));
+            cacheKey = String.format("%s:%d:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId(),companyUserId);
+            log.info("普通用户,使用缓存key: {}", cacheKey);
+
         }
+        rewardMoneyTopTenDTOS = redisCache.getCacheObject(cacheKey);
+        log.info("从Redis缓存中获取到的数据大小: {}", rewardMoneyTopTenDTOS != null ? rewardMoneyTopTenDTOS.size() : 0);
 
         return R.ok().put("data", rewardMoneyTopTenDTOS);
     }
@@ -189,8 +203,13 @@ public class IndexStatisticsController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
         param.setCompanyId(companyId);
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS;
+        if(loginUser.getUser().isAdmin()) {
+            rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId()));
+        } else {
+            rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId(),loginUser.getUser().getUserId()));
+        }
 
-        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId()));
         return R.ok().put("data", rewardMoneyTrendDTOS);
     }
 
@@ -206,8 +225,13 @@ public class IndexStatisticsController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
         param.setCompanyId(companyId);
+        List<CourseStatsDTO> courseStatsDTOS;
+        if(loginUser.getUser().isAdmin()) {
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
+        } else {
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId(),loginUser.getUser().getUserId()));
+        }
 
-        List<CourseStatsDTO> courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
         return R.ok().put("data", courseStatsDTOS);
     }
 
@@ -219,7 +243,14 @@ public class IndexStatisticsController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
 
-        DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED,companyId));
+        DealerAggregatedDTO dealerAggregatedDTO;
+
+        if(loginUser.getUser().isAdmin()) {
+            dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED,companyId));
+        } else {
+            dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED,companyId,loginUser.getUser().getUserId()));
+        }
+
 
         return R.ok().put("data",dealerAggregatedDTO);
     }

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

@@ -97,6 +97,16 @@ public class Company extends BaseEntity
     /** 后台制单是否需要付款 默认1 0-否 1-是*/
     private Integer isPay;
 
+    private String courseMiniAppId;
+
+    public String getCourseMiniAppId() {
+        return courseMiniAppId;
+    }
+
+    public void setCourseMiniAppId(String courseMiniAppId) {
+        this.courseMiniAppId = courseMiniAppId;
+    }
+
     public String getOmsCode() {
         return omsCode;
     }

+ 9 - 1
fs-service-system/src/main/java/com/fs/company/mapper/CompanyVoiceLogsMapper.java

@@ -205,7 +205,15 @@ public interface CompanyVoiceLogsMapper
             "</script>"})
     Integer selectCompanyVoiceLogsCountByCallerMobile(@Param("mobile")String callerMobile,@Param("type")Integer type);
     @Select({"<script> " +
-            "select u.nick_name, count(l.company_user_id) as call_count,count(ifnull(l.times &gt; 0,null)) as call_success_count ,sum(l.times) as times,sum(l.billing_time) as billing_time from  company_voice_logs l left join company_user u on l.company_user_id=u.user_id  " +
+            "SELECT\n" +
+            "\tu.user_id,\n" +
+            "\tu.nick_name,\n" +
+            "\tcount(ifnull(l.`status`=4,null)) as call_success_count,\n" +
+            "\tSUM(CASE WHEN l.`status`=4 THEN l.times ELSE 0 END) call_success_item,\n" +
+            "\tcount(l.company_user_id) as call_count,\n" +
+            "\tSUM(IF(l.times,l.times,0)) as times,\n" +
+            "\tsum(IF(l.billing_time,l.billing_time,0)) as billing_time,\n" +
+            "\tSUM(IF(l.times,l.times,0)) as amount_item from  company_voice_logs l left join company_user u on l.company_user_id=u.user_id  " +
             "where 1=1 " +
             "<if test = 'maps.type != null and maps.type ==1 '> " +
             "and TO_DAYS(l.start_time) = TO_DAYS(NOW()) " +

+ 12 - 6
fs-service-system/src/main/java/com/fs/company/vo/CompanyVoiceLogsStatisticsVO.java

@@ -9,18 +9,24 @@ import java.util.Date;
 @Data
 public class CompanyVoiceLogsStatisticsVO implements Serializable
 {
-    @Excel(name = "员工姓名")
+    @Excel(name = "序号",sort = 0,width = 20)
+    Long userId;
+    @Excel(name = "员工姓名",sort = 1,width = 20)
     String nickName;
-    @Excel(name = "拔打数")
+    @Excel(name = "外呼电话数",sort = 4,width = 20)
     Integer callCount;
-    @Excel(name = "接通数")
+    @Excel(name = "接听电活数",sort = 2,width = 20)
     Integer callSuccessCount;
-    @Excel(name = "接通率")
+    @Excel(name = "接听总时长",sort = 3,width = 20)
+    Integer callSuccessItem;
+    @Excel(name = "接通率",sort = 6,width = 20)
     Double callRate;
-    @Excel(name = "通话时长")
+    @Excel(name = "外呼通话时长",sort = 5,width = 20)
     Integer times;
-    @Excel(name = "消耗分钟")
+    @Excel(name = "消耗分钟",sort = 7,width = 20)
     Integer billingTime;
+    @Excel(name = "合计时间长",sort = 8,width = 20)
+    Integer amountItem;
 
 
 

+ 15 - 0
fs-service-system/src/main/java/com/fs/course/config/CourseMaConfig.java

@@ -35,4 +35,19 @@ public class CourseMaConfig {
      */
     private String msgDataFormat;
 
+    /**
+     * 类型 1小程序 2公众号
+     */
+    private String type;
+
+    /**
+     * 小程序log
+     */
+    private String log;
+
+    /**
+     * 小程序原始ID
+     */
+    private String username;
+
 }

+ 2 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java

@@ -61,4 +61,6 @@ public class FsCourseRedPacketLog extends BaseEntity
 
     private String result;
 
+    private String batchId;//微信批次单号
+
 }

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

@@ -166,5 +166,12 @@ public interface FsCourseRedPacketLogMapper
     @Select("SELECT company_id, SUM(amount) as money FROM fs_course_red_packet_log    WHERE status = 0 and create_time >= DATE_SUB(CURDATE(), INTERVAL 2 DAY)  AND create_time < DATE_SUB(CURDATE(), INTERVAL 1 DAY) GROUP BY company_id  ")
     List<RedPacketMoneyVO> selectFsCourseAddRedPacketLogByCompany();
 
-
+    @Select("SELECT * FROM fs_course_red_packet_log \n" +
+            "WHERE create_time <= DATE_SUB(NOW(), INTERVAL 10 MINUTE)  -- 10 分钟前或更早\n" +
+            "AND create_time >= DATE(NOW())  -- 但必须是今天\n" +
+            "AND status = 0;")
+    List<FsCourseRedPacketLog> selectFail();
+
+    @Select("SELECT * FROM fs_course_red_packet_log WHERE status = 0 and create_time > DATE_SUB(NOW(), INTERVAL 2 day) and company_user_id =#{userId}")
+    List<FsCourseRedPacketLog> selectFail(@Param("userId") Long userId);
 }

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

@@ -327,7 +327,7 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     void batchUpdateFsUserWatchLog(@Param("list") List<FsCourseWatchLog> logs);
 
-    @Select("select * from fs_course_watch_log where user_id = #{userId} and video_id = #{videoId} and send_type = 1")
+    @Select("select * from fs_course_watch_log where user_id = #{userId} and video_id = #{videoId} and send_type = 1 order by log_id desc limit 1")
     FsCourseWatchLog getCourseWatchLogByUser(@Param("userId") Long userId, @Param("videoId") Long videoId);
 
     @Select("WITH date_series AS (\n" +

+ 3 - 0
fs-service-system/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java

@@ -27,4 +27,7 @@ public class FsCourseSendRewardUParam implements Serializable
     private Integer sendType;
     private Long periodId;
 
+    private String appId;
+
+    private String code;
 }

+ 5 - 0
fs-service-system/src/main/java/com/fs/course/param/FsCourseTrafficLogParam.java

@@ -27,4 +27,9 @@ public class FsCourseTrafficLogParam {
 
     private String startDate;
     private String endDate;
+
+    /**
+     * tab类型
+     */
+    private String tabType;
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/course/param/PeriodCountParam.java

@@ -30,5 +30,8 @@ public class PeriodCountParam implements Serializable {
 
     private LocalDate maxDate;
 
+    @ApiModelProperty(value = "公司ID")
+    private Long companyId;
+
 }
 

+ 7 - 1
fs-service-system/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java

@@ -64,7 +64,13 @@ public interface IFsCourseRedPacketLogService
      */
     public int deleteFsCourseRedPacketLogByLogId(Long logId);
 
-    R syncRedPacket(String outBatchNo);
+    /**
+     *
+     * @param outBatchNo 商家批次单号
+     * @param batchId 微信批次单号
+     * @return
+     */
+    R syncRedPacket(String outBatchNo,String batchId);
 
     List<FsCourseRedPacketLogListPVO> selectFsCourseRedPacketLogListVO(FsCourseRedPacketLogParam fsCourseRedPacketLog);
     List<FsCourseRedPacketLogListPVO> selectFsCourseRedPacketLogListVONew(FsCourseRedPacketLogParam fsCourseRedPacketLog);

+ 20 - 29
fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java

@@ -82,6 +82,10 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
     @Autowired
     CloudHostProper cloudHostProper;
 
+    private static String TOKEN_VALID_CODE = "40001";
+
+    private volatile Integer version = 0;
+
 
     /**
      * 查询短链
@@ -329,12 +333,10 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
                 if (StringUtils.isBlank(miniprogramAppid)) {
                     return "未配置点播小程序id";
                 }
-//                if(StringUtils.isBlank(code)){
-//                    return "参数错误,请传入code";
-//                }
                 //获取微信token
                 final WxMaService wxService = WxMaConfiguration.getMaService(appId);
                 String token = wxService.getAccessToken();
+                log.info("小程序TOKEN值-------->刷新前TOKEN:{}", token);
                 HttpPost httpPost = new HttpPost("https://api.weixin.qq.com/wxa/generate_urllink?access_token=" + token);
                 JSONObject bodyObj = new JSONObject();
                 bodyObj.put("path", pageUrl);
@@ -343,10 +345,25 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
                 StringEntity entity = new StringEntity(bodyObj.toJSONString(),"UTF-8");
                 httpPost.setEntity(entity);
                 httpPost.setHeader("Content-type", "application/json");
+                httpPost.setHeader("cache-control","max-age=0");
                 HttpEntity response = client.execute(httpPost).getEntity();
                 String responseString = EntityUtils.toString(response);
                 log.info("微信小程序接口响应数据:{}", responseString);
                 JSONObject jsonObject = JSONObject.parseObject(responseString);
+
+                if(TOKEN_VALID_CODE.equals(jsonObject.getString("errcode"))){
+                    Integer curVersion =  Integer.valueOf(version);
+                    synchronized (TOKEN_VALID_CODE){
+                        if(curVersion.equals(version)){
+                            log.info("小程序TOKEN:40001进入强制刷新-------->刷新前TOKEN:{}", token);
+                            wxService.getAccessToken(true);
+                            version = version.equals(Integer.MAX_VALUE) ? 0 : curVersion + 1;
+                            log.info("小程序TOKEN:40001进入强制刷新-------->刷新后TOKEN:{}", wxService.getAccessToken());
+                        }
+                        return getGotoWxAppLink(linkStr,appId);
+                    }
+                }
+
                 if(null != jsonObject && !jsonObject.isEmpty() && jsonObject.containsKey("url_link")){
                     return jsonObject.getString("url_link");
                 }
@@ -361,32 +378,6 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
-//        String miniprogramAppid = config.getMiniprogramAppid();
-//        if (StringUtils.isBlank(miniprogramAppid)) {
-//                    return "未配置点播小程序id";
-//                }
-////        String envVersion = "trial";
-////        String envVersion = version;
-//        if (StringUtils.isNotBlank(linkStr)) {
-//            //解析pageLink
-//            String[] split = linkStr.split("\\?");
-//            if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
-//                //处理页面路径
-//                String pageUrl =split[0];
-//                if(pageUrl.startsWith("/")){
-//                    pageUrl = pageUrl.substring(1);
-//                }
-//                //处理参数
-//                String query = split[1];
-////                query = query.replace("\\u003d", "=");
-//                String wxAppLink = getWxAppLink(miniprogramAppid, pageUrl, query);
-//                return wxAppLink;
-//            } else {
-//                return "页面链接错误,获取失败";
-//            }
-//        }
         return "";
     }
 

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

@@ -100,11 +100,12 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
     }
 
     @Override
-    public R syncRedPacket(String outBatchNo) {
+    public R syncRedPacket(String outBatchNo,String batchId) {
         FsCourseRedPacketLog log = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByBatchNo(outBatchNo);
         if (log!=null){
             log.setStatus(1);
             log.setUpdateTime(new Date());
+            log.setBatchId(batchId);
             fsCourseRedPacketLogMapper.updateFsCourseRedPacketLog(log);
             return R.ok();
         }

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

@@ -35,7 +35,10 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -238,6 +241,7 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         CourseAnalysisParam courseAnalysisParam = new CourseAnalysisParam();
         courseAnalysisParam.setPeriodId(param.getPeriodId());
         courseAnalysisParam.setVideoIdList(param.getVideoIdList());
+        courseAnalysisParam.setCompanyId(param.getCompanyId());
         List<FsCourseAnalysisCountVO> courseCountList = fsUserMapper.courseAnalysisWatchLog(courseAnalysisParam);
         List<FsCourseAnalysisCountVO> redPacketCountList = fsUserMapper.courseAnalysisRedPacketCount(courseAnalysisParam);
         List<FsCourseAnalysisCountVO> answerCountList = fsUserMapper.courseAnalysisAnswerCount(courseAnalysisParam);

+ 152 - 5
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -12,6 +12,8 @@ import com.fs.common.enums.BizResponseEnum;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.course.config.CourseConfig;
@@ -27,7 +29,9 @@ import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoQVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.course.vo.newfs.*;
+import com.fs.his.domain.FsUserWx;
 import com.fs.his.param.WxSendRedPacketParam;
+import com.fs.his.service.IFsUserWxService;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwExternalContact;
@@ -61,6 +65,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.parameters.P;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -182,6 +187,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private SysDictDataMapper dictDataMapper;
 
+
+    @Autowired
+    private IFsUserWxService fsUserWxService;
     /**
      * 查询课堂视频
      *
@@ -825,6 +833,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         packetParam.setSource(param.getSource());
         packetParam.setRedPacketMode(config.getRedPacketMode());
         packetParam.setCompanyId(param.getCompanyId());
+        packetParam.setAppId(param.getAppId());
+        if (StringUtils.isNotEmpty(param.getCode())){
+            packetParam.setCode(param.getCode());
+        }
 
         //2025.7.11 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){
@@ -837,8 +849,10 @@ 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());
                 }
                 // 添加红包记录
                 redPacketLog.setCourseId(param.getCourseId());
@@ -875,7 +889,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             redPacketLog.setCompanyId(param.getCompanyId());
             redPacketLog.setUserId(param.getUserId());
             redPacketLog.setVideoId(param.getVideoId());
-            redPacketLog.setStatus(0);
+            redPacketLog.setStatus(1);//直接设置发送成功
+            redPacketLog.setResult("{\"msg\":\"发送0红包成功\",\"code\":200,\"isNew\":1}");
             redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
             redPacketLog.setCompanyUserId(param.getCompanyUserId());
             redPacketLog.setCreateTime(new Date());
@@ -891,6 +906,138 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
     }
 
+    /**
+     * 发放红包奖励
+     *
+     * @param param 请求参数
+     * @param user 用户信息
+     * @param log 观看日志
+     * @param video 视频信息
+     * @param config 配置信息
+     * @return 处理结果
+     */
+    private R sendRedPacketRewardFsUser(FsCourseSendRewardUParam param, FsUser user, FsCourseWatchLog log, FsUserCourseVideo video, CourseConfig config) {
+        // 判断是否属于领取红包时间(会员看课发放红包)
+        if (param.getPeriodId()!=null && param.getPeriodId()>0) {
+            FsUserCoursePeriodDays periodDays = new FsUserCoursePeriodDays();
+            periodDays.setVideoId(param.getVideoId());
+            periodDays.setPeriodId(param.getPeriodId());
+            //正常情况是只能查询到一条,之前可能存在重复的脏数据,暂使用查询list的方式
+            List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysList(periodDays);
+            if(fsUserCoursePeriodDays != null && !fsUserCoursePeriodDays.isEmpty()){
+                periodDays = fsUserCoursePeriodDays.get(0);
+            }
+            if(periodDays != null && periodDays.getLastJoinTime() !=null && LocalDateTime.now().isAfter(periodDays.getLastJoinTime())) {
+                return R.error(403,"已超过领取红包时间");
+            }
+        }
+
+        // 确定红包金额
+        BigDecimal amount = BigDecimal.ZERO;
+        FsUserCourseVideoRedPackage redPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(param.getVideoId(), param.getCompanyId(), param.getPeriodId());
+
+        if (redPackage != null) {
+            amount = redPackage.getRedPacketMoney();
+        } else if (video != null) {
+            amount = video.getRedPacketMoney();
+        }
+
+        // 准备发送红包参数
+        WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+//        packetParam.setOpenId(getOpenId(user.getUserId(), param.getCompanyId(), param.getSource()));
+        packetParam.setOpenId(user.getMpOpenId());
+        // 来源是小程序切换openId
+        if (param.getSource() == 2) {
+            //处理多小程序问题
+//            Company company = companyMapper.selectCompanyById(param.getCompanyId());
+//            if (company.getCourseMiniAppId()==null){
+//                return R.error("销售公司参数错误,未绑定小程序");
+//            }
+            FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
+            if (fsUserWx ==null || fsUserWx.getOpenId()==null){
+                return R.error("小程序openId参数缺失");
+            }
+
+//            System.out.println("小程序id"+user.getCourseMaOpenId());
+            //查出公司绑定openid并赋值
+            packetParam.setOpenId(fsUserWx.getOpenId());
+        }
+        packetParam.setAmount(amount);
+        packetParam.setSource(param.getSource());
+        packetParam.setRedPacketMode(config.getRedPacketMode());
+        packetParam.setCompanyId(param.getCompanyId());
+
+        System.out.println("红包金额"+amount);
+        System.out.println("红包商户号"+packetParam);
+        //2025.7.11 红包金额为0的时候
+        if (amount.compareTo(BigDecimal.ZERO)>0){
+            // 发送红包
+            R sendRedPacket = paymentService.sendRedPacket(packetParam);
+            if (sendRedPacket.get("code").equals(200)) {
+                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                TransferBillsResult transferBillsResult;
+                if (sendRedPacket.get("isNew").equals(1)){
+                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                    redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                }else {
+                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                }
+                // 添加红包记录
+                redPacketLog.setCourseId(param.getCourseId());
+//            redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                redPacketLog.setCompanyId(param.getCompanyId());
+                redPacketLog.setUserId(param.getUserId());
+                redPacketLog.setVideoId(param.getVideoId());
+                redPacketLog.setStatus(0);
+                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                redPacketLog.setCreateTime(new Date());
+                redPacketLog.setAmount(amount);
+                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                redPacketLog.setPeriodId(param.getPeriodId());
+
+                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                // 更新观看记录的奖励类型
+//            if (param.getLinkType() == null || param.getLinkType() == 0) {
+                log.setRewardType(config.getRewardType());
+                courseWatchLogMapper.updateFsCourseWatchLog(log);
+//            }
+                return sendRedPacket;
+            } else {
+                return R.error("奖励发送失败,请联系客服");
+            }
+
+        } else {
+            // 发送红包
+            FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+            // 添加红包记录
+            redPacketLog.setCourseId(param.getCourseId());
+//            redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+            redPacketLog.setCompanyId(param.getCompanyId());
+            redPacketLog.setUserId(param.getUserId());
+            redPacketLog.setVideoId(param.getVideoId());
+            redPacketLog.setStatus(1);//直接设置发送成功
+            redPacketLog.setResult("{\"msg\":\"发送0红包成功\",\"code\":200,\"isNew\":1}");
+            redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+            redPacketLog.setCompanyUserId(param.getCompanyUserId());
+            redPacketLog.setCreateTime(new Date());
+            redPacketLog.setAmount(BigDecimal.ZERO);
+            redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+            redPacketLog.setPeriodId(param.getPeriodId());
+            redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+            // 更新观看记录的奖励类型
+            log.setRewardType(config.getRewardType());
+            courseWatchLogMapper.updateFsCourseWatchLog(log);
+            return R.ok("红包发送成功");
+        }
+
+    }
+
     /**
      * 发放积分奖励
      *
@@ -1029,12 +1176,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
 
         // 逻辑调整:如果会员已经绑定了销售,直接提示,不添加重粉数据了-2025年6月16日14点53分
-        if (!param.getCompanyUserId().equals(userCompanyUser.getCompanyUserId())){
-            return ResponseResult.fail(406,"该用户已成为其他销售会员");
+        if (fsUser.getCompanyUserId() != null && !param.getCompanyUserId().equals(fsUser.getCompanyUserId())){
+            return ResponseResult.fail(500,"该用户("+fsUser.getUserId() + ")已成为其他销售会员");
         }
 
         // 如果开启了黑名单审核,需要提示
-        if(userCompanyUser.getStatus() == 0) {
+        if(fsUser.getStatus() == 0) {
 //            return ResponseResult.fail(505, "管理开启了会员审核,请等待审核");
             return ResponseResult.fail(BizResponseEnum.WAIT_APPROVAL,getCompanyUserQRCode(companyUser));
         }
@@ -1477,7 +1624,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         //读取配置给前端返回
         String json = sysConfigService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (org.apache.commons.lang3.StringUtils.isEmpty(user.getNickname()) || org.apache.commons.lang3.StringUtils.isEmpty(user.getAvatar())){
+        if (StringUtils.isEmpty(user.getNickname())){
             Map<String,Object> map = new HashMap<>();
             map.put("authType",config.getMiniAppAuthType());
             if (config.getMiniAppAuthType()==2){

+ 52 - 0
fs-service-system/src/main/java/com/fs/his/domain/FsUserWx.java

@@ -0,0 +1,52 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("fs_user_wx")
+public class FsUserWx {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 用户ID
+     */
+    private Long fsUserId;
+
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+    /**
+     * 小程序/公众号appId
+     */
+    private String appId;
+    /**
+     * 微信unionId
+     */
+    private String unionId;
+    /**
+     * 微信openId
+     */
+    private String openId;
+
+    /**
+     * 1:小程序 2:服务号
+     */
+    private Integer type;
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    /**
+     * 修改时间
+     */
+    private Date updateTime;
+}

+ 13 - 0
fs-service-system/src/main/java/com/fs/his/mapper/FsUserWxMapper.java

@@ -0,0 +1,13 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUserWx;
+
+public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
+
+    /**
+     * 根据FsUserId和AppId唯一键保存或更新
+     * @param wx    配置信息
+     */
+    void insertOrUpdateByUniqueKey(FsUserWx wx);
+}

+ 5 - 1
fs-service-system/src/main/java/com/fs/his/param/WxSendRedPacketParam.java

@@ -16,7 +16,11 @@ public class WxSendRedPacketParam implements Serializable {
 
     private Integer source=1;//来源 1:h5  2:看课小程序
 
-    private Integer redPacketMode; //红包模式
+    private Integer redPacketMode;//红包模式
+
+    private String appId;
+
+    private String code;
 
 
 }

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

@@ -0,0 +1,15 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserWx;
+
+public interface IFsUserWxService extends IService<FsUserWx> {
+
+    /**
+     * 根据FsUserId和AppId唯一键保存或更新
+     * @param wx    配置信息
+     */
+    void saveOrUpdateByUniqueKey(FsUserWx wx);
+
+    FsUserWx selectByAppIdAndUserId(String appId, Long userId,Integer type);
+}

+ 33 - 0
fs-service-system/src/main/java/com/fs/his/service/impl/FsUserWxServiceImpl.java

@@ -0,0 +1,33 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.his.domain.FsUserWx;
+import com.fs.his.mapper.FsUserWxMapper;
+import com.fs.his.service.IFsUserWxService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class FsUserWxServiceImpl extends ServiceImpl<FsUserWxMapper, FsUserWx> implements IFsUserWxService {
+
+    /**
+     * 根据FsUserId和AppId唯一键保存或更新
+     * @param wx    配置信息
+     */
+    @Override
+    public void saveOrUpdateByUniqueKey(FsUserWx wx) {
+        baseMapper.insertOrUpdateByUniqueKey(wx);
+    }
+
+    @Override
+    public FsUserWx selectByAppIdAndUserId(String appId, Long userId,Integer type) {
+        return this.baseMapper.selectOne(
+                new QueryWrapper<FsUserWx>()
+                        .eq("app_id", appId)
+                        .eq("fs_user_id", userId)
+                        .eq("type", type)
+        );
+    }
+}

+ 2 - 2
fs-service-system/src/main/java/com/fs/statis/service/impl/StatisticsCompanyServiceImpl.java

@@ -438,7 +438,7 @@ public class StatisticsCompanyServiceImpl implements IStatisticsCompanyService {
         param.setUserType(userType);
         List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS = this.watchEndPlayTrend(param);
 
-        redisCache.setCacheObject(String.format("%s:%d:%d",DATA_OVERVIEW_DEALER_CHARTS,type,userType),watchEndPlayTrendDTOS);
+        redisCache.setCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_CHARTS,type,userType,companyId),watchEndPlayTrendDTOS);
     }
 
     @Override
@@ -497,7 +497,7 @@ public class StatisticsCompanyServiceImpl implements IStatisticsCompanyService {
         param.setCompanyUserId(companyUserId);
         List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS = this.watchEndPlayTrend(param);
 
-        redisCache.setCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_CHARTS,type,userType,companyUserId),watchEndPlayTrendDTOS);
+        redisCache.setCacheObject(String.format("%s:%d:%d:%d:%d",DATA_OVERVIEW_DEALER_CHARTS,type,userType,companyId,companyUserId),watchEndPlayTrendDTOS);
     }
 
     @Override

+ 15 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreOrderMapper.java

@@ -1040,4 +1040,19 @@ public interface FsStoreOrderMapper
     int selectFsStoreOrderCount(FsStoreOrderStatisticsParam param);
 
     BigDecimal selectFsStoreOrderByPayPriceCount(FsStoreOrderStatisticsParam param);
+
+    /**
+     * 订单维度分页查询接口
+     * @param param 查询条件
+     * @return List<OrderStatisticsVo>
+     * **/
+    List<OrderStatisticsVo> selectOrderDimensionStatisticsList(@Param("param") OrderStatisticsParam param);
+
+
+    /**
+     * 订单销售维度分页查询接口
+     * @param param 查询条件
+     * @return List<OrderStatisticsVo>
+     * **/
+    List<OrderStatisticsVo> selectOrderSaleStatisticsList(@Param("param") OrderStatisticsParam param);
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserMapper.java

@@ -314,4 +314,7 @@ public interface FsUserMapper
     List<FSUserVO> selectFsUserVOListByProject(@Param("maps") FsUser fsUser);
 
     Map<String, Long> countUserCourse2(UserStatisticsCommonParam param);
+
+    Map<String, Long> countCourseDetailsNew(UserStatisticsCommonParam param);
+
 }

+ 3 - 2
fs-service-system/src/main/java/com/fs/store/param/FsUserEditParam.java

@@ -3,16 +3,17 @@ package com.fs.store.param;
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
 @Data
 public class FsUserEditParam implements Serializable
 {
 
-    @NotBlank(message = "用户昵称不能为空!")
+    @NotNull(message = "用户昵称不能为空!")
     private String nickname;
 
-    @NotBlank(message = "用户头像不能为空!")
+//    @NotBlank(message = "用户头像不能为空!")
     private String avatar;
 
     private Long userId;

+ 63 - 0
fs-service-system/src/main/java/com/fs/store/param/OrderStatisticsParam.java

@@ -0,0 +1,63 @@
+package com.fs.store.param;
+
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Set;
+
+@Data
+public class OrderStatisticsParam implements Serializable {
+    /**
+     * 企业ID
+     **/
+    private Long companyId;
+
+    /**
+     * 员工账号
+     **/
+    private String userName;
+
+    /**
+     * 销售名称
+     **/
+    private String companyUser;
+
+    /**
+     * 商品名称
+     **/
+    private String productName;
+
+    /**
+     * 标签名称
+     **/
+    private String cateName;
+
+    /**
+     * 起始日期
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate startDate;
+
+    /**
+     * 结束日期
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate endDate;
+
+    /**
+     * 分组类型(1:员工、2:标签、3:商品)
+     **/
+    private Integer groupType;
+
+    /**
+     * 标签ID
+     * **/
+    private Long cateId;
+
+
+    /**
+     * 商品ID
+     * **/
+    private Long productId;
+}

+ 7 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStoreOrderService.java

@@ -226,4 +226,11 @@ public interface IFsStoreOrderService
      * @return
      */
     BigDecimal selectFsStoreOrderByPayPriceCount(FsStoreOrderStatisticsParam param);
+
+    /**
+     * 订单维度分页查询接口
+     * @param param 查询条件
+     * @return List<OrderStatisticsVo>
+     * **/
+    List<OrderStatisticsVo> selectOrderDimensionStatisticsList(OrderStatisticsParam param);
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/store/service/IFsStorePaymentService.java

@@ -112,4 +112,7 @@ public interface IFsStorePaymentService
 
     R sendRedPacketTest(WxSendRedPacketParam param);
 
+    void bufaRedPacket();
+
+    R sendRewardByTest(Long userId);
 }

+ 52 - 23
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java

@@ -1298,17 +1298,27 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
                 }
             }
 
+            FsErpConfig erpConfig = configUtil.getErpConfig();
+            Integer erpType = erpConfig.getErpType();
+            Integer erpOpen = erpConfig.getErpOpen();
+            if (erpOpen != null && erpOpen == 1) {
+                if (erpType != null && erpType==2){
+                    // 同步订单完成状态到erp
+                    // 如果是物流代收 或者 货到付款
+                    if("2".equals(order.getPayType()) || "3".equals(order.getPayType())){
+                        // 已结算
+                        if("1".equals(order.getDeliveryPayStatus())){
+                            FsErpFinishPush fsErpFinishPush = new FsErpFinishPush();
+                            fsErpFinishPush.setOrderId(order.getId());
+                            fsErpFinishPush.setTaskStatus(0);
+                            fsErpFinishPush.setRetryCount(0);
+                            fsErpFinishPush.setCreateTime(new Date());
+                            fsErpFinishPushMapper.insert(fsErpFinishPush);
+                        }
+                    }
+                }
+            }
 
-//            // 同步订单完成状态到erp
-//            // 如果是线上支付
-//            if("1".equals(order.getPayType())){
-//                FsErpFinishPush fsErpFinishPush = new FsErpFinishPush();
-//                fsErpFinishPush.setOrderId(orderId);
-//                fsErpFinishPush.setTaskStatus(0);
-//                fsErpFinishPush.setRetryCount(0);
-//                fsErpFinishPush.setCreateTime(new Date());
-//                fsErpFinishPushMapper.insert(fsErpFinishPush);
-//            }
 
             //模板消息支付成功发布事件
             TemplateBean templateBean = TemplateBean.builder()
@@ -2252,19 +2262,28 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
                         importSuccessMsg.append("<br/>" + successNum + "、快递单号 " +dto.getDeliveryId() + " 导入成功");
 
 
-                        // 同步订单完成状态到erp
-                        // 如果是物流代收 或者 货到付款
-//                        if("2".equals(order.getPayType()) || "3".equals(order.getPayType())){
-//                            // 已结算
-//                            if("1".equals(dto.getDeliveryPayStatus())){
-//                                FsErpFinishPush fsErpFinishPush = new FsErpFinishPush();
-//                                fsErpFinishPush.setOrderId(order.getId());
-//                                fsErpFinishPush.setTaskStatus(0);
-//                                fsErpFinishPush.setRetryCount(0);
-//                                fsErpFinishPush.setCreateTime(new Date());
-//                                fsErpFinishPushMapper.insert(fsErpFinishPush);
-//                            }
-//                        }
+                        FsErpConfig erpConfig = configUtil.getErpConfig();
+                        Integer erpType = erpConfig.getErpType();
+                        Integer erpOpen = erpConfig.getErpOpen();
+                        if (erpOpen != null && erpOpen == 1) {
+                            if (erpType != null && erpType==2){
+                                // 同步订单完成状态到erp
+                                // 如果是物流代收 或者 货到付款
+                                if("2".equals(order.getPayType()) || "3".equals(order.getPayType())){
+                                    // 已结算
+                                    if("1".equals(dto.getDeliveryPayStatus())){
+                                        FsErpFinishPush fsErpFinishPush = new FsErpFinishPush();
+                                        fsErpFinishPush.setOrderId(order.getId());
+                                        fsErpFinishPush.setTaskStatus(0);
+                                        fsErpFinishPush.setRetryCount(0);
+                                        fsErpFinishPush.setCreateTime(new Date());
+                                        fsErpFinishPushMapper.insert(fsErpFinishPush);
+                                    }
+                                }
+                            }
+                        }
+
+
                     }
                     else{
                         String msg = "<br/>" + failureNum + "、快递单号 " + dto.getDeliveryId() + " 未签收或已导入,不能导入";
@@ -2651,4 +2670,14 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
         }
         return erpOrderService;
     }
+
+    @Override
+    public List<OrderStatisticsVo> selectOrderDimensionStatisticsList(OrderStatisticsParam param) {
+        return fsStoreOrderMapper.selectOrderSaleStatisticsList(param);
+//        if(param.getGroupType()==1){
+//            return fsStoreOrderMapper.selectOrderSaleStatisticsList(param);
+//        }else {
+//            return fsStoreOrderMapper.selectOrderDimensionStatisticsList(param);
+//        }
+    }
 }

+ 122 - 16
fs-service-system/src/main/java/com/fs/store/service/impl/FsStorePaymentServiceImpl.java

@@ -6,6 +6,8 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
@@ -23,6 +25,8 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.RedPacketConfig;
+import com.fs.course.domain.FsCourseRedPacketLog;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.pay.pay.config.PayConfig;
 import com.fs.pay.pay.domain.CreateWxOrderResult;
@@ -32,6 +36,7 @@ import com.fs.store.param.FsStoreStatisticsParam;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.store.vo.FsStorePaymentStatisticsVO;
 import com.fs.system.service.ISysConfigService;
+import com.fs.wx.miniapp.config.WxMaConfiguration;
 import com.fs.wx.miniapp.config.WxMaProperties;
 import com.fs.wx.pay.config.WxPayProperties;
 import com.fs.store.domain.FsUser;
@@ -53,6 +58,7 @@ 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 me.chanjar.weixin.common.error.WxErrorException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -377,7 +383,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         }
         //H5的用公众号的appid发,小程序的用小程序的appid来发
         if (param.getSource()==2){
-            config.setAppId(config.getMiniappId());
+            config.setAppId(param.getAppId());
         }
         //组合返回参数
         R result = new R();
@@ -471,7 +477,23 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
 
         ArrayList<TransferBatchesRequest.TransferDetail> transferDetailList = new ArrayList<>();
         TransferBatchesRequest.TransferDetail transferDetail = new TransferBatchesRequest.TransferDetail();
-        transferDetail.setOpenid(param.getOpenId());
+//        transferDetail.setOpenid(param.getOpenId());
+
+        String openId;
+        //临时方法,有code的时候获取一下客户的openId
+        if (StringUtils.isNotEmpty(param.getCode())){
+            WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
+            try {
+                WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
+                openId = session.getOpenid();
+            } catch (WxErrorException e){
+                return R.error("openId获取失败," + e.getMessage());
+            }
+        }else {
+            openId = param.getOpenId();
+        }
+
+        transferDetail.setOpenid(openId);
         String code1 = IdUtil.getSnowflake(0, 0).nextIdStr();
         transferDetail.setOutDetailNo("fsCourse" + code1);
         transferDetail.setTransferAmount(amount);
@@ -481,7 +503,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
 
         try {
             TransferBatchesResult transferBatchesResult = transferService.transferBatches(request);
-            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo());
+            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId());
         } catch (WxPayException e) {
             e.printStackTrace();
             return R.error("发送失败");
@@ -499,19 +521,18 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         config = JSONUtil.toBean(json, RedPacketConfig.class);
 
         param.setSource(2);
-//        param.setOpenId("ooXAA1Fw5ekSqCT-WLKpoA0cDVDo");
-        param.setAmount(new BigDecimal(0.1));
+        param.setAmount(param.getAmount());
 
 
         WxPayConfig payConfig = new WxPayConfig();
         BeanUtils.copyProperties(config, payConfig);
-        payConfig.setAppId(config.getMiniappId());
+        payConfig.setAppId(param.getAppId());
         WxPayService wxPayService = new WxPayServiceImpl();
         wxPayService.setConfig(payConfig);
         TransferService transferService = wxPayService.getTransferService();
 
         TransferBatchesRequest request = new TransferBatchesRequest();
-        request.setAppid(config.getMiniappId());
+        request.setAppid(param.getAppId());
         String code = IdUtil.getSnowflake(0, 0).nextIdStr();
         request.setOutBatchNo("fsCourse" + code);
         request.setBatchRemark("课堂答题奖励");
@@ -524,6 +545,21 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
         ArrayList<TransferBatchesRequest.TransferDetail> transferDetailList = new ArrayList<>();
         TransferBatchesRequest.TransferDetail transferDetail = new TransferBatchesRequest.TransferDetail();
         transferDetail.setOpenid(param.getOpenId());
+
+//        String openId;
+//        //临时方法,有code的时候获取一下客户的openId
+//        if (StringUtils.isNotEmpty(param.getCode())){
+//            WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
+//            try {
+//                WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
+//                openId = session.getOpenid();
+//            } catch (WxErrorException e){
+//                return R.error("openId获取失败," + e.getMessage());
+//            }
+//        }else {
+//            openId = param.getOpenId();
+//        }
+//        transferDetail.setOpenid(openId);
         String code1 = IdUtil.getSnowflake(0, 0).nextIdStr();
         transferDetail.setOutDetailNo("fsCourse" + code1);
         transferDetail.setTransferAmount(amount);
@@ -533,7 +569,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
 
         try {
             TransferBatchesResult transferBatchesResult = transferService.transferBatches(request);
-            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo());
+            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId());
         } catch (WxPayException e) {
             e.printStackTrace();
             return R.error("发送失败");
@@ -565,8 +601,8 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
             signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
             WxPayTransferBatchesNotifyV3Result result = wxPayService.parseTransferBatchesNotifyV3Result(notifyData,signatureHeader);
             logger.info("到零钱回调:{}",result.getResult());
-            if (result.getResult().getBatchStatus().equals("FINISHED")) {
-                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBatchNo());
+            if (result.getResult().getBatchStatus().equals("FINISHED") && result.getResult().getFailNum()==0) {
+                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBatchNo(),result.getResult().getBatchId());
                 logger.info("result:{}",r);
                 if (r.get("code").equals(200)){
                     return WxPayNotifyResponse.success("处理成功");
@@ -591,14 +627,9 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
             //创建微信订单
             WxPayConfig payConfig = new WxPayConfig();
             BeanUtils.copyProperties(config,payConfig);
-//            payConfig.setCertSerialNo("63AC73F33E0A21973BB1DE533421A2337FD91C20");
             WxPayService wxPayService = new WxPayServiceImpl();
             wxPayService.setConfig(payConfig);
             SignatureHeader signatureHeader = new SignatureHeader();
-//            signatureHeader.setTimeStamp("1622450000");  // 时间戳(Unix时间戳)
-//            signatureHeader.setNonce("5K8264ILTKCH16CQ2502SI8ZNMTM67VS");  // 随机字符串
-//            signatureHeader.setSerial("63AC73F33E0A21973BB1DE533421A2337FD91C20");
-//            signatureHeader.setSignature("HbjssPzTBkM2iSCmxknS663zigo3gQ1jGQ4R6E6x9356bmV6Um4WfgGWZOH+fdCx5dxjHEiIci5kOYKl0ZdRfnexFFuM2riXLSqnRboOJZ+ew8FH4ZP/zCxtlDnmIYbARoIN46RegcRmGgfOznkLcD7ihr0JixgoZ0BOYk7YLhhcbLZaE2OJmwyyYIdJCH5lvg0mXyX1yfutNxPZz13i3OmZiU42xYr4bByJICWMFTwkzha9GVfOp67q/oVu0bEGIMgGdAVoEUJZXOijKdZdOrieXT07wMU31KITKcnizaUGl5peXejbJEd6CAQcX5e8KRlRSjY8DybHAm0JawVuAw==");
             signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
             signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
             signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
@@ -606,7 +637,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
             TransferBillsNotifyResult result = wxPayService.parseTransferBillsNotifyV3Result(notifyData,signatureHeader);
             logger.info("到零钱回调:{}",result.getResult());
             if (result.getResult().getState().equals("SUCCESS")) {
-                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBillNo());
+                R r = redPacketLogService.syncRedPacket(result.getResult().getOutBillNo(),result.getResult().getTransferBillNo());
                 logger.info("result:{}",r);
                 if (r.get("code").equals(200)){
                     return WxPayNotifyResponse.success("处理成功");
@@ -621,4 +652,79 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService
             return WxPayNotifyResponse.fail(e.getMessage());
         }
     }
+
+    @Autowired
+    private FsCourseRedPacketLogMapper redPacketLogMapper;
+
+    @Override
+    public void bufaRedPacket() {
+//        String json = configService.selectConfigByKey("redPacket.config");
+//        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+        List<FsCourseRedPacketLog> logs = redPacketLogMapper.selectFail();
+        String appId = "wx414427b10866c04e";
+        for (FsCourseRedPacketLog log : logs){
+            FsUser user = userService.selectFsUserById(log.getUserId());
+            WxSendRedPacketParam param = new WxSendRedPacketParam();
+            param.setAppId(appId);
+            if (user==null || user.getMaOpenId()==null){
+                continue;
+            }
+            param.setOpenId(user.getMaOpenId());
+            param.setAmount(log.getAmount());
+            R r = this.sendRedPacketTest(param);
+            if (r.get("code").equals(200)){
+                log.setOutBatchNo(r.get("orderCode").toString());
+                log.setBatchId(r.get("batchId").toString());
+                redPacketLogMapper.updateFsCourseRedPacketLog(log);
+                logger.info("更新完成:{}",log.getLogId());
+            }
+
+        }
+
+//        Thread.sleep(30000);
+//        List<FsCourseRedPacketLog> logs2 = redPacketLogMapper.selectFail();
+//        String appId = "wx414427b10866c04e";
+//        for (FsCourseRedPacketLog log : logs){
+//            FsUser user = userService.selectFsUserById(log.getUserId());
+//            WxSendRedPacketParam param = new WxSendRedPacketParam();
+//            param.setAppId(appId);
+//            if (user==null || user.getMaOpenId()==null){
+//                continue;
+//            }
+//            param.setOpenId(user.getMaOpenId());
+//            param.setAmount(log.getAmount());
+//            R r = this.sendRedPacketTest(param);
+//            if (r.get("code").equals(200)){
+//                log.setOutBatchNo(r.get("orderCode").toString());
+//                log.setBatchId(r.get("batchId").toString());
+//                redPacketLogMapper.updateFsCourseRedPacketLog(log);
+//                logger.info("更新完成:{}",log.getLogId());
+//            }
+//
+//        }
+    }
+
+    @Override
+    public R sendRewardByTest(Long userId) {
+        List<FsCourseRedPacketLog> logs = redPacketLogMapper.selectFail(userId);
+        String appId = "wx414427b10866c04e";
+        for (FsCourseRedPacketLog log : logs) {
+            FsUser user = userService.selectFsUserById(log.getUserId());
+            WxSendRedPacketParam param = new WxSendRedPacketParam();
+            param.setAppId(appId);
+            if (user == null || user.getMaOpenId() == null) {
+                continue;
+            }
+            param.setOpenId(user.getMaOpenId());
+            param.setAmount(log.getAmount());
+            R r = this.sendRedPacketTest(param);
+            if (r.get("code").equals(200)) {
+                log.setOutBatchNo(r.get("orderCode").toString());
+                log.setBatchId(r.get("batchId").toString());
+                redPacketLogMapper.updateFsCourseRedPacketLog(log);
+                logger.info("更新完成:{}", log.getLogId());
+            }
+        }
+        return R.ok();
+        }
 }

+ 10 - 4
fs-service-system/src/main/java/com/fs/store/service/impl/FsUserServiceImpl.java

@@ -744,7 +744,8 @@ public class FsUserServiceImpl implements IFsUserService
             param.setCompanyId(companyUser.getCompanyId());
         }
         // 获取课程统计
-        Map<String, Long> couserMap = fsUserMapper.countUserCourse(param);
+//        Map<String, Long> couserMap = fsUserMapper.countUserCourse(param);
+        Map<String, Long> couserMap = fsUserMapper.countUserCourse2(param);
         if (couserMap != null) {
             fsUserStatisticsVO.setCourseWatchNum(couserMap.get("courseWatchNum").intValue()).setCourseCompleteNum(couserMap.get("courseCompleteNum").intValue());
 
@@ -782,8 +783,8 @@ public class FsUserServiceImpl implements IFsUserService
 
     @Override
     public FsUserStatisticsVO userStatisticsDetails(UserStatisticsCommonParam param) {
-//        FsUserStatisticsVO userStatisticsVO = getUserStatistics(param);
-        FsUserStatisticsVO userStatisticsVO = getUserStatistics2(param);
+        FsUserStatisticsVO userStatisticsVO = getUserStatistics(param);
+//        FsUserStatisticsVO userStatisticsVO = getUserStatistics2(param);
 
         // 判断是否是管理员
         CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getUserId());
@@ -791,7 +792,7 @@ public class FsUserServiceImpl implements IFsUserService
             param.setUserId(0L);
         }
         //统计课程数据详情,在查询统计详情的时候需要显示
-        Map<String, Long> courseDetailsMap = fsUserMapper.countCourseDetails(param);
+        Map<String, Long> courseDetailsMap = fsUserMapper.countCourseDetailsNew(param);
         if(courseDetailsMap != null && courseDetailsMap.get("courseNum") != null && courseDetailsMap.get("videoNum") != null && courseDetailsMap.get("courseUserNum") != null){
             userStatisticsVO.setCourseNum(Integer.parseInt(courseDetailsMap.get("courseNum").toString()))
                     .setVideoNum(Integer.parseInt(courseDetailsMap.get("videoNum").toString()))
@@ -979,6 +980,11 @@ public class FsUserServiceImpl implements IFsUserService
             return ResponseResult.fail(404,"当前用户信息不存在");
         }
 
+        // 逻辑调整:如果会员已经绑定了销售,直接提示,不添加重粉数据了-2025年6月16日14点53分
+        if (fsUser.getCompanyUserId() != null && !param.getCompanyUserId().equals(fsUser.getCompanyUserId())){
+            return ResponseResult.fail(500,"该用户("+fsUser.getUserId() + ")已成为其他销售会员");
+        }
+
         //判断该销售是否存在
         CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
         if (Objects.isNull(companyUser)){

+ 31 - 0
fs-service-system/src/main/java/com/fs/store/vo/OrderCateStatisticsVo.java

@@ -0,0 +1,31 @@
+package com.fs.store.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+/**
+ * 订单维度统计实体
+ **/
+@Data
+public class OrderCateStatisticsVo implements Serializable {
+    /**
+     * 标签名称
+     **/
+    @Excel(name = "标签名称", sort = 4, width = 20)
+    private String cateName;
+
+    /**
+     * 订单数
+     **/
+    @Excel(name = "销售订单数量", sort = 5, width = 20)
+    private BigInteger orderNum;
+
+    /**
+     * 标签ID
+     * **/
+    @Excel(name = "标签编码", sort = 1, width = 20)
+    private Long cateId;
+}

+ 31 - 0
fs-service-system/src/main/java/com/fs/store/vo/OrderProductStatisticsVo.java

@@ -0,0 +1,31 @@
+package com.fs.store.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+/**
+ * 订单维度统计实体
+ **/
+@Data
+public class OrderProductStatisticsVo implements Serializable {
+    /**
+     * 商品名称
+     **/
+    @Excel(name = "商品名称", sort = 4, width = 20)
+    private String productName;
+
+    /**
+     * 订单数
+     **/
+    @Excel(name = "销售订单数量", sort = 5, width = 20)
+    private BigInteger orderNum;
+
+    /**
+     * 商品ID
+     * **/
+    @Excel(name = "商品编码", sort = 1, width = 20)
+    private Long productId;
+}

+ 150 - 0
fs-service-system/src/main/java/com/fs/store/vo/OrderStatisticsVo.java

@@ -0,0 +1,150 @@
+package com.fs.store.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * 订单维度统计实体
+ **/
+@Data
+public class OrderStatisticsVo implements Serializable {
+    /**
+     * 员工账号
+     **/
+    @Excel(name = "员工账号", sort = 1, width = 30)
+    private String userName;
+    /**
+     * 员工名称
+     **/
+    @Excel(name = "员工名称", sort = 2, width = 20)
+    private String nickName;
+
+    /**
+     * 企业名称
+     **/
+    @Excel(name = "企业名称", sort = 0, width = 20)
+    private String companyName;
+
+    /**
+     * 商品名称
+     **/
+    private String productName;
+
+    /**
+     * 标签名称
+     **/
+    private String cateName;
+
+    /**
+     * 订单数
+     **/
+    private BigInteger orderNum;
+
+    /**
+     * 用户ID
+     **/
+    private Long userId;
+
+    /**
+     * 商品ID
+     **/
+    private Long productId;
+
+    /**
+     * 标签ID
+     **/
+    private Long cateId;
+
+    /**
+     * 总单数
+     **/
+    @Excel(name = "总单数", sort = 6, width = 20)
+    private BigInteger totalNum;
+
+    /**
+     * 总金额
+     **/
+    @Excel(name = "总金额", sort = 7, width = 20)
+    private BigDecimal totalPrice;
+
+    /**
+     * 成交单数
+     **/
+    @Excel(name = "成交单数", sort = 8, width = 20)
+    private BigInteger dealNum;
+
+    /**
+     * 成交金额
+     **/
+    @Excel(name = "成交金额", sort = 9, width = 20)
+    private BigDecimal dealPrice;
+
+    /**
+     * 取消单数
+     **/
+    @Excel(name = "取消单数", sort = 10, width = 20)
+    private BigInteger cancelNum;
+
+    /**
+     * 取消金额
+     **/
+    @Excel(name = "取消金额", sort = 11, width = 20)
+    private BigDecimal cancelPrice;
+
+    /**
+     * 待发货单数
+     **/
+    @Excel(name = "待发货单数", sort = 12, width = 20)
+    private BigInteger pendingNum;
+
+    /**
+     * 待发货金额
+     **/
+    @Excel(name = "待发货金额", sort = 13, width = 20)
+    private BigDecimal pendingPrice;
+
+    /**
+     * 发货单数
+     **/
+    @Excel(name = "发货单数", sort = 14, width = 20)
+    private BigInteger invoiceNum;
+
+    /**
+     * 发货金额
+     **/
+    @Excel(name = "发货金额", sort = 15, width = 20)
+    private BigDecimal invoicePrice;
+
+    /**
+     * 签收单数
+     **/
+    @Excel(name = "签收单数", sort = 14, width = 20)
+    private BigInteger signForNum;
+
+    /**
+     * 签收金额
+     **/
+    @Excel(name = "签收金额", sort = 15, width = 20)
+    private BigDecimal signFPrice;
+
+    /**
+     * 退单数
+     **/
+    @Excel(name = "退单数", sort = 16, width = 20)
+    private BigInteger chargebackNum;
+
+    /**
+     * 退单金额
+     **/
+    @Excel(name = "退单金额", sort = 17, width = 20)
+    private BigDecimal chargebackPrice;
+
+    /**
+     * 企业ID
+     * **/
+    private Long companyId;
+}

+ 5 - 0
fs-service-system/src/main/resources/application-config-zkzh.yml

@@ -76,6 +76,11 @@ wx:
         token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
         aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
         msgDataFormat: JSON
+      - appid: wx414427b10866c04e   #凯逸轩服装店C
+        secret: 4a56cc02f53859e1e9582c15d840c4af
+        token: Ncbnd7lJvkripVOpyTFAna6NAWCxCrvC
+        aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
+        msgDataFormat: JSON
 
   pay:
     appId: wx11a2ce7c2bbc4521 #微信公众号或者小程序等的appid

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

@@ -20,6 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="watchLogId"    column="watch_log_id"    />
         <result property="remark"    column="remark"    />
         <result property="result"    column="result"    />
+        <result property="batchId"    column="batch_id"    />
     </resultMap>
 
     <sql id="selectFsCourseRedPacketLogVo">
@@ -113,6 +114,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark,</if>
             <if test="periodId != null">period_id,</if>
             <if test="result != null">result,</if>
+            <if test="batchId != null">batch_id,</if>
+
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="courseId != null">#{courseId},</if>
@@ -130,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">#{remark},</if>
             <if test="periodId != null">#{periodId},</if>
             <if test="result != null">#{result},</if>
+            <if test="batchId != null">#{batchId},</if>
          </trim>
     </insert>
 
@@ -151,6 +155,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark = #{remark},</if>
             <if test="periodId != null">period_id = #{periodId},</if>
             <if test="result != null">result = #{result},</if>
+            <if test="batchId != null">batch_id = #{batchId},</if>
         </trim>
         where log_id = #{logId}
     </update>

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

@@ -62,7 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ,DATE_FORMAT(create_time, '%Y-%m-%d') AS `month`  from fs_course_traffic_log
         <where>
             <if test="startDate != null and endDate != null">
-                and create_time between #{startDate} AND #{endDate}
+                and DATE_FORMAT(create_time, '%Y-%m-%d') between #{startDate} AND #{endDate}
             </if>
             <if test='companyId !=null'>
                 and company_id = #{companyId}
@@ -74,7 +74,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and project = ${project}
             </if>
         </where>
-        group by company_id,`month`,project,course_id
+
+        <if test="tabType==null or tabType==''">
+            group by company_id,`month`,course_id,project
+        </if>
+        <if test="tabType!=null and tabType=='project'">
+            group by project,`month`
+        </if>
+        <if test="tabType!=null and tabType=='course'">
+            group by course_id,`month`
+        </if>
+        <if test="tabType!=null and tabType=='company'">
+            group by company_id,`month`
+        </if>
+
     </select>
     <select id="getTodayTrafficLog" resultType="java.lang.Long">
         SELECT

+ 1 - 0
fs-service-system/src/main/resources/mapper/course/FsUserWatchStatisticsMapper.xml

@@ -119,6 +119,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             fwl.company_id
         FROM
             fs_course_watch_log fwl
+
         WHERE
             fwl.send_type = 1
         GROUP BY

+ 19 - 0
fs-service-system/src/main/resources/mapper/his/FsUserWxMapper.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsUserWxMapper">
+
+    <insert id="insertOrUpdateByUniqueKey">
+        insert into fs_user_wx
+            (fs_user_id, app_id, union_id, open_id, create_time, update_time)
+        values
+            (#{fsUserId}, #{appId}, #{unionId}, #{openId}, #{createTime, jdbcType=TIMESTAMP}, #{updateTime, jdbcType=TIMESTAMP})
+        ON DUPLICATE KEY UPDATE
+        <if test="unionId != null">
+            union_id = VALUES(union_id),
+        </if>
+            open_id     = VALUES(open_id),
+            update_time = VALUES(update_time)
+    </insert>
+</mapper>

+ 12 - 0
fs-service-system/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml

@@ -213,6 +213,12 @@
             <if test="userType != null">
                 AND send_type = ${userType}
             </if>
+            <if test="companyId != null">
+                AND company_id = #{companyId}
+            </if>
+            <if test="companyUserId != null">
+                AND company_user_id = #{companyUserId}
+            </if>
         </where>
         GROUP BY
         start_date
@@ -322,6 +328,9 @@
             <if test="companyId != null">
                 and log.company_id = ${companyId}
             </if>
+            <if test="companyUserId != null">
+                and log.company_user_id = #{companyUserId}
+            </if>
         </where>
         GROUP BY
             <if test="dataType == 0">
@@ -361,6 +370,9 @@
             <if test="companyId != null">
                 and log.company_id = ${companyId}
             </if>
+            <if test="companyUserId != null">
+                and log.company_user_id = #{companyUserId}
+            </if>
         </where>
         group by start_date
     </select>

+ 217 - 0
fs-service-system/src/main/resources/mapper/store/FsStoreOrderMapper.xml

@@ -543,4 +543,221 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and company_user_id = #{companyUserId}
         </if>
     </select>
+
+    <select id="selectOrderDimensionStatisticsList" resultType="com.fs.store.vo.OrderStatisticsVo">
+        SELECT
+        <if test="param.groupType == 2">
+            fsp.cate_id,
+            fspc.cate_name,
+        </if>
+        <if test="param.groupType == 3">
+            fsoi.product_id,
+            fsp.product_name,
+        </if>
+        COUNT(*) orderNum
+        FROM
+        company_user cu
+        INNER JOIN company c ON cu.company_id = c.company_id
+        INNER JOIN fs_store_order fso ON cu.user_id = fso.company_user_id
+        INNER JOIN fs_store_order_item fsoi ON fso.id = fsoi.order_id
+        INNER JOIN fs_store_product fsp ON fsoi.product_id = fsp.product_id
+        INNER JOIN fs_store_product_category fspc ON fspc.cate_id = fsp.cate_id
+        WHERE
+        fso.is_del = '0'
+        AND fso.`status` > 1
+
+        <if test="param.companyId != null and param.companyId != ''">
+            AND  c.company_id = #{param.companyId}
+        </if>
+
+        <if test="param.userName != null and param.userName != ''">
+            AND cu.user_name Like CONCAT('%',#{param.userName},'%')
+        </if>
+
+        <if test="param.companyUser != null and param.companyUser != ''">
+            AND cu.nick_name Like CONCAT('%',#{param.companyUser},'%')
+        </if>
+
+        <if test="param.productName != null and param.productName != ''">
+            AND fsp.product_name Like CONCAT('%',#{param.productName},'%')
+        </if>
+
+        <if test="param.cateId != null and param.cateId != ''">
+            AND fspc.cate_id = #{param.cateId}
+        </if>
+
+        <if test="param.cateName != null and param.cateName != ''">
+            AND fspc.cate_name Like CONCAT('%',#{param.cateName},'%')
+        </if>
+
+        <if test="param.startDate != null">
+            AND DATE_FORMAT(fso.pay_time,'%Y-%m') >= DATE_FORMAT(#{param.startDate},'%Y-%m')
+        </if>
+
+        <if test="param.endDate != null">
+            AND DATE_FORMAT(fso.pay_time,'%Y-%m') &lt;= DATE_FORMAT(#{param.endDate},'%Y-%m')
+        </if>
+
+        GROUP BY
+
+        <if test="param.groupType == 2">
+            fsp.cate_id
+        </if>
+
+        <if test="param.groupType == 3">
+            fsoi.product_id
+        </if>
+        ORDER BY
+        cu.company_id DESC
+    </select>
+
+    <select id="selectOrderSaleStatisticsList" resultType="com.fs.store.vo.OrderStatisticsVo">
+      SELECT * FROM (  SELECT
+        cu.user_id,
+        cu.user_name,
+        cu.nick_name,
+        c.company_name,
+        c.company_id,
+        SUM(fso.`status` != 0) AS totalNum,
+        SUM(
+        CASE
+        WHEN fso.`status` != 0 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) totalPrice,
+        SUM(fso.`status` = 5) AS dealNum,
+        SUM(
+        CASE
+        WHEN fso.`status` = 5 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) AS dealPrice,
+        SUM(
+        fso.`status` = 1
+        OR fso.`status` = -2
+        ) AS cancelNum,
+        SUM(
+        CASE
+        WHEN fso.`status` = 1
+        OR fso.`status` = -2 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) AS cancelPrice,
+        SUM(
+        fso.`status` = 2
+        AND (
+        fso.delivery_pay_status = 0
+        OR fso.delivery_pay_status IS NULL
+        )
+        ) AS pendingNum,
+        SUM(
+        CASE
+        WHEN fso.`status` = 2
+        AND (
+        fso.delivery_pay_status = 0
+        OR fso.delivery_pay_status IS NULL
+        ) THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        )  AS pendingPrice,
+        SUM(
+        fso.`status` = 3
+        AND fso.delivery_pay_status > 0
+        ) AS invoiceNum,
+        SUM(
+        CASE
+        WHEN fso.`status` = 3
+        AND fso.delivery_pay_status > 0 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) AS invoicePrice,
+        IFNULL(
+        SUM(
+        fso.`status` > 3
+        AND fso.delivery_pay_status = 3
+        ),
+        0
+        ) AS signForNum,
+        SUM(
+        CASE
+        WHEN fso.`status` > 3
+        AND fso.delivery_pay_status = 3 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) AS signFPrice,
+        SUM(
+        fso.`status` = -1
+        AND fso.refund_status = 2
+        ) AS chargebackNum,
+        SUM(
+        CASE
+        WHEN fso.`status` = -1
+        AND fso.refund_status = 2 THEN
+        fso.total_price
+        ELSE
+        0
+        END
+        ) AS chargebackPrice
+        FROM
+        company_user cu
+        INNER JOIN company c ON cu.company_id = c.company_id
+        LEFT JOIN fs_store_order fso ON cu.user_id = fso.company_user_id
+        INNER JOIN fs_store_order_item fsoi ON fso.id = fsoi.order_id
+        INNER JOIN fs_store_product fsp ON fsoi.product_id = fsp.product_id
+        INNER JOIN fs_store_product_category fspc ON fspc.cate_id = fsp.cate_id
+        WHERE
+        fso.is_del = '0'
+        AND fso.`status` != 0
+        <if test="param.companyId != null and param.companyId != ''">
+            AND  c.company_id = #{param.companyId}
+        </if>
+
+        <if test="param.userName != null and param.userName != ''">
+            AND cu.user_name Like CONCAT('%',#{param.userName},'%')
+        </if>
+
+        <if test="param.companyUser != null and param.companyUser != ''">
+            AND cu.nick_name Like CONCAT('%',#{param.companyUser},'%')
+        </if>
+
+        <if test="param.productId != null and param.productId != ''">
+            AND fsp.product_id = #{param.productId}
+        </if>
+
+        <if test="param.productName != null and param.productName != ''">
+            AND fsp.product_name Like CONCAT('%',#{param.productName},'%')
+        </if>
+
+        <if test="param.cateName != null and param.cateName != ''">
+            AND fspc.cate_name Like CONCAT('%',#{param.cateName},'%')
+        </if>
+
+        <if test="param.cateId != null and param.cateId != ''">
+            AND fspc.cate_id = #{param.cateId}
+        </if>
+
+        <if test="param.startDate != null">
+            AND DATE_FORMAT(fso.pay_time,'%Y-%m') >= DATE_FORMAT(#{param.startDate},'%Y-%m')
+        </if>
+
+        <if test="param.endDate != null">
+            AND DATE_FORMAT(fso.pay_time,'%Y-%m') &lt;= DATE_FORMAT(#{param.endDate},'%Y-%m')
+        </if>
+        GROUP BY
+        cu.user_id
+        ) a ORDER BY
+        a.company_id DESC,a.totalNum DESC
+    </select>
 </mapper>

+ 0 - 1
fs-service-system/src/main/resources/mapper/store/FsUserCourseCountMapper.xml

@@ -62,7 +62,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ifnull ( sum( fs_user_course_count.miss_course_count ), 0 ) AS miss_course_count,
             ifnull ( sum( fs_user_course_count.part_course_count ), 0 ) AS part_course_count,
             Max( fs_user_course_count.last_watch_date ) AS last_watch_date,
-            ifnull ( sum( fs_user_course_count.stop_watch_days ), 0 ) AS stop_watch_days,
             Max( fs_user_course_count.complete_watch_date) AS complete_watch_date
             from fs_user_course_count where user_id = ${userId}
     </select>

+ 87 - 22
fs-service-system/src/main/resources/mapper/store/FsUserMapper.xml

@@ -176,9 +176,10 @@
             <if test="registerCode != null   and registerCode != '' ">and register_code = #{registerCode}</if>
             <if test="source != null  and source != '' ">and source = #{source}</if>
             <if test="isShow != null  ">and is_show = #{isShow}</if>
-            <if test="(username != null  and username != '') or (nickname != null  and nickname != '') or (phone != null  and phone != '')">
+            <if test="(username != null  and username != '') or (userId != null  and userId != '') or (nickname != null  and nickname != '') or (phone != null  and phone != '')">
                 and (
                 <if test="username != null  and username != ''">username like concat('%', #{username}, '%')</if>
+                <if test="userId != null  and userId != ''">or user_id = #{userId}</if>
                 <if test="nickname != null  and nickname != ''">or nickname like concat('%', #{nickname}, '%')</if>
                 <if test="phone != null  and phone != ''">or phone like concat('%',#{phone},'%')</if>
                 )
@@ -649,6 +650,14 @@
         <if test="projectId != null">
             AND fs_user_company_user.project_id = #{projectId}
         </if>
+        <choose>
+            <when test = "isBlack">
+                AND (fs_user.status = 0 or fs_user_company_user.is_repeat_fans = 1)
+            </when>
+            <otherwise>
+                AND (fs_user.status = 1 and (fs_user_company_user.is_repeat_fans = 0 or fs_user_company_user.is_repeat_fans is null ))
+            </otherwise>
+        </choose>
         <if test="keyword != null and keyword !='' ">
             AND (fs_user.nickname LIKE concat('%',#{keyword},'%')
             or  fs_user.phone LIKE concat('%',#{keyword},'%')
@@ -1530,6 +1539,9 @@
                     #{videoId}
                 </foreach>
             </if>
+            <if test="companyId != null">
+                AND flog.company_id = #{companyId}
+            </if>
         </where>
         GROUP BY
         flog.video_id
@@ -1573,7 +1585,9 @@
                     #{videoId}
                 </foreach>
             </if>
-
+            <if test="companyId != null">
+                AND fs_course_answer_logs.company_id = #{companyId}
+            </if>
         </where>
         GROUP BY
         fs_course_answer_logs.video_id
@@ -1698,6 +1712,9 @@
                     #{videoId}
                 </foreach>
             </if>
+            <if test="companyId != null">
+                AND fwl.company_id = #{companyId}
+            </if>
         </where>
         GROUP BY
         fwl.video_id
@@ -1784,6 +1801,7 @@
             fwl.period_id, fwl.video_id, fwl.company_user_id, fwl.company_id
         FROM
             fs_course_watch_log fwl
+
         WHERE
             fwl.send_type = 1
         GROUP BY
@@ -1825,6 +1843,7 @@
         FROM
             fs_course_answer_logs
                 LEFT JOIN fs_user ON fs_user.user_id = fs_course_answer_logs.user_id
+                LEFT JOIN company_user ON company_user.user_id = fs_user.company_user_id
         GROUP BY
             fs_course_answer_logs.period_id,
             fs_course_answer_logs.video_id,
@@ -1879,37 +1898,47 @@
             and cu.company_user_id = #{companyUserId}
         </if>
     </select>
+
     <select id="countUserCourse2" resultType="java.util.Map">
         SELECT
         (
         SELECT
-        count(1)
+        count(DISTINCT l.user_id)
         FROM
         fs_course_watch_log l
+        LEFT JOIN fs_user on fs_user.user_id = l.user_id
         LEFT JOIN company_user ON l.company_user_id = company_user.user_id
-        LEFT JOIN fs_user ON fs_user.user_id = l.user_id
         LEFT JOIN fs_user_company_user ON fs_user_company_user.user_id = fs_user.user_id and l.project =fs_user_company_user.project_id
         where
-            l.log_type != 3 and send_type = 1
-            <if test="userId != null and userId != 0 ">
-                and (fs_user_company_user.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
-            </if>
-            <if test="userId != null and userId == 0 ">
-                and l.company_id = #{companyId}
-            </if>
-            <if test="periodId != null and periodId != ''">
-                AND l.period_id = #{periodId}
-            </if>
-            <if test="videoId != null and videoId != ''">
-                AND l.video_id = #{videoId}
-            </if>
-            <if test="companyUserId != null and companyUserId != ''">
-                AND l.user_id = #{companyUserId}
-            </if>
+        l.log_type != 3 and send_type = 1
+        <if test="userId != null and userId != 0 ">
+            and (fs_user_company_user.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
+        </if>
+        <if test="userId != null and userId == 0 ">
+            and l.company_id = #{companyId}
+        </if>
+        <if test="periodId != null and periodId != ''">
+            AND l.period_id = #{periodId}
+        </if>
+        <if test="videoId != null and videoId != ''">
+            AND l.video_id = #{videoId}
+        </if>
+        <if test="startTime != null and startTime !='' ">
+            and l.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null and endTime != ''">
+            and l.create_time &lt;= #{endTime}
+        </if> <if test="companyUserId != null and companyUserId != ''">
+        AND l.user_id = #{companyUserId}
+    </if>
+        -- 单独通过销售id查询
+        <if test="companyUserId != null and companyUserId != ''">
+            AND l.company_user_id = #{companyUserId}
+        </if>
         ) as courseWatchNum,
         (
         SELECT
-        count(1)
+        count(DISTINCT l.user_id)
         FROM
         fs_course_watch_log l
         LEFT JOIN company_user ON l.company_user_id = company_user.user_id
@@ -1929,11 +1958,47 @@
         <if test="videoId != null and videoId != ''">
             AND l.video_id = #{videoId}
         </if>
+        <if test="startTime != null and startTime !='' ">
+            and l.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null and endTime != ''">
+            and l.create_time &lt;= #{endTime}
+        </if>
         -- 单独通过销售id查询
         <if test="companyUserId != null and companyUserId != ''">
-            AND l.user_id = #{companyUserId}
+            AND l.company_user_id = #{companyUserId}
         </if>
         ) as courseCompleteNum
     </select>
 
+    <select id="countCourseDetailsNew" resultType="Map">
+        SELECT
+        count( DISTINCT l.period_id ) as courseNum,
+        count( DISTINCT l.video_id ) as videoNum,
+        count( DISTINCT l.user_id ) as courseUserNum
+        FROM
+        fs_course_watch_log l
+        left join fs_user on fs_user.user_id = l.user_id
+        LEFT JOIN company_user ON l.company_user_id = company_user.user_id
+        WHERE
+        l.log_type != 3
+        AND send_type = 1
+        <if test="userId != null and userId != 0 ">
+            AND l.company_user_id = #{userId}
+        </if>
+        <if test="userId != null and userId == 0 ">
+            and l.company_id = #{companyId}
+        </if>
+        <if test="periodId != null and periodId != ''">
+            AND l.period_id =  #{periodId}
+        </if>
+        <if test="videoId != null and videoId != ''">
+            AND l.video_id = #{videoId}
+        </if>
+        -- 单独通过销售id查询
+        <if test="companyUserId != null and companyUserId != ''">
+            AND l.company_user_id = #{companyUserId}
+        </if>
+    </select>
+
 </mapper>

+ 16 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -15,6 +15,7 @@ import com.fs.course.service.*;
 import com.fs.course.service.impl.TencentCloudCosService;
 import com.fs.course.vo.*;
 import com.fs.his.vo.OptionsVO;
+import com.fs.store.service.IFsStorePaymentService;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -43,6 +44,8 @@ public class CourseController extends  AppBaseController{
     @Autowired
     private IFsUserCourseService courseService;
     @Autowired
+    private IFsStorePaymentService storePaymentService;
+    @Autowired
     private IFsUserCourseCategoryService courseCategoryService;
 
     @Autowired
@@ -539,4 +542,17 @@ public class CourseController extends  AppBaseController{
         return R.ok().put("data", pageInfo);
     }
 
+
+    /**
+     * 手动根据销售员工信息发放奖励
+     * @param userId
+     * @return
+     */
+    @GetMapping("/sendRewardByTest")
+    public R sendRewardByTest(@RequestBody Long userId)
+    {
+        logger.info("zyp \n【发放奖励】:{}",userId);
+        return  storePaymentService.sendRewardByTest(userId);
+    }
+
 }

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -307,6 +307,9 @@ public class UserController extends  AppBaseController {
         if (param.getNickname().length()>50){
             return R.error("请授权正确的昵称!");
         }
+        if (StringUtils.isEmpty(param.getAvatar())){
+            param.setAvatar("https://zkzh-2025.oss-cn-beijing.aliyuncs.com/fs/20250715/533c6f87cc2e4ba4a9504374bb58416d.png");
+        }
         FsUser user=new FsUser();
         user.setUserId(Long.parseLong(getUserId()));
         user.setAvatar(param.getAvatar());

+ 8 - 9
fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserController.java

@@ -81,7 +81,7 @@ public class WxCompanyUserController extends AppBaseController {
 //            return R.error("昵称不符合标准!");
 //        }
         //获取第二个小程序配置,序号从0开始
-        final WxMaService wxService = WxMaConfiguration.getMaService(maProperties.getConfigs().get(1).getAppid());
+        final WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
         try {
             WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
             this.logger.info(session.getSessionKey());
@@ -135,16 +135,13 @@ public class WxCompanyUserController extends AppBaseController {
 //                    userMap.setAvatar(param.getAvatar() != null ? param.getAvatar() : null);
                     userMap.setPhone(phoneNoInfo.getPhoneNumber());
                     // 逻辑调整:如果会员已经绑定了销售,直接提示,不让注册-2025年6月16日14点53分
-                    FsUserCompanyUser userCompanyUser = userCompanyUserService.selectByUserIdAndProjectId(user.getUserId(), param.getProjectId());
-                    if (Objects.nonNull(userCompanyUser) && !param.getCompanyUserId().equals(userCompanyUser.getCompanyUserId())){
-                        return R.error(406, "该用户已成为其他销售会员");
+                    if (user.getCompanyUserId() != null && !param.getCompanyUserId().equals(user.getCompanyUserId())){
+                        return R.error(500, "该用户("+user.getUserId() + ")已成为其他销售会员");
                     }
-//                    if(companyUser != null &&
-//                            (companyUser.getIsAllowedAllRegister() == null || companyUser.getIsAllowedAllRegister() == 1)
+//                    if((companyUser.getIsAllowedAllRegister() == null || companyUser.getIsAllowedAllRegister() == 1)
 //                            && companyUser.getIsNeedRegisterMember() != null && companyUser.getIsNeedRegisterMember() != 1){
-//                        if (Objects.isNull(userCompanyUser)) {
-//                            userCompanyUserService.bindRelationship(user.getUserId(), param.getProjectId(), companyUser.getCompanyId(), companyUser.getUserId(), defaultStatus);
-//                        }
+//                        userMap.setCompanyId(param.getCompanyId());
+//                        userMap.setCompanyUserId(param.getCompanyUserId());
 //                    }
                     userService.updateFsUser(userMap);
                 } else {
@@ -163,6 +160,8 @@ public class WxCompanyUserController extends AppBaseController {
                             && companyUser.getIsNeedRegisterMember() != null && companyUser.getIsNeedRegisterMember() != 1){
                         userCompanyUserService.bindRelationship(user.getUserId(), param.getProjectId(), companyUser.getCompanyId(), companyUser.getUserId(), defaultStatus);
                     }
+//                    redisCache.setCacheObject("");
+                    userService.insertFsUser(user);
                 }
             } else {
                 FsUser userMap = new FsUser();

+ 266 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxCompanyUserCopyController.java

@@ -0,0 +1,266 @@
+package com.fs.app.controller;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
+import cn.hutool.core.date.DateTime;
+import com.fs.app.param.LoginMaWxParam;
+import com.fs.app.utils.JwtUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+
+import com.fs.common.utils.IpUtil;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyDeptService;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
+
+import com.fs.his.domain.FsUserWx;
+import com.fs.his.service.IFsUserWxService;
+import com.fs.store.domain.FsUser;
+import com.fs.store.service.IFsUserService;
+import com.fs.system.mapper.SysConfigMapper;
+import com.fs.wx.miniapp.config.WxMaConfiguration;
+import com.fs.wx.miniapp.config.WxMaProperties;
+import io.jsonwebtoken.Claims;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Date;
+
+import static com.fs.common.utils.PhoneUtil.encryptPhone;
+
+
+@Api("微信小程序相关接口")
+@RestController
+@RequestMapping(value = "/app/wx/miniapp/test")
+@Slf4j
+public class WxCompanyUserCopyController extends AppBaseController {
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Autowired
+    private WxMaProperties maProperties;
+
+    @Autowired
+    JwtUtils jwtUtils;
+
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+
+    @Autowired
+    private IFsUserService userService;
+
+    @Autowired
+    ICompanyService companyService;
+
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
+
+    @Autowired
+    private IFsUserWxService fsUserWxService;
+
+    @ApiOperation("小程序-授权登录")
+    @PostMapping("/loginByMa")
+    public R login(@RequestBody LoginMaWxParam param) {
+        log.info("=====================进入小程序授权登录, 入参: {}", param);
+        if (StringUtils.isBlank(param.getCode())) {
+            return R.error("code不存在");
+        }
+
+        // 特殊(需求设计:需要根据公司是否开启黑名单来设置会员初始化的状态)
+        Company company = companyService.selectCompanyById(param.getCompanyId());
+        if (company==null || company.getStatus()==0){
+            return R.error("注册失败团队已停用,或不存在!");
+        }
+
+        // 根据销售后台设置的  是否需要单独注册会员 来判断是否需要设置销售的值
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(param.getCompanyUserId());;
+        if(companyUser == null || companyUser.getStatus().equals("1")){
+            return R.error("注册失败客服已停用,或不存在!");
+        }
+//        if (company.getCourseMiniAppId() == null) {
+//            return R.error("小程序参数错误!");
+//        }
+//        if (!param.getAppId().equals(company.getCourseMiniAppId())){
+//            return R.error("无权限,");
+//        }
+
+        final WxMaService wxService = WxMaConfiguration.getMaService(param.getAppId());
+        try {
+            WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(param.getCode());
+            this.logger.info(session.getSessionKey());
+            this.logger.info(session.getOpenid());
+            this.logger.info(session.getUnionid());
+            if (StringUtils.isEmpty(session.getOpenid())){
+                return R.error("登陆失败,openid未授权,请稍后再试!");
+            }
+
+            // 手机号信息
+            WxMaPhoneNumberInfo phoneNoInfo = new WxMaPhoneNumberInfo();;
+            if (param.getAuthType()==1){
+                phoneNoInfo = wxService.getUserService().getPhoneNoInfo(session.getSessionKey(), param.getEncryptedData(), param.getIv());
+                if (StringUtils.isEmpty(phoneNoInfo.getPhoneNumber())){
+                    return R.error("授权失败,请联系客服!");
+                }
+            }
+
+            FsUser user = getUserByAuthType(param, wxService, session, phoneNoInfo);
+
+
+            if (user!=null && user.getCompanyUserId() != null && !param.getCompanyUserId().equals(user.getCompanyUserId())) {
+                return R.error(500, "该用户("+user.getUserId() + ")已成为其他销售会员");
+            }
+
+            // 3. 处理用户注册或更新
+            String ip = IpUtil.getRequestIp();
+            user = handleUserRegisterOrUpdate(user, param, session, phoneNoInfo, company, companyUser, ip);
+
+            // 4. 处理用户与小程序的绑定
+            handleFsUserWx(user, param, company, session);
+
+            log.info("保存成功的用户信息user: {}, 用户id: {},小程序AppId:{}", user, user.getUserId(), param.getAppId());
+            String token = jwtUtils.generateToken(user.getUserId());
+            // 返回TOKEN和user
+            return R.ok("登录成功").put("token", token).put("user", user);
+        } catch (WxErrorException e) {
+            this.logger.error(e.getMessage(), e);
+            return R.error("授权失败," + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据authType获取用户信息
+     */
+    private FsUser getUserByAuthType(LoginMaWxParam param, WxMaService wxService, WxMaJscode2SessionResult session, WxMaPhoneNumberInfo phoneNoInfo) throws WxErrorException {
+        FsUser user = null;
+        if (param.getAuthType() == 1) {
+            user = userService.selectFsUserByPhone(encryptPhone(phoneNoInfo.getPhoneNumber()));
+        } else {
+            // unionid判定唯一
+            if (StringUtils.isNotEmpty(session.getUnionid())) {
+                user = userService.selectFsUserByUnionId(session.getUnionid());
+            }
+        }
+        return user;
+    }
+
+
+    /**
+     * 处理用户注册或更新
+     */
+    private FsUser handleUserRegisterOrUpdate(FsUser user, LoginMaWxParam param, WxMaJscode2SessionResult session, WxMaPhoneNumberInfo phoneNoInfo, Company company, CompanyUser companyUser, String ip) {
+        if (user == null) {
+            return createUser(param, session, phoneNoInfo, company, companyUser);
+        } else {
+            return updateUser(user, param, session, phoneNoInfo, company, companyUser);
+        }
+    }
+
+    /**
+     * 新增用户
+     */
+    private FsUser createUser(LoginMaWxParam param, WxMaJscode2SessionResult session, WxMaPhoneNumberInfo phoneNoInfo, Company company, CompanyUser companyUser) {
+        FsUser user = new FsUser();
+        user.setStatus((company != null ? company.getFsUserIsDefaultBlack() : 0) == 1 ? 0 : 1);
+        user.setUnionId(session.getUnionid() == null ? "" : session.getUnionid());
+        user.setCreateTime(new Date());
+        if (param.getAuthType() == 1 && phoneNoInfo != null) {
+            user.setPhone(phoneNoInfo.getPhoneNumber());
+        }
+        if((companyUser.getIsAllowedAllRegister() == null || companyUser.getIsAllowedAllRegister() == 1)
+                && companyUser.getIsNeedRegisterMember() != null && companyUser.getIsNeedRegisterMember() != 1){
+            user.setCompanyId(param.getCompanyId());
+            user.setCompanyUserId(param.getCompanyUserId());
+        }
+        userService.insertFsUser(user);
+        return user;
+    }
+
+    /**
+     * 修改用户
+     */
+    private FsUser updateUser(FsUser user, LoginMaWxParam param, WxMaJscode2SessionResult session, WxMaPhoneNumberInfo phoneNoInfo, Company company, CompanyUser companyUser) {
+        FsUser userMap = new FsUser();
+        userMap.setUserId(user.getUserId());
+        userMap.setUnionId(session.getUnionid() == null ? "" : session.getUnionid());
+        userMap.setUpdateTime(new DateTime());
+        if (param.getAuthType() == 1 && phoneNoInfo != null) {
+            userMap.setPhone(phoneNoInfo.getPhoneNumber());
+        }
+        userService.updateFsUser(userMap);
+        return userMap;
+    }
+
+
+    /**
+     * 处理用户与小程序的绑定
+     */
+    private void handleFsUserWx(FsUser user, LoginMaWxParam param, Company company, WxMaJscode2SessionResult session) {
+        if (user == null) return;
+        FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(company.getCourseMiniAppId(), user.getUserId(), 1);
+        if (fsUserWx == null) {
+            fsUserWx = new FsUserWx();
+            fsUserWx.setType(1);
+            fsUserWx.setFsUserId(user.getUserId());
+            fsUserWx.setCompanyId(param.getCompanyId());
+            fsUserWx.setAppId(company.getCourseMiniAppId());
+            fsUserWx.setOpenId(session.getOpenid());
+            fsUserWx.setUnionId(session.getUnionid() == null ? "" : session.getUnionid());
+            fsUserWx.setCreateTime(new Date());
+            fsUserWxService.save(fsUserWx);
+        } else {
+            fsUserWx.setFsUserId(user.getUserId());
+            fsUserWx.setCompanyId(param.getCompanyId());
+            fsUserWx.setAppId(company.getCourseMiniAppId());
+            fsUserWx.setOpenId(session.getOpenid());
+            fsUserWx.setUnionId(session.getUnionid() == null ? "" : session.getUnionid());
+            fsUserWx.setUpdateTime(new Date());
+            fsUserWxService.updateById(fsUserWx);
+        }
+    }
+
+//    @Login(isMiniLogin = true)
+//    @ApiOperation("获取销售通过小程序登录后的用户信息")
+//    @GetMapping("/getMaUser")
+//    public R getUserInfo() {
+//        try {
+//            CompanyUser companyUser = companyUserService.selectCompanyUserById(Long.parseLong(getUserId()));
+//            if (companyUser == null) {
+//                return R.error(401, "用户信息不存在");
+//            }
+//            return R.ok().put("user", companyUser);
+//        } catch (Exception e) {
+//            return R.error("操作异常");
+//        }
+//    }
+
+    /**
+     * 特殊要求:销售小程序临时登录,登录后页面中还有一个之前常用的登录,所以为了区分,token名称不能跟之前的一样
+     *
+     * @return 用户id
+     */
+    public String getUserId() {
+        String headValue = ServletUtils.getRequest().getHeader("UserToken");
+        Claims claims = jwtUtils.getClaimByToken(headValue);
+        String userId = claims.getSubject().toString();
+        return userId;
+    }
+
+
+}

+ 7 - 2
fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

@@ -112,12 +112,17 @@ public class WxPayController {
     }
 
     @PostMapping( "/test")
-    public R test() throws Exception {
-        WxSendRedPacketParam param  = new WxSendRedPacketParam();
+    public R test(@RequestBody WxSendRedPacketParam param) throws Exception {
         storePaymentService.sendRedPacketTest(param);
         return R.ok();
     }
 
+    @PostMapping( "/bufa")
+    public R bufa() throws Exception {
+        storePaymentService.bufaRedPacket();
+        return R.ok();
+    }
+
     @PostMapping( "/TransferNotify")
     public String TransferNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws Exception {
         return storePaymentService.transferNotify(notifyData,request);

+ 2 - 0
fs-user-app/src/main/java/com/fs/app/param/LoginMaWxParam.java

@@ -59,4 +59,6 @@ public class LoginMaWxParam implements Serializable {
     @ApiModelProperty(value = "小程序授权类型")
     private Integer authType;
 
+    private String appId;
+
 }