浏览代码

Merge branch 'refs/heads/master' into feature_netty连接_20260617

yuhongqi 2 天之前
父节点
当前提交
383f4f3d29
共有 62 个文件被更改,包括 1423 次插入179 次删除
  1. 9 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyWxAccountController.java
  2. 15 1
      fs-admin/src/main/java/com/fs/his/controller/FsCourseCouponUserController.java
  3. 1 1
      fs-admin/src/main/java/com/fs/his/controller/FsExpressCommonController.java
  4. 18 3
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  5. 19 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  6. 22 6
      fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java
  7. 17 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseCouponUserController.java
  8. 213 0
      fs-company/src/main/java/com/fs/company/controller/scrm/ScrmCustomerController.java
  9. 0 9
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java
  10. 57 59
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  11. 1 1
      fs-company/src/main/java/com/fs/hisStore/utils/CityTreeUtil.java
  12. 1 1
      fs-company/src/main/java/com/fs/hisStore/vo/CityVO.java
  13. 3 0
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  14. 5 3
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AiCallTaskNode.java
  15. 8 4
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  16. 8 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  17. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoH5DVO.java
  18. 2 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  19. 10 0
      fs-service/src/main/java/com/fs/his/domain/FsCourseCouponUser.java
  20. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  21. 27 7
      fs-service/src/main/java/com/fs/his/mapper/FsCourseCouponUserMapper.java
  22. 7 1
      fs-service/src/main/java/com/fs/his/mapper/FsUserCouponMapper.java
  23. 9 7
      fs-service/src/main/java/com/fs/his/service/IFsCourseCouponUserService.java
  24. 8 7
      fs-service/src/main/java/com/fs/his/service/impl/FsCourseCouponServiceImpl.java
  25. 13 7
      fs-service/src/main/java/com/fs/his/service/impl/FsCourseCouponUserServiceImpl.java
  26. 3 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  27. 73 0
      fs-service/src/main/java/com/fs/his/vo/FsCourseCouponUserVO.java
  28. 7 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java
  29. 2 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java
  30. 6 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java
  31. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  32. 10 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  33. 12 4
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderItemScrmServiceImpl.java
  34. 119 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  35. 6 2
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java
  36. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  37. 6 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java
  38. 16 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderRefundAmountVO.java
  39. 1 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  40. 69 7
      fs-service/src/main/java/com/fs/live/service/impl/LiveRedPacketLogServiceImpl.java
  41. 6 3
      fs-service/src/main/java/com/fs/qw/mapper/HyWorkTaskMapper.java
  42. 4 2
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  43. 58 0
      fs-service/src/main/java/com/fs/scrm/domain/ScrmCustomerInfo.java
  44. 36 0
      fs-service/src/main/java/com/fs/scrm/mapper/ScrmCustomerInfoMapper.java
  45. 42 0
      fs-service/src/main/java/com/fs/scrm/param/ScrmCustomerInfoQueryParam.java
  46. 14 0
      fs-service/src/main/java/com/fs/scrm/service/IScrmCustomerInfoService.java
  47. 39 0
      fs-service/src/main/java/com/fs/scrm/service/impl/ScrmCustomerInfoServiceImpl.java
  48. 104 0
      fs-service/src/main/java/com/fs/scrm/vo/ScrmCustomerInfoVO.java
  49. 2 2
      fs-service/src/main/resources/application-config-druid-bjzm.yml
  50. 3 3
      fs-service/src/main/resources/application-config-druid-hcl.yml
  51. 4 4
      fs-service/src/main/resources/application-druid-hcl.yml
  52. 28 1
      fs-service/src/main/resources/db/changelog/changes/20260613-live-user-add-is-del.sql
  53. 28 0
      fs-service/src/main/resources/db/changelog/table/live_group_type.sql
  54. 6 6
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticMapper.xml
  55. 19 8
      fs-service/src/main/resources/mapper/his/FsCourseCouponUserMapper.xml
  56. 7 1
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  57. 16 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreAfterSalesScrmMapper.xml
  58. 3 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  59. 13 5
      fs-service/src/main/resources/mapper/qw/QwRestrictionPushRecordMapper.xml
  60. 162 0
      fs-service/src/main/resources/mapper/scrm/ScrmCustomerInfoMapper.xml
  61. 3 5
      fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml
  62. 10 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

+ 9 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyWxAccountController.java

@@ -7,7 +7,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyWxAccount;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.company.service.ICompanyWxAccountService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -17,7 +19,7 @@ import java.util.List;
 
 /**
  * 企微账号Controller
- * 
+ *
  * @author fs
  * @date 2024-12-09
  */
@@ -28,6 +30,9 @@ public class CompanyWxAccountController extends BaseController
     @Autowired
     private ICompanyWxAccountService companyWxEnterpriseAccountService;
 
+    @Autowired
+    private ICompanyUserService userService;
+
     /**
      * 查询企微账号列表
      */
@@ -80,6 +85,9 @@ public class CompanyWxAccountController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody CompanyWxAccount companyWxAccount)
     {
+        Long companyUserId = companyWxAccount.getCompanyUserId();
+        CompanyUser companyUser = userService.selectCompanyUserById(companyUserId);
+        companyWxAccount.setCompanyId(companyUser.getCompanyId());
         return toAjax(companyWxEnterpriseAccountService.insertCompanyWxAccount(companyWxAccount));
     }
 

+ 15 - 1
fs-admin/src/main/java/com/fs/his/controller/FsCourseCouponUserController.java

@@ -1,6 +1,8 @@
 package com.fs.his.controller;
 
 import java.util.List;
+
+import com.fs.his.vo.FsCourseCouponUserVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -22,7 +24,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 用户看课优惠券Controller
- * 
+ *
  * @author fs
  * @date 2026-05-13
  */
@@ -45,6 +47,18 @@ public class FsCourseCouponUserController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询用户看课优惠券列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:courseCouponUser:list')")
+    @GetMapping("/listVO")
+    public TableDataInfo listVO(FsCourseCouponUser fsCourseCouponUser)
+    {
+        startPage();
+        List<FsCourseCouponUserVO> list = fsCourseCouponUserService.selectFsCourseCouponUserListVO(fsCourseCouponUser);
+        return getDataTable(list);
+    }
+
     /**
      * 导出用户看课优惠券列表
      */

+ 1 - 1
fs-admin/src/main/java/com/fs/his/controller/FsExpressCommonController.java

@@ -89,7 +89,7 @@ public class FsExpressCommonController extends BaseController {
 
 		} else{
 			FsUser newUser=new FsUser();
-			newUser.setPhone(tl.getPhoneNum());
+			newUser.setPhone(encryptPhone(tl.getPhoneNum()));
 			newUser.setNickName("微信用户"+tl.getPhoneNum().substring(tl.getPhoneNum().length()-4));
 			newUser.setStatus(1);
 			newUser.setIntegralStatus(1);

+ 18 - 3
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -1,6 +1,7 @@
 package com.fs.his.controller;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.BeanCopyUtils;
@@ -353,9 +354,23 @@ public class FsIntegralOrderController extends BaseController
             if (StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())) {
                 String lastFourNumber = "";
                 if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
-
-                    lastFourNumber = fsIntegralOrder.getUserPhone();
-                    if (lastFourNumber.length() == 11) {
+                    if("恒春来".equals(cloudHostProper.getCompanyName())
+                            && ObjectUtil.isNotEmpty(lastFourNumber = fsIntegralOrder.getVirtualPhone())){
+                        if (lastFourNumber.contains("-")) {
+                            String beforeDash = lastFourNumber.split("-")[0];
+                            lastFourNumber = beforeDash.length() >= 4 ? beforeDash.substring(beforeDash.length() - 4) : beforeDash;
+                        }else{
+                            lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                        }
+                        expressInfoDTO=expressService.getExpressInfo(fsIntegralOrder.getOrderCode(),fsIntegralOrder.getDeliverySn(),fsIntegralOrder.getDeliverySn(),lastFourNumber);
+                        if(expressInfoDTO!=null && !expressInfoDTO.isSuccess()){
+                            lastFourNumber = StrUtil.sub(fsIntegralOrder.getVirtualPhone(), fsIntegralOrder.getVirtualPhone().length(), -4);
+                            expressInfoDTO=expressService.getExpressInfo(fsIntegralOrder.getOrderCode(),fsIntegralOrder.getDeliverySn(),fsIntegralOrder.getDeliverySn(),lastFourNumber);
+                        }
+                        return R.ok().put("data",expressInfoDTO);
+                    }
+                    // 原逻辑
+                    else if ((lastFourNumber = fsIntegralOrder.getUserPhone()).length() == 11) {
                         lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
                     }
                 }

+ 19 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -5,6 +5,8 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.FSApplication;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -66,6 +68,7 @@ import com.fs.hisStore.dto.FsStoreOrderPayPostageEditDTO;
 import com.fs.hisStore.dto.StoreOrderExpressExportDTO;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.enums.ShipperCodeEnum;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
@@ -74,10 +77,12 @@ import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
+import org.junit.Test;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -167,6 +172,9 @@ public class FsStoreOrderScrmController extends BaseController {
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
 
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderMapper;
+
     @Value("${cloud_host.company_name}")
     private String signProjectName;
 
@@ -777,6 +785,17 @@ public class FsStoreOrderScrmController extends BaseController {
                         lastFourNumber = StrUtil.sub(order.getVirtualPhone(), order.getVirtualPhone().length(), -4);
                         expressInfoDTO=expressService.getExpressInfo(order.getOrderCode(),order.getDeliverySn(),order.getDeliveryId(),lastFourNumber);
                     }
+                    if(expressInfoDTO!=null){
+                        FsStoreOrderScrm map = new FsStoreOrderScrm();
+                        map.setDeliveryStatus(Integer.parseInt(expressInfoDTO.getState()));
+                        map.setId(order.getId());
+                        map.setDeliveryType(expressInfoDTO.getStateEx());
+                        fsStoreOrderMapper.updateFsStoreOrder(map);
+                        //如果是正常签收,更新订单状态
+                        if (expressInfoDTO.getState().equals("3") && (expressInfoDTO.getStateEx().equals("301") || expressInfoDTO.getStateEx().equals("302") || expressInfoDTO.getStateEx().equals("304") || expressInfoDTO.getStateEx().equals("311"))) {
+                            fsStoreOrderService.finishOrder(order.getId());
+                        }
+                    }
                     return R.ok().put("data",expressInfoDTO);
                 }
                 // 原逻辑

+ 22 - 6
fs-admin/src/main/java/com/fs/live/controller/LiveAutoTaskController.java

@@ -193,21 +193,37 @@ public class LiveAutoTaskController extends BaseController
     }
 
     public static Date formatTime(String duration) {
-        String[] parts = duration.split(":");
-        if (parts.length != 3) {
+
+        if (duration == null || duration.trim().isEmpty()) {
+            throw new IllegalArgumentException("时间字符串不能为空");
+        }
+
+        // 1. 去除首尾空格
+        String cleaned = duration.trim();
+
+        // 2. 只保留数字和冒号
+        cleaned = cleaned.replaceAll("[^0-9:]", "");
+
+        // 3. 验证格式
+        if (!cleaned.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
             throw new IllegalArgumentException("时间格式不正确,应为 hh:mm:ss");
         }
+
+        String[] parts = cleaned.split(":");
         int hours = Integer.parseInt(parts[0]);
         int minutes = Integer.parseInt(parts[1]);
         int seconds = Integer.parseInt(parts[2]);
 
-        // 获取当前日期的Calendar实例
+        // 4. 验证数值范围
+        if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
+            throw new IllegalArgumentException("时间数值超出有效范围");
+        }
+
         Calendar calendar = Calendar.getInstance();
-        // 设置时分秒
-        calendar.set(Calendar.HOUR_OF_DAY, hours); // 24小时制
+        calendar.set(Calendar.HOUR_OF_DAY, hours);
         calendar.set(Calendar.MINUTE, minutes);
         calendar.set(Calendar.SECOND, seconds);
-        calendar.set(Calendar.MILLISECOND, 0); // 毫秒设为0
+        calendar.set(Calendar.MILLISECOND, 0);
 
         return calendar.getTime();
     }

+ 17 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseCouponUserController.java

@@ -6,7 +6,9 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.his.domain.FsCourseCouponUser;
 import com.fs.his.service.IFsCourseCouponUserService;
 import com.fs.his.vo.FsCourseCouponUserRecordVO;
