Переглянути джерело

1.提交恒春来虚拟手机号获取物流信息
2.九州在线提交scrm界面和跟进客户功能

jzp 4 днів тому
батько
коміт
5381c6c307
22 змінених файлів з 785 додано та 10 видалено
  1. 1 1
      fs-admin/src/main/java/com/fs/his/controller/FsExpressCommonController.java
  2. 18 3
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  3. 19 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  4. 213 0
      fs-company/src/main/java/com/fs/company/controller/scrm/ScrmCustomerController.java
  5. 17 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  6. 1 1
      fs-company/src/main/java/com/fs/hisStore/utils/CityTreeUtil.java
  7. 1 1
      fs-company/src/main/java/com/fs/hisStore/vo/CityVO.java
  8. 8 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  9. 2 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  10. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  11. 3 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  12. 7 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java
  13. 2 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderPVO.java
  14. 58 0
      fs-service/src/main/java/com/fs/scrm/domain/ScrmCustomerInfo.java
  15. 36 0
      fs-service/src/main/java/com/fs/scrm/mapper/ScrmCustomerInfoMapper.java
  16. 42 0
      fs-service/src/main/java/com/fs/scrm/param/ScrmCustomerInfoQueryParam.java
  17. 14 0
      fs-service/src/main/java/com/fs/scrm/service/IScrmCustomerInfoService.java
  18. 39 0
      fs-service/src/main/java/com/fs/scrm/service/impl/ScrmCustomerInfoServiceImpl.java
  19. 104 0
      fs-service/src/main/java/com/fs/scrm/vo/ScrmCustomerInfoVO.java
  20. 28 0
      fs-service/src/main/resources/db/changelog/table/live_group_type.sql
  21. 7 1
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  22. 162 0
      fs-service/src/main/resources/mapper/scrm/ScrmCustomerInfoMapper.xml

+ 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);
                 }
                 // 原逻辑

+ 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);
+    }
+
+}

+ 17 - 0
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;
 
     /**
      * 查询订单列表
@@ -446,6 +452,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);
                 }
                 // 原逻辑

+ 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;

+ 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());
         }
 

+ 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){

+ 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;
+
 }

+ 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());

+ 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;
 }

+ 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;
+}

+ 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客户信息表';

+ 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}

+ 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>