瀏覽代碼

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

15376779826 3 天之前
父節點
當前提交
9ade843a38
共有 81 個文件被更改,包括 1732 次插入233 次删除
  1. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java
  2. 1 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  3. 1 0
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  4. 197 0
      fs-admin/src/main/java/com/fs/live/controller/OrderController.java
  5. 33 0
      fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java
  6. 7 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  7. 13 2
      fs-company/src/main/java/com/fs/company/controller/live/LiveWatchLogController.java
  8. 2 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwFriendWelcomeController.java
  9. 19 4
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  10. 4 2
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  11. 90 3
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  12. 21 6
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  13. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java
  14. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  15. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  16. 4 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java
  17. 56 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  18. 4 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  19. 2 2
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  20. 10 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  21. 6 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreAfterSalesScrm.java
  22. 4 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  23. 4 4
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  24. 2 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java
  25. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  26. 19 2
      fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java
  27. 31 0
      fs-service/src/main/java/com/fs/ipad/vo/WxSendTextAtMsgVo.java
  28. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java
  29. 34 0
      fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java
  30. 8 0
      fs-service/src/main/java/com/fs/live/service/ILiveUserFirstEntryService.java
  31. 8 0
      fs-service/src/main/java/com/fs/live/service/ILiveVideoService.java
  32. 8 0
      fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java
  33. 5 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  34. 6 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  35. 28 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveUserFirstEntryServiceImpl.java
  36. 27 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveVideoServiceImpl.java
  37. 10 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java
  38. 6 0
      fs-service/src/main/java/com/fs/live/vo/LiveAfterSalesVo.java
  39. 118 0
      fs-service/src/main/java/com/fs/live/vo/LiveWatchLogListVO.java
  40. 153 0
      fs-service/src/main/java/com/fs/live/vo/MergedOrderExportVO.java
  41. 6 0
      fs-service/src/main/java/com/fs/live/vo/MergedOrderVO.java
  42. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwFriendWelcomeMapper.java
  43. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  44. 1 1
      fs-service/src/main/java/com/fs/qw/service/IQwFriendWelcomeService.java
  45. 34 5
      fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java
  46. 3 0
      fs-service/src/main/java/com/fs/qw/vo/QwFriendWelcomeVO.java
  47. 9 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java
  48. 3 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java
  49. 3 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTempRules.java
  50. 6 0
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java
  51. 207 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  52. 16 1
      fs-service/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java
  53. 103 15
      fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java
  54. 46 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxSendTextAtMsgTwoDTO.java
  55. 7 0
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceNew.java
  56. 1 0
      fs-service/src/main/resources/application-config-dev-jnlzjk.yml
  57. 1 0
      fs-service/src/main/resources/application-config-druid-bjzm-test.yml
  58. 1 0
      fs-service/src/main/resources/application-config-druid-bjzm.yml
  59. 2 2
      fs-service/src/main/resources/application-config-druid-cfryt.yml
  60. 1 0
      fs-service/src/main/resources/application-config-druid-ddgy.yml
  61. 1 0
      fs-service/src/main/resources/application-config-druid-heyantang.yml
  62. 1 0
      fs-service/src/main/resources/application-config-druid-hsyy.yml
  63. 1 0
      fs-service/src/main/resources/application-config-druid-jnlzjk.yml
  64. 1 0
      fs-service/src/main/resources/application-config-druid-nmgyt.yml
  65. 1 0
      fs-service/src/main/resources/application-config-druid-sxjz.yml
  66. 3 9
      fs-service/src/main/resources/application-config-druid-ylrz.yml
  67. 2 0
      fs-service/src/main/resources/application-config-zkzh.yml
  68. 1 1
      fs-service/src/main/resources/application-druid-nmgyt.yml
  69. 1 1
      fs-service/src/main/resources/application-druid-sxjz.yml
  70. 1 1
      fs-service/src/main/resources/application-druid-zkzh.yml
  71. 5 1
      fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml
  72. 3 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  73. 147 147
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  74. 17 6
      fs-service/src/main/resources/mapper/hisStore/MergedOrderMapper.xml
  75. 2 1
      fs-service/src/main/resources/mapper/live/LiveAfterSalesMapper.xml
  76. 35 0
      fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml
  77. 1 1
      fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml
  78. 63 1
      fs-service/src/main/resources/mapper/store/FsUserCourseCountMapper.xml
  79. 13 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseTransferController.java
  80. 16 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java
  81. 3 2
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

+ 6 - 6
fs-admin/src/main/java/com/fs/his/controller/FsAdvController.java

@@ -40,7 +40,7 @@ public class FsAdvController extends BaseController
     /**
      * 查询广告列表
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:list')")
+    @PreAuthorize("@ss.hasPermi('store:adv:list') or @ss.hasPermi('his:adv:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsAdv fsAdv)
     {
@@ -52,7 +52,7 @@ public class FsAdvController extends BaseController
     /**
      * 导出广告列表
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:export')")
+    @PreAuthorize("@ss.hasPermi('store:adv:export') or @ss.hasPermi('his:adv:export')")
     @Log(title = "广告", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsAdv fsAdv)
@@ -65,7 +65,7 @@ public class FsAdvController extends BaseController
     /**
      * 获取广告详细信息
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:query')")
+    @PreAuthorize("@ss.hasPermi('store:adv:query') or @ss.hasPermi('his:adv:query')")
     @GetMapping(value = "/{advId}")
     public AjaxResult getInfo(@PathVariable("advId") String advId)
     {
@@ -76,7 +76,7 @@ public class FsAdvController extends BaseController
     /**
      * 新增广告
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:add')")
+    @PreAuthorize("@ss.hasPermi('store:adv:add') or @ss.hasPermi('his:adv:add')")
     @Log(title = "广告", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsAdv fsAdv)
@@ -91,7 +91,7 @@ public class FsAdvController extends BaseController
     /**
      * 修改广告
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:edit')")
+    @PreAuthorize("@ss.hasPermi('store:adv:edit') or @ss.hasPermi('his:adv:edit')")
     @Log(title = "广告", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsAdv fsAdv)
@@ -105,7 +105,7 @@ public class FsAdvController extends BaseController
     /**
      * 删除广告
      */
