Przeglądaj źródła

Merge remote-tracking branch 'origin/master'

18118724055@163.com 5 dni temu
rodzic
commit
51743596db

+ 48 - 1
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/CustomerTradeServiceImpl.java

@@ -15,7 +15,9 @@ import com.storlead.trade.entity.CustomerCompanyEntity;
 import com.storlead.trade.entity.CustomerEntity;
 import com.storlead.trade.entity.CustomerAnalysisResultEntity;
 import com.storlead.trade.entity.LiaisonEntity;
+import com.storlead.trade.entity.MarketEmailsEntity;
 import com.storlead.trade.mapper.CustomerEntityMapper;
+import com.storlead.trade.mapper.MarketEmailsMapper;
 import com.storlead.trade.service.CustomerCompanyTradeService;
 import com.storlead.trade.service.CustomerTradeService;
 import com.storlead.trade.service.CustomerAnalysisResultEntityService;
@@ -30,7 +32,11 @@ import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -51,16 +57,24 @@ public class CustomerTradeServiceImpl
     private final LiaisonTradeService liaisonTradeService;
     private final CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
     private final SysDictService sysDictService;
+    /**
+     * 直接注入 Mapper 而非 MarketEmailsService:MarketEmailsServiceImpl 依赖 CustomerTradeService,
+     * 而本类是构造器注入,再引 MarketEmailsService 会形成构造器循环依赖,启动时抛
+     * BeanCurrentlyInCreationException。Mapper 已由 TradeBaseMapper 统一路由到 trade 库。
+     */
+    private final MarketEmailsMapper marketEmailsMapper;
 
     public CustomerTradeServiceImpl(
             CustomerCompanyTradeService customerCompanyTradeService,
             LiaisonTradeService liaisonTradeService,
             CustomerAnalysisResultEntityService customerAnalysisResultEntityService,
-            SysDictService sysDictService) {
+            SysDictService sysDictService,
+            MarketEmailsMapper marketEmailsMapper) {
         this.customerCompanyTradeService = customerCompanyTradeService;
         this.liaisonTradeService = liaisonTradeService;
         this.customerAnalysisResultEntityService = customerAnalysisResultEntityService;
         this.sysDictService = sysDictService;
+        this.marketEmailsMapper = marketEmailsMapper;
     }
 
     @Override
@@ -268,9 +282,42 @@ public class CustomerTradeServiceImpl
                         .eq(CustomerAnalysisResultEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
                         .eq(CustomerAnalysisResultEntity::getCustomerId, customerId)
                         .last("LIMIT 1")));
+
+        // 互动记录:该客户全部已发送成功邮件(按发送时间倒序,首条即最后一次联系)
+        List<MarketEmailsEntity> sentEmailList = findSentEmailList(customerId);
+        vo.setInteractionRecordList(sentEmailList);
+
+        // 最后联系时间 / 天数:取互动记录首条,省掉一次查询
+        Date lastContactTime = sentEmailList.isEmpty() ? null : sentEmailList.get(0).getExpectSendTime();
+        vo.setLastContactTime(lastContactTime);
+        vo.setLastContactDays(calcDaysSince(lastContactTime));
         return vo;
     }
 
