Parcourir la source

Merge remote-tracking branch 'origin/master' into matser

吴树波 il y a 1 jour
Parent
commit
e4738a0fd1
43 fichiers modifiés avec 2142 ajouts et 323 suppressions
  1. 59 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  2. 1 1
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  3. 38 4
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  4. 28 5
      fs-common/src/main/java/com/fs/common/utils/SnowflakeUtil.java
  5. 5 1
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  6. 1 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  7. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsPrescribeMapper.java
  8. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsPrescribeService.java
  9. 7 1
      fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java
  10. 1 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  11. 2 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  12. 24 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  13. 6 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java
  14. 7 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  15. 48 45
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  16. 8 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  17. 48 52
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java
  18. 64 2
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  19. 10 0
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  20. 29 0
      fs-service/src/main/java/com/fs/live/param/LiveDataCompanyParam.java
  21. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveAfterSalesService.java
  22. 8 0
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  23. 4 0
      fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java
  24. 27 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesItemServiceImpl.java
  25. 234 30
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  26. 332 76
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  27. 478 27
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  28. 3 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  29. 62 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataCompanyVO.java
  30. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveVo.java
  31. 1 0
      fs-service/src/main/resources/application-config-druid-bjzm-test.yml
  32. 1 0
      fs-service/src/main/resources/application-config-druid-bjzm.yml
  33. 2 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  34. 51 8
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  35. 265 32
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  36. 5 8
      fs-service/src/main/resources/mapper/live/LiveGoodsMapper.xml
  37. 27 0
      fs-service/src/main/resources/mapper/live/LiveMapper.xml
  38. 1 8
      fs-user-app/src/main/java/com/fs/app/controller/HuifuPayController.java
  39. 37 0
      fs-user-app/src/main/java/com/fs/app/controller/IndexController.java
  40. 5 1
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveAfterSalesController.java
  41. 164 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCartController.java
  42. 40 6
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java
  43. 1 8
      fs-user-app/src/main/java/com/fs/app/controller/store/PayScrmController.java