-    @PreAuthorize("@ss.hasPermi('store:adv:remove')")
+    @PreAuthorize("@ss.hasPermi('store:adv:remove') or @ss.hasPermi('his:adv:remove')")
     @Log(title = "广告", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{advIds}")
     public AjaxResult remove(@PathVariable String[] advIds)

+ 1 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -106,6 +106,7 @@ public class FsStoreAfterSalesScrmController extends BaseController
                     .map(vo -> {
                         FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
                         try {
+                            zmvo.setPayCode(vo.getPayCode());
                             zmvo.setOrderCode(vo.getOrderCode());
                             zmvo.setStatus(vo.getOrderStatus().toString());
                             zmvo.setUserId(vo.getUserId());

+ 1 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java

@@ -120,6 +120,7 @@ public class LiveAfterSalesController extends BaseController
                     .map(vo -> {
                         FsStoreOrderItemExportRefundZMVO zmvo = new FsStoreOrderItemExportRefundZMVO();
                         try {
+                            zmvo.setPayCode(vo.getPayCode());
                             zmvo.setOrderCode(vo.getOrderCode());
                             zmvo.setStatus(vo.getOrderStatus().toString());
                             zmvo.setUserId(vo.getUserId());

+ 197 - 0
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -10,6 +10,9 @@ import com.fs.his.utils.PhoneUtil;
 import com.fs.hisStore.service.IMergedOrderService;
 import com.fs.live.param.MergedOrderQueryParam;
 import com.fs.live.vo.MergedOrderVO;
+import com.fs.live.vo.MergedOrderExportVO;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.BeanUtils;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -19,7 +22,10 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 合并订单Controller
@@ -34,6 +40,8 @@ public class OrderController extends BaseController
 {
     @Autowired
     private IMergedOrderService mergedOrderService;
+    // 设置最大导出数量限制为20000条
+    private static final int maxExportCount = 20000;
 
     /**
      * 查询合并订单列表
@@ -49,7 +57,196 @@ public class OrderController extends BaseController
             vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
             vo.setSalesPhone(ParseUtils.parsePhone(vo.getSalesPhone()));
             vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
+
+    /**
+     * 导出合并订单列表
+     */
+    @ApiOperation("导出合并订单列表")
+    @Log(title = "合并订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        
+        // 转换为导出VO
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, false);
+        
+        // 如果数据量在限制范围内,正常导出
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单数据");
+    }
+
+    /**
+     * 导出合并订单明细
+     */
+    @ApiOperation("导出合并订单明细")
+    @Log(title = "合并订单明细", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItems")
+    public AjaxResult exportItems(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细");
+    }
+
+    /**
+     * 导出合并订单(明文)
+     */
+    @ApiOperation("导出合并订单(明文)")
+    @Log(title = "合并订单(明文)", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportDetails")
+    public AjaxResult exportDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        // 转换为导出VO(明文模式,不脱敏)
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, true);
+
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单(明文)");
+    }
+
+    /**
+     * 导出合并订单明细(明文)
+     */
+    @ApiOperation("导出合并订单明细(明文)")
+    @Log(title = "合并订单明细(明文)", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItemsDetails")
+    public AjaxResult exportItemsDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细(明文)");
+    }
+
+    /**
+     * 导出合并订单发货单
+     */
+    @ApiOperation("导出合并订单发货单")
+    @Log(title = "合并订单发货单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportShipping")
+    public AjaxResult exportShipping(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单发货单");
+    }
+
+    /**
+     * 将 MergedOrderVO 转换为 MergedOrderExportVO
+     * @param list 原始数据列表
+     * @param isPlainText 是否为明文模式(true:不脱敏,false:脱敏)
+     * @return 导出VO列表
+     */
+    private List<MergedOrderExportVO> convertToExportVO(List<MergedOrderVO> list, boolean isPlainText)
+    {
+        if (list == null || list.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        return list.stream().map(vo -> {
+            MergedOrderExportVO exportVO = new MergedOrderExportVO();
+            
+            // 订单基本信息(参考 FsStoreOrderItemExportVO 的顺序)
+            exportVO.setOrderCode(vo.getOrderCode());
+            exportVO.setStatus(vo.getStatus() != null ? String.valueOf(vo.getStatus()) : null);
+            exportVO.setUserId(vo.getUserId());
+            
+            // 产品信息
+            exportVO.setProductName(vo.getProductName());
+            exportVO.setBarCode(vo.getBarCode());
+            exportVO.setProductSpec(vo.getProductSpec());
+            exportVO.setTotalNum(vo.getTotalNum());
+            exportVO.setPrice(vo.getTotalPrice()); // 产品价格使用订单总价
+            exportVO.setCost(vo.getCost());
+            exportVO.setFPrice(null); // 结算价,合并订单暂无此字段
+            exportVO.setCateName(vo.getCateName());
+            
+            // 收货信息
+            exportVO.setRealName(vo.getRealName());
+            if (isPlainText) {
+                exportVO.setUserPhone(vo.getUserPhone());
+                exportVO.setUserAddress(vo.getUserAddress());
+            } else {
+                exportVO.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+                exportVO.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            }
+            
+            // 时间信息
+            exportVO.setCreateTime(vo.getCreateTime());
+            exportVO.setPayTime(vo.getPayTime());
+            
+            // 物流信息
+            exportVO.setDeliverySn(vo.getDeliveryCode()); // 快递公司编号,合并订单暂无此字段
+            exportVO.setDeliveryName(vo.getDeliveryName()); // 快递公司,合并订单暂无此字段
+            exportVO.setDeliveryId(vo.getDeliveryId());
+            
+            // 公司和销售信息
+            exportVO.setCompanyName(vo.getCompanyName());
+            exportVO.setCompanyUserNickName(vo.getCompanyUserNickName());
+            
+            // 套餐信息
+            exportVO.setPackageName(null); // 套餐名称,合并订单暂无此字段
+            exportVO.setGroupBarCode(null); // 组合码,合并订单暂无此字段
+            
+            // 凭证信息
+            exportVO.setIsUpload(null); // 是否上传凭证,合并订单暂无此字段
+            exportVO.setUploadTime(null); // 上传时间,合并订单暂无此字段
+            
+            // 档期信息
+            exportVO.setScheduleName(null); // 归属档期,合并订单暂无此字段
+            
+            // 银行交易流水号
+            exportVO.setBankTransactionId(vo.getBankTransactionId());
+            
+            // 金额信息
+            exportVO.setTotalPrice(vo.getTotalPrice());
+            exportVO.setPayPrice(vo.getPayPrice());
+            exportVO.setPayMoney(vo.getPayMoney());
+            exportVO.setPayPostage(vo.getPayDelivery()); // 额外运费,合并订单暂无此字段
+            exportVO.setPayDelivery(vo.getPayDelivery());
+            
+            return exportVO;
+        }).collect(Collectors.toList());
+    }
 }

+ 33 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwFriendWelcomeController.java

@@ -0,0 +1,33 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.param.QwFriendWelcomeParam;
+import com.fs.qw.service.IQwFriendWelcomeService;
+import com.fs.qw.vo.QwFriendWelcomeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/friendWelcome")
+public class QwFriendWelcomeController extends BaseController {
+
+    @Autowired
+    private IQwFriendWelcomeService qwFriendWelcomeService;
+
+    /**
+     * 查询好友欢迎语列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:friendWelcome:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwFriendWelcomeParam qwFriendWelcomeParam) {
+        startPage();
+        List<QwFriendWelcomeVO> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+        return getDataTable(list);
+    }
+}

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

@@ -354,4 +354,11 @@ public class QwUserController extends BaseController {
         List<QwUserVO> list = qwUserService.selectAllQwUserListVO(qwUser);
         return getDataTable(list);
     }
+
+    @GetMapping("/getQwAllUserList")
+    public R getQwAllUserList(@RequestParam String corpId, @RequestParam Long companyId)
+    {
+        List<QwUserVO> list = companyUserService.selectCompanyQwUserList(corpId, companyId);
+        return  R.ok().put("data",list);
+    }
 }

+ 13 - 2
fs-company/src/main/java/com/fs/company/controller/live/LiveWatchLogController.java

@@ -3,6 +3,10 @@ package com.fs.company.controller.live;
 import java.util.List;
 
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.live.vo.LiveWatchLogListVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -34,7 +38,8 @@ public class LiveWatchLogController extends BaseController
 {
     @Autowired
     private ILiveWatchLogService liveWatchLogService;
-
+    @Autowired
+    private TokenService tokenService;
     /**
      * 查询直播看课记录列表
      */
@@ -43,7 +48,13 @@ public class LiveWatchLogController extends BaseController
     public TableDataInfo list(LiveWatchLog liveWatchLog)
     {
         startPage();
-        List<LiveWatchLog> list = liveWatchLogService.selectLiveWatchLogList(liveWatchLog);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if(null == loginUser) {
+           throw new RuntimeException("用户信息错误");
+        }
+        Long companyId = loginUser.getCompany().getCompanyId();
+        liveWatchLog.setCompanyId(companyId);
+        List<LiveWatchLogListVO> list = liveWatchLogService.selectLiveWatchLogListInfo(liveWatchLog);
         return getDataTable(list);
     }
 

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

@@ -17,6 +17,7 @@ import com.fs.qw.domain.QwFriendWelcome;
 import com.fs.qw.param.QwFriendWelcomeParam;
 import com.fs.qw.service.IQwFriendWelcomeService;
 import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.QwFriendWelcomeVO;
 import com.fs.qw.vo.QwOptionsVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -59,7 +60,7 @@ public class QwFriendWelcomeController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwFriendWelcomeParam.setCompanyId(loginUser.getCompany().getCompanyId());
         startPage();
-        List<QwFriendWelcome> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+        List<QwFriendWelcomeVO> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
         return getDataTable(list);
     }
 

+ 19 - 4
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -40,10 +40,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -248,6 +245,21 @@ public class IpadSendServer {
         }
     }
 
+        public void sendTxtAtMsg(BaseVo vo) {
+        WxSendTextAtMsgTwoDTO dto = new WxSendTextAtMsgTwoDTO();
+        List<WxSendTextAtMsgTwoDTO.Contentva> contentvaList = new ArrayList<>();
+        WxSendTextAtMsgTwoDTO.Contentva contentva = new WxSendTextAtMsgTwoDTO.Contentva();
+        contentva.setMsgtype(5);
+        contentva.setVid(0);
+        contentvaList.add(contentva);
+        dto.setContentva(contentvaList);
+        dto.setBase(vo);
+         dto.setUuid(vo.getUuid());
+        dto.setSend_userid(ipadSendUtils.userIds(vo));
+        dto.setIsRoom(true);
+       ipadSendUtils.sendTxtAtMsgVo(dto, vo.getServerId());
+    }
+    
     public void sendVoice(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
         if (StringUtils.isEmpty(content.getVoiceUrl()) || StringUtils.isEmpty(content.getVoiceDuration())) {
             log.debug("语音未生成无法发送,转文字发送:{}", vo);
@@ -465,6 +477,9 @@ public class IpadSendServer {
                     // 语音
                     sendWxVideo(vo, content);
                     break;
+                case "99":
+                    // 群发
+                    sendTxtAtMsg(vo);
                 default:
                     // 未知类型,记录警告
                     log.error("SOP_LOG_ID:{}错误的发送类型: {}", qwSopLogs.getId(), content.getContentType());

+ 4 - 2
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -665,12 +665,14 @@ public class Task {
                     if (currentTimeMillis >= endTimeMillis) {
                         log.info("直播间视频播放完成,开始打标签: liveId={}, startTime={}, videoDuration={}, endTime={}, currentTime={}", 
                                 liveId, startTimeMillis, videoDuration, endTimeMillis, currentTimeMillis);
+
+                        // 标记为已处理,稍后删除缓存
+                        processedLiveIds.add(liveId);
                         
                         // 调用打标签方法
                         liveWatchUserService.qwTagMarkByLiveWatchLog(liveId);
                         
-                        // 标记为已处理,稍后删除缓存
-                        processedLiveIds.add(liveId);
+
                     }
                 } catch (Exception e) {
                     log.error("处理直播间打标签缓存异常: key={}, error={}", key, e.getMessage(), e);

+ 90 - 3
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.hisStore.domain.FsUserScrm;
@@ -26,6 +27,7 @@ import com.fs.live.vo.LiveGoodsVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
@@ -336,6 +338,9 @@ public class WebSocketServer {
                                 redisCache.redisTemplate.opsForHash().put(hashKey, userIdField, currentDuration.toString());
                                 // 设置过期时间(2小时)
                                 redisCache.redisTemplate.expire(hashKey, 2, TimeUnit.HOURS);
+                                
+                                // 实时更新用户看课状态(仅在直播期间)
+                                updateWatchLogTypeInRealTime(liveId, watchUserId, currentDuration);
                             }
                         } catch (Exception e) {
                             log.error("心跳更新观看时长失败, liveId={}, userId={}", liveId, watchUserId, e);
@@ -1154,6 +1159,86 @@ public class WebSocketServer {
         }
     }
     
+    /**
+     * 实时更新用户看课状态(在心跳时调用)
+     * 在直播期间实时更新用户的看课状态,而不是等到关闭 WebSocket 或清理无效会话时才更新
+     * @param liveId 直播间ID
+     * @param userId 用户ID
+     * @param watchDuration 观看时长(秒)
+     */
+    public void updateWatchLogTypeInRealTime(Long liveId, Long userId, Long watchDuration) {
+        try {
+            // 获取当前直播/回放状态
+            Map<String, Integer> flagMap = liveWatchUserService.getLiveFlagWithCache(liveId);
+            Integer currentLiveFlag = flagMap.get("liveFlag");
+            
+            // 只在直播状态(liveFlag = 1)时更新
+            if (currentLiveFlag == null || currentLiveFlag != 1) {
+                return;
+            }
+            
+            // 获取用户的 companyId 和 companyUserId(使用带缓存的查询方法)
+            LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserIdWithCache(liveId, userId);
+            if (liveUserFirstEntry == null) {
+                return;
+            }
+            
+            Long companyId = liveUserFirstEntry.getCompanyId();
+            Long companyUserId = liveUserFirstEntry.getCompanyUserId();
+            
+            // 如果 companyId 和 companyUserId 有效,则更新看课状态
+            if (companyId != null && companyId > 0 && companyUserId != null && companyUserId > 0) {
+                // 检查是否达到关键观看时长节点,在这些节点实时更新
+                // 关键节点:3分钟(180秒)、20分钟(1200秒)、30分钟(1800秒)
+                boolean isKeyDuration = (watchDuration == 180 || watchDuration == 1200 || watchDuration == 1800) ||
+                                       (watchDuration > 180 && watchDuration % 60 == 0); // 每分钟更新一次
+                
+                // 使用 Redis 缓存控制更新频率,避免频繁更新数据库
+                // 策略:在关键节点立即更新,其他时候每60秒更新一次
+                String updateLockKey = "live:watch:log:update:lock:" + liveId + ":" + userId;
+                String lastUpdateKey = "live:watch:log:last:duration:" + liveId + ":" + userId;
+                
+                // 获取上次更新的时长
+                Long lastUpdateDuration = redisCache.getCacheObject(lastUpdateKey);
+                
+                // 如果达到关键节点,或者距离上次更新已超过60秒,则更新
+                boolean shouldUpdate = false;
+                if (isKeyDuration) {
+                    // 关键节点立即更新
+                    shouldUpdate = true;
+                } else if (lastUpdateDuration == null || (watchDuration - lastUpdateDuration) >= 60) {
+                    // 每60秒更新一次
+                    shouldUpdate = true;
+                }
+                
+                if (shouldUpdate) {
+                    // 使用分布式锁,避免并发更新(锁超时时间10秒)
+                    Boolean canUpdate = redisCache.setIfAbsent(updateLockKey, "1", 10, TimeUnit.SECONDS);
+                    
+                    if (Boolean.TRUE.equals(canUpdate)) {
+                        // 异步更新,避免阻塞心跳处理
+                        CompletableFuture.runAsync(() -> {
+                            try {
+                                updateLiveWatchLogTypeByDuration(liveId, userId, companyId, companyUserId, watchDuration);
+                                // 更新上次更新的时长
+                                redisCache.setCacheObject(lastUpdateKey, watchDuration, 2, TimeUnit.HOURS);
+                            } catch (Exception e) {
+                                log.error("实时更新看课状态异常:liveId={}, userId={}, error={}", 
+                                        liveId, userId, e.getMessage(), e);
+                            } finally {
+                                // 释放锁
+                                redisCache.deleteObject(updateLockKey);
+                            }
+                        });
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("实时更新看课状态异常:liveId={}, userId={}, error={}", 
+                    liveId, userId, e.getMessage(), e);
+        }
+    }
+    
     /**
      * 根据在线时长更新 LiveWatchLog 的 logType
      * @param liveId 直播间ID
@@ -1165,8 +1250,8 @@ public class WebSocketServer {
     private void updateLiveWatchLogTypeByDuration(Long liveId, Long userId, Long companyId, 
                                                    Long companyUserId, Long onlineSeconds) {
         try {
-            // 获取直播视频总时长(videoType = 1 的视频)
-            List<LiveVideo> videos = liveVideoService.listByLiveId(liveId, 1);
+            // 获取直播视频总时长(videoType = 1 的视频,使用带缓存的查询方法
+            List<LiveVideo> videos = liveVideoService.listByLiveIdWithCache(liveId, 1);
             long totalVideoDuration = 0L;
             if (videos != null && !videos.isEmpty()) {
                 totalVideoDuration = videos.stream()
@@ -1186,7 +1271,7 @@ public class WebSocketServer {
             if (logs == null || logs.isEmpty()) {
                 return;
             }
-            
+            Date now = DateUtil.getDate();
             for (LiveWatchLog log : logs) {
                 boolean needUpdate = false;
                 Integer newLogType = log.getLogType();
@@ -1199,11 +1284,13 @@ public class WebSocketServer {
                 // ③ 如果直播视频 >= 40分钟,在线时长 >= 30分钟,logType 设置为 2(完课)
                 else if (totalVideoDuration >= 2400 && onlineSeconds >= 1800) { // 40分钟 = 2400秒,30分钟 = 1800秒
                     newLogType = 2;
+                    log.setFinishTime(now);
                     needUpdate = true;
                 }
                 // 如果直播视频 >= 20分钟且 < 40分钟,在线时长 >= 20分钟,logType 设置为 2(完课)
                 else if (totalVideoDuration >= 1200 && totalVideoDuration < 2400 && onlineSeconds >= 1200) { // 20分钟 = 1200秒
                     newLogType = 2;
+                    log.setFinishTime(now);
                     needUpdate = true;
                 }
                 

+ 21 - 6
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -676,6 +676,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             content.setVideoId(e.getVideoId());
             content.setCourseType(e.getCourseType());
             content.setAiTouch(e.getAiTouch());
+            content.setIsAtAll(e.getIsAtAll());
             return content;
         }).sorted(Comparator.comparing(e -> LocalTime.parse(e.getTime() + ":00"))).peek(e -> e.setIndex(i.getAndIncrement())).collect(Collectors.toList());
     }
@@ -700,7 +701,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if(content.getSetting() == null){
             return;
         }
-        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType())).collect(Collectors.toList());
+        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType()) || "16".equals(e.getContentType())).collect(Collectors.toList());
         if (!setting.isEmpty()) {
             List<String> valuesList = PubFun.listToNewList(setting, QwSopTempSetting.Content.Setting::getValue);
             if (valuesList != null && !valuesList.isEmpty()) {
@@ -708,13 +709,22 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     List<QwSopTempVoice> voiceList = qwSopTempVoiceService.getVoiceByText(Long.parseLong(companyUserId), valuesList);
                     if (voiceList != null && !voiceList.isEmpty()) {
                         Map<String, QwSopTempVoice> collect = voiceList.stream().collect(Collectors.toMap(QwSopTempVoice::getVoiceTxt, e -> e));
-                        setting.parallelStream().filter(e -> "7".equals(e.getContentType())).forEach(st -> {
+
+                        setting.parallelStream().forEach(st -> {
                             QwSopTempVoice voice = collect.get(st.getValue());
-                            if (voice.getVoiceUrl() == null) {
+                            if (voice == null || voice.getVoiceUrl() == null) {
                                 return;
                             }
-                            st.setVoiceUrl(voice.getVoiceUrl());
-                            st.setVoiceDuration(voice.getDuration() + "");
+                            // 企微语音
+                            if ("7".equals(st.getContentType())) {
+                                st.setVoiceUrl(voice.getVoiceUrl());
+                                st.setVoiceDuration(voice.getDuration() + "");
+                            }
+                            // app语音
+                            else if ("16".equals(st.getContentType())) {
+                                st.setVoiceUrl(voice.getUserVoiceUrl());
+                                st.setVoiceDuration(voice.getDuration() + "");
+                            }
                         });
                     }
                 } catch (NumberFormatException e) {
@@ -1023,7 +1033,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             log.error("Cloned content settings are empty, skipping.");
             return;
         }
-
+        //如果是@所有人,就添加
+        if (1 == content.getIsAtAll()) {
+            QwSopTempSetting.Content.Setting atMsg = new QwSopTempSetting.Content.Setting();
+            atMsg.setContentType("99");
+            settings.add(atMsg);
+        }
         // 顺序处理每个 Setting,避免过多的并行导致线程开销
         for (QwSopTempSetting.Content.Setting setting : settings) {
             switch (setting.getContentType()) {

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

@@ -73,4 +73,7 @@ public class FsCourseRedPacketLog extends BaseEntity
      */
     private BigDecimal accBalanceAfter;
 
+    //商户号
+    private String mchId;
+
 }

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

@@ -75,6 +75,9 @@ public interface FsCourseRedPacketLogMapper
     @Select("select * from fs_course_red_packet_log where out_batch_no = #{outBatchNo}")
     FsCourseRedPacketLog selectFsCourseRedPacketLogByBatchNo(@Param("outBatchNo") String outBatchNo);
 
+    @Select("select * from fs_course_red_packet_log where batch_id = #{batchId}")
+    FsCourseRedPacketLog selectFsCourseRedPacketLogByBatchId(@Param("batchId") String batchId);
+
 
     @Select({"<script> " +
             "select o.*,v.title as video_name,u.phone as user_name  from fs_course_red_packet_log l " +

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

@@ -209,7 +209,7 @@ public interface FsUserCourseVideoMapper extends BaseMapper<FsUserCourseVideo> {
             "        <if test=\"data.keyword != null and data.keyword !='' \">\n" +
             "            AND v.title LIKE concat('%',#{data.keyword},'%')\n" +
             "        </if>" +
-            "order by v.video_id asc " +
+            "order by v.course_sort asc, v.video_id asc " +
             "</script>")
     List<FsCourseVideoListBySidebarVO> getFsCourseVideoListBySidebar(@Param("data") FsCourseListBySidebarParam param);
 

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

@@ -87,4 +87,8 @@ public interface IFsCourseRedPacketLogService
     void sendRedPacketBf();
 
     void queryRedPacketResult(String startTime, String endTime);
+
+    R getBillsByTransferBillNo(String batchId);
+
+    R transferBatchesBatchId(String batchId);
 }

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

@@ -28,6 +28,8 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest;
+import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import com.github.binarywang.wxpay.config.WxPayConfig;
@@ -476,4 +478,58 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
                 company.getCompanyId(), moneyLog.getMoney());
     }
 
+    @Override
+    public R transferBatchesBatchId(String batchId) {
+        FsCourseRedPacketLog log = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByBatchId(batchId);
+        if (log ==null){
+            return R.error("未查询到红包记录!");
+        }
+        String json = configService.selectConfigByKey("redPacket.config");
+        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+        //创建微信订单
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config,payConfig);
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService=wxPayService.getTransferService();
+
+        QueryTransferBatchesRequest request = new QueryTransferBatchesRequest();
+        request.setBatchId(batchId);
+
+        try {
+            QueryTransferBatchesResult queryTransferBatchesResult = transferService.transferBatchesBatchId(request);
+            logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryTransferBatchesResult);
+            return R.ok(queryTransferBatchesResult.toString());
+        } catch (WxPayException e) {
+            logger.error(e.getMessage());
+            return R.error(e.getMessage());
+        }
+    }
+
+    @Override
+    public R getBillsByTransferBillNo(String batchId) {
+        FsCourseRedPacketLog log = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByBatchId(batchId);
+        if (log ==null){
+            return R.error("未查询到红包记录!");
+        }
+        String json = configService.selectConfigByKey("redPacket.config");
+        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+        //创建微信订单
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config,payConfig);
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService=wxPayService.getTransferService();
+
+        try {
+            TransferBillsGetResult queryRedPacketResult = transferService.getBillsByTransferBillNo(batchId);
+            logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",log.getLogId(),queryRedPacketResult);
+            return R.ok(queryRedPacketResult.toString());
+        } catch (WxPayException e) {
+            logger.error(e.getMessage());
+            return R.error(e.getMessage());
+        }
+
+
+    }
 }

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

@@ -1886,6 +1886,10 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 }else {
                     redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
                 }
+                //存储商户ID
+                if (StringUtils.isNotEmpty(sendRedPacket.get("mchId").toString())){
+                    redPacketLog.setMchId(sendRedPacket.get("mchId").toString());
+                }
                 // 添加红包记录
                 redPacketLog.setCourseId(param.getCourseId());
                 redPacketLog.setCompanyId(param.getCompanyId());

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

@@ -325,11 +325,11 @@ public interface FsUserMapper
 
     List<FsUserSummaryCountTagVO> countTag(@Param("userId") Long userId, @Param("companyId") Long companyId);
 
-    Map<String, Long> countUserCourse(UserStatisticsCommonParam param);
+//    Map<String, Long> countUserCourse(UserStatisticsCommonParam param);
 
     Map<String, Long> countUserAnswer(UserStatisticsCommonParam param);
 
-    Map<String, Long> countCourseDetails(UserStatisticsCommonParam param);
+//    Map<String, Long> countCourseDetails(UserStatisticsCommonParam param);
 
     List<FsUserRankingVO> countUserRankingByComplete(@Param("userId") Long userId, @Param("companyId") Long companyId, @Param("startTime") String startTime, @Param("endTime") String endTime, @Param("periodId")String periodId, @Param("videoId")String videoId, @Param("order")String order);
 

+ 10 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -624,6 +624,10 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                     break;
                 case 2:
                     json = companyConfigService.selectRedPacketConfigByKey(param.getCompanyId());
+                    //如果分公司配置为空就走总后台的配置
+                    if (StringUtils.isEmpty(json)){
+                        json = configService.selectConfigByKey("redPacket.config");
+                    }
                     config = JSONUtil.toBean(json, RedPacketConfig.class);
                     break;
                 default:
@@ -749,6 +753,10 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                     break;
                 case 2:
                     json = companyConfigService.selectRedPacketConfigByKey(param.getCompanyId());
+                    //如果分公司配置为空就走总后台的配置
+                    if (StringUtils.isEmpty(json)){
+                        json = configService.selectConfigByKey("redPacket.config");
+                    }
                     config = JSONUtil.toBean(json, RedPacketConfig.class);
                     break;
                 default:
@@ -859,7 +867,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         try {
             TransferBillsResult transferBillsResult = transferService.transferBills(request);
             logger.info("商家转账支付完成:[msg:{}]", transferBillsResult);
-            return R.ok("发送红包成功").put("data", transferBillsResult);
+            return R.ok("发送红包成功").put("data", transferBillsResult).put("mchId", config.getMchId());
         } catch (Exception e) {
             logger.error("商家转账支付失败:参数: {} :原因: {}",JSON.toJSONString(param), e.getMessage(),e);
             throw new RuntimeException(e);
@@ -910,7 +918,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
 
         try {
             TransferBatchesResult transferBatchesResult = transferService.transferBatches(request);
-            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId());
+            return R.ok("发送红包成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId()).put("mchId", config.getMchId());
         } catch (Exception e) {
             logger.error("商家转账支付失败:参数: {} :原因: {}",JSON.toJSONString(param), e.getMessage(),e);
             throw new RuntimeException(e);

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreAfterSalesScrm.java

@@ -144,4 +144,10 @@ public class FsStoreAfterSalesScrm extends BaseEntity
     @TableField(exist = false)
     private String productName;
 
+    /**
+     * 用于查询汇付订单号
+     */
+    @TableField(exist = false)
+    private String hfOrderCode;
+
 }

+ 4 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -91,7 +91,7 @@ public interface FsStoreAfterSalesScrmMapper
             "cu.phonenumber as company_usere_phonenumber,o.pay_money,o.id as orderId,o.create_time as orderCreateTime,o.user_phone," +
             "o.real_name as userName,o.item_json,o.user_address,o.pay_time as orderPayTime,o.pay_price,o.total_postage," +
             "fsps.bank_serial_no,fsps.bank_transaction_id,o.delivery_id as orderDeliveryId,o.delivery_name as orderDeliveryName,o.delivery_sn as orderDeliverySn," +
-            "o.status as orderStatus " +
+            "o.status as orderStatus,fsps.pay_code as payCode " +
             " from fs_store_after_sales_scrm s " +
             " INNER join fs_store_order_scrm o on o.order_code=s.order_code " +
             " left join fs_user u on s.user_id=u.user_id " +
@@ -99,6 +99,9 @@ public interface FsStoreAfterSalesScrmMapper
             " left join company_user cu on cu.user_id=s.company_user_id " +
             " left join fs_store_payment_scrm fsps on fsps.business_order_id = o.id and fsps.status in (-1,1) " +
             " where 1=1 " +
+            "<if test =\"maps.hfOrderCode != null and  maps.hfOrderCode!='' \"> " +
+              "and fsps.pay_code = #{maps.hfOrderCode} " +
+            "</if>" +
             "<if test = 'maps.status != null    '> " +
             "and s.status = #{maps.status} " +
             "</if>" +

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

@@ -4227,7 +4227,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 this.updateFsStoreOrder(order);
             }
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             if((order.getPayType().equals("1")||order.getPayType().equals("2")||order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0))>0){
                 String json = configService.selectConfigByKey(STORE_PAY_CONF);
                 FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
@@ -4366,7 +4366,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if(!order.getIsPayRemain().equals(0)){
                 return R.error("此订单已支付");
             }
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();
@@ -4485,7 +4485,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             //易宝支付
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();
             storePayment.setCompanyId(order.getCompanyId());
@@ -4584,7 +4584,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         FsUserScrm user=userService.selectFsUserById(order.getUserId());
         if(user!=null){
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java

@@ -143,4 +143,6 @@ public class FsStoreAfterSalesVO implements Serializable
     private String orderDeliverySn;
     private String orderDeliveryName;
     private String orderDeliveryId;
+
+    private String payCode;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java

@@ -23,6 +23,9 @@ public class FsStoreOrderItemExportRefundZMVO implements Serializable  {
     @Excel(name = "订单号",sort = 1)
     private String orderCode;
 
+    @Excel(name = "支付单号",sort = 2)
+    private String payCode;
+
     @Excel(name = "订单状态", dictType = "store_order_status",sort = 10)
     private String status;
 

+ 19 - 2
fs-service/src/main/java/com/fs/ipad/IpadSendUtils.java

@@ -221,7 +221,7 @@ public class IpadSendUtils {
      * @param vo 调用接口参数
      * @return 返回的userid
      */
-    private Long userIds(BaseVo vo){
+    public Long userIds(BaseVo vo){
         if(vo.isRoom()){
             return chatIds(vo);
         }
@@ -245,6 +245,14 @@ public class IpadSendUtils {
      * @return 返回的userid
      */
     private Long chatIds(BaseVo vo){
+        try {
+            String idStr = redisCache.getCacheObject(USER_KEY + vo.getExId());
+            if(StringUtils.isNotEmpty(idStr)){
+                return Long.parseLong(idStr);
+            }
+        }catch (Exception e){
+            log.error("获取群ID失败", e);
+        }
         // 实时查询群聊信息
         QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(vo.getExId());
         if(qwGroupChat == null){
@@ -254,7 +262,10 @@ public class IpadSendUtils {
             return qwGroupChat.getRoomid();
         }
         // 找到对应的企业微信
-        QwUser qwUser = qwUserMapper.selectOne(new QueryWrapper<QwUser>().eq("corp_id", qwGroupChat.getCorpId()).eq("ipad_status", 1).last(" limit 1"));
+        QwUser qwUser = qwUserMapper.selectQwUserAppKeyAndIdByCorpIdAndUserIdAndIpad(qwGroupChat.getCorpId(), qwGroupChat.getOwner());
+        if(qwUser == null){
+            throw new BaseException("群主离线无法转换");
+        }
         WxWorkChatIdToRoomIdDTO tdo = new WxWorkChatIdToRoomIdDTO();
         // 重新组装数据
         tdo.setChatid(qwGroupChat.getChatId());
@@ -562,4 +573,10 @@ public class IpadSendUtils {
         }
         return data.stream().map(WxWorkVid2UserIdRespDTO::getUser_id).collect(Collectors.toList());
     }
+
+    public void sendTxtAtMsgVo(WxSendTextAtMsgTwoDTO dto, Long serverId){
+        WxWorkResponseDTO<WxSendTextAtMsgVo> result = wxWorkService.sendTextAtMsgTwo(dto, serverId);
+        log.info("发送@所有人返回数据:{}", result);
+        if(result.getErrcode() != 0) throw new BaseException("发送@所有人消息错误:" + result.getErrmsg());
+    }
 }

+ 31 - 0
fs-service/src/main/java/com/fs/ipad/vo/WxSendTextAtMsgVo.java

@@ -0,0 +1,31 @@
+package com.fs.ipad.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+
+@Data
+public class WxSendTextAtMsgVo {
+
+    private Long receiver;
+    private Long sender;
+    private List<atList> at_list;
+    private String sender_name;
+    private Long room_conversation_id;
+    private Integer is_room;
+    private Integer sendtime;
+    private Long msg_id;
+    private Long server_id;
+    private Long msgtype;
+    private String content;
+
+    @Data
+    public class atList {
+        private Long user_id;
+        private String nikeName;
+
+    }
+}
+
+

+ 3 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveWatchLogMapper.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.vo.LiveWatchLogListVO;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -71,4 +72,6 @@ public interface LiveWatchLogMapper extends BaseMapper<LiveWatchLog> {
 
     List<LiveWatchLog> selectLiveWatchLogByLiveId(@Param("liveId")Long liveId);
 
+    List<LiveWatchLogListVO> selectLiveWatchLogListInfo(LiveWatchLog liveWatchLog);
+
 }

+ 34 - 0
fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java

@@ -4,6 +4,7 @@ import com.fs.common.param.BaseQueryParam;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 /**
  * 合并订单查询参数
@@ -81,5 +82,38 @@ public class MergedOrderQueryParam extends BaseQueryParam implements Serializabl
 
     /** 员工姓名(company_user表的nick_name) */
     private String companyUserNickName;
+
+    /** 商品规格 */
+    private String productSpec;
+
+    /** 商品数量 */
+    private Integer totalNum;
+
+    /** 销售价格 */
+    private BigDecimal price;
+
+    /** 收货地址 */
+    private String userAddress;
+
+    /** 成本价格 */
+    private BigDecimal cost;
+
+    /** 供应商名称 */
+    private String supplierName;
+
+    /** 订单类型 */
+    private String orderType;
+
+    /** 上传凭证:0-未上传,1-已上传 */
+    private String isUpload;
+
+    /** 档期归属ID */
+    private Long scheduleId;
+
+    /** ERP账户 */
+    private String erpAccount;
+
+    /** ERP电话 */
+    private String erpPhoneNumber;
 }
 

+ 8 - 0
fs-service/src/main/java/com/fs/live/service/ILiveUserFirstEntryService.java

@@ -66,4 +66,12 @@ public interface ILiveUserFirstEntryService {
     List<LiveUserFirstProfit> selectLiveProfitList();
 
     LiveUserFirstEntry selectEntityByLiveIdUserId(long liveId, long userId);
+
+    /**
+     * 查询用户首次进入直播间记录(带Redis缓存)
+     * @param liveId 直播间ID
+     * @param userId 用户ID
+     * @return 用户首次进入直播间记录
+     */
+    LiveUserFirstEntry selectEntityByLiveIdUserIdWithCache(long liveId, long userId);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/live/service/ILiveVideoService.java

@@ -86,4 +86,12 @@ public interface ILiveVideoService
     LiveVideo selectLiveVideoByLiveIdAndType(Long id, int i);
 
     void updateFinishStatus(String string);
+
+    /**
+     * 根据直播间ID和类型查询视频列表(带Redis缓存)
+     * @param liveId 直播间ID
+     * @param type 视频类型
+     * @return 视频列表
+     */
+    List<LiveVideo> listByLiveIdWithCache(Long liveId, Integer type);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/live/service/ILiveWatchLogService.java

@@ -3,6 +3,7 @@ package com.fs.live.service;
 import java.util.List;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.live.domain.LiveWatchLog;
+import com.fs.live.vo.LiveWatchLogListVO;
 
 /**
  * 直播看课记录Service接口
@@ -58,4 +59,11 @@ public interface ILiveWatchLogService extends IService<LiveWatchLog>{
      * @return 结果
      */
     int deleteLiveWatchLogByLogId(Long logId);
+
+    /**
+     * 查询列表信息
+     * @param liveWatchLog
+     * @return
+     */
+    List<LiveWatchLogListVO> selectLiveWatchLogListInfo(LiveWatchLog liveWatchLog);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java

@@ -110,6 +110,11 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             BigDecimal watchRate = BigDecimal.valueOf(actualWatchDuration)
                     .multiply(BigDecimal.valueOf(100))
                     .divide(BigDecimal.valueOf(videoDuration), 2, RoundingMode.HALF_UP);
+            
+            // 限制完课比例最大值为100.00%(防止数据库字段溢出)
+            if (watchRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                watchRate = BigDecimal.valueOf(100);
+            }
 
             // 6. 判断是否达到完课标准
             if (watchRate.compareTo(BigDecimal.valueOf(completionRate)) < 0) {

+ 6 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -2012,6 +2012,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCreateTime(new Date());
         liveOrder.setUpdateTime(new Date());
         liveOrder.setPayDelivery(deliveryMoney);
+        liveOrder.setPayPostage(deliveryMoney);
         liveOrder.setProductId(fsStoreProduct.getProductId());
         liveOrder.setStatus(OrderInfoEnum.STATUS_0.getValue());
         liveOrder.setPayType("1");
@@ -2971,6 +2972,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 return R.error("订单生成失败,请重试");
             }
             LiveOrderPayment storePayment = liveOrderPaymentMapper.selectByBuissnessId(liveOrder.getOrderId());
+            storePayment.setAppId(liveOrder.getAppId());
             if (storePayment != null) {
                 storePayment.setStatus(1);
                 liveOrderPaymentMapper.updateLiveOrderPayment(storePayment);
@@ -3136,7 +3138,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 storePayment.setOpenId(openId);
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessId(String.valueOf(order.getOrderId()));
-                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
+                storePayment.setAppId(param.getAppId());
                 liveOrderPaymentMapper.insertLiveOrderPayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){
@@ -3645,6 +3647,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCreateTime(new Date());
         liveOrder.setUpdateTime(new Date());
         liveOrder.setPayDelivery(deliveryMoney);
+        // todo 过两个月,把这个字段切过来 2025 1216
+        liveOrder.setPayPostage(deliveryMoney);
         liveOrder.setProductId(fsStoreProduct.getProductId());
         liveOrder.setStatus(OrderInfoEnum.STATUS_0.getValue());
         liveOrder.setPayType("1");
@@ -3793,6 +3797,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
 
         liveOrder.setPayDelivery(storePostage);
+        liveOrder.setPayPostage(storePostage);
 
         return storePostage;
     }

+ 28 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveUserFirstEntryServiceImpl.java

@@ -3,6 +3,9 @@ package com.fs.live.service.impl;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 
 import com.fs.live.vo.LiveUserFirstProfit;
@@ -22,6 +25,9 @@ import com.fs.live.service.ILiveUserFirstEntryService;
 public class LiveUserFirstEntryServiceImpl implements ILiveUserFirstEntryService {
     @Autowired
     private LiveUserFirstEntryMapper baseMapper;
+    
+    @Autowired
+    private RedisCache redisCache;
 
     /**
      * 查询用户每日首次进入直播间记录
@@ -112,4 +118,26 @@ public class LiveUserFirstEntryServiceImpl implements ILiveUserFirstEntryService
     public LiveUserFirstEntry selectEntityByLiveIdUserId(long liveId,long userId) {
         return baseMapper.selectEntityByLiveIdUserId(liveId, userId);
     }
+
+    @Override
+    public LiveUserFirstEntry selectEntityByLiveIdUserIdWithCache(long liveId, long userId) {
+        // Redis缓存键
+        String cacheKey = "live:user:first:entry:" + liveId + ":" + userId;
+        
+        // 先从缓存中获取
+        LiveUserFirstEntry cachedEntry = redisCache.getCacheObject(cacheKey);
+        if (cachedEntry != null) {
+            return cachedEntry;
+        }
+        
+        // 缓存未命中,从数据库查询
+        LiveUserFirstEntry entry = baseMapper.selectEntityByLiveIdUserId(liveId, userId);
+        
+        // 将查询结果存入缓存,缓存时间1小时
+        if (entry != null) {
+            redisCache.setCacheObject(cacheKey, entry, 1, TimeUnit.HOURS);
+        }
+        
+        return entry;
+    }
 }

+ 27 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveVideoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.live.service.impl;
 
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.live.domain.LiveVideo;
@@ -17,6 +18,7 @@ import java.net.URI;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播视频Service业务层处理
@@ -29,6 +31,9 @@ public class LiveVideoServiceImpl implements ILiveVideoService
 {
     @Autowired
     private LiveVideoMapper liveVideoMapper;
+    
+    @Autowired
+    private RedisCache redisCache;
 
     /**
      * 查询直播视频
@@ -180,4 +185,26 @@ public class LiveVideoServiceImpl implements ILiveVideoService
         liveVideoMapper.updateFinishStatus(string);
     }
 
+    @Override
+    public List<LiveVideo> listByLiveIdWithCache(Long liveId, Integer type) {
+        // Redis缓存键
+        String cacheKey = "live:video:list:" + liveId + ":" + type;
+        
+        // 先从缓存中获取
+        List<LiveVideo> cachedVideos = redisCache.getCacheObject(cacheKey);
+        if (cachedVideos != null && !cachedVideos.isEmpty()) {
+            return cachedVideos;
+        }
+        
+        // 缓存未命中,从数据库查询
+        List<LiveVideo> videos = liveVideoMapper.selectByIdAndType(liveId, type);
+        
+        // 将查询结果存入缓存,缓存时间1小时
+        if (videos != null) {
+            redisCache.setCacheObject(cacheKey, videos, 1, TimeUnit.HOURS);
+        }
+        
+        return videos;
+    }
+
 }

+ 10 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchLogServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.live.service.impl;
 import java.util.List;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.live.vo.LiveWatchLogListVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.live.mapper.LiveWatchLogMapper;
@@ -18,6 +19,9 @@ import com.fs.live.service.ILiveWatchLogService;
 @Service
 public class LiveWatchLogServiceImpl extends ServiceImpl<LiveWatchLogMapper, LiveWatchLog> implements ILiveWatchLogService {
 
+    @Autowired
+    private LiveWatchLogMapper liveWatchLogMapper;
+
     /**
      * 查询直播看课记录
      * 
@@ -42,6 +46,12 @@ public class LiveWatchLogServiceImpl extends ServiceImpl<LiveWatchLogMapper, Liv
         return baseMapper.selectLiveWatchLogList(liveWatchLog);
     }
 
+    @Override
+    public List<LiveWatchLogListVO> selectLiveWatchLogListInfo(LiveWatchLog liveWatchLog){
+
+        return liveWatchLogMapper.selectLiveWatchLogListInfo(liveWatchLog);
+    }
+
     /**
      * 新增直播看课记录
      * 

+ 6 - 0
fs-service/src/main/java/com/fs/live/vo/LiveAfterSalesVo.java

@@ -1,5 +1,6 @@
 package com.fs.live.vo;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
@@ -189,6 +190,11 @@ public class LiveAfterSalesVo {
     private String orderDeliverySn;
     private String orderDeliveryName;
     private String orderDeliveryId;
+    /**
+     * 用于查询汇付订单号
+     */
+    private String hfOrderCode;
 
+    private String payCode;
 
 }

+ 118 - 0
fs-service/src/main/java/com/fs/live/vo/LiveWatchLogListVO.java

@@ -0,0 +1,118 @@
+package com.fs.live.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author MixLiu
+ * @date 2025/12/15 下午3:40)
+ */
+
+@Data
+public class LiveWatchLogListVO {
+
+    /** 日志id */
+    private Long logId;
+
+    /** 用户userId */
+    @Excel(name = "用户userId")
+    private Long userId;
+
+    /** 直播间id */
+    @Excel(name = "直播间id")
+    private Long liveId;
+
+    /** 记录类型 1看课中 2完课 3待看课 4看课中断 */
+    @Excel(name = "记录类型 1看课中 2完课 3待看课 4看课中断")
+    private Integer logType;
+
+    /** 外部联系人id */
+    @Excel(name = "外部联系人id")
+    private Long externalContactId;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 完课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "完课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date finishTime;
+
+    /** sop最后创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "sop最后创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date sopCreateTime;
+
+    /** 发送小程序appid */
+    @Excel(name = "发送小程序appid")
+    private String sendAppId;
+
+    /** 日志创建来源:1、个人sop,2、群聊sop,3、一键群发 */
+    @Excel(name = "日志创建来源:1、个人sop,2、群聊sop,3、一键群发")
+    private Integer logSource;
+
+    /** 分享人企微id */
+    @Excel(name = "分享人企微id")
+    private String qwUserId;
+    /**
+     * 查看直播类型:1、直播,2、回放
+     */
+    private Integer watchType;
+
+    /**
+     * 企微主体id
+     */
+    private String corpId;
+
+    /**
+     * 直播购买
+     */
+    private Integer liveBuy;
+
+    /**
+     * 回放购买
+     */
+    private Integer replayBuy;
+
+    /**
+     * 会员昵称
+     */
+    private String userName;
+    /**
+     * 会员头像
+     */
+    private String userAvatar;
+
+    /**
+     * 外部联系人名称
+     */
+    private String qwExternalName;
+
+    /**
+     * 外部联系人头像
+     */
+    private String qwExternalAvatar;
+
+    /**
+     * 直播间名称
+     */
+    private String liveName;
+
+    /**
+     * 所属销售
+     */
+    private String companyUserName;
+
+    /**
+     * 企微用户
+     */
+    private String qwUserName;
+}

+ 153 - 0
fs-service/src/main/java/com/fs/live/vo/MergedOrderExportVO.java

@@ -0,0 +1,153 @@
+package com.fs.live.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 合并订单导出VO(参考 FsStoreOrderItemExportVO 的格式)
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Data
+public class MergedOrderExportVO implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    /** 订单状态 */
+    @Excel(name = "订单状态", dictType = "store_order_status")
+    private String status;
+
+    /** 会员ID */
+    @Excel(name = "会员ID")
+    private Long userId;
+
+    /** 产品名称 */
+    @Excel(name = "产品名称")
+    private String productName;
+
+    /** 产品编码 */
+    @Excel(name = "产品编码")
+    private String barCode;
+
+    /** 规格 */
+    @Excel(name = "规格")
+    private String productSpec;
+
+    /** 产品数量 */
+    @Excel(name = "产品数量")
+    private Integer totalNum;
+
+    /** 产品价格 */
+    @Excel(name = "产品价格")
+    private BigDecimal price;
+
+    /** 成本价 */
+    @Excel(name = "成本价")
+    private BigDecimal cost;
+
+    /** 结算价 */
+    @Excel(name = "结算价")
+    private BigDecimal FPrice;
+
+    /** 商品分类 */
+    @Excel(name = "商品分类")
+    private String cateName;
+
+    /** 用户姓名 */
+    @Excel(name = "收货人姓名")
+    private String realName;
+
+    /** 用户电话 */
+    @Excel(name = "收货人电话")
+    private String userPhone;
+
+    /** 详细地址 */
+    @Excel(name = "详细地址")
+    private String userAddress;
+
+    /** 下单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "下单时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 支付时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date payTime;
+
+    /** 快递公司编号 */
+    @Excel(name = "快递公司编号")
+    private String deliverySn;
+
+    /** 快递名称/送货人姓名 */
+    @Excel(name = "快递公司")
+    private String deliveryName;
+
+    /** 快递单号/手机号 */
+    @Excel(name = "快递单号")
+    private String deliveryId;
+
+    /** 所属公司 */
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    /** 所属销售 */
+    @Excel(name = "所属销售")
+    private String companyUserNickName;
+
+    /** 套餐名称 */
+//    @Excel(name = "套餐名称")
+    private String packageName;
+
+    /** 组合码 */
+//    @Excel(name = "组合码")
+    private String groupBarCode;
+
+    /** 是否上传凭证 0:未上传 1:已上传 */
+//    @Excel(name = "是否上传凭证 0:未上传 1:已上传")
+    private Integer isUpload;
+
+    /** 上传时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @Excel(name = "上传时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date uploadTime;
+
+    /** 归属档期 */
+    @Excel(name = "归属档期")
+    private String scheduleName;
+
+    /** 银行交易流水号 */
+    @Excel(name = "银行交易流水号")
+    private String bankTransactionId;
+
+    /** 商品金额 */
+    @Excel(name = "商品金额")
+    private BigDecimal totalPrice;
+
+    /** 应付金额 */
+    @Excel(name = "应付金额")
+    private BigDecimal payPrice;
+
+    /** 实付金额 */
+    @Excel(name = "实付金额")
+    private BigDecimal payMoney;
+
+    /** 额外运费 */
+    @Excel(name = "额外运费")
+    private BigDecimal payPostage;
+
+    /** 物流代收金额 */
+    @Excel(name = "物流代收金额")
+    private BigDecimal payDelivery;
+}
+

+ 6 - 0
fs-service/src/main/java/com/fs/live/vo/MergedOrderVO.java

@@ -72,6 +72,12 @@ public class MergedOrderVO implements Serializable
     /** 物流单号 */
     private String deliveryId;
 
+    /** 物流公司编码 */
+    private String deliveryCode;
+
+    /** 物流公司名称 */
+    private String deliveryName;
+
     /** 是否可以申请售后 */
     private Integer isAfterSales;
 

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

@@ -161,5 +161,5 @@ public interface QwFriendWelcomeMapper
      * @param qwFriendWelcomeParam 参数
      * @return  list
      */
-    List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
+    List<QwFriendWelcomeVO> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
 }

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

@@ -506,4 +506,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     @Select("select * from qw_user where qw_user_id=#{qwUserId} and corp_id =#{corpId} limit 1")
     QwUser selectQwUserEntityByQwUserIdAndCorId(@Param("qwUserId")String qwUserId,@Param("corpId") String corpId);
+
+    @Select("select * from qw_user where ipad_status = 1 and corp_id=#{corpId} and qw_user_id=#{qwUserId} limit 1 ")
+    QwUser selectQwUserAppKeyAndIdByCorpIdAndUserIdAndIpad(@Param("corpId")String corpId,@Param("qwUserId") String qwUserId);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/IQwFriendWelcomeService.java

@@ -70,5 +70,5 @@ public interface IQwFriendWelcomeService
      * @param qwFriendWelcomeParam 参数
      * @return list
      */
-    List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
+    List<QwFriendWelcomeVO> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
 }

+ 34 - 5
fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java

@@ -5,7 +5,12 @@ import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCacheT;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwFriendWelcome;
+import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwFriendWelcomeItemMapper;
 import com.fs.qw.mapper.QwFriendWelcomeMapper;
 import com.fs.qw.mapper.QwUserMapper;
@@ -28,10 +33,10 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.net.URL;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 好友欢迎语Service业务层处理
@@ -55,6 +60,10 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
 
     @Autowired
     private RedisCacheT<Long> redisCache;
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
 
     /**
      * 查询好友欢迎语
@@ -389,8 +398,28 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
      * @return list
      */
     @Override
-    public List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam) {
-        return qwFriendWelcomeMapper.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+    public List<QwFriendWelcomeVO> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam) {
+        List<QwFriendWelcomeVO> qwFriendWelcomes = qwFriendWelcomeMapper.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+
+        if (qwFriendWelcomes.isEmpty()) {
+            return qwFriendWelcomes;
+        }
+
+        List<Long> companyIds = qwFriendWelcomes.stream().map(QwFriendWelcomeVO::getCompanyId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        List<String> corpIds = qwFriendWelcomes.stream().map(QwFriendWelcomeVO::getCorpId).filter(StringUtils::isNotBlank).distinct().collect(Collectors.toList());
+
+        Map<Long, String> companyMap = companyIds.isEmpty()
+                ? Collections.emptyMap()
+                : companyMapper.selectCompanyByIds(companyIds).stream().collect(Collectors.toMap(Company::getCompanyId, Company::getCompanyName, (a, b) -> a));
+        Map<String, String> corpMap = corpIds.isEmpty()
+                ? Collections.emptyMap()
+                : qwCompanyMapper.selectByCorpIds(corpIds).stream().collect(Collectors.toMap(QwCompany::getCorpId, QwCompany::getCorpName, (a, b) -> a
+        ));
+        qwFriendWelcomes.forEach(qwFriendWelcome -> {
+            qwFriendWelcome.setCompanyName(companyMap.getOrDefault(qwFriendWelcome.getCompanyId(), ""));
+            qwFriendWelcome.setCorpName(corpMap.getOrDefault(qwFriendWelcome.getCorpId(), ""));
+        });
+        return qwFriendWelcomes;
     }
 
     /**

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

@@ -67,4 +67,7 @@ public class QwFriendWelcomeVO extends BaseEntity
 
     public List<QwUserVO> userSelectList;
 
+    private String companyName;
+    private String corpName;
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -44,6 +44,8 @@ public class QwSopTempSetting implements Serializable{
         private String addTag;
 
         private String delTag;
+        
+        private Integer isAtAll;
 
         @Override
         public Content clone() {
@@ -151,6 +153,13 @@ public class QwSopTempSetting implements Serializable{
             //app显示标题 app用的参数
             private String title;
 
+            //福袋id
+            private Long luckyBagId;
+            /**
+             * 业务id
+             */
+            private String businessId;
+
 
             @Override
             public Setting clone() {

+ 3 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTemp.java

@@ -150,4 +150,7 @@ public class QwSopTemp implements Serializable
 
     @TableField(exist = false)
     private String createByDeptName;
+    
+    @TableField(exist = false)
+    private String openIsAtAll;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTempRules.java

@@ -83,4 +83,7 @@ public class QwSopTempRules{
     private Integer dayNum;
     @TableField(exist = false)
     private FsUserCourseVideoRedPackage red;
+    
+     /**是否@所有人  1是0否**/
+    private Integer isAtAll;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java

@@ -467,6 +467,12 @@ public class QwSopTempServiceImpl implements IQwSopTempService {
                     rules.setTime(time);
                 }
 
+                if (temp.getOpenIsAtAll() != null && temp.getOpenIsAtAll().equals("1")){
+                    rules.setIsAtAll(1);
+                }else {
+                    rules.setIsAtAll(0);
+                }
+
                 rules.setContentType(2);
                 rules.setType(2);
                 rules.setCourseType(0);

+ 207 - 0
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -585,6 +585,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     updateQwUserKey(sopLogs,qwUser.getQwUserId(),param.getSopId(),groupUser.getChatId());
                     //域名
                     String companyUserId = qwUser.getCompanyUserId().toString();
+                    String companyId = String.valueOf(qwUser.getCompanyId()).trim();
                     String domainName = companyUserMapper.selectDomainByUserId(Long.parseLong(companyUserId));
                     if (StringUtils.isEmpty(domainName)) {
                         domainName = config.getRealLinkDomainName();
@@ -705,6 +706,45 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 st.setMiniprogramAppid(sysConfig.getAppId());
                                 st.setMiniprogramPage(sortLiveLink);
                                 break;
+                            case "14":
+
+                                linkByMiniApp = createActivityLinkByMiniApp(st,sopLogs, qwGroupChat.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
+                                        String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),null ,config, groupUser.getChatId());
+
+                                miniAppId = null;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                    if (integerListMap != null) {
+                                        int listIndexTemp = 1;
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
+                                } else {
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                                }
+
+                                st.setMiniprogramTitle("福袋发放");
+
+                                st.setMiniprogramPage(linkByMiniApp);
+                                break;
+                            case "16":
+                                createVoiceUrl(st, companyUserId, qwSop);
+                                break;
+                            default:
+                                break;
                         }
                     }
                     setting.setSetting(list);
@@ -863,6 +903,60 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 st.setMiniprogramAppid(sysConfig.getAppId());
                                 st.setMiniprogramPage(sortLiveLink);
                                 break;
+                            case "14":
+                                String companyId = String.valueOf(qwUser.getCompanyId()).trim();
+                                linkByMiniApp = createActivityLinkByMiniApp(st,sopLogs, qwUser.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
+                                        String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),null, config, groupChat.getChatId());
+                                miniAppId = null;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                    if (integerListMap != null) {
+                                        int listIndexTemp = 1;
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
+                                } else {
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                                }
+
+                                st.setMiniprogramTitle("福袋发放");
+
+                                st.setMiniprogramPage(linkByMiniApp);
+                                break;
+                            case "15":
+                                String txt2 = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
+                                st.setValue(st.getValue().replaceAll("#客服称呼#", txt2).replaceAll("#销售称呼#", txt2));
+                                try {
+                                    replaceContent(st.getContentType(), st.getValue(), st::setValue, words); // 替换 value
+                                } catch (Exception e) {
+                                    throw new RuntimeException(e);
+                                }
+                                break;
+                            case "16":
+                                if (qwUser.getCompanyUserId() != null) {
+                                    createVoiceUrl(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
+                                }
+                                try {
+                                    if (qwUser.getCompanyUserId() != null) {
+                                        createVoiceUrlToIm(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
+                                    }
+                                } catch (Exception e) {
+                                    throw new RuntimeException(e);
+                                }
+                                break;
                         }
                     }
                     setting.setSetting(list);
@@ -1074,6 +1168,119 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             st.setMiniprogramAppid(sysConfig.getAppId());
                             st.setMiniprogramPage(sortLiveLink);
                             break;
+                        case "14":
+                            LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(st.getLuckyBagId());
+                            if(ObjectUtil.isNotEmpty(luckyBag)&&luckyBag.getDataStatus().equals("0")){
+                                sopLogs.setSendStatus(5L);
+                                sopLogs.setReceivingStatus(0L);
+                                sopLogs.setRemark("福袋配置被禁用");
+                            }
+                            else if (ObjectUtil.isNotEmpty(sopLogs.getFsUserId())){
+                                //获取配置并校验
+                                SysConfig luckyBagConfig = configService.selectConfigByConfigKey("luckyBag.config");
+                                if (ObjectUtil.isEmpty(luckyBagConfig)) {
+                                    sopLogs.setSendStatus(5L);
+                                    sopLogs.setReceivingStatus(0L);
+                                    sopLogs.setRemark("福袋配置不存在");
+                                }
+// 2. 解析配置值
+                                JSONObject jsonObject;
+                                try {
+                                    jsonObject = JSON.parseObject(luckyBagConfig.getConfigValue());
+                                    Integer count = jsonObject.getInteger("weekLimit");
+
+                                    // 查询用户记录并校验次数
+                                    LuckyBagCollectRecord queryRecord = new LuckyBagCollectRecord();
+                                    queryRecord.setUserId(sopLogs.getFsUserId());
+                                    queryRecord.setCollectType("1");
+                                    // 动态计算时间范围
+                                    LocalDate endDate = LocalDate.now();
+                                    LocalDate startDate = endDate.minusDays(6); // 包含今天
+
+                                    Map<String, Object> params = new HashMap<>();
+                                    params.put("beginSendTime", startDate.toString());
+                                    params.put("endSendTime", endDate.toString());
+                                    queryRecord.setParams(params);
+                                    List<LuckyBagCollectRecord> luckyBagCollectRecords =
+                                            luckyBagCollectRecordMapper.selectLuckyBagCollectRecordList(queryRecord);
+
+                                    // 判断是否超过限制
+                                    if (luckyBagCollectRecords.size() >= count) {
+                                        sopLogs.setSendStatus(5L);
+                                        sopLogs.setReceivingStatus(0L);
+                                        sopLogs.setRemark("超过福袋发放次数");
+                                    }
+
+                                } catch (Exception e) {
+                                    // 处理配置解析异常
+                                    sopLogs.setSendStatus(5L);
+                                    sopLogs.setReceivingStatus(0L);
+                                    sopLogs.setRemark("福袋配置解析失败");
+                                }
+                            }
+                            linkByMiniApp = createActivityLinkByMiniApp(st,sopLogs, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                    qwUserId, companyUserId, companyId, item.getExternalId(), config,null);
+
+                            miniAppId = null;
+
+                            if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                if (integerListMap != null) {
+                                    int effectiveGradeTemp = (item.getGrade() == null) ? 5 : item.getGrade();
+                                    int listIndexTemp = (effectiveGradeTemp == 1 || effectiveGradeTemp == 2) ? 0 : 1;
+
+                                    //评级是6 S级,则走A类小程序
+                                    if (effectiveGradeTemp==6){
+                                        listIndexTemp=2;
+                                    }
+
+                                    List<CompanyMiniapp> miniapps = integerListMap.get(listIndexTemp);
+
+                                    if (miniapps != null && !miniapps.isEmpty()) {
+                                        CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                        if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                            miniAppId = companyMiniapp.getAppId();
+                                        }
+                                    }
+                                }
+                            }
+
+                            if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                miniAppId = qwCompany.getMiniAppId();
+                            }
+
+                            if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                st.setMiniprogramAppid(miniAppId);
+                            } else {
+                                log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                            }
+
+                            st.setMiniprogramTitle("福袋发放");
+
+                            st.setMiniprogramPage(linkByMiniApp);
+                            break;
+                        case "15":
+                            String txt = StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText();
+                            st.setValue(st.getValue()
+                                    .replaceAll("#客服称呼#", txt)
+                                    .replaceAll("#销售称呼#", txt)
+                                    .replaceAll("#客户称呼#", StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? "同学" : contact.getStageStatus()));
+                            try {
+                                replaceContent(st.getContentType(), st.getValue(), st::setValue, words); // 替换 value
+                            } catch (Exception e) {
+                                throw new RuntimeException(e);
+                            }
+                            break;
+                        case "16":
+                            createVoiceUrl(st, companyUserId, qwSop);
+                            try {
+                                if (qwUser.getCompanyUserId() != null) {
+                                    createVoiceUrlToIm(st, String.valueOf(qwUser.getCompanyUserId()), qwSop);
+                                }
+                            } catch (Exception e) {
+                                throw new RuntimeException(e);
+                            }
+                            break;
                         default:
                             break;
 

+ 16 - 1
fs-service/src/main/java/com/fs/store/mapper/FsUserCourseCountMapper.java

@@ -68,8 +68,17 @@ public interface FsUserCourseCountMapper
     /**
      * 获取看课统计表结果
      * @return list
+     * @param userIds 用户id列表
      */
-    List<FsUserCourseCount> getCountResult();
+    List<FsUserCourseCount> getCountResult(@Param("userIds") List<Long> userIds);
+
+    /**
+     *
+     * @param offset 从第几条开始查询
+     * @param pageSize 每页数量
+     * @return
+     */
+    List<Long> getUsersByPage(@Param("offset") int offset, @Param("pageSize") int pageSize);
 
     /**
      * 获取最近七天每天最大心跳时间的看课记录数据
@@ -82,6 +91,12 @@ public interface FsUserCourseCountMapper
      */
     void insertFsUserCourseCountTask(FsUserCourseCount fsUserCourseCount);
 
+    /**
+     * 插入/更新看课统计表
+     * @param list
+     */
+    void batchInsertOrUpdate(@Param("list") List<FsUserCourseCount> list);
+
     /**
      * 查询会员最新的看课状态和心跳时间
      * @return

+ 103 - 15
fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java

@@ -2,16 +2,21 @@ package com.fs.store.service.impl;
 
 import com.fs.common.utils.DateUtils;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.impl.FsUserServiceImpl;
 import com.fs.store.domain.FsUserCourseCount;
 import com.fs.store.mapper.FsUserCourseCountMapper;
 import com.fs.store.service.IFsUserCourseCountService;
 import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.session.ExecutorType;
 import org.apache.ibatis.session.SqlSession;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -23,6 +28,7 @@ import java.util.stream.Collectors;
  * @date 2025-04-02
  */
 @Service
+@Slf4j
 public class FsUserCourseCountServiceImpl implements IFsUserCourseCountService
 {
     @Autowired
@@ -115,29 +121,111 @@ public class FsUserCourseCountServiceImpl implements IFsUserCourseCountService
 
     @Override
     public void insertFsUserCourseCountTask() {
-        // 1、获取统计结果
-        List<FsUserCourseCount> countResult = fsUserCourseCountMapper.getCountResult();
+        // 总处理量-执行中
+        int totalProcessed = 0;
+        long startTime = System.currentTimeMillis();
+
+        log.info("开始处理~~~~~~~~~~~~~~~~~");
+        // 1、分页分批次查询并处理数据
+        int page = 1;
+        int pageSize = 1000;
+        while (true) {
+            List<Long> userIds = fsUserCourseCountMapper.getUsersByPage((page - 1) * pageSize, pageSize);
+            if (userIds.isEmpty()) {
+                break;
+            }
+            log.info("处理第 {} 页,用户数: {}", page, userIds.size());
+
+            // 2、查询当前页用户的统计结果
+            List<FsUserCourseCount> countResult = Collections.emptyList();
+            if (!userIds.isEmpty()) {
+                countResult = fsUserCourseCountMapper.getCountResult(userIds);
+
+                // 3、分批插入数据
+                this.batchInsertOrUpdateNew(countResult);
+            }
+
+            totalProcessed += countResult.size();
+            // 每处理10页记录一次进度
+            if (page % 10 == 0) {
+                log.info("处理进度: 第{}页,已处理 {} 条记录", page, totalProcessed);
+            }
+            page++;
+        }
+
+        long endTime = System.currentTimeMillis();
+        log.info("处理完成!总共处理 {} 条记录,总耗时: {} 毫秒", totalProcessed, endTime - startTime);
+
+        // 获取统计结果
+//        List<FsUserCourseCount> countResult = fsUserCourseCountMapper.getCountResult();
 
         // 查询用户-每天的最新的看课状态,和最后的心跳时间
-        List<FsUserCourseCount> userStatusAndLastWatchDate = fsUserCourseCountMapper.getUserStatusAndLastWatchDate();
-        Map<String, FsUserCourseCount> map = userStatusAndLastWatchDate.stream()
-                .collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getUserId(),k.getProjectId(), k.getLastDate()), v -> v));
-
-        for (FsUserCourseCount data : countResult) {
-            String key = String.format("%s-%s-%s", data.getUserId(),data.getProjectId(), data.getLastDate());
-            FsUserCourseCount fsUserCourseCount = map.get(key);
-            if(fsUserCourseCount != null){
-                data.setLastWatchDate(fsUserCourseCount.getLastWatchDate());
-                data.setStatus(fsUserCourseCount.getStatus());
+//        List<FsUserCourseCount> userStatusAndLastWatchDate = fsUserCourseCountMapper.getUserStatusAndLastWatchDate();
+//        Map<String, FsUserCourseCount> map = userStatusAndLastWatchDate.stream()
+//                .collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getUserId(),k.getProjectId(), k.getLastDate()), v -> v));
+
+//        for (FsUserCourseCount data : countResult) {
+//            String key = String.format("%s-%s-%s", data.getUserId(),data.getProjectId(), data.getLastDate());
+//            FsUserCourseCount fsUserCourseCount = map.get(key);
+//            if(fsUserCourseCount != null){
+//                data.setLastWatchDate(fsUserCourseCount.getLastWatchDate());
+//                data.setStatus(fsUserCourseCount.getStatus());
 //                data.setStopWatchDays(fsUserCourseCount.getStopWatchDays());
-            }
+//            }
+//        }
+    }
+
+    /**
+     * 分批次插入数据
+     * @author Caolq
+     * @param list 数据列表
+     */
+    private void batchInsertOrUpdateNew(List<FsUserCourseCount> list){
+        if (CollectionUtils.isEmpty(list)) {
+            return;
         }
 
-        // 2、分批插入数据
-        this.batchInsert(countResult);
+        // 分批次处理,一次提交500条
+        int insertBatchSize = 500;
+        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
+            FsUserCourseCountMapper mapper = sqlSession.getMapper(FsUserCourseCountMapper.class);
+
+            long totalStartTime = System.currentTimeMillis();
+            int totalInserted = 0;
+
+            // 将数据分割
+            List<List<FsUserCourseCount>> batches = Lists.partition(list, insertBatchSize);
+
+            for (int i = 0; i < batches.size(); i++) {
+                List<FsUserCourseCount> batch = batches.get(i);
+                try {
+                    // 批量插入
+                    mapper.batchInsertOrUpdate(batch);
+
+                    // 定期提交事务,避免事务过大
+                    if ((i + 1) % 10 == 0) {
+                        sqlSession.commit();
+                        log.debug("已提交第 {} 到 {} 批次", i - 9, i + 1);
+                    }
+                    totalInserted += batch.size();
+                } catch (Exception e) {
+                    log.error("批次 {} 插入/更新失败,大小:{}", i + 1, batch.size(), e);
+                    sqlSession.rollback();
+                }
+            }
+
+            // 提交剩余未提交的数据,避免提交漏
+            sqlSession.commit();
 
+            long totalEndTime = System.currentTimeMillis();
+            log.info("当前页批量插入/更新完成,总记录数:{},总耗时:{} 毫秒", totalInserted, totalEndTime - totalStartTime);
+        } catch (Exception e) {
+            log.error("批量插入/更新过程中发生错误", e);
+            throw new RuntimeException("批量插入/更新失败", e);
+        }
     }
 
