Ver Fonte

1.新增同步口袋助理通话记录 2.微信订单导入和微信推送订单导出

wjj há 3 dias atrás
pai
commit
914843c24f
28 ficheiros alterados com 1288 adições e 30 exclusões
  1. 103 0
      fs-admin/src/main/java/com/fs/his/controller/FsKdzlCallRecordController.java
  2. 139 20
      fs-admin/src/main/java/com/fs/his/task/KdzlTask.java
  3. 27 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  4. 10 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCoursePlaySourceConfigController.java
  5. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java
  6. 92 0
      fs-service/src/main/java/com/fs/his/domain/FsKdzlCallRecord.java
  7. 67 0
      fs-service/src/main/java/com/fs/his/mapper/FsKdzlCallRecordMapper.java
  8. 61 0
      fs-service/src/main/java/com/fs/his/service/IFsKdzlCallRecordService.java
  9. 110 0
      fs-service/src/main/java/com/fs/his/service/impl/FsKdzlCallRecordServiceImpl.java
  10. 42 0
      fs-service/src/main/java/com/fs/hisStore/dto/MallOrderDownDTO.java
  11. 25 0
      fs-service/src/main/java/com/fs/hisStore/dto/MallOrderImportDTO.java
  12. 26 0
      fs-service/src/main/java/com/fs/hisStore/listenner/MallOrderImportDTOListener.java
  13. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  14. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  15. 8 4
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  16. 2 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStorePaymentScrmService.java
  17. 139 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  18. 5 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  19. 19 0
      fs-service/src/main/java/com/fs/kdzl/dto/CallFilter.java
  20. 10 0
      fs-service/src/main/java/com/fs/kdzl/dto/CallLast.java
  21. 24 0
      fs-service/src/main/java/com/fs/kdzl/dto/CallRecord.java
  22. 11 0
      fs-service/src/main/java/com/fs/kdzl/dto/CallRecordData.java
  23. 12 0
      fs-service/src/main/java/com/fs/kdzl/dto/CallRecordDataResponse.java
  24. 3 0
      fs-service/src/main/java/com/fs/kdzl/service/KdzlService.java
  25. 200 5
      fs-service/src/main/java/com/fs/kdzl/service/impl/KdzlServiceImpl.java
  26. 127 0
      fs-service/src/main/resources/mapper/his/FsKdzlCallRecordMapper.xml
  27. 9 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  28. 8 0
      fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

+ 103 - 0
fs-admin/src/main/java/com/fs/his/controller/FsKdzlCallRecordController.java

