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

新增营销调取ai功能

YPZ 2 тижнів тому
батько
коміт
27fef83cac
15 змінених файлів з 372 додано та 20 видалено
  1. 1 1
      java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/ChatController.java
  2. 1 1
      java/storlead-knowledge/storlead-knowledge-biz/src/main/java/com/storlead/knowledge/service/impl/ChatServiceImpl.java
  3. 4 0
      java/storlead-sasa/storlead-trade/pom.xml
  4. 10 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/config/AsyncConfig.java
  5. 3 1
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketEmailsController.java
  6. 3 3
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingCampaignSopDetailController.java
  7. 19 4
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketEmailsEntity.java
  8. 2 2
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignSopDetailEntity.java
  9. 10 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/mapper/MarketEmailsMapper.java
  10. 9 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketEmailsService.java
  11. 195 4
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketEmailsServiceImpl.java
  12. 10 4
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignEntityServiceImpl.java
  13. 37 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketEmailsCustomerVO.java
  14. 33 0
      java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/ScheduleStageVO.java
  15. 35 0
      java/storlead-sasa/storlead-trade/src/main/resources/mapper/MarketEmailsMapper.xml

+ 1 - 1
java/storlead-knowledge/storlead-knowledge-api/src/main/java/com/storlead/knowledge/api/ChatController.java

@@ -180,7 +180,7 @@ public class ChatController {
             entity.setAppId(chatDTO.getAppId());
             entity.setInputs(chatDTO.getInputs() != null ? JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO.getInputs()) : null);
             entity.setQuery(chatDTO.getQuery());
-            entity.setResponseMode(chatDTO.getResponse_mode());
+            entity.setResponseMode(chatDTO.getResponseMode());
             entity.setUserId(chatDTO.getUser());
             // streaming 模式下 answer 在 metadata 中逐句返回,message_end 事件本身不含完整 answer,此处留空
             entity.setAnswer(getJsonText(root, "answer"));

+ 1 - 1
java/storlead-knowledge/storlead-knowledge-biz/src/main/java/com/storlead/knowledge/service/impl/ChatServiceImpl.java

@@ -76,7 +76,7 @@ public class ChatServiceImpl implements ChatService {
             entity.setAppId(chatDTO.getAppId());
             entity.setInputs(chatDTO.getInputs() != null ? JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO.getInputs()) : null);
             entity.setQuery(chatDTO.getQuery());
-            entity.setResponseMode(chatDTO.getResponse_mode());
+            entity.setResponseMode(chatDTO.getResponseMode());
             entity.setUserId(chatDTO.getUser());
             entity.setAnswer(getJsonText(root, "answer"));
             String createdAt = getJsonText(root, "created_at");

+ 4 - 0
java/storlead-sasa/storlead-trade/pom.xml

@@ -76,6 +76,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-knowledge-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-knowledge-spi</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 10 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/config/AsyncConfig.java

@@ -0,0 +1,10 @@
+package com.storlead.trade.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+    // 启用 Spring 异步支持
+}

+ 3 - 1
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketEmailsController.java