+
     private void batchInsert(List<FsUserCourseCount> list) {
         // 分批次处理,一次提交500条
         List<List<FsUserCourseCount>> batches = Lists.partition(list, 500);

+ 46 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxSendTextAtMsgTwoDTO.java

@@ -0,0 +1,46 @@
+package com.fs.wxwork.dto;
+
+import com.fs.ipad.vo.BaseVo;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WxSendTextAtMsgTwoDTO extends BaseVo {
+    /**
+     * 消息的唯一标识符 (UUID)
+     */
+    private String uuid;
+
+    /**
+     * 要发送的人或群id
+     */
+    private Long send_userid;
+
+    /**
+     * 发送者用户 ID
+     */
+    private List<Contentva> contentva;
+    /**
+     * 是否为群组消息
+     * <p>
+     * true: 群组消息; false: 单聊消息
+     * </p>
+     */
+    private Boolean isRoom;
+
+    @Data
+    public static class Contentva {
+        private Integer msgtype;
+        private String content;
+        /**
+         * "msgtype":5,//@类型
+         * "vid":0 //填0就是所有人
+         * <p>
+         * "msgtype":5, //就是@人的类型
+         * "vid":788130xx38 //@人id
+         */
+        private Integer vid;
+    }
+
+}

+ 7 - 0
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceNew.java

@@ -9,6 +9,7 @@ import com.fs.ipad.param.WxSendAtMsgParam;
 import com.fs.ipad.vo.WxGetSessionRoomListVo;
 import com.fs.ipad.vo.WxRoomUserListVo;
 import com.fs.ipad.vo.WxSendAtMsgVo;
+import com.fs.ipad.vo.WxSendTextAtMsgVo;
 import com.fs.qw.domain.QwIpadServer;
 import com.fs.qw.service.IQwIpadServerService;
 import com.fs.wxwork.dto.*;
@@ -232,4 +233,10 @@ public class WxWorkServiceNew {
         String url = getUrl(serverId) + "/RoomIdToChatId";
         return WxWorkHttpUtilNew.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxWorkChatIdToRoomIdResp>>() {}, serverId);
     }