@@ -0,0 +1,103 @@
+package com.fs.his.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.his.domain.FsKdzlCallRecord;
+import com.fs.his.service.IFsKdzlCallRecordService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 口袋助理通话记录Controller
+ * 
+ * @author fs
+ * @date 2026-06-22
+ */
+@RestController
+@RequestMapping("/his/callRecord")
+public class FsKdzlCallRecordController extends BaseController
+{
+    @Autowired
+    private IFsKdzlCallRecordService fsKdzlCallRecordService;
+
+    /**
+     * 查询口袋助理通话记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        startPage();
+        List<FsKdzlCallRecord> list = fsKdzlCallRecordService.selectFsKdzlCallRecordList(fsKdzlCallRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出口袋助理通话记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:export')")
+    @Log(title = "口袋助理通话记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        List<FsKdzlCallRecord> list = fsKdzlCallRecordService.selectFsKdzlCallRecordList(fsKdzlCallRecord);
+        ExcelUtil<FsKdzlCallRecord> util = new ExcelUtil<FsKdzlCallRecord>(FsKdzlCallRecord.class);
+        return util.exportExcel(list, "口袋助理通话记录数据");
+    }
+
+    /**
+     * 获取口袋助理通话记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsKdzlCallRecordService.selectFsKdzlCallRecordById(id));
+    }
+
+    /**
+     * 新增口袋助理通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:add')")
+    @Log(title = "口袋助理通话记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        return toAjax(fsKdzlCallRecordService.insertFsKdzlCallRecord(fsKdzlCallRecord));
+    }
+
+    /**
+     * 修改口袋助理通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:edit')")
+    @Log(title = "口袋助理通话记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        return toAjax(fsKdzlCallRecordService.updateFsKdzlCallRecord(fsKdzlCallRecord));
+    }
+
+    /**
+     * 删除口袋助理通话记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:callRecord:remove')")
+    @Log(title = "口袋助理通话记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsKdzlCallRecordService.deleteFsKdzlCallRecordByIds(ids));
+    }
+}

+ 139 - 20
fs-admin/src/main/java/com/fs/his/task/KdzlTask.java

@@ -3,7 +3,10 @@ package com.fs.his.task;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.fs.his.domain.FsImportMember;
+import com.fs.his.domain.FsKdzlCallRecord;
 import com.fs.his.mapper.FsImportMemberMapper;
+import com.fs.his.mapper.FsKdzlCallRecordMapper;
+import com.fs.kdzl.dto.CallRecord;
 import com.fs.kdzl.dto.Custm;
 import com.fs.kdzl.dto.Property;
 import com.fs.kdzl.service.KdzlService;
@@ -12,10 +15,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -26,6 +26,8 @@ public class KdzlTask {
     private KdzlService kdzlService;
     @Autowired
     private FsImportMemberMapper importMemberMapper;
+    @Autowired
+    private FsKdzlCallRecordMapper kdzlCallRecordMapper;
 
     org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
 
@@ -35,17 +37,17 @@ public class KdzlTask {
         int loopCount = 0;
         int maxLoopCount = 100;
         int batchSize = 500;
-        
+
         while (loopCount < maxLoopCount) {
             loopCount++;
             List<Custm> custms = kdzlService.exportCustomers();
             logger.info("本次获取会员信息数量:{},当前循环次数:{}", custms.size(), loopCount);
-            
+
             if (CollectionUtils.isEmpty(custms)) {
                 logger.info("未获取到会员数据,同步结束");
                 break;
             }
-            
+
             totalCount += custms.size();
             logger.info("累计已同步会员数量:{}", totalCount);
 
@@ -117,9 +119,9 @@ public class KdzlTask {
                             int endIndex = Math.min(i + batchSize, membersToSave.size());
                             List<FsImportMember> batch = membersToSave.subList(i, endIndex);
                             importMemberMapper.batchInsertOrUpdateFsImportMember(batch);
-                            logger.info("分批批量保存会员成功,批次:{}/{}, 数量:{}", 
-                                    (i / batchSize + 1), 
-                                    (membersToSave.size() + batchSize - 1) / batchSize, 
+                            logger.info("分批批量保存会员成功,批次:{}/{}, 数量:{}",
+                                    (i / batchSize + 1),
+                                    (membersToSave.size() + batchSize - 1) / batchSize,
                                     batch.size());
                         }
                     }
@@ -127,19 +129,19 @@ public class KdzlTask {
                     log.error("批量保存会员失败,数量:{}, 错误: {}", membersToSave.size(), e.getMessage(), e);
                 }
             }
-            
+
             if (custms.size() < 2000) {
                 logger.info("本次获取数据量小于2000,所有会员数据已同步完毕");
                 break;
             }
-            
+
             logger.info("本次获取数据量等于2000,继续获取下一页数据...");
         }
 
         if (loopCount >= maxLoopCount) {
             logger.warn("已达到最大循环次数{},强制退出循环,防止死循环", maxLoopCount);
         }
-        
+
         logger.info("会员信息同步完成,总共同步数量:{}", totalCount);
     }
 
@@ -154,12 +156,12 @@ public class KdzlTask {
             loopCount++;
             List<Custm> highSeasCustms = kdzlService.exportHighSeasCustomers(null);
             logger.info("本次获取公海会员信息数量:{},当前循环次数:{}", highSeasCustms.size(), loopCount);
-            
+
             if (CollectionUtils.isEmpty(highSeasCustms)) {
                 logger.info("未获取到公海会员数据,同步结束");
                 break;
             }
-            
+
             totalCount += highSeasCustms.size();
             logger.info("累计已同步公海会员数量:{}", totalCount);
 
@@ -231,9 +233,9 @@ public class KdzlTask {
                             int endIndex = Math.min(i + batchSize, membersToSave.size());
                             List<FsImportMember> batch = membersToSave.subList(i, endIndex);
                             importMemberMapper.batchInsertOrUpdateFsImportMember(batch);
-                            logger.info("分批批量保存公海会员成功,批次:{}/{}, 数量:{}", 
-                                    (i / batchSize + 1), 
-                                    (membersToSave.size() + batchSize - 1) / batchSize, 
+                            logger.info("分批批量保存公海会员成功,批次:{}/{}, 数量:{}",
+                                    (i / batchSize + 1),
+                                    (membersToSave.size() + batchSize - 1) / batchSize,
                                     batch.size());
                         }
                     }
@@ -241,7 +243,7 @@ public class KdzlTask {
                     log.error("批量保存公海会员失败,数量:{}, 错误: {}", membersToSave.size(), e.getMessage(), e);
                 }
             }
-            
+
             if (highSeasCustms.size() < 2000) {
                 logger.info("本次获取数据量小于2000,所有公海会员数据已同步完毕");
                 break;
@@ -253,7 +255,124 @@ public class KdzlTask {
         if (loopCount >= maxLoopCount) {
             logger.warn("已达到最大循环次数{},强制退出循环,防止死循环", maxLoopCount);
         }
-        
+
         logger.info("公海会员信息同步完成,总共同步数量:{}", totalCount);
     }
+
+    public void syncCallRecord() {
+        logger.info("开始同步通话记录...");
+        int totalCount = 0;
+        int batchSize = 500;
+
+
+            // 1. 获取数据
+            List<CallRecord> callRecords = kdzlService.exportCallRecords();
+            logger.info("本次获取通话记录数量:{}", callRecords.size());
+
+            if (CollectionUtils.isEmpty(callRecords)) {
+                logger.info("未获取到通话记录数据,同步结束");
+                return;
+            }
+
+            // 2. 查询已存在的数据(批量)
+            List<FsKdzlCallRecord> existingFsKdzlCallRecordList =
+                    kdzlCallRecordMapper.selectExistingFsKdzlCallRecordList(callRecords);
+
+
+
+            // 4. 过滤出需要插入的新数据
+            List<FsKdzlCallRecord> callRecordsToSave = new ArrayList<>();
+
+            for (CallRecord callRecord : callRecords) {
+                try {
+                    // 从已有列表中查找匹配的记录(使用5个字段联合判断)
+                    FsKdzlCallRecord existingFsKdzlCallRecord = existingFsKdzlCallRecordList.parallelStream()
+                            .filter(record ->
+                                    Objects.equals(record.getUid(), callRecord.getUid()) &&
+                                            Objects.equals(record.getCallTime(), callRecord.getCallTime()) &&
+                                            Objects.equals(record.getCaller(), callRecord.getCaller()) &&
+                                            Objects.equals(record.getCalled(), callRecord.getCalled()) &&
+                                            Objects.equals(record.getType(), callRecord.getType())
+                            )
+                            .findFirst()
+                            .orElse(null);
+                    boolean isNew = (existingFsKdzlCallRecord == null);
+
+                    if (isNew) {
+                        // 不存在,新增
+                        FsKdzlCallRecord newRecord = new FsKdzlCallRecord();
+                        newRecord.setUid(callRecord.getUid());
+                        newRecord.setCallTime(callRecord.getCallTime());
+                        newRecord.setTalkLen(callRecord.getTalkLen());
+                        newRecord.setStatus(callRecord.getStatus());
+                        newRecord.setType(callRecord.getType());
+                        newRecord.setCaller(callRecord.getCaller());
+                        newRecord.setCalled(callRecord.getCalled());
+                        newRecord.setCustomerId(callRecord.getCustomerId());
+                        newRecord.setCustomerName(callRecord.getCustomerName());
+
+                        callRecordsToSave.add(newRecord);
+                    }
+                } catch (Exception e) {
+                    log.error("处理通话记录失败,uid: {}, 错误: {}", callRecord.getUid(), e.getMessage(), e);
+                }
+            }
+
+            logger.info("本次待插入数据:{} 条(已去重)", callRecordsToSave.size());
+
+            // 5. 批量插入(移到循环外面)
+            if (!callRecordsToSave.isEmpty()) {
+                try {
+                    if (callRecordsToSave.size() <= batchSize) {
+                        kdzlCallRecordMapper.batchInsertFsKdzlCallRecord(callRecordsToSave);
+                        logger.info("批量保存通话记录成功,数量:{}", callRecordsToSave.size());
+                    } else {
+                        // 按批次插入(每批500条)
+                        for (int i = 0; i < callRecordsToSave.size(); i += batchSize) {
+                            int endIndex = Math.min(i + batchSize, callRecordsToSave.size());
+                            List<FsKdzlCallRecord> batch = callRecordsToSave.subList(i, endIndex);
+
+                            kdzlCallRecordMapper.batchInsertFsKdzlCallRecord(batch);
+                            logger.info("分批保存成功,批次:{}/{},数量:{}",
+                                    (i / batchSize + 1),
+                                    (callRecordsToSave.size() + batchSize - 1) / batchSize,
+                                    batch.size()
+                            );
+                        }
+                    }
+                    totalCount += callRecordsToSave.size();
+                    logger.info("累计同步通话记录数量:{}", totalCount);
+
+                } catch (Exception e) {
+                    log.error("批量保存通话记录失败,数量:{}, 错误: {}", callRecordsToSave.size(), e.getMessage(), e);
+                    // 可以在这里做补偿逻辑,比如逐条插入
+                }
+            }
+    }
+
+    /**
+     * 构建组合键(从FsKdzlCallRecord)
+     */
+    private String buildCompositeKey(FsKdzlCallRecord record) {
+        return String.format("%d|%s|%s|%s|%d",
+                record.getUid(),
+                record.getCallTime().toString(),
+                record.getCaller(),
+                record.getCalled(),
+                record.getType()
+        );
+    }
+
+    /**
+     * 构建组合键(从CallRecord)
+     */
+    private String buildCompositeKeyFromCallRecord(CallRecord record) {
+        return String.format("%d|%s|%s|%s|%d",
+                record.getUid(),
+                record.getCallTime().toString(),
+                record.getCaller(),
+                record.getCalled(),
+                record.getType()
+        );
+    }
 }

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