@@ -16,12 +16,14 @@ import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
+import javax.annotation.Resource;
+
 @RestController
 @RequestMapping("/marketEmails")
 @Api(tags = "营销: 营销邮件管理")
 public class MarketEmailsController {
 
-    @Autowired
+    @Resource
     private MarketEmailsService marketEmailsService;
 
     @PostMapping("/list")

+ 3 - 3
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingCampaignSopDetailController.java

@@ -78,7 +78,7 @@ public class MarketingCampaignSopDetailController {
         LambdaQueryWrapper<MarketingCampaignSopDetailEntity> wrapper = new LambdaQueryWrapper<>();
         // 按营销活动id筛选
         if (!ObjectUtils.isEmpty(dto.getMarketingCampaignId())) {
-            wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, dto.getMarketingCampaignId());
+            wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, dto.getMarketingCampaignId());
         }
         // 按SOP明细id筛选
         if (!ObjectUtils.isEmpty(dto.getSopDetailId())) {
@@ -135,7 +135,7 @@ public class MarketingCampaignSopDetailController {
 
         // 查询该营销活动下的所有SOP明细(按步骤排序)
         LambdaQueryWrapper<MarketingCampaignSopDetailEntity> sopWrapper = new LambdaQueryWrapper<>();
-        sopWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, dto.getMarketingCampaignId());
+        sopWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, dto.getMarketingCampaignId());
         sopWrapper.orderByAsc(MarketingCampaignSopDetailEntity::getStep);
         List<MarketingCampaignSopDetailEntity> sopDetails = marketingCampaignSopDetailService.list(sopWrapper);
         if (CollectionUtils.isEmpty(sopDetails)) {
@@ -408,7 +408,7 @@ public class MarketingCampaignSopDetailController {
     @ApiOperation(value = "根据营销活动id查询SOP明细列表", notes = "查询该营销活动关联的所有SOP明细")
     public Result<Object> getByCampaignId(@PathVariable Long marketingCampaignId) {
         LambdaQueryWrapper<MarketingCampaignSopDetailEntity> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignId);
+        wrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, marketingCampaignId);
         wrapper.orderByAsc(MarketingCampaignSopDetailEntity::getStep);
         List<MarketingCampaignSopDetailEntity> list = marketingCampaignSopDetailService.list(wrapper);
         return Result.result(list);

+ 19 - 4
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketEmailsEntity.java

@@ -1,12 +1,15 @@
 package com.storlead.trade.entity;
 
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import cn.hutool.core.date.DateTime;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.storlead.framework.mybatis.entity.SysBaseField;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
 
 @Data
 @TableName("market_emails")
@@ -43,4 +46,16 @@ public class MarketEmailsEntity extends SysBaseField {
     @ApiModelProperty(value = "是否已回复:0否,1是")
     @TableField("has_reply")
     private Integer hasReply;
+
+    @ApiModelProperty(value = "预计发送时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    @TableField("expect_send_time")
+    private DateTime expectSendTime;
+
+    @ApiModelProperty(value = "邮件内容")
+    @TableField("content")
+    private String content;
+
 }

+ 2 - 2
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketingCampaignSopDetailEntity.java

@@ -26,8 +26,8 @@ public class MarketingCampaignSopDetailEntity extends SysBaseField implements Se
     private Long id;
 
     @ApiModelProperty(value = "营销活动id")
-    @TableField("marketing_campaign_id")
-    private Long marketingCampaignId;
+    @TableField("marketing_id")
+    private Long marketingId;
 
     @ApiModelProperty(value = "SOP明细id")
     @TableField("sop_detail_id")

+ 10 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/mapper/MarketEmailsMapper.java

@@ -1,9 +1,19 @@
 package com.storlead.trade.mapper;
 
+import com.storlead.trade.dto.MarketEmailsDTO;
 import com.storlead.trade.mapper.support.TradeBaseMapper;
 import com.storlead.trade.entity.MarketEmailsEntity;
+import com.storlead.trade.vo.MarketEmailsCustomerVO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 @Mapper
 public interface MarketEmailsMapper extends TradeBaseMapper<MarketEmailsEntity> {
+
+    /**
+     * 查询营销邮件关联客户的分析结果信息
+     */
+    List<MarketEmailsCustomerVO> selectCustomerInfoByCampaign(@Param("dto") MarketEmailsDTO dto);
 }

+ 9 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketEmailsService.java

@@ -2,8 +2,13 @@ package com.storlead.trade.service;
 
 import com.storlead.trade.dto.MarketEmailsDTO;
 import com.storlead.trade.entity.MarketEmailsEntity;
+import com.storlead.trade.entity.MarketingCampaignEntity;
+import com.storlead.trade.vo.MarketEmailsCustomerVO;
 import com.storlead.framework.common.result.Result;
 import com.storlead.framework.mybatis.service.MyBaseService;
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.List;
 
 public interface MarketEmailsService extends MyBaseService<MarketEmailsEntity> {
 
@@ -12,4 +17,8 @@ public interface MarketEmailsService extends MyBaseService<MarketEmailsEntity> {
     Result<Object> add(MarketEmailsDTO marketEmailsDTO);
 
     Result<Object> edit(MarketEmailsDTO marketEmailsDTO);
+
+    @Async
+    void addSendTime(MarketingCampaignEntity marketingCampaignEntity);
+
 }

+ 195 - 4
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketEmailsServiceImpl.java

@@ -1,28 +1,50 @@
 package com.storlead.trade.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.storlead.framework.common.constant.CommonConstant;
 import com.storlead.framework.common.result.Result;
 import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.knowledge.pojo.dto.ChatDTO;
+import com.storlead.knowledge.service.ChatService;
 import com.storlead.trade.dto.MarketEmailsDTO;
-import com.storlead.trade.entity.MarketEmailsEntity;
+import com.storlead.trade.entity.*;
 import com.storlead.trade.mapper.MarketEmailsMapper;
-import com.storlead.trade.service.MarketEmailsService;
+import com.storlead.trade.service.*;
+import com.storlead.trade.vo.MarketEmailsCustomerVO;
+import com.storlead.trade.vo.ScheduleStageVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
 
-import java.util.ArrayList;
-import java.util.List;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
 
 @Service
 public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMapper, MarketEmailsEntity>
         implements MarketEmailsService {
 
+    @Resource
+    private CustomerBaseEntityService customerBaseService;
+
+    @Resource
+    private CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
+
+    @Resource
+    private CustomerEntityService customerEntityService;
+    @Resource
+    private MarketingCampaignSopDetailService marketingCampaignSopDetailEntityService;
+    @Resource
+    private SopDetailService sopDetailService;
+    @Resource
+    private ChatService chatService;
+
+
     @Override
     public Result<Object> getList(MarketEmailsDTO dto) {
         Page<MarketEmailsEntity> page = new Page<>(dto.getPageIndex(), dto.getPageSize());
@@ -98,4 +120,173 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         }
         return Result.ok();
     }
+
+
+    @Override
+    public void addSendTime(MarketingCampaignEntity marketingCampaignEntity) {
+        //获取营销活动对应的SopList
+        LambdaQueryWrapper<MarketingCampaignSopDetailEntity> markerSopDetailWrapper = new LambdaQueryWrapper<>();
+        markerSopDetailWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, marketingCampaignEntity.getId());
+        markerSopDetailWrapper.eq(MarketingCampaignSopDetailEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        List<MarketingCampaignSopDetailEntity> marketingCampaignSopDetailEntityList = marketingCampaignSopDetailEntityService.list(markerSopDetailWrapper);
+        if (CollectionUtils.isEmpty(marketingCampaignSopDetailEntityList)){
+            return ;
+        }
+        //获取List sop_detail_id
+        List<Long> sopDetailIdList = marketingCampaignSopDetailEntityList.stream()
+                .map(MarketingCampaignSopDetailEntity::getSopDetailId)
+                .collect(Collectors.toList());
+        List<SopDetailEntity> sopDetailEntityList = sopDetailService.listByIds(sopDetailIdList).stream().toList();
+        // 1. 查询营销关联的客户基础信息
+        LambdaQueryWrapper<CustomerBaseEntity> customerBaseWrapper = new LambdaQueryWrapper<>();
+        customerBaseWrapper.eq(CustomerBaseEntity::getMarketingCampaignId, marketingCampaignEntity.getId());
+        customerBaseWrapper.eq(CustomerBaseEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        List<CustomerBaseEntity> customerBaseList = customerBaseService.list(customerBaseWrapper);
+        if (CollectionUtils.isEmpty(customerBaseList)) {
+            return ;
+        }
+
+        // 2. 提取分析结果id列表
+        List<Long> customerAnalysisIdList = customerBaseList.stream()
+                .map(CustomerBaseEntity::getCustomerAnalysisId)
+                .collect(Collectors.toList());
+
+        // 3. 批量查询分析结果(取 customerId = customer.id)
+        LambdaQueryWrapper<CustomerAnalysisResultEntity> analysisWrapper = new LambdaQueryWrapper<>();
+        analysisWrapper.in(CustomerAnalysisResultEntity::getId, customerAnalysisIdList);
+        analysisWrapper.eq(CustomerAnalysisResultEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        List<CustomerAnalysisResultEntity> analysisList = customerAnalysisResultEntityService.list(analysisWrapper);
+
+        // 4. 提取客户id列表,批量查询客户基本信息
+        List<Long> customerIdList = analysisList.stream()
+                .map(CustomerAnalysisResultEntity::getCustomerId)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Long, CustomerEntity> customerMap = customerIdList.isEmpty()
+                ? Collections.emptyMap()
+                : customerEntityService.listByIds(customerIdList).stream()
+                        .collect(Collectors.toMap(CustomerEntity::getId, c -> c));
+
+        // 5. 构造返回结果
+        List<MarketEmailsCustomerVO> voList = new ArrayList<>();
+        for (CustomerAnalysisResultEntity analysis : analysisList) {
+            MarketEmailsCustomerVO vo = new MarketEmailsCustomerVO();
+            vo.setAnalysisResultId(analysis.getId());
+            vo.setCustomerId(analysis.getCustomerId());
+            vo.setCustomerLevel(analysis.getCustomerLevel());
+            vo.setPotentialScore(analysis.getPotentialScore());
+            vo.setConfidence(analysis.getConfidence());
+
+            CustomerEntity customer = customerMap.get(analysis.getCustomerId());
+            if (customer != null) {
+                vo.setCustomerName(customer.getCustomerName());
+                vo.setCountry(customer.getCountry());
+                vo.setContinent(customer.getContinent());
+            }
+            voList.add(vo);
+        }
+        //按照国家对voList进行分组
+        List<MarketEmailsEntity> marketEmailList = new ArrayList<>();
+        Map<String, List<MarketEmailsCustomerVO>> voListByCountry = voList.stream()
+                .collect(Collectors.groupingBy(MarketEmailsCustomerVO::getCountry));
+
+        for (Map.Entry<String, List<MarketEmailsCustomerVO>> entry : voListByCountry.entrySet()){
+            String country = entry.getKey();
+            List<MarketEmailsCustomerVO> customerVOList = entry.getValue();
+
+            Map sendMap = new HashMap();
+            sendMap.put("conuntry", country);
+            sendMap.put("steps", sopDetailEntityList);
+
+            ChatDTO chatDTO = new ChatDTO();
+            chatDTO.setInputs(sendMap);
+            chatDTO.setQuery("send_time");
+            chatDTO.setResponseMode("blocking");
+            chatDTO.setAppId("app-USCuwcmrJi3SwwSz64viUczH");
+            chatDTO.setUser("1");
+
+            //调取/router/rest/workflows/streaming接口获取时间
+            Result<Object> result = chatService.requestAnalysisToAi(chatDTO);
+            if (result == null || !result.isSuccess() || result.getResult() == null) {
+                continue;
+            }
+
+            // 解析 AI 返回的调度阶段(不同国家)
+            List<ScheduleStageVO> scheduleList = parseScheduleStages(result.getResult());
+            if (CollectionUtils.isEmpty(scheduleList)) {
+                continue;
+            }
+            // 按 sequence 升序(1,2,3,4),与 sopDetailEntityList 按 step 顺次对齐
+            scheduleList.sort(Comparator.comparing(ScheduleStageVO::getSequence));
+            for (MarketEmailsCustomerVO customerVO : customerVOList) {
+                for (ScheduleStageVO scheduleStage : scheduleList){
+                    //对比sopDetailEntityList和scheduleStage找到step和sequence相同的SopDetailEntity
+                    SopDetailEntity sopDetail = sopDetailEntityList.stream()
+                            .filter(detail -> detail.getStep().equals(scheduleStage.getSequence()))
+                            .findFirst()
+                            .orElse(null);
+                    if (ObjectUtils.isEmpty(sopDetail)) {
+                        continue;
+                    }
+                    MarketEmailsEntity marketEmail = new MarketEmailsEntity();
+                    marketEmail.setMarketingCampaignId(marketingCampaignEntity.getId());
+                    marketEmail.setCustomerAnalysisId(customerVO.getAnalysisResultId());
+                    marketEmail.setSopDetailId(sopDetail.getId());
+                    marketEmail.setExpectSendTime(scheduleStage.getBeijingDatetime());
+                    marketEmailList.add(marketEmail);
+                }
+
+
+            }
+
+
+        }
+        if (!marketEmailList.isEmpty()){
+            this.saveBatch(marketEmailList);
+        }
+
+
+    }
+
+    /**
+     * 解析 AI 返回的调度阶段。返回值可能是 Map / JSON 字符串 / 列表,均统一转为 List<ScheduleStageVO>。
+     */
+    @SuppressWarnings("unchecked")
+    private List<ScheduleStageVO> parseScheduleStages(Object aiResult) {
+        try {
+            // 1) 已经是 List
+            if (aiResult instanceof List) {
+                return JSON.parseArray(JSON.toJSONString(aiResult), ScheduleStageVO.class);
+            }
+            // 2) 是 Map(常见:Map.of("answer", jsonString) 或 Map.of("data", Map.of("answer",...)))
+            if (aiResult instanceof Map) {
+                Map<String, Object> map = (Map<String, Object>) aiResult;
+                Object answer = firstNonNull(map.get("answer"), map.get("data"));
+                if (answer instanceof Map) {
+                    answer = firstNonNull(((Map<String, Object>) answer).get("answer"),
+                            ((Map<String, Object>) answer).get("output"));
+                }
+                if (answer == null) {
+                    return Collections.emptyList();
+                }
+                String json = answer.toString();
+                return JSON.parseArray(json, ScheduleStageVO.class);
+            }
+            // 3) 是 JSON 字符串
+            String s = aiResult.toString();
+            if (s.trim().startsWith("[")) {
+                return JSON.parseArray(s, ScheduleStageVO.class);
+            }
+            return Collections.emptyList();
+        } catch (Exception e) {
+            return Collections.emptyList();
+        }
+    }
+
+    private static Object firstNonNull(Object... arr) {
+        for (Object o : arr) {
+            if (o != null) return o;
+        }
+        return null;
+    }
 }

+ 10 - 4
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignEntityServiceImpl.java

@@ -44,6 +44,8 @@ public class MarketingCampaignEntityServiceImpl
     private SopDetailService sopDetailService;
     @Resource
     private MarketingCampaignSopDetailService marketingCampaignSopDetailService;
+    @Resource
+    private MarketEmailsService marketEmailsService;
     @Override
     public Result<Object> getList(MarketingCampaignDTO marketingCampaignDTO) {
         IPage<MarketingCampaignVO> pageVO = new Page<>(marketingCampaignDTO.getPageIndex(), marketingCampaignDTO.getPageSize());
@@ -143,7 +145,7 @@ public class MarketingCampaignEntityServiceImpl
                 this.updateById(marketingCampaignEntity);
                 //删除原有关联sopDetail
                 LambdaUpdateWrapper<MarketingCampaignSopDetailEntity> MarketingCampaignSopDetailUpdateWrapper = new LambdaUpdateWrapper<>();
-                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignEntity.getId());
+                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, marketingCampaignEntity.getId());
                 MarketingCampaignSopDetailUpdateWrapper.set(MarketingCampaignSopDetailEntity::getIsDelete, 1);
                 marketingCampaignSopDetailService.update(MarketingCampaignSopDetailUpdateWrapper);
 
@@ -151,13 +153,17 @@ public class MarketingCampaignEntityServiceImpl
                 List<MarketingCampaignSopDetailEntity> marketingCampaignSopDetailEntityList = new ArrayList<>();
                 for (SopDetailEntity sopDetailEntity : sopDetailEntityList) {
                     MarketingCampaignSopDetailEntity marketingCampaignSopDetailEntity = new MarketingCampaignSopDetailEntity();
-                    marketingCampaignSopDetailEntity.setMarketingCampaignId(marketingCampaignEntity.getId());
+                    marketingCampaignSopDetailEntity.setMarketingId(marketingCampaignEntity.getId());
                     marketingCampaignSopDetailEntity.setSopDetailId(sopDetailEntity.getId());
                     //预留补充ai生成的邮件内容模板
 //                    marketingCampaignSopDetailEntity.setEmailContent();
                     marketingCampaignSopDetailEntityList.add(marketingCampaignSopDetailEntity);
                 }
                 boolean saveOk = marketingCampaignSopDetailService.saveBatch(marketingCampaignSopDetailEntityList);
+
+                //生成每个客户的邮件发送时间
+                marketEmailsService.addSendTime(marketingCampaignEntity);
+
             }
 
         }
@@ -203,7 +209,7 @@ public class MarketingCampaignEntityServiceImpl
                 this.updateById(marketingCampaignEntity);
                 //删除原有关联sopDetail
                 LambdaUpdateWrapper<MarketingCampaignSopDetailEntity> MarketingCampaignSopDetailUpdateWrapper = new LambdaUpdateWrapper<>();
-                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingCampaignId, marketingCampaignEntity.getId());
+                MarketingCampaignSopDetailUpdateWrapper.eq(MarketingCampaignSopDetailEntity::getMarketingId, marketingCampaignEntity.getId());
                 MarketingCampaignSopDetailUpdateWrapper.set(MarketingCampaignSopDetailEntity::getIsDelete, 1);
                 marketingCampaignSopDetailService.update(MarketingCampaignSopDetailUpdateWrapper);
 