+    
+    
+    public WxWorkResponseDTO<WxSendTextAtMsgVo> sendTextAtMsgTwo(WxSendTextAtMsgTwoDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/SendTextAtMsgTwo";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxSendTextAtMsgVo>>() {});
+    }
 }

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

@@ -87,6 +87,7 @@ cloud_host:
   company_name: 济南联志健康
   projectCode: LZJK
   spaceName:
+  volcengineUrl: https://jnlzvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

+ 1 - 0
fs-service/src/main/resources/application-config-druid-bjzm-test.yml

@@ -86,6 +86,7 @@ cloud_host:
   company_name: 北京卓美
   projectCode: BJZM
   spaceName:
+  volcengineUrl: https://myhkvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

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

@@ -86,6 +86,7 @@ cloud_host:
   company_name: 北京卓美
   projectCode: BJZM
   spaceName:
+  volcengineUrl: https://myhkvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

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

@@ -87,8 +87,8 @@ cloud_host:
 headerImg:
   imgUrl: https://ysy-1329817240.cos.ap-guangzhou.myqcloud.com/ysy/20250820/2c47e4f105b641b4a49df50a77338e32.png
 ipad:
-  ipadUrl: http://ipad.ysya.top
-  aiApi: http://49.232.181.28:3000/api
+  ipadUrl: http://ipad.cfrytjkzx.cn
+  aiApi:
   voiceApi:
   commonApi:
 wx_miniapp_temp:

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