+ 59 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -295,6 +295,62 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         }
         param.setIsHealth("1");
         List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        if ("北京卓美".equals(signProjectName)) {
+            List<FsStoreOrderItemExportZMVO> zmvoList = list.stream()
+                    .map(vo -> {
+                        FsStoreOrderItemExportZMVO zmvo = new FsStoreOrderItemExportZMVO();
+                        try {
+                            BeanUtil.copyProperties(vo, zmvo);
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                        return zmvo;
+                    })
+                    .collect(Collectors.toList());
+            if (zmvoList != null) {
+                LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+                for (FsStoreOrderItemExportZMVO vo : zmvoList) {
+                    if("2".equals(vo.getOrderType())){
+                        vo.setOrderTypeStr("直播订单" );
+                    }else{
+                        vo.setOrderTypeStr("商城订单" );
+                    }
+                    if(StringUtils.isNotEmpty(vo.getJsonInfo())){
+                        try { 
+                            StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                            BeanUtil.copyProperties(orderProductDTO, vo);
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                    }
+
+                    if (vo.getUserPhone() != null) {
+                        String phone = vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{1})", "$1****$2");
+                        vo.setUserPhone(phone);
+                    }
+                    if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                        try {
+                            StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                            BeanUtil.copyProperties(orderProductDTO, vo);
+                        } catch (Exception e) {
+                        }
+                    }
+                    if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*")) && !Objects.isNull(vo.getCost())) {
+                        vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getNum())));
+                    } else {
+                        vo.setPayPostage(BigDecimal.ZERO);
+                        vo.setPayDelivery(BigDecimal.ZERO);
+                        vo.setCost(BigDecimal.ZERO);
+                        vo.setFPrice(BigDecimal.ZERO);
+                        vo.setBarCode("");
+                        vo.setCateName("");
+                        vo.setBankTransactionId("");
+                    }
+                }
+            }
+            ExcelUtil<FsStoreOrderItemExportZMVO> util = new ExcelUtil<FsStoreOrderItemExportZMVO>(FsStoreOrderItemExportZMVO.class);
+            return util.exportExcel(zmvoList, "订单明细数据");
+        }
         //对手机号脱敏
         if (list != null) {
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
@@ -377,9 +433,10 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                             }
                         }
                         if ((loginUser.getPermissions().contains("his:storeAfterSales:finance") || loginUser.getPermissions().contains("*:*:*") ) && !Objects.isNull(vo.getCost())) {
-                            vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getTotalNum())));
+                            vo.setFPrice(vo.getCost().multiply(BigDecimal.valueOf(vo.getNum())));
                         } else {
                             vo.setPayPostage(BigDecimal.ZERO);
+                            vo.setPayDelivery(BigDecimal.ZERO);
                             vo.setCost(BigDecimal.ZERO);
                             vo.setFPrice(BigDecimal.ZERO);
                             vo.setBarCode("");
@@ -533,6 +590,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         //通过商品ID获取关键字
         String firstKeyword = storeOrderDeliveryNoteExportVOList.stream()
                 .map(FsStoreOrderDeliveryNoteExportVO::getKeyword)
+                .filter(StringUtils::isNotEmpty)
                 .findFirst()
                 .orElse("无订单");
         String fileName="077AC"+firstKeyword+new SimpleDateFormat("yyyyMMdd").format(new Date());

+ 1 - 1
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -191,7 +191,7 @@ public class MallStoreTask
                 log.info("汇付返回"+o);
                 if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
                     String[] orderSpilt=o.getOrg_req_seq_id().split("-");
-                    if ("store".equals(orderSpilt[0])) {
+                    if ("store".equals(orderSpilt[0]) || "live".equals(orderSpilt[0])) {
                         orderService.payConfirm(1, null, orderSpilt[1], o.getOrg_hf_seq_id(), o.getOut_trans_id(), o.getParty_order_id());
                     }
                 }

+ 38 - 4
fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -5,14 +5,15 @@ import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.company.domain.CompanyUser;
-import com.fs.framework.web.service.TokenService;
 import com.fs.live.domain.LiveData;
+import com.fs.live.param.LiveDataCompanyParam;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
+import com.fs.live.vo.LiveDataCompanyVO;
 import com.fs.live.vo.LiveUserFirstVo;
 import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
@@ -21,6 +22,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -31,8 +33,6 @@ public class LiveDataController extends BaseController {
 
     @Autowired
     private ILiveDataService liveDataService;
-    @Autowired
-    private TokenService tokenService;
 
     /**
      * 直播数据页面卡片数据
@@ -190,4 +190,38 @@ public class LiveDataController extends BaseController {
         return util.exportExcel(list, "直播间用户详情数据");
     }
 
+    /**
+     * 查询分公司直播数据统计列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:list')")
+    @PostMapping("/listLiveDataCompany")
+    public TableDataInfo listLiveDataCompany(@RequestBody LiveDataCompanyParam param) {
+        List<LiveDataCompanyVO> list = liveDataService.listLiveDataCompany(param);
+        int total = list.size();
+        int pageNum = param.getPageNum() == null || param.getPageNum() < 1 ? 1 : param.getPageNum();
+        int pageSize = param.getPageSize() == null || param.getPageSize() < 1 ? total : param.getPageSize();
+        int fromIndex = Math.min((pageNum - 1) * pageSize, total);
+        int toIndex = Math.min(fromIndex + pageSize, total);
+        List<LiveDataCompanyVO> pageList = fromIndex >= toIndex ? Collections.emptyList() : list.subList(fromIndex, toIndex);
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(pageList);
+        rspData.setTotal(total);
+        return rspData;
+    }
+
+    /**
+     * 导出分公司直播数据统计
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:export')")
+    @Log(title = "分公司直播数据统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportLiveDataCompany")
+    public AjaxResult exportLiveDataCompany(@RequestBody LiveDataCompanyParam param) {
+        List<LiveDataCompanyVO> list = liveDataService.listLiveDataCompany(param);
+        ExcelUtil<LiveDataCompanyVO> util = new ExcelUtil<>(LiveDataCompanyVO.class);
+        return util.exportExcel(list, "分公司直播数据统计");
+    }
+
 }

+ 28 - 5
fs-common/src/main/java/com/fs/common/utils/SnowflakeUtil.java

@@ -2,6 +2,10 @@ package com.fs.common.utils;
 
 import cn.hutool.core.lang.Snowflake;
 import cn.hutool.core.util.IdUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
 
 /**
  * 雪花算法ID生成器
@@ -10,27 +14,42 @@ import cn.hutool.core.util.IdUtil;
  * @author zhangqin
  * @date 2025-11-03
  */
+@Component
 public class SnowflakeUtil {
 
     /**
-     * 工作机器ID(0-31)
+     * 工作机器ID(0-31),从配置文件读取,默认值为1
      */
-    private static final long WORKER_ID = 1;
+    @Value("${snowflake.worker-id:1}")
+    private long workerId;
 
     /**
-     * 数据中心ID(0-31)
+     * 数据中心ID(0-31),从配置文件读取,默认值为1
      */
-    private static final long DATACENTER_ID = 1;
+    @Value("${snowflake.datacenter-id:1}")
+    private long datacenterId;
 
     /**
      * 雪花算法实例(单例)
      */
-    private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(WORKER_ID, DATACENTER_ID);
+    private static Snowflake SNOWFLAKE;
+
+    /**
+     * 初始化雪花算法实例
+     */
+    @PostConstruct
+    public void init() {
+        SNOWFLAKE = IdUtil.getSnowflake(workerId, datacenterId);
+    }
 
     /**
      * 生成Long型ID
      */
     public static Long nextId() {
+        if (SNOWFLAKE == null) {
+            // 如果Spring未初始化,使用默认值
+            SNOWFLAKE = IdUtil.getSnowflake(1, 1);
+        }
         return SNOWFLAKE.nextId();
     }
 
@@ -38,6 +57,10 @@ public class SnowflakeUtil {
      * 生成String型ID
      */
     public static String nextIdStr() {
+        if (SNOWFLAKE == null) {
+            // 如果Spring未初始化,使用默认值
+            SNOWFLAKE = IdUtil.getSnowflake(1, 1);
+        }
         return SNOWFLAKE.nextIdStr();
     }
 

+ 5 - 1
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -785,7 +785,11 @@ public class WebSocketServer {
         if (session == null || !session.isOpen()) {
             return;
         }
-        session.getAsyncRemote().sendText(JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+        try {
+            sendMessage(session, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
+        } catch (IOException e) {
+            log.error(e.getMessage());
+        }
     }
 
     private void sendBlockMessage(Long liveId, Long userId) {

+ 1 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -156,5 +156,6 @@ public class FsCourseWatchLogListVO extends BaseEntity
     /**
      * 用户上次登录的ip
      */
+    @Excel(name = "看课IP")
     private String lastIp;
 }

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

@@ -285,6 +285,9 @@ public interface FsPrescribeMapper
     @Select("select p.prescribe_id from fs_store_order o LEFT JOIN fs_prescribe p on p.store_order_id =o.order_id where o.status=2 and  o.order_type=2 and p.prescribe_img_store_url is null ")
     List<Long> selectFsPrescribeByPrescribeIdByOrderType();
 
+    @Select("select p.prescribe_id from fs_store_order o LEFT JOIN fs_prescribe p on p.store_order_id =o.order_id where o.status=2 and p.prescribe_img_store_url is null ")
+    List<Long> selectFsPrescribeByOrderStatus();
+
     List<Map<String,Object>> selectUploadData(String date);
 
     @Select("select count(1) from fs_prescribe where inquiry_order_id = #{orderId}")

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

@@ -104,6 +104,8 @@ public interface IFsPrescribeService
 
     List<Long> selectFsPrescribeByPrescribeIdByOrderType();
 
+    List<Long> selectFsPrescribeByOrderStatus();
+
     void PrescribeStoreImg(Long id);
 
     List<FsPrescribeListDVO> selectFsPrescribeListDVOByCompanyUser(FsPrescribeListDCompanyParam param);

+ 7 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java

@@ -22,6 +22,7 @@ import com.fs.his.service.IFsPrescribeService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.IdCardUtil;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.his.utils.qrcode.QRCodeUtils;
 import com.fs.his.vo.*;
 import com.fs.im.dto.MsgCustomDTO;
@@ -740,6 +741,11 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
         return fsPrescribeMapper.selectFsPrescribeByPrescribeIdByOrderType();
     }
 
+    @Override
+    public List<Long> selectFsPrescribeByOrderStatus() {
+        return fsPrescribeMapper.selectFsPrescribeByOrderStatus();
+    }
+
     @Override
     public void PrescribeStoreImg(Long id) {
         FsPrescribeVO f = fsPrescribeMapper.selectFsPrescribeByPrescribeIdVO(id);
@@ -761,7 +767,7 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
             o.setPatientName(f.getPatientName());
             o.setPatientGender(f.getPatientGender());
             o.setPatientAge(f.getPatientAge());
-            o.setPatientTel(f.getPatientTel());
+            o.setPatientTel(StringUtils.isEmpty(f.getPatientTel()) ? f.getPatientTel(): PhoneUtil.decryptPhone(f.getPatientTel()));
             o.setPrescribeDoctorName(f.getPrescribeDoctorName());
             o.setUserName(fsStoreOrder.getUserName()); //发货地址
             o.setUserPhone(fsStoreOrder.getUserPhone()); //收货人手机

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -199,6 +199,7 @@ public class FsStoreOrderScrm extends BaseEntity
 
     private String itemJson;
 
+    // 直播订单类型:2
     private Integer orderType;
 
     private Long packageId;

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -75,8 +75,8 @@ public interface FsStoreOrderItemScrmMapper
     List<FsStoreOrderItemVO> selectFsStoreOrderItemListAndProductByOrderId(Long id);
 
     @Select({"<script> " +
-            "select i.*,o.user_id,psps.cost,o.pay_postage,o.total_num,o.status,fspcs.cate_name, o.real_name,o.user_phone,o.user_address,o.create_time,o.pay_time,o.delivery_sn,o.delivery_name,o.delivery_id, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber,o.upload_time ,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload   " +
-            " ,p.title as package_name,cts.name as scheduleName,os.pay_money, os.bank_transaction_id as bankTransactionId, o.delivery_send_time " +
+            "select i.*,o.user_id,psps.cost,o.pay_postage,o.total_num,o.status,fspcs.cate_name, o.real_name,o.user_phone,o.user_address,o.create_time,o.pay_time,o.delivery_sn,o.delivery_name,o.delivery_id, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber,o.upload_time ,CASE WHEN o.certificates IS NULL OR o.certificates = '' THEN 0 ELSE 1 END AS is_upload,p.title as package_name,cts.name as scheduleName,os.pay_money, os.bank_transaction_id as bankTransactionId, o.delivery_send_time," +
+            " o.order_code, o.pay_price, o.pay_money, o.deduction_price,o.pay_delivery, o.order_type,psps.price " +
             ", CASE o.is_audit WHEN 1 THEN '是' ELSE '否' END AS isAudit " +
             " from fs_store_order_item_scrm i " +
             " left join fs_store_order_scrm o on o.id=i.order_id" +

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

@@ -710,6 +710,12 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
+            "<if test = 'maps.salesName != null and maps.salesName != \"\" '> " +
+            "and cu.user_name like CONCAT('%', #{maps.salesName}, '%') " +
+            "</if>" +
+            "<if test = 'maps.payCode != null and maps.payCode != \"\" '> " +
+            "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
+            "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "and o.company_id is null " +
             "</if>" +
@@ -722,6 +728,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
+            "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
+            "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
+            "</if>" +
             "<if test = 'maps.orderType != null    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
@@ -800,6 +809,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
+            "<if test = 'maps.salesName != null and maps.salesName != \"\" '> " +
+            "and cu.user_name like CONCAT('%', #{maps.salesName}, '%') " +
+            "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "and o.company_id is null " +
             "</if>" +
@@ -812,6 +824,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
+            "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
+            "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
+            "</if>" +
             "<if test = 'maps.orderType != null    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
@@ -1336,6 +1351,12 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyId != null    '> " +
             "and o.company_id =#{maps.companyId} " +
             "</if>" +
+            "<if test = 'maps.salesName != null and maps.salesName != \"\" '> " +
+            "and cu.user_name like CONCAT('%', #{maps.salesName}, '%') " +
+            "</if>" +
+            "<if test = 'maps.payCode != null and maps.payCode != \"\" '> " +
+            "and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%') " +
+            "</if>" +
             "<if test = 'maps.isHealth != null and maps.isHealth !=  \"\"  '> " +
             "and o.company_id is null " +
             "</if>" +
@@ -1348,6 +1369,9 @@ public interface FsStoreOrderScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
+            "<if test = 'maps.productName != null and maps.productName != \"\" '> " +
+            "and EXISTS (select 1 from fs_store_order_item_scrm oi2 join fs_store_product_scrm fsp2 on oi2.product_id = fsp2.product_id where oi2.order_id = o.id and fsp2.product_name like CONCAT('%', #{maps.productName}, '%')) " +
+            "</if>" +
             "<if test = 'maps.orderType != null    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java

@@ -74,6 +74,12 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
 
     private String productName;
 
+    /** 销售名称(模糊搜索,对应company_user.user_name) */
+    private String salesName;
+
+    /** 汇付商户订单号(支付信息pay_code) */
+    private String payCode;
+
     private Integer isUpload;
 
     /** 开始时间 */

+ 7 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -884,8 +884,10 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
                         payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
                         wxPayService.setConfig(payConfig);
                         WxPayRefundRequest refundRequest = new WxPayRefundRequest();
-                        refundRequest.setOutTradeNo("store-"+payment.getPayCode());
-                        refundRequest.setOutRefundNo("store-"+payment.getPayCode());
+                        // 检查订单类型,如果是直播订单(orderType=2),使用 "live-" 前缀
+                        String prefix = (order.getOrderType() != null && order.getOrderType() == 2) ? "live-" : "store-";
+                        refundRequest.setOutTradeNo(prefix + payment.getPayCode());
+                        refundRequest.setOutRefundNo(prefix + payment.getPayCode());
                         refundRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
                         refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen(refundAmount.toString()));
                         try {
@@ -1527,6 +1529,9 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         String orderType = "store";
         // 开票冲红
         fsStoreOrderBillLogService.billBackByOrderId(fsStoreOrder.getId());
+        if (fsStoreOrder.getOrderType() == 2) {
+            orderType = "live";
+        }
 
 
         if (fsStoreOrder.getPackageOrderId() != null) {

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

@@ -28,11 +28,7 @@ import com.fs.common.event.TemplateEvent;
 import com.fs.common.event.TemplateListenEnum;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
-import com.fs.common.utils.CloudHostUtils;
-import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.IpUtil;
-import com.fs.common.utils.ServletUtils;
-import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.*;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.*;
@@ -145,6 +141,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 
 import javax.annotation.PostConstruct;
 import java.lang.reflect.Field;
@@ -165,6 +163,7 @@ import java.util.stream.Collectors;
 
 import static com.fs.his.utils.PhoneUtil.decryptPhone;
 import static com.fs.hisStore.constants.StoreConstants.DELIVERY;
+import static com.fs.hisStore.constants.UserAppsLockConstant.LOCK_KEY_PAY;
 
 /**
  * 订单Service业务层处理
@@ -210,6 +209,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Autowired
     private IFsStoreCartScrmService cartService;
 
@@ -882,7 +884,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             FsUserAddressScrm address = userAddressMapper.selectFsUserAddressById(param.getAddressId());
             //生成分布式唯一值
 
-            String orderSn = OrderCodeUtils.getOrderSn();
+            String orderSn = SnowflakeUtil.nextIdStr();
             //是否使用积分
             Boolean isIntegral = false;
             //组合数据
@@ -1172,7 +1174,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         List<FsPrescribeDrug> drugs = prescribeDrugMapper.selectFsPrescribeDrugList(map);
         FsStoreOrderScrm order = new FsStoreOrderScrm();
         List<FsStoreOrderItemScrm> items = new ArrayList<>();
-        String orderSn = OrderCodeUtils.getOrderSn();
+        String orderSn = SnowflakeUtil.nextIdStr();
         if (StringUtils.isEmpty(orderSn)) {
             return R.error("订单生成失败,请重试");
         }
@@ -1990,10 +1992,37 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     @Override
     @Transactional
-    //类型1支付回调 类型2货到付款
+    //类型1支付回调 类型2货到付款;使用 Redis 锁保证同一订单回调只被一个线程处理
     public String payConfirm(Integer type, Long orderId, String payCode, String tradeNo, String bankTransactionId, String bankSerialNo) {
-        //支付订单
-//        try {
+
+        FsStorePaymentScrm storePayment = paymentService.selectFsStorePaymentByCode(payCode);
+        if (storePayment == null || !storePayment.getStatus().equals(0)) {
+            return "";
+        }
+        RLock lock = redissonClient.getLock(String.format(LOCK_KEY_PAY, storePayment.getOrderId()));
+        try {
+            boolean locked = lock.tryLock(100, 30000, TimeUnit.MILLISECONDS);
+            if (!locked) {
+                log.warn("支付回调正在处理中,获取锁失败, payCode: {}", payCode);
+                return "";
+            }
+            return doPayConfirm(type, orderId, payCode, tradeNo, bankTransactionId, bankSerialNo);
+        } catch (InterruptedException e) {
+            log.error("支付回调获取锁被中断, payCode: {}", payCode, e);
+            Thread.currentThread().interrupt();
+            return "";
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.debug("支付回调锁已释放, payCode: {}", payCode);
+            }
+        }
+    }
+
+    /**
+     * 支付确认核心逻辑(在 Redis 锁内执行)
+     */
+    private String doPayConfirm(Integer type, Long orderId, String payCode, String tradeNo, String bankTransactionId, String bankSerialNo) {
         FsStoreOrderScrm order = null;
         if (type.equals(1)) {
             FsStorePaymentScrm storePayment = paymentService.selectFsStorePaymentByCode(payCode);
@@ -2028,17 +2057,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         //写入公司佣金 当有归属公司时只进行公司分佣
         if (order.getCompanyId() != null && order.getCompanyId() > 0) {
             companyService.addCompanyTuiMoney(order);
-            //没有归属公司对个人分佣
         } else if (order.getIsPackage() != 1 && order.getTuiUserId() != null && order.getTuiUserId() > 0) {
-            //处理佣金 套餐不分佣金
             FsStoreOrderItemScrm orderItemMap = new FsStoreOrderItemScrm();
             orderItemMap.setOrderId(order.getId());
             List<FsStoreOrderItemScrm> items = storeOrderItemService.selectFsStoreOrderItemList(orderItemMap);
             userService.addTuiMoney(order, items);
         }
-        //增加用户购买次数
         userService.incPayCount(order.getUserId());
-        //增加状态
         orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
                 OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
         FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
@@ -2047,44 +2072,20 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
         storeOrder.setPayTime(new Date());
         fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
-        // 添加订单审核
         if (storeOrder.getCompanyId() != null) {
             addOrderAudit(order);
         }
         try {
-            // 记录限购数量(订单创建成功后记录)
             List<FsStoreOrderItemVO> fsStoreOrderItemVOS = storeOrderItemService.selectFsStoreOrderItemListByOrderId(order.getId());
             if (fsStoreOrderItemVOS != null && !fsStoreOrderItemVOS.isEmpty()) {
                 for (FsStoreOrderItemVO fsStoreOrderItemVO : fsStoreOrderItemVOS) {
                     purchaseLimitService.increasePurchaseLimit(fsStoreOrderItemVO.getProductId(), order.getUserId(), Math.toIntExact(fsStoreOrderItemVO.getNum()));
                 }
             }
-
         } catch (Exception e) {
-            log.error("创建限购商品失败:{}",e.getMessage());
+            log.error("创建限购商品失败:{}", e.getMessage());
         }
-
-    return "SUCCESS";
-
-
-        //非处方直接提交OMS
-//            if(order.getIsPrescribe().equals(0)){
-//                createOmsOrder(order.getId());
-//            }
-//            else if(order.getIsPrescribe().equals(1)){
-//                //是否已开方
-//                FsPrescribe prescribe=prescribeService.selectFsPrescribeByOrderId(order.getId());
-//                if(prescribe!=null&&prescribe.getStatus()==1){
-//                    createOmsOrder(order.getId());
-//                }
-//            }
-//        }
-//        catch (Exception e){
-//            logger.info("payConfirm:"+e.getMessage());
-//            logger.info("payConfirm:"+e.getLocalizedMessage());
-//            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-//            return "";
-//        }
+        return "SUCCESS";
     }
 
     /**
@@ -2537,8 +2538,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                         payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
                         wxPayService.setConfig(payConfig);
                         WxPayRefundRequest refundRequest = new WxPayRefundRequest();
-                        refundRequest.setOutTradeNo("store-" + payment.getPayCode());
-                        refundRequest.setOutRefundNo("store-" + payment.getPayCode());
+                        // 检查订单类型,如果是直播订单(orderType=2),使用 "live-" 前缀
+                        String prefix = (order.getOrderType() != null && order.getOrderType() == 2) ? "live-" : "store-";
+                        refundRequest.setOutTradeNo(prefix + payment.getPayCode());
+                        refundRequest.setOutRefundNo(prefix + payment.getPayCode());
                         refundRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
                         refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
                         try {
@@ -4450,7 +4453,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 this.updateFsStoreOrder(order);
             }
-            String payCode =  OrderCodeUtils.getOrderSn();
+            String payCode =  SnowflakeUtil.nextIdStr();
             if((order.getPayType().equals("1")||order.getPayType().equals("2")||order.getPayType().equals("3")||order.getPayType().equals("5")) && order.getPayMoney().compareTo(new BigDecimal(0))>0){
                 if (StringUtils.isBlank(param.getAppId())) {
                     throw new IllegalArgumentException("appId不能为空");
@@ -4602,7 +4605,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if(!order.getIsPayRemain().equals(0)){
                 return R.error("此订单已支付");
             }
-            String payCode =  OrderCodeUtils.getOrderSn();
+            String payCode =  SnowflakeUtil.nextIdStr();
             if (StringUtils.isBlank(param.getAppId())) {
                 throw new IllegalArgumentException("appId不能为空");
             }
@@ -4846,7 +4849,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         FsUserScrm user=userService.selectFsUserById(order.getUserId());
         if(user!=null){
-            String payCode =  OrderCodeUtils.getOrderSn();
+            String payCode =  SnowflakeUtil.nextIdStr();
             if (StringUtils.isBlank(param.getAppId())) {
                 throw new IllegalArgumentException("appId不能为空");
             }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java

@@ -120,4 +120,12 @@ public class FsStoreOrderItemExportVO implements Serializable
     @Excel(name = "是否审核")
     private String isAudit;
 
+
+    private String orderType; // 订单类型 2.直播
+    private BigDecimal payPrice;// 应付金额
+    private BigDecimal deductionPrice;// 应付金额
+    private BigDecimal payDelivery;// 应付邮费
+
+
+
 }

+ 48 - 52
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java

@@ -9,116 +9,112 @@ import java.math.BigDecimal;
 import java.util.Date;
 
 /**
- * @author MixLiu
- * @date 2025/12/4 上午11:43)
+ * 商城订单明细导出VO-北京卓美(导出字段与合并订单 MergedOrderVO 一致)
  */
-
 @Data
-public class FsStoreOrderItemExportZMVO implements Serializable  {
-
+public class FsStoreOrderItemExportZMVO implements Serializable {
 
     private Long itemId;
 
-    /** 订单号 */
-    @Excel(name = "订单号",sort = 1)
+
+    @Excel(name = "订单类型")
+    private String orderTypeStr;
+
+    @Excel(name = "订单号")
     private String orderCode;
 
-    @Excel(name = "订单状态", dictType = "store_order_status",sort = 10)
+    @Excel(name = "订单状态", dictType = "store_order_status")
     private String status;
 
-    @Excel(name = "会员ID" ,sort = 20)
+    @Excel(name = "会员ID")
     private Long userId;
 
-    @Excel(name = "产品名称",sort = 30)
+    @Excel(name = "产品名称")
     private String productName;
 
-    @Excel(name = "产品编码",sort =40)
+    @Excel(name = "产品编码")
     private String barCode;
 
-
-    @Excel(name = "规格",sort =50)
+    @Excel(name = "规格")
     private String sku;
 
-    @Excel(name = "产品数量",sort =60)
+    @Excel(name = "产品数量")
     private Integer num;
 
+    @Excel(name = "成本单价")
+    private BigDecimal cost;
 
-    @Excel(name = "产品价格",sort =70)
+    @Excel(name = "商品金额")
     private BigDecimal price;
 
-    @Excel(name = "成本单价",sort =80)
-    private BigDecimal cost;
-    @Excel(name = "结算价",sort =90)
-    private BigDecimal FPrice;
+    @Excel(name = "应付金额")
+    private BigDecimal payPrice;
 
-    @Excel(name = "实付金额",sort =91)
+    @Excel(name = "实付金额")
     private BigDecimal payMoney;
 
-    @Excel(name = "额外运费",sort =100)
-    private BigDecimal payPostage;
-    private Integer totalNum;
-    @Excel(name = "商品分类",sort =100)
-    private String cateName;
 
+    @Excel(name = "结算价")
+    private BigDecimal FPrice;
 
-    private String jsonInfo;
+    @Excel(name = "额外运费")
+    private BigDecimal payDelivery;
+
+    @Excel(name = "商品分类")
+    private String cateName;
 
     /** 用户姓名 */
-    @Excel(name = "收货人姓名",sort =110)
+    @Excel(name = "收货人姓名")
     private String realName;
 
     /** 用户电话 */
-    @Excel(name = "收货人电话",sort =120)
+    @Excel(name = "收货人电话")
     private String userPhone;
 
     /** 详细地址 */
-    @Excel(name = "详细地址",sort =130)
+    @Excel(name = "详细地址")
     private String userAddress;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "下单时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 140)
+    @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",sort = 150)
+    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     private Date payTime;
 
     /** 快递公司编号 */
-    @Excel(name = "快递公司编号",sort = 160)
+    @Excel(name = "快递公司编号")
     private String deliverySn;
 
     /** 快递名称/送货人姓名 */
-    @Excel(name = "快递公司",sort = 170)
+    @Excel(name = "快递公司")
     private String deliveryName;
 
     /** 快递单号/手机号 */
-    @Excel(name = "快递单号",sort = 180)
+    @Excel(name = "快递单号")
     private String deliveryId;
 
-    @Excel(name = "所属公司",sort = 190)
+    @Excel(name = "所属公司")
     private String companyName;
-    @Excel(name = "所属销售",sort = 200)
+    @Excel(name = "所属销售")
     private String companyUserNickName;
 
-    @Excel(name = "套餐名称",sort = 210)
-    private String packageName;
+    //银行交易流水号
+    @Excel(name = "银行交易流水号")
+    private String bankTransactionId;
 
-    @Excel(name = "组合码",sort = 210)
-    private String groupBarCode;
 
-    @Excel(name = "是否上传凭证 0:未上传 1:已上传",sort = 210)
-    private Integer isUpload;
+    /** 商品分类(与 MergedOrderVO 一致,不导出) */
 
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "上传时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss",sort = 220)
+    // 以下字段供内部使用,不参与导出
+    private BigDecimal payPostage;
+    private Integer totalNum;
+    private String jsonInfo;
+    private String packageName;
+    private String groupBarCode;
+    private Integer isUpload;
     private Date uploadTime;
-
-    @Excel(name = "归属档期",sort = 230)
     private String scheduleName;
-
-    //银行交易流水号
-    @Excel(name = "银行交易流水号",sort = 240)
-    private String bankTransactionId;
-
-
+    private String orderType; // 订单类型 2.直播
 }

+ 64 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java

@@ -4,7 +4,8 @@ package com.fs.live.mapper;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveData;
-import com.fs.live.vo.LiveDashBoardDataVo;
+import com.fs.live.param.LiveDataCompanyParam;
+import com.fs.live.vo.LiveDataCompanyVO;
 import com.fs.live.vo.LiveDataDetailVo;
 import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveDataStatisticsVo;
@@ -15,7 +16,6 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.springframework.stereotype.Repository;
 
-import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
@@ -176,4 +176,66 @@ public interface LiveDataMapper {
      */
     @DataSource(DataSourceType.SLAVE)
     List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId,@Param("companyId") Long companyId,@Param("companyUserId") Long companyUserId);
+
+    /**
+     * 查询满足条件的直播间ID列表
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<Long> selectLiveIdsByCompanyParam(LiveDataCompanyParam param);
+
+    /**
+     * 分公司总到课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectAttendanceCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                           @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司总完课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectCompleteCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                         @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司直播课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectLiveAttendCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                           @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司直播完课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectLiveCompleteCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                             @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司回放课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectReplayAttendCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                             @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司回放完课人数统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectReplayCompleteCountByCompany(@Param("liveIds") List<Long> liveIds,
+                                                               @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司订单、GMV统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectCompanyOrderAndGmv(@Param("liveIds") List<Long> liveIds,
+                                                     @Param("companyIds") List<Long> companyIds);
+
+    /**
+     * 分公司员工数量统计
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<LiveDataCompanyVO> selectCompanyEmployeeCountByLiveIds(@Param("liveIds") List<Long> liveIds,
+                                                                @Param("companyIds") List<Long> companyIds);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -11,6 +11,7 @@ import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
+import java.util.Date;
 
 /**
  * 直播Mapper接口
@@ -171,6 +172,15 @@ public interface LiveMapper
             " </script>"})
     List<Live> listLiveData(@Param("param") LiveDataParam param);
 
+    /**
+     * 根据查询条件获取直播间ID集合
+     */
+    @DataSource(DataSourceType.SLAVE)
+    List<Long> selectLiveIdsByCompanyParam(@Param("companyName") String companyName,
+                                           @Param("startDate") Date startDate,
+                                           @Param("endDate") Date endDate,
+                                           @Param("companyIds") List<Long> companyIds);
+
     @Select({"<script>" +
             "select count(1) from ( " +
             "select * from live where 1=1 " +

+ 29 - 0
fs-service/src/main/java/com/fs/live/param/LiveDataCompanyParam.java

@@ -0,0 +1,29 @@
+package com.fs.live.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 分公司直播数据统计查询参数
+ */
+@Data
+public class LiveDataCompanyParam {
+
+    /** 分公司名称(模糊搜索) */
+    private String companyName;
+    private List<Long> companyIds;
+
+    /** 开始日期(年月日) */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startDate;
+
+    /** 结束日期(年月日) */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endDate;
+
+    private Integer pageNum = 1;
+    private Integer pageSize = 10;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveAfterSalesService.java

@@ -95,4 +95,6 @@ public interface ILiveAfterSalesService {
     R handleImmediatelyRefund(Long orderId);
 
     List<LiveAfterSalesVo> selectLiveAfterSalesVoListExport(LiveAfterSalesVo liveAfterSales);
+
+    R applyForStoreAfterSales(String userId, LiveAfterSalesParam param);
 }

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

@@ -3,6 +3,7 @@ package com.fs.live.service;
 
 import com.fs.common.core.domain.R;
 import com.fs.live.domain.LiveData;
+import com.fs.live.param.LiveDataCompanyParam;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.vo.*;
 
@@ -169,4 +170,11 @@ public interface ILiveDataService {
     List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId, Long companyId, Long companyUserId);
 
     List<LiveDataListVo> exportLiveData(LiveDataParam param);
+
+    /**
+     * 查询分公司直播数据统计列表
+     * @param param 查询参数
+     * @return 分公司统计数据
+     */
+    List<LiveDataCompanyVO> listLiveDataCompany(LiveDataCompanyParam param);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java

@@ -275,4 +275,8 @@ public interface ILiveOrderService {
     void updateTime(LiveOrder order);
 
     void initStock();
+
+    R createStoreOrder(LiveOrder param);
+
+    R handleStoreOrderPay(LiveOrderPayParam param);
 }

+ 27 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesItemServiceImpl.java

@@ -4,9 +4,12 @@ package com.fs.live.service.impl;
 import com.fs.live.domain.LiveAfterSalesItem;
 import com.fs.live.mapper.LiveAfterSalesItemMapper;
 import com.fs.live.service.ILiveAfterSalesItemService;
+import com.fs.hisStore.domain.FsStoreAfterSalesItemScrm;
+import com.fs.hisStore.service.IFsStoreAfterSalesItemScrmService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -21,6 +24,9 @@ public class LiveAfterSalesItemServiceImpl  implements ILiveAfterSalesItemServic
 
     @Autowired
     private LiveAfterSalesItemMapper baseMapper;
+    
+    @Autowired
+    private IFsStoreAfterSalesItemScrmService fsStoreAfterSalesItemScrmService;
 
     /**
      * 查询售后子
@@ -96,6 +102,26 @@ public class LiveAfterSalesItemServiceImpl  implements ILiveAfterSalesItemServic
 
     @Override
     public List<LiveAfterSalesItem> selectLiveAfterSalesItemByAfterId(Long afterId) {
-        return baseMapper.selectLiveAfterSalesItemByAfterId(afterId);
+        // 查询商城售后商品信息
+        FsStoreAfterSalesItemScrm queryItem = new FsStoreAfterSalesItemScrm();
+        queryItem.setStoreAfterSalesId(afterId);
+        List<FsStoreAfterSalesItemScrm> storeItems = fsStoreAfterSalesItemScrmService.selectFsStoreAfterSalesItemList(queryItem);
+        
+        if (storeItems == null || storeItems.isEmpty()) {
+            return Collections.emptyList();
+        }
+        
+        // 转换为 LiveAfterSalesItem(为了兼容性)
+        List<LiveAfterSalesItem> liveItems = new ArrayList<>();
+        for (FsStoreAfterSalesItemScrm storeItem : storeItems) {
+            LiveAfterSalesItem liveItem = new LiveAfterSalesItem();
+            liveItem.setId(storeItem.getId());
+            liveItem.setAfterSalesId(storeItem.getStoreAfterSalesId());
+            liveItem.setProductId(storeItem.getProductId());
+            liveItem.setJsonInfo(storeItem.getJsonInfo());
+            liveItem.setIsDel(storeItem.getIsDel());
+            liveItems.add(liveItem);
+        }
+        return liveItems;
     }
 }

+ 234 - 30
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -49,9 +49,15 @@ import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.enums.*;
+import com.fs.hisStore.param.FsStoreAfterSalesParam;
 import com.fs.hisStore.param.FsStoreAfterSalesProductParam;
-import com.fs.hisStore.service.IFsStoreProductScrmService;
-import com.fs.hisStore.service.IFsUserScrmService;
+import com.fs.hisStore.service.*;
+import com.fs.hisStore.mapper.FsStoreAfterSalesScrmMapper;
+import com.fs.hisStore.domain.FsStoreAfterSalesScrm;
+import com.fs.hisStore.domain.FsStoreAfterSalesItemScrm;
+import com.fs.hisStore.domain.FsStoreAfterSalesStatusScrm;
+import com.fs.hisStore.enums.AfterSalesStatusEnum;
+import com.fs.hisStore.enums.SysConfigEnum;
 import com.fs.hisStore.vo.FsStoreAfterSalesQueryVO;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import com.fs.huifuPay.domain.HuiFuRefundResult;
@@ -262,6 +268,189 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         }
         return liveAfterSalesVos;
     }
+
+    @Override
+    @Transactional
+    public R applyForStoreAfterSales(String userId, LiveAfterSalesParam param) {
+        log.info("申请退款请求信息:"+JSONUtil.toJsonStr(param));
+        // 使用Redis分布式锁,确保同一订单只能有一个服务器处理申请售后 因为卓美有一个订单生成了三个售后订单,数据一摸一样,除了id
+        String lockKey = "live:afterSales:apply:" + param.getOrderCode();
+        RLock lock = redissonClient.getLock(lockKey);
+
+
+
+        // 查询配置:是否删除历史售后数据
+        try {
+
+            // 尝试获取锁,等待时间3秒,锁过期时间30秒
+            boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
+            if (!locked) {
+                log.warn("申请售后订单锁获取失败,订单号:{}", param.getOrderCode());
+                return R.error("系统繁忙,请稍后重试");
+            }
+
+            String deleteAfterSalesConfig = configService.selectConfigByKey("delete_after_sales");
+            if (StringUtils.isNotEmpty(deleteAfterSalesConfig) && "true".equalsIgnoreCase(deleteAfterSalesConfig.trim())) {
+                // 查询历史售后数据,将 is_del 设置为 1
+                FsStoreAfterSalesScrm queryAfterSales = new FsStoreAfterSalesScrm();
+                queryAfterSales.setOrderCode(param.getOrderCode());
+                List<FsStoreAfterSalesScrm> historyAfterSalesList = fsStoreAfterSalesScrmMapper.selectFsStoreAfterSalesList(queryAfterSales);
+                if (historyAfterSalesList != null && !historyAfterSalesList.isEmpty()) {
+                    for (FsStoreAfterSalesScrm historyAfterSales : historyAfterSalesList) {
+                        if (historyAfterSales.getIsDel() != null && historyAfterSales.getIsDel() == 0) {
+                            FsStoreAfterSalesScrm updateAfterSales = new FsStoreAfterSalesScrm();
+                            updateAfterSales.setId(historyAfterSales.getId());
+                            updateAfterSales.setIsDel(1);
+                            fsStoreAfterSalesScrmMapper.updateFsStoreAfterSales(updateAfterSales);
+                            log.info("删除历史售后数据,售后ID:{},订单号:{}", historyAfterSales.getId(), param.getOrderCode());
+                        }
+                    }
+                }
+            }
+        
+            // 查询商城订单
+            FsStoreOrderScrm order = fsStoreOrderScrmService.selectFsStoreOrderByOrderCode(param.getOrderCode());
+            Integer orderStatus = order.getStatus();
+            Long userIdLong = Long.parseLong(userId);
+            if(!order.getUserId().equals(userIdLong)){
+                throw new CustomException("非法操作");
+            }
+            if(order.getStatus()==0){
+                return R.error("未支付订单不能申请售后");
+            }
+            if("1".equals(configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"))
+                    && StringUtils.isEmpty(order.getExtendOrderId())
+                    && !CloudHostUtils.hasCloudHostName("康年堂")){
+                log.info("erpOpen:{}",configUtil.generateConfigByKey(SysConfigEnum.HIS_CONFIG.getKey()).getString("erpOpen"));
+                return R.error("仓库未生成订单,暂时不能申请退款,请联系客服");
+            }
+            if(order.getStatus()== OrderInfoEnum.STATUS_NE3.getValue()){
+                return R.error("已取消订单不能申请售后");
+            }
+            if(order.getStatus()== OrderInfoEnum.STATUS_NE1.getValue()){
+                return R.error("已提交申请,等待处理");
+            }
+            //已完成订单七天后不能申请退款
+            if(order.getStatus().equals(OrderInfoEnum.STATUS_3.getValue())) {
+                String json=configService.selectConfigByKey("store.config");
+                StoreConfig config=JSONUtil.toBean(json,StoreConfig.class);
+                //已完成订单
+                if (order.getFinishTime() != null) {
+                    if (config.getStoreAfterSalesDay() != null && config.getStoreAfterSalesDay() > 0) {
+                        //判断完成时间是否超过指定时间
+                        Calendar calendarAfterSales = new GregorianCalendar();
+                        calendarAfterSales.setTime(order.getFinishTime());
+                        calendarAfterSales.add(Calendar.DATE, config.getStoreAfterSalesDay()); //把日期往后增加一天,整数  往后推,负数往前移动
+                        if (calendarAfterSales.getTime().getTime() < new Date().getTime()) {
+                            return R.error("此订单已超过售后时间,不能提交售后");
+                        }
+                    }
+                }
+            }
+            //拿到所有的商品
+            List<FsStoreOrderItemVO> orderItems=fsStoreOrderItemScrmService.selectFsStoreOrderItemListByOrderId(order.getId());
+            // 转换商品列表
+            List<FsStoreAfterSalesProductParam> productList = new ArrayList<>();
+            if (param.getProductList() != null) {
+                for (com.fs.live.param.LiveAfterSalesProductParam liveProduct : param.getProductList()) {
+                    FsStoreAfterSalesProductParam storeProduct = new FsStoreAfterSalesProductParam();
+                    storeProduct.setProductId(liveProduct.getProductId());
+                    storeProduct.setNum(liveProduct.getNum());
+                    productList.add(storeProduct);
+                }
+            }
+            for (FsStoreOrderItemVO item : orderItems) {
+                FsStoreAfterSalesProductParam productParam = productList.stream().filter(p -> p.getProductId().equals(item.getProductId())).findFirst().orElse(new FsStoreAfterSalesProductParam());
+                if (productParam.getProductId() != null) {
+                    item.setIsAfterSales(1);
+                    FsStoreOrderItemScrm orderItem=new FsStoreOrderItemScrm();
+                    try {
+                        BeanUtils.copyProperties(orderItem,item);
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    } catch (InvocationTargetException e) {
+                        throw new RuntimeException(e);
+                    }
+                    fsStoreOrderItemScrmService.updateFsStoreOrderItem(orderItem);
+                }
+            }
+            //更新订单状态
+            order.setStatus(OrderInfoEnum.STATUS_NE1.getValue());
+            order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_1.getValue());
+            order.setRefundReasonWap(param.getReasons());
+            order.setRefundReasonWapExplain(param.getExplains());
+            order.setRefundReasonTime(new Date());
+            fsStoreOrderScrmService.updateFsStoreOrder(order);
+            //生成售后订单
+            FsStoreAfterSalesScrm storeAfterSales = new FsStoreAfterSalesScrm();
+            storeAfterSales.setOrderCode(param.getOrderCode());
+            storeAfterSales.setRefundAmount(param.getRefundAmount());
+            storeAfterSales.setServiceType(param.getServiceType());
+            storeAfterSales.setReasons(param.getReasons());
+            storeAfterSales.setExplains(param.getExplains());
+            storeAfterSales.setExplainImg(param.getExplainImg());
+            storeAfterSales.setStatus(AfterSalesStatusEnum.STATUS_0.getValue());
+            storeAfterSales.setSalesStatus(0);
+            storeAfterSales.setCreateTime(Timestamp.valueOf(LocalDateTime.now()));
+            storeAfterSales.setIsDel(0);
+            storeAfterSales.setUserId(userIdLong);
+            storeAfterSales.setOrderStatus(orderStatus);
+            storeAfterSales.setCompanyId(order.getCompanyId());
+            storeAfterSales.setCompanyUserId(order.getCompanyUserId());
+            storeAfterSales.setPackageJson(order.getPackageJson());
+            storeAfterSales.setIsPackage(order.getIsPackage());
+            fsStoreAfterSalesScrmMapper.insertFsStoreAfterSales(storeAfterSales);
+            //售后商品详情
+            for (FsStoreAfterSalesProductParam productParam : productList) {
+                FsStoreOrderItemVO item = orderItems.stream().filter(p -> p.getProductId().equals(productParam.getProductId())).findFirst().orElse(new FsStoreOrderItemVO());
+                FsStoreAfterSalesItemScrm storeAfterSalesItem = new FsStoreAfterSalesItemScrm();
+                storeAfterSalesItem.setStoreAfterSalesId(storeAfterSales.getId());
+                storeAfterSalesItem.setProductId(item.getProductId());
+                storeAfterSalesItem.setNum(productParam.getNum());
+                storeAfterSalesItem.setJsonInfo(item.getJsonInfo());
+                storeAfterSalesItem.setIsDel(0);
+                fsStoreAfterSalesItemScrmService.insertFsStoreAfterSalesItem(storeAfterSalesItem);
+            }
+            //操作记录
+            FsStoreAfterSalesStatusScrm storeAfterSalesStatus = new FsStoreAfterSalesStatusScrm();
+            storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
+            storeAfterSalesStatus.setChangeType(0);
+            storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_0.getDesc());
+            storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
+            FsUserScrm user=userService.selectFsUserById(userIdLong);
+            storeAfterSalesStatus.setOperator(user.getNickname());
+            fsStoreAfterSalesStatusScrmService.insertFsStoreAfterSalesStatus(storeAfterSalesStatus);
+
+            //更新OMS
+            IErpOrderService erpOrderService = getErpService();
+            ErpRefundUpdateRequest request=new ErpRefundUpdateRequest();
+            request.setTid(order.getOrderCode());
+            request.setOid(order.getOrderCode());
+            request.setRefund_state(1);
+            request.setStoreAfterSalesId(storeAfterSales.getId());
+            request.setOrderStatus(orderStatus);
+            if (StringUtils.isNotBlank(order.getExtendOrderId())){
+                BaseResponse response=erpOrderService.refundUpdateScrm(request);
+                if(response.getSuccess()){
+                    return R.ok();
+                }
+                else{
+                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+                    return R.error(response.getErrorDesc());
+                }
+            }
+            return R.ok();
+        } catch (Exception e) {
+            log.error("查询或更新历史售后数据失败", e);
+            return R.error("系统繁忙,请稍后再试!");
+        } finally {
+            // 释放锁
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
     /**
      * 查询售后记录列表
      *
@@ -995,53 +1184,53 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
     @Override
     @Transactional
     public R revoke(String userId, LiveAfterSalesRevokeParam param) throws ParseException {
-        LiveAfterSales storeAfterSales = baseMapper.selectLiveAfterSalesById(param.getId());
+        // 查询商城售后信息
+        FsStoreAfterSalesScrm storeAfterSales = fsStoreAfterSalesScrmMapper.selectFsStoreAfterSalesById(param.getId());
         if (storeAfterSales == null) {
             throw new CustomException("未查询到售后订单信息");
         }
-        if (storeAfterSales.getSalesStatus() != 0) {
+        if(storeAfterSales.getSalesStatus()!=0){
             throw new CustomException("非法操作");
         }
-        if (storeAfterSales.getStatus().equals(LiveAfterSalesStatusEnum.STATUS_2.getValue()) || storeAfterSales.getStatus().equals(LiveAfterSalesStatusEnum.STATUS_3.getValue()) || storeAfterSales.getStatus().equals(LiveAfterSalesStatusEnum.STATUS_4.getValue())) {
+        if (storeAfterSales.getStatus().equals(AfterSalesStatusEnum.STATUS_2.getValue()) || storeAfterSales.getStatus().equals(AfterSalesStatusEnum.STATUS_3.getValue())|| storeAfterSales.getStatus().equals(AfterSalesStatusEnum.STATUS_4.getValue())) {
             throw new CustomException("已发货退款单不能撤销");
         }
         //只有未发货的可以撤销,
-        if (!storeAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue())) {
+        if (!storeAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue()) ) {
             throw new CustomException("只有未发货的订单可以撤销售后");
         }
+
         storeAfterSales.setSalesStatus(1);
-        LiveOrder order = liveOrderService.selectLiveOrderByOrderId(String.valueOf(storeAfterSales.getOrderId()));
+        FsStoreOrderScrm order = fsStoreOrderScrmService.selectFsStoreOrderByOrderCode(storeAfterSales.getOrderCode());
         order.setStatus(storeAfterSales.getOrderStatus());
-        order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_0.getValue().toString());
-        liveOrderService.updateLiveOrder(order);
+        order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_0.getValue());
+        fsStoreOrderScrmService.updateFsStoreOrder(order);
+
         //操作记录
-        LiveAfterSalesLogs logs = new LiveAfterSalesLogs();
-        logs.setChangeTime(new DateTime());
-        logs.setChangeType(5);
-        FsUserScrm user = userService.selectFsUserByUserId(Long.valueOf(order.getUserId()));
-        logs.setOperator(user.getNickname());
-        logs.setStoreAfterSalesId(storeAfterSales.getId());
-        logs.setChangeMessage(OrderInfoEnum.REFUND_STATUS_1.getDesc());
-        liveAfterSalesLogsMapper.insertLiveAfterSalesLogs(logs);
-        if (storeAfterSales.getOrderStatus().equals(1)) {
-            if (StringUtils.isNotEmpty(order.getExtendOrderId())) {
+        FsStoreAfterSalesStatusScrm storeAfterSalesStatus = new FsStoreAfterSalesStatusScrm();
+        storeAfterSalesStatus.setStoreAfterSalesId(storeAfterSales.getId());
+        storeAfterSalesStatus.setChangeType(5);
+        storeAfterSalesStatus.setChangeMessage(AfterSalesStatusEnum.STATUS_5.getDesc());
+        storeAfterSalesStatus.setChangeTime(Timestamp.valueOf(LocalDateTime.now()));
+        FsUserScrm user=userService.selectFsUserById(Long.parseLong(userId));
+        storeAfterSalesStatus.setOperator(user.getNickname());
+        fsStoreAfterSalesStatusScrmService.insertFsStoreAfterSalesStatus(storeAfterSalesStatus);
+        if (storeAfterSales.getOrderStatus().equals(OrderInfoEnum.STATUS_1.getValue()) ) {
+            if(StringUtils.isNotEmpty(order.getExtendOrderId())){
                 //更新订单code
                 String orderSn = OrderCodeUtils.getOrderSn();
-                if (StringUtils.isEmpty(orderSn)) {
-                    return R.error("订单生成失败,请重试");
-                }
-                LiveOrder orderMap = new LiveOrder();
-                orderMap.setOrderId(order.getOrderId());
+                FsStoreOrderScrm orderMap=new FsStoreOrderScrm();
+                orderMap.setId(order.getId());
                 orderMap.setOrderCode(orderSn);
-                orderMap.setStatus(order.getStatus());
-                liveOrderService.updateLiveOrder(orderMap);
-                liveOrderItemService.updateFsStoreOrderCode(order.getOrderId(), orderSn);
-                liveOrderService.createOmsOrder(order.getOrderId());
+                fsStoreOrderScrmService.updateFsStoreOrder(orderMap);
+                storeAfterSales.setOrderCode(orderSn);
+                fsStoreOrderItemScrmService.updateFsStoreOrderCode(order.getId(),orderSn);
+                //生成新的订单
+                fsStoreOrderScrmService.createOmsOrder(order.getId());
             }
         }
-        baseMapper.updateLiveAfterSales(storeAfterSales);
+        fsStoreAfterSalesScrmMapper.updateFsStoreAfterSales(storeAfterSales);
         return R.ok();
-
     }
 
 
@@ -1066,6 +1255,21 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
 
     @Autowired
     private LiveAfterSalesMapper liveAfterSalesMapper;
+    
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+    
+    @Autowired
+    private IFsStoreOrderItemScrmService fsStoreOrderItemScrmService;
+    
+    @Autowired
+    private com.fs.hisStore.mapper.FsStoreAfterSalesScrmMapper fsStoreAfterSalesScrmMapper;
+    
+    @Autowired
+    private IFsStoreAfterSalesItemScrmService fsStoreAfterSalesItemScrmService;
+    
+    @Autowired
+    private IFsStoreAfterSalesStatusScrmService fsStoreAfterSalesStatusScrmService;
 
     @Override
     @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)

+ 332 - 76
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -3,12 +3,14 @@ package com.fs.live.service.impl;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.hisStore.domain.FsUserScrm;
 import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.live.domain.*;
 import com.fs.live.mapper.*;
+import com.fs.live.param.LiveDataCompanyParam;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.service.ILiveUserFavoriteService;
@@ -21,14 +23,16 @@ import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.course.domain.FsUserCompanyUser;
 import com.fs.course.mapper.FsUserCompanyUserMapper;
-import com.fs.his.domain.FsUser;
-import com.fs.his.mapper.FsUserMapper;
 import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
+import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
+import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import java.util.stream.Collectors;
+import java.util.function.BiConsumer;
 
 import com.github.pagehelper.PageInfo;
-import io.swagger.models.auth.In;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -39,9 +43,11 @@ import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 
 import static com.fs.common.constant.LiveKeysConstant.*;
 
@@ -90,7 +96,9 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Autowired
     private CompanyUserMapper companyUserMapper;
     @Autowired
-    private LiveOrderMapper liveOrderMapper;
+    private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
+    @Autowired
+    private FsStoreOrderItemScrmMapper fsStoreOrderItemScrmMapper;
 
     /* 直播大屏展示 数据接口 */
     @Override
@@ -195,10 +203,7 @@ public class LiveDataServiceImpl implements ILiveDataService {
     @Override
     public List<LiveDataListVo> exportLiveData(LiveDataParam param){
         List<Live> lives = liveMapper.listLiveData(param);
-        int total = liveMapper.listLiveDataCount(param);
-
         if (lives == null || lives.isEmpty()) {
-            LiveDataStatisticsVo statistics = new LiveDataStatisticsVo();
             return Collections.emptyList();
         }
 
@@ -210,11 +215,6 @@ public class LiveDataServiceImpl implements ILiveDataService {
         // 查询统计数据(根据live_watch_user表查询用户的在线时长,计算平均时长
         // 根据live_video的文件时长,判断用户的完课情况
         // 根据live_order查询直播间的销量额和订单数)
-        LiveDataStatisticsVo statistics = baseMapper.selectLiveDataStatistics(liveIds);
-        if (statistics == null) {
-            statistics = new LiveDataStatisticsVo();
-        }
-
         // 查询列表数据(每个直播间的详细统计数据)
         List<LiveDataListVo> liveDataList = baseMapper.selectLiveDataListByLiveIds(liveIds);
         if (liveDataList == null) {
@@ -222,6 +222,268 @@ public class LiveDataServiceImpl implements ILiveDataService {
         }
         return liveDataList;
     }
+
+    /** 时间范围最大天数(一个月) */
+    private static final int MAX_DAYS = 31;
+    /** 分段查询步长(天) */
+    private static final int SEGMENT_STEP_DAYS = 7;
+
+    @Override
+    public List<LiveDataCompanyVO> listLiveDataCompany(LiveDataCompanyParam param) {
+        Date startDate = param.getStartDate();
+        Date endDate = param.getEndDate();
+        if (startDate == null || endDate == null) {
+            return Collections.emptyList();
+        }
+
+        LocalDate start = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        LocalDate end = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        long daysBetween = ChronoUnit.DAYS.between(start, end) + 1;
+
+        // 超过31天直接返回报错,时间范围最大一个月
+        if (daysBetween > MAX_DAYS) {
+            throw new ServiceException("查询时间范围不能超过31天,当前为" + daysBetween + "天");
+        }
+
+        // 超过7天使用多线程分段查询
+        if (daysBetween > SEGMENT_STEP_DAYS) {
+            return listLiveDataCompanyBySegment(param, start, end, daysBetween);
+        }
+
+        return queryLiveDataCompanyByDateRange(param);
+    }
+
+    /**
+     * 多线程分段查询:步长7天,等待各段数据返回后合并
+     */
+    private List<LiveDataCompanyVO> listLiveDataCompanyBySegment(LiveDataCompanyParam param,
+                                                                  LocalDate start, LocalDate end, long totalDays) {
+        List<LocalDate[]> segments = new ArrayList<>();
+        LocalDate segmentStart = start;
+        while (segmentStart.isBefore(end) || segmentStart.isEqual(end)) {
+            LocalDate segmentEnd = segmentStart.plusDays(SEGMENT_STEP_DAYS - 1);
+            if (segmentEnd.isAfter(end)) {
+                segmentEnd = end;
+            }
+            segments.add(new LocalDate[]{segmentStart, segmentEnd});
+            segmentStart = segmentEnd.plusDays(1);
+        }
+
+        ExecutorService executor = Executors.newFixedThreadPool(Math.min(segments.size(), 8));
+        List<Future<List<LiveDataCompanyVO>>> futures = new ArrayList<>();
+        try {
+            for (LocalDate[] seg : segments) {
+                LiveDataCompanyParam segmentParam = new LiveDataCompanyParam();
+                segmentParam.setCompanyName(param.getCompanyName());
+                segmentParam.setCompanyIds(param.getCompanyIds());
+                segmentParam.setStartDate(Date.from(seg[0].atStartOfDay(ZoneId.systemDefault()).toInstant()));
+                segmentParam.setEndDate(Date.from(seg[1].atStartOfDay(ZoneId.systemDefault()).toInstant()));
+
+                Future<List<LiveDataCompanyVO>> future = executor.submit(() -> queryLiveDataCompanyByDateRange(segmentParam));
+                futures.add(future);
+            }
+
+            List<List<LiveDataCompanyVO>> segmentResults = new ArrayList<>();
+            for (Future<List<LiveDataCompanyVO>> future : futures) {
+                segmentResults.add(future.get(60, TimeUnit.SECONDS));
+            }
+
+            return mergeSegmentResults(segmentResults);
+        } catch (ExecutionException e) {
+            log.error("分公司直播数据分段查询异常", e);
+            throw new RuntimeException("分公司直播数据查询失败:" + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("分公司直播数据分段查询被中断", e);
+            throw new RuntimeException("分公司直播数据查询被中断");
+        } catch (TimeoutException e) {
+            log.error("分公司直播数据分段查询超时", e);
+            throw new ServiceException("查询超时,请缩小时间范围后重试");
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    /**
+     * 合并分段查询结果:按分公司汇总各分段统计数据
+     */
+    private List<LiveDataCompanyVO> mergeSegmentResults(List<List<LiveDataCompanyVO>> segmentResults) {
+        Map<Long, LiveDataCompanyVO> resultMap = new HashMap<>();
+        for (List<LiveDataCompanyVO> list : segmentResults) {
+            if (list == null) continue;
+            for (LiveDataCompanyVO source : list) {
+                if (source == null || source.getCompanyId() == null) continue;
+                LiveDataCompanyVO target = resultMap.computeIfAbsent(source.getCompanyId(), k -> {
+                    LiveDataCompanyVO vo = new LiveDataCompanyVO();
+                    vo.setCompanyId(source.getCompanyId());
+                    vo.setCompanyName(source.getCompanyName());
+                    vo.setTotalAttendanceCount(0L);
+                    vo.setTotalCompleteCount(0L);
+                    vo.setLiveAttendanceCount(0L);
+                    vo.setLiveCompleteCount(0L);
+                    vo.setReplayAttendanceCount(0L);
+                    vo.setReplayCompleteCount(0L);
+                    vo.setOrderCount(0L);
+                    vo.setOrderUserCount(0L);
+                    vo.setEmployeeCount(0L);
+                    vo.setGmv(BigDecimal.ZERO);
+                    vo.setTotalCompleteRate(0.0);
+                    vo.setLiveCompleteRate(0.0);
+                    vo.setReplayCompleteRate(0.0);
+                    return vo;
+                });
+                // 累加各分段数据
+                target.setTotalAttendanceCount((target.getTotalAttendanceCount() == null ? 0L : target.getTotalAttendanceCount())
+                        + (source.getTotalAttendanceCount() == null ? 0L : source.getTotalAttendanceCount()));
+                target.setTotalCompleteCount((target.getTotalCompleteCount() == null ? 0L : target.getTotalCompleteCount())
+                        + (source.getTotalCompleteCount() == null ? 0L : source.getTotalCompleteCount()));
+                target.setLiveAttendanceCount((target.getLiveAttendanceCount() == null ? 0L : target.getLiveAttendanceCount())
+                        + (source.getLiveAttendanceCount() == null ? 0L : source.getLiveAttendanceCount()));
+                target.setLiveCompleteCount((target.getLiveCompleteCount() == null ? 0L : target.getLiveCompleteCount())
+                        + (source.getLiveCompleteCount() == null ? 0L : source.getLiveCompleteCount()));
+                target.setReplayAttendanceCount((target.getReplayAttendanceCount() == null ? 0L : target.getReplayAttendanceCount())
+                        + (source.getReplayAttendanceCount() == null ? 0L : source.getReplayAttendanceCount()));
+                target.setReplayCompleteCount((target.getReplayCompleteCount() == null ? 0L : target.getReplayCompleteCount())
+                        + (source.getReplayCompleteCount() == null ? 0L : source.getReplayCompleteCount()));
+                target.setOrderCount((target.getOrderCount() == null ? 0L : target.getOrderCount())
+                        + (source.getOrderCount() == null ? 0L : source.getOrderCount()));
+                target.setOrderUserCount((target.getOrderUserCount() == null ? 0L : target.getOrderUserCount())
+                        + (source.getOrderUserCount() == null ? 0L : source.getOrderUserCount()));
+                target.setGmv(roundGmv((target.getGmv() == null ? BigDecimal.ZERO : target.getGmv())
+                        .add(source.getGmv() == null ? BigDecimal.ZERO : source.getGmv())));
+                if (target.getEmployeeCount() == null && source.getEmployeeCount() != null) {
+                    target.setEmployeeCount(source.getEmployeeCount());
+                }
+            }
+        }
+        for (LiveDataCompanyVO vo : resultMap.values()) {
+            long totalAttend = vo.getTotalAttendanceCount() == null ? 0L : vo.getTotalAttendanceCount();
+            long totalComplete = vo.getTotalCompleteCount() == null ? 0L : vo.getTotalCompleteCount();
+            long liveAttend = vo.getLiveAttendanceCount() == null ? 0L : vo.getLiveAttendanceCount();
+            long liveComplete = vo.getLiveCompleteCount() == null ? 0L : vo.getLiveCompleteCount();
+            long replayAttend = vo.getReplayAttendanceCount() == null ? 0L : vo.getReplayAttendanceCount();
+            long replayComplete = vo.getReplayCompleteCount() == null ? 0L : vo.getReplayCompleteCount();
+            vo.setTotalCompleteRate(roundRate(totalAttend > 0 ? totalComplete * 100.0 / totalAttend : 0.0));
+            vo.setLiveCompleteRate(roundRate(liveAttend > 0 ? liveComplete * 100.0 / liveAttend : 0.0));
+            vo.setReplayCompleteRate(roundRate(replayAttend > 0 ? replayComplete * 100.0 / replayAttend : 0.0));
+            vo.setGmv(roundGmv(vo.getGmv()));
+        }
+        return resultMap.values().stream()
+                .sorted(Comparator.comparing(vo -> Optional.ofNullable(vo.getCompanyName()).orElse("")))
+                .collect(Collectors.toList());
+    }
+
+    /** 百分比四舍五入保留2位小数 */
+    private static double roundRate(double rate) {
+        return Math.round(rate * 100.0) / 100.0;
+    }
+
+    /** GMV四舍五入保留2位小数 */
+    private static BigDecimal roundGmv(BigDecimal gmv) {
+        return gmv == null ? BigDecimal.ZERO : gmv.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 从到课/完课等查询结果中提取公司ID集合(去重)
+     */
+    @SafeVarargs
+    private final List<Long> buildCompanyIdsFromResults(List<LiveDataCompanyVO>... lists) {
+        Set<Long> companyIds = new HashSet<>();
+        for (List<LiveDataCompanyVO> list : lists) {
+            if (list == null) continue;
+            for (LiveDataCompanyVO vo : list) {
+                if (vo != null && vo.getCompanyId() != null) {
+                    companyIds.add(vo.getCompanyId());
+                }
+            }
+        }
+        return new ArrayList<>(companyIds);
+    }
+
+    /**
+     * 按日期范围查询分公司直播数据(单段,供分段或直接调用)
+     */
+    private List<LiveDataCompanyVO> queryLiveDataCompanyByDateRange(LiveDataCompanyParam param) {
+        List<Long> liveIds = baseMapper.selectLiveIdsByCompanyParam(param);
+        if (liveIds == null || liveIds.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<LiveDataCompanyVO> attendanceList = baseMapper.selectAttendanceCountByCompany(liveIds, param.getCompanyIds());
+        List<LiveDataCompanyVO> completeList = baseMapper.selectCompleteCountByCompany(liveIds, param.getCompanyIds());
+        List<LiveDataCompanyVO> liveAttendList = baseMapper.selectLiveAttendCountByCompany(liveIds, param.getCompanyIds());
+        List<LiveDataCompanyVO> liveCompleteList = baseMapper.selectLiveCompleteCountByCompany(liveIds, param.getCompanyIds());
+        List<LiveDataCompanyVO> replayAttendList = baseMapper.selectReplayAttendCountByCompany(liveIds, param.getCompanyIds());
+        List<LiveDataCompanyVO> replayCompleteList = baseMapper.selectReplayCompleteCountByCompany(liveIds, param.getCompanyIds());
+        // 从上面查询结果中提取公司ID集合,供后续GMV和员工数查询使用
+        List<Long> companyIdsFromResult = buildCompanyIdsFromResults(
+                attendanceList, completeList, liveAttendList, liveCompleteList, replayAttendList, replayCompleteList);
+        List<LiveDataCompanyVO> gmvAndOrderList = baseMapper.selectCompanyOrderAndGmv(liveIds, companyIdsFromResult);
+        List<LiveDataCompanyVO> empCountList = baseMapper.selectCompanyEmployeeCountByLiveIds(liveIds, companyIdsFromResult);
+
+        Map<Long, LiveDataCompanyVO> resultMap = new HashMap<>();
+        BiConsumer<List<LiveDataCompanyVO>, BiConsumer<LiveDataCompanyVO, LiveDataCompanyVO>> merge =
+                (vos, setter) -> {
+                    if (vos == null) return;
+                    for (LiveDataCompanyVO source : vos) {
+                        if (source == null || source.getCompanyId() == null) continue;
+                        LiveDataCompanyVO target = resultMap.computeIfAbsent(source.getCompanyId(), k -> {
+                            LiveDataCompanyVO vo = new LiveDataCompanyVO();
+                            vo.setCompanyId(source.getCompanyId());
+                            vo.setCompanyName(source.getCompanyName());
+                            vo.setTotalAttendanceCount(0L);
+                            vo.setTotalCompleteCount(0L);
+                            vo.setLiveAttendanceCount(0L);
+                            vo.setLiveCompleteCount(0L);
+                            vo.setReplayAttendanceCount(0L);
+                            vo.setReplayCompleteCount(0L);
+                            vo.setOrderCount(0L);
+                            vo.setOrderUserCount(0L);
+                            vo.setEmployeeCount(0L);
+                            vo.setGmv(BigDecimal.ZERO);
+                            vo.setTotalCompleteRate(0.0);
+                            vo.setLiveCompleteRate(0.0);
+                            vo.setReplayCompleteRate(0.0);
+                            return vo;
+                        });
+                        setter.accept(target, source);
+                    }
+                };
+
+        merge.accept(attendanceList, (t, s) -> t.setTotalAttendanceCount(s.getTotalAttendanceCount() == null ? 0L : s.getTotalAttendanceCount()));
+        merge.accept(completeList, (t, s) -> t.setTotalCompleteCount(s.getTotalCompleteCount() == null ? 0L : s.getTotalCompleteCount()));
+        merge.accept(liveAttendList, (t, s) -> t.setLiveAttendanceCount(s.getLiveAttendanceCount() == null ? 0L : s.getLiveAttendanceCount()));
+        merge.accept(liveCompleteList, (t, s) -> t.setLiveCompleteCount(s.getLiveCompleteCount() == null ? 0L : s.getLiveCompleteCount()));
+        merge.accept(replayAttendList, (t, s) -> t.setReplayAttendanceCount(s.getReplayAttendanceCount() == null ? 0L : s.getReplayAttendanceCount()));
+        merge.accept(replayCompleteList, (t, s) -> t.setReplayCompleteCount(s.getReplayCompleteCount() == null ? 0L : s.getReplayCompleteCount()));
+        merge.accept(gmvAndOrderList, (t, s) -> {
+            t.setGmv(roundGmv(s.getGmv() == null ? BigDecimal.ZERO : s.getGmv()));
+            t.setOrderCount(s.getOrderCount() == null ? 0L : s.getOrderCount());
+            t.setOrderUserCount(s.getOrderUserCount() == null ? 0L : s.getOrderUserCount());
+        });
+        merge.accept(empCountList, (t, s) -> t.setEmployeeCount(s.getEmployeeCount() == null ? 0L : s.getEmployeeCount()));
+
+        for (LiveDataCompanyVO vo : resultMap.values()) {
+            long totalAttend = vo.getTotalAttendanceCount() == null ? 0L : vo.getTotalAttendanceCount();
+            long totalComplete = vo.getTotalCompleteCount() == null ? 0L : vo.getTotalCompleteCount();
+            long liveAttend = vo.getLiveAttendanceCount() == null ? 0L : vo.getLiveAttendanceCount();
+            long liveComplete = vo.getLiveCompleteCount() == null ? 0L : vo.getLiveCompleteCount();
+            long replayAttend = vo.getReplayAttendanceCount() == null ? 0L : vo.getReplayAttendanceCount();
+            long replayComplete = vo.getReplayCompleteCount() == null ? 0L : vo.getReplayCompleteCount();
+            vo.setTotalCompleteRate(roundRate(totalAttend > 0 ? totalComplete * 100.0 / totalAttend : 0.0));
+            vo.setLiveCompleteRate(roundRate(liveAttend > 0 ? liveComplete * 100.0 / liveAttend : 0.0));
+            vo.setReplayCompleteRate(roundRate(replayAttend > 0 ? replayComplete * 100.0 / replayAttend : 0.0));
+            vo.setGmv(roundGmv(vo.getGmv()));
+        }
+
+        return resultMap.values().stream()
+                .sorted(Comparator.comparing(vo -> Optional.ofNullable(vo.getCompanyName()).orElse("")))
+                .collect(Collectors.toList());
+    }
+
+
+
+
     /**
      * 查询直播数据
      *
@@ -883,36 +1145,29 @@ public class LiveDataServiceImpl implements ILiveDataService {
             detailVo.setLivePeak(liveData.getPeakConcurrentViewers());
         }
 
-        // 查询订单数据
-        LiveOrderMapper liveOrderMapper = SpringUtils.getBean(LiveOrderMapper.class);
-        LiveOrder orderQuery = new LiveOrder();
-        orderQuery.setLiveId(liveId);
-        List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
+        // 查询订单数据(fs_store_order_scrm,order_type=2,remark=liveId)
+        FsStoreOrderScrm orderQuery = new FsStoreOrderScrm();
+        orderQuery.setOrderType(2);
+        orderQuery.setRemark(String.valueOf(liveId));
+        List<FsStoreOrderScrm> orders = fsStoreOrderScrmMapper.selectFsStoreOrderList(orderQuery);
 
         BigDecimal gmv = orders.stream()
-                .filter(o -> "1".equals(o.getIsPay()))
-                .map(LiveOrder::getPayPrice)
+                .filter(o -> o.getPaid() != null && (o.getPaid() == 1 || "1".equals(String.valueOf(o.getPaid()))))
+                .map(FsStoreOrderScrm::getPayPrice)
                 .filter(Objects::nonNull)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         detailVo.setGmv(gmv);
 
         long paidUsers = orders.stream()
-                .filter(o -> "1".equals(o.getIsPay()))
-                .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
-                .map(o -> {
-                    try {
-                        return Long.parseLong(o.getUserId());
-                    } catch (NumberFormatException e) {
-                        return null;
-                    }
-                })
-                .filter(Objects::nonNull)
+                .filter(o -> o.getPaid() != null && (o.getPaid() == 1 || "1".equals(String.valueOf(o.getPaid()))))
+                .filter(o -> o.getUserId() != null)
+                .map(FsStoreOrderScrm::getUserId)
                 .distinct()
                 .count();
         detailVo.setPaidUsers(paidUsers);
 
         long paidOrders = orders.stream()
-                .filter(o -> "1".equals(o.getIsPay()))
+                .filter(o -> o.getPaid() != null && (o.getPaid() == 1 || "1".equals(String.valueOf(o.getPaid()))))
                 .count();
         detailVo.setPaidOrders(paidOrders);
 
@@ -944,9 +1199,11 @@ public class LiveDataServiceImpl implements ILiveDataService {
         // 查询观看用户
         List<LiveWatchUser> watchUsers = liveWatchUserMapper.selectLiveWatchUserListByLiveId(liveId);
 
-        LiveOrder orderQuery = new LiveOrder();
-        orderQuery.setLiveId(liveId);
-        List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
+        // 查询订单数据(fs_store_order_scrm,order_type=2,remark=liveId)
+        FsStoreOrderScrm orderQuery = new FsStoreOrderScrm();
+        orderQuery.setOrderType(2);
+        orderQuery.setRemark(String.valueOf(liveId));
+        List<FsStoreOrderScrm> orders = fsStoreOrderScrmMapper.selectFsStoreOrderList(orderQuery);
 
 
         // 按用户ID分组统计
@@ -998,28 +1255,15 @@ public class LiveDataServiceImpl implements ILiveDataService {
             }
         }
 
-        // 统计订单数据
-        Map<Long, List<LiveOrder>> userOrdersMap = orders.stream()
-                .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
-                .filter(o -> {
-                    try {
-                        Long.parseLong(o.getUserId());
-                        return true;
-                    } catch (NumberFormatException e) {
-                        return false;
-                    }
-                })
-                .collect(Collectors.groupingBy(o -> {
-                    try {
-                        return Long.parseLong(o.getUserId());
-                    } catch (NumberFormatException e) {
-                        return 0L;
-                    }
-                }));
+        // 统计订单数据(仅已支付订单)
+        Map<Long, List<FsStoreOrderScrm>> userOrdersMap = orders.stream()
+                .filter(o -> o.getUserId() != null)
+                .filter(o -> o.getPaid() != null && (o.getPaid() == 1 || "1".equals(String.valueOf(o.getPaid()))))
+                .collect(Collectors.groupingBy(FsStoreOrderScrm::getUserId));
 
-        for (Map.Entry<Long, List<LiveOrder>> entry : userOrdersMap.entrySet()) {
+        for (Map.Entry<Long, List<FsStoreOrderScrm>> entry : userOrdersMap.entrySet()) {
             Long userId = entry.getKey();
-            List<LiveOrder> userOrders = entry.getValue();
+            List<FsStoreOrderScrm> userOrders = entry.getValue();
 
             LiveUserDetailVo userDetail = userDetailMap.computeIfAbsent(userId, k -> {
                 LiveUserDetailVo vo = new LiveUserDetailVo();
@@ -1054,8 +1298,7 @@ public class LiveDataServiceImpl implements ILiveDataService {
 
             userDetail.setOrderCount((long) userOrders.size());
             BigDecimal orderAmount = userOrders.stream()
-                    .filter(o -> "1".equals(o.getIsPay()))
-                    .map(LiveOrder::getPayPrice)
+                    .map(FsStoreOrderScrm::getPayPrice)
                     .filter(Objects::nonNull)
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
             userDetail.setOrderAmount(orderAmount);
@@ -1073,37 +1316,50 @@ public class LiveDataServiceImpl implements ILiveDataService {
     }
 
     /**
-     * 查询单品销量统计
+     * 查询单品销量统计(从 fs_store_order_scrm,order_type=2,remark=liveId,按订单明细汇总)
      */
     private List<ProductSalesVo> getProductSalesList(Long liveId) {
-
-        List<LiveOrder> orders = liveOrderMapper.selectOrderByLiveId(liveId);
+        FsStoreOrderScrm orderQuery = new FsStoreOrderScrm();
+        orderQuery.setOrderType(2);
+        orderQuery.setRemark(String.valueOf(liveId));
+        List<FsStoreOrderScrm> orders = fsStoreOrderScrmMapper.selectFsStoreOrderList(orderQuery);
 
         // 按商品ID分组统计
         Map<Long, ProductSalesVo> productSalesMap = new HashMap<>();
 
-        for (LiveOrder order : orders) {
-            if (!"1".equals(order.getIsPay()) || order.getProductId() == null) {
+        for (FsStoreOrderScrm order : orders) {
+            if (order.getPaid() == null || (order.getPaid() != 1 && !"1".equals(String.valueOf(order.getPaid())))) {
+                continue;
+            }
+            List<FsStoreOrderItemVO> items = fsStoreOrderItemScrmMapper.selectFsStoreOrderItemListByOrderId(order.getId());
+            if (items == null || items.isEmpty()) {
                 continue;
             }
+            long totalNum = order.getTotalNum() != null && order.getTotalNum() > 0 ? order.getTotalNum() : 1;
+            BigDecimal orderPayPrice = order.getPayPrice() != null ? order.getPayPrice() : BigDecimal.ZERO;
 
-            ProductSalesVo productSales = productSalesMap.computeIfAbsent(order.getProductId(), k -> {
-                ProductSalesVo vo = new ProductSalesVo();
-                vo.setProductId(order.getProductId());
-                // 查询商品名称
-                FsStoreProductScrmMapper productMapper = SpringUtils.getBean(FsStoreProductScrmMapper.class);
-                FsStoreProductScrm product = productMapper.selectFsStoreProductById(order.getProductId());
-                if (product != null) {
-                    vo.setProductName(product.getProductName());
-                } else {
-                    vo.setProductName("未知商品");
+            for (FsStoreOrderItemVO item : items) {
+                if (item.getProductId() == null) {
+                    continue;
                 }
-                return vo;
-            });
+                long itemNum = item.getNum() != null ? item.getNum() : 0;
+                BigDecimal itemAmount = totalNum > 0 ? orderPayPrice.multiply(BigDecimal.valueOf(itemNum)).divide(BigDecimal.valueOf(totalNum), 2, RoundingMode.HALF_UP) : BigDecimal.ZERO;
+
+                ProductSalesVo productSales = productSalesMap.computeIfAbsent(item.getProductId(), k -> {
+                    ProductSalesVo vo = new ProductSalesVo();
+                    vo.setProductId(item.getProductId());
+                    FsStoreProductScrmMapper productMapper = SpringUtils.getBean(FsStoreProductScrmMapper.class);
+                    FsStoreProductScrm product = productMapper.selectFsStoreProductById(item.getProductId());
+                    if (product != null) {
+                        vo.setProductName(product.getProductName());
+                    } else {
+                        vo.setProductName("未知商品");
+                    }
+                    return vo;
+                });
 
-            productSales.setSalesCount(productSales.getSalesCount() + 1);
-            if (order.getPayPrice() != null) {
-                productSales.setSalesAmount(productSales.getSalesAmount().add(order.getPayPrice()));
+                productSales.setSalesCount(productSales.getSalesCount() + itemNum);
+                productSales.setSalesAmount(productSales.getSalesAmount().add(itemAmount));
             }
         }
 

+ 478 - 27
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -42,6 +42,7 @@ import com.fs.common.event.TemplateListenEnum;
 import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.*;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.Company;
@@ -73,14 +74,23 @@ import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.config.StoreConfig;
+import com.fs.hisStore.constants.StoreConstants;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.dto.*;
 import com.fs.hisStore.enums.*;
+import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
+import com.fs.common.utils.SnowflakeUtil;
+import com.fs.huifuPay.sdk.opps.core.utils.HuiFuUtils;
+import com.fs.his.domain.MerchantAppConfig;
+import com.fs.his.domain.FsPayConfig;
+import org.springframework.aop.framework.AopContext;
 import com.fs.hisStore.service.IFsStoreProductPurchaseLimitScrmService;
 import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
 import com.fs.hisStore.vo.*;
@@ -296,8 +306,27 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     private FSSysConfig sysConfig;
     @Autowired
     private LiveOrderMapper liveOrderMapper;
+    
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
+    
+    @Autowired
+    private FsStoreOrderItemScrmMapper fsStoreOrderItemScrmMapper;
+    
+    @Autowired
+    private IFsStoreOrderStatusScrmService orderStatusService;
+    
     @Autowired
     private ILiveGoodsService liveGoodsService;
+    
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+    
+    @Autowired
+    private FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
+    
+    @Autowired
+    private CloudHostProper cloudHostProper;
 
     @Autowired
     private ILiveUserFirstEntryService liveUserFirstEntryService;
@@ -2171,7 +2200,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
         liveOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
 
-        String orderSn = OrderCodeUtils.getOrderSn();
+        String orderSn = SnowflakeUtil.nextIdStr();
         log.info("订单生成:" + orderSn);
         liveOrder.setOrderCode(orderSn);
         BigDecimal totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
@@ -3091,33 +3120,31 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     public List<LiveGoodsVo> selectLiveOrderItemList(String orderId) {
         LiveOrder liveOrder = baseMapper.selectLiveOrderByOrderId(orderId);
         Asserts.notNull(liveOrder, String.format("该订单 %s 没有找到!", orderId));
+        List<LiveOrderItem> liveOrderItems = liveOrderItemMapper.selectCheckedByOrderId(Long.valueOf(orderId));
+        // 从订单明细的jsonInfo中提取barCode
+        Set<String> orderItemBarCodes = new HashSet<>();
+        for (LiveOrderItem item : liveOrderItems) {
+            if (StrUtil.isNotBlank(item.getJsonInfo())) {
+                try {
+                    com.fs.his.domain.FsStoreProduct cartDTO = JSONUtil.toBean(item.getJsonInfo(), com.fs.his.domain.FsStoreProduct.class);
+                    if (StrUtil.isNotBlank(cartDTO.getBarCode())) {
+                        orderItemBarCodes.add(cartDTO.getBarCode());
+                    }
+                } catch (Exception ignored) {
+                }
+            }
+        }
         List<LiveGoodsVo> liveGoodsVos = liveGoodsMapper.selectProductListByOrder(liveOrder);
 
         if (CollectionUtils.isEmpty(liveGoodsVos)) {
             return liveGoodsVos;
         }
-        List<String> productIds = liveGoodsVos.stream()
-                .map(LiveGoodsVo::getProductId)
-                .filter(Objects::nonNull)
-                .map(String::valueOf)
-                .distinct()
-                .collect(Collectors.toList());
-        if (CollectionUtils.isEmpty(productIds)) {
-            return liveGoodsVos;
+        // 与订单明细的barCode做交集,只返回barCode匹配的商品;若订单明细无法解析出barCode则返回全部
+        if (!orderItemBarCodes.isEmpty()) {
+            liveGoodsVos = liveGoodsVos.stream()
+                    .filter(vo -> vo.getBarCode() != null && orderItemBarCodes.contains(vo.getBarCode()))
+                    .collect(Collectors.toList());
         }
-
-//        Map<Long, FsStoreProductScrm> productWarehouseMap =
-//                fsStoreProductBaseMapper.selectWarehouseCodeByProductIds(productIds);
-//
-//        liveGoodsVos.forEach(goodsVo -> {
-//            Long productId = goodsVo.getProductId();
-//            if (productId != null) {
-//                FsStoreProductScrm storeProduct = productWarehouseMap.get(productId);
-//                if (storeProduct != null) {
-//                    goodsVo.setWarehouseCode(storeProduct.getWarehouseCode());
-//                }
-//            }
-//        });
         return liveGoodsVos;
 
     }
@@ -3155,7 +3182,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
 
 
-            String payCode = OrderCodeUtils.getOrderSn();
+            String payCode = SnowflakeUtil.nextIdStr();
             if (StringUtils.isEmpty(payCode)) {
                 return R.error("订单生成失败,请重试");
             }
@@ -3239,6 +3266,183 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         return baseMapper.selectLiveOrderListZmNew(liveOrder);
     }
 
+    @Override
+    public R handleStoreOrderPay(LiveOrderPayParam param) {
+        // 直播订单已合并到商城订单表:改为查询商城订单表
+        FsStoreOrderScrm order = fsStoreOrderScrmService.selectFsStoreOrderById(param.getOrderId());
+        if(order == null){
+            return R.error("订单不存在");
+        }
+        if(order.getStatus() != OrderInfoEnum.STATUS_0.getValue()){
+            return R.error("订单状态不正确");
+        }
+        String orderId = redisCache.getCacheObject("isPaying:" + param.getOrderId());
+        if(StringUtils.isNotEmpty(orderId) && orderId.equals(order.getId().toString())){
+            return R.error("正在支付中...");
+        }
+
+        FsUserScrm user = userService.selectFsUserById(order.getUserId());
+        if(user != null){
+            //已改价处理
+            if(order.getIsEditMoney() != null && order.getIsEditMoney() == 1){
+                //改过价不做处理
+            }
+            else{
+                String config = configService.selectConfigByKey("his.store");
+                com.fs.store.config.StoreConfig storeConfig = JSONUtil.toBean(config, com.fs.store.config.StoreConfig.class);
+                if(param.getPayType().equals(1)){
+                    order.setPayType("1");
+                    order.setPayMoney(order.getPayPrice());
+                    if(!"广州郑多燕".equals(cloudHostProper.getCompanyName())){
+                        order.setPayDelivery(BigDecimal.ZERO);
+                    }else {
+                        // 郑多燕单独设置支付类型
+                        order.setPayType(order.getPayPrice().compareTo(order.getTotalPrice()) == 0 ?"1":"5");
+                        log.info("支付------------实际支付金额和总金额:{},{},", order.getPayPrice(), order.getTotalPrice());
+                    }
+                }
+                else if(param.getPayType().equals(2)){
+                    order.setPayType("2");
+                    BigDecimal payMoney = order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate())).divide(new BigDecimal(100));
+                    payMoney = new BigDecimal(payMoney.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
+                    // 如果小程序需要支付的金额小于0.01元,不能走物流代收,让走货到付款 xgb
+                    if (payMoney.compareTo(new BigDecimal("0.01")) < 0) {
+                        return R.error("物流代收计算支付金额为0,不允许选择物流代收");
+                    }
+                    order.setPayDelivery(order.getPayPrice().subtract(payMoney));
+                    order.setPayMoney(payMoney);
+                }
+                else if(param.getPayType().equals(3)){
+                    //货到付款
+                    order.setPayType("3");
+                    BigDecimal amount = redisCache.getCacheObject("orderAmount:" + order.getId());
+                    BigDecimal payMoney = BigDecimal.ZERO;
+                    if (amount != null){
+                        payMoney = amount;
+                    }
+                    //运费
+                    BigDecimal payPostage = order.getPayPostage();
+                    if (payPostage == null || payPostage.compareTo(BigDecimal.ZERO) <= 0){
+                        payPostage = storeConfig.getPayPostage();
+                        if (payPostage == null){
+                            payPostage = BigDecimal.ZERO;
+                        }
+                        order.setPayPrice(order.getPayPrice().add(payPostage));
+                    }
+                    order.setPayPostage(payPostage);
+                    payMoney = payMoney.add(payPostage);
+                    order.setPayMoney(payMoney);
+                    order.setPayDelivery(order.getPayPrice().subtract(payMoney));
+                }
+                fsStoreOrderScrmService.updateFsStoreOrder(order);
+            }
+            String payCode = SnowflakeUtil.nextIdStr();
+            if((order.getPayType().equals("1") || order.getPayType().equals("2") || order.getPayType().equals("3") || order.getPayType().equals("5")) && order.getPayMoney().compareTo(new BigDecimal(0)) > 0){
+                if (StringUtils.isBlank(param.getAppId())) {
+                    throw new IllegalArgumentException("appId不能为空");
+                }
+                FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigMapper.selectCoursePlaySourceConfigByAppId(param.getAppId());
+                if (fsCoursePlaySourceConfig == null) {
+                    throw new CustomException("未找到appId对应的小程序配置: " + param.getAppId());
+                }
+                Long merchantConfigId = fsCoursePlaySourceConfig.getMerchantConfigId();
+                if (merchantConfigId == null || merchantConfigId <= 0) {
+                    throw new CustomException("小程序没有配置商户信息");
+                }
+                MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+                FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+                FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
+                storePayment.setCompanyId(order.getCompanyId());
+                storePayment.setCompanyUserId(order.getCompanyUserId());
+                storePayment.setPayMode(merchantAppConfig.getMerchantType());
+                storePayment.setStatus(0);
+                storePayment.setPayCode(payCode);
+                storePayment.setPayMoney(order.getPayMoney());
+                storePayment.setCreateTime(new Date());
+                storePayment.setPayTypeCode("weixin");
+                storePayment.setBusinessType(2);
+                storePayment.setRemark("直播订单支付");
+                storePayment.setOpenId(user.getRealName());
+                storePayment.setUserId(user.getUserId());
+                storePayment.setBusinessOrderId(order.getId().toString());
+                storePayment.setOrderId(order.getId());
+                storePayment.setAppId(fsCoursePlaySourceConfig.getAppid() == null ? "" : fsCoursePlaySourceConfig.getAppid());
+                storePayment.setMerConfigId(merchantAppConfig.getId());
+                storePayment.setBusinessCode(order.getOrderCode());
+                fsStorePaymentScrmMapper.insertFsStorePayment(storePayment);
+
+                if (merchantAppConfig.getMerchantType().equals("hf")){
+                    HuiFuCreateOrder o = new HuiFuCreateOrder();
+                    o.setTradeType("T_MINIAPP");
+                    o.setOpenid(user.getMaOpenId());
+                    o.setReqSeqId("live-" + storePayment.getPayCode());
+                    o.setTransAmt(storePayment.getPayMoney().toString());
+                    o.setGoodsDesc("直播订单支付");
+                    o.setAppId(param.getAppId());
+                    try {
+                        HuiFuUtils.doDiv(o, order.getCompanyId(), storePayment.getMerConfigId());
+                        //存储分账明细
+                        HuiFuUtils.saveDivItem(o, order.getOrderCode(), storePayment.getPayCode());
+                    } catch (Exception e) {
+                        log.error("-------------分账出错:{}", e.getMessage());
+                    }
+                    HuifuCreateOrderResult result = huiFuService.createOrder(o);
+                    if(result.getResp_code() != null && (result.getResp_code().equals("00000000") || result.getResp_code().equals("00000100"))){
+                        FsStorePaymentScrm mt = new FsStorePaymentScrm();
+                        mt.setPaymentId(storePayment.getPaymentId());
+                        mt.setTradeNo(result.getHf_seq_id());
+                        mt.setAppId(param.getAppId());
+                        mt.setBusinessCode(order.getOrderCode());
+                        fsStorePaymentScrmMapper.updateFsStorePayment(mt);
+                        redisCache.setCacheObject("isPaying:" + order.getId(), order.getId().toString(), 1, TimeUnit.MINUTES);
+                        Map<String, Object> resultMap = JSON.parseObject(result.getPay_info(), new TypeReference<Map<String, Object>>() {});
+                        String s = (String) resultMap.get("package");
+                        resultMap.put("packageValue", s);
+                        return R.ok().put("payType", param.getPayType()).put("result", resultMap);
+                    }
+                    else{
+                        return R.error(result.getResp_desc());
+                    }
+                }else if (merchantAppConfig.getMerchantType().equals("wx")){
+                    WxPayConfig payConfig = new WxPayConfig();
+                    payConfig.setAppId(fsCoursePlaySourceConfig.getAppid());
+                    payConfig.setMchId(fsPayConfig.getWxMchId());
+                    payConfig.setMchKey(fsPayConfig.getWxMchKey());
+                    payConfig.setSubAppId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                    payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+                    payConfig.setKeyPath(fsPayConfig.getKeyPath());
+                    payConfig.setNotifyUrl(fsPayConfig.getNotifyUrlScrm());
+                    wxPayService.setConfig(payConfig);
+                    WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+                    orderRequest.setOpenid(user.getMaOpenId());//公众号支付提供用户openid
+                    orderRequest.setBody("直播订单支付");
+                    orderRequest.setOutTradeNo("live-" + storePayment.getPayCode());
+                    orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+                    orderRequest.setTradeType("JSAPI");
+                    orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+                    //调用统一下单接口,获取"预支付交易会话标识"
+                    try {
+                        WxPayMpOrderResult orderResult = wxPayService.createOrder(orderRequest);
+                        return R.ok().put("result", orderResult).put("type", "wx").put("isPay", 0).put("payType", param.getPayType());
+                    } catch (WxPayException e) {
+                        e.printStackTrace();
+                        throw new CustomException("支付失败" + e.getMessage());
+                    }
+                }
+            }
+            else if(order.getPayType().equals("3") && order.getPayMoney().compareTo(new BigDecimal(0)) <= 0){
+                //货到付款
+                IFsStoreOrderScrmService fsStoreOrderScrmService = (IFsStoreOrderScrmService) AopContext.currentProxy();
+                fsStoreOrderScrmService.payConfirm(2, order.getId(), null, null, null, null);
+                return R.ok().put("payType", param.getPayType());
+            }
+            return R.error();
+        }
+        else{
+            return R.error("用户OPENID不存在");
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
     public R handleLiveOrderPay(LiveOrderPayParam param) {
@@ -3334,7 +3538,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 }
                 baseMapper.updateLiveOrder(order);
             }
-            String payCode = OrderCodeUtils.getOrderSn();
+            String payCode = SnowflakeUtil.nextIdStr();
 //            order.setOrderCode(orderCode);
 //            if(order.getPayType().equals("1")||order.getPayType().equals("2")){
             if ((order.getPayType().equals("1") || order.getPayType().equals("2") || order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0)) > 0) {
@@ -3768,6 +3972,253 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         });
     }
 
+
+    @Override
+    public R createStoreOrder(LiveOrder liveOrder) {
+        String orderKey= redisCache.getCacheObject("orderKey:"+liveOrder.getOrderKey());
+        if (StringUtils.isEmpty(orderKey)) {
+            return R.error("订单已过期");
+        }
+        if (liveOrder.getLiveId() == null) return R.error("直播ID不能为空");
+        if (liveOrder.getProductId() == null) return R.error("购物商品ID不能为空");
+        if (liveOrder.getUserName() == null) return R.error("用户名不能为空");
+        if (liveOrder.getUserPhone() == null) return R.error("用户手机号不能为空");
+        if (liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
+        if (liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
+        LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
+        if (goods == null) return R.error("当前商品不存在");
+        String configJson = configService.selectConfigByKey("his.store");
+        if (org.apache.commons.lang3.StringUtils.isNotEmpty(configJson)) {
+            com.fs.hisStore.config.StoreConfig config = com.alibaba.fastjson.JSON.parseObject(configJson, com.fs.hisStore.config.StoreConfig.class);
+            if (config != null && Boolean.TRUE.equals(config.getCheckStock())) {
+                CompletableFuture<Boolean> completableFuture = stockDeductService.deductStockAsync(liveOrder.getProductId(), liveOrder.getLiveId(), Integer.parseInt(liveOrder.getTotalNum()), Long.parseLong(liveOrder.getUserId()));
+                try {
+                    log.info("{}, 商品REDIS 库存扣减成功!", goods.getLiveId());
+                    if (!completableFuture.get()) {
+                        return R.error("抱歉,这款商品已被抢光,暂时无库存~");
+                    }
+                    log.info("{}, 商品REDIS 库存扣减成功!", goods.getLiveId());
+                } catch (InterruptedException e) {
+                    log.error("高并发处理失败", e);
+                    return R.error("订单创建失败:" + e.getMessage());
+                } catch (ExecutionException e) {
+                    log.error("高并发处理失败", e);
+                    return R.error("订单创建失败:" + e.getMessage());
+                }
+                if (goods.getStock() == null) return R.error("直播间商品库存不足");
+            }
+        }
+        Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
+        if (live == null) return R.error("当前直播不存在");
+        FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
+        if (fsStoreProduct == null) return R.error("商品不存在,购买失败");
+        if (fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
+        if (!"1".equals(fsStoreProduct.getIsAudit())) return R.error("商品已下架,购买失败");
+        if (liveOrder.getTotalNum() == null || StringUtils.isEmpty(liveOrder.getTotalNum())) liveOrder.setTotalNum("1");
+        FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(liveOrder.getAttrValueId());
+//        if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || attrValue.getStock() < Integer.parseInt(liveOrder.getTotalNum())){
+//            return R.error("抱歉,这款商品已被抢光,暂时无库存~");
+//        }
+
+
+        // 检查限购
+        Long userId = Long.parseLong(liveOrder.getUserId());
+        Integer purchaseNum = Integer.parseInt(liveOrder.getTotalNum());
+        checkPurchaseLimitForLiveOrder(userId, liveOrder.getProductId(), purchaseNum);
+        LiveGoodsUploadMqVo vo = LiveGoodsUploadMqVo.builder().goodsId(goods.getGoodsId()).goodsNum(Integer.parseInt(liveOrder.getTotalNum())).build();
+        try {
+            log.info("订单提交MQ:{}", vo);
+            rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
+        }catch (Exception e){
+            log.error("更新库存失败!{}", vo, e);
+        }
+        // 复制到商城订单
+        FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
+        copyLiveToStore(liveOrder, storeOrder);
+
+        //判断是否是三种特定产品
+        String storeHouseCode;
+        if (fsStoreProduct.getProductId() != null && (fsStoreProduct.getProductId().equals(3168L)
+                || fsStoreProduct.getProductId().equals(3184L)
+                || fsStoreProduct.getProductId().equals(3185L))) {
+            storeHouseCode = "YDSP001";
+        } else {
+            storeHouseCode = "CQDS001";
+        }
+        storeOrder.setStoreHouseCode(storeHouseCode);
+
+        LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveOrder.getLiveId(), Long.parseLong(liveOrder.getUserId()));
+        if (ObjectUtil.isNotEmpty(liveUserFirstEntry)) {
+            storeOrder.setCompanyId(liveUserFirstEntry.getCompanyId());
+            storeOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
+            storeOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
+        }
+
+//        String orderSn = SnowflakeUtil.nextIdStr();
+        String orderSn = SnowflakeUtil.nextIdStr();
+        log.info("订单生成:" + orderSn);
+        storeOrder.setOrderCode(orderSn);
+        // 注意:bizOrderType 字段需要在 FsStoreOrderScrm 实体类中添加
+        // storeOrder.setBizOrderType(1); // 设置为直播订单
+        
+        BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
+        if (attrValue != null) {
+            payPrice = attrValue.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
+        }
+        
+        // 计算运费
+        BigDecimal deliveryMoney = handleDeliveryMoney(liveOrder);
+        if (deliveryMoney.compareTo(BigDecimal.valueOf(-1)) == 0) {
+            return R.error("偏远地区暂不可购买");
+        }
+        payPrice = payPrice.add(deliveryMoney);
+        BigDecimal discountMoney = BigDecimal.ZERO;
+
+        //优惠券处理
+        if (liveOrder.getCouponUserId() != null) {
+            LiveCouponUser couponUser = liveCouponUserService.selectLiveCouponUserById(liveOrder.getCouponUserId());
+            if (couponUser != null && couponUser.getStatus() == 0) {
+                if (!couponUser.getUserId().toString().equals(liveOrder.getUserId())) {
+                    return R.error("非法操作");
+                }
+                if (couponUser.getUseMinPrice().compareTo(payPrice) < 1) {
+                    discountMoney = couponUser.getCouponPrice();
+                    storeOrder.setCouponId(couponUser.getId());
+                    storeOrder.setCouponPrice(couponUser.getCouponPrice());
+                    //更新优惠券状态
+                    couponUser.setStatus(1);
+                    couponUser.setUseTime(new Date());
+                    liveCouponUserService.updateLiveCouponUser(couponUser);
+                }
+            }
+        }
+        
+        // 设置商城订单字段(按照 createOrder 的逻辑)
+        storeOrder.setUserId(Long.parseLong(liveOrder.getUserId()));
+        storeOrder.setTotalNum(Long.parseLong(liveOrder.getTotalNum()));
+        storeOrder.setTotalPrice(payPrice);
+        storeOrder.setTotalPostage(deliveryMoney);
+        storeOrder.setPayPostage(deliveryMoney);
+        storeOrder.setPayDelivery(deliveryMoney);
+        storeOrder.setCouponPrice(discountMoney);
+        storeOrder.setDeductionPrice(BigDecimal.ZERO);
+        storeOrder.setPaid(0);
+        storeOrder.setPayType(StringUtils.isEmpty(liveOrder.getPayType()) ? "1" :liveOrder.getPayType());
+        storeOrder.setUseIntegral(BigDecimal.ZERO);
+        storeOrder.setBackIntegral(BigDecimal.ZERO);
+        storeOrder.setGainIntegral(BigDecimal.ZERO);
+        storeOrder.setCost(BigDecimal.ZERO);
+        storeOrder.setIsChannel(1);
+        storeOrder.setShippingType(1);
+        storeOrder.setCreateTime(new Date());
+        storeOrder.setIsPrescribe(0);
+        storeOrder.setOrderType(2);
+        
+        // 获取配置
+        String json = configService.selectConfigByKey("store.config");
+        StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+        if (config != null && config.getServiceFee() != null) {
+            storeOrder.setServiceFee(config.getServiceFee());
+        }
+        
+        // 设置支付金额
+        storeOrder.setPayPrice(payPrice.subtract(discountMoney));
+        storeOrder.setPayMoney(storeOrder.getPayPrice());
+        
+        // 设置订单状态
+        storeOrder.setStatus(0); // 待支付
+        
+        try {
+            // 插入商城订单
+            Integer flag = fsStoreOrderScrmMapper.insertFsStoreOrder(storeOrder);
+            if (flag == 0) {
+                return R.error("订单创建失败");
+            }
+            
+            // 保存订单明细
+            FsStoreCartDTO fsStoreCartDTO = new FsStoreCartDTO();
+            fsStoreCartDTO.setProductId(fsStoreProduct.getProductId());
+            fsStoreCartDTO.setPrice(attrValue != null ? attrValue.getPrice() : fsStoreProduct.getPrice());
+            fsStoreCartDTO.setSku(attrValue != null ? (attrValue.getSku() != null ? attrValue.getSku() : "") : "");
+            fsStoreCartDTO.setProductName(fsStoreProduct.getProductName());
+            fsStoreCartDTO.setNum(Integer.parseInt(liveOrder.getTotalNum()));
+            fsStoreCartDTO.setImage(fsStoreProduct.getImage());
+
+            if (attrValue != null) {
+                fsStoreCartDTO.setBarCode(attrValue.getBarCode());
+                fsStoreCartDTO.setGroupBarCode(attrValue.getGroupBarCode());
+            }
+            
+            FsStoreOrderItemScrm orderItem = new FsStoreOrderItemScrm();
+            orderItem.setOrderId(storeOrder.getId());
+            orderItem.setOrderCode(orderSn);
+            orderItem.setProductId(fsStoreProduct.getProductId());
+            orderItem.setProductAttrValueId(attrValue != null ? attrValue.getId() : null);
+            orderItem.setJsonInfo(JSONUtil.toJsonStr(fsStoreCartDTO));
+            orderItem.setNum(Integer.parseInt(liveOrder.getTotalNum()));
+            orderItem.setIsAfterSales(0);
+            orderItem.setIsPrescribe(0);
+            fsStoreOrderItemScrmMapper.insertFsStoreOrderItem(orderItem);
+            
+            // 更新订单的 itemJson
+            List<FsStoreOrderItemScrm> listOrderItem = new ArrayList<>();
+            listOrderItem.add(orderItem);
+            String itemJson = JSONUtil.toJsonStr(listOrderItem);
+            storeOrder.setItemJson(itemJson);
+            fsStoreOrderScrmMapper.updateFsStoreOrder(storeOrder);
+            
+            // 添加订单日志
+            orderStatusService.create(storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),
+                    OrderLogEnum.CREATE_ORDER.getDesc());
+            
+            // 设置直播订单的 orderId 为商城订单的 id
+
+            // 加入redis,24小时自动取消
+            String redisKey = StoreConstants.REDIS_ORDER_OUTTIME_UNPAY + storeOrder.getId();
+            if (config != null && config.getUnPayTime() != null && config.getUnPayTime() > 0) {
+                redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+            } else {
+                redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+            }
+            
+            redisCache.deleteObject("orderKey:" + liveOrder.getOrderKey());
+            //添加支付到期时间
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(storeOrder.getCreateTime());
+            if (config != null && config.getUnPayTime() != null) {
+                calendar.add(Calendar.MINUTE, config.getUnPayTime());
+            }
+            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String payLimitTime = format.format(calendar.getTime());
+            return R.ok("下单成功").put("order", storeOrder).put("payLimitTime", payLimitTime);
+        } catch (Exception e) {
+            // 异常处理
+            log.error("订单创建失败", e);
+            return R.error("订单创建失败:" + e.getMessage());
+        }
+    }
+
+
+
+    private void copyLiveToStore(LiveOrder liveOrder, FsStoreOrderScrm storeOrder) {
+        // 复制基本信息
+        if (liveOrder.getUserId() != null) {
+            storeOrder.setUserId(Long.parseLong(liveOrder.getUserId()));
+        }
+        storeOrder.setRealName(liveOrder.getUserName());
+        storeOrder.setUserPhone(liveOrder.getUserPhone());
+        storeOrder.setUserAddress(liveOrder.getUserAddress());
+        if (liveOrder.getTotalNum() != null) {
+            storeOrder.setTotalNum(Long.parseLong(liveOrder.getTotalNum()));
+        }
+        storeOrder.setCompanyId(liveOrder.getCompanyId());
+        storeOrder.setCompanyUserId(liveOrder.getCompanyUserId());
+        storeOrder.setTuiUserId(liveOrder.getTuiUserId());
+        storeOrder.setStoreId(liveOrder.getStoreId());
+        storeOrder.setMark(liveOrder.getRemark());
+        storeOrder.setRemark(String.valueOf(liveOrder.getLiveId()));
+    }
+
     public void deStockIncSale(List<FsStoreCartQueryVO> cartInfo) {
         for (FsStoreCartQueryVO storeCartVO : cartInfo) {
             fsStoreProductService.decProductStock(storeCartVO.getProductId(),
@@ -3879,7 +4330,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             liveOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
             liveOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
         }
-        String orderSn = OrderCodeUtils.getOrderSn();
+        String orderSn = SnowflakeUtil.nextIdStr();
 //        String orderSn = "123"; // todo yhq
         log.info("订单生成:" + orderSn);
         liveOrder.setOrderCode(orderSn);
@@ -4045,7 +4496,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             liveOrder.setCompanyUserId(liveUserFirstEntry.getCompanyUserId());
             liveOrder.setTuiUserId(liveUserFirstEntry.getCompanyUserId());
         }
-        String orderSn = OrderCodeUtils.getOrderSn();
+        String orderSn = SnowflakeUtil.nextIdStr();
 //        String orderSn = "123"; // todo yhq
         log.info("订单生成:" + orderSn);
         liveOrder.setOrderCode(orderSn);
@@ -4254,7 +4705,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
     @Override
     public R confirmOrder(LiveOrderConfirmParam param) {
-        String uuid = OrderCodeUtils.getOrderSn();
+        String uuid = SnowflakeUtil.nextIdStr();
         redisCache.setCacheObject("orderKey:" + uuid, uuid, 200, TimeUnit.MINUTES);
         return R.ok().put("orderKey", uuid);
     }

+ 3 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -381,7 +381,9 @@ public class LiveServiceImpl implements ILiveService
 
     @Override
     public Integer updateLiveIsAudit(Live live) {
-        return baseMapper.updateLiveIsAudit(live);
+        Integer i = baseMapper.updateLiveIsAudit(live);
+        clearLiveCache(live.getLiveId());
+        return i;
     }
 
 

+ 62 - 0
fs-service/src/main/java/com/fs/live/vo/LiveDataCompanyVO.java

@@ -0,0 +1,62 @@
+package com.fs.live.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 分公司直播数据统计VO
+ */
+@Data
+public class LiveDataCompanyVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 分公司ID,可能为空(总台) */
+    private Long companyId;
+
+    @Excel(name = "分公司名称")
+    private String companyName;
+
+    @Excel(name = "总到课人数(去重)")
+    private Long totalAttendanceCount;
+
+    @Excel(name = "总完课人数")
+    private Long totalCompleteCount;
+
+    /** 总完课率(百分比 0-100) */
+    @Excel(name = "总完课率")
+    private Double totalCompleteRate;
+
+    @Excel(name = "直播课人数(去重)")
+    private Long liveAttendanceCount;
+
+    @Excel(name = "直播完课人数")
+    private Long liveCompleteCount;
+
+    @Excel(name = "直播完课率")
+    private Double liveCompleteRate;
+
+    @Excel(name = "回放课人数(去重)")
+    private Long replayAttendanceCount;
+
+    @Excel(name = "回放完课人数(去重)")
+    private Long replayCompleteCount;
+
+    @Excel(name = "回放完课率")
+    private Double replayCompleteRate;
+
+    @Excel(name = "GMV")
+    private BigDecimal gmv = BigDecimal.ZERO;
+
+    @Excel(name = "订单数")
+    private Long orderCount;
+
+    @Excel(name = "下单人数")
+    private Long orderUserCount;
+
+    @Excel(name = "现存员工人数(去重)")
+    private Long employeeCount;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/live/vo/LiveVo.java

@@ -56,6 +56,7 @@ public class LiveVo {
     private Integer isShow;
     private String previewUrl;
     private Integer previewVideoType;
+    private Integer isDel;
     private Long previewVideoId;
     private Integer globalVisible;
     private Integer liveFlag;

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

@@ -97,6 +97,7 @@ headerImg:
 
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
+  wxIpadUrl:
   aiApi: 1212121212
   voiceApi:
   commonApi:

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

@@ -92,6 +92,7 @@ headerImg:
 
 ipad:
   ipadUrl: http://aipad.klbycp.com
+  wxIpadUrl:
   aiApi: http://49.232.181.28:3000/api
   voiceApi:
   commonApi:

+ 2 - 1
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -953,7 +953,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         l.qw_user_id,
         l.qw_external_contact_id,
         l.sop_id,
-        qec.create_time as qec_create_time
+        qec.create_time as qec_create_time,
+        u.last_ip
         FROM
         fs_course_watch_log l LEFT JOIN qw_external_contact qec on l.qw_external_contact_id = qec.id
         left join fs_user u on u.user_id = l.user_id

+ 51 - 8
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -154,6 +154,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deliveryTime != null "> and delivery_time = #{deliveryTime}</if>
             <if test="deliveryImportTime != null "> and delivery_import_time = #{deliveryImportTime}</if>
             <if test="backendEditProductType != null "> and backend_edit_product_type = #{backendEditProductType}</if>
+            <if test="remark != null and remark != ''"> and remark = #{remark}</if>
         </where>
     </select>
 
@@ -1194,7 +1195,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -1202,6 +1204,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>
@@ -1363,7 +1371,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -1371,6 +1380,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>
@@ -1509,7 +1524,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -1517,6 +1533,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>
@@ -1659,7 +1681,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -1667,6 +1690,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>
@@ -1719,7 +1748,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 <!--    商城订单查询接口-->
     <select id="selectFsStoreOrderListVO" resultType="com.fs.hisStore.vo.FsStoreOrderVO">
-        select DISTINCT o.id,o.order_code,o.extend_order_id,o.pay_order_id,o.bank_order_id,o.user_id,o.real_name,o.user_phone,o.user_address,o.cart_id,o.freight_price,o.total_num,o.total_price,o.total_postage,o.pay_price,o.pay_postage,o.pay_delivery,o.pay_money,o.deduction_price,o.coupon_id,o.coupon_price,o.paid,o.pay_time,o.pay_type,o.create_time,o.update_time,o.status,o.refund_status,o.refund_reason_wap_img,o.refund_reason_wap_explain,o.refund_reason_time,o.refund_reason_wap,o.refund_reason,o.refund_price,o.delivery_sn,o.delivery_name,o.delivery_type,o.delivery_id,o.gain_integral,o.use_integral,o.pay_integral,o.back_integral,o.mark,o.is_del,o.remark,o.verify_code,o.store_id,o.shipping_type,o.is_channel,o.is_remind,o.is_sys_del,o.is_prescribe,o.prescribe_id,o.company_id,o.company_user_id,o.is_package,o.package_json,o.order_type,o.package_id,o.finish_time,o.delivery_status,o.delivery_pay_status,o.delivery_time,o.delivery_pay_time,o.delivery_pay_money,o.tui_money,o.tui_money_status,o.delivery_import_time,o.tui_user_id,o.tui_user_money_status,o.order_create_type,o.store_house_code,o.dept_id,o.is_edit_money,o.customer_id,o.is_pay_remain,o.delivery_send_time,o.certificates,o.upload_time,o.item_json,o.schedule_id,o.delivery_pay_type,o.order_visit,o.service_fee,o.cycle,o.prescribe_price,o.follow_doctor_id,o.follow_time,o.user_coupon_id,o.order_medium,o.erp_phone
+        select DISTINCT o.id,o.order_code,o.extend_order_id,o.pay_order_id,o.bank_order_id,o.user_id,o.real_name,o.user_phone,o.user_address,o.cart_id,o.freight_price,o.total_num,o.total_price,o.total_postage,o.pay_price,o.pay_postage,o.pay_delivery,o.pay_money,o.deduction_price,o.coupon_id,o.coupon_price,o.paid,o.pay_time,o.pay_type,o.create_time,o.update_time,o.status,o.refund_status,o.refund_reason_wap_img,o.refund_reason_wap_explain,o.refund_reason_time,o.refund_reason_wap,o.refund_reason,o.refund_price,o.delivery_sn,o.delivery_name,o.delivery_type,o.delivery_id,o.gain_integral,o.use_integral,o.pay_integral,o.back_integral,o.mark,o.is_del,o.remark,o.verify_code,o.store_id,o.shipping_type,o.is_channel,o.is_remind,o.is_sys_del,o.is_prescribe,o.prescribe_id,o.company_id,o.company_user_id,o.is_package,o.package_json,o.order_type,o.package_id,o.finish_time,o.delivery_status,o.delivery_pay_status,o.delivery_time,o.delivery_pay_time,o.delivery_pay_money,o.tui_money,o.tui_money_status,o.delivery_import_time,o.tui_user_id,o.tui_user_money_status,o.order_create_type,o.store_house_code,o.dept_id,o.is_edit_money,o.customer_id,o.is_pay_remain,o.delivery_send_time,o.certificates,o.upload_time,o.item_json,o.schedule_id,o.delivery_pay_type,o.order_visit,o.service_fee,o.cycle,o.prescribe_price,o.follow_doctor_id,o.follow_time,o.user_coupon_id,o.order_medium,o.erp_phone,o.order_type
         ,u.phone,u.register_code,u.register_date,u.source, c.company_name ,cu.nick_name as company_user_nick_name ,cu.phonenumber as company_usere_phonenumber
         , csc.name miniProgramName,fsp.cost, fspc.cate_name,spavs.bar_code, sp_latest.bank_transaction_id as bankTransactionId, o.is_audit, o.audit_time
         from fs_store_order_scrm o
@@ -1805,7 +1834,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -1813,6 +1843,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>
@@ -1914,7 +1950,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             left join fs_store_order_item_scrm oi on o.id = oi.order_id
             left join fs_store_product_scrm fsp on fsp.product_id = oi.product_id
         </if>
-        <if test="(maps.appId != null and maps.appId != '') or (maps.bankTransactionId != null and  maps.bankTransactionId !='')">
+        <if test="(maps.appId != null and maps.appId != '') or (maps.bankTransactionId != null and  maps.bankTransactionId !='') or (maps.payCode != null and maps.payCode != '')">
             LEFT JOIN (
             SELECT
             sp.*,
@@ -1994,7 +2030,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and o.company_id =#{maps.companyId}
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
-                and o.company_id is null
+                and (o.company_id is null
+                or o.order_type = 2)
             </if>
             <if test="maps.notHealth != null  ">
                 and o.company_id is not null
@@ -2002,6 +2039,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
             </if>
+            <if test="maps.salesName != null and maps.salesName != ''">
+                and cu.user_name like CONCAT('%', #{maps.salesName}, '%')
+            </if>
+            <if test="maps.payCode != null and maps.payCode != ''">
+                and sp_latest.pay_code like CONCAT('%', #{maps.payCode}, '%')
+            </if>
             <if test="maps.companyUserNickName != null and  maps.companyUserNickName !=  '' ">
                 and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%')
             </if>

+ 265 - 32
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -302,37 +302,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             END) AS playbackCompletedCourses,
             COALESCE((
                 SELECT SUM( pay_price)
-                FROM live_order
-                WHERE live_id IN
+                FROM fs_store_order_scrm
+                WHERE order_type = 2
+                AND remark IN
                 <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
                     #{liveId}
                 </foreach>
-                AND is_pay = '1'
+                AND (paid = 1 OR paid = '1')
             ), 0) AS gmv,
             COALESCE((
                 select sum(acs.paid) from (SELECT COUNT(DISTINCT user_id) as paid
-                    FROM live_order
-                    WHERE live_id IN
+                    FROM fs_store_order_scrm
+                    WHERE order_type = 2
+                    AND remark IN
                     <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
                         #{liveId}
                     </foreach>
-                    AND is_pay = '1'
-                    group by live_id
+                    AND (paid = 1 OR paid = '1')
+                    group by remark
                 ) acs
             ), 0) AS paidUsers,
             COALESCE((
-                SELECT COUNT(DISTINCT order_id)
-                FROM live_order
-                WHERE live_id IN
+                SELECT COUNT(DISTINCT id)
+                FROM fs_store_order_scrm
+                WHERE order_type = 2
+                AND remark IN
                 <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
                     #{liveId}
                 </foreach>
-                AND is_pay = '1'
+                AND (paid = 1 OR paid = '1')
             ), 0) AS paidOrders,
             COALESCE((
-                SELECT COUNT(DISTINCT order_id)
-                FROM live_order
-                WHERE live_id IN
+                SELECT COUNT(DISTINCT id)
+                FROM fs_store_order_scrm
+                WHERE order_type = 2
+                AND remark IN
                 <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
                     #{liveId}
                 </foreach>
@@ -391,13 +395,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) video_duration ON l.live_id = video_duration.live_id
         LEFT JOIN (
             SELECT
-                live_id,
-                SUM( case when is_pay = '1' then pay_price else 0 end) AS gmv,
-                COUNT(distinct CASE WHEN is_pay = '1' THEN user_id END) AS paidUsers,
-                sum(CASE WHEN is_pay = '1' THEN 1 else 0 END) AS paidOrders,
-                COUNT(DISTINCT order_id) AS salesCount
-            FROM live_order
-            GROUP BY live_id
+                remark AS live_id,
+                SUM( case when (paid = 1 OR paid = '1') then pay_price else 0 end) AS gmv,
+                COUNT(distinct CASE WHEN (paid = 1 OR paid = '1') THEN user_id END) AS paidUsers,
+                sum(CASE WHEN (paid = 1 OR paid = '1') THEN 1 else 0 END) AS paidOrders,
+                COUNT(DISTINCT id) AS salesCount
+            FROM fs_store_order_scrm
+            WHERE order_type = 2
+            AND remark IN
+            <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+                #{liveId}
+            </foreach>
+            GROUP BY remark
         ) order_stats ON l.live_id = order_stats.live_id
         WHERE l.live_id IN
         <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
@@ -520,12 +529,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ) user_duration ON l.live_id = user_duration.live_id AND lwu.user_id = user_duration.user_id
         LEFT JOIN (
             SELECT
-                live_id,
-                SUM(CASE WHEN is_pay = '1' THEN pay_price ELSE 0 END) AS gmv,
-                COUNT(DISTINCT CASE WHEN is_pay = '1' THEN user_id END) AS paidUsers,
-                SUM(CASE WHEN is_pay = '1' THEN 1 ELSE 0 END) AS paidOrders
-            FROM live_order
-            GROUP BY live_id
+                remark AS live_id,
+                SUM(CASE WHEN (paid = 1 OR paid = '1') THEN pay_price ELSE 0 END) AS gmv,
+                COUNT(DISTINCT CASE WHEN (paid = 1 OR paid = '1') THEN user_id END) AS paidUsers,
+                SUM(CASE WHEN (paid = 1 OR paid = '1') THEN 1 ELSE 0 END) AS paidOrders
+            FROM fs_store_order_scrm
+            WHERE order_type = 2 AND remark = #{liveId}
+            GROUP BY remark
         ) order_stats ON l.live_id = order_stats.live_id
         WHERE l.live_id = #{liveId}
     </select>
@@ -545,11 +555,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         LEFT JOIN fs_user u ON lwu.user_id = u.user_id
         LEFT JOIN (
             SELECT
-                CAST(user_id AS UNSIGNED) AS user_id,
-                COUNT(DISTINCT order_id) AS orderCount,
-                SUM(CASE WHEN is_pay = '1' THEN pay_price ELSE 0 END) AS orderAmount
-            FROM live_order
-            WHERE live_id = #{liveId} AND user_id IS NOT NULL AND user_id != ''
+                user_id,
+                COUNT(DISTINCT id) AS orderCount,
+                SUM(CASE WHEN (paid = 1 OR paid = '1') THEN pay_price ELSE 0 END) AS orderAmount
+            FROM fs_store_order_scrm
+            WHERE order_type = 2 AND remark = #{liveId} AND user_id IS NOT NULL
             GROUP BY user_id
         ) order_info ON lwu.user_id = order_info.user_id
         left join live_user_first_entry lufe on lwu.live_id = lufe.live_id and lwu.user_id = lufe.user_id
@@ -565,4 +575,227 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY u.user_id, u.nick_name, u.nickname, order_info.orderCount, order_info.orderAmount, c.company_name, cu.user_name
         ORDER BY order_info.orderAmount DESC, liveWatchDuration DESC
     </select>
+
+    <!-- 查询满足条件的直播间ID -->
+    <select id="selectLiveIdsByCompanyParam" parameterType="com.fs.live.param.LiveDataCompanyParam" resultType="java.lang.Long">
+        SELECT distinct l.live_id
+        FROM live l
+                 LEFT JOIN company c ON l.company_id = c.company_id
+        WHERE l.is_del = 0
+          AND l.is_audit = 1
+          AND l.is_show = 1
+        <if test="startDate != null">
+            AND l.start_time &gt;= #{startDate}
+        </if>
+        <if test="endDate != null">
+            AND l.start_time &lt;= #{endDate}
+        </if>
+        ORDER BY l.live_id
+    </select>
+
+    <!-- 分公司总到课人数 -->
+    <select id="selectAttendanceCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT lwu.user_id), 0) AS totalAttendanceCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司总完课人数 -->
+    <select id="selectCompleteCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT CASE
+                WHEN lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND COALESCE(vd.total_duration, 0) > 0
+                THEN lwu.user_id END), 0) AS totalCompleteCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+                 LEFT JOIN (
+                    SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration
+                    FROM live_video
+                    WHERE video_type IN (1, 2)
+                    GROUP BY live_id
+                 ) vd ON lwu.live_id = vd.live_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司直播课人数 -->
+    <select id="selectLiveAttendCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END), 0) AS liveAttendanceCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司直播完课人数 -->
+    <select id="selectLiveCompleteCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT CASE
+                WHEN lwu.live_flag = 1
+                     AND lwu.replay_flag = 0
+                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND COALESCE(vd.total_duration, 0) > 0
+                THEN lwu.user_id END), 0) AS liveCompleteCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+                 LEFT JOIN (
+                    SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration
+                    FROM live_video
+                    WHERE video_type IN (1, 2)
+                    GROUP BY live_id
+                 ) vd ON lwu.live_id = vd.live_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司回放课人数 -->
+    <select id="selectReplayAttendCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END), 0) AS replayAttendanceCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司回放完课人数 -->
+    <select id="selectReplayCompleteCountByCompany" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lufe.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT CASE
+                WHEN lwu.live_flag = 0
+                     AND lwu.replay_flag = 1
+                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND COALESCE(vd.total_duration, 0) > 0
+                THEN lwu.user_id END), 0) AS replayCompleteCount
+        FROM live_watch_user lwu
+                 LEFT JOIN live_user_first_entry lufe ON lwu.live_id = lufe.live_id AND lwu.user_id = lufe.user_id
+                 LEFT JOIN company c ON lufe.company_id = c.company_id
+                 LEFT JOIN (
+                    SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration
+                    FROM live_video
+                    WHERE video_type IN (1, 2)
+                    GROUP BY live_id
+                 ) vd ON lwu.live_id = vd.live_id
+        WHERE lwu.live_id IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lufe.company_id, c.company_name
+    </select>
+
+    <!-- 分公司订单/GMV统计 -->
+    <select id="selectCompanyOrderAndGmv" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            lo.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(SUM(CASE WHEN lo.paid = '1' THEN lo.pay_price ELSE 0 END), 0) AS gmv,
+            COALESCE(COUNT(DISTINCT lo.id), 0) AS orderCount,
+            COALESCE(COUNT(DISTINCT lo.user_id), 0) AS orderUserCount
+        FROM fs_store_order_scrm lo
+                 LEFT JOIN company c ON lo.company_id = c.company_id
+        WHERE lo.order_type = 2 and lo.remark IN
+        <foreach collection="liveIds" item="liveId" open="(" separator="," close=")">
+            #{liveId}
+        </foreach>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lo.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY lo.company_id, c.company_name
+    </select>
+
+    <!-- 分公司员工数量统计 -->
+    <select id="selectCompanyEmployeeCountByLiveIds" resultType="com.fs.live.vo.LiveDataCompanyVO">
+        SELECT
+            cu.company_id AS companyId,
+            COALESCE(c.company_name, '总台') AS companyName,
+            COALESCE(COUNT(DISTINCT cu.user_id), 0) AS employeeCount
+        FROM company_user cu
+            LEFT JOIN company c ON cu.company_id = c.company_id
+        WHERE 
+        cu.status = 0
+        AND cu.del_flag = 0
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND cu.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        GROUP BY cu.company_id, c.company_name
+    </select>
 </mapper>

+ 5 - 8
fs-service/src/main/resources/mapper/live/LiveGoodsMapper.xml

@@ -255,14 +255,11 @@
     </delete>
 
     <select id="selectProductListByOrder" parameterType="com.fs.live.domain.LiveOrder" resultType="com.fs.live.vo.LiveGoodsVo">
-
-        select sp.image as img_url,sp.product_name,sp.price,sp.stock,sp.sales,sp.product_id,sp.ot_price, sp.is_show as fs_status,pav.bar_code
-        from fs_store_product_scrm sp
-        left join fs_store_product_attr_value_scrm pav
-        ON  sp.product_id = pav.product_id
-        <where>
-            sp.product_id = #{productId}
-        </where>
+        select distinct sp.image as img_url, sp.product_name, sp.price, sp.stock, sp.sales, sp.product_id, sp.ot_price, sp.is_show as fs_status, pav.bar_code
+        from live_order_item loi
+        inner join fs_store_product_scrm sp on loi.product_id = sp.product_id
+        left join fs_store_product_attr_value_scrm pav on sp.product_id = pav.product_id
+        where loi.order_id = #{orderId}
     </select>
 
     <select id="selectLiveGoodsListByStoreId" resultType="com.fs.live.vo.LiveGoodsVo">

+ 27 - 0
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -364,5 +364,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by create_time desc
     </select>
 
+    <select id="selectLiveIdsByCompanyParam" resultType="java.lang.Long">
+        SELECT l.live_id
+        FROM live l
+                 LEFT JOIN company c ON l.company_id = c.company_id
+                 LEFT JOIN live_user_first_entry lufe ON l.live_id = lufe.live_id
+        WHERE l.is_del = 0
+          AND l.is_audit = 1
+          AND l.is_show = 1
+        <if test="companyName != null and companyName != ''">
+            AND (c.company_name LIKE CONCAT('%', #{companyName}, '%')
+                OR (#{companyName} = '总台' AND l.company_id IS NULL))
+        </if>
+        <if test="startDate != null">
+            AND DATE(l.start_time) &gt;= DATE(#{startDate})
+        </if>
+        <if test="endDate != null">
+            AND DATE(l.start_time) &lt;= DATE(#{endDate})
+        </if>
+        <if test="companyIds != null and companyIds.size() > 0">
+            AND lufe.company_id IN
+            <foreach collection="companyIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        ORDER BY l.live_id
+    </select>
+
 
 </mapper>

+ 1 - 8
fs-user-app/src/main/java/com/fs/app/controller/HuifuPayController.java

@@ -60,6 +60,7 @@ public class HuifuPayController {
                     }
                     break;
                 case "store":
+                case "live":
                     storeOrderService.payConfirm("",orderId[1],huiFuResult.getHf_seq_id(),"",1,huiFuResult.getOut_trans_id(),huiFuResult.getParty_order_id());
                     try {
                         HuiFuUtils.updateDivItem(orderId[1]);
@@ -115,14 +116,6 @@ public class HuifuPayController {
                         logger.error("-------分账明细回调错误{}", e.getMessage());
                     }
                     break;
-                case "live":
-                    liveOrderService.payConfirm(1,null,orderId[1], huiFuResult.getHf_seq_id(),huiFuResult.getOut_trans_id(),huiFuResult.getParty_order_id());
-//                    try {
-//                        HuiFuUtils.updateDivItem(orderId[1]);
-//                    } catch (Exception e) {
-//                        logger.error("-------分账明细回调错误{}", e.getMessage());
-//                    }
-                    break;
             }
         }
 

+ 37 - 0
fs-user-app/src/main/java/com/fs/app/controller/IndexController.java

@@ -24,7 +24,10 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 
 @Api("首页接口")
@@ -141,6 +144,40 @@ public class IndexController extends AppBaseController {
 		return R.ok().put("data",chineseMedicine);
 	}
 
+	/**
+	 * 首页初始化核心数据(频道入口、分类标签、商品分类导航)
+	 * 合并请求,提升首屏加载速度
+	 */
+	@ApiOperation("首页初始化")
+	@GetMapping("/home/init")
+	public R homeInit() {
+		Map<String, Object> data = new HashMap<>();
+		data.put("channelList", Collections.emptyList());
+		data.put("categoryTags", Collections.emptyList());
+		data.put("goodsNav", Collections.emptyList());
+		return R.ok().put("data", data);
+	}
+
+	/**
+	 * 首页推荐区块(直播中、上新推荐等,可延迟加载)
+	 */
+	@ApiOperation("首页推荐区块")
+	@GetMapping("/home/recommend")
+	public R homeRecommend() {
+		return R.ok().put("data", Collections.emptyList());
+	}
+
+	/**
+	 * 首页商品列表(支持分类参数,可延迟/懒加载)
+	 */
+	@ApiOperation("首页商品列表")
+	@GetMapping("/home/goods")
+	public R homeGoods(@RequestParam(value = "category", defaultValue = "全部") String category,
+					  @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+					  @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
+		return R.ok().put("data", Collections.emptyList());
+	}
+
 	@ApiOperation("企业理念")
 	@GetMapping("/getConcept")
 	@Cacheable("getConcept")

+ 5 - 1
fs-user-app/src/main/java/com/fs/app/controller/live/LiveAfterSalesController.java

@@ -21,6 +21,9 @@ import com.fs.live.service.ILiveOrderItemService;
 import com.fs.live.service.ILiveOrderService;
 import com.fs.live.vo.LiveAfterSalesQueryVO;
 import com.fs.live.vo.LiveOrderItemVo;
+import com.fs.hisStore.service.IFsStoreAfterSalesScrmService;
+import com.fs.hisStore.param.FsStoreAfterSalesParam;
+import com.fs.hisStore.param.FsStoreAfterSalesProductParam;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -32,6 +35,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.List;
 
 @Slf4j
@@ -66,7 +70,7 @@ public class LiveAfterSalesController extends AppBaseController {
     @ApiOperation(value = "申请售后", notes = "申请售后")
     @RepeatSubmit
     public R applyAfterSales(@RequestBody LiveAfterSalesParam param) {
-        return storeAfterSalesService.applyForAfterSales(getUserId(), param);
+        return storeAfterSalesService.applyForStoreAfterSales(getUserId(), param);
     }
     @Login
     @PostMapping("/revoke")

+ 164 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveCartController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller.live;
 
 
+import com.alibaba.fastjson.JSON;
 import com.fs.app.annotation.Login;
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.Log;
@@ -8,12 +9,42 @@ import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCoursePlaySourceConfig;
+import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
+import com.fs.his.domain.FsPayConfig;
+import com.fs.his.domain.MerchantAppConfig;
+import com.fs.his.mapper.MerchantAppConfigMapper;
 import com.fs.live.domain.LiveCart;
 import com.fs.live.service.ILiveCartService;
 import com.fs.live.vo.LiveCartVo;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.service.IFsStorePaymentScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.domain.HuiFuRefundResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayRefundRequest;
+import com.fs.huifuPay.service.HuiFuService;
+import com.fs.his.domain.FsHfpayConfig;
+import com.fs.his.mapper.FsHfpayConfigMapper;
+import com.fs.config.cloud.CloudHostProper;
+import com.fs.common.utils.spring.SpringUtils;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryResult;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import lombok.extern.slf4j.Slf4j;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
@@ -21,7 +52,13 @@ import io.swagger.annotations.ApiParam;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import com.fs.common.utils.StringUtils;
 
 /**
  * 购物车Controller
@@ -29,6 +66,7 @@ import java.util.List;
  * @author fs
  * @date 2025-07-08
  */
+@Slf4j
 @RestController
 @RequestMapping("/live/liveCart")
 @Api(tags = "购物车管理")
@@ -36,6 +74,21 @@ public class LiveCartController extends AppBaseController
 {
     @Autowired
     private ILiveCartService liveCartService;
+    
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+    
+    @Autowired
+    private FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
+    
+    @Autowired
+    private HuiFuService huiFuService;
+    
+    @Autowired
+    private IFsStorePaymentScrmService fsStorePaymentScrmService;
+    
+    @Autowired
+    private CloudHostProper cloudHostProper;
 
     /**
      * 查询购物车列表
@@ -135,6 +188,117 @@ public class LiveCartController extends AppBaseController
             return R.error("操作异常");
         }
     }
+    @Autowired
+    FsCoursePlaySourceConfigMapper fsCoursePlaySourceConfigMapper;
+    @Autowired
+    MerchantAppConfigMapper merchantAppConfigMapper;
+    @Autowired
+    private WxPayService wxPayService;
+    @GetMapping("/test1")
+    public void test1() {
+        FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigMapper.selectCoursePlaySourceConfigByAppId("wx9bb2cd1e3afe714e");
+        if (fsCoursePlaySourceConfig == null) {
+            throw new CustomException("未找到appId对应的小程序配置: " );
+        }
+        Long merchantConfigId = fsCoursePlaySourceConfig.getMerchantConfigId();
+        if (merchantConfigId == null || merchantConfigId <= 0) {
+            throw new CustomException("小程序没有配置商户信息");
+        }
+        MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(fsCoursePlaySourceConfig.getMerchantConfigId());
+        FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(fsCoursePlaySourceConfig.getAppid());
+        payConfig.setMchId(fsPayConfig.getWxMchId());
+        payConfig.setMchKey(fsPayConfig.getWxMchKey());
+        payConfig.setKeyPath(fsPayConfig.getKeyPath());
+        payConfig.setSubAppId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+        payConfig.setSubMchId(org.apache.commons.lang3.StringUtils.trimToNull(null));
+        wxPayService.setConfig(payConfig);
+        WxPayRefundRequest refundRequest = new WxPayRefundRequest();
+        refundRequest.setOutTradeNo("live-live-" + "2017147035307872256");
+        refundRequest.setOutRefundNo("live-live-" + "2017147035307872256");
+        refundRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen("0.10"));
+        refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen("0.10"));
+        try {
+            WxPayRefundResult refundResult = wxPayService.refund(refundRequest);
+            WxPayRefundQueryResult refundQueryResult = wxPayService.refundQuery("", refundResult.getOutTradeNo(), refundResult.getOutRefundNo(), refundResult.getRefundId());
+            if(refundQueryResult!=null&&refundQueryResult.getResultCode().equals("SUCCESS")){
+                FsStorePaymentScrm paymentMap=new FsStorePaymentScrm();
+                paymentMap.setPaymentId(41868L);
+                paymentMap.setStatus(-1);
+                paymentMap.setRefundTime(DateUtils.getNowDate());
+                paymentMap.setRefundMoney(BigDecimal.valueOf(0.1));
+                fsStorePaymentScrmMapper.updateFsStorePayment(paymentMap);
+                log.error("成功");
+            }
+            else {
+                log.error("失败");
+            }
+        } catch (WxPayException e) {
+            log.error("失败");
+        }
+    }
+
+
+    /**
+     * 确认支付(银行回调补偿)
+     */
+    @GetMapping("/confirmPay/{orderCode}")
+    @ApiOperation("确认支付")
+    @ApiImplicitParam(name = "orderCode", value = "订单号", required = true, dataType = "String", paramType = "path")
+    public R confirmPay(@PathVariable("orderCode") String orderCode)
+    {
+        try {
+            log.info("确认支付请求,订单号: {}", orderCode);
+
+            // 查询商城订单
+            FsStoreOrderScrm order = fsStoreOrderScrmService.selectFsStoreOrderByOrderCode(orderCode);
+            if (order == null) {
+                return R.error("订单不存在");
+            }
+
+            // 查询未支付的支付记录
+            List<FsStorePaymentScrm> orderPayments = fsStorePaymentScrmMapper.selectNoPayByOrderId(order.getId());
+            if (orderPayments == null || orderPayments.isEmpty()) {
+                return R.error("未找到待支付的支付记录");
+            }
+
+            // 遍历支付记录,查询汇付订单状态
+            for (FsStorePaymentScrm payment : orderPayments) {
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new java.text.SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                request.setAppId(payment.getAppId());
+
+                HuiFuQueryOrderResult queryResult = null;
+                try {
+                    queryResult = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询汇付订单失败,订单号: {}, 错误: {}", orderCode, e.getMessage());
+                    continue;
+                }
+
+                log.info("汇付返回结果: {}", queryResult);
+
+                // 如果支付成功,进行回调保存
+                if ("00000000".equals(queryResult.getResp_code()) && "S".equals(queryResult.getTrans_stat())) {
+                    String[] orderSplit = queryResult.getOrg_req_seq_id().split("-");
+                    if (orderSplit.length > 1) {
+                        String payCode = orderSplit[1];
+                        // 调用支付确认
+                        fsStoreOrderScrmService.payConfirm(1, null, payCode, queryResult.getOrg_hf_seq_id(),
+                                queryResult.getOut_trans_id(), queryResult.getParty_order_id());
+                        return R.ok("支付确认成功");
+                    }
+                }
+            }
+
+            return R.error("未找到已支付的记录");
+        } catch (Exception e) {
+            log.error("确认支付异常,订单号: {}", orderCode, e);
+            return R.error("确认支付失败: " + e.getMessage());
+        }
+    }
 
 
 

+ 40 - 6
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -343,7 +343,7 @@ public class LiveOrderController extends AppBaseController
         String userId= getUserId();
         log.info("开始创建订单,登录用户id:{}", userId);
         param.setUserId(userId);
-        return orderService.createLiveOrder(param);
+        return orderService.createStoreOrder(param);
     }
     @Login
     @ApiOperation("创建订单测试")
@@ -385,16 +385,49 @@ public class LiveOrderController extends AppBaseController
         Long orderId = param.getOrderId();
         logger.info("开始处理支付请求, 订单号: {}, 支付类型: {}", orderId, param.getPayType());
         try{
-            Long existPayedRecordId = orderService.isExistPayedRecord(param.getOrderId());
-            if (null != existPayedRecordId) {
-                orderService.payConfirmPayment(existPayedRecordId);
+            // 查询商城订单
+            FsStoreOrderScrm storeOrder = fsStoreOrderScrmService.selectFsStoreOrderById(orderId);
+            if (storeOrder == null) {
+                return R.error("订单不存在");
+            }
+
+            // 检查订单状态,如果不是待支付状态,说明订单已支付
+            if (storeOrder.getStatus() != null && storeOrder.getStatus() != OrderInfoEnum.STATUS_0.getValue()) {
+                return R.error("当前订单已支付");
+            }
+
+            // 查询商城支付记录
+            List<FsStorePaymentScrm> paymentList = fsStorePaymentScrmMapper.selectFsStorePaymentByOrderId(orderId);
+            Long payCode = null;
+            if (paymentList != null && !paymentList.isEmpty()) {
+                // 检查是否有已支付的支付记录
+                for (FsStorePaymentScrm payment : paymentList) {
+                    if (payment.getStatus() != null && payment.getStatus() == 1) {
+                        V2TradePaymentScanpayQueryRequest bankRequest = new V2TradePaymentScanpayQueryRequest();
+                        bankRequest.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                        bankRequest.setOrgHfSeqId(payment.getTradeNo());
+                        bankRequest.setAppId(payment.getAppId());
+                        HuiFuQueryOrderResult queryOrderResult = null;
+                        try {
+                            queryOrderResult = huiFuService.queryOrder(bankRequest);
+                        } catch (Exception e) {
+                            throw new RuntimeException(e);
+                        }
+                        if ("00000000".equals(queryOrderResult.getResp_code())) {
+                            if (queryOrderResult.getTrans_stat().equals("S")) {
+                                payCode = payment.getPaymentId();
+                            }
+                        }
+                    }
+                }
+            }
+            if (payCode != null) {
                 return R.error("当前订单已支付");
             }
         } catch(Exception ex){
             log.error("校验当前订单是否存在已经支付的支付记录异常,param:{}",param,ex);
         }
 
-
         RLock lock = redissonClient.getLock(String.format(LOCK_KEY_PAY,orderId));
         R result = null;
 
@@ -405,7 +438,8 @@ public class LiveOrderController extends AppBaseController
                 return R.error("订单正在处理中,请勿重复提交");
             }
 
-            result = orderService.handleLiveOrderPay(param);
+//            result = orderService.handleLiveOrderPay(param);
+            result = orderService.handleStoreOrderPay(param);
 
         } catch (InterruptedException e) {
             logger.error("获取支付锁的过程被中断, 订单号: {}", orderId, e);

+ 1 - 8
fs-user-app/src/main/java/com/fs/app/controller/store/PayScrmController.java

@@ -81,6 +81,7 @@ public class PayScrmController {
             String[] order=o.getReq_seq_id().split("-");
             switch (order[0]) {
                 case "store":
+                case "live":
                     try {
                         HuiFuUtils.updateDivItem(order[1]);
                     } catch (Exception e) {
@@ -101,14 +102,6 @@ public class PayScrmController {
                         logger.error("-------分账明细回调错误{}", e.getMessage());
                     }
                     return storePaymentService.payConfirm(order[1],o.getHf_seq_id(),o.getOut_trans_id(),o.getParty_order_id());
-                case "live":
-                    liveOrderService.payConfirm(1,null,order[1], o.getHf_seq_id(),o.getOut_trans_id(),o.getParty_order_id());
-//                    try {
-//                        HuiFuUtils.updateDivItem(orderId[1]);
-//                    } catch (Exception e) {
-//                        logger.error("-------分账明细回调错误{}", e.getMessage());
-//                    }
-                    break;
             }
 
         }