YPZ преди 2 дни
родител
ревизия
44a650da96

+ 13 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketEmailsController.java

@@ -3,6 +3,7 @@ package com.storlead.trade.controller;
 import com.storlead.trade.dto.MarketEmailsDTO;
 import com.storlead.trade.service.MarketEmailsService;
 import com.storlead.framework.common.result.Result;
+import com.storlead.trade.vo.MarketEmailsManagementVO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -50,6 +51,12 @@ public class MarketEmailsController {
         return marketEmailsService.suspend(marketEmailsDTO);
     }
 
+    @PostMapping("/cancel")
+    @ApiOperation("营销: 取消发送邮件(按 id 或 idList,status 置 -2)")
+    public Result<Object> cancel(@RequestBody MarketEmailsDTO marketEmailsDTO) {
+        return marketEmailsService.cancel(marketEmailsDTO);
+    }
+
     @PostMapping("/add")
     @ApiOperation("营销: 营销邮件添加")
     public Result<?> add(@RequestBody MarketEmailsDTO marketEmailsDTO) {
@@ -88,4 +95,10 @@ public class MarketEmailsController {
         }
         return Result.result(marketEmailsService.getById(id));
     }
+
+    @GetMapping("/management-detail")
+    @ApiOperation("营销: 邮件管理弹窗(按 marketingCampaignId 查询所有邮件卡片)")
+    public Result<Object> managementDetail(Long id) {
+        return marketEmailsService.getManagementDetail(id);
+    }
 }

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

@@ -4,6 +4,7 @@ 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.trade.vo.MarketEmailsManagementVO;
 import com.storlead.framework.common.result.Result;
 import com.storlead.framework.mybatis.service.MyBaseService;
 import org.springframework.scheduling.annotation.Async;