@@ -88,6 +88,7 @@ cloud_host:
   company_name: 叮当国医
   projectCode: DDGY
   spaceName:
+  volcengineUrl:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://ddgy-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251010/ddgy.jpg

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

@@ -81,6 +81,7 @@ cloud_host:
   company_name: 鹤颜堂
   projectCode: CDHYT
   spaceName:
+  volcengineUrl:
 headerImg:
   imgUrl: https
 ipad:

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

@@ -83,6 +83,7 @@ cloud_host:
   company_name: 河山医院
   projectCode: heshanyy
   spaceName:
+  volcengineUrl:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://hsyy-1348049832.cos.ap-chongqing.myqcloud.com/hsyy.jpg

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

@@ -88,6 +88,7 @@ cloud_host:
   company_name: 济南联志健康
   projectCode: LZJK
   spaceName:
+  volcengineUrl: https://jnlzvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

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

@@ -87,6 +87,7 @@ cloud_host:
   company_name: 内蒙古一贴
   projectCode: NMGYT
   spaceName:
+  volcengineUrl:
 headerImg:
   imgUrl: https
 ipad:

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

@@ -88,6 +88,7 @@ cloud_host:
   company_name: 今正科技
   projectCode: SXJZ
   spaceName:
+  volcengineUrl:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png

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

