diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/CustomerStatisticsData.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/CustomerStatisticsData.java index e4038c5..ee12bb4 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/CustomerStatisticsData.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/CustomerStatisticsData.java @@ -63,4 +63,7 @@ public class CustomerStatisticsData implements Serializable { private int sortNo; + //是否是隐藏数据 + private Boolean hiddenFlag = false; + } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/DepartmentStatisticsData.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/DepartmentStatisticsData.java index 0ed447e..ebbf221 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/DepartmentStatisticsData.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/domain/DepartmentStatisticsData.java @@ -41,17 +41,53 @@ public class DepartmentStatisticsData implements Serializable { //非及时单占比(当日) private BigDecimal dailyNonTimelyOrderRatio; -/* //即时单 + //即时单 private Integer dailyTimelyCount; //非即时单 - private Integer dailyNonTimelyCount;*/ + private Integer dailyNonTimelyCount; //管理员分配(当日) private Integer managerAccepted; - /** - * 部门路径(如:苏州曼普/销售部/一组 或 苏州曼普/销售部/一组/盛宇婷) - */ + + //新增:家长出单率(当日) + private BigDecimal dailyParentOrderRate; + //新增:学生出单率(当日) + private BigDecimal dailyStudentOrderRate; + + //如果是四级的 默认是个人 前端显示时也是默认显示个人 + private Boolean personFlag; + private String departmentPath; + //公司名称 + private String corpName; + //部门名称 + private String departmentName; + //组名称 + private String groupName; + //个人名称 + private String personName; + + + // ========== 新增:家长/学生出单率统计(当日) ========== + /** + * 家长出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int parentDailyOrderCount = 0; + + /** + * 家长总数(当日进粉)- 用于出单率分母 + */ + private int parentDailyCount = 0; + + /** + * 学生出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int studentDailyOrderCount = 0; + + /** + * 学生总数(当日进粉)- 用于出单率分母 + */ + private int studentDailyCount = 0; /** * 创建时间 diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/DepartmentStatisticsAccumulator.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/DepartmentStatisticsAccumulator.java index fe8a6d6..c6bde5d 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/DepartmentStatisticsAccumulator.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/DepartmentStatisticsAccumulator.java @@ -55,5 +55,26 @@ public class DepartmentStatisticsAccumulator { * "由管理员XXX分配" * */ private int managerAcceptCount = 0; + + // ========== 新增:家长/学生出单率统计(当日) ========== + /** + * 家长出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int parentDailyOrderCount = 0; + + /** + * 家长总数(当日进粉)- 用于出单率分母 + */ + private int parentDailyCount = 0; + + /** + * 学生出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int studentDailyOrderCount = 0; + + /** + * 学生总数(当日进粉)- 用于出单率分母 + */ + private int studentDailyCount = 0; } } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/HandleAllData.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/HandleAllData.java index cbd6f3c..a7dfe84 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/HandleAllData.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/HandleAllData.java @@ -451,22 +451,58 @@ import java.util.concurrent.atomic.AtomicInteger; continue; } - // 提取组级别路径和员工级别路径 - // 例如:苏州曼普/销售部/二组/盛宇婷 - // 组级别:苏州曼普/销售部/二组 - // 员工级别:苏州曼普/销售部/二组/盛宇婷 + // 先计算所有条件,避免重复判断 + boolean isQValueMatch = matchesQValue(data, targetDate); + boolean isDateMatch = matchesDate(data, targetDate); + boolean isSourceMatch = matchesSource(data); + boolean isTimelyOrder = isTimelyOrder(data); + String customerAttr = data.getTagGroup6(); + boolean isParent = customerAttr != null && !customerAttr.trim().isEmpty() && customerAttr.contains("家长"); + boolean isStudent = customerAttr != null && !customerAttr.trim().isEmpty() && customerAttr.contains("学生"); + + // 提取各级路径 String[] pathParts = departmentPath.split("/"); - // 处理组级别统计(前3级) - if (pathParts.length >= 3) { - String groupPath = pathParts[0] + "/" + pathParts[1] + "/" + pathParts[2]; - processDepartmentRecord(data, targetDate, accumulator.getDepartmentStats(groupPath)); + // 构建各级路径并累加统计(只遍历一次,条件已预先计算) + StringBuilder pathBuilder = new StringBuilder(); + for (int i = 0; i < pathParts.length; i++) { + if (i > 0) { + pathBuilder.append("/"); + } + pathBuilder.append(pathParts[i]); + String currentPath = pathBuilder.toString(); + DepartmentStatisticsAccumulator.DepartmentStats stats = accumulator.getDepartmentStats(currentPath); + + // 累加统计(条件已预先计算,直接使用) + if (isQValueMatch) { + stats.setTotalOrderCount(stats.getTotalOrderCount() + 1); + if (isTimelyOrder) { + stats.setTimelyOrderCount(stats.getTimelyOrderCount() + 1); + } else { + stats.setNonTimelyOrderCount(stats.getNonTimelyOrderCount() + 1); + } + } + + if (isDateMatch) { + if (isSourceMatch) { + stats.setTotalAcceptCount(stats.getTotalAcceptCount() + 1); + if (isParent) { + stats.setParentDailyCount(stats.getParentDailyCount() + 1); + if (isQValueMatch && isTimelyOrder) { + stats.setParentDailyOrderCount(stats.getParentDailyOrderCount() + 1); + } + } else if (isStudent) { + stats.setStudentDailyCount(stats.getStudentDailyCount() + 1); + if (isQValueMatch && isTimelyOrder) { + stats.setStudentDailyOrderCount(stats.getStudentDailyOrderCount() + 1); + } + } + } else { + stats.setManagerAcceptCount(stats.getManagerAcceptCount() + 1); + } + } } - // 处理员工级别统计(完整路径) - if (pathParts.length >= 4) { - processDepartmentRecord(data, targetDate, accumulator.getDepartmentStats(departmentPath)); - } } // 检查是否还有下一页 @@ -530,38 +566,72 @@ import java.util.concurrent.atomic.AtomicInteger; } private boolean matchesQValue(CustomerExportData data,Date curDate) { - String orderDate = data.getTagGroup4(); // Q列:成交日期,格式:2.3 + String orderDate = data.getTagGroup4(); // Q列:成交日期 - // 基础校验 + // 支持多种格式: + // 1. 2026/2/24周二 或 2026/2/24 周一 + // 2. 1.7-小雅初中公众号K 或 1.7 + // 3. 2.3 if (orderDate == null || orderDate.trim().isEmpty()) { return false; } try { - //由于可能是多个逗号拼成的多个日期 任意一个日期符合都可以 + // 由于可能是多个逗号拼成的多个日期,任意一个日期符合都可以 String[] dates = orderDate.trim().split(","); - // 解析订单日期:2.3 -> [2, 3] - String[] parts = dates[dates.length - 1].trim().split("[.\\-/]"); - if (parts.length < 2) { - return false; - } + String lastDateStr = dates[dates.length - 1].trim(); - int orderMonth = Integer.parseInt(parts[0].trim()); - int orderDay = Integer.parseInt(parts[1].trim()); - - // 构建成交日期(基于addTime的年份) Calendar orderCal = Calendar.getInstance(); orderCal.setTime(curDate); - orderCal.set(Calendar.MONTH, orderMonth - 1); - orderCal.set(Calendar.DAY_OF_MONTH, orderDay); orderCal.set(Calendar.HOUR_OF_DAY, 0); orderCal.set(Calendar.MINUTE, 0); orderCal.set(Calendar.SECOND, 0); orderCal.set(Calendar.MILLISECOND, 0); - // 跨年处理:如果成交日期早于添加日期,年份+1 - if (orderCal.getTime().before(curDate)) { - orderCal.add(Calendar.YEAR, 1); + boolean parsed = false; + + // 格式1: 2026/2/24周二 或 2026/2/24 周一 (完整日期格式) + // 正则匹配: 年份/月份/日期 + java.util.regex.Pattern fullDatePattern = java.util.regex.Pattern.compile("(\\d{4})[/\\-](\\d{1,2})[/\\-](\\d{1,2})"); + java.util.regex.Matcher fullDateMatcher = fullDatePattern.matcher(lastDateStr); + if (fullDateMatcher.find()) { + int year = Integer.parseInt(fullDateMatcher.group(1)); + int month = Integer.parseInt(fullDateMatcher.group(2)); + int day = Integer.parseInt(fullDateMatcher.group(3)); + orderCal.set(Calendar.YEAR, year); + orderCal.set(Calendar.MONTH, month - 1); + orderCal.set(Calendar.DAY_OF_MONTH, day); + parsed = true; + } + + // 格式2和3: 1.7-小雅初中公众号K 或 2.3 (月.日格式) + if (!parsed) { + // 提取开头的月.日格式,忽略后面的文字 + java.util.regex.Pattern monthDayPattern = java.util.regex.Pattern.compile("^(\\d{1,2})[.\\-/](\\d{1,2})"); + java.util.regex.Matcher monthDayMatcher = monthDayPattern.matcher(lastDateStr); + if (monthDayMatcher.find()) { + int month = Integer.parseInt(monthDayMatcher.group(1)); + int day = Integer.parseInt(monthDayMatcher.group(2)); + orderCal.set(Calendar.MONTH, month - 1); + orderCal.set(Calendar.DAY_OF_MONTH, day); + // 跨年处理:如果成交日期早于当前日期,年份+1 + Calendar tempCal = Calendar.getInstance(); + tempCal.setTime(curDate); + tempCal.set(Calendar.MONTH, month - 1); + tempCal.set(Calendar.DAY_OF_MONTH, day); + tempCal.set(Calendar.HOUR_OF_DAY, 0); + tempCal.set(Calendar.MINUTE, 0); + tempCal.set(Calendar.SECOND, 0); + tempCal.set(Calendar.MILLISECOND, 0); + if (tempCal.getTime().before(curDate)) { + orderCal.add(Calendar.YEAR, 1); + } + parsed = true; + } + } + + if (!parsed) { + return false; } // 标准化目标日期(清除时分秒) @@ -572,15 +642,11 @@ import java.util.concurrent.atomic.AtomicInteger; targetCal.set(Calendar.SECOND, 0); targetCal.set(Calendar.MILLISECOND, 0); - boolean b = orderCal.getTimeInMillis() == targetCal.getTimeInMillis(); - if (b) { - return true; - } + return orderCal.getTimeInMillis() == targetCal.getTimeInMillis(); } catch (Exception e) { return false; } - return false; } private boolean matchOrderCompleted(CustomerExportData data,Date curDate) { @@ -591,6 +657,16 @@ import java.util.concurrent.atomic.AtomicInteger; } return true; } + + private boolean isTimelyOrder(CustomerExportData data) { + String orderStatus = data.getTagGroup7(); + if (orderStatus == null || orderStatus.trim().isEmpty()) { + return false; + } + String[] split = orderStatus.split(","); + String statusInfo = split[split.length - 1]; + return statusInfo.contains("已成交及时单9元+"); + } /** * 处理单条数据记录,累加到所有相关组的统计中 */ @@ -674,11 +750,23 @@ import java.util.concurrent.atomic.AtomicInteger; //todo 存在订单未完成 但是有标签的场景 stats.setParentOrderCount(stats.getParentOrderCount() + 1); + // 新增:出单率统计(当日) + stats.setParentDailyCount(stats.getParentDailyCount() + 1); + if (matchesQValue(data, date) && isTimelyOrder(data)) { + stats.setParentDailyOrderCount(stats.getParentDailyOrderCount() + 1); + } + } else if (customerAttr.contains("学生")) { stats.setStudentCount(stats.getStudentCount() + 1); stats.setStudentOrderCount(stats.getStudentOrderCount() + 1); + // 新增:N组学生出单率统计(当日) + stats.setStudentDailyCount(stats.getStudentDailyCount() + 1); + if (matchesQValue(data, date) && isTimelyOrder(data)) { + stats.setStudentDailyOrderCount(stats.getStudentDailyOrderCount() + 1); + } + } else { stats.setUnknownAttrCount(stats.getUnknownAttrCount() + 1); } @@ -773,10 +861,12 @@ import java.util.concurrent.atomic.AtomicInteger; // 4. 及时单占比 String timelyRate = calculateRate(stats.getTimelyOrderCount(), stats.getCustomerCount()); setIndicatorValue(corpId,indicatorMap,curDate, "及时单占比(当日)", groupName, timelyRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "及时单数量(当日)", groupName, String.valueOf(stats.getTimelyOrderCount()),(10*sortNo++),true); // 5. 非及时单占比 String nonTimelyRate = calculateRate(stats.getNonTimelyOrderCount(), stats.getCustomerCount()); setIndicatorValue(corpId,indicatorMap,curDate, "非及时单占比(当日)", groupName, nonTimelyRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "非及时单数量(当日)", groupName, String.valueOf(stats.getNonTimelyOrderCount()),(10*sortNo++),true); // 6. 客户属性数量 setIndicatorValue(corpId,indicatorMap,curDate, "客户属性数量(当日)", groupName, String.valueOf(stats.getTotalCustomerAttr()),(10*sortNo++)); @@ -784,14 +874,17 @@ import java.util.concurrent.atomic.AtomicInteger; // 7. 家长占比 String parentRate = calculateRate(stats.getParentCount(), stats.getTotalCustomerAttr()); setIndicatorValue(corpId,indicatorMap,curDate, "家长占比(当日)", groupName, parentRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "家长数量(当日)", groupName, String.valueOf(stats.getParentCount()),(10*sortNo++),true); // 8. 学生占比 String studentRate = calculateRate(stats.getStudentCount(), stats.getTotalCustomerAttr()); setIndicatorValue(corpId,indicatorMap,curDate, "学生占比(当日)", groupName, studentRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "学生数量(当日)", groupName, String.valueOf(stats.getStudentCount()),(10*sortNo++),true); // 9. 未知占比 String unknownRate = calculateRate(stats.getUnknownAttrCount(), stats.getTotalCustomerAttr()); setIndicatorValue(corpId,indicatorMap,curDate, "未知占比(当日)", groupName, unknownRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "未知数量(当日)", groupName, String.valueOf(stats.getUnknownAttrCount()),(10*sortNo++),true); // 10. 意向度数量 setIndicatorValue(corpId,indicatorMap,curDate, "意向度数量(当日)", groupName, String.valueOf(stats.getTotalIntention()),(10*sortNo++)); @@ -799,18 +892,22 @@ import java.util.concurrent.atomic.AtomicInteger; // 11. 主动报价占比 String activeQuoteRate = calculateRate(stats.getActiveQuoteCount(), stats.getTotalIntention()); setIndicatorValue(corpId,indicatorMap,curDate, "主动报价占比(当日)", groupName, activeQuoteRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "主动报价数量(当日)", groupName, String.valueOf(stats.getActiveQuoteCount()),(10*sortNo++),true); // 12. 被动报价占比 String passiveQuoteRate = calculateRate(stats.getPassiveQuoteCount(), stats.getTotalIntention()); setIndicatorValue(corpId,indicatorMap,curDate, "被动报价占比(当日)", groupName, passiveQuoteRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "被动报价数量(当日)", groupName, String.valueOf(stats.getPassiveQuoteCount()),(10*sortNo++),true); // 13. 未开口报价占比 String noQuoteRate = calculateRate(stats.getNoQuoteCount(), stats.getTotalIntention()); setIndicatorValue(corpId,indicatorMap,curDate, "未开口报价占比(当日)", groupName, noQuoteRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "未开口报价数量(当日)", groupName, String.valueOf(stats.getNoQuoteCount()),(10*sortNo++),true); // 14. 已删除报价占比 String deletedQuoteRate = calculateRate(stats.getDeletedQuoteCount(), stats.getTotalIntention()); setIndicatorValue(corpId,indicatorMap,curDate, "已删除报价占比(当日)", groupName, deletedQuoteRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "已删除数量(当日)", groupName, String.valueOf(stats.getDeletedQuoteCount()),(10*sortNo++),true); // 15. 年级数量 setIndicatorValue(corpId,indicatorMap,curDate, "年级数量(当日)", groupName, String.valueOf(stats.getTotalGrade()),(10*sortNo++)); @@ -818,23 +915,37 @@ import java.util.concurrent.atomic.AtomicInteger; // 16. 小学占比 String primaryRate = calculateRate(stats.getPrimaryCount(), stats.getTotalGrade()); setIndicatorValue(corpId,indicatorMap,curDate, "小学占比(当日)", groupName, primaryRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "小学数量(当日)", groupName, String.valueOf(stats.getPrimaryCount()),(10*sortNo++),true); // 17. 初中占比 String middleRate = calculateRate(stats.getMiddleCount(), stats.getTotalGrade()); setIndicatorValue(corpId,indicatorMap,curDate, "初中占比(当日)", groupName, middleRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "初中数量(当日)", groupName, String.valueOf(stats.getMiddleCount()),(10*sortNo++),true); // 18. 高中占比 String highRate = calculateRate(stats.getHighCount(), stats.getTotalGrade()); setIndicatorValue(corpId,indicatorMap,curDate, "高中占比(当日)", groupName, highRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "高中数量(当日)", groupName, String.valueOf(stats.getHighCount()),(10*sortNo++),true); // 19. 家长出单占比 String parentOrderRate = calculateRate(stats.getParentOrderCount(), stats.getParentCount()); setIndicatorValue(corpId,indicatorMap,curDate, "家长出单占比(当日)", groupName, parentOrderRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "家长出单数量(当日)", groupName, String.valueOf(stats.getParentOrderCount()),(10*sortNo++),true); // 20. 学生出单占比 String studentOrderRate = calculateRate(stats.getStudentOrderCount(), stats.getStudentCount()); setIndicatorValue(corpId,indicatorMap,curDate, "学生出单占比(当日)", groupName, studentOrderRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "学生出单数量(当日)", groupName, String.valueOf(stats.getStudentOrderCount()),(10*sortNo++),true); + // 21. 家长出单率(当日) + String parentDailyOrderRate = calculateRate(stats.getParentDailyOrderCount(), stats.getParentDailyCount()); + setIndicatorValue(corpId,indicatorMap,curDate, "家长出单率(当日)", groupName, parentDailyOrderRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "家长即时单数量(当日)", groupName, String.valueOf(stats.getParentDailyOrderCount()),(10*sortNo++),true); + + // 22. 学生出单率(当日) + String studentDailyOrderRate = calculateRate(stats.getStudentDailyOrderCount(), stats.getStudentDailyCount()); + setIndicatorValue(corpId,indicatorMap,curDate, "学生出单率(当日)", groupName, studentDailyOrderRate,(10*sortNo++)); + setIndicatorValue(corpId,indicatorMap,curDate, "学生即时单数量(当日)", groupName, String.valueOf(stats.getStudentDailyOrderCount()),(10*sortNo++),true); } @@ -865,6 +976,30 @@ import java.util.concurrent.atomic.AtomicInteger; setGroupValue(vo, groupName, value); } + /** + * 设置指标值到对应的 CustomerStatisticsData 对象 + * @param indicatorMap 指标映射表 + * @param indicatorName 指标名称(不带组名前缀) + * @param groupName 组名 + * @param value 指标值 + */ + private void setIndicatorValue(String corpId, Map indicatorMap, Date curDate, + String indicatorName, String groupName, String value, int sortNo, Boolean hiddenFlag) { + // 获取或创建该指标对应的 CustomerStatisticsData 对象 + CustomerStatisticsData vo = indicatorMap.computeIfAbsent(indicatorName, k -> { + CustomerStatisticsData newVo = new CustomerStatisticsData(); + newVo.setIndicatorName(indicatorName); + newVo.setCurDate(curDate); + newVo.setSortNo(sortNo); + newVo.setCorpId(corpId); + newVo.setHiddenFlag(hiddenFlag); + return newVo; + }); + + // 根据组名设置对应的字段值 + setGroupValue(vo, groupName, value); + } + /** * 根据组名设置对应字段的值 * @param vo CustomerStatisticsData 对象 @@ -947,6 +1082,22 @@ import java.util.concurrent.atomic.AtomicInteger; if(matchesSource(data)) { // 2. 总承接数(当日)- 排除"由管理员XXX分配" stats.setTotalAcceptCount(stats.getTotalAcceptCount() + 1); + + // 新增:家长/学生出单率统计 + String customerAttr = data.getTagGroup6(); + if (customerAttr != null && !customerAttr.trim().isEmpty()) { + if (customerAttr.contains("家长")) { + stats.setParentDailyCount(stats.getParentDailyCount() + 1); + if (matchesQValue(data, targetDate) && isTimelyOrder(data)) { + stats.setParentDailyOrderCount(stats.getParentDailyOrderCount() + 1); + } + } else if (customerAttr.contains("学生")) { + stats.setStudentDailyCount(stats.getStudentDailyCount() + 1); + if (matchesQValue(data, targetDate) && isTimelyOrder(data)) { + stats.setStudentDailyOrderCount(stats.getStudentDailyOrderCount() + 1); + } + } + } } else { // 由管理员XXX分配" stats.setManagerAcceptCount(stats.getManagerAcceptCount() + 1); @@ -975,6 +1126,25 @@ import java.util.concurrent.atomic.AtomicInteger; vo.setDepartmentPath(departmentPath); vo.setCorpId(corpId); + // 解析路径,设置各级名称 + // 路径格式:苏州曼普/销售部/二组/盛宇婷 + String[] pathParts = departmentPath.split("/"); + if (pathParts.length >= 1) { + vo.setCorpName(pathParts[0]); + } + if (pathParts.length >= 2) { + vo.setDepartmentName(pathParts[1]); + } + if (pathParts.length >= 3) { + vo.setGroupName(pathParts[2]); + } + if (pathParts.length >= 4) { + vo.setPersonName(pathParts[3]); + vo.setPersonFlag(true); + } else { + vo.setPersonFlag(false); + } + // 设置各项指标 // 1. 总承接数(当日) vo.setDailyTotalAccepted(stats.getTotalAcceptCount()); @@ -990,12 +1160,26 @@ import java.util.concurrent.atomic.AtomicInteger; // 4. 及时单占比(当日) BigDecimal timelyRate = calculateRateBigDecimal(stats.getTimelyOrderCount(), stats.getTotalOrderCount()); + vo.setDailyTimelyCount(stats.getTimelyOrderCount()); vo.setDailyTimelyOrderRatio(timelyRate); // 5. 非及时单占比(当日) BigDecimal nonTimelyRate = calculateRateBigDecimal(stats.getNonTimelyOrderCount(), stats.getTotalOrderCount()); + vo.setDailyNonTimelyCount(stats.getNonTimelyOrderCount()); vo.setDailyNonTimelyOrderRatio(nonTimelyRate); + // 6. 家长出单率(当日) + BigDecimal parentOrderRate = calculateRateBigDecimal(stats.getParentDailyOrderCount(), stats.getParentDailyCount()); + vo.setParentDailyOrderCount(stats.getParentDailyOrderCount()); + vo.setParentDailyCount(stats.getParentDailyCount()); + vo.setDailyParentOrderRate(parentOrderRate); + + // 7. 学生出单率(当日) + BigDecimal studentOrderRate = calculateRateBigDecimal(stats.getStudentDailyOrderCount(), stats.getStudentDailyCount()); + vo.setStudentDailyOrderCount(stats.getStudentDailyOrderCount()); + vo.setStudentDailyCount(stats.getStudentDailyCount()); + vo.setDailyStudentOrderRate(studentOrderRate); + results.add(vo); } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/StatisticsAccumulator.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/StatisticsAccumulator.java index a4a6277..2ef4c58 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/StatisticsAccumulator.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/helper/StatisticsAccumulator.java @@ -142,6 +142,27 @@ public class StatisticsAccumulator { */ private int studentOrderCount = 0; + // ========== 新增:家长/学生出单率统计(当日) ========== + /** + * 家长出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int parentDailyOrderCount = 0; + + /** + * 家长总数(当日进粉)- 用于出单率分母 + */ + private int parentDailyCount = 0; + + /** + * 学生出单数(当日成交)- 满足Q列=当日 AND T列=已成交及时单9元+ + */ + private int studentDailyOrderCount = 0; + + /** + * 学生总数(当日进粉)- 用于出单率分母 + */ + private int studentDailyCount = 0; + // ========== 成本数据 ========== /** * 总成本(手工填写) diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/CustomerStatisticsDataMapper.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/CustomerStatisticsDataMapper.java index e7201ec..a1b58b6 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/CustomerStatisticsDataMapper.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/CustomerStatisticsDataMapper.java @@ -46,5 +46,23 @@ public interface CustomerStatisticsDataMapper extends BaseMapper selectDailyDataByWeek( + @Param("corpId") String corpId, + @Param("year") Integer year, + @Param("week") Integer week, + @Param("indicatorName") String indicatorName + ); + + List selectDailyDataByMonth( + @Param("corpId") String corpId, + @Param("yearMonth") String yearMonth, + @Param("indicatorName") String indicatorName + ); + + List selectAllDailyData( + @Param("corpId") String corpId, + @Param("indicatorName") String indicatorName + ); + } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/DepartmentStatisticsDataMapper.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/DepartmentStatisticsDataMapper.java index cf9c6e7..74a5e15 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/DepartmentStatisticsDataMapper.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/mapper/DepartmentStatisticsDataMapper.java @@ -83,4 +83,32 @@ public interface DepartmentStatisticsDataMapper extends BaseMapper getSummary(@Param("corpId") String corpId,@Param("startDate") Date startDate, @Param("endDate") Date endDate, @Param("departmentPath") String departmentPath); + + /** + * 查询部门统计数据聚合列表(按部门路径聚合) + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param departmentPath 部门路径(可选) + * @return 部门统计数据聚合列表 + */ + List selectDepartmentStatisticsDataAggregatedList( + @Param("corpId") String corpId, + @Param("startDate") Date startDate, + @Param("endDate") Date endDate, + @Param("departmentPath") String departmentPath + ); + + /** + * 查询部门统计数据VO聚合列表(用于导出) + * @param startDate 开始日期 + * @param endDate 结束日期 + * @param departmentPath 部门路径(可选) + * @return 部门统计数据VO聚合列表 + */ + List selectDepartmentStatisticsDataVOAggregatedList( + @Param("corpId") String corpId, + @Param("startDate") Date startDate, + @Param("endDate") Date endDate, + @Param("departmentPath") String departmentPath + ); } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/ICustomerStatisticsDataService.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/ICustomerStatisticsDataService.java index b330f50..2136add 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/ICustomerStatisticsDataService.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/ICustomerStatisticsDataService.java @@ -58,5 +58,11 @@ public interface ICustomerStatisticsDataService { */ int deleteCustomerStatisticsDataByIds(Long[] ids); - int updateCost(String corpId,Date curDate, BigDecimal totalCost, String titleAttr); + int updateCost(String corpId,Date curDate, BigDecimal totalCost, String type,String titleAttr); + + List selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName); + + List selectByMonthAggregation(String corpId, String yearMonth, String indicatorName); + + List selectAllAggregation(String corpId, String indicatorName); } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/IDepartmentStatisticsDataService.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/IDepartmentStatisticsDataService.java index 64fcea5..17d5b39 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/IDepartmentStatisticsDataService.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/IDepartmentStatisticsDataService.java @@ -18,18 +18,20 @@ public interface IDepartmentStatisticsDataService { * @param startDate 开始日期 * @param endDate 结束日期 * @param departmentPath 部门路径 + * @param dataType 数据类型 * @return 部门统计数据列表 */ - List selectDepartmentStatisticsDataList(String corpId,Date startDate, Date endDate, String departmentPath); + List selectDepartmentStatisticsDataList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType); /** * 查询部门统计数据VO列表(用于导出) * @param startDate 开始日期 * @param endDate 结束日期 * @param departmentPath 部门路径 + * @param dataType 数据类型 * @return 部门统计数据VO列表 */ - List selectDepartmentStatisticsDataVOList(String corpId,Date startDate, Date endDate, String departmentPath); + List selectDepartmentStatisticsDataVOList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType); /** * 根据ID查询部门统计数据 @@ -59,5 +61,5 @@ public interface IDepartmentStatisticsDataService { */ int deleteDepartmentStatisticsDataByIds(Long[] ids); - Map getSummary(String corpId,Date startDate, Date endDate, String departmentPath); + Map getSummary(String corpId, Date startDate, Date endDate, String departmentPath, String dataType); } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/CustomerStatisticsDataServiceImpl.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/CustomerStatisticsDataServiceImpl.java index cf69d14..6b10b36 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/CustomerStatisticsDataServiceImpl.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/CustomerStatisticsDataServiceImpl.java @@ -5,6 +5,8 @@ import com.ruoyi.excel.wecom.domain.CustomerStatisticsData; import com.ruoyi.excel.wecom.mapper.CustomerStatisticsDataMapper; import com.ruoyi.excel.wecom.service.ICustomerStatisticsDataService; import com.ruoyi.excel.wecom.vo.CustomerStatisticsDataVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,8 +14,15 @@ import org.springframework.transaction.annotation.Transactional; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.WeekFields; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * 客户统计数据Service业务层处理 @@ -21,6 +30,8 @@ import java.util.List; @Service public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDataService { + private static final Logger log = LoggerFactory.getLogger(CustomerStatisticsDataServiceImpl.class); + @Autowired private CustomerStatisticsDataMapper customerStatisticsDataMapper; @@ -94,7 +105,7 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat @Override @Transactional - public int updateCost(String corpId,Date curDate, BigDecimal totalCost, String titleAttr) { + public int updateCost(String corpId,Date curDate, BigDecimal totalCost, String type,String titleAttr) { try { // 1. 查询该日期的所有记录 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); @@ -112,32 +123,50 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat CustomerStatisticsData danTiaoCostData = findByIndicatorName(dataList, "单条成本(当日)"); CustomerStatisticsData chengDanCostData = findByIndicatorName(dataList, "成单成本(当日)"); - // 3. 更新总成本 - setFieldValue(totalCostData, titleAttr, totalCost.toString()); + // 3. 根据type类型处理成本更新 + BigDecimal actualTotalCost; + if ("single".equals(type)) { + String jinFenShuStr = getFieldValue(jinFenShuData, titleAttr); + if (jinFenShuStr != null && !jinFenShuStr.trim().isEmpty()) { + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + actualTotalCost = totalCost.multiply(jinFenShu).setScale(2, RoundingMode.HALF_UP); + } else { + throw new RuntimeException("进粉数为空,无法计算总成本"); + } + setFieldValue(danTiaoCostData, titleAttr, totalCost.toString()); + customerStatisticsDataMapper.updateById(danTiaoCostData); + } else { + actualTotalCost = totalCost; + } + + // 4. 更新总成本 + setFieldValue(totalCostData, titleAttr, actualTotalCost.toString()); customerStatisticsDataMapper.updateById(totalCostData); - // 4. 获取进粉数和成单数 + // 5. 获取进粉数和成单数 String jinFenShuStr = getFieldValue(jinFenShuData, titleAttr); String chengDanShuStr = getFieldValue(chengDanShuData, titleAttr); - // 5. 计算并更新单条成本 - if (jinFenShuStr != null && !jinFenShuStr.trim().isEmpty()) { - BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); - if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal danTiaoCost = totalCost.divide(jinFenShu, 2, RoundingMode.HALF_UP); - setFieldValue(danTiaoCostData, titleAttr, danTiaoCost.toString()); - customerStatisticsDataMapper.updateById(danTiaoCostData); - } else { - setFieldValue(danTiaoCostData, titleAttr, "0"); - customerStatisticsDataMapper.updateById(danTiaoCostData); + // 6. 计算并更新单条成本(仅在非single模式下更新) + if (!"single".equals(type)) { + if (jinFenShuStr != null && !jinFenShuStr.trim().isEmpty()) { + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal danTiaoCost = actualTotalCost.divide(jinFenShu, 2, RoundingMode.HALF_UP); + setFieldValue(danTiaoCostData, titleAttr, danTiaoCost.toString()); + customerStatisticsDataMapper.updateById(danTiaoCostData); + } else { + setFieldValue(danTiaoCostData, titleAttr, "0"); + customerStatisticsDataMapper.updateById(danTiaoCostData); + } } } - // 6. 计算并更新成单成本 + // 7. 计算并更新成单成本 if (chengDanShuStr != null && !chengDanShuStr.trim().isEmpty()) { BigDecimal chengDanShu = new BigDecimal(chengDanShuStr); if (chengDanShu.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal chengDanCost = totalCost.divide(chengDanShu, 2, RoundingMode.HALF_UP); + BigDecimal chengDanCost = actualTotalCost.divide(chengDanShu, 2, RoundingMode.HALF_UP); setFieldValue(chengDanCostData, titleAttr, chengDanCost.toString()); customerStatisticsDataMapper.updateById(chengDanCostData); } else { @@ -178,9 +207,32 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat } } + /** + * 动态获取字段值 + */ + private String getFieldValue(CustomerStatisticsDataVO data, String fieldName) { + try { + Field field = CustomerStatisticsDataVO.class.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(data); + return value != null ? value.toString() : null; + } catch (Exception e) { + throw new RuntimeException("获取字段值失败: " + fieldName, e); + } + } /** * 动态设置字段值 */ + private void setFieldValue(CustomerStatisticsDataVO data, String fieldName, String value) { + try { + Field field = CustomerStatisticsDataVO.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(data, value); + } catch (Exception e) { + throw new RuntimeException("设置字段值失败: " + fieldName, e); + } + } + private void setFieldValue(CustomerStatisticsData data, String fieldName, String value) { try { Field field = CustomerStatisticsData.class.getDeclaredField(fieldName); @@ -191,4 +243,684 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat } } + @Override + public List selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName) { + List dailyDataList = customerStatisticsDataMapper.selectDailyDataByWeek(corpId, year, week, indicatorName); + return aggregateDataList(dailyDataList, year, week, null); + } + + @Override + public List selectByMonthAggregation(String corpId, String yearMonth, String indicatorName) { + log.info("按月聚合查询 - corpId: {}, yearMonth: {}, indicatorName: {}", corpId, yearMonth, indicatorName); + List dailyDataList = customerStatisticsDataMapper.selectDailyDataByMonth(corpId, yearMonth, indicatorName); + log.info("查询到 {} 条原始数据", dailyDataList != null ? dailyDataList.size() : 0); + if (dailyDataList != null && !dailyDataList.isEmpty()) { + log.info("第一条数据: indicatorName={}, ntfGroup={}, ofhGroup={}", + dailyDataList.get(0).getIndicatorName(), + dailyDataList.get(0).getNtfGroup(), + dailyDataList.get(0).getOfhGroup()); + } + return aggregateDataList(dailyDataList, null, null, yearMonth); + } + + @Override + public List selectAllAggregation(String corpId, String indicatorName) { + List dailyDataList = customerStatisticsDataMapper.selectAllDailyData(corpId, indicatorName); + return aggregateAllData(dailyDataList); + } + + private List aggregateAllData(List dailyDataList) { + if (dailyDataList == null || dailyDataList.isEmpty()) { + return new ArrayList<>(); + } + + Map> groupByIndicator = dailyDataList.stream() + .collect(Collectors.groupingBy(CustomerStatisticsData::getIndicatorName)); + + List allAggregatedList = new ArrayList<>(); + Map indicatorMap = new java.util.HashMap<>(); + Map hiddenFlagMap = new java.util.HashMap<>(); + + for (List indicatorDataList : groupByIndicator.values()) { + CustomerStatisticsData firstData = indicatorDataList.get(0); + String indicatorName = firstData.getIndicatorName(); + + CustomerStatisticsDataVO aggregated = new CustomerStatisticsDataVO(); + aggregated.setIndicatorName(indicatorName); + aggregated.setSortNo(firstData.getSortNo()); + + hiddenFlagMap.put(indicatorName, firstData.getHiddenFlag()); + + String[] groupFields = {"ntfGroup", "ofhGroup", "pswGroup", "wa1Group", "xb1Group", + "yc1Group", "zd1Group", "aaGroup", "acGroup", "adGroup", "aeGroup"}; + + boolean isPercentIndicator = indicatorName.contains("占比") || indicatorName.contains("转化率"); + boolean isDerivedCostIndicator = indicatorName.equals("单条成本(当日)") || indicatorName.equals("成单成本(当日)"); + + if (!isPercentIndicator && !isDerivedCostIndicator) { + for (String field : groupFields) { + BigDecimal sum = BigDecimal.ZERO; + boolean hasValue = false; + for (CustomerStatisticsData data : indicatorDataList) { + String value = getFieldValue(data, field); + if (value != null && !value.trim().isEmpty()) { + String cleanValue = value.replace("%", "").trim(); + if (cleanValue.equals("需手工填写")) { + continue; + } + if (cleanValue.matches("-?\\d+(\\.\\d+)?")) { + try { + sum = sum.add(new BigDecimal(cleanValue)); + hasValue = true; + } catch (NumberFormatException e) { + } + } + } + } + if (hasValue) { + setFieldValue(aggregated, field, sum.setScale(2, RoundingMode.HALF_UP).toString()); + } else if (indicatorName.equals("总成本(当日)")) { + setFieldValue(aggregated, field, "0"); + } + } + } + + allAggregatedList.add(aggregated); + indicatorMap.put(indicatorName, aggregated); + } + + recalculateCostIndicators(indicatorMap); + + List result = new ArrayList<>(); + for (CustomerStatisticsDataVO vo : allAggregatedList) { + Boolean hidden = hiddenFlagMap.get(vo.getIndicatorName()); + if (!Boolean.TRUE.equals(hidden)) { + result.add(vo); + } + } + + result.sort((a, b) -> Integer.compare(a.getSortNo(), b.getSortNo())); + + return result; + } + + private List aggregateDataList(List dailyDataList, Integer year, Integer week, String yearMonth) { + if (dailyDataList == null || dailyDataList.isEmpty()) { + return new ArrayList<>(); + } + + Map> groupByIndicator = dailyDataList.stream() + .collect(Collectors.groupingBy(CustomerStatisticsData::getIndicatorName)); + + List allAggregatedList = new ArrayList<>(); + Map indicatorMap = new java.util.HashMap<>(); + Map hiddenFlagMap = new java.util.HashMap<>(); + + for (List indicatorDataList : groupByIndicator.values()) { + CustomerStatisticsData firstData = indicatorDataList.get(0); + String indicatorName = firstData.getIndicatorName(); + + CustomerStatisticsDataVO aggregated = aggregateIndicatorData(indicatorDataList, year, week, yearMonth); + allAggregatedList.add(aggregated); + indicatorMap.put(indicatorName, aggregated); + hiddenFlagMap.put(indicatorName, firstData.getHiddenFlag()); + } + + recalculateCostIndicators(indicatorMap); + + List result = new ArrayList<>(); + for (CustomerStatisticsDataVO vo : allAggregatedList) { + Boolean hidden = hiddenFlagMap.get(vo.getIndicatorName()); + if (!Boolean.TRUE.equals(hidden)) { + result.add(vo); + } + } + + result.sort((a, b) -> Integer.compare(a.getSortNo(), b.getSortNo())); + + return result; + } + + private CustomerStatisticsDataVO aggregateIndicatorData(List dailyList, Integer year, Integer week, String yearMonth) { + CustomerStatisticsDataVO result = new CustomerStatisticsDataVO(); + String indicatorName = dailyList.get(0).getIndicatorName(); + result.setIndicatorName(indicatorName); + result.setSortNo(dailyList.get(0).getSortNo()); + + log.info("聚合指标: {}, 数据条数: {}", indicatorName, dailyList.size()); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + if (year != null && week != null) { + result.setYearWeek(year + "年第" + week + "周"); + WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 4); + LocalDate startDate = LocalDate.of(year, 1, 1) + .with(weekFields.weekOfYear(), week) + .with(DayOfWeek.MONDAY); + LocalDate endDate = startDate.plusDays(6); + result.setDateRange(startDate.format(formatter) + " 至 " + endDate.format(formatter)); + } else if (yearMonth != null) { + result.setYearMonth(yearMonth.substring(0, 4) + "年" + yearMonth.substring(5) + "月"); + LocalDate startDate = LocalDate.parse(yearMonth + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); + LocalDate endDate = startDate.withDayOfMonth(startDate.lengthOfMonth()); + result.setDateRange(startDate.format(formatter) + " 至 " + endDate.format(formatter)); + } + + String[] groupFields = {"ntfGroup", "ofhGroup", "pswGroup", "wa1Group", "xb1Group", + "yc1Group", "zd1Group", "aaGroup", "acGroup", "adGroup", "aeGroup"}; + + boolean isPercentIndicator = indicatorName.contains("占比") || indicatorName.contains("转化率"); + boolean isDerivedCostIndicator = indicatorName.equals("单条成本(当日)") || indicatorName.equals("成单成本(当日)"); + + if (isPercentIndicator) { + log.info("指标 {} 是占比指标,跳过聚合", indicatorName); + return result; + } + + if (isDerivedCostIndicator) { + log.info("指标 {} 是派生成本指标,跳过聚合", indicatorName); + return result; + } + + for (String field : groupFields) { + BigDecimal sum = BigDecimal.ZERO; + boolean hasValue = false; + for (CustomerStatisticsData data : dailyList) { + String value = getFieldValue(data, field); + if (value != null && !value.trim().isEmpty()) { + String cleanValue = value.replace("%", "").trim(); + if (indicatorName.equals("总成本(当日)") && !cleanValue.equals("需手工填写")) { + log.info("总成本数据 - curDate: {}, 字段: {}, 原始值: {}, 清理后: {}", + data.getCurDate(), field, value, cleanValue); + } + if (cleanValue.equals("需手工填写")) { + continue; + } + if (cleanValue.matches("-?\\d+(\\.\\d+)?")) { + try { + sum = sum.add(new BigDecimal(cleanValue)); + hasValue = true; + } catch (NumberFormatException e) { + } + } + } + } + if (hasValue) { + setFieldValue(result, field, sum.setScale(2, RoundingMode.HALF_UP).toString()); + log.info("指标 {} 字段 {} 最终聚合值: {}", indicatorName, field, sum.setScale(2, RoundingMode.HALF_UP)); + } else if (indicatorName.equals("总成本(当日)")) { + setFieldValue(result, field, "0"); + } + } + + return result; + } + + private void recalculateCostIndicators(Map indicatorMap) { + CustomerStatisticsDataVO totalCostData = indicatorMap.get("总成本(当日)"); + CustomerStatisticsDataVO jinFenShuData = indicatorMap.get("进粉数(当日)"); + CustomerStatisticsDataVO chengDanShuData = indicatorMap.get("成单数(当日)"); + CustomerStatisticsDataVO danTiaoCostData = indicatorMap.get("单条成本(当日)"); + CustomerStatisticsDataVO chengDanCostData = indicatorMap.get("成单成本(当日)"); + CustomerStatisticsDataVO zhuanHuaLvData = indicatorMap.get("转化率(当日)"); + CustomerStatisticsDataVO jiShiDanZhanBiData = indicatorMap.get("及时单占比(当日)"); + CustomerStatisticsDataVO feiJiShiDanZhanBiData = indicatorMap.get("非及时单占比(当日)"); + CustomerStatisticsDataVO jiaZhangZhanBiData = indicatorMap.get("家长占比(当日)"); + CustomerStatisticsDataVO keHuShuXingData = indicatorMap.get("客户属性数量(当日)"); + CustomerStatisticsDataVO xueShengZhanBiData = indicatorMap.get("学生占比(当日)"); + CustomerStatisticsDataVO weiZhiZhanBiData = indicatorMap.get("未知占比(当日)"); + CustomerStatisticsDataVO zhuDongBaoJiaZhanBiData = indicatorMap.get("主动报价占比(当日)"); + CustomerStatisticsDataVO beiDongBaoJiaZhanBiData = indicatorMap.get("被动报价占比(当日)"); + CustomerStatisticsDataVO weiKaiKouBaoJiaZhanBiData = indicatorMap.get("未开口报价占比(当日)"); + CustomerStatisticsDataVO yiShanChuBaoJiaZhanBiData = indicatorMap.get("已删除报价占比(当日)"); + CustomerStatisticsDataVO xiaoXueZhanBiData = indicatorMap.get("小学占比(当日)"); + CustomerStatisticsDataVO chuZhongZhanBiData = indicatorMap.get("初中占比(当日)"); + CustomerStatisticsDataVO gaoZhongZhanBiData = indicatorMap.get("高中占比(当日)"); + CustomerStatisticsDataVO jiaZhangChuDanZhanBiData = indicatorMap.get("家长出单占比(当日)"); + CustomerStatisticsDataVO xueShengChuDanZhanBiData = indicatorMap.get("学生出单占比(当日)"); + + CustomerStatisticsDataVO yiXiangDuShuLiangData = indicatorMap.get("意向度数量(当日)"); + CustomerStatisticsDataVO nianJiShuLiangData = indicatorMap.get("年级数量(当日)"); + + CustomerStatisticsDataVO jiShiDanShuLiangData = indicatorMap.get("及时单数量(当日)"); + CustomerStatisticsDataVO feiJiShiDanShuLiangData = indicatorMap.get("非及时单数量(当日)"); + CustomerStatisticsDataVO jiaZhangShuLiangData = indicatorMap.get("家长数量(当日)"); + CustomerStatisticsDataVO xueShengShuLiangData = indicatorMap.get("学生数量(当日)"); + CustomerStatisticsDataVO weiZhiShuLiangData = indicatorMap.get("未知数量(当日)"); + CustomerStatisticsDataVO zhuDongBaoJiaShuLiangData = indicatorMap.get("主动报价数量(当日)"); + CustomerStatisticsDataVO beiDongBaoJiaShuLiangData = indicatorMap.get("被动报价数量(当日)"); + CustomerStatisticsDataVO weiKaiKouBaoJiaShuLiangData = indicatorMap.get("未开口报价数量(当日)"); + CustomerStatisticsDataVO yiShanChuShuLiangData = indicatorMap.get("已删除数量(当日)"); + CustomerStatisticsDataVO xiaoXueShuLiangData = indicatorMap.get("小学数量(当日)"); + CustomerStatisticsDataVO chuZhongShuLiangData = indicatorMap.get("初中数量(当日)"); + CustomerStatisticsDataVO gaoZhongShuLiangData = indicatorMap.get("高中数量(当日)"); + CustomerStatisticsDataVO jiaZhangChuDanShuLiangData = indicatorMap.get("家长出单数量(当日)"); + CustomerStatisticsDataVO xueShengChuDanShuLiangData = indicatorMap.get("学生出单数量(当日)"); + + String[] groupFields = {"ntfGroup", "ofhGroup", "pswGroup", "wa1Group", "xb1Group", + "yc1Group", "zd1Group", "aaGroup", "acGroup", "adGroup", "aeGroup"}; + + log.info("开始计算派生指标 - totalCostData: {}, jinFenShuData: {}, chengDanShuData: {}, danTiaoCostData: {}, chengDanCostData: {}", + totalCostData != null, jinFenShuData != null, chengDanShuData != null, danTiaoCostData != null, chengDanCostData != null); + + if (totalCostData != null) { + log.info("总成本数据: ntfGroup={}, ofhGroup={}", totalCostData.getNtfGroup(), totalCostData.getOfhGroup()); + } + if (jinFenShuData != null) { + log.info("进粉数数据: ntfGroup={}, ofhGroup={}", jinFenShuData.getNtfGroup(), jinFenShuData.getOfhGroup()); + } + if (chengDanShuData != null) { + log.info("成单数数据: ntfGroup={}, ofhGroup={}", chengDanShuData.getNtfGroup(), chengDanShuData.getOfhGroup()); + } + + if (danTiaoCostData != null && totalCostData != null && jinFenShuData != null) { + for (String field : groupFields) { + String totalCostStr = getFieldValue(totalCostData, field); + String jinFenShuStr = getFieldValue(jinFenShuData, field); + log.debug("计算单条成本 - 字段: {}, 总成本: {}, 进粉数: {}", field, totalCostStr, jinFenShuStr); + if (totalCostStr != null && jinFenShuStr != null) { + try { + BigDecimal totalCost = parseCostValue(totalCostStr); + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + log.info("计算单条成本 - 字段: {}, 总成本: {}, 进粉数: {}", field, totalCost, jinFenShu); + if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal danTiaoCost = totalCost.divide(jinFenShu, 2, RoundingMode.HALF_UP); + setFieldValue(danTiaoCostData, field, danTiaoCost.toString()); + log.info("单条成本计算结果 - 字段: {}, 值: {}", field, danTiaoCost); + } else { + setFieldValue(danTiaoCostData, field, "0"); + } + } catch (NumberFormatException e) { + log.warn("计算单条成本失败 - 字段: {}, 错误: {}", field, e.getMessage()); + } + } + } + } + + if (chengDanCostData != null && totalCostData != null && chengDanShuData != null) { + for (String field : groupFields) { + String totalCostStr = getFieldValue(totalCostData, field); + String chengDanShuStr = getFieldValue(chengDanShuData, field); + log.debug("计算成单成本 - 字段: {}, 总成本: {}, 成单数: {}", field, totalCostStr, chengDanShuStr); + if (totalCostStr != null && chengDanShuStr != null) { + try { + BigDecimal totalCost = parseCostValue(totalCostStr); + BigDecimal chengDanShu = new BigDecimal(chengDanShuStr); + log.info("计算成单成本 - 字段: {}, 总成本: {}, 成单数: {}", field, totalCost, chengDanShu); + if (chengDanShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal chengDanCost = totalCost.divide(chengDanShu, 2, RoundingMode.HALF_UP); + setFieldValue(chengDanCostData, field, chengDanCost.toString()); + log.info("成单成本计算结果 - 字段: {}, 值: {}", field, chengDanCost); + } else { + setFieldValue(chengDanCostData, field, "0"); + } + } catch (NumberFormatException e) { + log.warn("计算成单成本失败 - 字段: {}, 错误: {}", field, e.getMessage()); + } + } + } + } + + if (zhuanHuaLvData != null && chengDanShuData != null && jinFenShuData != null) { + for (String field : groupFields) { + String chengDanShuStr = getFieldValue(chengDanShuData, field); + String jinFenShuStr = getFieldValue(jinFenShuData, field); + if (chengDanShuStr != null && jinFenShuStr != null) { + try { + BigDecimal chengDanShu = new BigDecimal(chengDanShuStr); + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhuanHuaLv = chengDanShu.divide(jinFenShu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(zhuanHuaLvData, field, zhuanHuaLv + "%"); + } else { + setFieldValue(zhuanHuaLvData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (jiShiDanZhanBiData != null && jiShiDanShuLiangData != null && jinFenShuData != null) { + for (String field : groupFields) { + String jiShiDanStr = getFieldValue(jiShiDanShuLiangData, field); + String jinFenShuStr = getFieldValue(jinFenShuData, field); + if (jiShiDanStr != null && jinFenShuStr != null) { + try { + BigDecimal jiShiDan = new BigDecimal(jiShiDanStr); + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = jiShiDan.divide(jinFenShu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(jiShiDanZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(jiShiDanZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (feiJiShiDanZhanBiData != null && feiJiShiDanShuLiangData != null && jinFenShuData != null) { + for (String field : groupFields) { + String feiJiShiDanStr = getFieldValue(feiJiShiDanShuLiangData, field); + String jinFenShuStr = getFieldValue(jinFenShuData, field); + if (feiJiShiDanStr != null && jinFenShuStr != null) { + try { + BigDecimal feiJiShiDan = new BigDecimal(feiJiShiDanStr); + BigDecimal jinFenShu = new BigDecimal(jinFenShuStr); + if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = feiJiShiDan.divide(jinFenShu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(feiJiShiDanZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(feiJiShiDanZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (keHuShuXingData != null && jiaZhangShuLiangData != null && xueShengShuLiangData != null && weiZhiShuLiangData != null) { + for (String field : groupFields) { + String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field); + String xueShengStr = getFieldValue(xueShengShuLiangData, field); + String weiZhiStr = getFieldValue(weiZhiShuLiangData, field); + try { + BigDecimal jiaZhang = jiaZhangStr != null ? new BigDecimal(jiaZhangStr) : BigDecimal.ZERO; + BigDecimal xueSheng = xueShengStr != null ? new BigDecimal(xueShengStr) : BigDecimal.ZERO; + BigDecimal weiZhi = weiZhiStr != null ? new BigDecimal(weiZhiStr) : BigDecimal.ZERO; + BigDecimal total = jiaZhang.add(xueSheng).add(weiZhi); + setFieldValue(keHuShuXingData, field, total.setScale(0, RoundingMode.HALF_UP).toString()); + } catch (NumberFormatException e) { + } + } + } + + if (jiaZhangZhanBiData != null && jiaZhangShuLiangData != null && keHuShuXingData != null) { + for (String field : groupFields) { + String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field); + String keHuShuXingStr = getFieldValue(keHuShuXingData, field); + if (jiaZhangStr != null && keHuShuXingStr != null) { + try { + BigDecimal jiaZhang = new BigDecimal(jiaZhangStr); + BigDecimal keHuShuXing = new BigDecimal(keHuShuXingStr); + if (keHuShuXing.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = jiaZhang.divide(keHuShuXing, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(jiaZhangZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(jiaZhangZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (xueShengZhanBiData != null && xueShengShuLiangData != null && keHuShuXingData != null) { + for (String field : groupFields) { + String xueShengStr = getFieldValue(xueShengShuLiangData, field); + String keHuShuXingStr = getFieldValue(keHuShuXingData, field); + if (xueShengStr != null && keHuShuXingStr != null) { + try { + BigDecimal xueSheng = new BigDecimal(xueShengStr); + BigDecimal keHuShuXing = new BigDecimal(keHuShuXingStr); + if (keHuShuXing.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = xueSheng.divide(keHuShuXing, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(xueShengZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(xueShengZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (weiZhiZhanBiData != null && weiZhiShuLiangData != null && keHuShuXingData != null) { + for (String field : groupFields) { + String weiZhiStr = getFieldValue(weiZhiShuLiangData, field); + String keHuShuXingStr = getFieldValue(keHuShuXingData, field); + if (weiZhiStr != null && keHuShuXingStr != null) { + try { + BigDecimal weiZhi = new BigDecimal(weiZhiStr); + BigDecimal keHuShuXing = new BigDecimal(keHuShuXingStr); + if (keHuShuXing.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = weiZhi.divide(keHuShuXing, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(weiZhiZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(weiZhiZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (zhuDongBaoJiaZhanBiData != null && zhuDongBaoJiaShuLiangData != null && yiXiangDuShuLiangData != null) { + for (String field : groupFields) { + String zhuDongStr = getFieldValue(zhuDongBaoJiaShuLiangData, field); + String yiXiangDuStr = getFieldValue(yiXiangDuShuLiangData, field); + if (zhuDongStr != null && yiXiangDuStr != null) { + try { + BigDecimal zhuDong = new BigDecimal(zhuDongStr); + BigDecimal yiXiangDu = new BigDecimal(yiXiangDuStr); + if (yiXiangDu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = zhuDong.divide(yiXiangDu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(zhuDongBaoJiaZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(zhuDongBaoJiaZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (beiDongBaoJiaZhanBiData != null && beiDongBaoJiaShuLiangData != null && yiXiangDuShuLiangData != null) { + for (String field : groupFields) { + String beiDongStr = getFieldValue(beiDongBaoJiaShuLiangData, field); + String yiXiangDuStr = getFieldValue(yiXiangDuShuLiangData, field); + if (beiDongStr != null && yiXiangDuStr != null) { + try { + BigDecimal beiDong = new BigDecimal(beiDongStr); + BigDecimal yiXiangDu = new BigDecimal(yiXiangDuStr); + if (yiXiangDu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = beiDong.divide(yiXiangDu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(beiDongBaoJiaZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(beiDongBaoJiaZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (weiKaiKouBaoJiaZhanBiData != null && weiKaiKouBaoJiaShuLiangData != null && yiXiangDuShuLiangData != null) { + for (String field : groupFields) { + String weiKaiKouStr = getFieldValue(weiKaiKouBaoJiaShuLiangData, field); + String yiXiangDuStr = getFieldValue(yiXiangDuShuLiangData, field); + if (weiKaiKouStr != null && yiXiangDuStr != null) { + try { + BigDecimal weiKaiKou = new BigDecimal(weiKaiKouStr); + BigDecimal yiXiangDu = new BigDecimal(yiXiangDuStr); + if (yiXiangDu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = weiKaiKou.divide(yiXiangDu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(weiKaiKouBaoJiaZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(weiKaiKouBaoJiaZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (yiShanChuBaoJiaZhanBiData != null && yiShanChuShuLiangData != null && yiXiangDuShuLiangData != null) { + for (String field : groupFields) { + String yiShanChuStr = getFieldValue(yiShanChuShuLiangData, field); + String yiXiangDuStr = getFieldValue(yiXiangDuShuLiangData, field); + if (yiShanChuStr != null && yiXiangDuStr != null) { + try { + BigDecimal yiShanChu = new BigDecimal(yiShanChuStr); + BigDecimal yiXiangDu = new BigDecimal(yiXiangDuStr); + if (yiXiangDu.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = yiShanChu.divide(yiXiangDu, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(yiShanChuBaoJiaZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(yiShanChuBaoJiaZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (xiaoXueZhanBiData != null && xiaoXueShuLiangData != null && nianJiShuLiangData != null) { + for (String field : groupFields) { + String xiaoXueStr = getFieldValue(xiaoXueShuLiangData, field); + String nianJiStr = getFieldValue(nianJiShuLiangData, field); + if (xiaoXueStr != null && nianJiStr != null) { + try { + BigDecimal xiaoXue = new BigDecimal(xiaoXueStr); + BigDecimal nianJi = new BigDecimal(nianJiStr); + if (nianJi.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = xiaoXue.divide(nianJi, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(xiaoXueZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(xiaoXueZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (chuZhongZhanBiData != null && chuZhongShuLiangData != null && nianJiShuLiangData != null) { + for (String field : groupFields) { + String chuZhongStr = getFieldValue(chuZhongShuLiangData, field); + String nianJiStr = getFieldValue(nianJiShuLiangData, field); + if (chuZhongStr != null && nianJiStr != null) { + try { + BigDecimal chuZhong = new BigDecimal(chuZhongStr); + BigDecimal nianJi = new BigDecimal(nianJiStr); + if (nianJi.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = chuZhong.divide(nianJi, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(chuZhongZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(chuZhongZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (gaoZhongZhanBiData != null && gaoZhongShuLiangData != null && nianJiShuLiangData != null) { + for (String field : groupFields) { + String gaoZhongStr = getFieldValue(gaoZhongShuLiangData, field); + String nianJiStr = getFieldValue(nianJiShuLiangData, field); + if (gaoZhongStr != null && nianJiStr != null) { + try { + BigDecimal gaoZhong = new BigDecimal(gaoZhongStr); + BigDecimal nianJi = new BigDecimal(nianJiStr); + if (nianJi.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = gaoZhong.divide(nianJi, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(gaoZhongZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(gaoZhongZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (jiaZhangChuDanZhanBiData != null && jiaZhangChuDanShuLiangData != null && jiaZhangShuLiangData != null) { + for (String field : groupFields) { + String jiaZhangChuDanStr = getFieldValue(jiaZhangChuDanShuLiangData, field); + String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field); + if (jiaZhangChuDanStr != null && jiaZhangStr != null) { + try { + BigDecimal jiaZhangChuDan = new BigDecimal(jiaZhangChuDanStr); + BigDecimal jiaZhang = new BigDecimal(jiaZhangStr); + if (jiaZhang.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = jiaZhangChuDan.divide(jiaZhang, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(jiaZhangChuDanZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(jiaZhangChuDanZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + + if (xueShengChuDanZhanBiData != null && xueShengChuDanShuLiangData != null && xueShengShuLiangData != null) { + for (String field : groupFields) { + String xueShengChuDanStr = getFieldValue(xueShengChuDanShuLiangData, field); + String xueShengStr = getFieldValue(xueShengShuLiangData, field); + if (xueShengChuDanStr != null && xueShengStr != null) { + try { + BigDecimal xueShengChuDan = new BigDecimal(xueShengChuDanStr); + BigDecimal xueSheng = new BigDecimal(xueShengStr); + if (xueSheng.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal zhanBi = xueShengChuDan.divide(xueSheng, 4, RoundingMode.HALF_UP) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP); + setFieldValue(xueShengChuDanZhanBiData, field, zhanBi + "%"); + } else { + setFieldValue(xueShengChuDanZhanBiData, field, "0%"); + } + } catch (NumberFormatException e) { + } + } + } + } + } + + private BigDecimal parseCostValue(String value) { + if (value == null || value.trim().isEmpty()) { + return BigDecimal.ZERO; + } + String cleanValue = value.trim(); + if (cleanValue.equals("需手工填写") || !cleanValue.matches("-?\\d+(\\.\\d+)?")) { + return BigDecimal.ZERO; + } + try { + return new BigDecimal(cleanValue); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } + } + } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/DepartmentStatisticsDataServiceImpl.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/DepartmentStatisticsDataServiceImpl.java index 94141d1..54fb514 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/DepartmentStatisticsDataServiceImpl.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/service/impl/DepartmentStatisticsDataServiceImpl.java @@ -26,11 +26,15 @@ public class DepartmentStatisticsDataServiceImpl implements IDepartmentStatistic * @param startDate 开始日期 * @param endDate 结束日期 * @param departmentPath 部门路径 + * @param dataType 数据类型 * @return 部门统计数据列表 */ @Override - public List selectDepartmentStatisticsDataList(String corpId,Date startDate, Date endDate, String departmentPath) { - return departmentStatisticsDataMapper.selectDepartmentStatisticsDataList(corpId,startDate, endDate, departmentPath); + public List selectDepartmentStatisticsDataList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType) { + if ("week".equals(dataType) || "month".equals(dataType)) { + return departmentStatisticsDataMapper.selectDepartmentStatisticsDataAggregatedList(corpId, startDate, endDate, departmentPath); + } + return departmentStatisticsDataMapper.selectDepartmentStatisticsDataList(corpId, startDate, endDate, departmentPath); } /** @@ -38,11 +42,15 @@ public class DepartmentStatisticsDataServiceImpl implements IDepartmentStatistic * @param startDate 开始日期 * @param endDate 结束日期 * @param departmentPath 部门路径 + * @param dataType 数据类型 * @return 部门统计数据VO列表 */ @Override - public List selectDepartmentStatisticsDataVOList(String corpId,Date startDate, Date endDate, String departmentPath) { - return departmentStatisticsDataMapper.selectDepartmentStatisticsDataVOList(corpId,startDate, endDate, departmentPath); + public List selectDepartmentStatisticsDataVOList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType) { + if ("week".equals(dataType) || "month".equals(dataType)) { + return departmentStatisticsDataMapper.selectDepartmentStatisticsDataVOAggregatedList(corpId, startDate, endDate, departmentPath); + } + return departmentStatisticsDataMapper.selectDepartmentStatisticsDataVOList(corpId, startDate, endDate, departmentPath); } /** @@ -90,7 +98,7 @@ public class DepartmentStatisticsDataServiceImpl implements IDepartmentStatistic } @Override - public Map getSummary(String corpId,Date startDate, Date endDate, String departmentPath) { - return departmentStatisticsDataMapper.getSummary(corpId,startDate,endDate,departmentPath); + public Map getSummary(String corpId, Date startDate, Date endDate, String departmentPath, String dataType) { + return departmentStatisticsDataMapper.getSummary(corpId, startDate, endDate, departmentPath); } } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/CustomerStatisticsDataVO.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/CustomerStatisticsDataVO.java index 5cd2ee2..d5990fc 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/CustomerStatisticsDataVO.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/CustomerStatisticsDataVO.java @@ -1,14 +1,12 @@ package com.ruoyi.excel.wecom.vo; import com.alibaba.excel.annotation.ExcelProperty; -import com.alibaba.excel.annotation.format.DateTimeFormat; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.ruoyi.common.annotation.Excel; import lombok.Getter; import lombok.Setter; import java.io.Serializable; -import java.util.Date; /** * 流量看板数据VO @@ -21,10 +19,9 @@ public class CustomerStatisticsDataVO implements Serializable { private static final long serialVersionUID = 1L; @ExcelProperty("统计日期") - @Excel(name ="日期",dateFormat = "yyyy-MM-dd") - @DateTimeFormat("yyyy-MM-dd") + @Excel(name ="日期") @ColumnWidth(20) - private Date curDate; + private String curDate; @ExcelProperty("重要指标") @Excel(name ="重要指标") @@ -85,4 +82,14 @@ public class CustomerStatisticsDataVO implements Serializable { @Excel(name ="AE组(G1组)") @ColumnWidth(15) private String aeGroup; + + + private String yearWeek; + + private String yearMonth; + + private String dateRange; + + private Integer sortNo; + } diff --git a/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/DepartmentStatisticsDataVO.java b/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/DepartmentStatisticsDataVO.java index bb4ac4e..4f0f351 100644 --- a/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/DepartmentStatisticsDataVO.java +++ b/excel-handle/src/main/java/com/ruoyi/excel/wecom/vo/DepartmentStatisticsDataVO.java @@ -53,4 +53,14 @@ public class DepartmentStatisticsDataVO implements Serializable { @Excel(name ="非及时单占比(当日") @ColumnWidth(15) private BigDecimal dailyNonTimelyOrderRatio; + + @ExcelProperty("家长成单率(当日") + @Excel(name ="家长成单率(当日") + @ColumnWidth(15) + private BigDecimal dailyParentOrderRate; + + @ExcelProperty("学生成单率(当日") + @Excel(name ="学生成单率(当日") + @ColumnWidth(15) + private BigDecimal dailyStudentOrderRate; } diff --git a/excel-handle/src/main/resources/mapper/wecom/CustomerStatisticsDataMapper.xml b/excel-handle/src/main/resources/mapper/wecom/CustomerStatisticsDataMapper.xml index 4eb005d..5e5e645 100644 --- a/excel-handle/src/main/resources/mapper/wecom/CustomerStatisticsDataMapper.xml +++ b/excel-handle/src/main/resources/mapper/wecom/CustomerStatisticsDataMapper.xml @@ -34,7 +34,7 @@ * FROM customer_statistics_data - corp_id = #{corpId} + corp_id = #{corpId} and hidden_flag = false AND cur_date >= #{startDate} @@ -48,4 +48,54 @@ ORDER BY cur_date DESC,sort_no + + + + + + + + + diff --git a/excel-handle/src/main/resources/mapper/wecom/DepartmentStatisticsDataMapper.xml b/excel-handle/src/main/resources/mapper/wecom/DepartmentStatisticsDataMapper.xml index c105775..2778372 100644 --- a/excel-handle/src/main/resources/mapper/wecom/DepartmentStatisticsDataMapper.xml +++ b/excel-handle/src/main/resources/mapper/wecom/DepartmentStatisticsDataMapper.xml @@ -52,6 +52,16 @@ daily_conversion_rate as dailyConversionRate, daily_timely_order_ratio as dailyTimelyOrderRatio, daily_non_timely_order_ratio as dailyNonTimelyOrderRatio, + CASE + WHEN parent_daily_count > 0 + THEN ROUND(parent_daily_order_count * 100.0 / parent_daily_count, 2) + ELSE 0 + END as dailyParentOrderRate, + CASE + WHEN student_daily_count > 0 + THEN ROUND(student_daily_order_count * 100.0 / student_daily_count, 2) + ELSE 0 + END as dailyStudentOrderRate, sort_no as sortNo FROM department_statistics_data @@ -76,9 +86,29 @@ daily_total_accepted as dailyTotalAccepted, daily_total_orders as dailyTotalOrders, daily_conversion_rate as dailyConversionRate, - daily_timely_order_ratio as dailyTimelyOrderRatio, - daily_non_timely_order_ratio as dailyNonTimelyOrderRatio, - sort_no as sortNo + daily_timely_order_ratio as dailyTimelyOrderRatio, + daily_non_timely_order_ratio as dailyNonTimelyOrderRatio, + daily_timely_count as dailyTimelyCount, + daily_non_timely_count as dailyNonTimelyCount, + parent_daily_order_count as parentDailyOrderCount, + parent_daily_count as parentDailyCount, + student_daily_order_count as studentDailyOrderCount, + student_daily_count as studentDailyCount, + CASE + WHEN parent_daily_count > 0 + THEN ROUND(parent_daily_order_count * 100.0 / parent_daily_count, 2) + ELSE 0 + END as dailyParentOrderRate, + CASE + WHEN student_daily_count > 0 + THEN ROUND(student_daily_order_count * 100.0 / student_daily_count, 2) + ELSE 0 + END as dailyStudentOrderRate, + person_flag as personFlag, + corp_name as corpName, + department_name as departmentName, + group_name as groupName, + person_name as personName FROM department_statistics_data corp_id = #{corpId} @@ -100,7 +130,7 @@ sum(ifnull(daily_total_accepted,0)+ifnull(manager_accepted,0)) as totalIns from department_statistics_data - corp_id = #{corpId} + corp_id = #{corpId} and person_flag = true AND stat_date >= #{startDate} @@ -113,4 +143,112 @@ + + + + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/CustomerStatisticsDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/CustomerStatisticsDataController.java index 0823662..988d317 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/CustomerStatisticsDataController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/CustomerStatisticsDataController.java @@ -7,7 +7,6 @@ import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.utils.CorpContextHolder; import com.ruoyi.common.utils.poi.ExcelUtil; -import com.ruoyi.excel.wecom.domain.CustomerStatisticsData; import com.ruoyi.excel.wecom.service.ICustomerStatisticsDataService; import com.ruoyi.excel.wecom.vo.CustomerStatisticsDataVO; import org.springframework.beans.factory.annotation.Autowired; @@ -36,14 +35,30 @@ public class CustomerStatisticsDataController extends BaseController { @PreAuthorize("@ss.hasPermi('wecom:customerStatistics:list')") @GetMapping("/list") public TableDataInfo list( + @RequestParam(value = "dataType", required = false) String dataType, + @RequestParam(value = "year", required = false) Integer year, + @RequestParam(value = "week", required = false) Integer week, + @RequestParam(value = "yearMonth", required = false) String yearMonth, @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate, @RequestParam(value = "indicatorName", required = false) String indicatorName) { String corpId = CorpContextHolder.getCurrentCorpId(); - startPage(); - List list = customerStatisticsDataService.selectCustomerStatisticsDataList(corpId,startDate, endDate, indicatorName); - return getDataTable(list); + List list; + if ("week".equals(dataType)) { + list = customerStatisticsDataService.selectByWeekAggregation(corpId, year, week, indicatorName); + return getDataTable(list); + } else if ("month".equals(dataType)) { + list = customerStatisticsDataService.selectByMonthAggregation(corpId, yearMonth, indicatorName); + return getDataTable(list); + } else if ("all".equals(dataType)) { + list = customerStatisticsDataService.selectAllAggregation(corpId, indicatorName); + return getDataTable(list); + } else { + startPage(); + list = customerStatisticsDataService.selectCustomerStatisticsDataVOList(corpId, startDate, endDate, indicatorName); + return getDataTable(list); + } } /** @@ -53,14 +68,28 @@ public class CustomerStatisticsDataController extends BaseController { @Log(title = "客户统计数据", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, + @RequestParam(value = "dataType", required = false) String dataType, + @RequestParam(value = "year", required = false) Integer year, + @RequestParam(value = "week", required = false) Integer week, + @RequestParam(value = "yearMonth", required = false) String yearMonth, @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate, @RequestParam(value = "indicatorName", required = false) String indicatorName) { String corpId = CorpContextHolder.getCurrentCorpId(); - List list = customerStatisticsDataService.selectCustomerStatisticsDataVOList(corpId,startDate, endDate, indicatorName); + List dataList; + if ("week".equals(dataType)) { + dataList = customerStatisticsDataService.selectByWeekAggregation(corpId, year, week, indicatorName); + } else if ("month".equals(dataType)) { + dataList = customerStatisticsDataService.selectByMonthAggregation(corpId, yearMonth, indicatorName); + } else if ("all".equals(dataType)) { + dataList = customerStatisticsDataService.selectAllAggregation(corpId, indicatorName); + } else { + dataList = customerStatisticsDataService.selectCustomerStatisticsDataVOList(corpId, startDate, endDate, indicatorName); + } + ExcelUtil util = new ExcelUtil<>(CustomerStatisticsDataVO.class); - util.exportExcel(response, list, "流量看板数据"); + util.exportExcel(response, dataList, "流量看板数据"); } /** @@ -80,10 +109,11 @@ public class CustomerStatisticsDataController extends BaseController { @Log(title = "客户统计数据", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestParam(value = "curDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date curDate, - @RequestParam(value = "totalCost")BigDecimal totalCost, + @RequestParam(value = "cost")BigDecimal cost, + @RequestParam(value = "type")String type, @RequestParam(value = "attr")String attr) { String corpId = CorpContextHolder.getCurrentCorpId(); - return toAjax(customerStatisticsDataService.updateCost(corpId,curDate,totalCost,attr)); + return toAjax(customerStatisticsDataService.updateCost(corpId,curDate,cost,type,attr)); } } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/DepartmentStatisticsDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/DepartmentStatisticsDataController.java index 9d4828b..89ce0f3 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/DepartmentStatisticsDataController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/wocom/DepartmentStatisticsDataController.java @@ -18,7 +18,9 @@ import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,11 +42,19 @@ public class DepartmentStatisticsDataController extends BaseController { public TableDataInfo list( @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate, - @RequestParam(value = "departmentPath", required = false) String departmentPath) { + @RequestParam(value = "departmentPath", required = false) String departmentPath, + @RequestParam(value = "dataType", required = false) String dataType, + @RequestParam(value = "year", required = false) Integer year, + @RequestParam(value = "week", required = false) Integer week, + @RequestParam(value = "month", required = false) Integer month) { String corpId = CorpContextHolder.getCurrentCorpId(); + Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate); + startDate = dateRange[0]; + endDate = dateRange[1]; + startPage(); - List list = departmentStatisticsDataService.selectDepartmentStatisticsDataList(corpId,startDate, endDate, departmentPath); + List list = departmentStatisticsDataService.selectDepartmentStatisticsDataList(corpId, startDate, endDate, departmentPath, dataType); return getDataTable(list); } @@ -57,10 +67,18 @@ public class DepartmentStatisticsDataController extends BaseController { public void export(HttpServletResponse response, @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate, - @RequestParam(value = "departmentPath", required = false) String departmentPath) { + @RequestParam(value = "departmentPath", required = false) String departmentPath, + @RequestParam(value = "dataType", required = false) String dataType, + @RequestParam(value = "year", required = false) Integer year, + @RequestParam(value = "week", required = false) Integer week, + @RequestParam(value = "month", required = false) Integer month) { String corpId = CorpContextHolder.getCurrentCorpId(); - List list = departmentStatisticsDataService.selectDepartmentStatisticsDataVOList(corpId,startDate, endDate, departmentPath); + Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate); + startDate = dateRange[0]; + endDate = dateRange[1]; + + List list = departmentStatisticsDataService.selectDepartmentStatisticsDataVOList(corpId, startDate, endDate, departmentPath, dataType); ExcelUtil util = new ExcelUtil<>(DepartmentStatisticsDataVO.class); util.exportExcel(response, list, "销售看板数据"); } @@ -83,10 +101,21 @@ public class DepartmentStatisticsDataController extends BaseController { public AjaxResult summary( @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate, - @RequestParam(value = "departmentPath", required = false) String departmentPath) { + @RequestParam(value = "departmentPath", required = false) String departmentPath, + @RequestParam(value = "dataType", required = false) String dataType, + @RequestParam(value = "year", required = false) Integer year, + @RequestParam(value = "week", required = false) Integer week, + @RequestParam(value = "month", required = false) Integer month) { String corpId = CorpContextHolder.getCurrentCorpId(); - Map map = departmentStatisticsDataService.getSummary(corpId,startDate, endDate, departmentPath); + Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate); + startDate = dateRange[0]; + endDate = dateRange[1]; + + Map map = departmentStatisticsDataService.getSummary(corpId, startDate, endDate, departmentPath, dataType); + if(map == null) { + return success(new HashMap<>()); + } BigDecimal totalOrders = map.get("totalOrders"); BigDecimal totalIns = map.get("totalIns"); if(totalOrders == null) { @@ -104,4 +133,60 @@ public class DepartmentStatisticsDataController extends BaseController { } return success(map); } + + /** + * 根据数据类型计算日期范围 + * @param dataType 数据类型:week/month/all + * @param year 年份 + * @param week 周数(1-53) + * @param month 月份(1-12) + * @param startDate 原始开始日期 + * @param endDate 原始结束日期 + * @return 日期数组[startDate, endDate] + */ + private Date[] calculateDateRange(String dataType, Integer year, Integer week, Integer month, Date startDate, Date endDate) { + if (dataType == null || dataType.isEmpty()) { + return new Date[]{startDate, endDate}; + } + + if ("all".equals(dataType)) { + return new Date[]{null, null}; + } + + Calendar cal = Calendar.getInstance(); + + if ("week".equals(dataType) && year != null && week != null) { + cal.set(Calendar.YEAR, year); + cal.set(Calendar.WEEK_OF_YEAR, week); + cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + startDate = cal.getTime(); + + cal.add(Calendar.DAY_OF_MONTH, 6); + endDate = cal.getTime(); + + return new Date[]{startDate, endDate}; + } + + if ("month".equals(dataType) && year != null && month != null) { + cal.set(Calendar.YEAR, year); + cal.set(Calendar.MONTH, month - 1); + 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); + startDate = cal.getTime(); + + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + endDate = cal.getTime(); + + return new Date[]{startDate, endDate}; + } + + return new Date[]{startDate, endDate}; + } } diff --git a/ruoyi-ui/src/api/wecom/customerStatistics.js b/ruoyi-ui/src/api/wecom/customerStatistics.js index 6a5b593..d59a2dd 100644 --- a/ruoyi-ui/src/api/wecom/customerStatistics.js +++ b/ruoyi-ui/src/api/wecom/customerStatistics.js @@ -27,13 +27,14 @@ export function addCustomerStatistics(data) { } // 修改客户统计数据 -export function updateCustomerStatistics(date,cost,attr) { +export function updateCustomerStatistics(date,cost,attr,type) { return request({ url: '/wecom/customerStatistics', method: 'put', params: { curDate: date, - totalCost: cost, + cost: cost, + type: type, attr: attr } }) diff --git a/ruoyi-ui/src/views/wecom/customerStatistics/index.vue b/ruoyi-ui/src/views/wecom/customerStatistics/index.vue index 6222f0d..bef0001 100644 --- a/ruoyi-ui/src/views/wecom/customerStatistics/index.vue +++ b/ruoyi-ui/src/views/wecom/customerStatistics/index.vue @@ -1,7 +1,52 @@