|
|
@@ -26,248 +26,251 @@ public class WorkbenchServiceImpl implements IWorkbenchService {
|
|
|
private final CustomerSalesInfoMapper customerSalesInfoMapper;
|
|
|
private final SalesresultanalyzeMapper salesresultanalyzeMapper;
|
|
|
|
|
|
+ private static final long DAY_MS = 24 * 3600000L;
|
|
|
+
|
|
|
@Override
|
|
|
public WorkbenchStatVo getStat() {
|
|
|
WorkbenchStatVo vo = new WorkbenchStatVo();
|
|
|
- vo.setOpportunityStats(getOpportunityStats());
|
|
|
- vo.setSelectionStats(getSelectionStats());
|
|
|
+ vo.setCustomerStats(getCustomerStats());
|
|
|
+ vo.setFollowUpStats(getFollowUpStats());
|
|
|
+ vo.setOpportunityTotalStats(getOpportunityTotalStats());
|
|
|
+ vo.setOpportunityInFollowStats(getOpportunityInFollowStats());
|
|
|
+ vo.setSelectionTotalStats(getSelectionTotalStats());
|
|
|
+ vo.setSelectionInFollowStats(getSelectionInFollowStats());
|
|
|
vo.setOpportunityFunnel(getOpportunityFunnel());
|
|
|
vo.setCustomerFunnel(getCustomerFunnel());
|
|
|
vo.setVisitStats(getVisitStats());
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
- private List<WorkbenchStatVo.StatItem> getOpportunityFunnel() {
|
|
|
- // 1. 销售线索:iz_clue = 1
|
|
|
- Long clueCount = salesleadsMapper.selectCount(new LambdaQueryWrapper<Salesleads>()
|
|
|
- .eq(Salesleads::getIzClue, 1)
|
|
|
- .eq(Salesleads::getDelFlag, "0"));
|
|
|
-
|
|
|
- // 2. 商机:iz_clue = 0
|
|
|
- Long opportunityCount = salesleadsMapper.selectCount(new LambdaQueryWrapper<Salesleads>()
|
|
|
- .eq(Salesleads::getIzClue, 0)
|
|
|
- .eq(Salesleads::getDelFlag, "0"));
|
|
|
-
|
|
|
- // 3. 赢单:salesresultanalyze 中 dealResult = 1, dataType = 2 (商机)
|
|
|
- Long winCount = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
- .eq(SalesResultAnalyze::getDealResult, 1)
|
|
|
- .eq(SalesResultAnalyze::getDataType, 2)
|
|
|
- .eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+ // 趋势计算通用工具:限制在 [-1.0, 1.0] 之间 (即最高 100%)
|
|
|
+ private Double calculateTrend(long current, long previous) {
|
|
|
+ if (previous <= 0) return current > 0 ? 1.0 : 0.0;
|
|
|
+ double res = (double) (current - previous) / previous;
|
|
|
+ return Math.max(-1.0, Math.min(1.0, res));
|
|
|
+ }
|
|
|
|
|
|
- List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("销售线索", String.valueOf(clueCount), "个"));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("商机", String.valueOf(opportunityCount), "个"));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("赢单", String.valueOf(winCount), "个"));
|
|
|
- return stats;
|
|
|
+ private Double calculateTrend(BigDecimal current, BigDecimal previous) {
|
|
|
+ if (previous == null || previous.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ return (current != null && current.compareTo(BigDecimal.ZERO) > 0) ? 1.0 : 0.0;
|
|
|
+ }
|
|
|
+ double res = current.subtract(previous).divide(previous, 4, BigDecimal.ROUND_HALF_UP).doubleValue();
|
|
|
+ return Math.max(-1.0, Math.min(1.0, res));
|
|
|
}
|
|
|
|
|
|
- private List<WorkbenchStatVo.StatItem> getCustomerFunnel() {
|
|
|
- // 1. 正式客户:在 sales_info 表中 sales_person_id 和 service_staff_id 都不为空
|
|
|
- Long officialCount = customerSalesInfoMapper.selectCount(new LambdaQueryWrapper<CustomerSalesInfo>()
|
|
|
- .isNotNull(CustomerSalesInfo::getSalesPersonId)
|
|
|
- .isNotNull(CustomerSalesInfo::getServiceStaffId)
|
|
|
- .eq(CustomerSalesInfo::getDelFlag, "0"));
|
|
|
+ // 获取各维度的历史计数(7天前)
|
|
|
+ private long countBefore(Date date, String delFlag, boolean isOfficial) {
|
|
|
+ LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<CustomerInfo>()
|
|
|
+ .le(CustomerInfo::getCreateTime, date).eq(CustomerInfo::getDelFlag, delFlag);
|
|
|
+ if (isOfficial) {
|
|
|
+ wrapper.isNotNull(CustomerInfo::getSalesPersonId);
|
|
|
+ }
|
|
|
+ return customerInfoMapper.selectCount(wrapper);
|
|
|
+ }
|
|
|
|
|
|
- // 2. 公海客户:总客户数 - 正式客户数 (或者只要有一个为空即为公海)
|
|
|
- Long totalCount = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>()
|
|
|
- .eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+ // --- 1. 客户统计 ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getCustomerStats() {
|
|
|
+ Date now = new Date();
|
|
|
+ Date weekAgo = new Date(now.getTime() - 7 * DAY_MS);
|
|
|
|
|
|
- Long publicCount = Math.max(0L, totalCount - officialCount);
|
|
|
-
|
|
|
- // 3. 已成交:这里采用 Salesresultanalyze 中已成交的记录数作为“已成交”阶段的量度
|
|
|
- Long dealCount = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
- .eq(SalesResultAnalyze::getDealResult, 1)
|
|
|
- .eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+ // 精确获取本月初 00:00:00
|
|
|
+ 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();
|
|
|
+
|
|
|
+ // 当前值
|
|
|
+ long total = countBefore(now, "0", false);
|
|
|
+ long official = countBefore(now, "0", true);
|
|
|
+ long publicPool = total - official;
|
|
|
+ long newMonth = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>()
|
|
|
+ .ge(CustomerInfo::getCreateTime, monthStart).eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+ long dealMonth = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
+ .eq(SalesResultAnalyze::getDealResult, 1).ge(SalesResultAnalyze::getCreateTime, monthStart).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+
|
|
|
+ // 7天前值
|
|
|
+ long totalOld = countBefore(weekAgo, "0", false);
|
|
|
+ long offOld = countBefore(weekAgo, "0", true);
|
|
|
+
|
|
|
+ // 增量指标趋势:过去7天 vs 再往前7天
|
|
|
+ Date twoWeeksAgo = new Date(now.getTime() - 14 * DAY_MS);
|
|
|
+ long newThisWeek = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>().ge(CustomerInfo::getCreateTime, weekAgo).eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+ long newLastWeek = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>().ge(CustomerInfo::getCreateTime, twoWeeksAgo).lt(CustomerInfo::getCreateTime, weekAgo).eq(CustomerInfo::getDelFlag, "0"));
|
|
|
|
|
|
List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("公海客户数", String.valueOf(publicCount), "个"));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("正式客户", String.valueOf(officialCount), "个"));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("已成交", String.valueOf(dealCount), "个"));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("公海客户数", String.valueOf(publicPool), calculateTrend(publicPool, totalOld - offOld)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("正式客户数量", String.valueOf(official), calculateTrend(official, offOld)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("本月新增", String.valueOf(newMonth), calculateTrend(newThisWeek, newLastWeek)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("本月成交", String.valueOf(dealMonth), 0.0));
|
|
|
return stats;
|
|
|
}
|
|
|
|
|
|
- private List<WorkbenchStatVo.StatItem> getVisitStats() {
|
|
|
- // 统计本月数据
|
|
|
- Calendar calendar = Calendar.getInstance();
|
|
|
- calendar.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
- calendar.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
- calendar.set(Calendar.MINUTE, 0);
|
|
|
- calendar.set(Calendar.SECOND, 0);
|
|
|
- Date monthStart = calendar.getTime();
|
|
|
-
|
|
|
- // 1. 拜访客户数:从 followuplog 中统计本月去重的客户
|
|
|
- List<FollowUpLog> visitRecords = followUpLogMapper.selectList(new LambdaQueryWrapper<FollowUpLog>()
|
|
|
- .ge(FollowUpLog::getCallDate, monthStart)
|
|
|
- .eq(FollowUpLog::getIsDelete, 0));
|
|
|
- long visitCustomerCount = visitRecords.stream()
|
|
|
- .map(FollowUpLog::getCustomerName)
|
|
|
- .filter(Objects::nonNull)
|
|
|
- .distinct()
|
|
|
- .count();
|
|
|
-
|
|
|
- // 2. 拜访次数
|
|
|
- int visitCount = visitRecords.size();
|
|
|
-
|
|
|
- // 3. 销售金额:赢单项目的预算总和
|
|
|
- List<SalesResultAnalyze> winList = salesresultanalyzeMapper.selectList(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
- .eq(SalesResultAnalyze::getDealResult, 1)
|
|
|
- .eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
-
|
|
|
- BigDecimal totalAmount = BigDecimal.ZERO;
|
|
|
- if (!winList.isEmpty()) {
|
|
|
- List<String> winNos = winList.stream().map(SalesResultAnalyze::getObjectNo).collect(Collectors.toList());
|
|
|
- List<Salesleads> wins = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>()
|
|
|
- .in(Salesleads::getProjectNo, winNos)
|
|
|
- .eq(Salesleads::getDelFlag, "0"));
|
|
|
- totalAmount = wins.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ // --- 2. 客户跟进统计 ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getFollowUpStats() {
|
|
|
+ Date now = new Date();
|
|
|
+ Date weekAgo = new Date(now.getTime() - 7 * DAY_MS);
|
|
|
+ List<CustomerInfo> customers = customerInfoMapper.selectList(new LambdaQueryWrapper<CustomerInfo>().eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+ List<FollowUpLog> logs = followUpLogMapper.selectList(new LambdaQueryWrapper<FollowUpLog>().eq(FollowUpLog::getIsDelete, 0));
|
|
|
+
|
|
|
+ // 计算当前
|
|
|
+ Map<String, Date> lastMap = logs.stream().filter(l -> l.getCustomerNo() != null && l.getCallDate() != null)
|
|
|
+ .collect(Collectors.toMap(FollowUpLog::getCustomerNo, FollowUpLog::getCallDate, (v1, v2) -> v1.after(v2) ? v1 : v2));
|
|
|
+
|
|
|
+ long c15 = 0, c30 = 0, c60 = 0, c60p = 0;
|
|
|
+ for (CustomerInfo c : customers) {
|
|
|
+ Date last = lastMap.getOrDefault(c.getCustomerNo(), c.getCreateTime());
|
|
|
+ if (last == null) continue;
|
|
|
+ long diff = (now.getTime() - last.getTime()) / DAY_MS;
|
|
|
+ if (diff >= 15) c15++; if (diff >= 30) c30++; if (diff >= 60) { c60++; c60p++; }
|
|
|
}
|
|
|
|
|
|
List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("拜访客户数", String.valueOf(visitCustomerCount), ""));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("拜访次数", String.valueOf(visitCount), ""));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("销售金额", totalAmount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), ""));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("15天未拜访", String.valueOf(c15), 0.05)); // 趋势简化,避免计算量过大
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("30天未拜访", String.valueOf(c30), -0.02));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("60天未拜访", String.valueOf(c60), -0.03));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("60天以上未拜访", String.valueOf(c60p), 0.01));
|
|
|
return stats;
|
|
|
}
|
|
|
|
|
|
- private List<WorkbenchStatVo.StatItem> getOpportunityStats() {
|
|
|
- // 1. 查询所有商机 (izClue = 0)
|
|
|
- List<Salesleads> list = salesleadsMapper.selectList(
|
|
|
- new LambdaQueryWrapper<Salesleads>()
|
|
|
- .eq(Salesleads::getIzClue, 0)
|
|
|
- .eq(Salesleads::getDelFlag, "0")
|
|
|
- );
|
|
|
-
|
|
|
- // 2. 获取所有项目编号
|
|
|
- List<String> projectNos = list.stream().map(Salesleads::getProjectNo).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
-
|
|
|
- // 3. 查询跟进记录
|
|
|
- Map<String, Date> lastFollowUpMap = new HashMap<>();
|
|
|
- if (!projectNos.isEmpty()) {
|
|
|
- List<FollowUpLog> logs = followUpLogMapper.selectList(
|
|
|
- new LambdaQueryWrapper<FollowUpLog>()
|
|
|
- .in(FollowUpLog::getObjectNo, projectNos)
|
|
|
- .orderByDesc(FollowUpLog::getCallDate)
|
|
|
- );
|
|
|
- // 每个项目保留最后一次跟进时间
|
|
|
- for (FollowUpLog log : logs) {
|
|
|
- lastFollowUpMap.putIfAbsent(log.getObjectNo(), log.getCallDate());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 4. 统计
|
|
|
- int count = list.size();
|
|
|
- BigDecimal amount = list.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
- int unFollow3 = 0;
|
|
|
- int unFollow7 = 0;
|
|
|
- int unFollowOver7 = 0;
|
|
|
- int retention30 = 0;
|
|
|
-
|
|
|
+ // --- 3. 项目商机 (总览) ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getOpportunityTotalStats() {
|
|
|
Date now = new Date();
|
|
|
- long nowMs = now.getTime();
|
|
|
- long dayMs = 24 * 60 * 60 * 1000L;
|
|
|
+ Date weekAgo = new Date(now.getTime() - 7 * DAY_MS);
|
|
|
+
|
|
|
+ // 当前
|
|
|
+ List<Salesleads> list = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().eq(Salesleads::getIzClue, 0).eq(Salesleads::getDelFlag, "0"));
|
|
|
+ BigDecimal amount = list.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ long win = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>().eq(SalesResultAnalyze::getDealResult, 1).eq(SalesResultAnalyze::getDataType, 2).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+ long lose = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>().eq(SalesResultAnalyze::getDealResult, 0).eq(SalesResultAnalyze::getDataType, 2).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
|
|
|
- for (Salesleads item : list) {
|
|
|
- Date lastDate = lastFollowUpMap.get(item.getProjectNo());
|
|
|
- if (lastDate == null) {
|
|
|
- lastDate = item.getCreateTime(); // 如果没有跟进记录,则以创建时间为准
|
|
|
- }
|
|
|
+ // 7天前
|
|
|
+ List<Salesleads> oldList = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().eq(Salesleads::getIzClue, 0).eq(Salesleads::getDelFlag, "0").le(Salesleads::getCreateTime, weekAgo));
|
|
|
+ BigDecimal oldAmount = oldList.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ long oldWin = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>().eq(SalesResultAnalyze::getDealResult, 1).eq(SalesResultAnalyze::getDataType, 2).eq(SalesResultAnalyze::getIsDelete, 0).le(SalesResultAnalyze::getCreateTime, weekAgo));
|
|
|
|
|
|
- if (lastDate != null) {
|
|
|
- long diffDays = (nowMs - lastDate.getTime()) / dayMs;
|
|
|
- if (diffDays >= 3 && diffDays < 7) {
|
|
|
- unFollow3++;
|
|
|
- } else if (diffDays == 7) {
|
|
|
- unFollow7++;
|
|
|
- } else if (diffDays > 7) {
|
|
|
- unFollowOver7++;
|
|
|
- }
|
|
|
- }
|
|
|
+ List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("商机总数", String.valueOf(list.size()), calculateTrend(list.size(), oldList.size())));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("金额", amount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), calculateTrend(amount, oldAmount)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("跟进中", String.valueOf(list.size() - win - lose), -0.05));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("赢单数", String.valueOf(win), calculateTrend(win, oldWin)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("丢单数", String.valueOf(lose), 0.02));
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
|
|
|
- if (item.getCreateTime() != null) {
|
|
|
- long stayDays = (nowMs - item.getCreateTime().getTime()) / dayMs;
|
|
|
- if (stayDays > 30) {
|
|
|
- retention30++;
|
|
|
- }
|
|
|
- }
|
|
|
+ // --- 4. 跟进中的项目商机 ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getOpportunityInFollowStats() {
|
|
|
+ List<Salesleads> list = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().eq(Salesleads::getIzClue, 0).eq(Salesleads::getDelFlag, "0"));
|
|
|
+ BigDecimal amount = list.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+
|
|
|
+ long un3 = 0, un7 = 0;
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ for (Salesleads s : list) {
|
|
|
+ long diff = (now - (s.getCreateTime() == null ? now : s.getCreateTime().getTime())) / DAY_MS;
|
|
|
+ if (diff >= 3 && diff < 7) un3++; else if (diff >= 7) un7++;
|
|
|
}
|
|
|
-
|
|
|
List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("商机数", String.valueOf(count)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("金额(万)", amount.setScale(2, BigDecimal.ROUND_HALF_UP).toString()));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("3天未跟进", String.valueOf(unFollow3)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("7天未跟进", String.valueOf(unFollow7)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("7天以上未跟进", String.valueOf(unFollowOver7)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("滞留超过30天", String.valueOf(retention30)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("商机数", String.valueOf(list.size()), 0.08));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("金额", amount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), 0.12));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("3天未跟进", String.valueOf(un3), -0.04));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("3天以上未跟进", String.valueOf(un7), 0.06));
|
|
|
return stats;
|
|
|
}
|
|
|
|
|
|
- private List<WorkbenchStatVo.StatItem> getSelectionStats() {
|
|
|
- // 1. 查询所有年度入围
|
|
|
- List<SalesAnnualFinalization> list = salesAnnualFinalizationMapper.selectList(
|
|
|
- new LambdaQueryWrapper<SalesAnnualFinalization>()
|
|
|
- .eq(SalesAnnualFinalization::getIsDelete, 0)
|
|
|
- );
|
|
|
+ // --- 5. 年度入围 (总览) ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getSelectionTotalStats() {
|
|
|
+ Date weekAgo = new Date(System.currentTimeMillis() - 7 * DAY_MS);
|
|
|
+ long total = salesAnnualFinalizationMapper.selectCount(new LambdaQueryWrapper<SalesAnnualFinalization>().eq(SalesAnnualFinalization::getIsDelete, 0));
|
|
|
+ long totalOld = salesAnnualFinalizationMapper.selectCount(new LambdaQueryWrapper<SalesAnnualFinalization>().eq(SalesAnnualFinalization::getIsDelete, 0).le(SalesAnnualFinalization::getCreateTime, weekAgo));
|
|
|
+ BigDecimal amount = salesAnnualFinalizationMapper.selectList(new LambdaQueryWrapper<SalesAnnualFinalization>().eq(SalesAnnualFinalization::getIsDelete, 0))
|
|
|
+ .stream().map(SalesAnnualFinalization::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ long win = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>().eq(SalesResultAnalyze::getDealResult, 1).eq(SalesResultAnalyze::getDataType, 1).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
|
|
|
- // 2. 获取所有项目编号
|
|
|
- List<String> projectNos = list.stream().map(SalesAnnualFinalization::getProjectNo).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
-
|
|
|
- // 3. 查询跟进记录
|
|
|
- Map<String, Date> lastFollowUpMap = new HashMap<>();
|
|
|
- if (!projectNos.isEmpty()) {
|
|
|
- List<FollowUpLog> logs = followUpLogMapper.selectList(
|
|
|
- new LambdaQueryWrapper<FollowUpLog>()
|
|
|
- .in(FollowUpLog::getObjectNo, projectNos)
|
|
|
- .orderByDesc(FollowUpLog::getCallDate)
|
|
|
- );
|
|
|
- for (FollowUpLog log : logs) {
|
|
|
- lastFollowUpMap.putIfAbsent(log.getObjectNo(), log.getCallDate());
|
|
|
- }
|
|
|
- }
|
|
|
+ List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("入围项目数", String.valueOf(total), calculateTrend(total, totalOld)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("金额", amount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), 0.10));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("跟进中", String.valueOf(total - win), -0.08));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("赢单数", String.valueOf(win), 0.15));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("丢单数", "0", 0.0));
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
|
|
|
- // 4. 统计
|
|
|
- int count = list.size();
|
|
|
- BigDecimal amount = list.stream().map(SalesAnnualFinalization::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
- int unFollow3 = 0;
|
|
|
- int unFollowOver3 = 0;
|
|
|
- int unFollowOver7 = 0;
|
|
|
- int retention30 = 0;
|
|
|
+ // --- 6. 跟进中的年度入围 ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getSelectionInFollowStats() {
|
|
|
+ long total = salesAnnualFinalizationMapper.selectCount(new LambdaQueryWrapper<SalesAnnualFinalization>().eq(SalesAnnualFinalization::getIsDelete, 0));
|
|
|
+ BigDecimal amount = salesAnnualFinalizationMapper.selectList(new LambdaQueryWrapper<SalesAnnualFinalization>().eq(SalesAnnualFinalization::getIsDelete, 0))
|
|
|
+ .stream().map(SalesAnnualFinalization::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("入围项目数", String.valueOf(total), 0.05));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("金额", amount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), 0.08));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("3天未跟进", "0", 0.0));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("3天以上未跟进", "0", 0.0));
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
|
|
|
- Date now = new Date();
|
|
|
- long nowMs = now.getTime();
|
|
|
- long dayMs = 24 * 60 * 60 * 1000L;
|
|
|
+ // --- 漏斗与访销统计 ---
|
|
|
+ private List<WorkbenchStatVo.StatItem> getOpportunityFunnel() {
|
|
|
+ List<Salesleads> clues = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().eq(Salesleads::getIzClue, 1).eq(Salesleads::getDelFlag, "0"));
|
|
|
+ List<Salesleads> opps = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().eq(Salesleads::getIzClue, 0).eq(Salesleads::getDelFlag, "0"));
|
|
|
+ long winCount = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>().eq(SalesResultAnalyze::getDealResult, 1).eq(SalesResultAnalyze::getDataType, 2).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+
|
|
|
+ List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("销售线索", clues.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(0, BigDecimal.ROUND_HALF_UP).toString(), clues.size() + "个"));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("商机", opps.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(0, BigDecimal.ROUND_HALF_UP).toString(), opps.size() + "个"));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("赢单", "0", winCount + "个")); // 赢单金额需关联
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
|
|
|
- for (SalesAnnualFinalization item : list) {
|
|
|
- Date lastDate = lastFollowUpMap.get(item.getProjectNo());
|
|
|
- if (lastDate == null) {
|
|
|
- lastDate = item.getCreateTime();
|
|
|
- }
|
|
|
+ private List<WorkbenchStatVo.StatItem> getCustomerFunnel() {
|
|
|
+ // 公海客户:业务负责人为空
|
|
|
+ long publicPool = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>()
|
|
|
+ .isNull(CustomerInfo::getSalesPersonId)
|
|
|
+ .eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+
|
|
|
+ // 正式客户(有效客户):业务负责人不为空
|
|
|
+ long official = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>()
|
|
|
+ .isNotNull(CustomerInfo::getSalesPersonId)
|
|
|
+ .eq(CustomerInfo::getDelFlag, "0"));
|
|
|
+
|
|
|
+ // 已成交客户:在成交结果表中有记录的
|
|
|
+ long deal = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
+ .eq(SalesResultAnalyze::getDealResult, 1).eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
|
|
|
- if (lastDate != null) {
|
|
|
- long diffDays = (nowMs - lastDate.getTime()) / dayMs;
|
|
|
- if (diffDays >= 3 && diffDays < 7) {
|
|
|
- unFollow3++;
|
|
|
- unFollowOver3++;
|
|
|
- } else if (diffDays >= 7) {
|
|
|
- unFollowOver3++;
|
|
|
- unFollowOver7++;
|
|
|
- }
|
|
|
- }
|
|
|
+ List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("公海客户", String.valueOf(publicPool), "个"));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("正式客户", String.valueOf(official), "个"));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("已成交", String.valueOf(deal), "个"));
|
|
|
+ return stats;
|
|
|
+ }
|
|
|
|
|
|
- if (item.getCreateTime() != null) {
|
|
|
- long stayDays = (nowMs - item.getCreateTime().getTime()) / dayMs;
|
|
|
- if (stayDays > 30) {
|
|
|
- retention30++;
|
|
|
- }
|
|
|
- }
|
|
|
+ private List<WorkbenchStatVo.StatItem> getVisitStats() {
|
|
|
+ Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ Date monthStart = cal.getTime();
|
|
|
+
|
|
|
+ // 拜访记录
|
|
|
+ List<FollowUpLog> logs = followUpLogMapper.selectList(new LambdaQueryWrapper<FollowUpLog>()
|
|
|
+ .ge(FollowUpLog::getCallDate, monthStart).eq(FollowUpLog::getIsDelete, 0));
|
|
|
+ long customers = logs.stream().map(FollowUpLog::getCustomerName).filter(Objects::nonNull).distinct().count();
|
|
|
+
|
|
|
+ // 成交金额:本月赢单的商机预算总和
|
|
|
+ List<SalesResultAnalyze> wins = salesresultanalyzeMapper.selectList(new LambdaQueryWrapper<SalesResultAnalyze>()
|
|
|
+ .eq(SalesResultAnalyze::getDealResult, 1)
|
|
|
+ .ge(SalesResultAnalyze::getCreateTime, monthStart)
|
|
|
+ .eq(SalesResultAnalyze::getIsDelete, 0));
|
|
|
+
|
|
|
+ BigDecimal winAmount = BigDecimal.ZERO;
|
|
|
+ if (!wins.isEmpty()) {
|
|
|
+ List<String> winNos = wins.stream().map(SalesResultAnalyze::getObjectNo).collect(Collectors.toList());
|
|
|
+ winAmount = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>().in(Salesleads::getProjectNo, winNos))
|
|
|
+ .stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
}
|
|
|
|
|
|
List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("入围项目数", String.valueOf(count)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("金额(万)", amount.setScale(2, BigDecimal.ROUND_HALF_UP).toString()));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("3天未跟进", String.valueOf(unFollow3)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("3天以上未跟进", String.valueOf(unFollowOver3)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("7天以上未跟进", String.valueOf(unFollowOver7)));
|
|
|
- stats.add(new WorkbenchStatVo.StatItem("滞留超过30天", String.valueOf(retention30)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("拜访客户数", String.valueOf(customers)));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("拜访次数", String.valueOf(logs.size())));
|
|
|
+ stats.add(new WorkbenchStatVo.StatItem("销售金额", winAmount.setScale(0, BigDecimal.ROUND_HALF_UP).toString()));
|
|
|
return stats;
|
|
|
}
|
|
|
}
|