@@ -73,21 +73,15 @@ nuonuo:
 tencent_cloud_config:
   secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
   secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
-  bucket: jnlzjk-1323137866
+  bucket: ylrz-1323137866
   app_id: 1323137866
   region: ap-chongqing
-  proxy: jnlzjk
-tmp_secret_config:
-  secret_id: AKIDCj7NSNAovtqeJpBau8GZ4CGB71thXIxX
-  secret_key: lTB5zwqqz7CNhzDOWivFWedgfTBgxgBT
-  bucket: fs-1319721001
-  app_id: 1319721001
-  region: ap-chongqing
-  proxy: fs
+  proxy: ylrz
 cloud_host:
   company_name: 云联融智
   projectCode: YLRZ
   spaceName:
+  volcengineUrl: https://myhkvolcengine.ylrztop.com
 headerImg:
   imgUrl:
 

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

@@ -115,6 +115,8 @@ tencent_cloud_config:
 cloud_host:
   company_name: 中康
   projectCode: ZKZH
+  spaceName: sxzk-2114522511
+  volcengineUrl:
 headerImg:
   imgUrl: https://zkzh-2025.oss-cn-beijing.aliyuncs.com/fs/20250619/e31b5e051a474a7a9b4ad02575b46196.png
 

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

@@ -156,4 +156,4 @@ openIM:
 im:
     type: NONE
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: false
+isNewWxMerchant: true

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

@@ -164,7 +164,7 @@ token:
 openIM:
     secret: openIM123
     userID: imAdmin
-    url: https://web.im.fbylive.com/api
+    url: https://web.im.xianhthj.cn/api
 #是否使用新im
 im:
     type: NONE

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

@@ -147,6 +147,6 @@ openIM:
 im:
     type: NONE
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: false
+isNewWxMerchant: true
 
 

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

@@ -23,10 +23,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="periodId"    column="period_id"    />
         <result property="batchId"    column="batch_id"    />
         <result property="appId"    column="app_id"    />