+    /**
+     * 互动记录:该客户全部「发送成功」邮件(market_emails.status = 1),按预计发送时间倒序;
+     * 无记录时返回空数组(不返回 null)。
+     */
+    private List<MarketEmailsEntity> findSentEmailList(Long customerId) {
+        return marketEmailsMapper.selectList(
+                new LambdaQueryWrapper<MarketEmailsEntity>()
+                        .eq(MarketEmailsEntity::getCustomerId, customerId)
+                        .eq(MarketEmailsEntity::getStatus, 1)
+                        .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
+                        .orderByDesc(MarketEmailsEntity::getExpectSendTime));
+    }
+
+    /**
+     * 按自然日计算 date 距今的天数(当天为 0);date 为 null 时返回 null。
+     */
+    private Integer calcDaysSince(Date date) {
+        if (date == null) {
+            return null;
+        }
+        LocalDate day = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        return (int) ChronoUnit.DAYS.between(day, LocalDate.now());
+    }
+
     @Override
     @Transactional(rollbackFor = Throwable.class)
     public Result<Object> add(CustomerDTO dto) {

+ 27 - 1
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketEmailsServiceImpl.java

@@ -1167,6 +1167,7 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         queryStopWrapper.in(MarketEmailsEntity::getMarketingCampaignId, marketingCampaignIdListWithReplyStop);
         queryStopWrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
         queryStopWrapper.eq(MarketEmailsEntity::getHasReply, 1);
+        queryStopWrapper.eq(MarketEmailsEntity::getHasReader, 1);
         List<MarketEmailsEntity> marketEmailsStopList = this.list(queryStopWrapper);
         List<Long> customerIdList = new ArrayList<>();
         if (!CollectionUtils.isEmpty(marketEmailsStopList)){
@@ -1261,7 +1262,32 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         marketingCampaignQueryWrapper.eq(MarketingCampaignEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
         marketingCampaignQueryWrapper.eq(MarketingCampaignEntity::getStatus, 1);
         marketingCampaignQueryWrapper.eq(MarketingCampaignEntity::getFollowUpRulesNotOpened, 1);
-        List<MarketingCampaignEntity> marketingCampaignList = marketingCampaignService.list(marketingCampaignQueryWrapper);
+        List<MarketingCampaignEntity> marketingCampaignFirstList = marketingCampaignService.list(marketingCampaignQueryWrapper);
+        if (CollectionUtils.isEmpty(marketingCampaignFirstList)){
+            return;
+        }
+        List<MarketingCampaignEntity> marketingCampaignList = new ArrayList<>();
+        //提取未开启客户回复后停止自动化的营销活动
+        List<MarketingCampaignEntity> marketingCampaignListA = marketingCampaignFirstList.stream().filter(
+                campaign -> campaign.getFollowUpRulesReplied() == null || campaign.getFollowUpRulesReplied() == 0
+        ).collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(marketingCampaignListA)){
+            marketingCampaignList.addAll(marketingCampaignListA);
+        }
+        List<MarketingCampaignEntity> marketingCampaignListB = marketingCampaignFirstList.stream().filter(
+                campaign -> campaign.getFollowUpRulesReplied() != null && campaign.getFollowUpRulesReplied() == 1
+        ).collect(Collectors.toList());
+        //排除有客户回复的营销活动
+        marketingCampaignListB = marketingCampaignListB.stream().filter(campaign -> {
+            LambdaQueryWrapper<MarketEmailsEntity> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+            queryWrapper.eq(MarketEmailsEntity::getMarketingCampaignId, campaign.getId());
+            queryWrapper.eq(MarketEmailsEntity::getHasReply, 1);
+            return this.count(queryWrapper) == 0;
+        }).collect(Collectors.toList());
+        if (!CollectionUtils.isEmpty(marketingCampaignListB)){
+            marketingCampaignList.addAll(marketingCampaignListB);
+        }
         if (CollectionUtils.isEmpty(marketingCampaignList)){
             return;
         }

+ 1 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignServiceImpl.java

@@ -263,6 +263,7 @@ public class MarketingCampaignServiceImpl
         Map<Long, List<MarketEmailsEntity>> emailsBySopDetail = new HashMap<>();
         List<MarketEmailsEntity> campaignEmails = marketEmailsService.lambdaQuery()
                 .eq(MarketEmailsEntity::getMarketingCampaignId, id)
+                .eq(MarketEmailsEntity::getHasFirst, 1)
                 .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
                 .list();
         for (MarketEmailsEntity email : campaignEmails) {

+ 26 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/CustomerVO.java

@@ -4,10 +4,18 @@ import com.storlead.trade.entity.CustomerCompanyEntity;
 import com.storlead.trade.entity.CustomerEntity;
 import com.storlead.trade.entity.CustomerAnalysisResultEntity;
 import com.storlead.trade.entity.LiaisonEntity;
+import com.storlead.trade.entity.MarketEmailsEntity;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
 
 @Data
 @EqualsAndHashCode(callSuper = true)
@@ -22,4 +30,22 @@ public class CustomerVO extends CustomerEntity {
 
     @ApiModelProperty(value = "客户分析结果(一对一关联 customer_analysis_result)")
     private CustomerAnalysisResultEntity customerAnalysisResult;
+
+    @ApiModelProperty(value = "累计订单数(暂无数据源,默认 0)")
+    private Integer totalOrderCount = 0;
+
+    @ApiModelProperty(value = "累计订单金额(暂无数据源,默认 0)")
+    private BigDecimal totalOrderAmount = BigDecimal.ZERO;
+
+    @ApiModelProperty(value = "最后联系时间(最后一次发送成功邮件的预计发送时间;从未发送过成功邮件为 null)")
+    @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")
+    private Date lastContactTime;
+
+    @ApiModelProperty(value = "最后联系天数(距最后一次发送成功邮件的天数;从未发送过成功邮件为 null)")
+    private Integer lastContactDays;
+
+    @ApiModelProperty(value = "互动记录列表(该客户全部已发送成功的邮件,按发送时间倒序;无记录为空数组)")
+    private List<MarketEmailsEntity> interactionRecordList;
 }

+ 4 - 2
ui/smarttrade-platform/src/components/layout/TopBar.vue

@@ -632,12 +632,13 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import AddMarketing from '../addMarketing.vue'
 import { getNoReadCount, getMessageList, updateRead, updateReadAll, listPageBuUserSet } from '@/api/layout'
 import moment from 'moment'
 
 const route = useRoute()
+const router = useRouter()
 const showQuickActions = ref(false)
 const showNotifications = ref(false)
 const showHelp = ref(false)
@@ -855,7 +856,8 @@ const getIconColor = (type) => {
 
 const handleNotificationClick = (notification) => {
   setRead(notification)
-  selectedNotification.value = notification
+  // selectedNotification.value = notification
+  window.open(notification.callbackUrl, '_blank')
   // if(notification.callbackUrl){
   //     window.open(item.callbackUrl, '_blank')
   // }