@@ -3,6 +3,7 @@ package com.fs.hisStore.controller;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.EasyExcel;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.constant.HttpStatus;
@@ -26,6 +27,7 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.MemberExcelDTO;
 import com.fs.his.param.FsStoreOrderScheduleStatisticsParam;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsUserService;
@@ -38,6 +40,7 @@ import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.domain.FsStoreOrderStatusScrm;
 import com.fs.hisStore.domain.FsStorePaymentScrm;
 import com.fs.his.dto.ExpressInfoDTO;
+import com.fs.hisStore.dto.MallOrderDownDTO;
 import com.fs.hisStore.dto.StoreOrderExpressExportDTO;
 import com.fs.hisStore.dto.StoreOrderProductDTO;
 import com.fs.hisStore.enums.ShipperCodeEnum;
@@ -55,8 +58,11 @@ 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.IOException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.net.URLEncoder;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -797,6 +803,27 @@ public class FsStoreOrderScrmController extends BaseController {
         return dataTable;
     }
 
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:generateMallOrderPushExcel')")
+    @PostMapping("/generateMallOrderPushExcel")
+    public AjaxResult downloadTemplate(@RequestParam("file") MultipartFile file){
+        fsStoreOrderService.generateMallOrderPushExcel(file);
+        return AjaxResult.success();
+    }
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:downloadMallOrderPushExcel')")
+    @GetMapping("/downloadMallOrderPushExcel")
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        List<MallOrderDownDTO> mallOrderDownDTOS = fsStoreOrderService.downloadMallOrderPushExcel();
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setCharacterEncoding("utf-8");
+        String fileName = URLEncoder.encode("微信发货订单", "UTF-8");
+        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
+
+        // 写入Excel并输出
+        EasyExcel.write(response.getOutputStream(), MallOrderDownDTO.class)
+                .sheet("微信订单数据")
+                .doWrite(mallOrderDownDTOS);
+    }
+
     /**
      * 对象列表类型转换
      */

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCoursePlaySourceConfigController.java

@@ -2,6 +2,7 @@ package com.fs.company.controller.course;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -161,4 +162,13 @@ public class FsCoursePlaySourceConfigController extends BaseController {
         }
         return R.ok().put("date",start);
     }