@@ -211,7 +217,7 @@ public class MarketingCampaignEntityServiceImpl
                 List<MarketingCampaignSopDetailEntity> marketingCampaignSopDetailEntityList = new ArrayList<>();
                 for (SopDetailEntity sopDetailEntity : sopDetailEntityList) {
                     MarketingCampaignSopDetailEntity marketingCampaignSopDetailEntity = new MarketingCampaignSopDetailEntity();
-                    marketingCampaignSopDetailEntity.setMarketingCampaignId(marketingCampaignEntity.getId());
+                    marketingCampaignSopDetailEntity.setMarketingId(marketingCampaignEntity.getId());
                     marketingCampaignSopDetailEntity.setSopDetailId(sopDetailEntity.getId());
                     //预留补充ai生成的邮件内容模板
 //                    marketingCampaignSopDetailEntity.setEmailContent();

+ 37 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketEmailsCustomerVO.java

@@ -0,0 +1,37 @@
+package com.storlead.trade.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "MarketEmailsCustomerVO", description = "营销邮件客户信息VO")
+public class MarketEmailsCustomerVO {
+
+    @ApiModelProperty(value = "客户分析结果id")
+    private Long analysisResultId;
+
+    @ApiModelProperty(value = "客户id")
+    private Long customerId;
+
+    @ApiModelProperty(value = "客户名称")
+    private String customerName;
+
+    @ApiModelProperty(value = "客户所在国家")
+    private String country;
+
+    @ApiModelProperty(value = "客户所属大洲")
+    private String continent;
+
+    @ApiModelProperty(value = "客户等级")
+    private String customerLevel;
+
+    @ApiModelProperty(value = "邮箱(主联络人)")
+    private String email;
+
+    @ApiModelProperty(value = "潜力分")
+    private Double potentialScore;
+
+    @ApiModelProperty(value = "置信度")
+    private Double confidence;
+}

