|
|
@@ -13,12 +13,14 @@ import com.storlead.trade.entity.*;
|
|
|
import com.storlead.trade.mapper.MarketingCampaignEntityMapper;
|
|
|
import com.storlead.trade.service.*;
|
|
|
import com.storlead.trade.vo.CampaignStatsVO;
|
|
|
+import com.storlead.trade.vo.MarketEmailsCustomerVO;
|
|
|
import com.storlead.trade.vo.MarketingCampaignDetailVO;
|
|
|
import com.storlead.user.service.IUserService;
|
|
|
import com.storlead.trade.vo.MarketingCampaignVO;
|
|
|
import com.storlead.framework.common.constant.CommonConstant;
|
|
|
import com.storlead.framework.common.result.Result;
|
|
|
import com.storlead.trade.service.impl.support.TradeDataSourceServiceImpl;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -34,7 +36,10 @@ import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.stream.Collectors;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
|
+@Slf4j
|
|
|
@Service
|
|
|
@DS(DSConstants.DATASOURCE_TRADE)
|
|
|
public class MarketingCampaignServiceImpl
|
|
|
@@ -230,10 +235,6 @@ public class MarketingCampaignServiceImpl
|
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
|
// supervisorList 转为逗号分隔字符串
|
|
|
marketingCampaignEntity.setSupervisor(joinSupervisorList(marketingCampaignDTO));
|
|
|
- // 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
|
- if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
|
- marketingCampaignEntity.setEmailSentTime(null);
|
|
|
- }
|
|
|
boolean isOk = this.save(marketingCampaignEntity);
|
|
|
if (!isOk) {
|
|
|
|
|
|
@@ -296,10 +297,6 @@ public class MarketingCampaignServiceImpl
|
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
|
// supervisorList 转为逗号分隔字符串
|
|
|
marketingCampaignEntity.setSupervisor(joinSupervisorList(marketingCampaignDTO));
|
|
|
- // 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
|
- if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
|
- marketingCampaignEntity.setEmailSentTime(null);
|
|
|
- }
|
|
|
boolean isOk = this.updateById(marketingCampaignEntity);
|
|
|
if (!isOk) {
|
|
|
return Result.error("修改失败");
|
|
|
@@ -376,6 +373,111 @@ public class MarketingCampaignServiceImpl
|
|
|
return Result.ok();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 生命周期状态常量(对应 marketing_campaign.status 注释:0未开始 1运行中 2暂停 3完成)
|
|
|
+ */
|
|
|
+ private static final int STATUS_NOT_STARTED = 0;
|
|
|
+ private static final int STATUS_RUNNING = 1;
|
|
|
+ private static final int STATUS_PAUSED = 2;
|
|
|
+ private static final int STATUS_COMPLETED = 3;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按开始/结束日期同步所有营销活动的生命周期状态。
|
|
|
+ * <p>每天 8 点由 {@code MarketStartOrEndTask} 调度调用。</p>
|
|
|
+ * 规则:
|
|
|
+ * <ul>
|
|
|
+ * <li>今天 < 开始日期 → 未开始(0)</li>
|
|
|
+ * <li>开始日期 ≤ 今天 ≤ 结束日期 → 运行中(1)</li>
|
|
|
+ * <li>今天 > 结束日期 → 完成(3)</li>
|
|
|
+ * </ul>
|
|
|
+ * 手动暂停(2)的活动不自动覆盖,尊重人工操作;开始/结束日期任一缺失则跳过。
|
|
|
+ *
|
|
|
+ * @return 被更新的活动数量
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void syncStartEndStatus() {
|
|
|
+ LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai"));
|
|
|
+ List<MarketingCampaignEntity> all = this.lambdaQuery()
|
|
|
+ .eq(MarketingCampaignEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
+ .list();
|
|
|
+ if (CollectionUtils.isEmpty(all)) {
|
|
|
+ log.info("营销活动开始/结束状态同步:无未删除活动,跳过");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 按目标状态分组,只收集需要变更的 id(跳过暂停 / 日期缺失 / 状态已一致)
|
|
|
+ // 后续每种状态仅执行一次批量 UPDATE,而非逐条 UPDATE
|
|
|
+ Map<Integer, List<Long>> idsByStatus = new HashMap<>(4);
|
|
|
+ // 变更的实体列表(status 已回填为目标状态,用于区分改为开始/结束)
|
|
|
+ List<MarketingCampaignEntity> changed = new ArrayList<>();
|
|
|
+ for (MarketingCampaignEntity c : all) {
|
|
|
+ Integer expected = computeStartEndStatus(c, today);
|
|
|
+ if (expected == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Integer current = c.getStatus();
|
|
|
+ // 尊重手动暂停,不自动覆盖
|
|
|
+ if (current != null && current == STATUS_PAUSED) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (expected.equals(current)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ idsByStatus.computeIfAbsent(expected, k -> new ArrayList<>()).add(c.getId());
|
|
|
+ c.setStatus(expected);
|
|
|
+ changed.add(c);
|
|
|
+ }
|
|
|
+ // 每种目标状态执行一次批量 UPDATE
|
|
|
+ for (Map.Entry<Integer, List<Long>> entry : idsByStatus.entrySet()) {
|
|
|
+ List<Long> ids = entry.getValue();
|
|
|
+ if (CollectionUtils.isEmpty(ids)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ this.lambdaUpdate()
|
|
|
+ .in(MarketingCampaignEntity::getId, ids)
|
|
|
+ .set(MarketingCampaignEntity::getStatus, entry.getKey())
|
|
|
+ .update();
|
|
|
+ }
|
|
|
+ // 按目标状态拆分列表并打印:哪些改为开始、哪些改为结束
|
|
|
+ List<Long> started = changed.stream()
|
|
|
+ .filter(e -> STATUS_RUNNING == e.getStatus())
|
|
|
+ .map(MarketingCampaignEntity::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<Long> ended = changed.stream()
|
|
|
+ .filter(e -> STATUS_COMPLETED == e.getStatus())
|
|
|
+ .map(MarketingCampaignEntity::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ log.info("营销活动开始/结束状态同步完成:改为开始(运行中)={}, 改为结束(完成)={}", started, ended);
|
|
|
+ MarketEmailsCustomerVO startVO = new MarketEmailsCustomerVO();
|
|
|
+ startVO.setMarketingCampaignIdList(started);
|
|
|
+ //修改状态为待发送
|
|
|
+ startVO.setStatus(-1);
|
|
|
+ marketEmailsService.updateCustomerEmailStatus(startVO);
|
|
|
+
|
|
|
+ MarketEmailsCustomerVO endVO = new MarketEmailsCustomerVO();
|
|
|
+ endVO.setMarketingCampaignIdList(ended);
|
|
|
+ //修改状态为待发送
|
|
|
+ endVO.setStatus(-3);
|
|
|
+ marketEmailsService.updateCustomerEmailStatus(endVO);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据开始/结束日期与今天计算期望的状态值;开始/结束日期任一缺失返回 null。
|
|
|
+ */
|
|
|
+ private Integer computeStartEndStatus(MarketingCampaignEntity c, LocalDate today) {
|
|
|
+ LocalDate begin = c.getBeginData();
|
|
|
+ LocalDate end = c.getEndDate();
|
|
|
+ if (begin == null || end == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (today.isBefore(begin)) {
|
|
|
+ return STATUS_NOT_STARTED;
|
|
|
+ }
|
|
|
+ if (!today.isAfter(end)) {
|
|
|
+ return STATUS_RUNNING;
|
|
|
+ }
|
|
|
+ return STATUS_COMPLETED;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public Result<Object> getDashboardStats() {
|
|
|
// 计算本月起止时间
|