修改统计数据显示问题

This commit is contained in:
MerCry 2026-03-04 13:20:26 +08:00
parent 61d640da2b
commit a5264f2ab8
21 changed files with 1854 additions and 159 deletions

View File

@ -63,4 +63,7 @@ public class CustomerStatisticsData implements Serializable {
private int sortNo;
//是否是隐藏数据
private Boolean hiddenFlag = false;
}

View File

@ -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;
/**
* 创建时间

View File

@ -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;
}
}

View File

@ -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<String, CustomerStatisticsData> 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);
}

View File

@ -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;
// ========== 成本数据 ==========
/**
* 总成本手工填写

View File

@ -46,5 +46,23 @@ public interface CustomerStatisticsDataMapper extends BaseMapper<CustomerStatist
@Param("indicatorName") String indicatorName
);
List<CustomerStatisticsData> selectDailyDataByWeek(
@Param("corpId") String corpId,
@Param("year") Integer year,
@Param("week") Integer week,
@Param("indicatorName") String indicatorName
);
List<CustomerStatisticsData> selectDailyDataByMonth(
@Param("corpId") String corpId,
@Param("yearMonth") String yearMonth,
@Param("indicatorName") String indicatorName
);
List<CustomerStatisticsData> selectAllDailyData(
@Param("corpId") String corpId,
@Param("indicatorName") String indicatorName
);
}

View File

@ -83,4 +83,32 @@ public interface DepartmentStatisticsDataMapper extends BaseMapper<DepartmentSta
Map<String, BigDecimal> 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<DepartmentStatisticsData> 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<DepartmentStatisticsDataVO> selectDepartmentStatisticsDataVOAggregatedList(
@Param("corpId") String corpId,
@Param("startDate") Date startDate,
@Param("endDate") Date endDate,
@Param("departmentPath") String departmentPath
);
}

View File

@ -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<CustomerStatisticsDataVO> selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName);
List<CustomerStatisticsDataVO> selectByMonthAggregation(String corpId, String yearMonth, String indicatorName);
List<CustomerStatisticsDataVO> selectAllAggregation(String corpId, String indicatorName);
}

View File

@ -18,18 +18,20 @@ public interface IDepartmentStatisticsDataService {
* @param startDate 开始日期
* @param endDate 结束日期
* @param departmentPath 部门路径
* @param dataType 数据类型
* @return 部门统计数据列表
*/
List<DepartmentStatisticsData> selectDepartmentStatisticsDataList(String corpId,Date startDate, Date endDate, String departmentPath);
List<DepartmentStatisticsData> selectDepartmentStatisticsDataList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType);
/**
* 查询部门统计数据VO列表(用于导出)
* @param startDate 开始日期
* @param endDate 结束日期
* @param departmentPath 部门路径
* @param dataType 数据类型
* @return 部门统计数据VO列表
*/
List<DepartmentStatisticsDataVO> selectDepartmentStatisticsDataVOList(String corpId,Date startDate, Date endDate, String departmentPath);
List<DepartmentStatisticsDataVO> selectDepartmentStatisticsDataVOList(String corpId, Date startDate, Date endDate, String departmentPath, String dataType);
/**
* 根据ID查询部门统计数据
@ -59,5 +61,5 @@ public interface IDepartmentStatisticsDataService {
*/
int deleteDepartmentStatisticsDataByIds(Long[] ids);
Map<String, BigDecimal> getSummary(String corpId,Date startDate, Date endDate, String departmentPath);
Map<String, BigDecimal> getSummary(String corpId, Date startDate, Date endDate, String departmentPath, String dataType);
}

View File