+ 33 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/ScheduleStageVO.java

@@ -0,0 +1,33 @@
+package com.storlead.trade.vo;
+
+import cn.hutool.core.date.DateTime;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class ScheduleStageVO {
+
+    @ApiModelProperty(value = "阶段序号")
+    private Integer sequence;
+
+    @ApiModelProperty(value = "阶段名称")
+    @JSONField(name = "stage_name")
+    private String stageName;
+
+    @ApiModelProperty(value = "当地时间")
+    @JSONField(name = "local_datetime")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    private DateTime localDatetime;
+
+    @ApiModelProperty(value = "北京时间")
+    @JSONField(name = "beijing_datetime")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    private DateTime beijingDatetime;
+
+    @ApiModelProperty(value = "星期几")
+    private String weekday;
+}

+ 35 - 0
java/storlead-sasa/storlead-trade/src/main/resources/mapper/MarketEmailsMapper.xml

@@ -0,0 +1,35 @@
+<?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.storlead.trade.mapper.MarketEmailsMapper">
+
+    <!-- 查询营销邮件关联客户的分析结果信息 -->
+    <select id="selectCustomerInfoByCampaign" resultType="com.storlead.trade.vo.MarketEmailsCustomerVO">
+        SELECT
+            car.id                        AS analysisResultId,
+            c.id                          AS customerId,
+            c.customer_name               AS customerName,
+            c.country                     AS country,
+            c.continent                   AS continent,
+            car.customer_level            AS customerLevel,
+            l.email                       AS email,
+            car.potential_score           AS potentialScore,
+            car.confidence                AS confidence
+        FROM market_emails me
+        LEFT JOIN customer_base cb ON me.customer_analysis_id = cb.customer_analysis_id
+                                   AND me.marketing_campaign_id = cb.marketing_campaign_id
+        LEFT JOIN customer c ON cb.customer_analysis_id = c.id
+        LEFT JOIN customer_analysis_result car ON c.id = car.customer_id
+        LEFT JOIN liaison l ON c.id = l.customer_id AND l.has_primary_lialison = 1
+        WHERE me.marketing_campaign_id = #{dto.marketingCampaignId}
+          AND me.is_delete = 0
+          AND c.is_delete = 0
+        <if test="dto.customerAnalysisId != null">
+            AND me.customer_analysis_id = #{dto.customerAnalysisId}
+        </if>
+        <if test="dto.status != null">
+            AND me.status = #{dto.status}
+        </if>
+        ORDER BY me.create_time DESC
+    </select>
+
+</mapper>