|
@@ -14,6 +14,7 @@ import com.storlead.trade.mapper.MarketingCampaignEntityMapper;
|
|
|
import com.storlead.trade.service.*;
|
|
import com.storlead.trade.service.*;
|
|
|
import com.storlead.trade.vo.CampaignStatsVO;
|
|
import com.storlead.trade.vo.CampaignStatsVO;
|
|
|
import com.storlead.trade.vo.MarketingCampaignDetailVO;
|
|
import com.storlead.trade.vo.MarketingCampaignDetailVO;
|
|
|
|
|
+import com.storlead.user.service.IUserService;
|
|
|
import com.storlead.trade.vo.MarketingCampaignVO;
|
|
import com.storlead.trade.vo.MarketingCampaignVO;
|
|
|
import com.storlead.framework.common.constant.CommonConstant;
|
|
import com.storlead.framework.common.constant.CommonConstant;
|
|
|
import com.storlead.framework.common.result.Result;
|
|
import com.storlead.framework.common.result.Result;
|
|
@@ -27,7 +28,11 @@ import org.springframework.util.ObjectUtils;
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
|
|
|
+import java.util.Calendar;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
@Service
|
|
@Service
|
|
@@ -50,6 +55,8 @@ public class MarketingCampaignServiceImpl
|
|
|
private MarketEmailsService marketEmailsService;
|
|
private MarketEmailsService marketEmailsService;
|
|
|
@Resource
|
|
@Resource
|
|
|
private CustomerTradeService customerTradeService;
|
|
private CustomerTradeService customerTradeService;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private IUserService userService;
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public Result<Object> getList(MarketingCampaignDTO marketingCampaignDTO) {
|
|
public Result<Object> getList(MarketingCampaignDTO marketingCampaignDTO) {
|
|
@@ -166,6 +173,13 @@ public class MarketingCampaignServiceImpl
|
|
|
detailVO.setSopDetailList(relationList);
|
|
detailVO.setSopDetailList(relationList);
|
|
|
detailVO.setCustomerList(customerEntityList);
|
|
detailVO.setCustomerList(customerEntityList);
|
|
|
|
|
|
|
|
|
|
+ // 3.1 supervisor 联动:解析为id列表 + 查user表
|
|
|
|
|
+ List<Long> supervisorIdList = parseSupervisorList(campaign.getSupervisor());
|
|
|
|
|
+ detailVO.setSupervisorList(supervisorIdList);
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(supervisorIdList)) {
|
|
|
|
|
+ detailVO.setSupervisorUserList(userService.getUserListByIds(supervisorIdList));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 4. 计算达成率统计(从 market_emails 表取实际数据)
|
|
// 4. 计算达成率统计(从 market_emails 表取实际数据)
|
|
|
CampaignStatsVO stats = new CampaignStatsVO();
|
|
CampaignStatsVO stats = new CampaignStatsVO();
|
|
|
Integer sendVolume = campaign.getSendVolume();
|
|
Integer sendVolume = campaign.getSendVolume();
|
|
@@ -214,6 +228,8 @@ public class MarketingCampaignServiceImpl
|
|
|
public Result<Object> addMarket(MarketingCampaignDTO marketingCampaignDTO) {
|
|
public Result<Object> addMarket(MarketingCampaignDTO marketingCampaignDTO) {
|
|
|
MarketingCampaignEntity marketingCampaignEntity = new MarketingCampaignEntity();
|
|
MarketingCampaignEntity marketingCampaignEntity = new MarketingCampaignEntity();
|
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
|
|
|
+ // supervisorList 转为逗号分隔字符串
|
|
|
|
|
+ marketingCampaignEntity.setSupervisor(joinSupervisorList(marketingCampaignDTO));
|
|
|
// 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
// 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
|
if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
|
marketingCampaignEntity.setEmailSentTime(null);
|
|
marketingCampaignEntity.setEmailSentTime(null);
|
|
@@ -278,6 +294,8 @@ public class MarketingCampaignServiceImpl
|
|
|
MarketingCampaignEntity marketingCampaignOld = this.getById(marketingCampaignDTO.getId());
|
|
MarketingCampaignEntity marketingCampaignOld = this.getById(marketingCampaignDTO.getId());
|
|
|
MarketingCampaignEntity marketingCampaignEntity = new MarketingCampaignEntity();
|
|
MarketingCampaignEntity marketingCampaignEntity = new MarketingCampaignEntity();
|
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
BeanUtils.copyProperties(marketingCampaignDTO, marketingCampaignEntity);
|
|
|
|
|
+ // supervisorList 转为逗号分隔字符串
|
|
|
|
|
+ marketingCampaignEntity.setSupervisor(joinSupervisorList(marketingCampaignDTO));
|
|
|
// 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
// 过滤 AI 最优时间占位值,避免写入 DATETIME 列报错
|
|
|
if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
if ("optimal".equalsIgnoreCase(marketingCampaignEntity.getEmailSentTime())) {
|
|
|
marketingCampaignEntity.setEmailSentTime(null);
|
|
marketingCampaignEntity.setEmailSentTime(null);
|
|
@@ -357,4 +375,126 @@ public class MarketingCampaignServiceImpl
|
|
|
this.updateById(entity);
|
|
this.updateById(entity);
|
|
|
return Result.ok();
|
|
return Result.ok();
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Result<Object> getDashboardStats() {
|
|
|
|
|
+ // 计算本月起止时间
|
|
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
|
|
+ cal.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
|
|
+ cal.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
|
|
+ cal.set(Calendar.MINUTE, 0);
|
|
|
|
|
+ cal.set(Calendar.SECOND, 0);
|
|
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
|
|
+ Date monthStart = cal.getTime();
|
|
|
|
|
+
|
|
|
|
|
+ // 本月已发送数(market_emails.status=1)
|
|
|
|
|
+ Integer sentCount = marketEmailsService.lambdaQuery()
|
|
|
|
|
+ .eq(MarketEmailsEntity::getStatus, 1)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
|
|
+ .ge(MarketEmailsEntity::getCreateTime, monthStart)
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ // 本月打开数
|
|
|
|
|
+ Integer openCount = marketEmailsService.lambdaQuery()
|
|
|
|
|
+ .eq(MarketEmailsEntity::getStatus, 1)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
|
|
+ .ge(MarketEmailsEntity::getCreateTime, monthStart)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getHasReader, 1)
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ // 本月回复数
|
|
|
|
|
+ Integer replyCount = marketEmailsService.lambdaQuery()
|
|
|
|
|
+ .eq(MarketEmailsEntity::getStatus, 1)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
|
|
+ .ge(MarketEmailsEntity::getCreateTime, monthStart)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getHasReply, 1)
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ int sent = sentCount == null ? 0 : sentCount.intValue();
|
|
|
|
|
+ int open = openCount == null ? 0 : openCount.intValue();
|
|
|
|
|
+ int reply = replyCount == null ? 0 : replyCount.intValue();
|
|
|
|
|
+
|
|
|
|
|
+ int avgOpenRate = sent > 0 ? Math.round(open * 100f / sent) : 0;
|
|
|
|
|
+ int replyRate = sent > 0 ? Math.round(reply * 100f / sent) : 0;
|
|
|
|
|
+ // TODO: market_emails 表未加 has_clicked 字段,点击率暂返回 0,添加后替换为 hasClick=1 统计
|
|
|
|
|
+ int clickRate = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 受保护客户数(distinct customer_id where has_protect=1)
|
|
|
|
|
+ List<MarketEmailsEntity> protectedList = marketEmailsService.lambdaQuery()
|
|
|
|
|
+ .eq(MarketEmailsEntity::getHasProtect, 1)
|
|
|
|
|
+ .eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
|
|
+ .isNotNull(MarketEmailsEntity::getCustomerId)
|
|
|
|
|
+ .select(MarketEmailsEntity::getCustomerId)
|
|
|
|
|
+ .list();
|
|
|
|
|
+ long protectedCustomers = protectedList.stream()
|
|
|
|
|
+ .map(MarketEmailsEntity::getCustomerId)
|
|
|
|
|
+ .filter(java.util.Objects::nonNull)
|
|
|
|
|
+ .distinct()
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ // 待审核营销活动数(status=0 且未软删)
|
|
|
|
|
+ Integer pendingCount = this.lambdaQuery()
|
|
|
|
|
+ .eq(MarketingCampaignEntity::getStatus, 0)
|
|
|
|
|
+ .eq(MarketingCampaignEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
|
|
+ .count();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> stats = new HashMap<>();
|
|
|
|
|
+ stats.put("monthlySent", formatCount(sent));
|
|
|
|
|
+ stats.put("avgOpenRate", avgOpenRate + "%");
|
|
|
|
|
+ // TODO: market_emails 表未加 has_clicked 字段,点击率暂返回 0%,添加后替换为 hasClick=1 统计
|
|
|
|
|
+ stats.put("clickRate", clickRate + "%");
|
|
|
|
|
+ stats.put("replyRate", replyRate + "%");
|
|
|
|
|
+ stats.put("protectedCustomers", String.valueOf((int) protectedCustomers));
|
|
|
|
|
+ stats.put("pendingReview", String.valueOf(pendingCount == null ? 0 : pendingCount.intValue()));
|
|
|
|
|
+
|
|
|
|
|
+ return Result.result(stats);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化计数显示:<1000 原样, 1000-9999 转为 "X.XK", >=10000 转为 "X.XW"
|
|
|
|
|
+ */
|
|
|
|
|
+ private String formatCount(int n) {
|
|
|
|
|
+ if (n < 1000) {
|
|
|
|
|
+ return String.valueOf(n);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (n < 10000) {
|
|
|
|
|
+ return String.format("%.1fK", n / 1000.0);
|
|
|
|
|
+ }
|
|
|
|
|
+ return String.format("%.1fW", n / 10000.0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * supervisorList 转逗号分隔字符串。如未传 supervisorList 则取原 supervisor 字符串。
|
|
|
|
|
+ */
|
|
|
|
|
+ private String joinSupervisorList(MarketingCampaignDTO dto) {
|
|
|
|
|
+ if (dto == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!CollectionUtils.isEmpty(dto.getSupervisorList())) {
|
|
|
|
|
+ return dto.getSupervisorList().stream()
|
|
|
|
|
+ .filter(java.util.Objects::nonNull)
|
|
|
|
|
+ .map(String::valueOf)
|
|
|
|
|
+ .collect(Collectors.joining(","));
|
|
|
|
|
+ }
|
|
|
|
|
+ return dto.getSupervisor();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析逗号分隔的 supervisor 字符串为 id 列表。
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Long> parseSupervisorList(String supervisor) {
|
|
|
|
|
+ if (supervisor == null || supervisor.trim().isEmpty()) {
|
|
|
|
|
+ return new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Long> ids = new ArrayList<>();
|
|
|
|
|
+ for (String s : supervisor.split(",")) {
|
|
|
|
|
+ if (!s.trim().isEmpty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ids.add(Long.parseLong(s.trim()));
|
|
|
|
|
+ } catch (NumberFormatException ignore) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ids;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|