+import com.fs.his.vo.FsCourseCouponUserVO;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -20,10 +22,25 @@ public class FsCourseCouponUserController extends BaseController {
     @Autowired
     private IFsCourseCouponUserService courseCouponUserService;
 
+    @Autowired
+    private IFsCourseCouponUserService fsCourseCouponUserService;
+
     @GetMapping("/getRecordList")
     public TableDataInfo getRecordList(FsCourseCouponUser courseCouponUser) {
         startPage();
         List<FsCourseCouponUserRecordVO> vos = courseCouponUserService.selectCourseCouponUserRecordList(courseCouponUser);
         return getDataTable(vos);
     }
+
+    /**
+     * 查询用户看课优惠券列表
+     */
+    @GetMapping("/listVO")
+    public TableDataInfo listVO(FsCourseCouponUser fsCourseCouponUser)
+    {
+        startPage();
+        List<FsCourseCouponUserVO> list = fsCourseCouponUserService.selectFsCourseCouponUserListVO(fsCourseCouponUser);
+        return getDataTable(list);
+    }
+
 }

+ 213 - 0
fs-company/src/main/java/com/fs/company/controller/scrm/ScrmCustomerController.java

@@ -0,0 +1,213 @@
+package com.fs.company.controller.scrm;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
+import com.fs.company.vo.CustomerRoboticCallOutCountVO;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.scrm.domain.ScrmCustomerInfo;
+import com.fs.scrm.param.ScrmCustomerInfoQueryParam;
+import com.fs.scrm.service.IScrmCustomerInfoService;
+import com.fs.scrm.vo.ScrmCustomerInfoVO;
+import com.github.pagehelper.PageHelper;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * SCRM客户Controller
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@RestController
+@RequestMapping("/scrm/customer")
+public class ScrmCustomerController extends BaseController
+{
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private IScrmCustomerInfoService scrmCustomerInfoService;
+    @Autowired
+    private ICompanyVoiceRoboticCallLogCallphoneService companyVoiceRoboticCallLogCallphoneService;
+
+    // ==================== SCRM客户信息 ====================
+
+    @ApiOperation("获取我的客户列表(联查qw_external_contact + scrm_customer_info)")
+    @GetMapping("/getMyCustomerList")
+    public TableDataInfo getMyCustomerList(ScrmCustomerInfoQueryParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        param.setCompanyUserId(loginUser.getUser().getUserId());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (!StringUtils.isEmpty(param.getCreateTimeRange()))
+        {
+            String[] times = param.getCreateTimeRange().split("--");
+            if (times.length == 2)
+            {
+                param.setCreateTimeStart(times[0].trim());
+                param.setCreateTimeEnd(times[1].trim());
+            }
+        }
+        List<ScrmCustomerInfoVO> list = scrmCustomerInfoService.selectScrmCustomerInfoList(param);
+        if (list != null)
+        {
+            for (ScrmCustomerInfoVO vo : list)
+            {
+                if (vo.getRemarkMobiles() != null)
+                {
+                    vo.setRemarkMobiles(vo.getRemarkMobiles().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                }
+            }
+            fillScrmCustomerListRoboticCallOutCount(list, param.getCompanyId());
+        }
+        return getDataTable(list);
+    }
+
+    @ApiOperation("修改SCRM客户信息")
+    @Log(title = "SCRM客户", businessType = BusinessType.UPDATE)
+    @PutMapping("/edit")
+    public AjaxResult edit(@RequestBody ScrmCustomerInfo scrmCustomerInfo)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        scrmCustomerInfo.setCompanyId(loginUser.getCompany().getCompanyId());
+        scrmCustomerInfo.setUpdateBy(loginUser.getUsername());
+        return AjaxResult.success(scrmCustomerInfoService.saveOrUpdateScrmCustomerInfo(scrmCustomerInfo) > 0);
+    }
+
+    private void fillScrmCustomerListRoboticCallOutCount(List<ScrmCustomerInfoVO> list, Long companyId)
+    {
+        if (list == null || list.isEmpty())
+        {
+            return;
+        }
+        List<Long> externalContactIds = list.stream().map(ScrmCustomerInfoVO::getExternalContactId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (externalContactIds.isEmpty())
+        {
+            return;
+        }
+        Map<Long, Long> countMap = companyVoiceRoboticCallLogCallphoneService.countRoboticCallOutByCustomerIds(externalContactIds, companyId).stream()
+                .collect(Collectors.toMap(CustomerRoboticCallOutCountVO::getCustomerId, CustomerRoboticCallOutCountVO::getCallCount, (a, b) -> a));
+        for (ScrmCustomerInfoVO vo : list)
+        {
+            long n = vo.getExternalContactId() == null ? 0L : countMap.getOrDefault(vo.getExternalContactId(), 0L);
+            vo.setRoboticCallOutCount((int) n);
+        }
+    }
+
+    // ==================== 客户跟进记录 ====================
+
+    @ApiOperation("上传跟进记录文件(上传到OSS存储桶)")
+    @PostMapping("/followUp/upload")
+    public AjaxResult uploadFollowUpFile(@RequestParam("file") MultipartFile file,
+                                         @RequestParam("externalContactId") String externalContactId,
+                                         @RequestParam(value = "userId", required = false) Long userId) throws Exception
+    {
+        if (file.isEmpty())
+        {
+            return AjaxResult.error("上传文件不能为空");
+        }
+        String originalFilename = file.getOriginalFilename();
+        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String prefix = originalFilename.substring(0, originalFilename.lastIndexOf("."));
+        CloudStorageService storage = OSSFactory.build();
+        String ossUrl = storage.upload(file.getBytes(), prefix + System.currentTimeMillis() + suffix);
+        Map<String, Object> data = new HashMap<>();
+        data.put("fileName", originalFilename);
+        data.put("fileUrl", ossUrl);
+        data.put("fileSize", file.getSize());
+        data.put("uploadTime", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
+        return AjaxResult.success(data);
+    }
+
+    @ApiOperation("保存跟进记录和录音(JSON方式)")
+    @PostMapping("/followUp/save")
+    public AjaxResult saveFollowUpRecord(@RequestBody Map<String, Object> params)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long externalContactId = params.get("externalContactId") != null ? Long.valueOf(params.get("externalContactId").toString()) : null;
+        if (externalContactId == null)
+        {
+            return AjaxResult.error("externalContactId不能为空");
+        }
+        ScrmCustomerInfo info = new ScrmCustomerInfo();
+        info.setExternalContactId(externalContactId);
+        info.setCompanyId(loginUser.getCompany().getCompanyId());
+        info.setUpdateBy(loginUser.getUsername());
+        // 跟进记录JSON
+        if (params.get("visitRecords") != null)
+        {
+            info.setVisitRecords(params.get("visitRecords").toString());
+        }
+        // 录音记录JSON
+        if (params.get("recordings") != null)
+        {
+            info.setRecordings(params.get("recordings").toString());
+        }
+        scrmCustomerInfoService.saveOrUpdateScrmCustomerInfo(info);
+        return AjaxResult.success();
+    }
+
+    @ApiOperation("获取跟进统计")
+    @GetMapping("/followUp/statistics")
+    public AjaxResult getFollowUpStatistics(@RequestParam("externalContactId") Long externalContactId)
+    {
+        ScrmCustomerInfo info = scrmCustomerInfoService.selectScrmCustomerInfoByExternalContactId(externalContactId);
+        Map<String, Object> result = new HashMap<>();
+        int unfollowDays = 0;
+        int followCount = 0;
+        if (info != null)
+        {
+            // 统计跟进记录条数
+            if (info.getVisitRecords() != null && !info.getVisitRecords().isEmpty())
+            {
+                try
+                {
+                    List<?> visitList = new com.fasterxml.jackson.databind.ObjectMapper().readValue(info.getVisitRecords(), List.class);
+                    followCount += visitList.size();
+                }
+                catch (Exception ignored) {}
+            }
+            // 统计录音条数
+            if (info.getRecordings() != null && !info.getRecordings().isEmpty())
+            {
+                try
+                {
+                    List<?> recordingList = new com.fasterxml.jackson.databind.ObjectMapper().readValue(info.getRecordings(), List.class);
+                    followCount += recordingList.size();
+                }
+                catch (Exception ignored) {}
+            }
+            // 计算未跟进天数
+            if (info.getLastTime() != null)
+            {
+                long diffMillis = System.currentTimeMillis() - info.getLastTime().getTime();
+                unfollowDays = (int) java.util.concurrent.TimeUnit.MILLISECONDS.toDays(diffMillis);
+            }
+        }
+        result.put("unfollowDays", unfollowDays);
+        result.put("followCount", followCount);
+        return AjaxResult.success(result);
+    }
+
+}

+ 0 - 9
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java

@@ -238,15 +238,6 @@ public class FsIntegralOrderController extends BaseController
         if (order == null) {
             throw new ServiceException("订单不存在");
         }
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        if (!loginUser.getCompany().getCompanyId().equals(order.getCompanyId())) {
-            throw new ServiceException("非法操作");
-        }
-        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
-            if (order.getCompanyUserId() == null || !loginUser.getUser().getUserId().equals(order.getCompanyUserId())) {
-                throw new ServiceException("非法操作");
-            }
-        }
     }
 
     private boolean hasRoleKey(LoginUser loginUser, String roleKey) {

+ 57 - 59
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -7,6 +7,7 @@ import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.FsCompanyApplication;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.Log;
 import com.fs.common.constant.HttpStatus;
@@ -45,13 +46,16 @@ import com.fs.hisStore.dto.FsStoreOrderPayDeliveryDTO;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.enums.OrderLogEnum;
 import com.fs.hisStore.enums.ShipperCodeEnum;
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.ApiOperation;
+import org.junit.jupiter.api.Test;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -109,6 +113,8 @@ public class FsStoreOrderScrmController extends BaseController
     private IFsUserCoursePeriodService fsUserCoursePeriodService;
     @Autowired
     private IFsUserCourseVideoService fsUserCourseVideoService;
+    @Autowired
+    private FsStoreOrderScrmMapper fsStoreOrderMapper;
 
     /**
      * 查询订单列表
@@ -118,7 +124,7 @@ public class FsStoreOrderScrmController extends BaseController
     public TableDataInfo list(FsStoreOrderParam param)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        applyCompanyDataScope(param, loginUser);
 
         // 郑多燕需求
         if("广州郑多燕".equals(cloudHostProper.getCompanyName())){
@@ -127,32 +133,14 @@ public class FsStoreOrderScrmController extends BaseController
             if(!companyUsers.isEmpty()){
                 List<Long> companyUserIds = companyUsers.stream().map(CompanyUser::getUserId).collect(Collectors.toList());
                 param.setCompanyUserIds(companyUserIds);
+                param.setCompanyUserId(null);
             } else {
-                // 表示数据权限是本人
                 param.setCompanyUserId(loginUser.getUser().getUserId());
             }
         }
         startPage();
-
-        String configJson = configService.selectConfigByKey("his.store");
-        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
-        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
-                param.setCompanyUserId(loginUser.getUser().getUserId());
-            }
-            param.setIsCompanyOrder(1);//是否销售订单
-        }
-
-
-        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getPayTimeRange())){
-            param.setPayTimeList(param.getPayTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
-            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
-        }
+        applyCompanyOrderModeIfEnabled(param);
+        fillListTimeRange(param);
         List<FsStoreOrderVO> list = fsStoreOrderService.selectFsStoreOrderListVO(param);
         return getDataTable(list);
     }
@@ -211,24 +199,9 @@ public class FsStoreOrderScrmController extends BaseController
             return AjaxResult.error("请筛选数据导出");
         }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId(loginUser.getCompany().getCompanyId());
-        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getPayTimeRange())){
-            param.setPayTimeList(param.getPayTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
-            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
-        }
-        String configJson = configService.selectConfigByKey("his.store");
-        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
-        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
-                param.setCompanyUserId(loginUser.getUser().getUserId());
-            }
-            param.setIsCompanyOrder(1);//是否销售订单
-        }
+        applyCompanyDataScope(param, loginUser);
+        applyCompanyOrderModeIfEnabled(param);
+        fillListTimeRange(param);
         List<FsStoreOrderErpExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
         //对手机号脱敏
         if(list!=null){
@@ -446,6 +419,17 @@ public class FsStoreOrderScrmController extends BaseController
                         lastFourNumber = StrUtil.sub(order.getVirtualPhone(), order.getVirtualPhone().length(), -4);
                         expressInfoDTO=expressService.getExpressInfo(order.getOrderCode(),order.getDeliverySn(),order.getDeliveryId(),lastFourNumber);
                     }
+                    if(expressInfoDTO!=null){
+                        FsStoreOrderScrm map = new FsStoreOrderScrm();
+                        map.setDeliveryStatus(Integer.parseInt(expressInfoDTO.getState()));
+                        map.setId(order.getId());
+                        map.setDeliveryType(expressInfoDTO.getStateEx());
+                        fsStoreOrderMapper.updateFsStoreOrder(map);
+                        //如果是正常签收,更新订单状态
+                        if (expressInfoDTO.getState().equals("3") && (expressInfoDTO.getStateEx().equals("301") || expressInfoDTO.getStateEx().equals("302") || expressInfoDTO.getStateEx().equals("304") || expressInfoDTO.getStateEx().equals("311"))) {
+                            fsStoreOrderService.finishOrder(order.getId());
+                        }
+                    }
                     return R.ok().put("data",expressInfoDTO);
                 }
                 // 原逻辑
@@ -538,25 +522,9 @@ public class FsStoreOrderScrmController extends BaseController
             return AjaxResult.error("请筛选数据导出");
         }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId(loginUser.getCompany().getCompanyId());
-
-        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
-            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getPayTimeRange())){
-            param.setPayTimeList(param.getPayTimeRange().split("--"));
-        }
-        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
-            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
-        }
-        String configJson = configService.selectConfigByKey("his.store");
-        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
-        if(storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())){
-            if(!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")){//非管理员看见自己数据
-                param.setCompanyUserId(loginUser.getUser().getUserId());
-            }
-            param.setIsCompanyOrder(1);//是否销售订单
-        }
+        applyCompanyDataScope(param, loginUser);
+        applyCompanyOrderModeIfEnabled(param);
+        fillListTimeRange(param);
         List<FsStoreOrderItemExportVO> list=orderItemService.selectFsStoreOrderItemListExportVO(param);
         //对手机号脱敏
         if(list!=null){
@@ -700,4 +668,34 @@ public class FsStoreOrderScrmController extends BaseController
         }
         return roles.stream().anyMatch(role -> roleKey.equals(role.getRoleKey()));
     }
+
+    /**
+     * 销售端数据权限:限定本公司;非管理员且非财务订单角色仅看本人订单
+     */
+    private void applyCompanyDataScope(FsStoreOrderParam param, LoginUser loginUser) {
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (!"00".equals(loginUser.getUser().getUserType()) && !hasRoleKey(loginUser, "finance_order_goods")) {
+            param.setCompanyUserId(loginUser.getUser().getUserId());
+        }
+    }
+
+    private void applyCompanyOrderModeIfEnabled(FsStoreOrderParam param) {
+        String configJson = configService.selectConfigByKey("his.store");
+        StoreConfig storeConfig = JSONUtil.toBean(configJson, StoreConfig.class);
+        if (storeConfig != null && Boolean.TRUE.equals(storeConfig.getEnableCompanyOrderMode())) {
+            param.setIsCompanyOrder(1);
+        }
+    }
+
+    private void fillListTimeRange(FsStoreOrderParam param) {
+        if (!StringUtils.isEmpty(param.getCreateTimeRange())) {
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getPayTimeRange())) {
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if (!StringUtils.isEmpty(param.getDeliveryImportTimeRange())) {
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+    }
 }

+ 1 - 1
fs-company/src/main/java/com/fs/hisStore/utils/CityTreeUtil.java

@@ -1,5 +1,5 @@
 package com.fs.hisStore.utils;
-import com.fs.store.vo.CityVO;
+import com.fs.hisStore.vo.CityVO;
 import org.springframework.util.CollectionUtils;
 
 import java.util.ArrayList;

+ 1 - 1
fs-company/src/main/java/com/fs/hisStore/vo/CityVO.java

@@ -1,4 +1,4 @@
-package com.fs.store.vo;
+package com.fs.hisStore.vo;
 
 import lombok.Getter;
 import lombok.Setter;

+ 3 - 0
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -394,6 +394,9 @@ public class Task {
         }
     }
 