@ -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<CustomerStatisticsData> 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<CustomerStatisticsDataVO> selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName) {
List<CustomerStatisticsData> dailyDataList = customerStatisticsDataMapper.selectDailyDataByWeek(corpId, year, week, indicatorName);
return aggregateDataList(dailyDataList, year, week, null);
}
@Override
public List<CustomerStatisticsDataVO> selectByMonthAggregation(String corpId, String yearMonth, String indicatorName) {
log.info("按月聚合查询 - corpId: {}, yearMonth: {}, indicatorName: {}", corpId, yearMonth, indicatorName);
List<CustomerStatisticsData> 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<CustomerStatisticsDataVO> selectAllAggregation(String corpId, String indicatorName) {
List<CustomerStatisticsData> dailyDataList = customerStatisticsDataMapper.selectAllDailyData(corpId, indicatorName);
return aggregateAllData(dailyDataList);
}
private List<CustomerStatisticsDataVO> aggregateAllData(List<CustomerStatisticsData> dailyDataList) {
if (dailyDataList == null || dailyDataList.isEmpty()) {
return new ArrayList<>();
}
Map<String, List<CustomerStatisticsData>> groupByIndicator = dailyDataList.stream()
.collect(Collectors.groupingBy(CustomerStatisticsData::getIndicatorName));
List<CustomerStatisticsDataVO> allAggregatedList = new ArrayList<>();
Map<String, CustomerStatisticsDataVO> indicatorMap = new java.util.HashMap<>();
Map<String, Boolean> hiddenFlagMap = new java.util.HashMap<>();
for (List<CustomerStatisticsData> 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<CustomerStatisticsDataVO> 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<CustomerStatisticsDataVO> aggregateDataList(List<CustomerStatisticsData> dailyDataList, Integer year, Integer week, String yearMonth) {
if (dailyDataList == null || dailyDataList.isEmpty()) {
return new ArrayList<>();
}
Map<String, List<CustomerStatisticsData>> groupByIndicator = dailyDataList.stream()
.collect(Collectors.groupingBy(CustomerStatisticsData::getIndicatorName));
List<CustomerStatisticsDataVO> allAggregatedList = new ArrayList<>();
Map<String, CustomerStatisticsDataVO> indicatorMap = new java.util.HashMap<>();
Map<String, Boolean> hiddenFlagMap = new java.util.HashMap<>();
for (List<CustomerStatisticsData> 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<CustomerStatisticsDataVO> 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<CustomerStatisticsData> 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<String, CustomerStatisticsDataVO> 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;
}
}
}

View File

@ -26,11 +26,15 @@ public class DepartmentStatisticsDataServiceImpl implements IDepartmentStatistic
* @param startDate 开始日期
* @param endDate 结束日期
* @param departmentPath 部门路径
* @param dataType 数据类型
* @return 部门统计数据列表
*/
@Override
public List<DepartmentStatisticsData> selectDepartmentStatisticsDataList(String corpId,Date startDate, Date endDate, String departmentPath) {
return departmentStatisticsDataMapper.selectDepartmentStatisticsDataList(corpId,startDate, endDate, departmentPath);
public List<DepartmentStatisticsData> 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<DepartmentStatisticsDataVO> selectDepartmentStatisticsDataVOList(String corpId,Date startDate, Date endDate, String departmentPath) {
return departmentStatisticsDataMapper.selectDepartmentStatisticsDataVOList(corpId,startDate, endDate, departmentPath);
public List<DepartmentStatisticsDataVO> 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<String, BigDecimal> getSummary(String corpId,Date startDate, Date endDate, String departmentPath) {
return departmentStatisticsDataMapper.getSummary(corpId,startDate,endDate,departmentPath);
public Map<String, BigDecimal> getSummary(String corpId, Date startDate, Date endDate, String departmentPath, String dataType) {
return departmentStatisticsDataMapper.getSummary(corpId, startDate, endDate, departmentPath);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -34,7 +34,7 @@
*
FROM customer_statistics_data
<where>
corp_id = #{corpId}
corp_id = #{corpId} and hidden_flag = false
<if test="startDate != null">
AND cur_date &gt;= #{startDate}
</if>
@ -48,4 +48,54 @@
ORDER BY cur_date DESC,sort_no
</select>
<select id="selectDailyDataByWeek" resultType="com.ruoyi.excel.wecom.domain.CustomerStatisticsData">
SELECT * FROM customer_statistics_data
<where>
corp_id = #{corpId}
AND YEAR(cur_date) = #{year}
AND WEEK(cur_date, 1) = #{week}
<if test="indicatorName != null and indicatorName != ''">
AND indicator_name LIKE CONCAT('%', #{indicatorName}, '%')
</if>
</where>
ORDER BY cur_date, sort_no
</select>
<select id="selectDailyDataByMonth" resultType="com.ruoyi.excel.wecom.domain.CustomerStatisticsData">
SELECT * FROM customer_statistics_data
<where>
corp_id = #{corpId}
AND DATE_FORMAT(cur_date, '%Y-%m') = #{yearMonth}
<if test="indicatorName != null and indicatorName != ''">
AND indicator_name LIKE CONCAT('%', #{indicatorName}, '%')
</if>
</where>
ORDER BY cur_date, sort_no
</select>
<!-- 调试查询检查indicator_name是否有隐藏字符 -->
<select id="selectDebugIndicatorName" resultType="java.util.Map">
SELECT id, cur_date,
CONCAT('[', indicator_name, ']') as name_with_brackets,
LENGTH(indicator_name) as name_length
FROM customer_statistics_data
WHERE corp_id = #{corpId}
AND DATE_FORMAT(cur_date, '%Y-%m') = #{yearMonth}
<if test="indicatorName != null and indicatorName != ''">
AND indicator_name LIKE CONCAT('%', #{indicatorName}, '%')
</if>
ORDER BY cur_date
</select>
<select id="selectAllDailyData" resultType="com.ruoyi.excel.wecom.domain.CustomerStatisticsData">
SELECT * FROM customer_statistics_data
<where>
corp_id = #{corpId}
<if test="indicatorName != null and indicatorName != ''">
AND indicator_name LIKE CONCAT('%', #{indicatorName}, '%')
</if>
</where>
ORDER BY cur_date, sort_no
</select>
</mapper>

View File

@ -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
<where>
@ -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
<where>
corp_id = #{corpId}
@ -100,7 +130,7 @@
sum(ifnull(daily_total_accepted,0)+ifnull(manager_accepted,0)) as totalIns
from department_statistics_data
<where>
corp_id = #{corpId}
corp_id = #{corpId} and person_flag = true
<if test="startDate != null">
AND stat_date &gt;= #{startDate}
</if>
@ -113,4 +143,112 @@
</where>
</select>
<select id="selectDepartmentStatisticsDataAggregatedList"
resultType="com.ruoyi.excel.wecom.domain.DepartmentStatisticsData">
SELECT
department_path as departmentPath,
SUM(daily_total_accepted) as dailyTotalAccepted,
SUM(daily_total_orders) as dailyTotalOrders,
CASE
WHEN SUM(daily_total_accepted) > 0
THEN ROUND(SUM(daily_total_orders) * 100.0 / SUM(daily_total_accepted), 2)
ELSE 0
END as dailyConversionRate,
CASE
WHEN SUM(daily_total_orders) > 0
THEN ROUND(SUM(IFNULL(daily_timely_count, 0)) * 100.0 / SUM(daily_total_orders), 2)
ELSE 0
END as dailyTimelyOrderRatio,
CASE
WHEN SUM(daily_total_orders) > 0
THEN ROUND(SUM(IFNULL(daily_non_timely_count, 0)) * 100.0 / SUM(daily_total_orders), 2)
ELSE 0
END as dailyNonTimelyOrderRatio,
SUM(IFNULL(daily_timely_count, 0)) as dailyTimelyCount,
SUM(IFNULL(daily_non_timely_count, 0)) as dailyNonTimelyCount,
SUM(IFNULL(parent_daily_order_count, 0)) as parentDailyOrderCount,
SUM(IFNULL(parent_daily_count, 0)) as parentDailyCount,
SUM(IFNULL(student_daily_order_count, 0)) as studentDailyOrderCount,
SUM(IFNULL(student_daily_count, 0)) as studentDailyCount,
CASE
WHEN SUM(IFNULL(parent_daily_count, 0)) > 0
THEN ROUND(SUM(IFNULL(parent_daily_order_count, 0)) * 100.0 / SUM(IFNULL(parent_daily_count, 0)), 2)
ELSE 0
END as dailyParentOrderRate,
CASE
WHEN SUM(IFNULL(student_daily_count, 0)) > 0
THEN ROUND(SUM(IFNULL(student_daily_order_count, 0)) * 100.0 / SUM(IFNULL(student_daily_count, 0)), 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,
MIN(sort_no) as sortNo
FROM department_statistics_data
<where>
corp_id = #{corpId}
<if test="startDate != null">
AND stat_date &gt;= #{startDate}
</if>
<if test="endDate != null">
AND stat_date &lt;= #{endDate}
</if>
<if test="departmentPath != null and departmentPath != ''">
AND department_path LIKE CONCAT(#{departmentPath}, '%')
</if>
</where>
GROUP BY department_path, person_flag, corp_name, department_name, group_name, person_name
ORDER BY department_path, sortNo
</select>
<select id="selectDepartmentStatisticsDataVOAggregatedList" resultType="com.ruoyi.excel.wecom.vo.DepartmentStatisticsDataVO">
SELECT
department_path as departmentPath,
SUM(daily_total_accepted) as dailyTotalAccepted,
SUM(daily_total_orders) as dailyTotalOrders,
CASE
WHEN SUM(daily_total_accepted) > 0
THEN ROUND(SUM(daily_total_orders) * 100.0 / SUM(daily_total_accepted), 2)
ELSE 0
END as dailyConversionRate,
CASE
WHEN SUM(daily_total_orders) > 0
THEN ROUND(SUM(IFNULL(daily_timely_count, 0)) * 100.0 / SUM(daily_total_orders), 2)
ELSE 0
END as dailyTimelyOrderRatio,
CASE
WHEN SUM(daily_total_orders) > 0
THEN ROUND(SUM(IFNULL(daily_non_timely_count, 0)) * 100.0 / SUM(daily_total_orders), 2)
ELSE 0
END as dailyNonTimelyOrderRatio,
CASE
WHEN SUM(IFNULL(parent_daily_count, 0)) > 0
THEN ROUND(SUM(IFNULL(parent_daily_order_count, 0)) * 100.0 / SUM(IFNULL(parent_daily_count, 0)), 2)
ELSE 0
END as dailyParentOrderRate,
CASE
WHEN SUM(IFNULL(student_daily_count, 0)) > 0
THEN ROUND(SUM(IFNULL(student_daily_order_count, 0)) * 100.0 / SUM(IFNULL(student_daily_count, 0)), 2)
ELSE 0
END as dailyStudentOrderRate,
MIN(sort_no) as sortNo
FROM department_statistics_data
<where>
corp_id = #{corpId}
<if test="startDate != null">
AND stat_date &gt;= #{startDate}
</if>
<if test="endDate != null">
AND stat_date &lt;= #{endDate}
</if>
<if test="departmentPath != null and departmentPath != ''">
AND department_path LIKE CONCAT(#{departmentPath}, '%')
</if>
</where>
GROUP BY department_path
ORDER BY department_path, sortNo
</select>
</mapper>

View File

@ -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<CustomerStatisticsData> list = customerStatisticsDataService.selectCustomerStatisticsDataList(corpId,startDate, endDate, indicatorName);
return getDataTable(list);
List<CustomerStatisticsDataVO> 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<CustomerStatisticsDataVO> list = customerStatisticsDataService.selectCustomerStatisticsDataVOList(corpId,startDate, endDate, indicatorName);
List<CustomerStatisticsDataVO> 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<CustomerStatisticsDataVO> 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));
}
}

View File

@ -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<DepartmentStatisticsData> list = departmentStatisticsDataService.selectDepartmentStatisticsDataList(corpId,startDate, endDate, departmentPath);
List<DepartmentStatisticsData> 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<DepartmentStatisticsDataVO> list = departmentStatisticsDataService.selectDepartmentStatisticsDataVOList(corpId,startDate, endDate, departmentPath);
Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate);
startDate = dateRange[0];
endDate = dateRange[1];
List<DepartmentStatisticsDataVO> list = departmentStatisticsDataService.selectDepartmentStatisticsDataVOList(corpId, startDate, endDate, departmentPath, dataType);
ExcelUtil<DepartmentStatisticsDataVO> 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<String, BigDecimal> map = departmentStatisticsDataService.getSummary(corpId,startDate, endDate, departmentPath);
Date[] dateRange = calculateDateRange(dataType, year, week, month, startDate, endDate);
startDate = dateRange[0];
endDate = dateRange[1];
Map<String, BigDecimal> 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};
}
}

View File

@ -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
}
})

View File

@ -1,7 +1,52 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="开始日期" prop="startDate">
<el-form-item label="数据类型" prop="dataType">
<el-select
v-model="queryParams.dataType"
placeholder="请选择数据类型"
clearable
@change="handleDataTypeChange"
>
<el-option label="按天查询" value="day" />
<el-option label="自然周叠加数据" value="week" />
<el-option label="自然月叠加数据" value="month" />
<el-option label="所有数据" value="all" />
</el-select>
</el-form-item>
<el-form-item v-if="queryParams.dataType === 'week'" label="年份" prop="year">
<el-date-picker
v-model="queryParams.year"
type="year"
placeholder="选择年份"
value-format="yyyy"
style="width: 120px"
/>
</el-form-item>
<el-form-item v-if="queryParams.dataType === 'week'" label="周数" prop="week">
<el-select
v-model="queryParams.week"
placeholder="选择周数"
clearable
style="width: 120px"
>
<el-option v-for="i in 53" :key="i" :label="'第' + i + '周'" :value="i" />
</el-select>
<span v-if="weekDateRange" style="margin-left: 10px; color: #909399; font-size: 12px;">{{ weekDateRange }}</span>
</el-form-item>
<el-form-item v-if="queryParams.dataType === 'month'" label="年月" prop="yearMonth">
<el-date-picker
v-model="queryParams.yearMonth"
type="month"
placeholder="选择年月"
value-format="yyyy-MM"
/>
</el-form-item>
<el-form-item v-if="queryParams.dataType === 'day' || !queryParams.dataType" label="开始日期" prop="startDate">
<el-date-picker
v-model="queryParams.startDate"
type="date"
@ -10,7 +55,7 @@
clearable
/>
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-form-item v-if="queryParams.dataType === 'day' || !queryParams.dataType" label="结束日期" prop="endDate">
<el-date-picker
v-model="queryParams.endDate"
type="date"
@ -49,22 +94,32 @@
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="日期" align="center" prop="curDate" width="120">
<el-table-column label="日期" align="center" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.curDate, '{y}-{m}-{d}') }}</span>
<div v-if="queryParams.dataType === 'week'">
<div>{{ scope.row.yearWeek }}</div>
<div style="color: #909399; font-size: 12px;">{{ scope.row.dateRange }}</div>
</div>
<div v-else-if="queryParams.dataType === 'month'">
<div>{{ scope.row.yearMonth }}</div>
<div style="color: #909399; font-size: 12px;">{{ scope.row.dateRange }}</div>
</div>
<div v-else-if="queryParams.dataType === 'all'">
<span>全部数据</span>
</div>
<span v-else>{{ parseTime(scope.row.curDate, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="重要指标" align="center" prop="indicatorName" width="180" :show-overflow-tooltip="true" />
<el-table-column label="N组(投放)" align="center" prop="ntfGroup" width="100">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.ntfGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.ntfGroup,'ntfGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.ntfGroup,'ntfGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.ntfGroup,'ntfGroup')"
/>
<span v-else>{{ scope.row.ntfGroup }}</span>
</template>
@ -72,13 +127,12 @@
<el-table-column label="O组(公司孵化)" align="center" prop="ofhGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.ofhGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.ofhGroup,'ofhGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.ofhGroup,'ofhGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.ofhGroup,'ofhGroup')"
/>
<span v-else>{{ scope.row.ofhGroup }}</span>
</template>
@ -86,13 +140,12 @@
<el-table-column label="P组(商务)" align="center" prop="pswGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.pswGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.pswGroup,'pswGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.pswGroup,'pswGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.pswGroup,'pswGroup')"
/>
<span v-else>{{ scope.row.pswGroup }}</span>
</template>
@ -100,13 +153,12 @@
<el-table-column label="W组(A1组)" align="center" prop="wa1Group" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.wa1Group"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.wa1Group,'wa1Group')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.wa1Group,'wa1Group')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.wa1Group,'wa1Group')"
/>
<span v-else>{{ scope.row.wa1Group }}</span>
</template>
@ -114,13 +166,12 @@
<el-table-column label="X组(B1组)" align="center" prop="xb1Group" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.xb1Group"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.xb1Group,'xb1Group')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.xb1Group,'xb1Group')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.xb1Group,'xb1Group')"
/>
<span v-else>{{ scope.row.xb1Group }}</span>
</template>
@ -128,13 +179,12 @@
<el-table-column label="Y组(C1组)" align="center" prop="yc1Group" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.yc1Group"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.yc1Group,'yc1Group')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.yc1Group,'yc1Group')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.yc1Group,'yc1Group')"
/>
<span v-else>{{ scope.row.yc1Group }}</span>
</template>
@ -142,13 +192,12 @@
<el-table-column label="Z组(D1组)" align="center" prop="zd1Group" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.zd1Group"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.zd1Group,'zd1Group')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.zd1Group,'zd1Group')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.zd1Group,'zd1Group')"
/>
<span v-else>{{ scope.row.zd1Group }}</span>
</template>
@ -156,13 +205,12 @@
<el-table-column label="AA组(E1组)" align="center" prop="aaGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.aaGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.aaGroup,'aaGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.aaGroup,'aaGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.aaGroup,'aaGroup')"
/>
<span v-else>{{ scope.row.aaGroup }}</span>
</template>
@ -170,13 +218,12 @@
<el-table-column label="AC组(自然流)" align="center" prop="acGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.acGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.acGroup,'acGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.acGroup,'acGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.acGroup,'acGroup')"
/>
<span v-else>{{ scope.row.acGroup }}</span>
</template>
@ -184,13 +231,12 @@
<el-table-column label="AD组(F1组)" align="center" prop="adGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.adGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.adGroup,'adGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.adGroup,'adGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.adGroup,'adGroup')"
/>
<span v-else>{{ scope.row.adGroup }}</span>
</template>
@ -198,13 +244,12 @@
<el-table-column label="AE组(G1组)" align="center" prop="aeGroup" width="120">
<template slot-scope="scope">
<el-input-number
v-if="scope.row.indicatorName === '总成本(当日)'"
v-if="canEditCost(scope.row)"
v-model="scope.row.aeGroup"
size="mini"
:controls="false"
style="width: 100%"
@blur="handleUpdateCost(scope.row.curDate,scope.row.aeGroup,'aeGroup')"
@keyup.enter.native="handleUpdateCost(scope.row.curDate,scope.row.aeGroup,'aeGroup')"
@keyup.enter.native="handleUpdateCost(scope.row,scope.row.aeGroup,'aeGroup')"
/>
<span v-else>{{ scope.row.aeGroup }}</span>
</template>
@ -228,35 +273,70 @@ export default {
name: "CustomerStatistics",
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
dataList: [],
//
weekDateRange: '',
queryParams: {
pageNum: 1,
pageSize: 10,
dataType: undefined,
year: undefined,
week: undefined,
yearMonth: undefined,
startDate: undefined,
endDate: undefined,
indicatorName: undefined
}
}
},
watch: {
'queryParams.week'(newVal) {
this.updateWeekDateRange()
},
'queryParams.year'(newVal) {
this.updateWeekDateRange()
}
},
created() {
this.getList()
},
methods: {
/** 查询客户统计数据列表 */
updateWeekDateRange() {
if (this.queryParams.year && this.queryParams.week) {
const year = parseInt(this.queryParams.year)
const week = this.queryParams.week
const startDate = this.getWeekStartDate(year, week)
const endDate = new Date(startDate)
endDate.setDate(endDate.getDate() + 6)
this.weekDateRange = this.formatDate(startDate) + ' 至 ' + this.formatDate(endDate)
} else {
this.weekDateRange = ''
}
},
getWeekStartDate(year, week) {
const date = new Date(year, 0, 1)
const dayOfWeek = date.getDay()
const daysToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek
date.setDate(date.getDate() + daysToMonday)
date.setDate(date.getDate() + (week - 1) * 7)
return date
},
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return year + '-' + month + '-' + day
},
canEditCost(row) {
const isCostIndicator = row.indicatorName === '总成本(当日)' || row.indicatorName === '单条成本(当日)'
const isDayMode = this.queryParams.dataType === 'day' || !this.queryParams.dataType
return isCostIndicator && isDayMode
},
getList() {
this.loading = true
console.log('=== 开始请求数据 ===')
@ -274,7 +354,7 @@ export default {
console.log('第一条数据的 aaGroup:', response.rows[0].aaGroup)
console.log('第一条数据的 nGroup:', response.rows[0].nGroup)
} else {
console.warn('⚠️ rows 为空或不存在')
console.warn('rows 为空或不存在')
}
this.dataList = response.rows
@ -290,23 +370,28 @@ export default {
this.loading = false
})
},
/** 搜索按钮操作 */
handleDataTypeChange() {
this.queryParams.year = undefined
this.queryParams.week = undefined
this.queryParams.yearMonth = undefined
this.queryParams.startDate = undefined
this.queryParams.endDate = undefined
this.weekDateRange = ''
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.handleDataTypeChange()
this.handleQuery()
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
/** 导出按钮操作 */
handleExport() {
const formattedDate = this.parseTime(new Date(), '{y}-{m}-{d}');
@ -314,9 +399,7 @@ export default {
...this.queryParams
}, `流量看板数据_${formattedDate}.xlsx`)
},
/** 处理成本更新 */
handleUpdateCost(date, cost, attr) {
// cost
handleUpdateCost(row, cost, attr) {
if (cost === '' || cost === null || cost === undefined) {
this.$modal.msgWarning("请输入成本金额");
return;
@ -328,18 +411,30 @@ export default {
return;
}
// :
if (numCost < 0) {
this.$modal.msgWarning("成本金额不能为负数");
return;
}
const formattedDate = this.parseTime(date, '{y}-{m}-{d}');
updateCustomerStatistics(formattedDate, numCost, attr).then(response => {
this.$modal.msgSuccess("修改成功");
this.getList();
const indicatorName = row.indicatorName;
const type = indicatorName === '单条成本(当日)' ? 'single' : 'total';
const costTypeName = indicatorName === '单条成本(当日)' ? '单条成本' : '总成本';
let formattedDate = row.curDate;
if (row.curDate instanceof Date) {
formattedDate = this.parseTime(row.curDate, '{y}-{m}-{d}');
} else if (typeof row.curDate === 'string' && row.curDate.length > 10) {
formattedDate = row.curDate.substring(0, 10);
}
this.$modal.confirm(`确认修改${formattedDate}${costTypeName}${numCost}`).then(() => {
updateCustomerStatistics(formattedDate, numCost, attr, type).then(response => {
this.$modal.msgSuccess("修改成功");
this.getList();
}).catch(() => {
this.getList();
});
}).catch(() => {
this.getList(); //
this.getList();
});
}
}

View File

@ -1,7 +1,19 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="开始日期" prop="startDate">
<el-form-item label="数据类型" prop="dataType">
<el-select
v-model="queryParams.dataType"
placeholder="请选择数据类型"
@change="handleDataTypeChange"
>
<el-option label="按天查询" value="day" />
<el-option label="自然周叠加数据" value="week" />
<el-option label="自然月叠加数据" value="month" />
<el-option label="所有数据" value="all" />
</el-select>
</el-form-item>
<el-form-item label="开始日期" prop="startDate" v-if="showDateRange">
<el-date-picker
v-model="queryParams.startDate"
type="date"
@ -10,7 +22,7 @@
clearable
/>
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-form-item label="结束日期" prop="endDate" v-if="showDateRange">
<el-date-picker
v-model="queryParams.endDate"
type="date"
@ -19,6 +31,50 @@
clearable
/>
</el-form-item>
<el-form-item label="年份" prop="year" v-if="showYearSelect">
<el-select
v-model="queryParams.year"
placeholder="选择年份"
clearable
@change="updateWeekDateRange"
>
<el-option
v-for="item in yearOptions"
:key="item"
:label="item + '年'"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="周数" prop="week" v-if="showWeekSelect">
<el-select
v-model="queryParams.week"
placeholder="选择周数"
clearable
@change="updateWeekDateRange"
>
<el-option
v-for="item in weekOptions"
:key="item"
:label="item + '周'"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item v-if="showWeekDateRange && weekDateRange">
<el-tag type="info" size="medium">
{{ weekDateRange }}
</el-tag>
</el-form-item>
<el-form-item label="年月" prop="yearMonth" v-if="showMonthSelect">
<el-date-picker
v-model="queryParams.yearMonth"
type="month"
placeholder="选择年月"
value-format="yyyy-MM"
@change="handleYearMonthChange"
/>
</el-form-item>
<el-form-item label="部门路径" prop="departmentPath">
<el-input
v-model="queryParams.departmentPath"
@ -70,7 +126,7 @@
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="日期" align="center" prop="statDate" width="120">
<el-table-column label="日期" align="center" prop="statDate" width="120" v-if="showStatDate">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.statDate, '{y}-{m}-{d}') }}</span>
</template>
@ -81,6 +137,8 @@
<el-table-column label="当日转化率" align="center" prop="dailyConversionRate" width="180" :show-overflow-tooltip="true" />
<el-table-column label="当日及时单占比" align="center" prop="dailyTimelyOrderRatio" width="180" :show-overflow-tooltip="true" />
<el-table-column label="当日非及时单占比" align="center" prop="dailyNonTimelyOrderRatio" width="180" :show-overflow-tooltip="true" />
<el-table-column label="家长成单率" align="center" prop="dailyParentOrderRate" width="180" :show-overflow-tooltip="true" />
<el-table-column label="学生成单率" align="center" prop="dailyStudentOrderRate" width="180" :show-overflow-tooltip="true" />
</el-table>
<pagination
@ -126,23 +184,140 @@ export default {
pageSize: 10,
startDate: undefined,
endDate: undefined,
departmentPath: undefined
}
departmentPath: undefined,
dataType: 'day',
year: undefined,
week: undefined,
month: undefined,
yearMonth: undefined
},
//
yearOptions: [],
// (1-53)
weekOptions: [],
//
weekDateRange: ''
}
},
computed: {
//
showDateRange() {
return this.queryParams.dataType === 'day'
},
//
showYearSelect() {
return this.queryParams.dataType === 'week'
},
//
showWeekSelect() {
return this.queryParams.dataType === 'week'
},
//
showWeekDateRange() {
return this.queryParams.dataType === 'week'
},
//
showMonthSelect() {
return this.queryParams.dataType === 'month'
},
//
showStatDate() {
return this.queryParams.dataType === 'day'
}
},
created() {
this.initYearOptions()
this.initWeekOptions()
this.getList()
},
methods: {
//
initYearOptions() {
const currentYear = new Date().getFullYear()
const years = []
for (let i = currentYear; i >= currentYear - 5; i--) {
years.push(i)
}
this.yearOptions = years
},
// (1-53)
initWeekOptions() {
const weeks = []
for (let i = 1; i <= 53; i++) {
weeks.push(i)
}
this.weekOptions = weeks
},
//
handleDataTypeChange(val) {
this.queryParams.year = undefined
this.queryParams.week = undefined
this.queryParams.month = undefined
this.queryParams.yearMonth = undefined
this.queryParams.startDate = undefined
this.queryParams.endDate = undefined
this.weekDateRange = ''
},
//
updateWeekDateRange() {
if (this.queryParams.year && this.queryParams.week) {
const dateRange = this.getWeekDateRange(this.queryParams.year, this.queryParams.week)
this.weekDateRange = `${dateRange.startDate}${dateRange.endDate}`
} else {
this.weekDateRange = ''
}
},
//
getWeekDateRange(year, week) {
const firstDayOfYear = new Date(year, 0, 1)
const daysOffset = (week - 1) * 7
let firstDayOfWeek = new Date(firstDayOfYear)
firstDayOfWeek.setDate(firstDayOfYear.getDate() + daysOffset)
const dayOfWeek = firstDayOfWeek.getDay()
const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek
firstDayOfWeek.setDate(firstDayOfWeek.getDate() + diff)
const lastDayOfWeek = new Date(firstDayOfWeek)
lastDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)
const formatDate = (date) => {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
return {
startDate: formatDate(firstDayOfWeek),
endDate: formatDate(lastDayOfWeek)
}
},
//
handleYearMonthChange(val) {
if (val) {
const [year, month] = val.split('-')
this.queryParams.year = parseInt(year)
this.queryParams.month = parseInt(month)
} else {
this.queryParams.year = undefined
this.queryParams.month = undefined
}
},
/** 查询部门统计数据列表 */
getList() {
this.loading = true
listDepartmentStatistics(this.queryParams).then(response => {
const params = { ...this.queryParams }
if (params.dataType === 'day') {
params.dataType = undefined
}
if (params.dataType === 'month' && params.yearMonth) {
const [year, month] = params.yearMonth.split('-')
params.year = parseInt(year)
params.month = parseInt(month)
}
listDepartmentStatistics(params).then(response => {
this.dataList = response.rows
this.total = response.total
this.loading = false
})
getDepartmentStatisticsSummary(this.queryParams).then(response => {
getDepartmentStatisticsSummary(params).then(response => {
this.summaryData = response.data
})
},
@ -154,6 +329,12 @@ export default {
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
this.queryParams.dataType = 'day'
this.queryParams.year = undefined
this.queryParams.week = undefined
this.queryParams.month = undefined
this.queryParams.yearMonth = undefined
this.weekDateRange = ''
this.handleQuery()
},
//
@ -166,8 +347,17 @@ export default {
/** 导出按钮操作 */
handleExport() {
const formattedDate = this.parseTime(new Date(), '{y}-{m}-{d}');
const params = { ...this.queryParams }
if (params.dataType === 'day') {
params.dataType = undefined
}
if (params.dataType === 'month' && params.yearMonth) {
const [year, month] = params.yearMonth.split('-')
params.year = parseInt(year)
params.month = parseInt(month)
}
this.download('/wecom/departmentStatistics/export', {
...this.queryParams
...params
}, `销售看板数据_${formattedDate}.xlsx`)
}
}

View File

@ -0,0 +1,30 @@
-- 部门统计表新增字段
-- 执行时间: 2026-03-02
-- 新增家长出单率(当日)
ALTER TABLE department_statistics_data
ADD COLUMN daily_parent_order_rate DECIMAL(10,2) DEFAULT NULL COMMENT '家长出单率(当日)';
-- 新增学生出单率(当日)
ALTER TABLE department_statistics_data
ADD COLUMN daily_student_order_rate DECIMAL(10,2) DEFAULT NULL COMMENT '学生出单率(当日)';
-- 新增是否个人标识
ALTER TABLE department_statistics_data
ADD COLUMN person_flag TINYINT(1) DEFAULT 0 COMMENT '是否个人标识0-否1-是)';
-- 新增公司名称
ALTER TABLE department_statistics_data
ADD COLUMN corp_name VARCHAR(100) DEFAULT NULL COMMENT '公司名称';
-- 新增部门名称
ALTER TABLE department_statistics_data
ADD COLUMN department_name VARCHAR(100) DEFAULT NULL COMMENT '部门名称';
-- 新增组名称
ALTER TABLE department_statistics_data
ADD COLUMN group_name VARCHAR(100) DEFAULT NULL COMMENT '组名称';
-- 新增个人名称
ALTER TABLE department_statistics_data
ADD COLUMN person_name VARCHAR(100) DEFAULT NULL COMMENT '个人名称';