+        <result property="mchId"    column="mch_id"    />
     </resultMap>
 
     <sql id="selectFsCourseRedPacketLogVo">
-        select log_id,watch_log_id, remark,out_batch_no,status,update_time,course_id, user_id, video_id, company_user_id, company_id, amount, create_time, qw_user_id,period_id,result,app_id,batch_id from fs_course_red_packet_log
+        select log_id,watch_log_id,mch_id, remark,out_batch_no,status,update_time,course_id, user_id, video_id, company_user_id, company_id, amount, create_time, qw_user_id,period_id,result,app_id,batch_id from fs_course_red_packet_log
     </sql>
 
     <select id="selectFsCourseRedPacketLogList" parameterType="FsCourseRedPacketLog" resultMap="FsCourseRedPacketLogResult">
@@ -112,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">app_id,</if>
             <if test="accBalanceBefore != null">acc_balance_before,</if>
             <if test="accBalanceAfter != null">acc_balance_after,</if>
+            <if test="mchId != null">mch_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="courseId != null">#{courseId},</if>
@@ -133,6 +135,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="appId != null">#{appId},</if>
             <if test="accBalanceBefore != null">#{accBalanceBefore},</if>
             <if test="accBalanceAfter != null">#{accBalanceAfter},</if>
+            <if test="mchId != null">#{mchId},</if>
         </trim>
     </insert>
 
@@ -155,6 +158,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark = #{remark},</if>
             <if test="batchId != null">batch_id = #{batchId},</if>
             <if test="appId != null">app_id = #{appId},</if>
+            <if test="mchId != null">mch_id = #{mchId},</if>
         </trim>
         where log_id = #{logId}
     </update>

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

@@ -842,6 +842,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test ='maps.logType !=null'>
                 and l.log_type = #{maps.logType}
             </if>
+            <if test ='maps.periodId !=null'>
+                and  l.period_id = #{maps.periodId}
+            </if>
             <if test ='maps.companyId !=null'>
                 and l.company_id = #{maps.companyId}
             </if>

+ 147 - 147
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -1010,77 +1010,77 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         company_tag.tag_id
     </select>
 
-    <select id="countUserCourse" resultType="Map">
-        SELECT
-        (SELECT COUNT(*) FROM (
-        SELECT 1
-        FROM fs_user_course_count fcc
-        JOIN fs_user ON fs_user.user_id = fcc.user_id
-        JOIN fs_user_company_user ucu ON ucu.user_id = fs_user.user_id
-        JOIN company_user ON ucu.company_user_id = company_user.user_id
-        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET(fcpd.period_id, fcc.course_ids) > 0
-        <where>
-            fcc.project_id = ucu.project_id
-            <if test="userId != null and userId != 0 ">
-                and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
-            </if>
-            <if test="userId != null and userId == 0 ">
-                and ucu.company_id = #{companyId}
-            </if>
-            <if test="startTime != null and startTime !='' ">
-                and fcc.create_time &gt;= #{startTime}
-            </if>
-            <if test="endTime != null and endTime != ''">
-                and fcc.create_time &lt;= #{endTime}
-            </if>
-            <if test="periodId != null and periodId != ''">
-                AND fcpd.period_id = #{periodId}
-            </if>
-            <if test="videoId != null and videoId != ''">
-                AND fcpd.video_id = #{videoId}
-            </if>
-            -- 单独通过销售id查询
-            <if test="companyUserId != null and companyUserId != ''">
-                AND company_user.user_id = #{companyUserId}
-            </if>
-        </where>
-        ) as courseWatchNum,
-        (SELECT COUNT(*) FROM (
-        SELECT 1
-        FROM fs_user_course_count fcc
-        JOIN fs_user ON fs_user.user_id = fcc.user_id
-        JOIN fs_user_company_user ucu ON ucu.user_id = fs_user.user_id
-        JOIN company_user ON ucu.company_user_id = company_user.user_id
-        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET(fcpd.period_id, fcc.course_ids) > 0
-        <where>
-            fcc.project_id = ucu.project_id
-            <if test="userId != null and userId != 0 ">
-                and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )
-            </if>
-            <if test="userId != null and userId == 0 ">
-                and ucu.company_id = #{companyId}
-            </if>
-            AND fcc.complete_watch_count > 0
-            <if test="startTime != null and startTime !='' ">
-                and fcc.create_time &gt;= #{startTime}
-            </if>
-            <if test="endTime != null and endTime != ''">
-                and fcc.create_time &lt;= #{endTime}
-            </if>
-            <if test="periodId != null and periodId != ''">
-                AND fcpd.period_id = #{periodId}
-            </if>
-            <if test="videoId != null and videoId != ''">
-                AND fcpd.video_id = #{videoId}
-            </if>
-            -- 单独通过销售id查询
-            <if test="companyUserId != null and companyUserId != ''">
-                AND company_user.user_id = #{companyUserId}
-            </if>
-        </where>
-        GROUP BY fcc.user_id, ucu.project_id
-        ) AS complete_counts ) as courseCompleteNum
-    </select>
+<!--    <select id="countUserCourse" resultType="Map">-->
+<!--        SELECT-->
+<!--        (SELECT COUNT(*) FROM (-->
+<!--        SELECT 1-->
+<!--        FROM fs_user_course_count fcc-->
+<!--        JOIN fs_user ON fs_user.user_id = fcc.user_id-->
+<!--        JOIN fs_user_company_user ucu ON ucu.user_id = fs_user.user_id-->
+<!--        JOIN company_user ON ucu.company_user_id = company_user.user_id-->
+<!--        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET(fcpd.period_id, fcc.course_ids) > 0-->
+<!--        <where>-->
+<!--            fcc.project_id = ucu.project_id-->
+<!--            <if test="userId != null and userId != 0 ">-->
+<!--                and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )-->
+<!--            </if>-->
+<!--            <if test="userId != null and userId == 0 ">-->
+<!--                and ucu.company_id = #{companyId}-->
+<!--            </if>-->
+<!--            <if test="startTime != null and startTime !='' ">-->
+<!--                and fcc.create_time &gt;= #{startTime}-->
+<!--            </if>-->
+<!--            <if test="endTime != null and endTime != ''">-->
+<!--                and fcc.create_time &lt;= #{endTime}-->
+<!--            </if>-->
+<!--            <if test="periodId != null and periodId != ''">-->
+<!--                AND fcpd.period_id = #{periodId}-->
+<!--            </if>-->
+<!--            <if test="videoId != null and videoId != ''">-->
+<!--                AND fcpd.video_id = #{videoId}-->
+<!--            </if>-->
+<!--            &#45;&#45; 单独通过销售id查询-->
+<!--            <if test="companyUserId != null and companyUserId != ''">-->
+<!--                AND company_user.user_id = #{companyUserId}-->
+<!--            </if>-->
+<!--        </where>-->
+<!--        ) as courseWatchNum,-->
+<!--        (SELECT COUNT(*) FROM (-->
+<!--        SELECT 1-->
+<!--        FROM fs_user_course_count fcc-->
+<!--        JOIN fs_user ON fs_user.user_id = fcc.user_id-->
+<!--        JOIN fs_user_company_user ucu ON ucu.user_id = fs_user.user_id-->
+<!--        JOIN company_user ON ucu.company_user_id = company_user.user_id-->
+<!--        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET(fcpd.period_id, fcc.course_ids) > 0-->
+<!--        <where>-->
+<!--            fcc.project_id = ucu.project_id-->
+<!--            <if test="userId != null and userId != 0 ">-->
+<!--                and (ucu.company_user_id = #{userId} OR company_user.parent_id = #{userId} )-->
+<!--            </if>-->
+<!--            <if test="userId != null and userId == 0 ">-->
+<!--                and ucu.company_id = #{companyId}-->
+<!--            </if>-->
+<!--            AND fcc.complete_watch_count > 0-->
+<!--            <if test="startTime != null and startTime !='' ">-->
+<!--                and fcc.create_time &gt;= #{startTime}-->
+<!--            </if>-->
+<!--            <if test="endTime != null and endTime != ''">-->
+<!--                and fcc.create_time &lt;= #{endTime}-->
+<!--            </if>-->
+<!--            <if test="periodId != null and periodId != ''">-->
+<!--                AND fcpd.period_id = #{periodId}-->
+<!--            </if>-->
+<!--            <if test="videoId != null and videoId != ''">-->
+<!--                AND fcpd.video_id = #{videoId}-->
+<!--            </if>-->
+<!--            &#45;&#45; 单独通过销售id查询-->
+<!--            <if test="companyUserId != null and companyUserId != ''">-->
+<!--                AND company_user.user_id = #{companyUserId}-->
+<!--            </if>-->
+<!--        </where>-->
+<!--        GROUP BY fcc.user_id, ucu.project_id-->
+<!--        ) AS complete_counts ) as courseCompleteNum-->
+<!--    </select>-->
     <select id="countUserStats" resultType="Map">
         SELECT
         -- 观看人数
@@ -1450,82 +1450,82 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) AS answerRightNum
     </select>
 