+    /**
+    * 执行自动化任务 脚本
+    */
     private void processAutoTask(Set<String> range) {
         for (String liveAutoTask : range) {
             LiveAutoTask task = JSON.parseObject(liveAutoTask, LiveAutoTask.class);

+ 5 - 3
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiCallTaskNode.java

@@ -29,6 +29,7 @@ import com.fs.enums.NodeTypeEnum;
 import com.fs.enums.TaskTypeEnum;
 import com.fs.his.config.CidPhoneConfig;
 import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 
 import java.util.*;
@@ -268,12 +269,13 @@ public class AiCallTaskNode extends AbstractWorkflowNode {
         try{
             CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(robotic.getCompanyId(), "cId.config");
             CidPhoneConfig cidConf = JSONObject.parseObject(companyConfig.getConfigValue(), CidPhoneConfig.class);
-            if(null != cidConf && StringUtils.isNotBlank(cidConf.getCallbackUrl())){
+            if(null != cidConf && !StringUtil.strIsNullOrEmpty(cidConf.getCallbackUrl())){
                 callBackUrl = cidConf.getCallbackUrl();
             }
+
             //读取总后台配置
-            if(StringUtils.isBlank(callBackUrl)){
-                String s = configService.selectConfigByKey("cId.config");
+            if(StringUtil.strIsNullOrEmpty(callBackUrl)){
+                String s = configService.selectConfigByKey("cid.config");
                 JSONObject obj = JSONObject.parseObject(s);
                 if(null != obj && obj.containsKey("callbackUrl") && StringUtils.isNotBlank(obj.getString("callbackUrl"))){
                     callBackUrl = obj.getString("callbackUrl");

+ 8 - 4
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -140,10 +140,10 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     @Select("select l.*,v.title,c.course_name from fs_course_watch_log l LEFT JOIN fs_user_course_video v ON v.video_id = l.video_id LEFT JOIN fs_user_course c ON c.course_id = l.course_id WHERE l.qw_external_contact_id =#{id} ORDER BY l.create_time  desc LIMIT 1  ")
     FsCourseWatchLogVO selectFsCourseWatchLogByExtId(Long id);
 
-    @Select("select l.qw_external_contact_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id FROM fs_course_watch_log l LEFT JOIN qw_external_contact ext ON ext.id =l.qw_external_contact_id  where l.sop_id=#{SopId} and  date(l.create_time)= CURDATE() and l.log_type =3 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 ) ")
+    @Select("select l.qw_external_contact_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id FROM fs_course_watch_log l LEFT JOIN qw_external_contact ext ON ext.id =l.qw_external_contact_id  where l.sop_id=#{SopId} and  AND l.create_time >= CURDATE() AND l.create_time < CURDATE() + INTERVAL 1 DAY  and l.log_type =3 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 ) ")
     List<FsCourseWatchLogTaskVO> selectFsCourseWatchLogByDaySopId3(@Param("SopId")String SopId);
 
-    @Select("select l.qw_external_contact_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id FROM fs_course_watch_log l LEFT JOIN qw_external_contact ext ON ext.id =l.qw_external_contact_id  where l.sop_id=#{SopId} and  date(l.create_time)= CURDATE() and l.log_type =4 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 ) ")
+    @Select("select l.qw_external_contact_id,l.company_id,l.company_user_id,l.log_type,ext.`level`,l.qw_user_id FROM fs_course_watch_log l LEFT JOIN qw_external_contact ext ON ext.id =l.qw_external_contact_id  where l.sop_id=#{SopId} and  AND l.create_time >= CURDATE() AND l.create_time < CURDATE() + INTERVAL 1 DAY and l.log_type =4 and l.video_id not in (select video_id from fs_user_course_video WHERE is_first=1 ) ")
     List<FsCourseWatchLogTaskVO> selectFsCourseWatchLogByDaySopId4(@Param("SopId")String SopId);
 
 //    @Select("   SELECT\n" +
@@ -625,8 +625,12 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     List<WatchLogDTO> selectFsCourseWatchLog30DayByExtId(@Param("extId") Long extId);
 
     @Select("SELECT * FROM fs_course_watch_log " +
-            "WHERE log_type = 2 AND send_finish_msg = 0 and send_type = 2 " +
-            "AND send_type = 2 and finish_time >= #{startDate} AND finish_time < #{endDate} and log_id > #{maxId} order by log_id asc  " +
+            "WHERE send_type = 2 " +
+            "AND log_type = 2 " +
+            "AND send_finish_msg = 0 " +
+            "AND finish_time >= #{startDate} AND finish_time < #{endDate} " +
+            "AND log_id > #{maxId} " +
+            "ORDER BY log_id ASC " +
             "LIMIT #{limit}")
     List<FsCourseWatchLog> selectFsCourseWatchLogFinishBatchByDate(
             @Param("startDate") Date startDate,

+ 8 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1618,6 +1618,10 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 return R.error(401, "授权后可继续!");
             }
 
+            if("wxcf4c7fb36154bf30".equals(param.getAppId())){
+                config.setRewardType(3);
+            }
+
             log.info("奖励类型:{}", config.getRewardType());
             // 根据奖励类型发放不同奖励
             switch (config.getRewardType()) {
@@ -1639,7 +1643,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                     if (!Objects.equals(sendRed.get("code"), 200)) {
                         return sendRed;
                     }
-                    return sendIntegralReward(param, user, watchLog, config);
+                    R r = sendIntegralReward(param, user, watchLog, config);
+                    r.put("res",sendRed);
+                    return r;
                 default:
                     return R.error("参数错误!");
             }
@@ -2536,7 +2542,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         logger.info("发放奖励====================》看课记录,{}", log);
 
         // 红德堂不要积分转红包
-        if (CloudHostUtils.hasCloudHostName("弘德堂")) {
+        if (CloudHostUtils.hasCloudHostName("弘德堂") || CloudHostUtils.hasCloudHostName("恒春来")) {
             return R.ok("奖励发放成功").put("rewardType", config.getRewardType());
         }
 

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

@@ -60,6 +60,9 @@ public class FsUserCourseVideoH5DVO extends BaseEntity
      */
     private String showProduct;
 
+    private Integer isDel;
+    private Integer isOnPut;
+
     /**
      * 商品列表
      */

+ 2 - 1
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -409,7 +409,8 @@ public class AiHookServiceImpl implements AiHookService {
         }
         if(user.getFastGptRoleId()==null){
             log.error("未绑定角色");
-            return userIsReply(sender, uid, user);
+            return R.ok();
+            //return userIsReply(sender, uid, user);
         }
 
         if(user.getAiStatus() == 1){

+ 10 - 0
fs-service/src/main/java/com/fs/his/domain/FsCourseCouponUser.java

@@ -58,4 +58,14 @@ public class FsCourseCouponUser {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @TableField(fill = FieldFill.INSERT_UPDATE)
     private Date updateTime;
+
+
+    /** 类型  1课程发的 2直播发的 */
+    @Excel(name = "类型  1课程发的 2直播发的")
+    private Integer sendType;
+
+
+    /** 直播间id */
+    @Excel(name = "直播间id")
+    private Long liveId;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -153,4 +153,7 @@ public class FsIntegralOrder
     @ApiModelProperty("商品对应的数量(多个)")
     private String quantityCart;
 
+    //虚拟手机号
+    private String virtualPhone;
+
 }

+ 27 - 7
fs-service/src/main/java/com/fs/his/mapper/FsCourseCouponUserMapper.java

@@ -5,19 +5,20 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsCourseCouponUser;
 import com.fs.his.vo.CourseCouponUserListUVO;
 import com.fs.his.vo.FsCourseCouponUserRecordVO;
+import com.fs.his.vo.FsCourseCouponUserVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 /**
  * 用户看课优惠券Mapper接口
- * 
+ *
  * @author fs
  * @date 2026-05-13
  */
 public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>{
     /**
      * 查询用户看课优惠券
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 用户看课优惠券
      */
@@ -25,15 +26,34 @@ public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>
 
     /**
      * 查询用户看课优惠券列表
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 用户看课优惠券集合
      */
     List<FsCourseCouponUser> selectFsCourseCouponUserList(FsCourseCouponUser fsCourseCouponUser);
 
+    @Select("<script>" +
+            "select cu.id,cu.coupon_id,cu.user_id,cu.limit_time,cu.start_time," +
+            "cu.create_time,cu.update_time,cu.status,fu.nick_name,cc.title as couponTitle " +
+            "from fs_course_coupon_user cu " +
+            "LEFT JOIN fs_course_coupon cc on cu.coupon_id = cc.id " +
+            "LEFT JOIN fs_user fu on cu.user_id = fu.user_id " +
+            "<where>" +
+            "    <if test='data != null'>" +
+            "    <if test='data.couponId != null'> and cu.coupon_id = #{data.couponId}</if>" +
+            "    <if test='data.userId != null'> and cu.user_id = #{data.userId}</if>" +
+            "    <if test='data.status != null'> and cu.status = #{data.status}</if>" +
+            "    <if test='data.sendType != null'> and cu.send_type = #{data.sendType}</if>" +
+            "    <if test='data.liveId != null'> and cu.live_id = #{data.liveId}</if>" +
+            "  </if>" +
+            "</where>" +
+            "order by cu.create_time desc" +
+            "</script>")
+    List<FsCourseCouponUserVO> selectFsCourseCouponUserListVO(@Param("data") FsCourseCouponUser fsCourseCouponUser);
+
     /**
      * 新增用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -41,7 +61,7 @@ public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>
 
     /**
      * 修改用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -49,7 +69,7 @@ public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>
 
     /**
      * 删除用户看课优惠券
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 结果
      */
@@ -57,7 +77,7 @@ public interface FsCourseCouponUserMapper extends BaseMapper<FsCourseCouponUser>
 
     /**
      * 批量删除用户看课优惠券
-     * 
+     *
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */

+ 7 - 1
fs-service/src/main/java/com/fs/his/mapper/FsUserCouponMapper.java

@@ -67,7 +67,13 @@ public interface FsUserCouponMapper
     public int deleteFsUserCouponByIds(Long[] ids);
 
     @Select({"<script> " +
-            " select uc.*,u.nick_name,u.phone,c.title,c.price,c.coupon_type,su.nick_name sendUserName,com.company_name,comu.user_name  company_user_name FROM fs_user_coupon uc LEFT JOIN fs_user u ON uc.user_id=u.user_id LEFT JOIN fs_coupon c on c.coupon_id=uc.coupon_id LEFT JOIN sys_user su ON su.user_id =uc.send_user_id LEFT JOIN company com ON com.company_id=uc.company_id LEFT JOIN company_user comu on comu.user_id=uc.company_user_id "+
+            " select uc.*,u.nick_name,u.phone,c.title,c.price,c.coupon_type,su.nick_name sendUserName,com.company_name,comu.user_name  company_user_name" +
+            " FROM fs_user_coupon uc  " +
+            "LEFT JOIN fs_user u ON uc.user_id=u.user_id " +
+            "LEFT JOIN fs_coupon c on c.coupon_id=uc.coupon_id " +
+            "LEFT JOIN sys_user su ON su.user_id =uc.send_user_id " +
+            "LEFT JOIN company com ON com.company_id=uc.company_id " +
+            "LEFT JOIN company_user comu on comu.user_id=uc.company_user_id "+
             " <where>  \n" +
             "            <if test=\"couponId != null \"> and uc.coupon_id = #{couponId}</if>\n" +
             "            <if test=\"userId != null \"> and uc.user_id = #{userId}</if>\n" +

+ 9 - 7
fs-service/src/main/java/com/fs/his/service/IFsCourseCouponUserService.java

@@ -6,17 +6,18 @@ import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsCourseCouponUser;
 import com.fs.his.vo.CourseCouponUserListUVO;
 import com.fs.his.vo.FsCourseCouponUserRecordVO;
+import com.fs.his.vo.FsCourseCouponUserVO;
 
 /**
  * 用户看课优惠券Service接口
- * 
+ *
  * @author fs
  * @date 2026-05-13
  */
 public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>{
     /**
      * 查询用户看课优惠券
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 用户看课优惠券
      */
@@ -24,15 +25,16 @@ public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>
 
     /**
      * 查询用户看课优惠券列表
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 用户看课优惠券集合
      */
     List<FsCourseCouponUser> selectFsCourseCouponUserList(FsCourseCouponUser fsCourseCouponUser);
+    List<FsCourseCouponUserVO> selectFsCourseCouponUserListVO(FsCourseCouponUser fsCourseCouponUser);
 
     /**
      * 新增用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -40,7 +42,7 @@ public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>
 
     /**
      * 修改用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -48,7 +50,7 @@ public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>
 
     /**
      * 批量删除用户看课优惠券
-     * 
+     *
      * @param ids 需要删除的用户看课优惠券主键集合
      * @return 结果
      */
@@ -56,7 +58,7 @@ public interface IFsCourseCouponUserService extends IService<FsCourseCouponUser>
 
     /**
      * 删除用户看课优惠券信息
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 结果
      */

+ 8 - 7
fs-service/src/main/java/com/fs/his/service/impl/FsCourseCouponServiceImpl.java

@@ -30,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 课程优惠券Service业务层处理
- * 
+ *
  * @author fs
  * @date 2026-05-13
  */
@@ -53,7 +53,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
     private FsCourseWatchLogMapper courseWatchLogMapper;
     /**
      * 查询课程优惠券
-     * 
+     *
      * @param id 课程优惠券主键
      * @return 课程优惠券
      */
@@ -65,7 +65,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
 
     /**
      * 查询课程优惠券列表
-     * 
+     *
      * @param fsCourseCoupon 课程优惠券
      * @return 课程优惠券
      */
@@ -77,7 +77,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
 
     /**
      * 新增课程优惠券
-     * 
+     *
      * @param fsCourseCoupon 课程优惠券
      * @return 结果
      */
@@ -91,7 +91,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
 
     /**
      * 修改课程优惠券
-     * 
+     *
      * @param fsCourseCoupon 课程优惠券
      * @return 结果
      */
@@ -104,7 +104,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
 
     /**
      * 批量删除课程优惠券
-     * 
+     *
      * @param ids 需要删除的课程优惠券主键
      * @return 结果
      */
@@ -116,7 +116,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
 
     /**
      * 删除课程优惠券信息
-     * 
+     *
      * @param id 课程优惠券主键
      * @return 结果
      */
@@ -371,6 +371,7 @@ public class FsCourseCouponServiceImpl extends ServiceImpl<FsCourseCouponMapper,
             couponUser.setLimitTime(fsCourseCoupon.getLimitTime());
             couponUser.setCreateTime(new Date());
             couponUser.setLogId(log.getLogId());
+            couponUser.setSendType(1);
             int i = courseCouponUserMapper.insertFsCourseCouponUser(couponUser);
 
             if (i > 0) {

+ 13 - 7
fs-service/src/main/java/com/fs/his/service/impl/FsCourseCouponUserServiceImpl.java

@@ -10,6 +10,7 @@ import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.his.vo.CourseCouponUserListUVO;
 import com.fs.his.vo.FsCourseCouponUserRecordVO;
+import com.fs.his.vo.FsCourseCouponUserVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.mapper.FsCourseCouponUserMapper;
@@ -18,7 +19,7 @@ import com.fs.his.service.IFsCourseCouponUserService;
 
 /**
  * 用户看课优惠券Service业务层处理
- * 
+ *
  * @author fs
  * @date 2026-05-13
  */
@@ -27,7 +28,7 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
 
     /**
      * 查询用户看课优惠券
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 用户看课优惠券
      */
@@ -39,7 +40,7 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
 
     /**
      * 查询用户看课优惠券列表
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 用户看课优惠券
      */
@@ -49,9 +50,14 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
         return baseMapper.selectFsCourseCouponUserList(fsCourseCouponUser);
     }
 
+    @Override
+    public List<FsCourseCouponUserVO> selectFsCourseCouponUserListVO(FsCourseCouponUser fsCourseCouponUser) {
+        return baseMapper.selectFsCourseCouponUserListVO(fsCourseCouponUser);
+    }
+
     /**
      * 新增用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -64,7 +70,7 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
 
     /**
      * 修改用户看课优惠券
-     * 
+     *
      * @param fsCourseCouponUser 用户看课优惠券
      * @return 结果
      */
@@ -77,7 +83,7 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
 
     /**
      * 批量删除用户看课优惠券
-     * 
+     *
      * @param ids 需要删除的用户看课优惠券主键
      * @return 结果
      */
@@ -89,7 +95,7 @@ public class FsCourseCouponUserServiceImpl extends ServiceImpl<FsCourseCouponUse
 
     /**
      * 删除用户看课优惠券信息
-     * 
+     *
      * @param id 用户看课优惠券主键
      * @return 结果
      */

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -1343,6 +1343,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
                 if (StringUtils.isNotBlank(vo.getDeliverySn())) {
                     param.setDeliverySn(vo.getDeliverySn());
                 }
+                if (StringUtils.isNotBlank(vo.getVirtualPhone())) {
+                    param.setVirtualPhone(vo.getVirtualPhone());
+                }
 
                 param.setDeliveryStatus((vo.getDeliveryStatus()==null|| vo.getDeliveryStatus().isEmpty())?null:Integer.valueOf(vo.getDeliveryStatus()));
                 param.setDeliveryType(vo.getDeliveryType().isEmpty()?null:vo.getDeliveryType());

+ 73 - 0
fs-service/src/main/java/com/fs/his/vo/FsCourseCouponUserVO.java

@@ -0,0 +1,73 @@
+package com.fs.his.vo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 用户看课优惠券对象 fs_course_coupon_user
+ *
+ * @author fs
+ * @date 2026-05-13
+ */
+@Data
+public class FsCourseCouponUserVO {
+
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 优惠券id */
+    @Excel(name = "优惠券id")
+    private Long couponId;
+    private String couponTitle;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    private String nickName;
+
+
+    /** 有效期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "有效期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date limitTime;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date startTime;
+
+    /** 核销状态 0-未核销 1-已核销 */
+    @Excel(name = "核销状态 0-未核销 1-已核销")
+    private Integer status;
+
+    /**
+     * 看课记录ID
+     */
+    private Long logId;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+
+    /** 类型  1课程发的 2直播发的 */
+    @Excel(name = "类型  1课程发的 2直播发的")
+    private Integer sendType;
+
+
+    /** 直播间id */
+    @Excel(name = "直播间id")
+    private Long liveId;
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java

@@ -27,6 +27,13 @@ public class FsIntegralOrderExcelVO implements Serializable {
     /** shou */
     @Excel(name = "收货人电话")
     private String userPhone;
+
+    /**
+     * 虚拟手机号
+     */
+    @Excel(name = "虚拟手机号", width = 30, sort = 45)
+    private String virtualPhone;
+
     /** 详情地址 */
     @Excel(name = "详情地址(例:广东省 韶关市 仁化县 亨特中心22楼)使用空格分割")
     private String userAddress;

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

@@ -81,4 +81,6 @@ public class FsIntegralOrderPVO extends BaseEntity
     private String deliveryType;
 
     private Integer deliveryStatus;
+
+    private String virtualPhone;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreAfterSalesScrmMapper.java

@@ -10,6 +10,7 @@ import com.fs.hisStore.domain.FsStoreAfterSalesScrm;
 import com.fs.hisStore.param.FsStoreAfterSalesQueryParam;
 import com.fs.hisStore.vo.FsStoreAfterSalesQueryVO;
 import com.fs.hisStore.vo.FsStoreAfterSalesVO;
+import com.fs.hisStore.vo.FsStoreOrderRefundAmountVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -324,6 +325,11 @@ public interface FsStoreAfterSalesScrmMapper
             "WHERE sas.order_code = #{orderCode} AND sas.status = 4 AND sas.is_del = 0 LIMIT 1")
     FsStoreAfterSalesScrm selectFsStoreAfterSalesByOrderCode(@Param("orderCode") String orderCode);
 
+    /**
+     * 按订单号批量查询退款金额(仅 order_code、refund_amount,财务已审核且未删除)
+     */
+    List<FsStoreOrderRefundAmountVO> selectRefundAmountByOrderCodes(@Param("orderCodes") List<String> orderCodes);
+
     /**
      * 售后详情(含一级/二级原因展示文案,来自落库字段)
      */

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

@@ -77,7 +77,7 @@ public interface FsStoreOrderItemScrmMapper
 
     @Select({"<script> " +
             "select i.*,o.user_id,u.nick_name,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,sp_latest.pay_money, sp_latest.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 " +
+            " o.order_code, o.pay_price, o.pay_money, o.deduction_price,o.pay_delivery, o.order_type, o.refund_price as refundPrice, psps.price " +
             ", CASE o.is_audit WHEN 1 THEN '是' ELSE '否' END AS isAudit " +
             ", sas.audit_remark as auditRemark, sas.reason_level1_text as reasonValue1, sas.reason_level2_text as reasonValue2 " +
             ", o.delivery_type" +

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

@@ -214,6 +214,16 @@ public interface IFsStoreOrderScrmService
 
     List<FsStoreOrderErpExportVO> selectFsStoreOrderListVOByExport(FsStoreOrderParam param);
 
+    /**
+     * 填充导出订单的退款类型、退款金额(退款中/已退款订单)
+     */
+    void fillOrderRefundExportInfo(List<? extends FsStoreOrderExportVO> list);
+
+    /**
+     * 填充导出订单明细的退款类型、退款金额(退款中/已退款订单)
+     */
+    void fillOrderItemRefundExportInfo(List<FsStoreOrderItemExportVO> list);
+
     List<FsStoreOrderPromotionExportVO> selectFsPromotionOrderListVOByExport(FsStoreOrderParam param);
 
     List<FsStorePayRemainOrderExportVO> selectFsStorePayRemainOrderListVOByExport(FsStoreOrderParam param);

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

@@ -7,14 +7,16 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.his.vo.FsStoreOrderItemListDVO;
 import com.fs.hisStore.dto.FsStoreCartDTO;
+import com.fs.hisStore.domain.FsStoreOrderItemScrm;
+import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
 import com.fs.hisStore.param.FsStoreOrderParam;
 import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
+import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
-import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
-import com.fs.hisStore.domain.FsStoreOrderItemScrm;
-import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
 
 /**
  * 订单详情Service业务层处理
@@ -28,6 +30,10 @@ public class FsStoreOrderItemScrmServiceImpl implements IFsStoreOrderItemScrmSer
     @Autowired
     private FsStoreOrderItemScrmMapper fsStoreOrderItemMapper;
 
+    @Autowired
+    @Lazy
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
+
     /**
      * 查询订单详情
      *
@@ -112,7 +118,9 @@ public class FsStoreOrderItemScrmServiceImpl implements IFsStoreOrderItemScrmSer
 
     @Override
     public List<FsStoreOrderItemExportVO> selectFsStoreOrderItemListExportVO(FsStoreOrderParam fsStoreOrder) {
-        return fsStoreOrderItemMapper.selectFsStoreOrderItemListExportVO(fsStoreOrder);
+        List<FsStoreOrderItemExportVO> list = fsStoreOrderItemMapper.selectFsStoreOrderItemListExportVO(fsStoreOrder);
+        fsStoreOrderScrmService.fillOrderItemRefundExportInfo(list);
+        return list;
     }
 
     @Override

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

@@ -91,6 +91,9 @@ import com.fs.hisStore.param.FsStoreOrderParam;
 import com.fs.hisStore.param.FsStoreOrderPayParam;
 import com.fs.hisStore.vo.*;
 import com.fs.hisStore.vo.FsStoreOrderErpExportVO;
+import com.fs.hisStore.vo.FsStoreOrderExportVO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportVO;
+import com.fs.hisStore.vo.FsStoreOrderRefundAmountVO;
 import com.fs.hisStore.vo.FsStoreOrderItemVO;
 import com.fs.hisStore.vo.FsStoreOrderVO;
 import com.fs.hisStore.vo.FsStoreProductAttrValueVO;
@@ -3975,12 +3978,125 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     @Override
     public List<FsStoreOrderErpExportVO> selectFsStoreOrderListVOByExport(FsStoreOrderParam param) {
+        List<FsStoreOrderErpExportVO> list;
         if (CloudHostUtils.hasCloudHostName("金牛明医","康年堂")){
-            return fsStoreOrderMapper.selectFsStoreOrderErpListVOByExport(param);
-
+            list = fsStoreOrderMapper.selectFsStoreOrderErpListVOByExport(param);
         } else {
-            return fsStoreOrderMapper.selectFsStoreOrderListVOByExport(param);
+            list = fsStoreOrderMapper.selectFsStoreOrderListVOByExport(param);
+        }
+        fillOrderRefundExportInfo(list);
+        return list;
+    }
+
+    @Override
+    public void fillOrderRefundExportInfo(List<? extends FsStoreOrderExportVO> list) {
+        if (CollectionUtil.isEmpty(list)) {
+            return;
+        }
+        List<String> needLookupOrderCodes = new ArrayList<>();
+        for (FsStoreOrderExportVO vo : list) {
+            if (!isRefundedOrderStatus(vo.getStatus())) {
+                continue;
+            }
+            if (needsAfterSalesRefundLookup(vo.getRefundPrice()) && StrUtil.isNotBlank(vo.getOrderCode())) {
+                needLookupOrderCodes.add(vo.getOrderCode());
+            }
+        }
+        Map<String, BigDecimal> refundAmountMap = batchLoadRefundAmountByOrderCodes(needLookupOrderCodes);
+        for (FsStoreOrderExportVO vo : list) {
+            if (!isRefundedOrderStatus(vo.getStatus())) {
+                continue;
+            }
+            BigDecimal refundAmount = vo.getRefundPrice();
+            if (needsAfterSalesRefundLookup(refundAmount)) {
+                refundAmount = refundAmountMap.get(vo.getOrderCode());
+                if (refundAmount != null) {
+                    vo.setRefundPrice(refundAmount);
+                }
+            }
+            BigDecimal payAmount = vo.getPayMoney() != null ? vo.getPayMoney() : vo.getPayPrice();
+            if (refundAmount != null && payAmount != null) {
+                vo.setRefundType(refundAmount.compareTo(payAmount) < 0 ? "部分退款" : "全额退款");
+            }
+        }
+    }
+
+    private static final int REFUND_AMOUNT_BATCH_SIZE = 1000;
+
+    private Map<String, BigDecimal> batchLoadRefundAmountByOrderCodes(List<String> orderCodes) {
+        if (CollectionUtil.isEmpty(orderCodes)) {
+            return Collections.emptyMap();
+        }
+        List<String> distinctOrderCodes = orderCodes.stream()
+                .filter(StrUtil::isNotBlank)
+                .distinct()
+                .collect(Collectors.toList());
+        if (distinctOrderCodes.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        Map<String, BigDecimal> refundAmountMap = new HashMap<>(distinctOrderCodes.size());
+        for (int i = 0; i < distinctOrderCodes.size(); i += REFUND_AMOUNT_BATCH_SIZE) {
+            int end = Math.min(i + REFUND_AMOUNT_BATCH_SIZE, distinctOrderCodes.size());
+            List<String> batch = distinctOrderCodes.subList(i, end);
+            List<FsStoreOrderRefundAmountVO> rows = fsStoreAfterSalesScrmMapper.selectRefundAmountByOrderCodes(batch);
+            if (CollectionUtil.isEmpty(rows)) {
+                continue;
+            }
+            for (FsStoreOrderRefundAmountVO row : rows) {
+                if (row.getOrderCode() != null && row.getRefundAmount() != null) {
+                    refundAmountMap.put(row.getOrderCode(), row.getRefundAmount());
+                }
+            }
+        }
+        return refundAmountMap;
+    }
+
+    private boolean isRefundedOrderStatus(String status) {
+        if (status == null) {
+            return false;
+        }
+        try {
+            int orderStatus = Integer.parseInt(status);
+            return orderStatus == -1 || orderStatus == -2;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    private boolean needsAfterSalesRefundLookup(BigDecimal refundPrice) {
+        return refundPrice == null || refundPrice.compareTo(BigDecimal.ZERO) == 0;
+    }
 
+    @Override
+    public void fillOrderItemRefundExportInfo(List<FsStoreOrderItemExportVO> list) {
+        if (CollectionUtil.isEmpty(list)) {
+            return;
+        }
+        List<String> needLookupOrderCodes = new ArrayList<>();
+        for (FsStoreOrderItemExportVO vo : list) {
+            if (!isRefundedOrderStatus(vo.getStatus())) {
+                continue;
+            }
+            if (needsAfterSalesRefundLookup(vo.getRefundPrice()) && StrUtil.isNotBlank(vo.getOrderCode())) {
+                needLookupOrderCodes.add(vo.getOrderCode());
+            }
+        }
+        Map<String, BigDecimal> refundAmountMap = batchLoadRefundAmountByOrderCodes(needLookupOrderCodes);
+        for (FsStoreOrderItemExportVO vo : list) {
+            if (!isRefundedOrderStatus(vo.getStatus())) {
+                continue;
+            }
+            BigDecimal refundAmount = vo.getRefundPrice();
+            if (needsAfterSalesRefundLookup(refundAmount)) {
+                refundAmount = refundAmountMap.get(vo.getOrderCode());
+            }
+            if (refundAmount != null) {
+                vo.setRefundPrice(refundAmount);
+            }
+            BigDecimal payAmount = vo.getPayMoney() != null ? vo.getPayMoney() : vo.getPayPrice();
+            if (refundAmount != null && payAmount != null) {
+                vo.setRefundType(refundAmount.compareTo(payAmount) < 0 ? "部分退款" : "全额退款");
+            }
         }
     }
 

+ 6 - 2
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderExportVO.java

@@ -170,6 +170,10 @@ public class FsStoreOrderExportVO implements Serializable
 ////    @Excel(name = "不退款的理由")
 //    private String refundReason;
 
+    /** 退款类型:全额退款 / 部分退款 */
+    @Excel(name = "退款类型")
+    private String refundType;
+
     /** 退款金额 */
     @Excel(name = "退款金额",cellType= Excel.ColumnType.NUMERIC)
     private BigDecimal refundPrice;
@@ -198,8 +202,8 @@ public class FsStoreOrderExportVO implements Serializable
 //    /** 给用户退了多少积分 */
 //    private BigDecimal backIntegral;
 
-    /** 备注 */
-    @Excel(name = "备注")
+    /** 用户订单备注 */
+    @Excel(name = "用户备注")
     private String mark;
 
 //    /** 是否删除 */

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

@@ -143,5 +143,11 @@ public class FsStoreOrderItemExportVO implements Serializable
     @Excel(name = "售后备注")
     private String auditRemark;
 
+    @Excel(name = "退款类型")
+    private String refundType;
+
+    @Excel(name = "退款金额", cellType = Excel.ColumnType.NUMERIC)
+    private BigDecimal refundPrice;
+
 
 }

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportZMVO.java

@@ -132,4 +132,10 @@ public class FsStoreOrderItemExportZMVO implements Serializable {
 
     @Excel(name = "售后备注")
     private String auditRemark;
+
+    @Excel(name = "退款类型")
+    private String refundType;
+
+    @Excel(name = "退款金额", cellType = Excel.ColumnType.NUMERIC)
+    private BigDecimal refundPrice;
 }

+ 16 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderRefundAmountVO.java

@@ -0,0 +1,16 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 订单导出批量填充退款金额(仅必要字段)
+ */
+@Data
+public class FsStoreOrderRefundAmountVO {
+
+    private String orderCode;
+
+    private BigDecimal refundAmount;
+}

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

@@ -1342,7 +1342,7 @@ public class LiveDataServiceImpl implements ILiveDataService {
         Live live = liveMapper.selectLiveByLiveId(liveId);
         LiveWatchConfig config = JSON.parseObject(live.getConfigJson(), LiveWatchConfig.class);
 
-        if (config.getEnabled() && 3 == config.getParticipateCondition()) {
+        if (config!=null && config.getEnabled() && 3 == config.getParticipateCondition()) {
 
             Long completionRate = config.getCompletionRate();
 

+ 69 - 7
fs-service/src/main/java/com/fs/live/service/impl/LiveRedPacketLogServiceImpl.java

@@ -26,9 +26,9 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.BalanceRollbackErrorMapper;
 import com.fs.course.param.FsCourseSendRewardUParam;
 import com.fs.course.service.impl.FsUserCourseVideoServiceImpl;
-import com.fs.his.domain.FsUser;
-import com.fs.his.domain.FsUserIntegralLogs;
-import com.fs.his.domain.FsUserWx;
+import com.fs.his.domain.*;
+import com.fs.his.mapper.FsCourseCouponMapper;
+import com.fs.his.mapper.FsCourseCouponUserMapper;
 import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
@@ -121,6 +121,12 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
     @Autowired
     private ILiveCouponUserService liveCouponUserService;
 
+    @Autowired
+    private FsCourseCouponMapper fsCourseCouponMapper;
+
+    @Autowired
+    private FsCourseCouponUserMapper courseCouponUserMapper;
+
 
     /**
      * 查询直播红包 记录
@@ -483,13 +489,13 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
         // 积分
         if (matched.getRewardTypes() != null && matched.getRewardTypes().contains("2")) {
             config.setScoreAmount(matched.getScoreAmount());
-            lastResult = sendLiveIntegralReward(param, user, watchUser, config);
+            sendLiveIntegralReward(param, user, watchUser, config);
             hasReward = true;
             combinedRewardType |= 2;
         }
-        // 核销卷
+        // 课程优惠卷(核销卷
         if (matched.getRewardTypes() != null && matched.getRewardTypes().contains("3")) {
-            lastResult=sendIssueCoupon(param.getLiveId(),user.getUserId(),matched.getCouponId(),watchUser);
+            sendIssueCourseCoupon(param.getLiveId(),user.getUserId(),matched.getCouponId(),watchUser);
             hasReward = true;
             combinedRewardType |= 4;
         }
@@ -528,7 +534,7 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
     }
 
     /**
-    * 发放优惠
+    * 发放直播核销
     */
     public R sendIssueCoupon(Long liveId, Long userId, Long couponId,LiveWatchUser watchUser) {
 
@@ -602,6 +608,62 @@ public class LiveRedPacketLogServiceImpl extends ServiceImpl<LiveRedPacketLogMap
         return R.ok();
     }
 
+    /**
+     * 发放直播-课程优惠卷卷
+     */
+    public R sendIssueCourseCoupon(Long liveId, Long userId, Long couponId,LiveWatchUser watchUser) {
+
+        //优惠券
+        FsCourseCoupon fsCourseCoupon = fsCourseCouponMapper.selectFsCourseCouponById(couponId);
+
+        if (fsCourseCoupon == null) {
+            watchUser.setRemark("核销卷不存在-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        if (fsCourseCoupon.getStatus() != 1) {
+            watchUser.setRemark("核销卷已停用-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        if (fsCourseCoupon.getRemainNumber() <= 0) {
+            watchUser.setRemark("核销卷剩余数量不足-发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        //用户领取优惠券总数
+        Integer count = courseCouponUserMapper.selectCountByUserIdAndCouponId(userId, couponId);
+        if (count >= fsCourseCoupon.getLimitCount()) {
+            watchUser.setRemark("该核销卷领取达到上限,发放失败");
+            watchUserMapper.updateLiveWatchUser(watchUser);
+            return  R.ok();
+        }
+
+        //发放优惠券
+        FsCourseCouponUser couponUser = new FsCourseCouponUser();
+        couponUser.setCouponId(couponId);
+        couponUser.setUserId(userId);
+        couponUser.setStartTime(new Date());
+        couponUser.setLimitTime(fsCourseCoupon.getLimitTime());
+        couponUser.setCreateTime(new Date());
+        couponUser.setLiveId(liveId);
+        couponUser.setSendType(2);
+        int i = courseCouponUserMapper.insertFsCourseCouponUser(couponUser);
+
+        //扣库存
+        if (i > 0) {
+            FsCourseCoupon coupon = new FsCourseCoupon();
+            coupon.setId(couponId);
+            coupon.setRemainNumber(fsCourseCoupon.getRemainNumber() - 1);
+            fsCourseCouponMapper.updateFsCourseCoupon(coupon);
+        }
+
+        return R.ok();
+    }
+
     @Override
     public R syncLiveRedPacket(String outBatchNo, String batchId) {
         LiveRedPacketLog log = redPacketLogMapper.selectLiveRedPacketLogByBatchNo(outBatchNo);

+ 6 - 3
fs-service/src/main/java/com/fs/qw/mapper/HyWorkTaskMapper.java

@@ -72,10 +72,12 @@ public interface HyWorkTaskMapper extends BaseMapper<HyWorkTask>{
 
     List<QwWorkTaskListVO> selectHyWorkTaskListVONew(QwWorkTaskListParam qwWorkTask);
 
-    @Select("select ext_id from hy_work_task where type=2 and DATE(create_time) = CURDATE() ")
+    @Select("select ext_id from hy_work_task where type = 2 " +
+            "and create_time >= curdate() and create_time < curdate() + interval 1 day")
     List<Long> selectHyWorkTaskByType();
 
-    @Select("select id,ext_id from hy_work_task where type=2 and status=0 and DATE(create_time) = CURDATE() ")
+    @Select("select id, ext_id from hy_work_task where type = 2 and status = 0 " +
+            "and create_time >= curdate() and create_time < curdate() + interval 1 day")
     List<QwWorkTask> selectHyWorkTaskByTypeStatus();
     @Update({
             "<script>",
@@ -89,7 +91,8 @@ public interface HyWorkTaskMapper extends BaseMapper<HyWorkTask>{
     })
     void updateHyWorkTaskStatus(@Param("overIds")List<Long> overIds);
 
-    @Select("select id,ext_id from hy_work_task where type=2 and status=1 and DATE(create_time) = CURDATE() ")
+    @Select("select id, ext_id from hy_work_task where type = 2 and status = 1 " +
+            "and create_time >= curdate() and create_time < curdate() + interval 1 day")
     List<QwWorkTask> selectHyWorkTaskByTypeStatus1();
     @Update({
             "<script>",

+ 4 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java

@@ -94,10 +94,12 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
             "order by t.score desc,e.last_watch_time ,t.id desc "+
             "</script>"})
     List<QwWorkTaskListVO> selectQwWorkTaskListVO(QwWorkTaskListParam qwWorkTask);
-    @Select("select ext_id from qw_work_task where type=2 and DATE(create_time) = CURDATE() ")
+    @Select("select ext_id from qw_work_task where type = 2 " +
+            "and create_time >= curdate() and create_time < curdate() + interval 1 day")
     List<Long> selectQwWorkTaskByType();
 
-    @Select("select id,ext_id from qw_work_task where type=2 and status=0 and DATE(create_time) = CURDATE() ")
+    @Select("select id, ext_id from qw_work_task where type = 2 and status = 0 " +
+            "and create_time >= curdate() and create_time < curdate() + interval 1 day")
     List<QwWorkTask> selectQwWorkTaskByTypeStatus();
     @Update({
             "<script>",

+ 58 - 0
fs-service/src/main/java/com/fs/scrm/domain/ScrmCustomerInfo.java

@@ -0,0 +1,58 @@
+package com.fs.scrm.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * SCRM客户信息对象 scrm_customer_info
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ScrmCustomerInfo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** qw_external_contact.id */
+    private Long externalContactId;
+
+    /** 客户来源 */
+    private String source;
+
+    /** 客户类型 */
+    private String customerType;
+
+    /** 跟进阶段 */
+    private String visitStatus;
+
+    /** 自定义标签(逗号分隔) */
+    private String tags;
+
+    /** 备注 */
+    private String remark;
+
+    /** 最新联系时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastTime;
+
+    /** 跟进内容 */
+    private String followContent;
+
+    /** 跟进记录JSON([{registerDesc, visitStatus, visitTime}]) */
+    private String visitRecords;
+
+    /** 录音记录JSON([{fileName, fileUrl, fileSize, duration, uploadTime}]) */
+    private String recordings;
+
+    /** 公司ID */
+    private Long companyId;
+}

+ 36 - 0
fs-service/src/main/java/com/fs/scrm/mapper/ScrmCustomerInfoMapper.java

@@ -0,0 +1,36 @@
+package com.fs.scrm.mapper;
+
+import com.fs.scrm.domain.ScrmCustomerInfo;
+import com.fs.scrm.param.ScrmCustomerInfoQueryParam;
+import com.fs.scrm.vo.ScrmCustomerInfoVO;
+
+import java.util.List;
+
+/**
+ * SCRM客户信息Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+public interface ScrmCustomerInfoMapper
+{
+    /**
+     * 查询SCRM客户信息(联查qw_external_contact)
+     */
+    List<ScrmCustomerInfoVO> selectScrmCustomerInfoList(ScrmCustomerInfoQueryParam param);
+
+    /**
+     * 根据externalContactId查询
+     */
+    ScrmCustomerInfo selectScrmCustomerInfoByExternalContactId(Long externalContactId);
+
+    /**
+     * 新增
+     */
+    int insertScrmCustomerInfo(ScrmCustomerInfo scrmCustomerInfo);
+
+    /**
+     * 修改
+     */
+    int updateScrmCustomerInfo(ScrmCustomerInfo scrmCustomerInfo);
+}

+ 42 - 0
fs-service/src/main/java/com/fs/scrm/param/ScrmCustomerInfoQueryParam.java

@@ -0,0 +1,42 @@
+package com.fs.scrm.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * SCRM客户信息查询参数
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@Data
+public class ScrmCustomerInfoQueryParam implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 分页参数 */
+    private Integer pageNum;
+    private Integer pageSize;
+
+    /** qw_external_contact.id */
+    private Long externalContactId;
+
+    /** 小程序用户ID */
+    private Long fsUserId;
+
+    /** 公司ID */
+    private Long companyId;
+
+    /** 公司员工ID */
+    private Long companyUserId;
+
+    /** 创建时间范围 */
+    private String createTimeRange;
+
+    /** 创建时间开始 */
+    private String createTimeStart;
+
+    /** 创建时间结束 */
+    private String createTimeEnd;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/scrm/service/IScrmCustomerInfoService.java

@@ -0,0 +1,14 @@
+package com.fs.scrm.service;
+
+import com.fs.scrm.domain.ScrmCustomerInfo;
+import com.fs.scrm.param.ScrmCustomerInfoQueryParam;
+import com.fs.scrm.vo.ScrmCustomerInfoVO;
+import java.util.List;
+
+public interface IScrmCustomerInfoService {
+    List<ScrmCustomerInfoVO> selectScrmCustomerInfoList(ScrmCustomerInfoQueryParam var1);
+
+    int saveOrUpdateScrmCustomerInfo(ScrmCustomerInfo var1);
+
+    ScrmCustomerInfo selectScrmCustomerInfoByExternalContactId(Long var1);
+}

+ 39 - 0
fs-service/src/main/java/com/fs/scrm/service/impl/ScrmCustomerInfoServiceImpl.java

@@ -0,0 +1,39 @@
+package com.fs.scrm.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.scrm.domain.ScrmCustomerInfo;
+import com.fs.scrm.mapper.ScrmCustomerInfoMapper;
+import com.fs.scrm.param.ScrmCustomerInfoQueryParam;
+import com.fs.scrm.service.IScrmCustomerInfoService;
+import com.fs.scrm.vo.ScrmCustomerInfoVO;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ScrmCustomerInfoServiceImpl implements IScrmCustomerInfoService {
+    @Autowired
+    private ScrmCustomerInfoMapper scrmCustomerInfoMapper;
+
+    public ScrmCustomerInfoServiceImpl() {
+    }
+
+    public List<ScrmCustomerInfoVO> selectScrmCustomerInfoList(ScrmCustomerInfoQueryParam param) {
+        return this.scrmCustomerInfoMapper.selectScrmCustomerInfoList(param);
+    }
+
+    public int saveOrUpdateScrmCustomerInfo(ScrmCustomerInfo scrmCustomerInfo) {
+        ScrmCustomerInfo exist = this.scrmCustomerInfoMapper.selectScrmCustomerInfoByExternalContactId(scrmCustomerInfo.getExternalContactId());
+        if (exist != null) {
+            scrmCustomerInfo.setUpdateTime(DateUtils.getNowDate());
+            return this.scrmCustomerInfoMapper.updateScrmCustomerInfo(scrmCustomerInfo);
+        } else {
+            scrmCustomerInfo.setCreateTime(DateUtils.getNowDate());
+            return this.scrmCustomerInfoMapper.insertScrmCustomerInfo(scrmCustomerInfo);
+        }
+    }
+
+    public ScrmCustomerInfo selectScrmCustomerInfoByExternalContactId(Long externalContactId) {
+        return this.scrmCustomerInfoMapper.selectScrmCustomerInfoByExternalContactId(externalContactId);
+    }
+}

+ 104 - 0
fs-service/src/main/java/com/fs/scrm/vo/ScrmCustomerInfoVO.java

@@ -0,0 +1,104 @@
+package com.fs.scrm.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * SCRM客户信息VO(联查qw_external_contact)
+ *
+ * @author fs
+ * @date 2025-06-17
+ */
+@Data
+public class ScrmCustomerInfoVO implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    // ========== scrm_customer_info 字段 ==========
+    /** scrm_customer_info.id */
+    private Long scrmInfoId;
+
+    /** qw_external_contact.id */
+    private Long externalContactId;
+
+    /** 客户来源 */
+    private String source;
+
+    /** 客户类型 */
+    private String customerType;
+
+    /** 跟进阶段 */
+    private String visitStatus;
+
+    /** 自定义标签(逗号分隔) */
+    private String tags;
+
+    /** 备注 */
+    private String remark;
+
+    /** 最新联系时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastTime;
+
+    /** 跟进内容 */
+    private String followContent;
+
+    /** 跟进记录JSON */
+    private String visitRecords;
+
+    /** 录音记录JSON */
+    private String recordings;
+
+    // ========== qw_external_contact 字段 ==========
+    /** 企微客户名称 */
+    private String name;
+
+    /** 企微用户ID */
+    private String userId;
+
+    /** 外部联系人ID */
+    private String externalUserId;
+
+    /** 备注手机号 */
+    private String remarkMobiles;
+
+    /** 性别 */
+    private Integer gender;
+
+    /** 用户类别 */
+    private Integer type;
+
+    /** 来源 */
+    private Integer addWay;
+
+    /** 状态 */
+    private Integer status;
+
+    /** 企微标签 */
+    private String tagIds;
+
+    /** 小程序用户ID */
+    private Long fsUserId;
+
+    /** 企微员工ID */
+    private Long qwUserId;
+
+    /** 公司员工ID */
+    private Long companyUserId;
+
+    /** 企业ID */
+    private String corpId;
+
+    /** 公司ID */
+    private Long companyId;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    // ========== 统计字段 ==========
+    /** AI外呼呼出次数 */
+    private Integer roboticCallOutCount;
+}

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

@@ -10,8 +10,8 @@ logging:
 wx:
   miniapp:
     configs:
-      - appid: wx40dcfa2797d6fc2d   #云联融智
-        secret: 3c44fb7a1d5ad3aa90efe576c9fb3d19 #北京卓美
+      - appid: wxf3e96f6e152915ad   #云联融智
+        secret: 0d4ba75283b8e557d38a7958c15c6065 #北京卓美
         token: cbnd7lJvkripVOpyTFAna6NAWCxCrvC
         aesKey: HlEiBB55eaWUaeBVAQO3cWKWPYv1vOVQSq7nFNICw4E
         msgDataFormat: JSON

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

@@ -60,11 +60,11 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://192.168.0.93:8010
-  h5CommonApi: http://192.168.0.93:8010
+  commonApi: http://127.0.0.1:8010
+  h5CommonApi: http://127.0.0.1:8010
   jwt:
     # 加密秘钥
-    secret: f4e2e52034348f86b67cde581c0f9eb5
+    secret: f4e2e52034348f81237cde581c0f9eb5
     # token有效时长,7天,单位秒
     expire: 31536000
     header: AppToken

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

@@ -47,10 +47,10 @@ spring:
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
+                    enabled: true
+                    url: jdbc:mysql://192.168.0.165:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
                 # 初始连接数
                 initialSize: 5
                 # 最小连接池数量

+ 28 - 1
fs-service/src/main/resources/db/changelog/changes/20260613-live-user-add-is-del.sql

@@ -38,7 +38,7 @@ ALTER TABLE live_coupon_user
 --precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user'
 --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'live_coupon_user' AND column_name = 'verify_user_id'
 ALTER TABLE live_coupon_user
-    ADD COLUMN verify_user_id BigInteger DEFAULT NULL COMMENT '核销销售用户ID';
+    ADD COLUMN verify_user_id BIGINT DEFAULT NULL COMMENT '核销销售用户ID';
 --rollback ALTER TABLE live_coupon_user DROP COLUMN verify_user_id;
 
 --changeset sgw:20260616-live_coupon_user_verify_time
@@ -48,3 +48,30 @@ ALTER TABLE live_coupon_user
 ALTER TABLE live_coupon_user
     ADD COLUMN verify_time DATETIME DEFAULT NULL COMMENT '核销时间';
 --rollback ALTER TABLE live_coupon_user DROP COLUMN verify_time;
+
+
+--changeset sgw:20260618-fs_course_coupon_user_type
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fs_course_coupon_user'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fs_course_coupon_user' AND column_name = 'send_type'
+ALTER TABLE fs_course_coupon_user
+    ADD COLUMN send_type integer DEFAULT NULL COMMENT '类型  1课程发的 2直播发的';
+--rollback ALTER TABLE fs_course_coupon_user DROP COLUMN send_type;
+
+--changeset sgw:20260618-fs_course_coupon_user_live_id
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fs_course_coupon_user'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fs_course_coupon_user' AND column_name = 'live_id'
+ALTER TABLE fs_course_coupon_user
+    ADD COLUMN live_id BIGINT DEFAULT NULL COMMENT '直播间id';
+--rollback ALTER TABLE fs_course_coupon_user DROP COLUMN type;
+
+
+
+--changeset jzp:20260618-fs_integral_order
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fs_integral_order'
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fs_integral_order' AND column_name = 'virtual_phone'
+ALTER TABLE `fs_integral_order`
+    ADD COLUMN `virtual_phone` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '虚拟手机号' AFTER `quantity_cart`;
+--rollback ALTER TABLE fs_integral_order DROP COLUMN type;

+ 28 - 0
fs-service/src/main/resources/db/changelog/table/live_group_type.sql

@@ -12,3 +12,31 @@ CREATE TABLE `live_group_type` (
                                    `update_time` datetime DEFAULT NULL,
                                    PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+
+
+--changeset jzp:20260618-scrm_customer_info
+--preconditions onFail:MARK_RAN
+--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'scrm_customer_info'
+CREATE TABLE `scrm_customer_info` (
+                                      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+                                      `external_contact_id` bigint NOT NULL COMMENT 'qw_external_contact.id',
+                                      `source` varchar(64) DEFAULT NULL COMMENT '客户来源',
+                                      `customer_type` varchar(64) DEFAULT NULL COMMENT '客户类型',
+                                      `visit_status` varchar(64) DEFAULT NULL COMMENT '跟进阶段',
+                                      `tags` varchar(500) DEFAULT NULL COMMENT '自定义标签(逗号分隔)',
+                                      `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+                                      `last_time` datetime DEFAULT NULL COMMENT '最新联系时间',
+                                      `follow_content` text COMMENT '跟进内容',
+                                      `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+                                      `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+                                      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+                                      `update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
+                                      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+                                      `visit_records` text COMMENT '跟进记录JSON([{registerDesc, visitStatus, visitTime}])',
+                                      `recordings` text COMMENT '录音记录JSON([{fileName, fileUrl, fileSize, duration, uploadTime}])',
+                                      PRIMARY KEY (`id`),
+                                      UNIQUE KEY `uk_external_contact_id` (`external_contact_id`),
+                                      KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='SCRM客户信息表';

+ 6 - 6
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.company.mapper.CompanyVoiceRoboticMapper">
-    
+
     <resultMap type="CompanyVoiceRobotic" id="CompanyVoiceRoboticResult">
         <result property="id"    column="id"    />
         <result property="name"    column="name"    />
@@ -36,7 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectCompanyVoiceRoboticList" parameterType="CompanyVoiceRobotic" resultMap="CompanyVoiceRoboticResult">
         <include refid="selectCompanyVoiceRoboticVo"/>
-        <where>  
+        <where>
             del_flag = 0
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
             <if test="taskName != null  and taskName != ''"> and task_name like concat('%', #{taskName}, '%')</if>
@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
                    order by a.id desc
     </select>
-    
+
     <select id="selectCompanyVoiceRoboticById" parameterType="Long" resultMap="CompanyVoiceRoboticResult">
         <include refid="selectCompanyVoiceRoboticVo"/>
         where id = #{id} and del_flag = 0
@@ -103,7 +103,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        inner join company_user b on a.company_user_id = b.user_id
     </select>
     <select id="qwUserListCompany" resultType="com.fs.company.vo.CompanyVoiceRoboticQwUserListVo">
-        select a.id, a.wx_nick_name, a.wx_no, u.nick_name as companyUserName
+        select a.id, a.wx_nick_name, a.wx_no, u.nick_name as companyUserName,a.phone
         from company_wx_account a
        inner join company_user u on a.company_user_id = u.user_id
        inner join company_dept d on u.dept_id = d.dept_id
@@ -194,7 +194,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </update>
 
     <update id="deleteCompanyVoiceRoboticByIds" parameterType="String">
-        update company_voice_robotic set del_flag = 1 where id in 
+        update company_voice_robotic set del_flag = 1 where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
@@ -237,4 +237,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                                               and del_flag = 0
                                               order by create_time desc
     </select>
-</mapper>
+</mapper>

+ 19 - 8
fs-service/src/main/resources/mapper/his/FsCourseCouponUserMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.his.mapper.FsCourseCouponUserMapper">
-    
+
     <resultMap type="FsCourseCouponUser" id="FsCourseCouponUserResult">
         <result property="id"    column="id"    />
         <result property="couponId"    column="coupon_id"    />
@@ -14,29 +14,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="updateTime"    column="update_time"    />
         <result property="status"    column="status"    />
         <result property="logId"    column="log_id"    />
+        <result property="sendType"    column="send_type"    />
+        <result property="liveId"    column="live_id"    />
     </resultMap>
 
     <sql id="selectFsCourseCouponUserVo">
-        select id, coupon_id, user_id, limit_time, start_time, create_time, update_time, status, log_id from fs_course_coupon_user
+        select id, coupon_id, user_id, limit_time, start_time, create_time, update_time, status, log_id,send_type,live_id from fs_course_coupon_user
     </sql>
 
     <select id="selectFsCourseCouponUserList" parameterType="FsCourseCouponUser" resultMap="FsCourseCouponUserResult">
         <include refid="selectFsCourseCouponUserVo"/>
-        <where>  
+        <where>
             <if test="couponId != null "> and coupon_id = #{couponId}</if>
             <if test="userId != null "> and user_id = #{userId}</if>
             <if test="limitTime != null "> and limit_time = #{limitTime}</if>
             <if test="startTime != null "> and start_time = #{startTime}</if>
             <if test="status != null "> and status = #{status}</if>
-             <if test="logId != null "> and log_id = #{logId}</if>
+            <if test="logId != null "> and log_id = #{logId}</if>
+            <if test="sendType != null "> and send_type = #{sendType}</if>
+            <if test="liveId != null "> and live_id = #{liveId}</if>
         </where>
     </select>
-    
+
     <select id="selectFsCourseCouponUserById" parameterType="Long" resultMap="FsCourseCouponUserResult">
         <include refid="selectFsCourseCouponUserVo"/>
         where id = #{id}
     </select>
-        
+
+
     <insert id="insertFsCourseCouponUser" parameterType="FsCourseCouponUser" useGeneratedKeys="true" keyProperty="id">
         insert into fs_course_coupon_user
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -48,6 +53,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">update_time,</if>
             <if test="status != null">status,</if>
             <if test="logId != null">log_id,</if>
+            <if test="sendType != null">send_type,</if>
+            <if test="liveId != null">live_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="couponId != null">#{couponId},</if>
@@ -58,6 +65,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">#{updateTime},</if>
             <if test="status != null">#{status},</if>
             <if test="logId != null">#{logId},</if>
+            <if test="sendType != null">#{sendType},</if>
+            <if test="liveId != null">#{liveId},</if>
          </trim>
     </insert>
 
@@ -72,6 +81,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="status != null">status = #{status},</if>
             <if test="logId != null">log_id = #{logId},</if>
+            <if test="sendType != null">send_type = #{sendType},</if>
+            <if test="liveId != null">live_id = #{liveId},</if>
         </trim>
         where id = #{id}
     </update>
@@ -81,9 +92,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteFsCourseCouponUserByIds" parameterType="String">
-        delete from fs_course_coupon_user where id in 
+        delete from fs_course_coupon_user where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
-</mapper>
+</mapper>

+ 7 - 1
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -30,12 +30,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="integralByCart"    column="integral_by_cart"    />
         <result property="barCodeCart"    column="bar_code_cart"    />
         <result property="quantityCart"    column="quantity_cart"    />
+        <result property="virtualPhone"    column="virtual_phone"    />
     </resultMap>
 
     <sql id="selectFsIntegralOrderVo">
         select order_id, order_code, user_id,bar_code, user_name, user_phone, user_address, item_json, integral,pay_money,is_pay,pay_time,pay_type, status,
                delivery_code, delivery_name, delivery_sn, delivery_time, create_time,qw_user_id,company_user_id,company_id,remark,login_account,
-               item_cart_json,integral_by_cart,bar_code_cart,quantity_cart
+               item_cart_json,integral_by_cart,bar_code_cart,quantity_cart,virtual_phone
         from fs_integral_order
     </sql>
 
@@ -55,6 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="qwUserId != null  and qwUserId != ''"> and qw_user_id = #{qwUserId}</if>
             <if test="companyUserId != null  and companyUserId != ''"> and company_user_id = #{companyUserId}</if>
             <if test="companyId != null  and companyId != ''"> and company_id = #{companyId}</if>
+            <if test="virtualPhone != null  and virtualPhone != ''"> and virtual_phone = #{virtualPhone}</if>
             <if test="createTime != null "> and create_time = #{createTime}</if>
         </where>
     </select>
@@ -95,6 +97,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         fio.company_id,
         fio.remark,
         fio.login_account,
+        fio.virtual_phone,
         fiod.erp_phone
         FROM
         fs_integral_order fio
@@ -174,6 +177,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="integralByCart != null">integral_by_cart,</if>
             <if test="barCodeCart != null">bar_code_cart,</if>
             <if test="quantityCart != null">quantity_cart,</if>
+            <if test="virtualPhone != null">virtual_phone,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="orderId != null">#{orderId},</if>
@@ -204,6 +208,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="integralByCart != null">#{integralByCart},</if>
             <if test="barCodeCart != null">#{barCodeCart},</if>
             <if test="quantityCart != null">#{quantityCart},</if>
+            <if test="virtualPhone != null">#{virtualPhone},</if>
          </trim>
     </insert>
 
@@ -229,6 +234,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="remark != null">remark = #{remark},</if>
             <if test="barCode != null">bar_code = #{barCode},</if>
+            <if test="virtualPhone != null">virtual_phone = #{virtualPhone},</if>
             <if test="loginAccount != '' and loginAccount != null">login_account = #{loginAccount},</if>
         </trim>
         where order_id = #{orderId}

+ 16 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreAfterSalesScrmMapper.xml

@@ -243,4 +243,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </delete>
 
+    <select id="selectRefundAmountByOrderCodes" resultType="com.fs.hisStore.vo.FsStoreOrderRefundAmountVO">
+        SELECT t.order_code AS orderCode, t.refund_amount AS refundAmount
+        FROM fs_store_after_sales_scrm t
+        INNER JOIN (
+            SELECT order_code, MAX(id) AS max_id
+            FROM fs_store_after_sales_scrm
+            WHERE is_del = 0
+              AND status = 4
+              AND order_code IN
+            <foreach collection="orderCodes" item="orderCode" open="(" separator="," close=")">
+                #{orderCode}
+            </foreach>
+            GROUP BY order_code
+        ) latest ON t.id = latest.max_id
+    </select>
+
 </mapper>

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

@@ -2205,6 +2205,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.deliveryStatus != null     ">
                 and o.delivery_status =#{maps.deliveryStatus}
             </if>
+            <if test="maps.isCompanyOrder != null and maps.isCompanyOrder > 0">
+                and o.order_type != 3 and o.order_type != 2
+            </if>
             <if test="maps.deliveryExceptionStatus != null">
                 and o.delivery_exception_status = #{maps.deliveryExceptionStatus}
             </if>

+ 13 - 5
fs-service/src/main/resources/mapper/qw/QwRestrictionPushRecordMapper.xml

@@ -14,11 +14,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </update>
 
     <select id="selectFastgptPushTokenTotal" resultType="com.fs.fastGpt.domain.FastGptPushTokenTotal">
-        select id,`type`,qw_user_id as qwUserId,company_id as companyId,status,date_format(create_time,'%Y-%m-%d') as statTime,count(id) count
+        select `type`,
+               qw_user_id as qwUserId,
+               company_id as companyId,
+               1 as status,
+               #{createTime} as statTime,
+               count(id) as count
         from qw_restriction_push_record
-        where  create_time like concat(#{createTime}, '%') and qw_user_id is not null and status = 1
-        GROUP BY `type`,company_id,qw_user_id
-        ORDER BY `type`,company_id,qw_user_id
+        where create_time >= concat(#{createTime}, ' 00:00:00')
+          and create_time &lt;= concat(#{createTime}, ' 23:59:59')
+          and qw_user_id is not null
+          and status = 1
+        group by `type`, company_id, qw_user_id
+        order by `type`, company_id, qw_user_id
     </select>
 
     <select id="selectFastGptPushTokenTotalByInfo" resultType="com.fs.fastGpt.domain.FastGptPushTokenTotal">
@@ -72,4 +80,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
         group by t4.dept_id,ft.stat_time
     </select>
-</mapper>
+</mapper>

+ 162 - 0
fs-service/src/main/resources/mapper/scrm/ScrmCustomerInfoMapper.xml

@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.scrm.mapper.ScrmCustomerInfoMapper">
+
+    <resultMap type="com.fs.scrm.domain.ScrmCustomerInfo" id="ScrmCustomerInfoResult">
+        <result property="id"                column="id"                  />
+        <result property="externalContactId" column="external_contact_id" />
+        <result property="source"            column="source"              />
+        <result property="customerType"      column="customer_type"       />
+        <result property="visitStatus"       column="visit_status"        />
+        <result property="tags"              column="tags"                />
+        <result property="remark"            column="remark"              />
+        <result property="lastTime"          column="last_time"           />
+        <result property="followContent"     column="follow_content"      />
+        <result property="visitRecords"     column="visit_records"       />
+        <result property="recordings"       column="recordings"          />
+        <result property="companyId"         column="company_id"          />
+        <result property="createBy"          column="create_by"           />
+        <result property="createTime"        column="create_time"         />
+        <result property="updateBy"          column="update_by"           />
+        <result property="updateTime"        column="update_time"         />
+    </resultMap>
+
+    <resultMap type="com.fs.scrm.vo.ScrmCustomerInfoVO" id="ScrmCustomerInfoVOResult">
+        <!-- scrm_customer_info -->
+        <result property="scrmInfoId"        column="scrm_info_id"        />
+        <result property="externalContactId" column="external_contact_id" />
+        <result property="source"            column="source"              />
+        <result property="customerType"      column="customer_type"       />
+        <result property="visitStatus"       column="visit_status"        />
+        <result property="tags"              column="tags"                />
+        <result property="remark"            column="remark"              />
+        <result property="lastTime"          column="last_time"           />
+        <result property="followContent"     column="follow_content"      />
+        <result property="visitRecords"     column="visit_records"       />
+        <result property="recordings"       column="recordings"          />
+        <!-- qw_external_contact -->
+        <result property="name"              column="name"                />
+        <result property="userId"            column="user_id"             />
+        <result property="externalUserId"    column="external_user_id"    />
+        <result property="remarkMobiles"     column="remark_mobiles"      />
+        <result property="gender"            column="gender"              />
+        <result property="type"              column="type"                />
+        <result property="addWay"            column="add_way"             />
+        <result property="status"            column="status"              />
+        <result property="tagIds"            column="tag_ids"             />
+        <result property="fsUserId"          column="fs_user_id"          />
+        <result property="qwUserId"          column="qw_user_id"          />
+        <result property="companyUserId"     column="company_user_id"     />
+        <result property="corpId"            column="corp_id"             />
+        <result property="companyId"         column="company_id"          />
+        <result property="createTime"        column="create_time"         />
+        <result property="roboticCallOutCount" column="robotic_call_out_count" />
+    </resultMap>
+
+    <select id="selectScrmCustomerInfoList" parameterType="com.fs.scrm.param.ScrmCustomerInfoQueryParam" resultMap="ScrmCustomerInfoVOResult">
+        select
+            sci.id as scrm_info_id,
+            sci.external_contact_id,
+            sci.source,
+            sci.customer_type,
+            sci.visit_status,
+            sci.tags,
+            sci.remark,
+            sci.last_time,
+            sci.follow_content,
+            sci.visit_records,
+            sci.recordings,
+            qec.id as external_contact_id,
+            qec.name,
+            qec.user_id,
+            qec.external_user_id,
+            qec.remark_mobiles,
+            qec.gender,
+            qec.type,
+            qec.add_way,
+            qec.status,
+            qec.tag_ids,
+            qec.fs_user_id,
+            qec.qw_user_id,
+            qec.company_user_id,
+            qec.corp_id,
+            qec.company_id,
+            qec.create_time,
+            0 as robotic_call_out_count
+        from qw_external_contact qec
+        left join scrm_customer_info sci on sci.external_contact_id = qec.id
+        <where>
+            <if test="externalContactId != null"> and qec.id = #{externalContactId}</if>
+            <if test="fsUserId != null"> and qec.fs_user_id = #{fsUserId}</if>
+            <if test="companyId != null"> and qec.company_id = #{companyId}</if>
+            <if test="companyUserId != null"> and qec.company_user_id = #{companyUserId}</if>
+            <if test="createTimeStart != null and createTimeStart != '' and createTimeEnd != null and createTimeEnd != ''">
+                and qec.create_time between #{createTimeStart} and #{createTimeEnd}
+            </if>
+        </where>
+        order by qec.create_time desc, qec.id desc
+    </select>
+
+    <select id="selectScrmCustomerInfoByExternalContactId" parameterType="Long" resultMap="ScrmCustomerInfoResult">
+        select * from scrm_customer_info where external_contact_id = #{externalContactId}
+    </select>
+
+    <insert id="insertScrmCustomerInfo" parameterType="com.fs.scrm.domain.ScrmCustomerInfo" useGeneratedKeys="true" keyProperty="id">
+        insert into scrm_customer_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="externalContactId != null">external_contact_id,</if>
+            <if test="source != null">source,</if>
+            <if test="customerType != null">customer_type,</if>
+            <if test="visitStatus != null">visit_status,</if>
+            <if test="tags != null">tags,</if>
+            <if test="remark != null">remark,</if>
+            <if test="lastTime != null">last_time,</if>
+            <if test="followContent != null">follow_content,</if>
+            <if test="visitRecords != null">visit_records,</if>
+            <if test="recordings != null">recordings,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="externalContactId != null">#{externalContactId},</if>
+            <if test="source != null">#{source},</if>
+            <if test="customerType != null">#{customerType},</if>
+            <if test="visitStatus != null">#{visitStatus},</if>
+            <if test="tags != null">#{tags},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="lastTime != null">#{lastTime},</if>
+            <if test="followContent != null">#{followContent},</if>
+            <if test="visitRecords != null">#{visitRecords},</if>
+            <if test="recordings != null">#{recordings},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateScrmCustomerInfo" parameterType="com.fs.scrm.domain.ScrmCustomerInfo">
+        update scrm_customer_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="source != null">source = #{source},</if>
+            <if test="customerType != null">customer_type = #{customerType},</if>
+            <if test="visitStatus != null">visit_status = #{visitStatus},</if>
+            <if test="tags != null">tags = #{tags},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="lastTime != null">last_time = #{lastTime},</if>
+            <if test="followContent != null">follow_content = #{followContent},</if>
+            <if test="visitRecords != null">visit_records = #{visitRecords},</if>
+            <if test="recordings != null">recordings = #{recordings},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where external_contact_id = #{externalContactId}
+    </update>
+
+</mapper>

+ 3 - 5
fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml

@@ -331,14 +331,12 @@
         COUNT(DISTINCT a.user_id) AS answer_user_count,
         COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
         FROM fs_course_watch_log w
-        LEFT JOIN fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
+        LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = w.log_id
         <where>
             <if test="startTime != null">w.create_time <![CDATA[>=]]> #{startTime}</if>
             <if test="endTime != null">AND w.create_time <![CDATA[<]]> #{endTime}</if>
             <if test="userType != null">AND w.send_type = #{userType}</if>
             <if test="companyId != null">AND w.company_id = #{companyId}</if>
-<!--            <if test="startTime != null">AND a.create_time <![CDATA[>=]]> #{startTime}</if> -->
-<!--            <if test="endTime != null">AND a.create_time <![CDATA[<]]> #{endTime}</if> -->
         </where>
         GROUP BY w.course_id
         ORDER BY answer_user_count
@@ -358,7 +356,7 @@
         COUNT(DISTINCT a.user_id) AS answer_user_count,
         COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
         FROM fs_course_watch_log w
-        LEFT JOIN fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
+        LEFT JOIN fs_course_answer_logs a ON a.watch_log_id = w.log_id
         <where>
             <if test="startTime != null">w.create_time <![CDATA[>=]]> #{startTime}</if>
             <if test="endTime != null">AND w.create_time <![CDATA[<]]> #{endTime}</if>
@@ -383,7 +381,7 @@
         COUNT(DISTINCT a.user_id) AS answer_user_count,
         COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
         FROM fs_course_answer_logs a
-        INNER JOIN fs_course_watch_log w ON a.video_id = w.video_id AND a.user_id = w.user_id
+        INNER JOIN fs_course_watch_log w ON a.watch_log_id = w.log_id
         <where>
             w.course_id IN
             <foreach collection="courseIds" item="courseId" open="(" close=")" separator=",">

+ 10 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -338,6 +338,16 @@ public class CourseQwController extends AppBaseController {
     public R isAddKf(@RequestBody FsUserCourseVideoAddKfUParam param) {
 
         R r = null;
+
+        //临时版-应该放在查视频详情的时候,但是没前端代码
+        FsUserCourseVideoH5DVO course = courseService.selectFsUserCourseVideoH5DVOByVideoId(param.getVideoId());
+        if (course.getIsDel()==1){
+            return  R.error("链接已过期删,请联系助手,重新发最新的");
+        }
+        if (course.getIsOnPut()==1){
+            return R.error("链接已过期下,请联系助手,重新发最新的");
+        }
+
         Long userId = Long.parseLong(getUserId());
         param.setUserId(userId);
         if(param.getIsOpen() == null || param.getIsOpen() == 0){