+
+    @GetMapping("/getOptions")
+    public AjaxResult getOptions(){
+        LambdaQueryWrapper<FsCoursePlaySourceConfig> queryWrapper = Wrappers.<FsCoursePlaySourceConfig>lambdaQuery()
+                .eq(FsCoursePlaySourceConfig::getIsDel, 0)
+                .eq(FsCoursePlaySourceConfig::getStatus, 0);
+        List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(queryWrapper);
+        return AjaxResult.success(list);
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsCoursePlaySourceConfig.java

@@ -95,4 +95,9 @@ public class FsCoursePlaySourceConfig {
      * 是否是互医/商城小程序
      */
     private Integer isMall;
+
+    /**
+     * 状态 0-未封禁 1-已封禁
+     */
+    private Integer status;
 }

+ 92 - 0
fs-service/src/main/java/com/fs/his/domain/FsKdzlCallRecord.java

@@ -0,0 +1,92 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 口袋助理通话记录对象 fs_kdzl_call_record
+ *
+ * @author fs
+ * @date 2026-06-22
+ */
+@Data
+public class FsKdzlCallRecord {
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long uid;
+
+    /** $column.columnComment */
+    @Excel(name = "用户id")
+    private Long callTime;
+
+    /** 通话时长 */
+    @Excel(name = "通话时长")
+    private Long talkLen;
+
+    /** 接通状态(1= 接通,2= 未接通) */
+    @Excel(name = "接通状态", readConverterExp = "1==,接=通,2=,未=接通")
+    private Integer status;
+
+    /** 通话类型(1= 拨打,2= 接听) */
+    @Excel(name = "通话类型", readConverterExp = "1==,拨=打,2=,接=听")
+    private Integer type;
+
+    /** 主叫号码 */
+    @Excel(name = "主叫号码")
+    private String caller;
+
+    /** 被叫号码 */
+    @Excel(name = "被叫号码")
+    private String called;
+
+    /** 客户id */
+    @Excel(name = "客户id")
+    private Long customerId;
+
+    /** 客户名称 */
+    @Excel(name = "客户名称")
+    private String customerName;
+
+    /** 通话时长格式化 */
+    @TableField(exist = false)
+    private String talkLenTime;
+
+    /** 拨打时间 */
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date callTimeDate;
+
+    @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;
+
+
+
+    /** 拨打时间开始(时间戳) */
+    @JsonIgnore
+    @TableField(exist = false)
+    private Long beginCallTime;
+
+    /** 拨打时间结束(时间戳) */
+    @JsonIgnore
+    @TableField(exist = false)
+    private Long endCallTime;
+
+
+}

+ 67 - 0
fs-service/src/main/java/com/fs/his/mapper/FsKdzlCallRecordMapper.java

@@ -0,0 +1,67 @@
+package com.fs.his.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsKdzlCallRecord;
+import com.fs.kdzl.dto.CallRecord;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 口袋助理通话记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-06-22
+ */
+public interface FsKdzlCallRecordMapper extends BaseMapper<FsKdzlCallRecord>{
+    /**
+     * 查询口袋助理通话记录
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 口袋助理通话记录
+     */
+    FsKdzlCallRecord selectFsKdzlCallRecordById(Long id);
+
+    /**
+     * 查询口袋助理通话记录列表
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 口袋助理通话记录集合
+     */
+    List<FsKdzlCallRecord> selectFsKdzlCallRecordList(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 新增口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    int insertFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 修改口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    int updateFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 删除口袋助理通话记录
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 结果
+     */
+    int deleteFsKdzlCallRecordById(Long id);
+
+    /**
+     * 批量删除口袋助理通话记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsKdzlCallRecordByIds(Long[] ids);
+
+    List<FsKdzlCallRecord> selectExistingFsKdzlCallRecordList(@Param("list") List<CallRecord> list);
+
+    int batchInsertFsKdzlCallRecord(@Param("list") List<FsKdzlCallRecord> list);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/his/service/IFsKdzlCallRecordService.java

@@ -0,0 +1,61 @@
+package com.fs.his.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsKdzlCallRecord;
+
+/**
+ * 口袋助理通话记录Service接口
+ * 
+ * @author fs
+ * @date 2026-06-22
+ */
+public interface IFsKdzlCallRecordService extends IService<FsKdzlCallRecord>{
+    /**
+     * 查询口袋助理通话记录
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 口袋助理通话记录
+     */
+    FsKdzlCallRecord selectFsKdzlCallRecordById(Long id);
+
+    /**
+     * 查询口袋助理通话记录列表
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 口袋助理通话记录集合
+     */
+    List<FsKdzlCallRecord> selectFsKdzlCallRecordList(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 新增口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    int insertFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 修改口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    int updateFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord);
+
+    /**
+     * 批量删除口袋助理通话记录
+     * 
+     * @param ids 需要删除的口袋助理通话记录主键集合
+     * @return 结果
+     */
+    int deleteFsKdzlCallRecordByIds(Long[] ids);
+
+    /**
+     * 删除口袋助理通话记录信息
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 结果
+     */
+    int deleteFsKdzlCallRecordById(Long id);
+}

+ 110 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsKdzlCallRecordServiceImpl.java

@@ -0,0 +1,110 @@
+package com.fs.his.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.his.mapper.FsKdzlCallRecordMapper;
+import com.fs.his.domain.FsKdzlCallRecord;
+import com.fs.his.service.IFsKdzlCallRecordService;
+
+/**
+ * 口袋助理通话记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-06-22
+ */
+@Service
+public class FsKdzlCallRecordServiceImpl extends ServiceImpl<FsKdzlCallRecordMapper, FsKdzlCallRecord> implements IFsKdzlCallRecordService {
+
+    /**
+     * 查询口袋助理通话记录
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 口袋助理通话记录
+     */
+    @Override
+    public FsKdzlCallRecord selectFsKdzlCallRecordById(Long id)
+    {
+        return baseMapper.selectFsKdzlCallRecordById(id);
+    }
+
+    /**
+     * 查询口袋助理通话记录列表
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 口袋助理通话记录
+     */
+    @Override
+    public List<FsKdzlCallRecord> selectFsKdzlCallRecordList(FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        List<FsKdzlCallRecord> list = baseMapper.selectFsKdzlCallRecordList(fsKdzlCallRecord);
+        if (list != null && !list.isEmpty()) {
+            for (FsKdzlCallRecord record : list) {
+                if (record.getCallTime() != null) {
+                    record.setCallTimeDate(new Date(record.getCallTime()));
+                }
+                if (record.getTalkLen() != null) {
+                    long totalSeconds = record.getTalkLen();
+                    long minutes = totalSeconds / 60;
+                    long seconds = totalSeconds % 60;
+                    record.setTalkLenTime(minutes + "分" + seconds + "秒");
+                }
+            }
+        }
+
+        return list;
+    }
+
+    /**
+     * 新增口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        fsKdzlCallRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsKdzlCallRecord(fsKdzlCallRecord);
+    }
+
+    /**
+     * 修改口袋助理通话记录
+     * 
+     * @param fsKdzlCallRecord 口袋助理通话记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsKdzlCallRecord(FsKdzlCallRecord fsKdzlCallRecord)
+    {
+        fsKdzlCallRecord.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsKdzlCallRecord(fsKdzlCallRecord);
+    }
+
+    /**
+     * 批量删除口袋助理通话记录
+     * 
+     * @param ids 需要删除的口袋助理通话记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsKdzlCallRecordByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsKdzlCallRecordByIds(ids);
+    }
+
+    /**
+     * 删除口袋助理通话记录信息
+     * 
+     * @param id 口袋助理通话记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsKdzlCallRecordById(Long id)
+    {
+        return baseMapper.deleteFsKdzlCallRecordById(id);
+    }
+}

+ 42 - 0
fs-service/src/main/java/com/fs/hisStore/dto/MallOrderDownDTO.java

@@ -0,0 +1,42 @@
+package com.fs.hisStore.dto;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+@Data
+public class MallOrderDownDTO {
+
+    @ExcelProperty("交易单号")
+    private String tradeNo;
+
+    @ExcelProperty("商户单号")
+    private String outTradeNo;
+
+    @ExcelProperty("商户号")
+    private String merchantId;
+
+    @ExcelProperty("发货方式")
+    private String deliverType;
+
+    @ExcelProperty("发货模式")
+    private String deliverMode;
+
+    @ExcelProperty("快递公司")
+    private String deliverCompany;
+
+    @ExcelProperty("快递单号(多个快递单使用;分隔)")
+    private String deliverNo;
+
+    @ExcelProperty("是否完成发货")
+    private String deliverFinished;
+
+    @ExcelProperty("是否重新发货")
+    private String deliverReissue;
+
+    @ExcelProperty("商品信息")
+    private String productInfo;
+
+    @ExcelIgnore  // 加上这个注解,导出时就会忽略该字段
+    private Long orderId;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/hisStore/dto/MallOrderImportDTO.java

@@ -0,0 +1,25 @@
+package com.fs.hisStore.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+@Data
+public class MallOrderImportDTO {
+    @ExcelProperty("支付时间")
+    private String payTime;
+
+    @ExcelProperty("交易单号")
+    private String tradeNo;
+
+    @ExcelProperty("商户单号")
+    private String outTradeNo;
+
+    @ExcelProperty("商户号")
+    private String merchantId;
+
+    @ExcelProperty("支付金额")
+    private String payAmount;
+
+    @ExcelProperty("状态")
+    private String  status;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/hisStore/listenner/MallOrderImportDTOListener.java

@@ -0,0 +1,26 @@
+package com.fs.hisStore.listenner;
+
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.fs.hisStore.dto.MallOrderImportDTO;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MallOrderImportDTOListener extends AnalysisEventListener<MallOrderImportDTO> {
+
+    private final List<MallOrderImportDTO> cachedData  = new ArrayList<>();
+    @Override
+    public void invoke(MallOrderImportDTO mallOrderImportDTO, AnalysisContext analysisContext) {
+        cachedData.add(mallOrderImportDTO);
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+
+    }
+
+    public List<MallOrderImportDTO> getResult(){
+        return cachedData;
+    }
+}

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

@@ -1257,6 +1257,8 @@ public interface FsStoreOrderScrmMapper
      * */
     List<FsStoreOrderScrmIdVo> selectIdListByHandleCollectionIds(@Param("handwriteCollectionIds") List<Long> handwriteCollectionIds);
 
+    List<FsStoreOrderScrm> selectStoreOrderScrmByOrderIds(@Param("orderCodeList")List<Long> orderIdList);
+
 
     /**
      * 查询商城处方订单每个手机号的有效下单数列表

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

@@ -356,4 +356,6 @@ public interface FsStorePaymentScrmMapper
     List<FsStorePaymentScrm> selectFsStorePaymentByOrderScrm(Long orderId);
     @Select("select * from fs_store_payment_scrm where business_type=2 and order_id=#{orderId} limit 1")
     List<FsStorePaymentScrm> selectFsStorePaymentByOrderIdNew();
+
+    List<FsStorePaymentScrm> selectFsStorePaymentByBankTransactionIds(@Param("tradeNoList") List<String> tradeNoList);
 }

+ 8 - 4
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -23,14 +23,14 @@ import com.fs.hisStore.domain.FsStoreOrderItemScrm;
 import com.fs.hisStore.domain.FsStoreOrderLogsScrm;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.domain.FsStorePaymentScrm;
-import com.fs.hisStore.dto.ExpressNotifyDTO;
-import com.fs.hisStore.dto.ExpressResultDTO;
-import com.fs.hisStore.dto.FsStoreOrderComputeDTO;
-import com.fs.hisStore.dto.StoreOrderExpressExportDTO;
+import com.fs.hisStore.dto.*;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.vo.*;
 
 import com.fs.his.vo.FsPrescribeVO;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * 订单Service接口
@@ -313,4 +313,8 @@ public interface IFsStoreOrderScrmService
     List<FsStoreOrderScheduleStatisticsVO> selectScheduleStatisticsVOList(FsStoreOrderScheduleStatisticsParam param);
 
     Long selectScheduleStatisticsVOListCount(FsStoreOrderScheduleStatisticsParam param);
+
+    void generateMallOrderPushExcel(MultipartFile file);
+
+    List<MallOrderDownDTO> downloadMallOrderPushExcel();
 }

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

@@ -119,4 +119,6 @@ public interface IFsStorePaymentScrmService
     R paymentByWxaCode(FsStorePaymentPayParam param);
 
     List<FsStorePaymentScrm> selectFsStorePaymentByOrderIdNew(Long id);
+
+    List<FsStorePaymentScrm> selectFsStorePaymentByBankTransactionIds(List<String> bankTransactionIds);
 }

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

@@ -11,6 +11,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.excel.EasyExcel;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.TypeReference;
@@ -66,12 +67,12 @@ import com.fs.his.service.IFsPrescribeDataScrmService;
 import com.fs.his.service.IFsPrescribeService;
 import com.fs.his.service.IFsUserWatchService;
 import com.fs.his.utils.ConfigUtil;
-import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.*;
 import com.fs.his.vo.FsPrescribeVO;
 import com.fs.hisStore.config.FsErpConfig;
 import com.fs.hisStore.constants.ErpTypeEnum;
 import com.fs.hisStore.dto.*;
+import com.fs.hisStore.listenner.MallOrderImportDTOListener;
 import com.fs.hisStore.mapper.*;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.vo.*;
@@ -132,8 +133,10 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.PostConstruct;
+import java.io.IOException;
 import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.nio.charset.Charset;
@@ -5049,4 +5052,139 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     public Long selectScheduleStatisticsVOListCount(FsStoreOrderScheduleStatisticsParam param) {
         return fsStoreOrderMapper.selectScheduleStatisticsScrmVOListCount(param);
     }
+
+    @Override
+    public void generateMallOrderPushExcel(MultipartFile file) {
+        MallOrderImportDTOListener importDTOListener = new MallOrderImportDTOListener();
+        try {
+            EasyExcel.read(file.getInputStream(), MallOrderImportDTO.class, importDTOListener).sheet()
+                    .doRead();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        List<MallOrderImportDTO> dataList = importDTOListener.getResult();
+        // ========== 2. 处理数据 ==========
+        if (CollectionUtil.isNotEmpty(dataList)) {
+
+            //导出数据
+            List<MallOrderDownDTO> downList = dataList.stream()
+                    .map(importDTO -> {
+                        MallOrderDownDTO downDTO = new MallOrderDownDTO();
+                        downDTO.setTradeNo(importDTO.getTradeNo());
+                        downDTO.setOutTradeNo(importDTO.getOutTradeNo());
+                        downDTO.setMerchantId(importDTO.getMerchantId());
+
+                        // 设置默认值
+                        downDTO.setDeliverType("物流快递");
+                        downDTO.setDeliverMode("统一发货");
+                        downDTO.setDeliverCompany("顺丰速运");
+                        return downDTO;
+                    })
+                    .collect(Collectors.toList());
+
+
+            //交易单号
+            List<String> tradeNoList = dataList
+                    .stream()
+                    .map(MallOrderImportDTO::getTradeNo)
+                    .collect(Collectors.toList());
+            //支付流水
+            List<FsStorePaymentScrm> paymentScrmList = paymentService.selectFsStorePaymentByBankTransactionIds(tradeNoList);
+            if (CollectionUtil.isNotEmpty(paymentScrmList)) {
+
+                //订单号数据
+                Map<String, FsStorePaymentScrm> paymentMap = paymentScrmList
+                        .stream()
+                        .filter(p -> p.getTradeNo() != null && !p.getTradeNo().isEmpty())
+                        .collect(Collectors.toMap(
+                            FsStorePaymentScrm::getBankTransactionId,
+                            Function.identity(),
+                            (a, b) -> b
+                ));
+                downList.forEach(downDTO -> {
+                    FsStorePaymentScrm paymentScrm = paymentMap.get(downDTO.getTradeNo());
+                    if (paymentScrm != null) {
+                        downDTO.setOrderId(paymentScrm.getOrderId());
+                    }
+                });
+                List<Long> orderIds = paymentScrmList.stream().map(FsStorePaymentScrm::getOrderId).collect(Collectors.toList());
+
+                //订单
+                List<FsStoreOrderScrm> storeOrderScrms = fsStoreOrderMapper.selectStoreOrderScrmByOrderIds(orderIds);
+
+                //运单号数据
+                if (CollectionUtil.isNotEmpty(storeOrderScrms)) {
+                    Map<Long, FsStoreOrderScrm> orderScrmMap = storeOrderScrms
+                            .stream()
+                            .filter(storeOrderScrm -> storeOrderScrm.getId() != null)
+                            .collect(Collectors.toMap(FsStoreOrderScrm::getId, Function.identity()
+                                    , (existing, replacement) -> replacement  // 如果有重复key,保留后者
+                            ));
+                    downList.forEach(downDTO -> {
+                        Long orderId = downDTO.getOrderId();
+                        if (orderId != null) {
+                            FsStoreOrderScrm storeOrderScrm = orderScrmMap.get(orderId);
+                            if (storeOrderScrm != null) {
+                                downDTO.setDeliverNo(storeOrderScrm.getDeliveryId());
+                            }
+                        }
+                    });
+                }
+                //订单明细
+                List<FsStoreOrderItemVO> itemVOS = fsStoreOrderItemMapper.selectByOrderIds(orderIds);
+                Map<Long, List<FsStoreOrderItemVO>> orderItemMap = Optional.ofNullable(itemVOS)
+                        .orElse(Collections.emptyList())
+                        .stream()
+                        .filter(item -> item.getOrderId() != null)
+                        .collect(Collectors.groupingBy(FsStoreOrderItemVO::getOrderId));
+
+                //商品名称数据
+                if (CollectionUtil.isNotEmpty(orderItemMap)) {
+                    downList.forEach(downDTO -> {
+                        Long orderId = downDTO.getOrderId();
+                        if (orderId != null) {
+                            List<FsStoreOrderItemVO> itemList = orderItemMap.get(orderId);
+                            String productNames = extractProductNamesFromJson(itemList);
+                            downDTO.setProductInfo(productNames);
+                        }
+                    });
+                }
+            }
+//            try {
+//                EasyExcel.write(response.getOutputStream(), MallOrderDownDTO.class)
+//                        .sheet("订单数据")
+//                        .doWrite(downList);
+//            } catch (IOException e) {
+//                throw new CustomException(e.getMessage());
+//            }
+            redisCache.setCacheObject("wxOrder:", downList);
+        }
+    }
+    private String extractProductNamesFromJson(List<FsStoreOrderItemVO> items) {
+        if (CollectionUtils.isEmpty(items)) {
+            return "";
+        }
+
+        return items.stream()
+                .map(FsStoreOrderItemVO::getJsonInfo)
+                .filter(StringUtils::isNotBlank)
+                .map(jsonInfo -> {
+                    try {
+                        return JSONUtil.parseObj(jsonInfo).getStr("productName");
+                    } catch (Exception e) {
+                        // 记录日志,跳过解析失败的记录
+                        log.warn("解析商品名称失败, jsonInfo: {}", jsonInfo, e);
+                        return null;
+                    }
+                })
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.joining("、"));
+    }
+
+    @Override
+    public List<MallOrderDownDTO> downloadMallOrderPushExcel() {
+        List<MallOrderDownDTO> downDTOS = redisCache.getCacheObject("wxOrder:");
+        redisCache.deleteObject("wxOrder:");
+        return downDTOS;
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -902,4 +902,9 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
     public List<FsStorePaymentScrm> selectFsStorePaymentByOrderIdNew(Long id) {
         return fsStorePaymentMapper.selectFsStorePaymentByOrderIdNew();
     }
+
+    @Override
+    public List<FsStorePaymentScrm> selectFsStorePaymentByBankTransactionIds(List<String> bankTransactionIds) {
+        return fsStorePaymentMapper.selectFsStorePaymentByBankTransactionIds(bankTransactionIds);
+    }
 }

+ 19 - 0
fs-service/src/main/java/com/fs/kdzl/dto/CallFilter.java

@@ -0,0 +1,19 @@
+package com.fs.kdzl.dto;
+
+import lombok.Data;
+
+@Data
+public class CallFilter {
+    private Filter filter;
+    private CallLast last;
+    private Long limit;
+    @Data
+    public static class Filter {
+        private TimeRange timeRange;
+    }
+    @Data
+    public  static class TimeRange {
+        private Long stime;
+        private Long etime;
+    }
+}

+ 10 - 0
fs-service/src/main/java/com/fs/kdzl/dto/CallLast.java

@@ -0,0 +1,10 @@
+package com.fs.kdzl.dto;
+
+import lombok.Data;
+
+@Data
+public class CallLast {
+    private Long lastId;
+
+    private Long lastKey;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/kdzl/dto/CallRecord.java

@@ -0,0 +1,24 @@
+package com.fs.kdzl.dto;
+
+import lombok.Data;
+
+@Data
+public class CallRecord {
+    private Long uid;
+
+    private Long callTime;
+
+    private Long talkLen;
+
+    private Integer status;
+
+    private Integer type;
+
+    private String caller;
+
+    private String called;
+
+    private Long customerId;
+
+    private String customerName;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/kdzl/dto/CallRecordData.java

@@ -0,0 +1,11 @@
+package com.fs.kdzl.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CallRecordData {
+    private List<CallRecord> records;
+    private CallLast last;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/kdzl/dto/CallRecordDataResponse.java

@@ -0,0 +1,12 @@
+package com.fs.kdzl.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CallRecordDataResponse {
+    private Integer result;
+    private String errmsg;
+    private CallRecordData data;
+}

+ 3 - 0
fs-service/src/main/java/com/fs/kdzl/service/KdzlService.java

@@ -1,5 +1,6 @@
 package com.fs.kdzl.service;
 
+import com.fs.kdzl.dto.CallRecord;
 import com.fs.kdzl.dto.Custm;
 import com.fs.kdzl.dto.KdzlCustomerDTO;
 
@@ -27,4 +28,6 @@ public interface KdzlService {
      */
     List<Custm> exportHighSeasCustomers(Long psid);
 
+    List<CallRecord> exportCallRecords();
+
 }

+ 200 - 5
fs-service/src/main/java/com/fs/kdzl/service/impl/KdzlServiceImpl.java

@@ -1,13 +1,10 @@
 package com.fs.kdzl.service.impl;
 
-import com.alibaba.fastjson.JSONArray;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.http.HttpUtils;
-import com.fs.kdzl.dto.Custm;
-import com.fs.kdzl.dto.CustomerResponse;
-import com.fs.kdzl.dto.HighSeaCustomerResponse;
-import com.fs.kdzl.dto.KdzlCustomerDTO;
+import com.fs.kdzl.dto.*;
 import com.fs.kdzl.service.KdzlService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -15,7 +12,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -34,6 +35,7 @@ public class KdzlServiceImpl implements KdzlService {
     private static final int CACHE_ADVANCE_EXPIRE_SECONDS = 600;
     private static final String CUSTOMER_NEXT_CACHE_KEY = "kdzl:customer_next";
     private static final int MAX_COUNT = 2000;
+    private static final String CALL_RECORD_LAST_CACHE_KEY = "kdzl:call_record_last";
 
     @Autowired
     private RedisCache redisCache;
@@ -197,4 +199,197 @@ public class KdzlServiceImpl implements KdzlService {
         return customerList;
     }
 
+//    @Override
+//    public List<CallRecord> exportCallRecords() {
+//        List<CallRecord> callRecordList = new ArrayList<>();
+//        try {
+//            String accessToken = getAccessToken();
+//            if (accessToken.isEmpty()) {
+//                log.error("获取access_token失败,无法导出通话记录");
+//                return callRecordList;
+//            }
+//            //构建查询参数
+//            CallFilter callFilter = new CallFilter();
+//            //分页
+//            CallLast last = null;
+//            callFilter.setCallLast(last);
+//            //查询日期
+//            long eTime = Instant.now().toEpochMilli();
+//            ZonedDateTime zonedDateTime = ZonedDateTime.of(2026, 6, 19, 0, 0, 0, 0, ZoneOffset.ofHours(8));
+//            long sTime = zonedDateTime.toInstant().toEpochMilli();
+//            CallFilter.TimeRange timeRange = new CallFilter.TimeRange();
+//            timeRange.setStime(sTime);
+//            timeRange.setEtime(eTime);
+//
+//            CallFilter.Filter filter = new CallFilter.Filter();
+//            filter.setTimeRange(timeRange);
+//            callFilter.setFilter(filter);
+//            //查询条数
+//            callFilter.setLimit(2000L);
+//
+//            boolean hasMore = true;
+//            while (hasMore) {
+//                String url = URL1 + "/open/api/phonesale/getCallRecordList?access_token=" + accessToken;
+//                String response = HttpUtils.doPost(url, JSONUtil.toJsonStr(callFilter));
+//                if (response.isEmpty()) {
+//                    log.error("导出通话记录失败,响应为空");
+//                    return callRecordList;
+//                }
+//                JSONObject jsonObject = JSONObject.parseObject(response);
+//                CallRecordDataResponse callRecordData  = jsonObject.toJavaObject(CallRecordDataResponse.class);
+//                Integer result = callRecordData.getResult();
+//                if (result != null && result == 0) {
+//                    CallRecordData data = callRecordData.getData();
+//                    if (data != null && !CollectionUtils.isEmpty(data.getRecords())) {
+//                        callRecordList.addAll(callRecordData.getData().getRecords());
+//                        log.info("成功导出通话记录数量: {}, 当前分页: {}", callRecordList.size(), callFilter.getCallLast());
+//                        CallLast nextLast = data.getLast();
+//                        if (nextLast != null) {
+//                            callFilter.setLast(nextLast);
+//                        } else {
+//                            hasMore = false;  // 没有下一页了
+//                        }
+//                    }
+//                }  else {
+//                    String errMsg = jsonObject.getString("errmsg");
+//                    log.error("导出通话记录失败,错误码: {}, 错误信息: {}", result, errMsg);
+//                }
+//            }
+////            String url = URL1 + "/open/api/phonesale/getCallRecordList?access_token=" + accessToken;
+////            String response = HttpUtils.doPost(url, JSONUtil.toJsonStr(callFilter));
+////
+////            if (response.isEmpty()) {
+////                log.error("导出通话记录失败,响应为空");
+////                return callRecordList;
+////            }
+////
+////            JSONObject jsonObject = JSONObject.parseObject(response);
+////            CallRecordDataResponse callRecordData  = jsonObject.toJavaObject(CallRecordDataResponse.class);
+////            Integer result = callRecordData.getResult();
+////
+////            if (result != null && result == 0) {
+////                if (callRecordData.getData() != null && !CollectionUtils.isEmpty(callRecordData.getData().getRecords())) {
+////                    callRecordList = callRecordData.getData().getRecords();
+////                    log.info("成功导出通话记录数量: {}, 当前分页: {}", callRecordList.size(), callFilter.getCallLast());
+////                    last = callRecordData.getData().getLast();
+////                }
+////            } else {
+////                String errMsg = jsonObject.getString("errmsg");
+////                log.error("导出通话记录失败,错误码: {}, 错误信息: {}", result, errMsg);
+////            }
+//        } catch (Exception e) {
+//            log.error("导出通话记录异常", e);
+//        }
+//        return callRecordList;
+//    }
+@Override
+public List<CallRecord> exportCallRecords() {
+    List<CallRecord> callRecordList = new ArrayList<>();
+    try {
+        String accessToken = getAccessToken();
+        if (accessToken.isEmpty()) {
+            log.error("获取access_token失败,无法导出通话记录");
+            return callRecordList;
+        }
+
+        CallFilter callFilter = new CallFilter();
+        long eTime = Instant.now().toEpochMilli();
+        ZonedDateTime zonedDateTime = ZonedDateTime.of(2026, 6, 19, 0, 0, 0, 0, ZoneOffset.ofHours(8));
+        long sTime = zonedDateTime.toInstant().toEpochMilli();
+
+        CallFilter.TimeRange timeRange = new CallFilter.TimeRange();
+        timeRange.setStime(sTime);
+        timeRange.setEtime(eTime);
+
+        CallFilter.Filter filter = new CallFilter.Filter();
+        filter.setTimeRange(timeRange);
+        callFilter.setFilter(filter);
+        callFilter.setLimit(2000L);
+
+        int emptyCount = 0;  // 连续空数据计数器
+        final int MAX_EMPTY_RETRY = 3;  // 最大空数据重试次数
+        final int MAX_TOTAL_RECORDS = 100000;  // 安全上限,防止无限循环
+
+        boolean hasMore = true;
+        while (hasMore) {
+            String url = URL1 + "/open/api/phonesale/getCallRecordList?access_token=" + accessToken;
+            String jsonStr = JSONUtil.toJsonStr(callFilter);
+            String response = HttpUtils.doPost(url, jsonStr);
+
+            if (response.isEmpty()) {
+                log.error("导出通话记录失败,响应为空");
+                break;  // 改为 break 而不是 return,保留已获取的数据
+            }
+
+            JSONObject jsonObject = JSONObject.parseObject(response);
+            CallRecordDataResponse callRecordData = jsonObject.toJavaObject(CallRecordDataResponse.class);
+            Integer result = callRecordData.getResult();
+
+            if (result != null && result == 0) {
+                CallRecordData data = callRecordData.getData();
+
+                if (data != null && !CollectionUtils.isEmpty(data.getRecords())) {
+                    int beforeSize = callRecordList.size();
+                    callRecordList.addAll(data.getRecords());
+                    int addedCount = callRecordList.size() - beforeSize;
+
+                    log.info("成功导出通话记录,本次新增: {}, 累计总数: {}, 当前分页参数: {}",
+                            addedCount, callRecordList.size(), callFilter.getLast());
+
+                    // 安全检查1:总数量上限
+                    if (callRecordList.size() >= MAX_TOTAL_RECORDS) {
+                        log.warn("通话记录数量达到安全上限 {},停止导出", MAX_TOTAL_RECORDS);
+                        break;
+                    }
+
+                    // 安全检查2:数据是否真的增加了(防止重复数据)
+                    if (addedCount == 0) {
+                        emptyCount++;
+                        log.warn("本次未新增数据,连续空次数: {}/{}", emptyCount, MAX_EMPTY_RETRY);
+                        if (emptyCount >= MAX_EMPTY_RETRY) {
+                            log.error("连续 {} 次未获取到新数据,可能存在死循环,强制退出", MAX_EMPTY_RETRY);
+                            break;
+                        }
+                    } else {
+                        emptyCount = 0;  // 重置计数器
+                    }
+
+                    CallLast nextLast = data.getLast();
+
+                    // 安全检查3:last 对象是否与本次请求相同(死循环检测)
+                    if (nextLast != null && nextLast.equals(callFilter.getLast())) {
+                        log.error("检测到重复的分页参数,last 对象未变化,强制退出循环。last: {}", nextLast);
+                        break;
+                    }
+
+                    if (nextLast != null) {
+                        callFilter.setLast(nextLast);
+                    } else {
+                        log.info("没有下一页了,分页结束");
+                        hasMore = false;
+                    }
+                } else {
+                    log.info("未查询到通话记录数据,分页结束");
+                    hasMore = false;
+                }
+            } else {
+                String errMsg = jsonObject.getString("errmsg");
+                log.error("导出通话记录失败,错误码: {}, 错误信息: {}", result, errMsg);
+                break;  // 错误时退出,保留已获取数据
+            }
+        }
+    } catch (Exception e) {
+        log.error("导出通话记录异常", e);
+    }
+    return callRecordList;
+}
+    public static void main(String[] args) {
+        long epochMilli = Instant.now().toEpochMilli();
+        System.out.println(epochMilli);
+
+        ZonedDateTime zonedDateTime = ZonedDateTime.of(2026, 6, 19, 0, 0, 0, 0, ZoneOffset.ofHours(8));
+        long epochMilli1 = zonedDateTime.toInstant().toEpochMilli();
+        System.out.println(epochMilli1);
+    }
+
 }

+ 127 - 0
fs-service/src/main/resources/mapper/his/FsKdzlCallRecordMapper.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.his.mapper.FsKdzlCallRecordMapper">
+    
+    <resultMap type="FsKdzlCallRecord" id="FsKdzlCallRecordResult">
+        <result property="id"    column="id"    />
+        <result property="uid"    column="uid"    />
+        <result property="callTime"    column="call_time"    />
+        <result property="talkLen"    column="talk_len"    />
+        <result property="status"    column="status"    />
+        <result property="type"    column="type"    />
+        <result property="caller"    column="caller"    />
+        <result property="called"    column="called"    />
+        <result property="customerId"    column="customer_id"    />
+        <result property="customerName"    column="customer_name"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsKdzlCallRecordVo">
+        select id, uid, call_time, talk_len, status, type, caller, called, customer_id, customer_name, create_time, update_time from fs_kdzl_call_record
+    </sql>
+
+    <select id="selectFsKdzlCallRecordList" parameterType="FsKdzlCallRecord" resultMap="FsKdzlCallRecordResult">
+        <include refid="selectFsKdzlCallRecordVo"/>
+        <where>  
+            <if test="uid != null "> and uid = #{uid}</if>
+            <if test="callTime != null "> and call_time = #{callTime}</if>
+            <if test="talkLen != null "> and talk_len = #{talkLen}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="type != null "> and type = #{type}</if>
+            <if test="caller != null  and caller != ''"> and caller = #{caller}</if>
+            <if test="called != null  and called != ''"> and called = #{called}</if>
+            <if test="customerId != null "> and customer_id = #{customerId}</if>
+            <if test="customerName != null  and customerName != ''"> and customer_name like concat('%', #{customerName}, '%')</if>
+            <if test="beginCallTime != null and endCallTime != null"> and call_time between #{beginCallTime} and #{endCallTime}</if>
+        </where>
+    </select>
+    
+    <select id="selectFsKdzlCallRecordById" parameterType="Long" resultMap="FsKdzlCallRecordResult">
+        <include refid="selectFsKdzlCallRecordVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertFsKdzlCallRecord" parameterType="FsKdzlCallRecord">
+        insert into fs_kdzl_call_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="uid != null">uid,</if>
+            <if test="callTime != null">call_time,</if>
+            <if test="talkLen != null">talk_len,</if>
+            <if test="status != null">status,</if>
+            <if test="type != null">type,</if>
+            <if test="caller != null">caller,</if>
+            <if test="called != null">called,</if>
+            <if test="customerId != null">customer_id,</if>
+            <if test="customerName != null">customer_name,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="uid != null">#{uid},</if>
+            <if test="callTime != null">#{callTime},</if>
+            <if test="talkLen != null">#{talkLen},</if>
+            <if test="status != null">#{status},</if>
+            <if test="type != null">#{type},</if>
+            <if test="caller != null">#{caller},</if>
+            <if test="called != null">#{called},</if>
+            <if test="customerId != null">#{customerId},</if>
+            <if test="customerName != null">#{customerName},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsKdzlCallRecord" parameterType="FsKdzlCallRecord">
+        update fs_kdzl_call_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="uid != null">uid = #{uid},</if>
+            <if test="callTime != null">call_time = #{callTime},</if>
+            <if test="talkLen != null">talk_len = #{talkLen},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="type != null">type = #{type},</if>
+            <if test="caller != null">caller = #{caller},</if>
+            <if test="called != null">called = #{called},</if>
+            <if test="customerId != null">customer_id = #{customerId},</if>
+            <if test="customerName != null">customer_name = #{customerName},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsKdzlCallRecordById" parameterType="Long">
+        delete from fs_kdzl_call_record where id = #{id}
+    </delete>
+
+    <delete id="deleteFsKdzlCallRecordByIds" parameterType="String">
+        delete from fs_kdzl_call_record where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <!-- 批量查询已存在的组合 -->
+    <select id="selectExistingFsKdzlCallRecordList" resultMap="FsKdzlCallRecordResult">
+        SELECT *
+        FROM fs_kdzl_call_record
+        WHERE (uid, call_time, caller, called, type) IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            (#{item.uid}, #{item.callTime}, #{item.caller}, #{item.called}, #{item.type})
+        </foreach>
+    </select>
+
+    <!-- 只插入不存在的记录 -->
+    <insert id="batchInsertFsKdzlCallRecord">
+        INSERT IGNORE INTO fs_kdzl_call_record (uid, call_time, talk_len, status,  customer_id, customer_name,  caller, called, type, create_time)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.uid}, #{item.callTime}, #{item.talkLen}, #{item.status},  #{item.customerId},#{item.customerName}, #{item.caller}, #{item.called}, #{item.type}, NOW())
+        </foreach>
+        <!-- 这里配合Java逻辑,过滤掉已存在的 -->
+    </insert>
+</mapper>

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

@@ -1211,4 +1211,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY user_phone
     </select>
 
+
+    <select id="selectStoreOrderScrmByOrderIds" resultMap="FsStoreOrderResult">
+        <include refid="selectFsStoreOrderVo"/>
+        where id in
+        <foreach collection="orderCodeList" item="orderId" open="(" separator="," close=")">
+            #{orderId}
+        </foreach>
+    </select>
+
 </mapper>

+ 8 - 0
fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

@@ -215,5 +215,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="selectFsStorePaymentByBankTransactionIds" resultMap="FsStorePaymentResult">
+        <include refid="selectFsStorePaymentVo"/>
+        where bank_transaction_id in
+        <foreach collection="tradeNoList" item="tradeNo" open="(" separator="," close=")">
+            #{tradeNo}
+        </foreach>
+    </select>
+
 
 </mapper>