-    <select id="countCourseDetails" resultType="Map">
-        select (SELECT
-        count( DISTINCT fcpd.period_id )
-        FROM
-        fs_user_course_period_days fcpd
-        LEFT JOIN fs_user_course_period fpd on fpd.period_id = fcpd.period_id
-        LEFT JOIN fs_user_course_count fcc ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0
-        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id
-        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
-        WHERE 1=1 and fcpd.del_flag =0 and fpd.del_flag =0
-        AND FIND_IN_SET(#{companyId}, fpd.company_id)
-        <if test="userId != null and userId != 0 ">
-            AND ucu.company_user_id = #{userId}
-        </if>
-        <if test="userId != null and userId == 0 ">
-            and ucu.company_id = #{companyId}
-        </if>
-        <if test="periodId != null and periodId != ''">
-            AND fcpd.period_id =  #{periodId}
-        </if>
-        -- 单独通过销售id查询
-        <if test="companyUserId != null and companyUserId != ''">
-            AND ucu.company_user_id = #{companyUserId}
-        </if>
-        ) as courseNum,
-
-        (SELECT count(DISTINCT fcpd.video_id)
-        FROM fs_user_course_period_days fcpd
-        LEFT JOIN fs_user_course_period fpd on fpd.period_id = fcpd.period_id
-        LEFT JOIN fs_user_course_count fcc ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0
-        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id
-        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
-        WHERE 1=1 and fcpd.del_flag =0 and fpd.del_flag =0
-        AND FIND_IN_SET(#{companyId}, fpd.company_id)
-        <if test="userId != null and userId != 0 ">
-            AND ucu.company_user_id = #{userId}
-        </if>
-        <if test="userId != null and userId == 0 ">
-            and ucu.company_id = #{companyId}
-        </if>
-        <if test="periodId != null and periodId != ''">
-            AND fcpd.period_id =  #{periodId}
-        </if>
-        <if test="videoId != null and videoId != ''">
-            AND fcpd.video_id = #{videoId}
-        </if>
-        -- 单独通过销售id查询
-        <if test="companyUserId != null and companyUserId != ''">
-            AND ucu.company_user_id = #{companyUserId}
-        </if>
-        ) as videoNum,
-
-        ( SELECT count(DISTINCT fs_user.user_id ) FROM fs_user_course_count fcc
-        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0
-        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id
-        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
-        <if test="periodId != null and periodId != ''">
-            AND fcpd.period_id =  #{periodId}
-        </if>
-        <if test="videoId != null and videoId != ''">
-            AND fcpd.video_id = #{videoId}
-        </if>
-        -- 单独通过销售id查询
-        <if test="companyUserId != null and companyUserId != ''">
-            AND ucu.company_user_id = #{companyUserId}
-        </if>
-        <where>
-            <if test="userId != null and userId != 0 ">
-                AND ucu.company_user_id = #{userId}
-            </if>
-            <if test="userId != null and userId == 0 ">
-                and ucu.company_id = #{companyId}
-            </if>
-        </where>
-        ) as courseUserNum
-    </select>
+<!--    <select id="countCourseDetails" resultType="Map">-->
+<!--        select (SELECT-->
+<!--        count( DISTINCT fcpd.period_id )-->
+<!--        FROM-->
+<!--        fs_user_course_period_days fcpd-->
+<!--        LEFT JOIN fs_user_course_period fpd on fpd.period_id = fcpd.period_id-->
+<!--        LEFT JOIN fs_user_course_count fcc ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0-->
+<!--        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id-->
+<!--        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id-->
+<!--        WHERE 1=1 and fcpd.del_flag =0 and fpd.del_flag =0-->
+<!--        AND FIND_IN_SET(#{companyId}, fpd.company_id)-->
+<!--        <if test="userId != null and userId != 0 ">-->
+<!--            AND ucu.company_user_id = #{userId}-->
+<!--        </if>-->
+<!--        <if test="userId != null and userId == 0 ">-->
+<!--            and ucu.company_id = #{companyId}-->
+<!--        </if>-->
+<!--        <if test="periodId != null and periodId != ''">-->
+<!--            AND fcpd.period_id =  #{periodId}-->
+<!--        </if>-->
+<!--        &#45;&#45; 单独通过销售id查询-->
+<!--        <if test="companyUserId != null and companyUserId != ''">-->
+<!--            AND ucu.company_user_id = #{companyUserId}-->
+<!--        </if>-->
+<!--        ) as courseNum,-->
+
+<!--        (SELECT count(DISTINCT fcpd.video_id)-->
+<!--        FROM fs_user_course_period_days fcpd-->
+<!--        LEFT JOIN fs_user_course_period fpd on fpd.period_id = fcpd.period_id-->
+<!--        LEFT JOIN fs_user_course_count fcc ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0-->
+<!--        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id-->
+<!--        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id-->
+<!--        WHERE 1=1 and fcpd.del_flag =0 and fpd.del_flag =0-->
+<!--        AND FIND_IN_SET(#{companyId}, fpd.company_id)-->
+<!--        <if test="userId != null and userId != 0 ">-->
+<!--            AND ucu.company_user_id = #{userId}-->
+<!--        </if>-->
+<!--        <if test="userId != null and userId == 0 ">-->
+<!--            and ucu.company_id = #{companyId}-->
+<!--        </if>-->
+<!--        <if test="periodId != null and periodId != ''">-->
+<!--            AND fcpd.period_id =  #{periodId}-->
+<!--        </if>-->
+<!--        <if test="videoId != null and videoId != ''">-->
+<!--            AND fcpd.video_id = #{videoId}-->
+<!--        </if>-->
+<!--        &#45;&#45; 单独通过销售id查询-->
+<!--        <if test="companyUserId != null and companyUserId != ''">-->
+<!--            AND ucu.company_user_id = #{companyUserId}-->
+<!--        </if>-->
+<!--        ) as videoNum,-->
+
+<!--        ( SELECT count(DISTINCT fs_user.user_id ) FROM fs_user_course_count fcc-->
+<!--        LEFT JOIN fs_user_course_period_days fcpd ON FIND_IN_SET( fcpd.period_id, fcc.course_ids ) > 0-->
+<!--        LEFT JOIN fs_user ON fs_user.user_id = fcc.user_id-->
+<!--        left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id-->
+<!--        <if test="periodId != null and periodId != ''">-->
+<!--            AND fcpd.period_id =  #{periodId}-->
+<!--        </if>-->
+<!--        <if test="videoId != null and videoId != ''">-->
+<!--            AND fcpd.video_id = #{videoId}-->
+<!--        </if>-->
+<!--        &#45;&#45; 单独通过销售id查询-->
+<!--        <if test="companyUserId != null and companyUserId != ''">-->
+<!--            AND ucu.company_user_id = #{companyUserId}-->
+<!--        </if>-->
+<!--        <where>-->
+<!--            <if test="userId != null and userId != 0 ">-->
+<!--                AND ucu.company_user_id = #{userId}-->
+<!--            </if>-->
+<!--            <if test="userId != null and userId == 0 ">-->
+<!--                and ucu.company_id = #{companyId}-->
+<!--            </if>-->
+<!--        </where>-->
+<!--        ) as courseUserNum-->
+<!--    </select>-->
 
     <select id="countUserRankingByComplete" resultType="com.fs.store.vo.h5.FsUserRankingVO">
         SELECT

+ 17 - 6
fs-service/src/main/resources/mapper/hisStore/MergedOrderMapper.xml

@@ -20,6 +20,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       o.package_json,
       o.item_json,
       o.delivery_id,
+      o.delivery_sn as deliveryCode,
+      o.delivery_name as deliveryName,
+
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -86,9 +89,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           <if test="maps.deliveryId != null and maps.deliveryId != ''">
             AND o.delivery_id LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
-          <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
-            AND o.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
-          </if>
+        <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+        </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
           </if>
@@ -96,7 +99,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND o.real_name LIKE CONCAT('%', #{maps.realName}, '%')
           </if>
           <if test="maps.productName != null and maps.productName != ''">
-            AND fspc.productName LIKE CONCAT('%', #{maps.productName}, '%')
+            AND fspc.product_name LIKE CONCAT('%', #{maps.productName}, '%')
           </if>
           <if test="maps.deliveryStatus != null">
             AND o.delivery_status = #{maps.deliveryStatus}
@@ -151,6 +154,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       o.package_json,
       o.item_json,
       o.delivery_id,
+    o.delivery_sn as deliveryCode,
+    o.delivery_name as deliveryName,
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -218,7 +223,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND o.delivery_id LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
           <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
-            AND o.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
           </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
@@ -282,6 +287,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       NULL AS package_json,
       o.item_json,
       o.delivery_sn AS delivery_id,
+
+        o.delivery_code as deliveryCode,
+        o.delivery_name as deliveryName,
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -300,7 +308,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         fspc.product_name as productName,
         fspc.prescribe_spec as productSpec,
         fspc.cost as cost,
-        o.pay_delivery as payDelivery,
+        o.pay_postage as payDelivery,
         o.discount_money as discountMoney,
         fss.store_id as storeId,
         fss.store_name as storeName,
@@ -351,6 +359,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           <if test="maps.deliveryId != null and maps.deliveryId != ''">
             AND o.delivery_sn LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
+        <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+        </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
           </if>

+ 2 - 1
fs-service/src/main/resources/mapper/live/LiveAfterSalesMapper.xml

@@ -68,7 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         las.order_status, las.create_time, las.is_del, las.user_id, las.consignee, las.phone_number, las.address, las.company_id, las.company_user_id, las.dept_id,
         cu.nick_name as company_user_nick_name, c.company_name,lo.order_id,lo.order_code,lo.user_phone,las.user_id,lo.item_json,lo.pay_time as orderPayTime,
         lo.user_address,lo.user_name,lo.pay_price,lo.total_postage,lop.bank_serial_no,lo.delivery_sn as orderDeliveryId,lo.delivery_name as orderDeliveryName,
-        lo.delivery_code as orderDeliverySn,lo.status as orderStatus,lop.bank_transaction_id,lo.pay_money
+        lo.delivery_code as orderDeliverySn,lo.status as orderStatus,lop.bank_transaction_id,lo.pay_money,lop.pay_code as payCode
         from live_after_sales las
         left join live_order lo on lo.order_id = las.order_id
         left join company_user cu on cu.user_id = las.company_user_id
@@ -79,6 +79,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
 
         <where>
+            <if test="hfOrderCode != null and hfOrderCode != ''"> and lop.pay_code = #{hfOrderCode}</if>
             <if test="liveId != null and liveId != ''"> and las.live_id = #{liveId}</if>
             <if test="companyUserNickName != null and companyUserNickName != ''"> and cu.nick_name like concat(#{companyUserNickName},'%')</if>
             <if test="storeId != null and storeId != ''"> and las.store_id = #{storeId}</if>

+ 35 - 0
fs-service/src/main/resources/mapper/live/LiveWatchLogMapper.xml

@@ -210,4 +210,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLiveWatchLogByLiveId" resultType="com.fs.live.domain.LiveWatchLog">
         select * from live_watch_log where live_id = #{liveId}
     </select>
+
+    <select id="selectLiveWatchLogListInfo"   parameterType="LiveWatchLog" resultType="com.fs.live.vo.LiveWatchLogListVO">
+        select
+        t2.nick_name as userName,
+        t2.avatar as userAvatar,
+        t3.live_name,
+        t4.name as qwExternalName,
+        t4.avatar as qwExternalAvatar,
+        t5.nick_name as companyUserName,
+        t6.qw_user_name,
+        t1.*
+        from live_watch_log t1
+        left join fs_user t2 on t1.user_id = t2.user_id
+        left join live t3 on t3.live_id = t1.live_id
+        left join qw_external_contact t4 on t4.id = t1.external_contact_id
+        left join company_user t5 on t5.user_id = t1.company_user_id
+        left join qw_user t6 on t6.id = t1.qw_user_id
+        <where>
+            <if test="userId != null "> and t1.user_id = #{userId}</if>
+            <if test="liveId != null "> and t1.live_id = #{liveId}</if>
+            <if test="logType != null "> and t1.log_type = #{logType}</if>
+            <if test="externalContactId != null "> and t1.external_contact_id = #{externalContactId}</if>
+            <if test="companyId != null "> and t1.company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and t1.company_user_id = #{companyUserId}</if>
+            <if test="finishTime != null "> and t1.finish_time = #{finishTime}</if>
+            <if test="sopCreateTime != null "> and t1.sop_create_time = #{sopCreateTime}</if>
+            <if test="sendAppId != null  and sendAppId != ''"> and t1.send_app_id = #{sendAppId}</if>
+            <if test="logSource != null "> and t1.log_source = #{logSource}</if>
+            <if test="qwUserId != null  and qwUserId != ''"> and t1.qw_user_id = #{qwUserId}</if>
+            <if test="watchType != null">and t1.watch_type = #{watchType} </if>
+            <if test="corpId != null">and t1.corp_id = #{corpId} </if>
+            <if test="liveBuy != null">and t1.live_buy = #{liveBuy} </if>
+            <if test="replayBuy != null">and t1.replay_buy = #{replayBuy} </if>
+        </where>
+    </select>
 </mapper>

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

@@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
 
-    <select id="selectQwFriendWelcomeList" resultType="com.fs.qw.domain.QwFriendWelcome">
+    <select id="selectQwFriendWelcomeList" resultType="com.fs.qw.vo.QwFriendWelcomeVO">
         select
             w.*
         from qw_friend_welcome w

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

@@ -165,9 +165,23 @@
             NOW() AS updateTime,
             DATE_FORMAT(fwl.create_time,'%Y-%m-%d') AS create_date,
             DATE (fwl.create_time ) AS lastDate
+            ,Max( fwl.last_heartbeat_time ) AS lastWatchDate,
+            CASE
+            WHEN fwl.log_type = 1
+            OR fwl.log_type = 2 THEN
+            1
+            WHEN fwl.log_type = 4 THEN
+            2
+            WHEN fwl.log_type = 3 THEN
+            3
+        END AS STATUS
         FROM fs_course_watch_log fwl
         left join fs_user_company_user ucu on ucu.user_id = fwl.user_id
-        where fwl.send_type = 1 and fwl.create_time &gt;= DATE_SUB(CURDATE(), INTERVAL 15 DAY) and fwl.project = ucu.project_id
+        where fwl.user_id in
+        <foreach item="userId" collection="userIds" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+            and fwl.send_type = 1 and fwl.create_time >= DATE_SUB(CURDATE(), INTERVAL 15 DAY) and fwl.project = ucu.project_id
         GROUP BY
             fwl.user_id, date(fwl.create_time),ucu.project_id
     </select>
@@ -199,6 +213,16 @@
             fs_course_watch_log.user_id, date(fs_course_watch_log.create_time),ucu.project_id
     </select>
 
+    <select id="getUsersByPage" resultType="Long">
+        SELECT DISTINCT
+            fs_user.user_id
+        FROM
+            fs_user
+                left join fs_user_company_user ucu on ucu.user_id = fs_user.user_id
+        where ucu.company_user_id is not null
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
 
     <insert id="insertFsUserCourseCountTask" parameterType="FsUserCourseCount" useGeneratedKeys="true" keyProperty="id">
         insert into fs_user_course_count
@@ -256,6 +280,44 @@
         </trim>
     </insert>
 
+
+    <insert id="batchInsertOrUpdate">
+        INSERT INTO fs_user_course_count
+            ( user_id,
+            watch_course_count,
+            miss_course_count,
+            miss_course_status,
+            course_ids,
+            part_course_count,
+            last_watch_date,
+            status,
+            create_time,
+            update_time,
+            complete_watch_date,
+            complete_watch_count,
+            watch_times,
+            create_date,
+            project_id )
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.userId}, #{item.watchCourseCount}, #{item.missCourseCount}, #{item.missCourseStatus}, #{item.courseIds},
+             #{item.partCourseCount}, #{item.lastWatchDate}, #{item.status},#{item.createTime},#{item.updateTime},#{item.completeWatchDate}
+            ,#{item.completeWatchCount},#{item.watchTimes},#{item.createDate},#{item.projectId})
+        </foreach>
+        on duplicate key update
+        watch_course_count = VALUES(watch_course_count),
+        miss_course_count = VALUES(miss_course_count),
+        miss_course_status = VALUES(miss_course_status),
+        course_ids = VALUES(course_ids),
+        part_course_count = VALUES(part_course_count),
+        last_watch_date = VALUES(last_watch_date),
+        status = VALUES(status),
+        complete_watch_date = VALUES(complete_watch_date),
+        complete_watch_count = VALUES(complete_watch_count),
+        watch_times = VALUES(watch_times),
+        update_time = NOW()
+    </insert>
+
     <select id="selectUserLastCount" resultType="com.fs.store.vo.FsUserLastCount">
         SELECT
         fs_user_course_count.user_id,

+ 13 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseTransferController.java

@@ -71,4 +71,17 @@ public class CourseTransferController {
         return paymentService.TransferNotifyWithCompanyId(companyId,notifyData,request);
     }
 
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    @GetMapping("/getBillsByTransferBillNo")
+    public R getBillsByTransferBillNo(String transferBillNo){
+        return fsCourseRedPacketLogService.getBillsByTransferBillNo(transferBillNo);
+    }
+
+    @GetMapping("/transferBatchesBatchId")
+    public R transferBatchesBatchId(String batchId){
+        return fsCourseRedPacketLogService.transferBatchesBatchId(batchId);
+    }
+
 }

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

@@ -89,4 +89,20 @@ public class LiveCompletionPointsController extends AppBaseController {
         
         return R.ok().put("data", result);
     }
+
+    /**
+     * 用于测试和调试
+     */
+    @PostMapping("/test/create")
+    public R testCreateRecord(@RequestParam Long liveId, @RequestParam(required = false) Long watchDuration) {
+        Long userId = Long.parseLong(getUserId());
+        
+        try {
+            // 调用完课记录创建方法(watchDuration为null时会自动从数据库累计)
+            completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, watchDuration);
+            return R.ok("完课记录创建成功,请查看records接口查看结果");
+        } catch (Exception e) {
+            return R.error("创建失败: " + e.getMessage());
+        }
+    }
 }

+ 3 - 2
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -24,6 +24,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.core.config.WxMaConfiguration;
+import com.fs.core.utils.OrderCodeUtils;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
@@ -971,7 +972,7 @@ public class LiveOrderController extends AppBaseController
 
             String json = configService.selectConfigByKey("his.pay");
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             //易宝支付
             LiveOrderPayment storePayment=new LiveOrderPayment();
             storePayment.setCompanyId(order.getCompanyId());
@@ -1085,7 +1086,7 @@ public class LiveOrderController extends AppBaseController
                 return R.error("只有顺丰物流支持尾款支付");
             }
 
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey("his.pay");
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             LiveOrderPayment storePayment=new LiveOrderPayment();