@@ -34,6 +35,19 @@ public interface MarketEmailsService extends MyBaseService<MarketEmailsEntity> {
      */
     Result<Object> suspend(MarketEmailsDTO marketEmailsDTO);
 
+    /**
+     * 取消发送邮件(按 id 或 idList,status 置 -2;软取消,不影响 isDelete)
+     */
+    Result<Object> cancel(MarketEmailsDTO marketEmailsDTO);
+
+    /**
+     * 邮件管理弹窗(按营销活动 id 查询,拼装一个营销活动下所有邮件卡片)
+     *
+     * @param marketingCampaignId 营销活动 id(MarketingCampaignEntity.id)
+     * @return List<VO>,每个元素一张卡片(含 customerName / stageName / recipient / expectSendTime / status)
+     */
+    Result<Object> getManagementDetail(Long marketingCampaignId);
+
     @Async
     void addSendTime(MarketingCampaignEntity marketingCampaignEntity);
 

+ 106 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketEmailsServiceImpl.java

@@ -21,6 +21,7 @@ import com.storlead.trade.entity.*;
 import com.storlead.trade.mapper.MarketEmailsMapper;
 import com.storlead.trade.service.*;
 import com.storlead.trade.vo.MarketEmailsCustomerVO;
+import com.storlead.trade.vo.MarketEmailsManagementVO;
 import com.storlead.trade.vo.MarketEmailsOpenVO;
 import com.storlead.trade.vo.ScheduleStageVO;
 import org.slf4j.Logger;
@@ -64,6 +65,10 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
     @Resource
     private WorkflowsService workflowsService;
 
+    @Resource
+    @org.springframework.context.annotation.Lazy
+    private MarketingCampaignService marketingCampaignService;
+
 
     @Override
     public Result<Object> getList(MarketEmailsDTO dto) {
@@ -315,6 +320,107 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         return Result.ok();
     }
 
+    /**
+     * 取消发送邮件:status 置 -2(软取消,不删数据,isDelete 保持 0)
+     * 支持单条(dto.id)或批量(dto.idList)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<Object> cancel(MarketEmailsDTO dto) {
+        if (dto == null) {
+            return Result.error("请求体不能为空");
+        }
+        List<Long> ids = new ArrayList<>();
+        if (!ObjectUtils.isEmpty(dto.getId())) {
+            ids.add(dto.getId());
+        }
+        if (!CollectionUtils.isEmpty(dto.getIdList())) {
+            ids.addAll(dto.getIdList());
+        }
+        if (ids.isEmpty()) {
+            return Result.error("id 或 idList 不能为空");
+        }
+
+        LambdaUpdateWrapper<MarketEmailsEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.in(MarketEmailsEntity::getId, ids);
+        wrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        // status = -2 表示取消发送
+        wrapper.set(MarketEmailsEntity::getStatus, -2);
+        boolean ok = this.update(wrapper);
+        if (!ok) {
+            return Result.error("取消发送失败");
+        }
+        return Result.ok();
+    }
+
+
+    @Override
+    public Result<Object> getManagementDetail(Long marketingCampaignId) {
+        if (ObjectUtils.isEmpty(marketingCampaignId)) {
+            return Result.error("marketingCampaignId不能为空");
+        }
+
+        // 营销活动名(用于拼接在卡片标题位置或返回顶部)
+        String campaignName = null;
+        MarketingCampaignEntity campaign = marketingCampaignService.getById(marketingCampaignId);
+        if (campaign != null) {
+            campaignName = campaign.getName();
+        }
+
+        // 查该营销活动下所有未软删的邮件
+        LambdaQueryWrapper<MarketEmailsEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MarketEmailsEntity::getMarketingCampaignId, marketingCampaignId);
+        wrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.orderByAsc(MarketEmailsEntity::getExpectSendTime);
+        List<MarketEmailsEntity> entityList = this.list(wrapper);
+        if (CollectionUtils.isEmpty(entityList)) {
+            return Result.ok(new ArrayList<MarketEmailsManagementVO>());
+        }
+
+        // 批量收 ID,减少 N+1
+        java.util.Set<Long> customerIdSet = new java.util.HashSet<>();
+        java.util.Set<Long> sopDetailIdSet = new java.util.HashSet<>();
+        for (MarketEmailsEntity e : entityList) {
+            if (e.getCustomerId() != null) customerIdSet.add(e.getCustomerId());
+            if (e.getMarketingCampaignSopDetailId() != null) sopDetailIdSet.add(e.getMarketingCampaignSopDetailId());
+        }
+
+        Map<Long, CustomerEntity> customerMap = customerIdSet.isEmpty()
+                ? Collections.emptyMap()
+                : customerTradeService.listByIds(new java.util.ArrayList<>(customerIdSet)).stream()
+                    .collect(Collectors.toMap(CustomerEntity::getId, c -> c, (a, c) -> a));
+        Map<Long, MarketingCampaignSopDetailEntity> sopDetailMap = sopDetailIdSet.isEmpty()
+                ? Collections.emptyMap()
+                : marketingCampaignSopDetailEntityService.listByIds(new java.util.ArrayList<>(sopDetailIdSet)).stream()
+                    .collect(Collectors.toMap(MarketingCampaignSopDetailEntity::getId, s -> s, (a, c) -> a));
+
+        // 组装 VO
+        List<MarketEmailsManagementVO> voList = new ArrayList<>(entityList.size());
+        for (MarketEmailsEntity entity : entityList) {
+            MarketEmailsManagementVO vo = new MarketEmailsManagementVO();
+            BeanUtils.copyProperties(entity, vo);
+            // expectSendTime 是 DateTime,VO 是 Date,手动 set(BeanUtils 类型不一致不复制)
+            if (entity.getExpectSendTime() != null) {
+                vo.setExpectSendTime(entity.getExpectSendTime());
+            }
+            vo.setCampaignName(campaignName);
+
+            CustomerEntity customer = customerMap.get(entity.getCustomerId());
+            if (customer != null) {
+                vo.setCustomerName(customer.getCustomerName());
+                vo.setCustomerContact(customer.getCollaborator());
+            }
+
+            MarketingCampaignSopDetailEntity sopDetail = sopDetailMap.get(entity.getMarketingCampaignSopDetailId());
+            if (sopDetail != null) {
+                vo.setStageName(sopDetail.getName());
+            }
+
+            voList.add(vo);
+        }
+        return Result.ok(voList);
+    }
+
 
     @Override
     public void addSendTime(MarketingCampaignEntity marketingCampaignEntity) {

+ 75 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketEmailsManagementVO.java

@@ -0,0 +1,75 @@
+package com.storlead.trade.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 邮件管理弹窗卡片 VO
+ * <p>
+ * 按 marketingCampaignId(MarketingCampaignEntity.id)查询,拼装一个营销活动下所有邮件卡片。
+ * </p>
+ *
+ * @author QClaw Generated
+ * @since 2026-08-27
+ */
+@Data
+@ApiModel(value = "MarketEmailsManagementVO", description = "邮件管理弹窗卡片 VO")
+public class MarketEmailsManagementVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "邮件主键 id")
+    private Long id;
+
+    @ApiModelProperty(value = "营销活动 id")
+    private Long marketingCampaignId;
+
+    @ApiModelProperty(value = "营销活动名(marketing_campaign.name)")
+    private String campaignName;
+
+    @ApiModelProperty(value = "客户 id")
+    private Long customerId;
+
+    @ApiModelProperty(value = "客户名称")
+    private String customerName;
+
+    @ApiModelProperty(value = "客户联系人称呼(如 admin)")
+    private String customerContact;
+
+    @ApiModelProperty(value = "收件人邮箱(market_emails.recipient)")
+    private String recipient;
+
+    @ApiModelProperty(value = "阶段名(首次介绍 / 报价邮件 等,来自 marketing_campaign_sop_detail.name)")
+    private String stageName;
+
+    @ApiModelProperty(value = "预计发送时间")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date expectSendTime;
+
+    @ApiModelProperty(value = "邮件状态(1 已发送 / 2 待发送 / 0 已取消)")
+    private Integer status;
+
+    @ApiModelProperty(value = "邮件内容")
+    private String content;
+
+    @ApiModelProperty(value = "是否已读(0 否 1 是)")
+    private Integer hasReader;
+
+    @ApiModelProperty(value = "是否已保护(0 否 1 是)")
+    private Integer hasProtect;
+
+    @ApiModelProperty(value = "是否已暂停(0 否 1 是)")
+    private Integer hasSuspend;
+
+    @ApiModelProperty(value = "首次打开时间")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date firstOpenTime;
+
+    @ApiModelProperty(value = "关联的营销活动 SOP 详情 id(market_emails.marketing_campaign_sop_detail_id)")
+    private Long marketingCampaignSopDetailId;
+}