增加新界面用于数据展示
This commit is contained in:
parent
143a9e90be
commit
6c16ea1661
|
|
@ -0,0 +1,191 @@
|
||||||
|
-- ==========================================
|
||||||
|
-- 流量看板V2 数据表和菜单配置
|
||||||
|
-- ==========================================
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- 1. 数据表创建
|
||||||
|
-- ----------------------------
|
||||||
|
|
||||||
|
-- 客户统计数据表V2(支持标签级成本,行列转换存储)
|
||||||
|
DROP TABLE IF EXISTS `customer_statistics_data_v2`;
|
||||||
|
|
||||||
|
CREATE TABLE `customer_statistics_data_v2` (
|
||||||
|
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`corp_id` VARCHAR(100) NOT NULL COMMENT '企业ID',
|
||||||
|
`cur_date` DATE NOT NULL COMMENT '统计日期',
|
||||||
|
|
||||||
|
-- 维度信息
|
||||||
|
`group_name` VARCHAR(50) NOT NULL COMMENT '组名(N组、O组等)',
|
||||||
|
`tag_name` VARCHAR(100) DEFAULT NULL COMMENT '标签名(NULL表示组级汇总)',
|
||||||
|
`tag_group_id` VARCHAR(100) DEFAULT NULL COMMENT '标签组ID(关联wecom_tag_group)',
|
||||||
|
`tag_id` VARCHAR(100) DEFAULT NULL COMMENT '标签ID(关联wecom_tag)',
|
||||||
|
|
||||||
|
-- 层级关系
|
||||||
|
`data_level` TINYINT DEFAULT 1 COMMENT '数据级别:1-组级汇总,2-标签级明细',
|
||||||
|
`parent_id` BIGINT(20) DEFAULT NULL COMMENT '父记录ID(标签级数据对应组级记录的ID)',
|
||||||
|
|
||||||
|
-- 成本数据(支持标签级成本)
|
||||||
|
`total_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '总成本(手工录入)',
|
||||||
|
`single_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '单条成本(计算得出)',
|
||||||
|
`order_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '成单成本(计算得出)',
|
||||||
|
`cost_input_type` VARCHAR(20) DEFAULT NULL COMMENT '成本录入类型:total-总成本,single-单条成本',
|
||||||
|
|
||||||
|
-- 数量指标
|
||||||
|
`order_count` INT DEFAULT 0 COMMENT '成单数',
|
||||||
|
`customer_count` INT DEFAULT 0 COMMENT '进粉数',
|
||||||
|
`timely_order_count` INT DEFAULT 0 COMMENT '及时单数',
|
||||||
|
`non_timely_order_count` INT DEFAULT 0 COMMENT '非及时单数',
|
||||||
|
|
||||||
|
-- 比率指标
|
||||||
|
`conversion_rate` VARCHAR(10) DEFAULT '0%' COMMENT '转化率',
|
||||||
|
`timely_rate` VARCHAR(10) DEFAULT '0%' COMMENT '及时单占比',
|
||||||
|
`non_timely_rate` VARCHAR(10) DEFAULT '0%' COMMENT '非及时单占比',
|
||||||
|
|
||||||
|
-- 客户属性指标
|
||||||
|
`customer_attr_count` INT DEFAULT 0 COMMENT '客户属性数量',
|
||||||
|
`parent_count` INT DEFAULT 0 COMMENT '家长数量',
|
||||||
|
`student_count` INT DEFAULT 0 COMMENT '学生数量',
|
||||||
|
`teacher_count` INT DEFAULT 0 COMMENT '老师数量',
|
||||||
|
`unknown_attr_count` INT DEFAULT 0 COMMENT '未知属性数量',
|
||||||
|
`parent_rate` VARCHAR(10) DEFAULT '0%' COMMENT '家长占比',
|
||||||
|
`student_rate` VARCHAR(10) DEFAULT '0%' COMMENT '学生占比',
|
||||||
|
`teacher_rate` VARCHAR(10) DEFAULT '0%' COMMENT '老师占比',
|
||||||
|
`unknown_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未知占比',
|
||||||
|
|
||||||
|
-- 出单率指标
|
||||||
|
`parent_order_count` INT DEFAULT 0 COMMENT '家长出单数量',
|
||||||
|
`student_order_count` INT DEFAULT 0 COMMENT '学生出单数量',
|
||||||
|
`teacher_order_count` INT DEFAULT 0 COMMENT '老师出单数量',
|
||||||
|
`unknown_order_count` INT DEFAULT 0 COMMENT '未知出单数量',
|
||||||
|
`parent_daily_count` INT DEFAULT 0 COMMENT '家长当日数量',
|
||||||
|
`student_daily_count` INT DEFAULT 0 COMMENT '学生当日数量',
|
||||||
|
`teacher_daily_count` INT DEFAULT 0 COMMENT '老师当日数量',
|
||||||
|
`unknown_daily_count` INT DEFAULT 0 COMMENT '未知当日数量',
|
||||||
|
`parent_daily_order_count` INT DEFAULT 0 COMMENT '家长当日出单数量',
|
||||||
|
`student_daily_order_count` INT DEFAULT 0 COMMENT '学生当日出单数量',
|
||||||
|
`teacher_daily_order_count` INT DEFAULT 0 COMMENT '老师当日出单数量',
|
||||||
|
`unknown_daily_order_count` INT DEFAULT 0 COMMENT '未知当日出单数量',
|
||||||
|
`parent_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '家长出单率',
|
||||||
|
`student_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '学生出单率',
|
||||||
|
`teacher_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '老师出单率',
|
||||||
|
`unknown_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未知出单率',
|
||||||
|
|
||||||
|
-- 意向度指标
|
||||||
|
`intention_count` INT DEFAULT 0 COMMENT '意向度数量',
|
||||||
|
`active_quote_count` INT DEFAULT 0 COMMENT '主动报价数量',
|
||||||
|
`passive_quote_count` INT DEFAULT 0 COMMENT '被动报价数量',
|
||||||
|
`no_quote_count` INT DEFAULT 0 COMMENT '未开口报价数量',
|
||||||
|
`deleted_quote_count` INT DEFAULT 0 COMMENT '已删除报价数量',
|
||||||
|
`active_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '主动报价占比',
|
||||||
|
`passive_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '被动报价占比',
|
||||||
|
`no_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未开口报价占比',
|
||||||
|
`deleted_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '已删除报价占比',
|
||||||
|
|
||||||
|
-- 年级指标
|
||||||
|
`grade_count` INT DEFAULT 0 COMMENT '年级数量',
|
||||||
|
`primary_count` INT DEFAULT 0 COMMENT '小学数量',
|
||||||
|
`middle_count` INT DEFAULT 0 COMMENT '初中数量',
|
||||||
|
`high_count` INT DEFAULT 0 COMMENT '高中数量',
|
||||||
|
`primary_rate` VARCHAR(10) DEFAULT '0%' COMMENT '小学占比',
|
||||||
|
`middle_rate` VARCHAR(10) DEFAULT '0%' COMMENT '初中占比',
|
||||||
|
`high_rate` VARCHAR(10) DEFAULT '0%' COMMENT '高中占比',
|
||||||
|
|
||||||
|
`sort_no` INT DEFAULT 0 COMMENT '排序号',
|
||||||
|
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_corp_date_group_tag` (`corp_id`, `cur_date`, `group_name`, `tag_name`),
|
||||||
|
INDEX `idx_corp_date` (`corp_id`, `cur_date`),
|
||||||
|
INDEX `idx_group_name` (`group_name`),
|
||||||
|
INDEX `idx_data_level` (`data_level`),
|
||||||
|
INDEX `idx_parent_id` (`parent_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户统计数据表V2(支持标签级成本,行列转换)';
|
||||||
|
|
||||||
|
-- 成本录入记录表(用于追溯)
|
||||||
|
DROP TABLE IF EXISTS `cost_input_record_v2`;
|
||||||
|
|
||||||
|
CREATE TABLE `cost_input_record_v2` (
|
||||||
|
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`corp_id` VARCHAR(100) NOT NULL COMMENT '企业ID',
|
||||||
|
`cur_date` DATE NOT NULL COMMENT '统计日期',
|
||||||
|
`group_name` VARCHAR(50) NOT NULL COMMENT '组名',
|
||||||
|
`tag_name` VARCHAR(100) DEFAULT NULL COMMENT '标签名(NULL表示组级)',
|
||||||
|
`cost_type` VARCHAR(20) NOT NULL COMMENT 'total-总成本,single-单条成本',
|
||||||
|
`input_value` DECIMAL(12,2) NOT NULL COMMENT '录入值',
|
||||||
|
`actual_total_cost` DECIMAL(12,2) NOT NULL COMMENT '实际总成本',
|
||||||
|
`input_by` VARCHAR(50) DEFAULT NULL COMMENT '录入人',
|
||||||
|
`input_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '录入时间',
|
||||||
|
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_corp_date_group` (`corp_id`, `cur_date`, `group_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成本录入记录表V2';
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- 2. 菜单配置
|
||||||
|
-- 父菜单ID: 2000 (企业微信统计)
|
||||||
|
-- 新菜单ID从 2100 开始
|
||||||
|
-- ----------------------------
|
||||||
|
|
||||||
|
-- 流量看板V2 菜单(主菜单)
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2100, '流量看板V2', 2000, 0, 'customerStatisticsV2', 'wecom/customerStatisticsV2/index', NULL, 'CustomerStatisticsV2', 1, 0, 'C', '0', '0', 'wecom:customerStatisticsV2:list', 'chart', 'admin', NOW(), '', NULL, '流量看板V2菜单(支持标签级成本)');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 查询按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2101, '流量看板V2查询', 2100, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:query', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 新增按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2102, '流量看板V2新增', 2100, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:add', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 修改按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2103, '流量看板V2修改', 2100, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:edit', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 删除按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2104, '流量看板V2删除', 2100, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:remove', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 导出按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2105, '流量看板V2导出', 2100, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:export', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 成本录入按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2106, '流量看板V2成本录入', 2100, 6, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:cost', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 重新计算按钮
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2107, '流量看板V2重新计算', 2100, 7, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:recalculate', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
-- 流量看板V2 - 树状数据查询
|
||||||
|
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES (2108, '流量看板V2树状查询', 2100, 8, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'wecom:customerStatisticsV2:tree', '#', 'admin', NOW(), '', NULL, '');
|
||||||
|
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- 3. 菜单说明
|
||||||
|
-- ----------------------------
|
||||||
|
-- 菜单类型说明:
|
||||||
|
-- M: 目录
|
||||||
|
-- C: 菜单
|
||||||
|
-- F: 按钮
|
||||||
|
--
|
||||||
|
-- 菜单层级结构:
|
||||||
|
-- 2000 企业微信统计 (目录)
|
||||||
|
-- ├── 2001 客户列表数据
|
||||||
|
-- ├── 2002 客户联系统计
|
||||||
|
-- ├── 2003 流量看板数据 (原V1版本)
|
||||||
|
-- ├── 2004 销售看板数据
|
||||||
|
-- ├── 2100 流量看板V2 (新增,支持标签级成本)
|
||||||
|
-- │ ├── 2101 查询
|
||||||
|
-- │ ├── 2102 新增
|
||||||
|
-- │ ├── 2103 修改
|
||||||
|
-- │ ├── 2104 删除
|
||||||
|
-- │ ├── 2105 导出
|
||||||
|
-- │ ├── 2106 成本录入
|
||||||
|
-- │ ├── 2107 重新计算
|
||||||
|
-- │ └── 2108 树状查询
|
||||||
|
-- └── 3000 企业信息
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
package com.ruoyi.excel.wecom.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户统计数据V2(支持标签级成本,行列转换存储)
|
||||||
|
* 与V1的区别:
|
||||||
|
* 1. V1:行是指标,列是组
|
||||||
|
* 2. V2:行是组/标签,列是指标
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("customer_statistics_data_v2")
|
||||||
|
public class CustomerStatisticsDataV2 implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String corpId;
|
||||||
|
|
||||||
|
private Date curDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组名(N组、O组等)
|
||||||
|
*/
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签名(NULL表示组级汇总)
|
||||||
|
*/
|
||||||
|
private String tagName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签组ID(关联wecom_tag_group)
|
||||||
|
*/
|
||||||
|
private String tagGroupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签ID(关联wecom_tag)
|
||||||
|
*/
|
||||||
|
private String tagId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据级别:1-组级汇总,2-标签级明细
|
||||||
|
*/
|
||||||
|
private Integer dataLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父记录ID(标签级数据对应组级记录的ID)
|
||||||
|
*/
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
// ==================== 成本数据 ====================
|
||||||
|
/**
|
||||||
|
* 总成本(手工录入)
|
||||||
|
*/
|
||||||
|
private BigDecimal totalCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条成本(计算得出)
|
||||||
|
*/
|
||||||
|
private BigDecimal singleCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成单成本(计算得出)
|
||||||
|
*/
|
||||||
|
private BigDecimal orderCost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成本录入类型:total-总成本,single-单条成本
|
||||||
|
*/
|
||||||
|
private String costInputType;
|
||||||
|
|
||||||
|
// ==================== 数量指标 ====================
|
||||||
|
/**
|
||||||
|
* 成单数
|
||||||
|
*/
|
||||||
|
private Integer orderCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进粉数
|
||||||
|
*/
|
||||||
|
private Integer customerCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 及时单数
|
||||||
|
*/
|
||||||
|
private Integer timelyOrderCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非及时单数
|
||||||
|
*/
|
||||||
|
private Integer nonTimelyOrderCount;
|
||||||
|
|
||||||
|
// ==================== 比率指标 ====================
|
||||||
|
/**
|
||||||
|
* 转化率
|
||||||
|
*/
|
||||||
|
private String conversionRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 及时单占比
|
||||||
|
*/
|
||||||
|
private String timelyRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非及时单占比
|
||||||
|
*/
|
||||||
|
private String nonTimelyRate;
|
||||||
|
|
||||||
|
// ==================== 客户属性指标 ====================
|
||||||
|
/**
|
||||||
|
* 客户属性数量
|
||||||
|
*/
|
||||||
|
private Integer customerAttrCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 家长数量
|
||||||
|
*/
|
||||||
|
private Integer parentCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生数量
|
||||||
|
*/
|
||||||
|
private Integer studentCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老师数量
|
||||||
|
*/
|
||||||
|
private Integer teacherCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未知属性数量
|
||||||
|
*/
|
||||||
|
private Integer unknownAttrCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 家长占比
|
||||||
|
*/
|
||||||
|
private String parentRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生占比
|
||||||
|
*/
|
||||||
|
private String studentRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 老师占比
|
||||||
|
*/
|
||||||
|
private String teacherRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未知占比
|
||||||
|
*/
|
||||||
|
private String unknownRate;
|
||||||
|
|
||||||
|
// ==================== 出单率指标 ====================
|
||||||
|
private Integer parentOrderCount;
|
||||||
|
private Integer studentOrderCount;
|
||||||
|
private Integer teacherOrderCount;
|
||||||
|
private Integer unknownOrderCount;
|
||||||
|
|
||||||
|
private Integer parentDailyCount;
|
||||||
|
private Integer studentDailyCount;
|
||||||
|
private Integer teacherDailyCount;
|
||||||
|
private Integer unknownDailyCount;
|
||||||
|
|
||||||
|
private Integer parentDailyOrderCount;
|
||||||
|
private Integer studentDailyOrderCount;
|
||||||
|
private Integer teacherDailyOrderCount;
|
||||||
|
private Integer unknownDailyOrderCount;
|
||||||
|
|
||||||
|
private String parentOrderRate;
|
||||||
|
private String studentOrderRate;
|
||||||
|
private String teacherOrderRate;
|
||||||
|
private String unknownOrderRate;
|
||||||
|
|
||||||
|
// ==================== 意向度指标 ====================
|
||||||
|
private Integer intentionCount;
|
||||||
|
private Integer activeQuoteCount;
|
||||||
|
private Integer passiveQuoteCount;
|
||||||
|
private Integer noQuoteCount;
|
||||||
|
private Integer deletedQuoteCount;
|
||||||
|
|
||||||
|
private String activeQuoteRate;
|
||||||
|
private String passiveQuoteRate;
|
||||||
|
private String noQuoteRate;
|
||||||
|
private String deletedQuoteRate;
|
||||||
|
|
||||||
|
// ==================== 年级指标 ====================
|
||||||
|
private Integer gradeCount;
|
||||||
|
private Integer primaryCount;
|
||||||
|
private Integer middleCount;
|
||||||
|
private Integer highCount;
|
||||||
|
|
||||||
|
private String primaryRate;
|
||||||
|
private String middleRate;
|
||||||
|
private String highRate;
|
||||||
|
|
||||||
|
// ==================== 其他 ====================
|
||||||
|
private Integer sortNo;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
// ==================== 非持久化字段 ====================
|
||||||
|
/**
|
||||||
|
* 年份周数显示(如:2026年第10周)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String yearWeek;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年月显示(如:2026年03月)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String yearMonth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期范围显示(如:2026-03-02 至 2026-03-08)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String dateRange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子标签列表(用于树状展示)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private List<CustomerStatisticsDataV2> children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为叶子节点(标签级)
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private Boolean leaf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点显示名称(组名或标签名)
|
||||||
|
*/
|
||||||
|
public String getDisplayName() {
|
||||||
|
if (tagName != null && !tagName.isEmpty()) {
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取完整路径(用于树状展示)
|
||||||
|
*/
|
||||||
|
public String getFullPath() {
|
||||||
|
if (tagName != null && !tagName.isEmpty()) {
|
||||||
|
return groupName + "/" + tagName;
|
||||||
|
}
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.ruoyi.excel.wecom.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签树DTO
|
||||||
|
* 用于返回组-标签的树状结构
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TagTreeDTO {
|
||||||
|
|
||||||
|
/** 节点ID */
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/** 节点名称 */
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
/** 节点类型:group-组,tag-标签 */
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/** 组名 */
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/** 标签名 */
|
||||||
|
private String tagName;
|
||||||
|
|
||||||
|
/** 子节点 */
|
||||||
|
private List<TagTreeDTO> children;
|
||||||
|
|
||||||
|
/** 数量统计 */
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@Autowired
|
@Autowired
|
||||||
private CorpInfoMapper corpInfoMapper;
|
private CorpInfoMapper corpInfoMapper;
|
||||||
|
|
||||||
|
private List<String> finishFlag = Arrays.asList("已成交及时单9元+", "已成交非及时单9元+");
|
||||||
|
private List<String> timelyFinishFlag = Arrays.asList("已成交及时单9元+");
|
||||||
|
private List<String> noTimelyfinishFlag = Arrays.asList("已成交非及时单9元+");
|
||||||
/**
|
/**
|
||||||
* 线程池配置 - 用于并行处理客户数据
|
* 线程池配置 - 用于并行处理客户数据
|
||||||
* 设置为4个线程,适应4核8G服务器环境
|
* 设置为4个线程,适应4核8G服务器环境
|
||||||
|
|
@ -773,15 +776,15 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
||||||
stats.setTeacherDailyOrderCount(stats.getTeacherDailyOrderCount() + 1);
|
stats.setTeacherDailyOrderCount(stats.getTeacherDailyOrderCount() + 1);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
stats.setUnknownAttrCount(stats.getUnknownAttrCount() + 1);
|
||||||
stats.setUnknownAttrCount(stats.getUnknownAttrCount() + 1);
|
stats.setUnkownOrderCount(stats.getUnkownOrderCount() + 1);
|
||||||
stats.setUnkownOrderCount(stats.getUnkownOrderCount() + 1);
|
|
||||||
|
|
||||||
// 新增:N组未知出单率统计(当日)
|
// 新增:N组未知出单率统计(当日)
|
||||||
stats.setUnkownDailyCount(stats.getUnkownDailyCount() + 1);
|
stats.setUnkownDailyCount(stats.getUnkownDailyCount() + 1);
|
||||||
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
||||||
stats.setUnkownDailyOrderCount(stats.getUnkownDailyOrderCount() + 1);
|
stats.setUnkownDailyOrderCount(stats.getUnkownDailyOrderCount() + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,25 +867,29 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
// 1. 成单数
|
// 1. 成单数
|
||||||
//成单数 需要从历史的所有数据中获取 成交日期 = date的数据
|
//成单数 需要从历史的所有数据中获取 成交日期 = date的数据
|
||||||
Long finishCount = customerExportDataMapper.selectByFinishDate(corpId,curDate,GROUP_ATTR_MAP.get(groupName));
|
Long finishCount = customerExportDataMapper.selectByFinishDate(corpId,curDate,GROUP_ATTR_MAP.get(groupName),finishFlag);
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "成单数(当日)", groupName, String.valueOf(finishCount),(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "成单数(当日)", groupName, String.valueOf(finishCount),(10*sortNo++));
|
||||||
|
|
||||||
// 2. 进粉数
|
// 2. 进粉数
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "进粉数(当日)", groupName, String.valueOf(stats.getCustomerCount()),(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "进粉数(当日)", groupName, String.valueOf(stats.getCustomerCount()),(10*sortNo++));
|
||||||
|
|
||||||
// 3. 转化率
|
// 3. 转化率 = 成单数 / 进粉数(使用finishCount而不是stats.getOrderCount())
|
||||||
String conversionRate = calculateRate(stats.getOrderCount(), stats.getCustomerCount());
|
String conversionRate = calculateRate(finishCount.intValue(), stats.getCustomerCount());
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "转化率(当日)", groupName, conversionRate,(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "转化率(当日)", groupName, conversionRate,(10*sortNo++));
|
||||||
|
|
||||||
// 4. 及时单占比
|
// 4. 及时单占比 = 及时单数 / 成单数(当日)
|
||||||
String timelyRate = calculateRate(stats.getTimelyOrderCount(), stats.getCustomerCount());
|
// 及时单数量需要从历史数据中获取(根据成交日期和订单状态)
|
||||||
|
Long timelyCount = customerExportDataMapper.selectTimelyOrderCount(corpId, curDate, GROUP_ATTR_MAP.get(groupName),timelyFinishFlag);
|
||||||
|
String timelyRate = calculateRate(timelyCount.intValue(), finishCount.intValue());
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "及时单占比(当日)", groupName, timelyRate,(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "及时单占比(当日)", groupName, timelyRate,(10*sortNo++));
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "及时单数量(当日)", groupName, String.valueOf(stats.getTimelyOrderCount()),(10*sortNo++),true);
|
setIndicatorValue(corpId,indicatorMap,curDate, "及时单数量(当日)", groupName, String.valueOf(timelyCount),(10*sortNo++),true);
|
||||||
|
|
||||||
// 5. 非及时单占比
|
// 5. 非及时单占比 = 非及时单数 / 成单数(当日)
|
||||||
String nonTimelyRate = calculateRate(stats.getNonTimelyOrderCount(), stats.getCustomerCount());
|
// 非及时单数量需要从历史数据中获取(根据成交日期和订单状态)
|
||||||
|
Long nonTimelyCount = customerExportDataMapper.selectNonTimelyOrderCount(corpId, curDate, GROUP_ATTR_MAP.get(groupName),noTimelyfinishFlag);
|
||||||
|
String nonTimelyRate = calculateRate(nonTimelyCount.intValue(), finishCount.intValue());
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "非及时单占比(当日)", groupName, nonTimelyRate,(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "非及时单占比(当日)", groupName, nonTimelyRate,(10*sortNo++));
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "非及时单数量(当日)", groupName, String.valueOf(stats.getNonTimelyOrderCount()),(10*sortNo++),true);
|
setIndicatorValue(corpId,indicatorMap,curDate, "非及时单数量(当日)", groupName, String.valueOf(nonTimelyCount),(10*sortNo++),true);
|
||||||
|
|
||||||
// 6. 客户属性数量
|
// 6. 客户属性数量
|
||||||
setIndicatorValue(corpId,indicatorMap,curDate, "客户属性数量(当日)", groupName, String.valueOf(stats.getTotalCustomerAttr()),(10*sortNo++));
|
setIndicatorValue(corpId,indicatorMap,curDate, "客户属性数量(当日)", groupName, String.valueOf(stats.getTotalCustomerAttr()),(10*sortNo++));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,816 @@
|
||||||
|
package com.ruoyi.excel.wecom.helper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.ruoyi.excel.wecom.domain.CorpInfo;
|
||||||
|
import com.ruoyi.excel.wecom.domain.CustomerExportData;
|
||||||
|
import com.ruoyi.excel.wecom.domain.CustomerStatisticsDataV2;
|
||||||
|
import com.ruoyi.excel.wecom.mapper.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流量看板数据处理V2
|
||||||
|
* 支持标签级成本,行列转换存储
|
||||||
|
* 与V1的区别:
|
||||||
|
* 1. V1:行是指标,列是组
|
||||||
|
* 2. V2:行是组/标签,列是指标
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class HandleAllDataV2 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomerExportDataMapper customerExportDataMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomerStatisticsDataV2Mapper dataV2Mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WecomTagGroupMapper wecomTagGroupMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WecomTagMapper wecomTagMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CorpInfoMapper corpInfoMapper;
|
||||||
|
private List<String> finishFlag = Arrays.asList("已成交及时单9元+", "已成交非及时单9元+");
|
||||||
|
private List<String> timelyFinishFlag = Arrays.asList("已成交及时单9元+");
|
||||||
|
private List<String> noTimelyfinishFlag = Arrays.asList("已成交非及时单9元+");
|
||||||
|
/**
|
||||||
|
* 组名到字段名的映射(使用括号内的名称)
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> GROUP_FIELD_MAP = new LinkedHashMap<>();
|
||||||
|
static {
|
||||||
|
GROUP_FIELD_MAP.put("投放", "tagGroup1");
|
||||||
|
GROUP_FIELD_MAP.put("公司孵化", "tagGroup2");
|
||||||
|
GROUP_FIELD_MAP.put("商务", "tagGroup3");
|
||||||
|
GROUP_FIELD_MAP.put("A1组", "tagGroup10");
|
||||||
|
GROUP_FIELD_MAP.put("B1组", "tagGroup11");
|
||||||
|
GROUP_FIELD_MAP.put("C1组", "tagGroup12");
|
||||||
|
GROUP_FIELD_MAP.put("D1组", "tagGroup13");
|
||||||
|
GROUP_FIELD_MAP.put("E1组", "tagGroup14");
|
||||||
|
GROUP_FIELD_MAP.put("自然流", "tagGroup16");
|
||||||
|
GROUP_FIELD_MAP.put("F1组", "tagGroup17");
|
||||||
|
GROUP_FIELD_MAP.put("G1组", "tagGroup18");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组名到数据库字段名的映射(用于SQL查询)
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> GROUP_ATTR_MAP = new LinkedHashMap<>();
|
||||||
|
static {
|
||||||
|
GROUP_ATTR_MAP.put("投放", "tag_group1");
|
||||||
|
GROUP_ATTR_MAP.put("公司孵化", "tag_group2");
|
||||||
|
GROUP_ATTR_MAP.put("商务", "tag_group3");
|
||||||
|
GROUP_ATTR_MAP.put("A1组", "tag_group10");
|
||||||
|
GROUP_ATTR_MAP.put("B1组", "tag_group11");
|
||||||
|
GROUP_ATTR_MAP.put("C1组", "tag_group12");
|
||||||
|
GROUP_ATTR_MAP.put("D1组", "tag_group13");
|
||||||
|
GROUP_ATTR_MAP.put("E1组", "tag_group14");
|
||||||
|
GROUP_ATTR_MAP.put("自然流", "tag_group16");
|
||||||
|
GROUP_ATTR_MAP.put("F1组", "tag_group17");
|
||||||
|
GROUP_ATTR_MAP.put("G1组", "tag_group18");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程池配置
|
||||||
|
*/
|
||||||
|
private final ExecutorService executorService = Executors.newFixedThreadPool(
|
||||||
|
4,
|
||||||
|
new ThreadFactory() {
|
||||||
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable r) {
|
||||||
|
Thread thread = new Thread(r, "customer-data-v2-handler-" + threadNumber.getAndIncrement());
|
||||||
|
thread.setDaemon(false);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建所有日期的流量看板数据V2
|
||||||
|
*/
|
||||||
|
public void createAllReportDataV2() {
|
||||||
|
List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo());
|
||||||
|
int batchSize = 10;
|
||||||
|
|
||||||
|
for (CorpInfo item : corpInfos) {
|
||||||
|
try {
|
||||||
|
String corpId = item.getCorpId();
|
||||||
|
List<Date> allDate = getAllDate(corpId);
|
||||||
|
|
||||||
|
for (int i = 0; i < allDate.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, allDate.size());
|
||||||
|
List<Date> batchDates = allDate.subList(i, end);
|
||||||
|
|
||||||
|
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||||
|
for (Date date : batchDates) {
|
||||||
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||||
|
createReportDataV2(corpId, date);
|
||||||
|
}, executorService);
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("多线程处理流量看板V2数据时发生错误: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建指定日期的流量看板数据V2
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 统计日期
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void createReportDataV2(String corpId, Date date) {
|
||||||
|
log.info("开始创建V2流量看板数据:corpId={}, date={}", corpId, date);
|
||||||
|
|
||||||
|
// 1. 先删除当天已存在的数据
|
||||||
|
LambdaQueryWrapper<CustomerStatisticsDataV2> deleteWrapper = new LambdaQueryWrapper<>();
|
||||||
|
deleteWrapper.eq(CustomerStatisticsDataV2::getCorpId, corpId)
|
||||||
|
.eq(CustomerStatisticsDataV2::getCurDate, date);
|
||||||
|
dataV2Mapper.delete(deleteWrapper);
|
||||||
|
|
||||||
|
// 2. 重新计算并插入当天数据
|
||||||
|
List<CustomerStatisticsDataV2> dataList = calculateStatisticsV2(corpId, date);
|
||||||
|
|
||||||
|
// 3. 批量插入
|
||||||
|
if (!dataList.isEmpty()) {
|
||||||
|
// 分批插入,每批500条
|
||||||
|
int batchSize = 500;
|
||||||
|
for (int i = 0; i < dataList.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, dataList.size());
|
||||||
|
List<CustomerStatisticsDataV2> batch = dataList.subList(i, end);
|
||||||
|
dataV2Mapper.batchInsert(batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("V2流量看板数据创建完成:corpId={}, date={}, 共{}条记录",
|
||||||
|
corpId, date, dataList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算统计数据V2
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 目标日期
|
||||||
|
* @return 统计结果列表
|
||||||
|
*/
|
||||||
|
private List<CustomerStatisticsDataV2> calculateStatisticsV2(String corpId, Date date) {
|
||||||
|
// 1. 初始化累加器(按组和标签)
|
||||||
|
GroupTagAccumulator accumulator = new GroupTagAccumulator();
|
||||||
|
|
||||||
|
// 2. 分页查询并累加统计
|
||||||
|
int pageSize = 1000;
|
||||||
|
int pageNum = 1;
|
||||||
|
LambdaQueryWrapper<CustomerExportData> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(CustomerExportData::getCorpId, corpId)
|
||||||
|
.eq(CustomerExportData::getAddDate, date);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Page<CustomerExportData> page = new Page<>(pageNum, pageSize);
|
||||||
|
Page<CustomerExportData> pageData = customerExportDataMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
for (CustomerExportData data : pageData.getRecords()) {
|
||||||
|
processDataRecordV2(data, date, accumulator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pageData.hasNext()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pageNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 从累加器生成最终结果
|
||||||
|
return generateStatisticsResultsV2(corpId, date, accumulator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单条数据记录,累加到组级和标签级统计
|
||||||
|
*/
|
||||||
|
private void processDataRecordV2(CustomerExportData data, Date date, GroupTagAccumulator accumulator) {
|
||||||
|
// 遍历所有组
|
||||||
|
for (Map.Entry<String, String> entry : GROUP_FIELD_MAP.entrySet()) {
|
||||||
|
String groupName = entry.getKey();
|
||||||
|
String fieldName = entry.getValue();
|
||||||
|
|
||||||
|
// 获取该组的标签值
|
||||||
|
String tagValue = getFieldValue(data, fieldName);
|
||||||
|
|
||||||
|
// 如果该组标签为空,跳过
|
||||||
|
if (tagValue == null || tagValue.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取该组的统计器
|
||||||
|
GroupStatistics groupStats = accumulator.getGroupStats(groupName);
|
||||||
|
|
||||||
|
// 累加组级统计
|
||||||
|
accumulateGroupStatistics(data, date, groupStats);
|
||||||
|
|
||||||
|
// 解析标签值(可能是逗号分隔的多个标签)
|
||||||
|
String[] tags = tagValue.split(",");
|
||||||
|
for (String tag : tags) {
|
||||||
|
tag = tag.trim();
|
||||||
|
if (tag.isEmpty()) continue;
|
||||||
|
|
||||||
|
// 累加到标签级统计
|
||||||
|
TagStatistics tagStats = accumulator.getTagStats(groupName, tag);
|
||||||
|
accumulateGroupStatistics(data, date, tagStats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用反射获取字段值(带缓存)
|
||||||
|
*/
|
||||||
|
private String getFieldValue(CustomerExportData data, String fieldName) {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Field field = CustomerExportData.class.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object value = field.get(data);
|
||||||
|
return value == null ? "" : value.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 累加单条数据的统计指标
|
||||||
|
*/
|
||||||
|
private void accumulateGroupStatistics(CustomerExportData data, Date date, BaseStatistics stats) {
|
||||||
|
// 1. 成单数统计
|
||||||
|
if (matchesQValue(data, date)) {
|
||||||
|
stats.setOrderCount(stats.getOrderCount() + 1);
|
||||||
|
String orderStatus = data.getTagGroup7();
|
||||||
|
if (orderStatus != null) {
|
||||||
|
String[] split = orderStatus.split(",");
|
||||||
|
String statusInfo = split[split.length - 1];
|
||||||
|
if (statusInfo.contains("已成交及时单9元+")) {
|
||||||
|
stats.setTimelyOrderCount(stats.getTimelyOrderCount() + 1);
|
||||||
|
} else if (statusInfo.contains("已成交非及时单9元+")) {
|
||||||
|
stats.setNonTimelyOrderCount(stats.getNonTimelyOrderCount() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 来源筛选
|
||||||
|
if (!matchesSource(data) || !matchesDate(data, date)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 进粉数
|
||||||
|
stats.setCustomerCount(stats.getCustomerCount() + 1);
|
||||||
|
|
||||||
|
// 3. 客户属性统计
|
||||||
|
String customerAttr = data.getTagGroup6();
|
||||||
|
if (customerAttr != null && !customerAttr.trim().isEmpty()) {
|
||||||
|
stats.setCustomerAttrCount(stats.getCustomerAttrCount() + 1);
|
||||||
|
if (customerAttr.contains("家长")) {
|
||||||
|
stats.setParentCount(stats.getParentCount() + 1);
|
||||||
|
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);
|
||||||
|
stats.setStudentDailyCount(stats.getStudentDailyCount() + 1);
|
||||||
|
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
||||||
|
stats.setStudentDailyOrderCount(stats.getStudentDailyOrderCount() + 1);
|
||||||
|
}
|
||||||
|
} else if (customerAttr.contains("老师")) {
|
||||||
|
stats.setTeacherCount(stats.getTeacherCount() + 1);
|
||||||
|
stats.setTeacherOrderCount(stats.getTeacherOrderCount() + 1);
|
||||||
|
stats.setTeacherDailyCount(stats.getTeacherDailyCount() + 1);
|
||||||
|
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
||||||
|
stats.setTeacherDailyOrderCount(stats.getTeacherDailyOrderCount() + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 属性不为空但无法识别(未知)
|
||||||
|
stats.setUnknownAttrCount(stats.getUnknownAttrCount() + 1);
|
||||||
|
stats.setUnknownOrderCount(stats.getUnknownOrderCount() + 1);
|
||||||
|
stats.setUnknownDailyCount(stats.getUnknownDailyCount() + 1);
|
||||||
|
if (matchesQValue(data, date) && isTimelyOrder(data)) {
|
||||||
|
stats.setUnknownDailyOrderCount(stats.getUnknownDailyOrderCount() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 注意:属性为空的客户不统计在 customerAttrCount 和 unknownAttrCount 中
|
||||||
|
|
||||||
|
// 4. 意向度统计
|
||||||
|
String intention = data.getTagGroup15();
|
||||||
|
if (intention != null && !intention.trim().isEmpty() && !"空白".equals(intention)) {
|
||||||
|
stats.setIntentionCount(stats.getIntentionCount() + 1);
|
||||||
|
if (intention.contains("主动报价")) {
|
||||||
|
stats.setActiveQuoteCount(stats.getActiveQuoteCount() + 1);
|
||||||
|
} else if (intention.contains("被动报价")) {
|
||||||
|
stats.setPassiveQuoteCount(stats.getPassiveQuoteCount() + 1);
|
||||||
|
} else if (intention.contains("未开口")) {
|
||||||
|
stats.setNoQuoteCount(stats.getNoQuoteCount() + 1);
|
||||||
|
} else if (intention.contains("已删除")) {
|
||||||
|
stats.setDeletedQuoteCount(stats.getDeletedQuoteCount() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 年级统计
|
||||||
|
String grade = data.getTagGroup5();
|
||||||
|
if (grade != null && !grade.trim().isEmpty() && !"空白".equals(grade)) {
|
||||||
|
stats.setGradeCount(stats.getGradeCount() + 1);
|
||||||
|
if (isPrimarySchool(grade)) {
|
||||||
|
stats.setPrimaryCount(stats.getPrimaryCount() + 1);
|
||||||
|
} else if (isMiddleSchool(grade)) {
|
||||||
|
stats.setMiddleCount(stats.getMiddleCount() + 1);
|
||||||
|
} else if (isHighSchool(grade)) {
|
||||||
|
stats.setHighCount(stats.getHighCount() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从累加器生成最终统计结果V2
|
||||||
|
*/
|
||||||
|
private List<CustomerStatisticsDataV2> generateStatisticsResultsV2(
|
||||||
|
String corpId, Date date, GroupTagAccumulator accumulator) {
|
||||||
|
List<CustomerStatisticsDataV2> result = new ArrayList<>();
|
||||||
|
int sortNo = 0;
|
||||||
|
long tempId = 1; // 临时ID生成器
|
||||||
|
|
||||||
|
// 1. 生成组级数据
|
||||||
|
Map<String, Long> groupIdMap = new HashMap<>();
|
||||||
|
for (Map.Entry<String, GroupStatistics> entry : accumulator.getGroupStatsMap().entrySet()) {
|
||||||
|
String groupName = entry.getKey();
|
||||||
|
GroupStatistics stats = entry.getValue();
|
||||||
|
|
||||||
|
// 使用SQL查询获取成单数(与V1保持一致)
|
||||||
|
Long finishCount = customerExportDataMapper.selectByFinishDate(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName),finishFlag);
|
||||||
|
stats.setOrderCount(finishCount.intValue());
|
||||||
|
|
||||||
|
// 使用SQL查询获取及时单数量(根据成交日期和订单状态)
|
||||||
|
Long timelyCount = customerExportDataMapper.selectTimelyOrderCount(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName),timelyFinishFlag);
|
||||||
|
stats.setTimelyOrderCount(timelyCount.intValue());
|
||||||
|
|
||||||
|
// 使用SQL查询获取非及时单数量(根据成交日期和订单状态)
|
||||||
|
Long nonTimelyCount = customerExportDataMapper.selectNonTimelyOrderCount(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName),noTimelyfinishFlag);
|
||||||
|
stats.setNonTimelyOrderCount(nonTimelyCount.intValue());
|
||||||
|
|
||||||
|
CustomerStatisticsDataV2 groupData = createStatisticsDataV2(
|
||||||
|
corpId, date, groupName, null, stats, sortNo++);
|
||||||
|
groupData.setDataLevel(1);
|
||||||
|
groupData.setId(tempId); // 设置临时ID
|
||||||
|
result.add(groupData);
|
||||||
|
|
||||||
|
// 临时存储组ID(用于标签级数据的parentId)
|
||||||
|
groupIdMap.put(groupName, tempId);
|
||||||
|
tempId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 生成标签级数据
|
||||||
|
for (Map.Entry<String, Map<String, TagStatistics>> groupEntry :
|
||||||
|
accumulator.getTagStatsMap().entrySet()) {
|
||||||
|
String groupName = groupEntry.getKey();
|
||||||
|
Long parentId = groupIdMap.get(groupName);
|
||||||
|
|
||||||
|
for (Map.Entry<String, TagStatistics> tagEntry : groupEntry.getValue().entrySet()) {
|
||||||
|
String tagName = tagEntry.getKey();
|
||||||
|
TagStatistics stats = tagEntry.getValue();
|
||||||
|
|
||||||
|
// 使用SQL查询获取该标签的成单数(根据标签值筛选)
|
||||||
|
Long finishCount = customerExportDataMapper.selectByFinishDateAndTag(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName), tagName,finishFlag);
|
||||||
|
stats.setOrderCount(finishCount.intValue());
|
||||||
|
|
||||||
|
// 使用SQL查询获取该标签的及时单数量
|
||||||
|
Long timelyCount = customerExportDataMapper.selectTimelyOrderCountByTag(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName), tagName,timelyFinishFlag);
|
||||||
|
stats.setTimelyOrderCount(timelyCount.intValue());
|
||||||
|
|
||||||
|
// 使用SQL查询获取该标签的非及时单数量
|
||||||
|
Long nonTimelyCount = customerExportDataMapper.selectNonTimelyOrderCountByTag(
|
||||||
|
corpId, date, GROUP_ATTR_MAP.get(groupName), tagName,noTimelyfinishFlag);
|
||||||
|
stats.setNonTimelyOrderCount(nonTimelyCount.intValue());
|
||||||
|
|
||||||
|
CustomerStatisticsDataV2 tagData = createStatisticsDataV2(
|
||||||
|
corpId, date, groupName, tagName, stats, sortNo++);
|
||||||
|
tagData.setDataLevel(2);
|
||||||
|
tagData.setId(tempId); // 设置临时ID
|
||||||
|
tagData.setParentId(parentId);
|
||||||
|
result.add(tagData);
|
||||||
|
tempId++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建统计数据V2对象
|
||||||
|
*/
|
||||||
|
private CustomerStatisticsDataV2 createStatisticsDataV2(
|
||||||
|
String corpId, Date date, String groupName, String tagName,
|
||||||
|
BaseStatistics stats, int sortNo) {
|
||||||
|
CustomerStatisticsDataV2 data = new CustomerStatisticsDataV2();
|
||||||
|
data.setCorpId(corpId);
|
||||||
|
data.setCurDate(date);
|
||||||
|
data.setGroupName(groupName);
|
||||||
|
data.setTagName(tagName);
|
||||||
|
data.setSortNo(sortNo);
|
||||||
|
|
||||||
|
// 数量指标
|
||||||
|
data.setOrderCount(stats.getOrderCount());
|
||||||
|
data.setCustomerCount(stats.getCustomerCount());
|
||||||
|
data.setTimelyOrderCount(stats.getTimelyOrderCount());
|
||||||
|
data.setNonTimelyOrderCount(stats.getNonTimelyOrderCount());
|
||||||
|
|
||||||
|
// 比率指标
|
||||||
|
// 转化率 = 成单数 / 进粉数
|
||||||
|
data.setConversionRate(calculateRate(stats.getOrderCount(), stats.getCustomerCount()));
|
||||||
|
// 及时单占比 = 及时单数 / 成单数(当日)
|
||||||
|
data.setTimelyRate(calculateRate(stats.getTimelyOrderCount(), stats.getOrderCount()));
|
||||||
|
// 非及时单占比 = 非及时单数 / 成单数(当日)
|
||||||
|
data.setNonTimelyRate(calculateRate(stats.getNonTimelyOrderCount(), stats.getOrderCount()));
|
||||||
|
|
||||||
|
// 客户属性指标
|
||||||
|
data.setCustomerAttrCount(stats.getCustomerAttrCount());
|
||||||
|
data.setParentCount(stats.getParentCount());
|
||||||
|
data.setStudentCount(stats.getStudentCount());
|
||||||
|
data.setTeacherCount(stats.getTeacherCount());
|
||||||
|
data.setUnknownAttrCount(stats.getUnknownAttrCount());
|
||||||
|
data.setParentRate(calculateRate(stats.getParentCount(), stats.getCustomerAttrCount()));
|
||||||
|
data.setStudentRate(calculateRate(stats.getStudentCount(), stats.getCustomerAttrCount()));
|
||||||
|
data.setTeacherRate(calculateRate(stats.getTeacherCount(), stats.getCustomerAttrCount()));
|
||||||
|
data.setUnknownRate(calculateRate(stats.getUnknownAttrCount(), stats.getCustomerAttrCount()));
|
||||||
|
|
||||||
|
// 出单率指标
|
||||||
|
data.setParentOrderCount(stats.getParentOrderCount());
|
||||||
|
data.setStudentOrderCount(stats.getStudentOrderCount());
|
||||||
|
data.setTeacherOrderCount(stats.getTeacherOrderCount());
|
||||||
|
data.setUnknownOrderCount(stats.getUnknownOrderCount());
|
||||||
|
data.setParentDailyCount(stats.getParentDailyCount());
|
||||||
|
data.setStudentDailyCount(stats.getStudentDailyCount());
|
||||||
|
data.setTeacherDailyCount(stats.getTeacherDailyCount());
|
||||||
|
data.setUnknownDailyCount(stats.getUnknownDailyCount());
|
||||||
|
data.setParentDailyOrderCount(stats.getParentDailyOrderCount());
|
||||||
|
data.setStudentDailyOrderCount(stats.getStudentDailyOrderCount());
|
||||||
|
data.setTeacherDailyOrderCount(stats.getTeacherDailyOrderCount());
|
||||||
|
data.setUnknownDailyOrderCount(stats.getUnknownDailyOrderCount());
|
||||||
|
data.setParentOrderRate(calculateRate(stats.getParentDailyOrderCount(), stats.getParentDailyCount()));
|
||||||
|
data.setStudentOrderRate(calculateRate(stats.getStudentDailyOrderCount(), stats.getStudentDailyCount()));
|
||||||
|
data.setTeacherOrderRate(calculateRate(stats.getTeacherDailyOrderCount(), stats.getTeacherDailyCount()));
|
||||||
|
data.setUnknownOrderRate(calculateRate(stats.getUnknownDailyOrderCount(), stats.getUnknownDailyCount()));
|
||||||
|
|
||||||
|
// 意向度指标
|
||||||
|
data.setIntentionCount(stats.getIntentionCount());
|
||||||
|
data.setActiveQuoteCount(stats.getActiveQuoteCount());
|
||||||
|
data.setPassiveQuoteCount(stats.getPassiveQuoteCount());
|
||||||
|
data.setNoQuoteCount(stats.getNoQuoteCount());
|
||||||
|
data.setDeletedQuoteCount(stats.getDeletedQuoteCount());
|
||||||
|
data.setActiveQuoteRate(calculateRate(stats.getActiveQuoteCount(), stats.getIntentionCount()));
|
||||||
|
data.setPassiveQuoteRate(calculateRate(stats.getPassiveQuoteCount(), stats.getIntentionCount()));
|
||||||
|
data.setNoQuoteRate(calculateRate(stats.getNoQuoteCount(), stats.getIntentionCount()));
|
||||||
|
data.setDeletedQuoteRate(calculateRate(stats.getDeletedQuoteCount(), stats.getIntentionCount()));
|
||||||
|
|
||||||
|
// 年级指标
|
||||||
|
data.setGradeCount(stats.getGradeCount());
|
||||||
|
data.setPrimaryCount(stats.getPrimaryCount());
|
||||||
|
data.setMiddleCount(stats.getMiddleCount());
|
||||||
|
data.setHighCount(stats.getHighCount());
|
||||||
|
data.setPrimaryRate(calculateRate(stats.getPrimaryCount(), stats.getGradeCount()));
|
||||||
|
data.setMiddleRate(calculateRate(stats.getMiddleCount(), stats.getGradeCount()));
|
||||||
|
data.setHighRate(calculateRate(stats.getHighCount(), stats.getGradeCount()));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算百分比
|
||||||
|
*/
|
||||||
|
private String calculateRate(int count, int total) {
|
||||||
|
if (total == 0) {
|
||||||
|
return "0%";
|
||||||
|
}
|
||||||
|
BigDecimal rate = new BigDecimal(count)
|
||||||
|
.multiply(new BigDecimal(100))
|
||||||
|
.divide(new BigDecimal(total), 2, RoundingMode.HALF_UP);
|
||||||
|
return rate.toString() + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据是否匹配日期
|
||||||
|
*/
|
||||||
|
private boolean matchesDate(CustomerExportData data, Date date) {
|
||||||
|
if (data.getAddDate() != null && date != null) {
|
||||||
|
return date.compareTo(data.getAddDate()) == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据来源是否匹配
|
||||||
|
*/
|
||||||
|
private boolean matchesSource(CustomerExportData data) {
|
||||||
|
if (data.getSource() != null &&
|
||||||
|
data.getSource().contains("管理员") &&
|
||||||
|
data.getSource().contains("分配")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查成交日期是否匹配
|
||||||
|
*/
|
||||||
|
private boolean matchesQValue(CustomerExportData data, Date curDate) {
|
||||||
|
String orderDate = data.getTagGroup4();
|
||||||
|
if (orderDate == null || orderDate.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String[] dates = orderDate.trim().split(",");
|
||||||
|
String lastDateStr = dates[dates.length - 1].trim();
|
||||||
|
|
||||||
|
Calendar orderCal = Calendar.getInstance();
|
||||||
|
orderCal.setTime(curDate);
|
||||||
|
orderCal.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
orderCal.set(Calendar.MINUTE, 0);
|
||||||
|
orderCal.set(Calendar.SECOND, 0);
|
||||||
|
orderCal.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
boolean parsed = false;
|
||||||
|
|
||||||
|
// 格式1: 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: 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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Calendar targetCal = Calendar.getInstance();
|
||||||
|
targetCal.setTime(curDate);
|
||||||
|
targetCal.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
targetCal.set(Calendar.MINUTE, 0);
|
||||||
|
targetCal.set(Calendar.SECOND, 0);
|
||||||
|
targetCal.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
return orderCal.getTimeInMillis() == targetCal.getTimeInMillis();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为及时单
|
||||||
|
*/
|
||||||
|
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元+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为小学
|
||||||
|
*/
|
||||||
|
private boolean isPrimarySchool(String grade) {
|
||||||
|
return grade.contains("小学") || grade.contains("一年级") || grade.contains("二年级") ||
|
||||||
|
grade.contains("三年级") || grade.contains("四年级") || grade.contains("五年级") ||
|
||||||
|
grade.contains("六年级");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为初中
|
||||||
|
*/
|
||||||
|
private boolean isMiddleSchool(String grade) {
|
||||||
|
return grade.contains("初中") || grade.contains("初一") ||
|
||||||
|
grade.contains("初二") || grade.contains("初三");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为高中
|
||||||
|
*/
|
||||||
|
private boolean isHighSchool(String grade) {
|
||||||
|
return grade.contains("高中") || grade.contains("高一") ||
|
||||||
|
grade.contains("高二") || grade.contains("高三");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有日期
|
||||||
|
*/
|
||||||
|
private List<Date> getAllDate(String corpId) {
|
||||||
|
return customerExportDataMapper.getDistinctDate(corpId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭线程池
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
executorService.shutdown();
|
||||||
|
try {
|
||||||
|
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 内部类定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础统计类
|
||||||
|
*/
|
||||||
|
public static class BaseStatistics {
|
||||||
|
// 数量指标
|
||||||
|
private int orderCount;
|
||||||
|
private int customerCount;
|
||||||
|
private int timelyOrderCount;
|
||||||
|
private int nonTimelyOrderCount;
|
||||||
|
|
||||||
|
// 客户属性指标
|
||||||
|
private int customerAttrCount;
|
||||||
|
private int parentCount;
|
||||||
|
private int studentCount;
|
||||||
|
private int teacherCount;
|
||||||
|
private int unknownAttrCount;
|
||||||
|
|
||||||
|
// 出单率指标
|
||||||
|
private int parentOrderCount;
|
||||||
|
private int studentOrderCount;
|
||||||
|
private int teacherOrderCount;
|
||||||
|
private int unknownOrderCount;
|
||||||
|
private int parentDailyCount;
|
||||||
|
private int studentDailyCount;
|
||||||
|
private int teacherDailyCount;
|
||||||
|
private int unknownDailyCount;
|
||||||
|
private int parentDailyOrderCount;
|
||||||
|
private int studentDailyOrderCount;
|
||||||
|
private int teacherDailyOrderCount;
|
||||||
|
private int unknownDailyOrderCount;
|
||||||
|
|
||||||
|
// 意向度指标
|
||||||
|
private int intentionCount;
|
||||||
|
private int activeQuoteCount;
|
||||||
|
private int passiveQuoteCount;
|
||||||
|
private int noQuoteCount;
|
||||||
|
private int deletedQuoteCount;
|
||||||
|
|
||||||
|
// 年级指标
|
||||||
|
private int gradeCount;
|
||||||
|
private int primaryCount;
|
||||||
|
private int middleCount;
|
||||||
|
private int highCount;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public int getOrderCount() { return orderCount; }
|
||||||
|
public void setOrderCount(int orderCount) { this.orderCount = orderCount; }
|
||||||
|
public int getCustomerCount() { return customerCount; }
|
||||||
|
public void setCustomerCount(int customerCount) { this.customerCount = customerCount; }
|
||||||
|
public int getTimelyOrderCount() { return timelyOrderCount; }
|
||||||
|
public void setTimelyOrderCount(int timelyOrderCount) { this.timelyOrderCount = timelyOrderCount; }
|
||||||
|
public int getNonTimelyOrderCount() { return nonTimelyOrderCount; }
|
||||||
|
public void setNonTimelyOrderCount(int nonTimelyOrderCount) { this.nonTimelyOrderCount = nonTimelyOrderCount; }
|
||||||
|
public int getCustomerAttrCount() { return customerAttrCount; }
|
||||||
|
public void setCustomerAttrCount(int customerAttrCount) { this.customerAttrCount = customerAttrCount; }
|
||||||
|
public int getParentCount() { return parentCount; }
|
||||||
|
public void setParentCount(int parentCount) { this.parentCount = parentCount; }
|
||||||
|
public int getStudentCount() { return studentCount; }
|
||||||
|
public void setStudentCount(int studentCount) { this.studentCount = studentCount; }
|
||||||
|
public int getTeacherCount() { return teacherCount; }
|
||||||
|
public void setTeacherCount(int teacherCount) { this.teacherCount = teacherCount; }
|
||||||
|
public int getUnknownAttrCount() { return unknownAttrCount; }
|
||||||
|
public void setUnknownAttrCount(int unknownAttrCount) { this.unknownAttrCount = unknownAttrCount; }
|
||||||
|
public int getParentOrderCount() { return parentOrderCount; }
|
||||||
|
public void setParentOrderCount(int parentOrderCount) { this.parentOrderCount = parentOrderCount; }
|
||||||
|
public int getStudentOrderCount() { return studentOrderCount; }
|
||||||
|
public void setStudentOrderCount(int studentOrderCount) { this.studentOrderCount = studentOrderCount; }
|
||||||
|
public int getTeacherOrderCount() { return teacherOrderCount; }
|
||||||
|
public void setTeacherOrderCount(int teacherOrderCount) { this.teacherOrderCount = teacherOrderCount; }
|
||||||
|
public int getUnknownOrderCount() { return unknownOrderCount; }
|
||||||
|
public void setUnknownOrderCount(int unknownOrderCount) { this.unknownOrderCount = unknownOrderCount; }
|
||||||
|
public int getParentDailyCount() { return parentDailyCount; }
|
||||||
|
public void setParentDailyCount(int parentDailyCount) { this.parentDailyCount = parentDailyCount; }
|
||||||
|
public int getStudentDailyCount() { return studentDailyCount; }
|
||||||
|
public void setStudentDailyCount(int studentDailyCount) { this.studentDailyCount = studentDailyCount; }
|
||||||
|
public int getTeacherDailyCount() { return teacherDailyCount; }
|
||||||
|
public void setTeacherDailyCount(int teacherDailyCount) { this.teacherDailyCount = teacherDailyCount; }
|
||||||
|
public int getUnknownDailyCount() { return unknownDailyCount; }
|
||||||
|
public void setUnknownDailyCount(int unknownDailyCount) { this.unknownDailyCount = unknownDailyCount; }
|
||||||
|
public int getParentDailyOrderCount() { return parentDailyOrderCount; }
|
||||||
|
public void setParentDailyOrderCount(int parentDailyOrderCount) { this.parentDailyOrderCount = parentDailyOrderCount; }
|
||||||
|
public int getStudentDailyOrderCount() { return studentDailyOrderCount; }
|
||||||
|
public void setStudentDailyOrderCount(int studentDailyOrderCount) { this.studentDailyOrderCount = studentDailyOrderCount; }
|
||||||
|
public int getTeacherDailyOrderCount() { return teacherDailyOrderCount; }
|
||||||
|
public void setTeacherDailyOrderCount(int teacherDailyOrderCount) { this.teacherDailyOrderCount = teacherDailyOrderCount; }
|
||||||
|
public int getUnknownDailyOrderCount() { return unknownDailyOrderCount; }
|
||||||
|
public void setUnknownDailyOrderCount(int unknownDailyOrderCount) { this.unknownDailyOrderCount = unknownDailyOrderCount; }
|
||||||
|
public int getIntentionCount() { return intentionCount; }
|
||||||
|
public void setIntentionCount(int intentionCount) { this.intentionCount = intentionCount; }
|
||||||
|
public int getActiveQuoteCount() { return activeQuoteCount; }
|
||||||
|
public void setActiveQuoteCount(int activeQuoteCount) { this.activeQuoteCount = activeQuoteCount; }
|
||||||
|
public int getPassiveQuoteCount() { return passiveQuoteCount; }
|
||||||
|
public void setPassiveQuoteCount(int passiveQuoteCount) { this.passiveQuoteCount = passiveQuoteCount; }
|
||||||
|
public int getNoQuoteCount() { return noQuoteCount; }
|
||||||
|
public void setNoQuoteCount(int noQuoteCount) { this.noQuoteCount = noQuoteCount; }
|
||||||
|
public int getDeletedQuoteCount() { return deletedQuoteCount; }
|
||||||
|
public void setDeletedQuoteCount(int deletedQuoteCount) { this.deletedQuoteCount = deletedQuoteCount; }
|
||||||
|
public int getGradeCount() { return gradeCount; }
|
||||||
|
public void setGradeCount(int gradeCount) { this.gradeCount = gradeCount; }
|
||||||
|
public int getPrimaryCount() { return primaryCount; }
|
||||||
|
public void setPrimaryCount(int primaryCount) { this.primaryCount = primaryCount; }
|
||||||
|
public int getMiddleCount() { return middleCount; }
|
||||||
|
public void setMiddleCount(int middleCount) { this.middleCount = middleCount; }
|
||||||
|
public int getHighCount() { return highCount; }
|
||||||
|
public void setHighCount(int highCount) { this.highCount = highCount; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组级统计
|
||||||
|
*/
|
||||||
|
public static class GroupStatistics extends BaseStatistics {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签级统计
|
||||||
|
*/
|
||||||
|
public static class TagStatistics extends BaseStatistics {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组和标签累加器
|
||||||
|
*/
|
||||||
|
public static class GroupTagAccumulator {
|
||||||
|
private final Map<String, GroupStatistics> groupStatsMap = new LinkedHashMap<>();
|
||||||
|
private final Map<String, Map<String, TagStatistics>> tagStatsMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public GroupStatistics getGroupStats(String groupName) {
|
||||||
|
return groupStatsMap.computeIfAbsent(groupName, k -> new GroupStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStatistics getTagStats(String groupName, String tagName) {
|
||||||
|
Map<String, TagStatistics> tagMap = tagStatsMap.computeIfAbsent(groupName, k -> new LinkedHashMap<>());
|
||||||
|
return tagMap.computeIfAbsent(tagName, k -> new TagStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, GroupStatistics> getGroupStatsMap() {
|
||||||
|
return groupStatsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, TagStatistics>> getTagStatsMap() {
|
||||||
|
return tagStatsMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户导出数据Mapper
|
* 客户导出数据Mapper
|
||||||
|
|
@ -59,7 +60,69 @@ public interface CustomerExportDataMapper extends BaseMapper<CustomerExportData>
|
||||||
);
|
);
|
||||||
|
|
||||||
Long selectByFinishDate(
|
Long selectByFinishDate(
|
||||||
@Param("corpId") String corpId,@Param("date") Date date,@Param("attr") String attr);
|
@Param("corpId") String corpId,@Param("date") Date date,
|
||||||
|
@Param("attr") String attr,@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询及时单数量(根据成交日期和订单状态)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 成交日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @return 及时单数量
|
||||||
|
*/
|
||||||
|
Long selectTimelyOrderCount(
|
||||||
|
@Param("corpId") String corpId, @Param("date") Date date,
|
||||||
|
@Param("attr") String attr,@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询非及时单数量(根据成交日期和订单状态)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 成交日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @return 非及时单数量
|
||||||
|
*/
|
||||||
|
Long selectNonTimelyOrderCount(
|
||||||
|
@Param("corpId") String corpId, @Param("date") Date date,
|
||||||
|
@Param("attr") String attr,@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询成单数(根据成交日期、组字段和标签值)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 成交日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @param tagValue 标签值
|
||||||
|
* @return 成单数
|
||||||
|
*/
|
||||||
|
Long selectByFinishDateAndTag(
|
||||||
|
@Param("corpId") String corpId, @Param("date") Date date,
|
||||||
|
@Param("attr") String attr, @Param("tagValue") String tagValue,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询及时单数量(根据成交日期、订单状态和标签值)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 成交日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @param tagValue 标签值
|
||||||
|
* @return 及时单数量
|
||||||
|
*/
|
||||||
|
Long selectTimelyOrderCountByTag(
|
||||||
|
@Param("corpId") String corpId, @Param("date") Date date,
|
||||||
|
@Param("attr") String attr, @Param("tagValue") String tagValue,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询非及时单数量(根据成交日期、订单状态和标签值)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 成交日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @param tagValue 标签值
|
||||||
|
* @return 非及时单数量
|
||||||
|
*/
|
||||||
|
Long selectNonTimelyOrderCountByTag(
|
||||||
|
@Param("corpId") String corpId, @Param("date") Date date,
|
||||||
|
@Param("attr") String attr, @Param("tagValue") String tagValue,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计客户导出数据VO数量(用于异步导出)
|
* 统计客户导出数据VO数量(用于异步导出)
|
||||||
|
|
@ -94,5 +157,88 @@ public interface CustomerExportDataMapper extends BaseMapper<CustomerExportData>
|
||||||
@Param("offset") int offset,
|
@Param("offset") int offset,
|
||||||
@Param("limit") int limit
|
@Param("limit") int limit
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期范围查询成单数(根据finish_date)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param attr 组字段名(如tag_group11)
|
||||||
|
* @return 成单数
|
||||||
|
*/
|
||||||
|
Long selectOrderCountByFinishDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("attr") String attr,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期范围查询及时单数量(根据finish_date)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @return 及时单数量
|
||||||
|
*/
|
||||||
|
Long selectTimelyOrderCountByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("attr") String attr,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期范围查询非及时单数量(根据finish_date)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param attr 组字段名
|
||||||
|
* @return 非及时单数量
|
||||||
|
*/
|
||||||
|
Long selectNonTimelyOrderCountByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("attr") String attr,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询所有组的成单数(根据finish_date)
|
||||||
|
* 一次查询返回所有组的成单数,避免N+1查询问题
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期(可为null,表示不限制)
|
||||||
|
* @param endDate 结束日期(可为null,表示不限制)
|
||||||
|
* @return Map格式:key=组字段名,value=成单数
|
||||||
|
*/
|
||||||
|
Map<String, Object> selectOrderCountBatchByFinishDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询所有组的及时单数量(根据finish_date)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return Map格式:key=组字段名,value=及时单数量
|
||||||
|
*/
|
||||||
|
Map<String, Object> selectTimelyOrderCountBatchByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,@Param("successFlags") List<String> successFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询所有组的非及时单数量(根据finish_date)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return Map格式:key=组字段名,value=非及时单数量
|
||||||
|
*/
|
||||||
|
Map<String, Object> selectNonTimelyOrderCountBatchByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,@Param("successFlags") List<String> successFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,21 @@ public interface CustomerStatisticsDataMapper extends BaseMapper<CustomerStatist
|
||||||
@Param("indicatorName") String indicatorName
|
@Param("indicatorName") String indicatorName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期范围查询周数据(修复跨年周问题)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param indicatorName 指标名称
|
||||||
|
* @return 客户统计数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsData> selectDailyDataByWeekRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("indicatorName") String indicatorName
|
||||||
|
);
|
||||||
|
|
||||||
List<CustomerStatisticsData> selectDailyDataByMonth(
|
List<CustomerStatisticsData> selectDailyDataByMonth(
|
||||||
@Param("corpId") String corpId,
|
@Param("corpId") String corpId,
|
||||||
@Param("yearMonth") String yearMonth,
|
@Param("yearMonth") String yearMonth,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.ruoyi.excel.wecom.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.ruoyi.excel.wecom.domain.CustomerStatisticsDataV2;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户统计数据V2 Mapper接口
|
||||||
|
* 支持标签级成本,行列转换存储
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface CustomerStatisticsDataV2Mapper extends BaseMapper<CustomerStatisticsDataV2> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据企业ID、日期、组名、标签名查询数据
|
||||||
|
*/
|
||||||
|
CustomerStatisticsDataV2 selectByCorpDateGroupTag(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("curDate") Date curDate,
|
||||||
|
@Param("groupName") String groupName,
|
||||||
|
@Param("tagName") String tagName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询组级数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectGroupLevelList(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询标签级数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectTagLevelList(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("groupName") String groupName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据企业ID、日期、组名查询组级数据
|
||||||
|
*/
|
||||||
|
CustomerStatisticsDataV2 selectGroupLevelByCorpDateGroup(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("curDate") Date curDate,
|
||||||
|
@Param("groupName") String groupName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据企业ID、日期、组名查询标签级数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectTagLevelByCorpDateGroup(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("curDate") Date curDate,
|
||||||
|
@Param("groupName") String groupName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入数据
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("list") List<CustomerStatisticsDataV2> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定日期范围的数据
|
||||||
|
*/
|
||||||
|
int deleteByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询树状结构数据(组+标签)
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectTreeData(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据筛选条件查询数据列表(支持按组、标签筛选)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectListByFilter(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("groupName") String groupName,
|
||||||
|
@Param("tagName") String tagName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按日期范围聚合查询(支持按组、标签筛选)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 聚合后的数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectAggregatedByDateRange(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("startDate") Date startDate,
|
||||||
|
@Param("endDate") Date endDate,
|
||||||
|
@Param("groupName") String groupName,
|
||||||
|
@Param("tagName") String tagName
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有数据并聚合(支持按组、标签筛选)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 聚合后的数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectAllAggregated(
|
||||||
|
@Param("corpId") String corpId,
|
||||||
|
@Param("groupName") String groupName,
|
||||||
|
@Param("tagName") String tagName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
package com.ruoyi.excel.wecom.service;
|
||||||
|
|
||||||
|
import com.ruoyi.excel.wecom.domain.CustomerStatisticsDataV2;
|
||||||
|
import com.ruoyi.excel.wecom.domain.dto.TagTreeDTO;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户统计数据V2 Service接口
|
||||||
|
* 支持标签级成本,行列转换存储
|
||||||
|
*/
|
||||||
|
public interface ICustomerStatisticsDataV2Service {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询客户统计数据V2列表
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return 客户统计数据V2列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectCustomerStatisticsDataV2List(
|
||||||
|
String corpId, Date startDate, Date endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询树状结构数据(组+标签)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return 树状结构数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectTreeData(
|
||||||
|
String corpId, Date startDate, Date endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询标签树(只返回组-标签结构,不返回统计数据)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return 标签树列表
|
||||||
|
*/
|
||||||
|
List<TagTreeDTO> selectTagTree(String corpId, Date startDate, Date endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询客户统计数据V2列表(支持按组、标签筛选)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 客户统计数据V2列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectCustomerStatisticsDataV2List(
|
||||||
|
String corpId, Date startDate, Date endDate, String groupName, String tagName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询客户统计数据V2
|
||||||
|
* @param id 主键ID
|
||||||
|
* @return 客户统计数据V2
|
||||||
|
*/
|
||||||
|
CustomerStatisticsDataV2 selectCustomerStatisticsDataV2ById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增客户统计数据V2
|
||||||
|
* @param data 客户统计数据V2
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int insertCustomerStatisticsDataV2(CustomerStatisticsDataV2 data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改客户统计数据V2
|
||||||
|
* @param data 客户统计数据V2
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int updateCustomerStatisticsDataV2(CustomerStatisticsDataV2 data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除客户统计数据V2
|
||||||
|
* @param ids 需要删除的数据ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int deleteCustomerStatisticsDataV2ByIds(Long[] ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录入成本(支持组级和标签级)
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 日期
|
||||||
|
* @param groupName 组名
|
||||||
|
* @param tagName 标签名(null表示组级)
|
||||||
|
* @param costValue 成本值
|
||||||
|
* @param inputType 录入类型:total-总成本,single-单条成本
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int inputCost(String corpId, Date date, String groupName, String tagName,
|
||||||
|
BigDecimal costValue, String inputType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算指定日期的统计数据
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param date 日期
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int recalculateStatistics(String corpId, Date date);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算指定日期范围的统计数据
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
int recalculateStatisticsRange(String corpId, Date startDate, Date endDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按周聚合查询
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param year 年份
|
||||||
|
* @param week 周数
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 聚合后的数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectByWeekAggregation(
|
||||||
|
String corpId, Integer year, Integer week, String groupName, String tagName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按月聚合查询
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param yearMonth 年月(格式:yyyy-MM)
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 聚合后的数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectByMonthAggregation(
|
||||||
|
String corpId, String yearMonth, String groupName, String tagName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有数据的聚合
|
||||||
|
* @param corpId 企业ID
|
||||||
|
* @param groupName 组名(可选)
|
||||||
|
* @param tagName 标签名(可选)
|
||||||
|
* @return 聚合后的数据列表
|
||||||
|
*/
|
||||||
|
List<CustomerStatisticsDataV2> selectAllAggregation(
|
||||||
|
String corpId, String groupName, String tagName);
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.temporal.WeekFields;
|
import java.time.temporal.WeekFields;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -245,8 +246,131 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CustomerStatisticsDataVO> selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName) {
|
public List<CustomerStatisticsDataVO> selectByWeekAggregation(String corpId, Integer year, Integer week, String indicatorName) {
|
||||||
List<CustomerStatisticsData> dailyDataList = customerStatisticsDataMapper.selectDailyDataByWeek(corpId, year, week, indicatorName);
|
log.info("========== V1周聚合查询开始 ==========");
|
||||||
return aggregateDataList(dailyDataList, year, week, null);
|
log.info("参数: corpId={}, year={}, week={}, indicatorName={}", corpId, year, week, indicatorName);
|
||||||
|
|
||||||
|
// 计算周范围(修复跨年问题)
|
||||||
|
Date[] weekRange = calculateWeekRangeFixed(year, week);
|
||||||
|
if (weekRange == null) {
|
||||||
|
// 如果周范围无效(跨年),返回空列表
|
||||||
|
log.info("周范围无效(跨年),返回空列表");
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
Date startDate = weekRange[0];
|
||||||
|
Date endDate = weekRange[1];
|
||||||
|
log.info("周日期范围: {} 至 {}", startDate, endDate);
|
||||||
|
|
||||||
|
// 使用日期范围查询(修复跨年周问题)
|
||||||
|
List<CustomerStatisticsData> dailyDataList = customerStatisticsDataMapper.selectDailyDataByWeekRange(corpId, startDate, endDate, indicatorName);
|
||||||
|
log.info("查询到原始数据: {}条", dailyDataList != null ? dailyDataList.size() : 0);
|
||||||
|
|
||||||
|
if (dailyDataList != null && !dailyDataList.isEmpty()) {
|
||||||
|
// 按指标分组统计
|
||||||
|
Map<String, Long> indicatorCount = dailyDataList.stream()
|
||||||
|
.collect(Collectors.groupingBy(CustomerStatisticsData::getIndicatorName, Collectors.counting()));
|
||||||
|
log.info("指标分布: {}", indicatorCount);
|
||||||
|
|
||||||
|
// 查看第一条数据的详细信息
|
||||||
|
CustomerStatisticsData first = dailyDataList.get(0);
|
||||||
|
log.info("第一条数据: date={}, indicator={}, ntfGroup={}, ofhGroup={}, wa1Group={}, xb1Group={}",
|
||||||
|
first.getCurDate(), first.getIndicatorName(),
|
||||||
|
first.getNtfGroup(), first.getOfhGroup(), first.getWa1Group(), first.getXb1Group());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CustomerStatisticsDataVO> result = aggregateDataList(dailyDataList, year, week, null);
|
||||||
|
log.info("聚合后结果: {}条", result != null ? result.size() : 0);
|
||||||
|
log.info("========== V1周聚合查询结束 ==========");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算周范围(修复跨年问题)
|
||||||
|
* 规则:
|
||||||
|
* 1. 周的第一天是周一(不是周日)
|
||||||
|
* 2. 第一周:从1月1日开始,到1月1日所在周的周日结束
|
||||||
|
* - 如果1月1日是周日,第一周只有1天(1月1日当天)
|
||||||
|
* 3. 最后一周:从最后一周的周一(12月31日往前推)开始,到12月31日结束
|
||||||
|
* - 如果12月31日是周一,最后一周只有1天(12月31日当天)
|
||||||
|
* 4. 其他周:按正常的周一到周日计算
|
||||||
|
*/
|
||||||
|
private Date[] calculateWeekRangeFixed(int year, int week) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.set(Calendar.YEAR, year);
|
||||||
|
calendar.set(Calendar.MONTH, Calendar.JANUARY);
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
calendar.set(Calendar.MINUTE, 0);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// 获取1月1日是星期几(1=周日,2=周一,...,7=周六)
|
||||||
|
int jan1DayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||||
|
// 转换为:0=周日,1=周一,...,6=周六
|
||||||
|
int jan1Weekday = (jan1DayOfWeek == Calendar.SUNDAY) ? 0 : (jan1DayOfWeek - 1);
|
||||||
|
|
||||||
|
// 计算12月31日
|
||||||
|
Calendar dec31Cal = Calendar.getInstance();
|
||||||
|
dec31Cal.set(Calendar.YEAR, year);
|
||||||
|
dec31Cal.set(Calendar.MONTH, Calendar.DECEMBER);
|
||||||
|
dec31Cal.set(Calendar.DAY_OF_MONTH, 31);
|
||||||
|
dec31Cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
dec31Cal.set(Calendar.MINUTE, 0);
|
||||||
|
dec31Cal.set(Calendar.SECOND, 0);
|
||||||
|
dec31Cal.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// 第一周:从1月1日开始
|
||||||
|
if (week == 1) {
|
||||||
|
Date startDate = calendar.getTime();
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
// 如果1月1日是周日(0),则第一周只有1天(当天)
|
||||||
|
// 否则,计算到本周日
|
||||||
|
int daysToSunday;
|
||||||
|
if (jan1Weekday == 0) {
|
||||||
|
daysToSunday = 0; // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
daysToSunday = 7 - jan1Weekday; // 到本周日
|
||||||
|
}
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, daysToSunday);
|
||||||
|
Date endDate = calendar.getTime();
|
||||||
|
return new Date[]{startDate, endDate};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算第一周结束日期(1月1日所在周的周日)
|
||||||
|
Calendar firstWeekEndCal = (Calendar) calendar.clone();
|
||||||
|
int firstWeekDays;
|
||||||
|
if (jan1Weekday == 0) {
|
||||||
|
firstWeekDays = 0; // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
firstWeekDays = 7 - jan1Weekday; // 到本周日
|
||||||
|
}
|
||||||
|
firstWeekEndCal.add(Calendar.DAY_OF_MONTH, firstWeekDays);
|
||||||
|
|
||||||
|
// 计算第二周开始日期(第一周结束后的周一)
|
||||||
|
Calendar secondWeekStartCal = (Calendar) firstWeekEndCal.clone();
|
||||||
|
secondWeekStartCal.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
|
||||||
|
// 计算目标周的开始和结束
|
||||||
|
// 从第二周开始,每周都是周一到周日
|
||||||
|
Calendar targetWeekStartCal = (Calendar) secondWeekStartCal.clone();
|
||||||
|
targetWeekStartCal.add(Calendar.WEEK_OF_YEAR, week - 2);
|
||||||
|
|
||||||
|
// 如果开始日期已经跨年了(大于12月31日),则返回null
|
||||||
|
if (targetWeekStartCal.after(dec31Cal)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date startDate = targetWeekStartCal.getTime();
|
||||||
|
|
||||||
|
Calendar targetWeekEndCal = (Calendar) targetWeekStartCal.clone();
|
||||||
|
targetWeekEndCal.add(Calendar.DAY_OF_MONTH, 6);
|
||||||
|
Date endDate = targetWeekEndCal.getTime();
|
||||||
|
|
||||||
|
// 如果目标周的结束超过了12月31日,则调整到12月31日
|
||||||
|
if (targetWeekEndCal.after(dec31Cal)) {
|
||||||
|
endDate = dec31Cal.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date[]{startDate, endDate};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -422,11 +546,19 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 针对成单数和进粉数添加详细日志
|
||||||
|
boolean isTargetIndicator = indicatorName.equals("成单数(当日)") || indicatorName.equals("进粉数(当日)");
|
||||||
|
if (isTargetIndicator) {
|
||||||
|
log.info("========== 开始聚合指标: {} ==========", indicatorName);
|
||||||
|
}
|
||||||
|
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
BigDecimal sum = BigDecimal.ZERO;
|
BigDecimal sum = BigDecimal.ZERO;
|
||||||
boolean hasValue = false;
|
boolean hasValue = false;
|
||||||
|
|
||||||
for (CustomerStatisticsData data : dailyList) {
|
for (CustomerStatisticsData data : dailyList) {
|
||||||
String value = getFieldValue(data, field);
|
String value = getFieldValue(data, field);
|
||||||
|
|
||||||
if (value != null && !value.trim().isEmpty()) {
|
if (value != null && !value.trim().isEmpty()) {
|
||||||
String cleanValue = value.replace("%", "").trim();
|
String cleanValue = value.replace("%", "").trim();
|
||||||
if (indicatorName.equals("总成本(当日)") && !cleanValue.equals("需手工填写")) {
|
if (indicatorName.equals("总成本(当日)") && !cleanValue.equals("需手工填写")) {
|
||||||
|
|
@ -438,20 +570,31 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
if (cleanValue.matches("-?\\d+(\\.\\d+)?")) {
|
if (cleanValue.matches("-?\\d+(\\.\\d+)?")) {
|
||||||
try {
|
try {
|
||||||
sum = sum.add(new BigDecimal(cleanValue));
|
BigDecimal decimalValue = new BigDecimal(cleanValue);
|
||||||
|
sum = sum.add(decimalValue);
|
||||||
hasValue = true;
|
hasValue = true;
|
||||||
|
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
|
if (isTargetIndicator) {
|
||||||
|
log.info(" 解析失败: {}", cleanValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasValue) {
|
if (hasValue) {
|
||||||
setFieldValue(result, field, sum.setScale(2, RoundingMode.HALF_UP).toString());
|
setFieldValue(result, field, sum.setScale(2, RoundingMode.HALF_UP).toString());
|
||||||
log.info("指标 {} 字段 {} 最终聚合值: {}", indicatorName, field, sum.setScale(2, RoundingMode.HALF_UP));
|
if (isTargetIndicator) {
|
||||||
|
log.info("指标 {} 字段 {} 最终聚合值: {}", indicatorName, field, sum.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
}
|
||||||
} else if (indicatorName.equals("总成本(当日)")) {
|
} else if (indicatorName.equals("总成本(当日)")) {
|
||||||
setFieldValue(result, field, "0");
|
setFieldValue(result, field, "0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTargetIndicator) {
|
||||||
|
log.info("========== 结束聚合指标: {} ==========", indicatorName);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -469,6 +612,8 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
CustomerStatisticsDataVO keHuShuXingData = indicatorMap.get("客户属性数量(当日)");
|
CustomerStatisticsDataVO keHuShuXingData = indicatorMap.get("客户属性数量(当日)");
|
||||||
CustomerStatisticsDataVO xueShengZhanBiData = indicatorMap.get("学生占比(当日)");
|
CustomerStatisticsDataVO xueShengZhanBiData = indicatorMap.get("学生占比(当日)");
|
||||||
CustomerStatisticsDataVO weiZhiZhanBiData = indicatorMap.get("未知占比(当日)");
|
CustomerStatisticsDataVO weiZhiZhanBiData = indicatorMap.get("未知占比(当日)");
|
||||||
|
CustomerStatisticsDataVO laoshiZhanBiData = indicatorMap.get("老师占比(当日)");
|
||||||
|
|
||||||
CustomerStatisticsDataVO zhuDongBaoJiaZhanBiData = indicatorMap.get("主动报价占比(当日)");
|
CustomerStatisticsDataVO zhuDongBaoJiaZhanBiData = indicatorMap.get("主动报价占比(当日)");
|
||||||
CustomerStatisticsDataVO beiDongBaoJiaZhanBiData = indicatorMap.get("被动报价占比(当日)");
|
CustomerStatisticsDataVO beiDongBaoJiaZhanBiData = indicatorMap.get("被动报价占比(当日)");
|
||||||
CustomerStatisticsDataVO weiKaiKouBaoJiaZhanBiData = indicatorMap.get("未开口报价占比(当日)");
|
CustomerStatisticsDataVO weiKaiKouBaoJiaZhanBiData = indicatorMap.get("未开口报价占比(当日)");
|
||||||
|
|
@ -486,6 +631,7 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
CustomerStatisticsDataVO feiJiShiDanShuLiangData = indicatorMap.get("非及时单数量(当日)");
|
CustomerStatisticsDataVO feiJiShiDanShuLiangData = indicatorMap.get("非及时单数量(当日)");
|
||||||
CustomerStatisticsDataVO jiaZhangShuLiangData = indicatorMap.get("家长数量(当日)");
|
CustomerStatisticsDataVO jiaZhangShuLiangData = indicatorMap.get("家长数量(当日)");
|
||||||
CustomerStatisticsDataVO xueShengShuLiangData = indicatorMap.get("学生数量(当日)");
|
CustomerStatisticsDataVO xueShengShuLiangData = indicatorMap.get("学生数量(当日)");
|
||||||
|
CustomerStatisticsDataVO laoshiShuLiangData = indicatorMap.get("老师数量(当日)");
|
||||||
CustomerStatisticsDataVO weiZhiShuLiangData = indicatorMap.get("未知数量(当日)");
|
CustomerStatisticsDataVO weiZhiShuLiangData = indicatorMap.get("未知数量(当日)");
|
||||||
CustomerStatisticsDataVO zhuDongBaoJiaShuLiangData = indicatorMap.get("主动报价数量(当日)");
|
CustomerStatisticsDataVO zhuDongBaoJiaShuLiangData = indicatorMap.get("主动报价数量(当日)");
|
||||||
CustomerStatisticsDataVO beiDongBaoJiaShuLiangData = indicatorMap.get("被动报价数量(当日)");
|
CustomerStatisticsDataVO beiDongBaoJiaShuLiangData = indicatorMap.get("被动报价数量(当日)");
|
||||||
|
|
@ -497,6 +643,11 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
CustomerStatisticsDataVO jiaZhangChuDanShuLiangData = indicatorMap.get("家长出单数量(当日)");
|
CustomerStatisticsDataVO jiaZhangChuDanShuLiangData = indicatorMap.get("家长出单数量(当日)");
|
||||||
CustomerStatisticsDataVO xueShengChuDanShuLiangData = indicatorMap.get("学生出单数量(当日)");
|
CustomerStatisticsDataVO xueShengChuDanShuLiangData = indicatorMap.get("学生出单数量(当日)");
|
||||||
|
|
||||||
|
CustomerStatisticsDataVO jiazhangChuDanLvData = indicatorMap.get("家长出单率(当日)");
|
||||||
|
CustomerStatisticsDataVO jiazhangJishiDanShuLiangData = indicatorMap.get("家长即时单数量(当日)");
|
||||||
|
CustomerStatisticsDataVO xueshengChuDanLvData = indicatorMap.get("学生出单率(当日)");
|
||||||
|
CustomerStatisticsDataVO xueshengJishiDanShuLiangData = indicatorMap.get("学生即时单数量(当日)");
|
||||||
|
|
||||||
String[] groupFields = {"ntfGroup", "ofhGroup", "pswGroup", "wa1Group", "xb1Group",
|
String[] groupFields = {"ntfGroup", "ofhGroup", "pswGroup", "wa1Group", "xb1Group",
|
||||||
"yc1Group", "zd1Group", "aaGroup", "acGroup", "adGroup", "aeGroup"};
|
"yc1Group", "zd1Group", "aaGroup", "acGroup", "adGroup", "aeGroup"};
|
||||||
|
|
||||||
|
|
@ -583,16 +734,16 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jiShiDanZhanBiData != null && jiShiDanShuLiangData != null && jinFenShuData != null) {
|
if (jiShiDanZhanBiData != null && jiShiDanShuLiangData != null && chengDanShuData != null) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String jiShiDanStr = getFieldValue(jiShiDanShuLiangData, field);
|
String jiShiDanStr = getFieldValue(jiShiDanShuLiangData, field);
|
||||||
String jinFenShuStr = getFieldValue(jinFenShuData, field);
|
String chengDanShuStr = getFieldValue(chengDanShuData, field);
|
||||||
if (jiShiDanStr != null && jinFenShuStr != null) {
|
if (jiShiDanStr != null && chengDanShuStr != null) {
|
||||||
try {
|
try {
|
||||||
BigDecimal jiShiDan = new BigDecimal(jiShiDanStr);
|
BigDecimal jiShiDan = new BigDecimal(jiShiDanStr);
|
||||||
BigDecimal jinFenShu = new BigDecimal(jinFenShuStr);
|
BigDecimal chengDanShu = new BigDecimal(chengDanShuStr);
|
||||||
if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) {
|
if (chengDanShu.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
BigDecimal zhanBi = jiShiDan.divide(jinFenShu, 4, RoundingMode.HALF_UP)
|
BigDecimal zhanBi = jiShiDan.divide(chengDanShu, 4, RoundingMode.HALF_UP)
|
||||||
.multiply(new BigDecimal("100"))
|
.multiply(new BigDecimal("100"))
|
||||||
.setScale(2, RoundingMode.HALF_UP);
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
setFieldValue(jiShiDanZhanBiData, field, zhanBi + "%");
|
setFieldValue(jiShiDanZhanBiData, field, zhanBi + "%");
|
||||||
|
|
@ -605,16 +756,16 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feiJiShiDanZhanBiData != null && feiJiShiDanShuLiangData != null && jinFenShuData != null) {
|
if (feiJiShiDanZhanBiData != null && feiJiShiDanShuLiangData != null && chengDanShuData != null) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String feiJiShiDanStr = getFieldValue(feiJiShiDanShuLiangData, field);
|
String feiJiShiDanStr = getFieldValue(feiJiShiDanShuLiangData, field);
|
||||||
String jinFenShuStr = getFieldValue(jinFenShuData, field);
|
String chengDanShuStr = getFieldValue(chengDanShuData, field);
|
||||||
if (feiJiShiDanStr != null && jinFenShuStr != null) {
|
if (feiJiShiDanStr != null && chengDanShuStr != null) {
|
||||||
try {
|
try {
|
||||||
BigDecimal feiJiShiDan = new BigDecimal(feiJiShiDanStr);
|
BigDecimal feiJiShiDan = new BigDecimal(feiJiShiDanStr);
|
||||||
BigDecimal jinFenShu = new BigDecimal(jinFenShuStr);
|
BigDecimal chengDanShu = new BigDecimal(chengDanShuStr);
|
||||||
if (jinFenShu.compareTo(BigDecimal.ZERO) > 0) {
|
if (chengDanShu.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
BigDecimal zhanBi = feiJiShiDan.divide(jinFenShu, 4, RoundingMode.HALF_UP)
|
BigDecimal zhanBi = feiJiShiDan.divide(chengDanShu, 4, RoundingMode.HALF_UP)
|
||||||
.multiply(new BigDecimal("100"))
|
.multiply(new BigDecimal("100"))
|
||||||
.setScale(2, RoundingMode.HALF_UP);
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
setFieldValue(feiJiShiDanZhanBiData, field, zhanBi + "%");
|
setFieldValue(feiJiShiDanZhanBiData, field, zhanBi + "%");
|
||||||
|
|
@ -627,23 +778,25 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keHuShuXingData != null && jiaZhangShuLiangData != null && xueShengShuLiangData != null && weiZhiShuLiangData != null) {
|
if (keHuShuXingData != null && jiaZhangShuLiangData != null && laoshiShuLiangData != null && xueShengShuLiangData != null && weiZhiShuLiangData != null) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
||||||
String xueShengStr = getFieldValue(xueShengShuLiangData, field);
|
String xueShengStr = getFieldValue(xueShengShuLiangData, field);
|
||||||
|
String laoShiStr = getFieldValue(laoshiShuLiangData, field);
|
||||||
String weiZhiStr = getFieldValue(weiZhiShuLiangData, field);
|
String weiZhiStr = getFieldValue(weiZhiShuLiangData, field);
|
||||||
try {
|
try {
|
||||||
BigDecimal jiaZhang = jiaZhangStr != null ? new BigDecimal(jiaZhangStr) : BigDecimal.ZERO;
|
BigDecimal jiaZhang = jiaZhangStr != null ? new BigDecimal(jiaZhangStr) : BigDecimal.ZERO;
|
||||||
BigDecimal xueSheng = xueShengStr != null ? new BigDecimal(xueShengStr) : BigDecimal.ZERO;
|
BigDecimal xueSheng = xueShengStr != null ? new BigDecimal(xueShengStr) : BigDecimal.ZERO;
|
||||||
|
BigDecimal laoshi = laoShiStr != null ? new BigDecimal(laoShiStr) : BigDecimal.ZERO;
|
||||||
BigDecimal weiZhi = weiZhiStr != null ? new BigDecimal(weiZhiStr) : BigDecimal.ZERO;
|
BigDecimal weiZhi = weiZhiStr != null ? new BigDecimal(weiZhiStr) : BigDecimal.ZERO;
|
||||||
BigDecimal total = jiaZhang.add(xueSheng).add(weiZhi);
|
BigDecimal total = jiaZhang.add(xueSheng).add(weiZhi).add(laoshi);
|
||||||
setFieldValue(keHuShuXingData, field, total.setScale(0, RoundingMode.HALF_UP).toString());
|
setFieldValue(keHuShuXingData, field, total.setScale(0, RoundingMode.HALF_UP).toString());
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jiaZhangZhanBiData != null && jiaZhangShuLiangData != null && keHuShuXingData != null) {
|
if (jiaZhangZhanBiData != null && jiaZhangShuLiangData != null && keHuShuXingData != null ) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
||||||
String keHuShuXingStr = getFieldValue(keHuShuXingData, field);
|
String keHuShuXingStr = getFieldValue(keHuShuXingData, field);
|
||||||
|
|
@ -709,6 +862,29 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (laoshiZhanBiData != null && laoshiShuLiangData != null && keHuShuXingData != null) {
|
||||||
|
for (String field : groupFields) {
|
||||||
|
String laoshiStr = getFieldValue(laoshiShuLiangData, field);
|
||||||
|
String keHuShuXingStr = getFieldValue(keHuShuXingData, field);
|
||||||
|
if (laoshiStr != null && keHuShuXingStr != null) {
|
||||||
|
try {
|
||||||
|
BigDecimal laoshi = new BigDecimal(laoshiStr);
|
||||||
|
BigDecimal keHuShuXing = new BigDecimal(keHuShuXingStr);
|
||||||
|
if (keHuShuXing.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal zhanBi = laoshi.divide(keHuShuXing, 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(new BigDecimal("100"))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
setFieldValue(laoshiZhanBiData, field, zhanBi + "%");
|
||||||
|
} else {
|
||||||
|
setFieldValue(laoshiZhanBiData, field, "0%");
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (zhuDongBaoJiaZhanBiData != null && zhuDongBaoJiaShuLiangData != null && yiXiangDuShuLiangData != null) {
|
if (zhuDongBaoJiaZhanBiData != null && zhuDongBaoJiaShuLiangData != null && yiXiangDuShuLiangData != null) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String zhuDongStr = getFieldValue(zhuDongBaoJiaShuLiangData, field);
|
String zhuDongStr = getFieldValue(zhuDongBaoJiaShuLiangData, field);
|
||||||
|
|
@ -863,7 +1039,52 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jiaZhangChuDanZhanBiData != null && jiaZhangChuDanShuLiangData != null && jiaZhangShuLiangData != null) {
|
if (jiazhangChuDanLvData != null && jiaZhangShuLiangData != null && jiazhangJishiDanShuLiangData != null) {
|
||||||
|
for (String field : groupFields) {
|
||||||
|
String jiazhangJishidanStr = getFieldValue(jiazhangJishiDanShuLiangData, field);
|
||||||
|
String jiazhangShuLiangStr = getFieldValue(jiaZhangShuLiangData, field);
|
||||||
|
if (jiazhangJishidanStr != null && jiazhangShuLiangStr != null) {
|
||||||
|
try {
|
||||||
|
BigDecimal jiasZhangJishidan = new BigDecimal(jiazhangJishidanStr);
|
||||||
|
BigDecimal jiazhangshuliang = new BigDecimal(jiazhangShuLiangStr);
|
||||||
|
if (jiazhangshuliang.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal zhanBi = jiasZhangJishidan.divide(jiazhangshuliang, 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(new BigDecimal("100"))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
setFieldValue(jiazhangChuDanLvData, field, zhanBi + "%");
|
||||||
|
} else {
|
||||||
|
setFieldValue(jiazhangChuDanLvData, field, "0%");
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (xueshengChuDanLvData != null && xueShengShuLiangData != null && xueshengJishiDanShuLiangData != null) {
|
||||||
|
for (String field : groupFields) {
|
||||||
|
String xueshengJishidanStr = getFieldValue(xueshengJishiDanShuLiangData, field);
|
||||||
|
String xueshengShuLiangStr = getFieldValue(xueShengShuLiangData, field);
|
||||||
|
if (xueshengJishidanStr != null && xueshengShuLiangStr != null) {
|
||||||
|
try {
|
||||||
|
BigDecimal xueshengJishidan = new BigDecimal(xueshengJishidanStr);
|
||||||
|
BigDecimal xueshengshuliang = new BigDecimal(xueshengShuLiangStr);
|
||||||
|
if (xueshengshuliang.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
BigDecimal zhanBi = xueshengJishidan.divide(xueshengshuliang, 4, RoundingMode.HALF_UP)
|
||||||
|
.multiply(new BigDecimal("100"))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
setFieldValue(xueshengChuDanLvData, field, zhanBi + "%");
|
||||||
|
} else {
|
||||||
|
setFieldValue(xueshengChuDanLvData, field, "0%");
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if (jiaZhangChuDanZhanBiData != null && jiaZhangChuDanShuLiangData != null && jiaZhangShuLiangData != null) {
|
||||||
for (String field : groupFields) {
|
for (String field : groupFields) {
|
||||||
String jiaZhangChuDanStr = getFieldValue(jiaZhangChuDanShuLiangData, field);
|
String jiaZhangChuDanStr = getFieldValue(jiaZhangChuDanShuLiangData, field);
|
||||||
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
String jiaZhangStr = getFieldValue(jiaZhangShuLiangData, field);
|
||||||
|
|
@ -905,7 +1126,7 @@ public class CustomerStatisticsDataServiceImpl implements ICustomerStatisticsDat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal parseCostValue(String value) {
|
private BigDecimal parseCostValue(String value) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,718 @@
|
||||||
|
package com.ruoyi.excel.wecom.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.excel.wecom.domain.CustomerStatisticsDataV2;
|
||||||
|
import com.ruoyi.excel.wecom.domain.dto.TagTreeDTO;
|
||||||
|
import com.ruoyi.excel.wecom.mapper.CustomerExportDataMapper;
|
||||||
|
import com.ruoyi.excel.wecom.mapper.CustomerStatisticsDataV2Mapper;
|
||||||
|
import com.ruoyi.excel.wecom.service.ICustomerStatisticsDataV2Service;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户统计数据V2 Service业务层处理
|
||||||
|
* 支持标签级成本,行列转换存储
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class CustomerStatisticsDataV2ServiceImpl implements ICustomerStatisticsDataV2Service {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomerStatisticsDataV2Mapper dataV2Mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CustomerExportDataMapper exportDataMapper;
|
||||||
|
private List<String> finishFlag = Arrays.asList("已成交及时单9元+", "已成交非及时单9元+");
|
||||||
|
private List<String> timelyFinishFlag = Arrays.asList("已成交及时单9元+");
|
||||||
|
private List<String> noTimelyfinishFlag = Arrays.asList("已成交非及时单9元+");
|
||||||
|
/**
|
||||||
|
* 组名到数据库字段的映射
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> GROUP_NAME_TO_ATTR_MAP = new LinkedHashMap<>();
|
||||||
|
static {
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("投放", "tag_group1");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("公司孵化", "tag_group2");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("商务", "tag_group3");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("A1组", "tag_group10");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("B1组", "tag_group11");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("C1组", "tag_group12");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("D1组", "tag_group13");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("E1组", "tag_group14");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("自然流", "tag_group16");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("F1组", "tag_group17");
|
||||||
|
GROUP_NAME_TO_ATTR_MAP.put("G1组", "tag_group18");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectCustomerStatisticsDataV2List(
|
||||||
|
String corpId, Date startDate, Date endDate) {
|
||||||
|
return dataV2Mapper.selectGroupLevelList(corpId, startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectCustomerStatisticsDataV2List(
|
||||||
|
String corpId, Date startDate, Date endDate, String groupName, String tagName) {
|
||||||
|
return dataV2Mapper.selectListByFilter(corpId, startDate, endDate, groupName, tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TagTreeDTO> selectTagTree(String corpId, Date startDate, Date endDate) {
|
||||||
|
log.info("selectTagTree called with corpId={}, startDate={}, endDate={}", corpId, startDate, endDate);
|
||||||
|
|
||||||
|
// 查询所有数据,提取组-标签结构
|
||||||
|
List<CustomerStatisticsDataV2> allData = dataV2Mapper.selectTreeData(corpId, startDate, endDate);
|
||||||
|
log.info("selectTreeData returned {} records", allData.size());
|
||||||
|
|
||||||
|
// 按组名分组
|
||||||
|
Map<String, List<CustomerStatisticsDataV2>> groupDataMap = allData.stream()
|
||||||
|
.filter(d -> d.getGroupName() != null)
|
||||||
|
.collect(Collectors.groupingBy(CustomerStatisticsDataV2::getGroupName));
|
||||||
|
log.info("groupDataMap has {} groups", groupDataMap.size());
|
||||||
|
|
||||||
|
List<TagTreeDTO> result = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<CustomerStatisticsDataV2>> entry : groupDataMap.entrySet()) {
|
||||||
|
String groupName = entry.getKey();
|
||||||
|
List<CustomerStatisticsDataV2> groupDataList = entry.getValue();
|
||||||
|
|
||||||
|
// 创建组节点
|
||||||
|
TagTreeDTO groupNode = new TagTreeDTO();
|
||||||
|
groupNode.setId("group_" + groupName);
|
||||||
|
groupNode.setLabel(groupName);
|
||||||
|
groupNode.setType("group");
|
||||||
|
groupNode.setGroupName(groupName);
|
||||||
|
groupNode.setChildren(new ArrayList<>());
|
||||||
|
|
||||||
|
// 提取该组下的所有标签
|
||||||
|
Set<String> tagSet = new LinkedHashSet<>();
|
||||||
|
for (CustomerStatisticsDataV2 data : groupDataList) {
|
||||||
|
if (data.getTagName() != null && !data.getTagName().isEmpty()) {
|
||||||
|
tagSet.add(data.getTagName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建标签节点
|
||||||
|
for (String tagName : tagSet) {
|
||||||
|
TagTreeDTO tagNode = new TagTreeDTO();
|
||||||
|
tagNode.setId("tag_" + groupName + "_" + tagName);
|
||||||
|
tagNode.setLabel(tagName);
|
||||||
|
tagNode.setType("tag");
|
||||||
|
tagNode.setGroupName(groupName);
|
||||||
|
tagNode.setTagName(tagName);
|
||||||
|
tagNode.setCount(1);
|
||||||
|
groupNode.getChildren().add(tagNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNode.setCount(groupNode.getChildren().size());
|
||||||
|
result.add(groupNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按组名排序
|
||||||
|
result.sort(Comparator.comparing(TagTreeDTO::getLabel));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectTreeData(
|
||||||
|
String corpId, Date startDate, Date endDate) {
|
||||||
|
// 1. 查询所有数据(组级+标签级)
|
||||||
|
List<CustomerStatisticsDataV2> allData = dataV2Mapper.selectTreeData(
|
||||||
|
corpId, startDate, endDate);
|
||||||
|
|
||||||
|
// 2. 构建树状结构
|
||||||
|
Map<Long, CustomerStatisticsDataV2> groupMap = new LinkedHashMap<>();
|
||||||
|
List<CustomerStatisticsDataV2> result = new ArrayList<>();
|
||||||
|
|
||||||
|
// 先处理组级数据
|
||||||
|
for (CustomerStatisticsDataV2 data : allData) {
|
||||||
|
if (data.getDataLevel() != null && data.getDataLevel() == 1) {
|
||||||
|
data.setChildren(new ArrayList<>());
|
||||||
|
data.setLeaf(false);
|
||||||
|
groupMap.put(data.getId(), data);
|
||||||
|
result.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再处理标签级数据,挂载到对应组下
|
||||||
|
for (CustomerStatisticsDataV2 data : allData) {
|
||||||
|
if (data.getDataLevel() != null && data.getDataLevel() == 2) {
|
||||||
|
data.setLeaf(true);
|
||||||
|
CustomerStatisticsDataV2 parent = groupMap.get(data.getParentId());
|
||||||
|
if (parent != null) {
|
||||||
|
parent.getChildren().add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerStatisticsDataV2 selectCustomerStatisticsDataV2ById(Long id) {
|
||||||
|
return dataV2Mapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int insertCustomerStatisticsDataV2(CustomerStatisticsDataV2 data) {
|
||||||
|
return dataV2Mapper.insert(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updateCustomerStatisticsDataV2(CustomerStatisticsDataV2 data) {
|
||||||
|
return dataV2Mapper.updateById(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deleteCustomerStatisticsDataV2ByIds(Long[] ids) {
|
||||||
|
int count = 0;
|
||||||
|
for (Long id : ids) {
|
||||||
|
count += dataV2Mapper.deleteById(id);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int inputCost(String corpId, Date date, String groupName, String tagName,
|
||||||
|
BigDecimal costValue, String inputType) {
|
||||||
|
try {
|
||||||
|
// 1. 查询目标记录
|
||||||
|
CustomerStatisticsDataV2 data = dataV2Mapper.selectByCorpDateGroupTag(
|
||||||
|
corpId, date, groupName, tagName);
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
throw new RuntimeException("未找到对应的统计数据:" + groupName +
|
||||||
|
(tagName != null ? "/" + tagName : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 计算实际总成本
|
||||||
|
BigDecimal actualTotalCost;
|
||||||
|
boolean isSingleInput = "single".equals(inputType);
|
||||||
|
if (isSingleInput) {
|
||||||
|
// 单条成本 × 进粉数 = 总成本
|
||||||
|
actualTotalCost = costValue.multiply(
|
||||||
|
new BigDecimal(data.getCustomerCount()));
|
||||||
|
data.setSingleCost(costValue);
|
||||||
|
data.setTotalCost(actualTotalCost); // 更新总成本
|
||||||
|
} else {
|
||||||
|
actualTotalCost = costValue;
|
||||||
|
data.setTotalCost(costValue);
|
||||||
|
}
|
||||||
|
data.setCostInputType(inputType);
|
||||||
|
|
||||||
|
// 3. 计算派生成本指标(单条成本录入时保留用户输入的单条成本值)
|
||||||
|
calculateDerivedCosts(data, actualTotalCost, isSingleInput);
|
||||||
|
|
||||||
|
// 4. 更新记录
|
||||||
|
dataV2Mapper.updateById(data);
|
||||||
|
|
||||||
|
// 5. 如果是标签级成本,需要重新计算组级汇总
|
||||||
|
if (tagName != null && !tagName.isEmpty()) {
|
||||||
|
recalculateGroupCost(corpId, date, groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("成本录入成功:corpId={}, date={}, group={}, tag={}, cost={}",
|
||||||
|
corpId, date, groupName, tagName, costValue);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("成本录入失败:" + e.getMessage(), e);
|
||||||
|
throw new RuntimeException("成本录入失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算派生成本指标
|
||||||
|
* @param data 数据对象
|
||||||
|
* @param totalCost 总成本
|
||||||
|
* @param preserveSingleCost 是否保留原有的单条成本值(单条成本录入模式时为true)
|
||||||
|
*/
|
||||||
|
private void calculateDerivedCosts(CustomerStatisticsDataV2 data, BigDecimal totalCost, boolean preserveSingleCost) {
|
||||||
|
// 初始化成本为0
|
||||||
|
if (!preserveSingleCost) {
|
||||||
|
data.setSingleCost(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
data.setOrderCost(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
// 单条成本 = 总成本 / 进粉数(仅在非单条成本录入模式下计算)
|
||||||
|
if (!preserveSingleCost && data.getCustomerCount() != null && data.getCustomerCount() > 0) {
|
||||||
|
BigDecimal singleCost = totalCost.divide(
|
||||||
|
new BigDecimal(data.getCustomerCount()), 2, RoundingMode.HALF_UP);
|
||||||
|
data.setSingleCost(singleCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成单成本 = 总成本 / 成单数
|
||||||
|
if (data.getOrderCount() != null && data.getOrderCount() > 0) {
|
||||||
|
BigDecimal orderCost = totalCost.divide(
|
||||||
|
new BigDecimal(data.getOrderCount()), 2, RoundingMode.HALF_UP);
|
||||||
|
data.setOrderCost(orderCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算派生成本指标(默认不保留单条成本)
|
||||||
|
*/
|
||||||
|
private void calculateDerivedCosts(CustomerStatisticsDataV2 data, BigDecimal totalCost) {
|
||||||
|
calculateDerivedCosts(data, totalCost, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算组级成本汇总
|
||||||
|
*/
|
||||||
|
private void recalculateGroupCost(String corpId, Date date, String groupName) {
|
||||||
|
// 1. 查询该组下所有标签级数据
|
||||||
|
List<CustomerStatisticsDataV2> tagDataList = dataV2Mapper
|
||||||
|
.selectTagLevelByCorpDateGroup(corpId, date, groupName);
|
||||||
|
|
||||||
|
// 2. 汇总标签成本
|
||||||
|
BigDecimal totalCostSum = BigDecimal.ZERO;
|
||||||
|
int totalCustomerCount = 0;
|
||||||
|
|
||||||
|
for (CustomerStatisticsDataV2 tagData : tagDataList) {
|
||||||
|
if (tagData.getTotalCost() != null) {
|
||||||
|
totalCostSum = totalCostSum.add(tagData.getTotalCost());
|
||||||
|
}
|
||||||
|
if (tagData.getCustomerCount() != null) {
|
||||||
|
totalCustomerCount += tagData.getCustomerCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新组级记录
|
||||||
|
CustomerStatisticsDataV2 groupData = dataV2Mapper
|
||||||
|
.selectGroupLevelByCorpDateGroup(corpId, date, groupName);
|
||||||
|
|
||||||
|
if (groupData != null) {
|
||||||
|
groupData.setTotalCost(totalCostSum);
|
||||||
|
if (totalCustomerCount > 0) {
|
||||||
|
BigDecimal avgSingleCost = totalCostSum.divide(
|
||||||
|
new BigDecimal(totalCustomerCount), 2, RoundingMode.HALF_UP);
|
||||||
|
groupData.setSingleCost(avgSingleCost);
|
||||||
|
}
|
||||||
|
calculateDerivedCosts(groupData, totalCostSum);
|
||||||
|
dataV2Mapper.updateById(groupData);
|
||||||
|
|
||||||
|
log.info("组级成本重新计算完成:group={}, totalCost={}", groupName, totalCostSum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int recalculateStatistics(String corpId, Date date) {
|
||||||
|
// 删除旧数据
|
||||||
|
dataV2Mapper.deleteByDateRange(corpId, date, date);
|
||||||
|
// 重新计算逻辑在 HandleAllDataV2 中实现
|
||||||
|
log.info("统计数据已删除,等待重新计算:corpId={}, date={}", corpId, date);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int recalculateStatisticsRange(String corpId, Date startDate, Date endDate) {
|
||||||
|
// 删除旧数据
|
||||||
|
dataV2Mapper.deleteByDateRange(corpId, startDate, endDate);
|
||||||
|
// 重新计算逻辑在 HandleAllDataV2 中实现
|
||||||
|
log.info("统计数据已删除,等待重新计算:corpId={}, startDate={}, endDate={}",
|
||||||
|
corpId, startDate, endDate);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectByWeekAggregation(
|
||||||
|
String corpId, Integer year, Integer week, String groupName, String tagName) {
|
||||||
|
log.info("周聚合查询: corpId={}, year={}, week={}, groupName={}, tagName={}", corpId, year, week, groupName, tagName);
|
||||||
|
|
||||||
|
// 计算周的开始和结束日期(使用与V1一致的MySQL WEEK函数逻辑)
|
||||||
|
Date[] weekRange = calculateWeekRangeByMySQL(year, week);
|
||||||
|
if (weekRange == null) {
|
||||||
|
// 如果周范围无效(跨年),返回空列表
|
||||||
|
log.info("周范围无效(跨年),返回空列表");
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
Date startDate = weekRange[0];
|
||||||
|
Date endDate = weekRange[1];
|
||||||
|
log.info("周日期范围: {} 至 {}", formatDate(startDate), formatDate(endDate));
|
||||||
|
|
||||||
|
// 查询该周的数据并聚合
|
||||||
|
List<CustomerStatisticsDataV2> result = dataV2Mapper.selectAggregatedByDateRange(corpId, startDate, endDate, groupName, tagName);
|
||||||
|
log.info("周聚合查询结果: {}条记录", result.size());
|
||||||
|
|
||||||
|
// 从原始数据表按finish_date重新查询成单数(解决跨日成交问题)
|
||||||
|
recalculateOrderCountFromOriginalData(corpId, startDate, endDate, result);
|
||||||
|
|
||||||
|
// 重新计算比率指标(SQL只聚合数量,比率需要重新计算)
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
recalculateRates(data);
|
||||||
|
log.info("记录: groupName={}, tagName={}, dataLevel={}, customerCount={}, orderCount={}, conversionRate={}",
|
||||||
|
data.getGroupName(), data.getTagName(), data.getDataLevel(), data.getCustomerCount(),
|
||||||
|
data.getOrderCount(), data.getConversionRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置显示字段
|
||||||
|
String yearWeek = year + "年第" + week + "周";
|
||||||
|
String dateRange = formatDate(startDate) + " 至 " + formatDate(endDate);
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
data.setYearWeek(yearWeek);
|
||||||
|
data.setDateRange(dateRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectByMonthAggregation(
|
||||||
|
String corpId, String yearMonth, String groupName, String tagName) {
|
||||||
|
// 解析年月
|
||||||
|
String[] parts = yearMonth.split("-");
|
||||||
|
int year = Integer.parseInt(parts[0]);
|
||||||
|
int month = Integer.parseInt(parts[1]);
|
||||||
|
|
||||||
|
// 计算月的开始和结束日期
|
||||||
|
Date[] monthRange = calculateMonthRange(year, month);
|
||||||
|
Date startDate = monthRange[0];
|
||||||
|
Date endDate = monthRange[1];
|
||||||
|
|
||||||
|
// 查询该月的数据并聚合
|
||||||
|
List<CustomerStatisticsDataV2> result = dataV2Mapper.selectAggregatedByDateRange(corpId, startDate, endDate, groupName, tagName);
|
||||||
|
|
||||||
|
// 从原始数据表按finish_date重新查询成单数(解决跨日成交问题)
|
||||||
|
recalculateOrderCountFromOriginalData(corpId, startDate, endDate, result);
|
||||||
|
|
||||||
|
// 重新计算比率指标
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
recalculateRates(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置显示字段
|
||||||
|
String yearMonthDisplay = year + "年" + String.format("%02d", month) + "月";
|
||||||
|
String dateRange = formatDate(startDate) + " 至 " + formatDate(endDate);
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
data.setYearMonth(yearMonthDisplay);
|
||||||
|
data.setDateRange(dateRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CustomerStatisticsDataV2> selectAllAggregation(
|
||||||
|
String corpId, String groupName, String tagName) {
|
||||||
|
// 查询所有数据并聚合
|
||||||
|
List<CustomerStatisticsDataV2> result = dataV2Mapper.selectAllAggregated(corpId, groupName, tagName);
|
||||||
|
|
||||||
|
// 从原始数据表按finish_date重新查询成单数(解决跨日成交问题)
|
||||||
|
// 对于全部数据,不限制日期范围
|
||||||
|
recalculateOrderCountFromOriginalData(corpId, null, null, result);
|
||||||
|
|
||||||
|
// 重新计算比率指标
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
recalculateRates(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置显示字段
|
||||||
|
for (CustomerStatisticsDataV2 data : result) {
|
||||||
|
data.setYearWeek("全部数据");
|
||||||
|
data.setYearMonth("全部数据");
|
||||||
|
data.setDateRange("全部数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从原始数据表按finish_date重新查询成单数
|
||||||
|
* 解决跨日成交问题:成单数应该按finish_date统计,而不是按add_date
|
||||||
|
* 优化:使用批量查询,避免N+1问题
|
||||||
|
*/
|
||||||
|
private void recalculateOrderCountFromOriginalData(String corpId, Date startDate, Date endDate,
|
||||||
|
List<CustomerStatisticsDataV2> dataList) {
|
||||||
|
if (dataList == null || dataList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询所有组的成单数(一次SQL查询)
|
||||||
|
Map<String, Object> orderCountMap = exportDataMapper.selectOrderCountBatchByFinishDateRange(corpId, startDate, endDate,finishFlag);
|
||||||
|
if (orderCountMap == null) {
|
||||||
|
orderCountMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询所有组的及时单数量(一次SQL查询)
|
||||||
|
Map<String, Object> timelyCountMap = exportDataMapper.selectTimelyOrderCountBatchByDateRange(corpId, startDate, endDate,timelyFinishFlag);
|
||||||
|
if (timelyCountMap == null) {
|
||||||
|
timelyCountMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询所有组的非及时单数量(一次SQL查询)
|
||||||
|
Map<String, Object> nonTimelyCountMap = exportDataMapper.selectNonTimelyOrderCountBatchByDateRange(corpId, startDate, endDate,noTimelyfinishFlag);
|
||||||
|
if (nonTimelyCountMap == null) {
|
||||||
|
nonTimelyCountMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历数据,从批量查询结果中获取对应的值
|
||||||
|
for (CustomerStatisticsDataV2 data : dataList) {
|
||||||
|
String groupName = data.getGroupName();
|
||||||
|
if (groupName == null || groupName.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String attr = GROUP_NAME_TO_ATTR_MAP.get(groupName);
|
||||||
|
if (attr == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从批量查询结果中获取成单数
|
||||||
|
Object orderCountObj = orderCountMap.get(attr);
|
||||||
|
int orderCount = 0;
|
||||||
|
if (orderCountObj != null) {
|
||||||
|
if (orderCountObj instanceof Number) {
|
||||||
|
orderCount = ((Number) orderCountObj).intValue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
orderCount = Integer.parseInt(orderCountObj.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法解析成单数: {}", orderCountObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.setOrderCount(orderCount);
|
||||||
|
|
||||||
|
// 从批量查询结果中获取及时单数量
|
||||||
|
Object timelyCountObj = timelyCountMap.get(attr);
|
||||||
|
int timelyCount = 0;
|
||||||
|
if (timelyCountObj != null) {
|
||||||
|
if (timelyCountObj instanceof Number) {
|
||||||
|
timelyCount = ((Number) timelyCountObj).intValue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
timelyCount = Integer.parseInt(timelyCountObj.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法解析及时单数量: {}", timelyCountObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.setTimelyOrderCount(timelyCount);
|
||||||
|
|
||||||
|
// 从批量查询结果中获取非及时单数量
|
||||||
|
Object nonTimelyCountObj = nonTimelyCountMap.get(attr);
|
||||||
|
int nonTimelyCount = 0;
|
||||||
|
if (nonTimelyCountObj != null) {
|
||||||
|
if (nonTimelyCountObj instanceof Number) {
|
||||||
|
nonTimelyCount = ((Number) nonTimelyCountObj).intValue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
nonTimelyCount = Integer.parseInt(nonTimelyCountObj.toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法解析非及时单数量: {}", nonTimelyCountObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.setNonTimelyOrderCount(nonTimelyCount);
|
||||||
|
|
||||||
|
log.debug("重新计算成单数: groupName={}, orderCount={}, timelyCount={}, nonTimelyCount={}",
|
||||||
|
groupName, data.getOrderCount(), data.getTimelyOrderCount(), data.getNonTimelyOrderCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算比率指标
|
||||||
|
* 在SQL聚合数量后,需要重新计算各种比率
|
||||||
|
*/
|
||||||
|
private void recalculateRates(CustomerStatisticsDataV2 data) {
|
||||||
|
// 转化率 = 成单数 / 进粉数
|
||||||
|
data.setConversionRate(calculateRate(data.getOrderCount(), data.getCustomerCount()));
|
||||||
|
|
||||||
|
// 及时单占比 = 及时单数 / 成单数
|
||||||
|
data.setTimelyRate(calculateRate(data.getTimelyOrderCount(), data.getOrderCount()));
|
||||||
|
|
||||||
|
// 非及时单占比 = 非及时单数 / 成单数
|
||||||
|
data.setNonTimelyRate(calculateRate(data.getNonTimelyOrderCount(), data.getOrderCount()));
|
||||||
|
|
||||||
|
// 客户属性占比
|
||||||
|
data.setParentRate(calculateRate(data.getParentCount(), data.getCustomerAttrCount()));
|
||||||
|
data.setStudentRate(calculateRate(data.getStudentCount(), data.getCustomerAttrCount()));
|
||||||
|
data.setTeacherRate(calculateRate(data.getTeacherCount(), data.getCustomerAttrCount()));
|
||||||
|
data.setUnknownRate(calculateRate(data.getUnknownAttrCount(), data.getCustomerAttrCount()));
|
||||||
|
|
||||||
|
// 意向度占比
|
||||||
|
data.setActiveQuoteRate(calculateRate(data.getActiveQuoteCount(), data.getIntentionCount()));
|
||||||
|
data.setPassiveQuoteRate(calculateRate(data.getPassiveQuoteCount(), data.getIntentionCount()));
|
||||||
|
data.setNoQuoteRate(calculateRate(data.getNoQuoteCount(), data.getIntentionCount()));
|
||||||
|
data.setDeletedQuoteRate(calculateRate(data.getDeletedQuoteCount(), data.getIntentionCount()));
|
||||||
|
|
||||||
|
// 年级占比
|
||||||
|
data.setPrimaryRate(calculateRate(data.getPrimaryCount(), data.getGradeCount()));
|
||||||
|
data.setMiddleRate(calculateRate(data.getMiddleCount(), data.getGradeCount()));
|
||||||
|
data.setHighRate(calculateRate(data.getHighCount(), data.getGradeCount()));
|
||||||
|
|
||||||
|
// 出单率
|
||||||
|
data.setParentOrderRate(calculateRate(data.getParentDailyOrderCount(), data.getParentDailyCount()));
|
||||||
|
data.setStudentOrderRate(calculateRate(data.getStudentDailyOrderCount(), data.getStudentDailyCount()));
|
||||||
|
data.setTeacherOrderRate(calculateRate(data.getTeacherDailyOrderCount(), data.getTeacherDailyCount()));
|
||||||
|
data.setUnknownOrderRate(calculateRate(data.getUnknownDailyOrderCount(), data.getUnknownDailyCount()));
|
||||||
|
|
||||||
|
// 成本指标(基于聚合后的总成本重新计算)
|
||||||
|
// 初始化成本为0
|
||||||
|
data.setSingleCost(BigDecimal.ZERO);
|
||||||
|
data.setOrderCost(BigDecimal.ZERO);
|
||||||
|
|
||||||
|
// 确保totalCost不为null
|
||||||
|
if (data.getTotalCost() == null) {
|
||||||
|
data.setTotalCost(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.getTotalCost().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
|
// 单条成本 = 总成本 / 进粉数
|
||||||
|
if (data.getCustomerCount() != null && data.getCustomerCount() > 0) {
|
||||||
|
BigDecimal singleCost = data.getTotalCost().divide(
|
||||||
|
new BigDecimal(data.getCustomerCount()), 2, RoundingMode.HALF_UP);
|
||||||
|
data.setSingleCost(singleCost);
|
||||||
|
}
|
||||||
|
// 成单成本 = 总成本 / 成单数
|
||||||
|
if (data.getOrderCount() != null && data.getOrderCount() > 0) {
|
||||||
|
BigDecimal orderCost = data.getTotalCost().divide(
|
||||||
|
new BigDecimal(data.getOrderCount()), 2, RoundingMode.HALF_UP);
|
||||||
|
data.setOrderCost(orderCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算比率(返回百分比字符串)
|
||||||
|
*/
|
||||||
|
private String calculateRate(Integer count, Integer total) {
|
||||||
|
if (total == null || total == 0 || count == null) {
|
||||||
|
return "0%";
|
||||||
|
}
|
||||||
|
BigDecimal rate = new BigDecimal(count)
|
||||||
|
.multiply(new BigDecimal(100))
|
||||||
|
.divide(new BigDecimal(total), 2, RoundingMode.HALF_UP);
|
||||||
|
return rate.toString() + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算周范围(修复跨年问题)
|
||||||
|
* 规则:
|
||||||
|
* 1. 周的第一天是周一(不是周日)
|
||||||
|
* 2. 第一周:从1月1日开始,到1月1日所在周的周日结束
|
||||||
|
* - 如果1月1日是周日,第一周只有1天(1月1日当天)
|
||||||
|
* 3. 最后一周:从最后一周的周一(12月31日往前推)开始,到12月31日结束
|
||||||
|
* - 如果12月31日是周一,最后一周只有1天(12月31日当天)
|
||||||
|
* 4. 其他周:按正常的周一到周日计算
|
||||||
|
*/
|
||||||
|
private Date[] calculateWeekRangeByMySQL(int year, int week) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.set(Calendar.YEAR, year);
|
||||||
|
calendar.set(Calendar.MONTH, Calendar.JANUARY);
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
calendar.set(Calendar.MINUTE, 0);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// 获取1月1日是星期几(1=周日,2=周一,...,7=周六)
|
||||||
|
int jan1DayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||||
|
// 转换为:0=周日,1=周一,...,6=周六
|
||||||
|
int jan1Weekday = (jan1DayOfWeek == Calendar.SUNDAY) ? 0 : (jan1DayOfWeek - 1);
|
||||||
|
|
||||||
|
// 计算12月31日
|
||||||
|
Calendar dec31Cal = Calendar.getInstance();
|
||||||
|
dec31Cal.set(Calendar.YEAR, year);
|
||||||
|
dec31Cal.set(Calendar.MONTH, Calendar.DECEMBER);
|
||||||
|
dec31Cal.set(Calendar.DAY_OF_MONTH, 31);
|
||||||
|
dec31Cal.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
dec31Cal.set(Calendar.MINUTE, 0);
|
||||||
|
dec31Cal.set(Calendar.SECOND, 0);
|
||||||
|
dec31Cal.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
// 第一周:从1月1日开始
|
||||||
|
if (week == 1) {
|
||||||
|
Date startDate = calendar.getTime();
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
// 如果1月1日是周日(0),则第一周只有1天(当天)
|
||||||
|
// 否则,计算到本周日
|
||||||
|
int daysToSunday;
|
||||||
|
if (jan1Weekday == 0) {
|
||||||
|
daysToSunday = 0; // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
daysToSunday = 7 - jan1Weekday; // 到本周日
|
||||||
|
}
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, daysToSunday);
|
||||||
|
Date endDate = calendar.getTime();
|
||||||
|
return new Date[]{startDate, endDate};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算第一周结束日期(1月1日所在周的周日)
|
||||||
|
Calendar firstWeekEndCal = (Calendar) calendar.clone();
|
||||||
|
int firstWeekDays;
|
||||||
|
if (jan1Weekday == 0) {
|
||||||
|
firstWeekDays = 0; // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
firstWeekDays = 7 - jan1Weekday; // 到本周日
|
||||||
|
}
|
||||||
|
firstWeekEndCal.add(Calendar.DAY_OF_MONTH, firstWeekDays);
|
||||||
|
|
||||||
|
// 计算第二周开始日期(第一周结束后的周一)
|
||||||
|
Calendar secondWeekStartCal = (Calendar) firstWeekEndCal.clone();
|
||||||
|
secondWeekStartCal.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
|
||||||
|
// 计算目标周的开始和结束
|
||||||
|
// 从第二周开始,每周都是周一到周日
|
||||||
|
Calendar targetWeekStartCal = (Calendar) secondWeekStartCal.clone();
|
||||||
|
targetWeekStartCal.add(Calendar.WEEK_OF_YEAR, week - 2);
|
||||||
|
|
||||||
|
// 如果开始日期已经跨年了(大于12月31日),则返回null
|
||||||
|
if (targetWeekStartCal.after(dec31Cal)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date startDate = targetWeekStartCal.getTime();
|
||||||
|
|
||||||
|
Calendar targetWeekEndCal = (Calendar) targetWeekStartCal.clone();
|
||||||
|
targetWeekEndCal.add(Calendar.DAY_OF_MONTH, 6);
|
||||||
|
Date endDate = targetWeekEndCal.getTime();
|
||||||
|
|
||||||
|
// 如果目标周的结束超过了12月31日,则调整到12月31日
|
||||||
|
if (targetWeekEndCal.after(dec31Cal)) {
|
||||||
|
endDate = dec31Cal.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date[]{startDate, endDate};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算月的开始和结束日期
|
||||||
|
*/
|
||||||
|
private Date[] calculateMonthRange(int year, int month) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.set(Calendar.YEAR, year);
|
||||||
|
calendar.set(Calendar.MONTH, month - 1);
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
calendar.set(Calendar.MINUTE, 0);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
Date startDate = calendar.getTime();
|
||||||
|
|
||||||
|
calendar.add(Calendar.MONTH, 1);
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, -1);
|
||||||
|
Date endDate = calendar.getTime();
|
||||||
|
|
||||||
|
return new Date[]{startDate, endDate};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期为 yyyy-MM-dd 字符串
|
||||||
|
*/
|
||||||
|
private String formatDate(Date date) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(date);
|
||||||
|
int year = calendar.get(Calendar.YEAR);
|
||||||
|
int month = calendar.get(Calendar.MONTH) + 1;
|
||||||
|
int day = calendar.get(Calendar.DAY_OF_MONTH);
|
||||||
|
return String.format("%d-%02d-%02d", year, month, day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -139,7 +139,10 @@
|
||||||
<select id="selectByFinishDate" resultType="java.lang.Long">
|
<select id="selectByFinishDate" resultType="java.lang.Long">
|
||||||
select count(1) from customer_export_data
|
select count(1) from customer_export_data
|
||||||
WHERE
|
WHERE
|
||||||
corp_id = #{corpId} and finish_date = #{date}
|
corp_id = #{corpId} and finish_date = #{date} and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
AND tag_group1 is not null
|
AND tag_group1 is not null
|
||||||
</if>
|
</if>
|
||||||
|
|
@ -196,6 +199,225 @@
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询及时单数量(根据成交日期和订单状态) -->
|
||||||
|
<select id="selectTimelyOrderCount" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and finish_date = #{date}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 is not null
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询非及时单数量(根据成交日期和订单状态) -->
|
||||||
|
<select id="selectNonTimelyOrderCount" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and finish_date = #{date}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 is not null
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询成单数(根据成交日期、组字段和标签值) -->
|
||||||
|
<select id="selectByFinishDateAndTag" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and finish_date = #{date} and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询及时单数量(根据成交日期、订单状态和标签值) -->
|
||||||
|
<select id="selectTimelyOrderCountByTag" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and finish_date = #{date}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询非及时单数量(根据成交日期、订单状态和标签值) -->
|
||||||
|
<select id="selectNonTimelyOrderCountByTag" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and finish_date = #{date}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 = #{tagValue}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
<!-- 分页查询客户导出数据VO列表(用于异步导出) -->
|
<!-- 分页查询客户导出数据VO列表(用于异步导出) -->
|
||||||
<select id="selectCustomerExportDataVOListByPage" resultType="com.ruoyi.excel.wecom.vo.CustomerExportDataVO">
|
<select id="selectCustomerExportDataVOListByPage" resultType="com.ruoyi.excel.wecom.vo.CustomerExportDataVO">
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -248,4 +470,236 @@
|
||||||
LIMIT #{limit} OFFSET #{offset}
|
LIMIT #{limit} OFFSET #{offset}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 按日期范围查询成单数(根据finish_date) -->
|
||||||
|
<select id="selectOrderCountByFinishDateRange" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId} and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 is not null
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按日期范围查询及时单数量(根据finish_date) -->
|
||||||
|
<select id="selectTimelyOrderCountByDateRange" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 is not null
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按日期范围查询非及时单数量(根据finish_date) -->
|
||||||
|
<select id="selectNonTimelyOrderCountByDateRange" resultType="java.lang.Long">
|
||||||
|
select count(1) from customer_export_data
|
||||||
|
WHERE
|
||||||
|
corp_id = #{corpId}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group1'">
|
||||||
|
AND tag_group1 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group2'">
|
||||||
|
AND tag_group2 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group3'">
|
||||||
|
AND tag_group3 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group10'">
|
||||||
|
AND tag_group10 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group11'">
|
||||||
|
AND tag_group11 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group12'">
|
||||||
|
AND tag_group12 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group13'">
|
||||||
|
AND tag_group13 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group14'">
|
||||||
|
AND tag_group14 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group16'">
|
||||||
|
AND tag_group16 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group17'">
|
||||||
|
AND tag_group17 is not null
|
||||||
|
</if>
|
||||||
|
<if test="attr != null and attr != '' and attr == 'tag_group18'">
|
||||||
|
AND tag_group18 is not null
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量查询所有组的成单数(根据finish_date)- 一次查询返回所有组的数据 -->
|
||||||
|
<select id="selectOrderCountBatchByFinishDateRange" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN tag_group1 IS NOT NULL THEN 1 ELSE 0 END) as tag_group1,
|
||||||
|
SUM(CASE WHEN tag_group2 IS NOT NULL THEN 1 ELSE 0 END) as tag_group2,
|
||||||
|
SUM(CASE WHEN tag_group3 IS NOT NULL THEN 1 ELSE 0 END) as tag_group3,
|
||||||
|
SUM(CASE WHEN tag_group10 IS NOT NULL THEN 1 ELSE 0 END) as tag_group10,
|
||||||
|
SUM(CASE WHEN tag_group11 IS NOT NULL THEN 1 ELSE 0 END) as tag_group11,
|
||||||
|
SUM(CASE WHEN tag_group12 IS NOT NULL THEN 1 ELSE 0 END) as tag_group12,
|
||||||
|
SUM(CASE WHEN tag_group13 IS NOT NULL THEN 1 ELSE 0 END) as tag_group13,
|
||||||
|
SUM(CASE WHEN tag_group14 IS NOT NULL THEN 1 ELSE 0 END) as tag_group14,
|
||||||
|
SUM(CASE WHEN tag_group16 IS NOT NULL THEN 1 ELSE 0 END) as tag_group16,
|
||||||
|
SUM(CASE WHEN tag_group17 IS NOT NULL THEN 1 ELSE 0 END) as tag_group17,
|
||||||
|
SUM(CASE WHEN tag_group18 IS NOT NULL THEN 1 ELSE 0 END) as tag_group18
|
||||||
|
FROM customer_export_data
|
||||||
|
WHERE corp_id = #{corpId} and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量查询所有组的及时单数量(根据finish_date) -->
|
||||||
|
<select id="selectTimelyOrderCountBatchByDateRange" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN tag_group1 IS NOT NULL THEN 1 ELSE 0 END) as tag_group1,
|
||||||
|
SUM(CASE WHEN tag_group2 IS NOT NULL THEN 1 ELSE 0 END) as tag_group2,
|
||||||
|
SUM(CASE WHEN tag_group3 IS NOT NULL THEN 1 ELSE 0 END) as tag_group3,
|
||||||
|
SUM(CASE WHEN tag_group10 IS NOT NULL THEN 1 ELSE 0 END) as tag_group10,
|
||||||
|
SUM(CASE WHEN tag_group11 IS NOT NULL THEN 1 ELSE 0 END) as tag_group11,
|
||||||
|
SUM(CASE WHEN tag_group12 IS NOT NULL THEN 1 ELSE 0 END) as tag_group12,
|
||||||
|
SUM(CASE WHEN tag_group13 IS NOT NULL THEN 1 ELSE 0 END) as tag_group13,
|
||||||
|
SUM(CASE WHEN tag_group14 IS NOT NULL THEN 1 ELSE 0 END) as tag_group14,
|
||||||
|
SUM(CASE WHEN tag_group16 IS NOT NULL THEN 1 ELSE 0 END) as tag_group16,
|
||||||
|
SUM(CASE WHEN tag_group17 IS NOT NULL THEN 1 ELSE 0 END) as tag_group17,
|
||||||
|
SUM(CASE WHEN tag_group18 IS NOT NULL THEN 1 ELSE 0 END) as tag_group18
|
||||||
|
FROM customer_export_data
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量查询所有组的非及时单数量(根据finish_date) -->
|
||||||
|
<select id="selectNonTimelyOrderCountBatchByDateRange" resultType="java.util.Map">
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN tag_group1 IS NOT NULL THEN 1 ELSE 0 END) as tag_group1,
|
||||||
|
SUM(CASE WHEN tag_group2 IS NOT NULL THEN 1 ELSE 0 END) as tag_group2,
|
||||||
|
SUM(CASE WHEN tag_group3 IS NOT NULL THEN 1 ELSE 0 END) as tag_group3,
|
||||||
|
SUM(CASE WHEN tag_group10 IS NOT NULL THEN 1 ELSE 0 END) as tag_group10,
|
||||||
|
SUM(CASE WHEN tag_group11 IS NOT NULL THEN 1 ELSE 0 END) as tag_group11,
|
||||||
|
SUM(CASE WHEN tag_group12 IS NOT NULL THEN 1 ELSE 0 END) as tag_group12,
|
||||||
|
SUM(CASE WHEN tag_group13 IS NOT NULL THEN 1 ELSE 0 END) as tag_group13,
|
||||||
|
SUM(CASE WHEN tag_group14 IS NOT NULL THEN 1 ELSE 0 END) as tag_group14,
|
||||||
|
SUM(CASE WHEN tag_group16 IS NOT NULL THEN 1 ELSE 0 END) as tag_group16,
|
||||||
|
SUM(CASE WHEN tag_group17 IS NOT NULL THEN 1 ELSE 0 END) as tag_group17,
|
||||||
|
SUM(CASE WHEN tag_group18 IS NOT NULL THEN 1 ELSE 0 END) as tag_group18
|
||||||
|
FROM customer_export_data
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
and tag_group7 in
|
||||||
|
<foreach collection="successFlags" item="successFlag" open="(" separator="," close=")">
|
||||||
|
#{successFlag}
|
||||||
|
</foreach>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND finish_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND finish_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,20 @@
|
||||||
ORDER BY cur_date, sort_no
|
ORDER BY cur_date, sort_no
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 按日期范围查询周数据(修复跨年周问题) -->
|
||||||
|
<select id="selectDailyDataByWeekRange" resultType="com.ruoyi.excel.wecom.domain.CustomerStatisticsData">
|
||||||
|
SELECT * FROM customer_statistics_data
|
||||||
|
<where>
|
||||||
|
corp_id = #{corpId}
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
<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 id="selectDailyDataByMonth" resultType="com.ruoyi.excel.wecom.domain.CustomerStatisticsData">
|
||||||
SELECT * FROM customer_statistics_data
|
SELECT * FROM customer_statistics_data
|
||||||
<where>
|
<where>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,416 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.excel.wecom.mapper.CustomerStatisticsDataV2Mapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.ruoyi.excel.wecom.domain.CustomerStatisticsDataV2">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="corp_id" property="corpId"/>
|
||||||
|
<result column="cur_date" property="curDate"/>
|
||||||
|
<result column="group_name" property="groupName"/>
|
||||||
|
<result column="tag_name" property="tagName"/>
|
||||||
|
<result column="tag_group_id" property="tagGroupId"/>
|
||||||
|
<result column="tag_id" property="tagId"/>
|
||||||
|
<result column="data_level" property="dataLevel"/>
|
||||||
|
<result column="parent_id" property="parentId"/>
|
||||||
|
<result column="total_cost" property="totalCost"/>
|
||||||
|
<result column="single_cost" property="singleCost"/>
|
||||||
|
<result column="order_cost" property="orderCost"/>
|
||||||
|
<result column="cost_input_type" property="costInputType"/>
|
||||||
|
<result column="order_count" property="orderCount"/>
|
||||||
|
<result column="customer_count" property="customerCount"/>
|
||||||
|
<result column="timely_order_count" property="timelyOrderCount"/>
|
||||||
|
<result column="non_timely_order_count" property="nonTimelyOrderCount"/>
|
||||||
|
<result column="conversion_rate" property="conversionRate"/>
|
||||||
|
<result column="timely_rate" property="timelyRate"/>
|
||||||
|
<result column="non_timely_rate" property="nonTimelyRate"/>
|
||||||
|
<result column="customer_attr_count" property="customerAttrCount"/>
|
||||||
|
<result column="parent_count" property="parentCount"/>
|
||||||
|
<result column="student_count" property="studentCount"/>
|
||||||
|
<result column="teacher_count" property="teacherCount"/>
|
||||||
|
<result column="unknown_attr_count" property="unknownAttrCount"/>
|
||||||
|
<result column="parent_rate" property="parentRate"/>
|
||||||
|
<result column="student_rate" property="studentRate"/>
|
||||||
|
<result column="teacher_rate" property="teacherRate"/>
|
||||||
|
<result column="unknown_rate" property="unknownRate"/>
|
||||||
|
<result column="parent_order_count" property="parentOrderCount"/>
|
||||||
|
<result column="student_order_count" property="studentOrderCount"/>
|
||||||
|
<result column="teacher_order_count" property="teacherOrderCount"/>
|
||||||
|
<result column="unknown_order_count" property="unknownOrderCount"/>
|
||||||
|
<result column="parent_daily_count" property="parentDailyCount"/>
|
||||||
|
<result column="student_daily_count" property="studentDailyCount"/>
|
||||||
|
<result column="teacher_daily_count" property="teacherDailyCount"/>
|
||||||
|
<result column="unknown_daily_count" property="unknownDailyCount"/>
|
||||||
|
<result column="parent_daily_order_count" property="parentDailyOrderCount"/>
|
||||||
|
<result column="student_daily_order_count" property="studentDailyOrderCount"/>
|
||||||
|
<result column="teacher_daily_order_count" property="teacherDailyOrderCount"/>
|
||||||
|
<result column="unknown_daily_order_count" property="unknownDailyOrderCount"/>
|
||||||
|
<result column="parent_order_rate" property="parentOrderRate"/>
|
||||||
|
<result column="student_order_rate" property="studentOrderRate"/>
|
||||||
|
<result column="teacher_order_rate" property="teacherOrderRate"/>
|
||||||
|
<result column="unknown_order_rate" property="unknownOrderRate"/>
|
||||||
|
<result column="intention_count" property="intentionCount"/>
|
||||||
|
<result column="active_quote_count" property="activeQuoteCount"/>
|
||||||
|
<result column="passive_quote_count" property="passiveQuoteCount"/>
|
||||||
|
<result column="no_quote_count" property="noQuoteCount"/>
|
||||||
|
<result column="deleted_quote_count" property="deletedQuoteCount"/>
|
||||||
|
<result column="active_quote_rate" property="activeQuoteRate"/>
|
||||||
|
<result column="passive_quote_rate" property="passiveQuoteRate"/>
|
||||||
|
<result column="no_quote_rate" property="noQuoteRate"/>
|
||||||
|
<result column="deleted_quote_rate" property="deletedQuoteRate"/>
|
||||||
|
<result column="grade_count" property="gradeCount"/>
|
||||||
|
<result column="primary_count" property="primaryCount"/>
|
||||||
|
<result column="middle_count" property="middleCount"/>
|
||||||
|
<result column="high_count" property="highCount"/>
|
||||||
|
<result column="primary_rate" property="primaryRate"/>
|
||||||
|
<result column="middle_rate" property="middleRate"/>
|
||||||
|
<result column="high_rate" property="highRate"/>
|
||||||
|
<result column="sort_no" property="sortNo"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id, corp_id, cur_date, group_name, tag_name, tag_group_id, tag_id, data_level, parent_id,
|
||||||
|
total_cost, single_cost, order_cost, cost_input_type,
|
||||||
|
order_count, customer_count, timely_order_count, non_timely_order_count,
|
||||||
|
conversion_rate, timely_rate, non_timely_rate,
|
||||||
|
customer_attr_count, parent_count, student_count, teacher_count, unknown_attr_count,
|
||||||
|
parent_rate, student_rate, teacher_rate, unknown_rate,
|
||||||
|
parent_order_count, student_order_count, teacher_order_count, unknown_order_count,
|
||||||
|
parent_daily_count, student_daily_count, teacher_daily_count, unknown_daily_count,
|
||||||
|
parent_daily_order_count, student_daily_order_count, teacher_daily_order_count, unknown_daily_order_count,
|
||||||
|
parent_order_rate, student_order_rate, teacher_order_rate, unknown_order_rate,
|
||||||
|
intention_count, active_quote_count, passive_quote_count, no_quote_count, deleted_quote_count,
|
||||||
|
active_quote_rate, passive_quote_rate, no_quote_rate, deleted_quote_rate,
|
||||||
|
grade_count, primary_count, middle_count, high_count,
|
||||||
|
primary_rate, middle_rate, high_rate,
|
||||||
|
sort_no, create_time, update_time
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<!-- 根据企业ID、日期、组名、标签名查询数据 -->
|
||||||
|
<select id="selectByCorpDateGroupTag" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
AND cur_date = #{curDate}
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
AND (
|
||||||
|
(tag_name = #{tagName} AND #{tagName} IS NOT NULL AND #{tagName} != '')
|
||||||
|
OR (tag_name IS NULL AND (#{tagName} IS NULL OR #{tagName} = ''))
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询组级数据列表 -->
|
||||||
|
<select id="selectGroupLevelList" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
AND data_level = 1
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
ORDER BY cur_date DESC, sort_no, group_name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询标签级数据列表 -->
|
||||||
|
<select id="selectTagLevelList" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
AND data_level = 2
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="groupName != null and groupName != ''">
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
</if>
|
||||||
|
ORDER BY cur_date DESC, group_name, tag_name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据企业ID、日期、组名查询组级数据 -->
|
||||||
|
<select id="selectGroupLevelByCorpDateGroup" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
AND cur_date = #{curDate}
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
AND data_level = 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据企业ID、日期、组名查询标签级数据列表 -->
|
||||||
|
<select id="selectTagLevelByCorpDateGroup" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
AND cur_date = #{curDate}
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
AND data_level = 2
|
||||||
|
ORDER BY tag_name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 批量插入数据 -->
|
||||||
|
<insert id="batchInsert">
|
||||||
|
INSERT INTO customer_statistics_data_v2 (
|
||||||
|
corp_id, cur_date, group_name, tag_name, tag_group_id, tag_id, data_level, parent_id,
|
||||||
|
total_cost, single_cost, order_cost, cost_input_type,
|
||||||
|
order_count, customer_count, timely_order_count, non_timely_order_count,
|
||||||
|
conversion_rate, timely_rate, non_timely_rate,
|
||||||
|
customer_attr_count, parent_count, student_count, teacher_count, unknown_attr_count,
|
||||||
|
parent_rate, student_rate, teacher_rate, unknown_rate,
|
||||||
|
parent_order_count, student_order_count, teacher_order_count, unknown_order_count,
|
||||||
|
parent_daily_count, student_daily_count, teacher_daily_count, unknown_daily_count,
|
||||||
|
parent_daily_order_count, student_daily_order_count, teacher_daily_order_count, unknown_daily_order_count,
|
||||||
|
parent_order_rate, student_order_rate, teacher_order_rate, unknown_order_rate,
|
||||||
|
intention_count, active_quote_count, passive_quote_count, no_quote_count, deleted_quote_count,
|
||||||
|
active_quote_rate, passive_quote_rate, no_quote_rate, deleted_quote_rate,
|
||||||
|
grade_count, primary_count, middle_count, high_count,
|
||||||
|
primary_rate, middle_rate, high_rate,
|
||||||
|
sort_no
|
||||||
|
) VALUES
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(
|
||||||
|
#{item.corpId}, #{item.curDate}, #{item.groupName}, #{item.tagName}, #{item.tagGroupId}, #{item.tagId},
|
||||||
|
#{item.dataLevel}, #{item.parentId},
|
||||||
|
#{item.totalCost}, #{item.singleCost}, #{item.orderCost}, #{item.costInputType},
|
||||||
|
#{item.orderCount}, #{item.customerCount}, #{item.timelyOrderCount}, #{item.nonTimelyOrderCount},
|
||||||
|
#{item.conversionRate}, #{item.timelyRate}, #{item.nonTimelyRate},
|
||||||
|
#{item.customerAttrCount}, #{item.parentCount}, #{item.studentCount}, #{item.teacherCount}, #{item.unknownAttrCount},
|
||||||
|
#{item.parentRate}, #{item.studentRate}, #{item.teacherRate}, #{item.unknownRate},
|
||||||
|
#{item.parentOrderCount}, #{item.studentOrderCount}, #{item.teacherOrderCount}, #{item.unknownOrderCount},
|
||||||
|
#{item.parentDailyCount}, #{item.studentDailyCount}, #{item.teacherDailyCount}, #{item.unknownDailyCount},
|
||||||
|
#{item.parentDailyOrderCount}, #{item.studentDailyOrderCount}, #{item.teacherDailyOrderCount}, #{item.unknownDailyOrderCount},
|
||||||
|
#{item.parentOrderRate}, #{item.studentOrderRate}, #{item.teacherOrderRate}, #{item.unknownOrderRate},
|
||||||
|
#{item.intentionCount}, #{item.activeQuoteCount}, #{item.passiveQuoteCount}, #{item.noQuoteCount}, #{item.deletedQuoteCount},
|
||||||
|
#{item.activeQuoteRate}, #{item.passiveQuoteRate}, #{item.noQuoteRate}, #{item.deletedQuoteRate},
|
||||||
|
#{item.gradeCount}, #{item.primaryCount}, #{item.middleCount}, #{item.highCount},
|
||||||
|
#{item.primaryRate}, #{item.middleRate}, #{item.highRate},
|
||||||
|
#{item.sortNo}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!-- 删除指定日期范围的数据 -->
|
||||||
|
<delete id="deleteByDateRange">
|
||||||
|
DELETE FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
</delete>
|
||||||
|
|
||||||
|
<!-- 查询树状结构数据(组+标签) -->
|
||||||
|
<select id="selectTreeData" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
ORDER BY data_level, group_name, tag_name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据筛选条件查询数据列表(支持按组、标签筛选) -->
|
||||||
|
<select id="selectListByFilter" resultMap="BaseResultMap">
|
||||||
|
SELECT <include refid="Base_Column_List"/>
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="groupName != null and groupName != ''">
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
</if>
|
||||||
|
<if test="tagName != null and tagName != ''">
|
||||||
|
AND tag_name = #{tagName}
|
||||||
|
</if>
|
||||||
|
ORDER BY cur_date DESC, data_level ASC, group_name ASC, sort_no ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按日期范围聚合查询(支持按组、标签筛选) -->
|
||||||
|
<!-- 注意:比率指标需要在Java层重新计算,SQL只聚合数量指标 -->
|
||||||
|
<select id="selectAggregatedByDateRange" resultMap="BaseResultMap">
|
||||||
|
SELECT
|
||||||
|
NULL as id,
|
||||||
|
#{corpId} as corp_id,
|
||||||
|
MAX(cur_date) as cur_date,
|
||||||
|
group_name,
|
||||||
|
tag_name,
|
||||||
|
NULL as parent_id,
|
||||||
|
MIN(sort_no) as sort_no,
|
||||||
|
data_level,
|
||||||
|
SUM(order_count) as order_count,
|
||||||
|
SUM(customer_count) as customer_count,
|
||||||
|
SUM(customer_attr_count) as customer_attr_count,
|
||||||
|
SUM(parent_count) as parent_count,
|
||||||
|
SUM(student_count) as student_count,
|
||||||
|
SUM(teacher_count) as teacher_count,
|
||||||
|
SUM(unknown_attr_count) as unknown_attr_count,
|
||||||
|
SUM(timely_order_count) as timely_order_count,
|
||||||
|
SUM(non_timely_order_count) as non_timely_order_count,
|
||||||
|
NULL as conversion_rate,
|
||||||
|
NULL as timely_rate,
|
||||||
|
NULL as non_timely_rate,
|
||||||
|
NULL as parent_rate,
|
||||||
|
NULL as student_rate,
|
||||||
|
NULL as teacher_rate,
|
||||||
|
NULL as unknown_rate,
|
||||||
|
SUM(total_cost) as total_cost,
|
||||||
|
NULL as single_cost,
|
||||||
|
NULL as order_cost,
|
||||||
|
SUM(intention_count) as intention_count,
|
||||||
|
SUM(active_quote_count) as active_quote_count,
|
||||||
|
SUM(passive_quote_count) as passive_quote_count,
|
||||||
|
SUM(no_quote_count) as no_quote_count,
|
||||||
|
SUM(deleted_quote_count) as deleted_quote_count,
|
||||||
|
NULL as active_quote_rate,
|
||||||
|
NULL as passive_quote_rate,
|
||||||
|
NULL as no_quote_rate,
|
||||||
|
NULL as deleted_quote_rate,
|
||||||
|
SUM(grade_count) as grade_count,
|
||||||
|
SUM(primary_count) as primary_count,
|
||||||
|
SUM(middle_count) as middle_count,
|
||||||
|
SUM(high_count) as high_count,
|
||||||
|
NULL as primary_rate,
|
||||||
|
NULL as middle_rate,
|
||||||
|
NULL as high_rate,
|
||||||
|
SUM(parent_order_count) as parent_order_count,
|
||||||
|
SUM(student_order_count) as student_order_count,
|
||||||
|
SUM(teacher_order_count) as teacher_order_count,
|
||||||
|
SUM(unknown_order_count) as unknown_order_count,
|
||||||
|
SUM(parent_daily_count) as parent_daily_count,
|
||||||
|
SUM(student_daily_count) as student_daily_count,
|
||||||
|
SUM(teacher_daily_count) as teacher_daily_count,
|
||||||
|
SUM(unknown_daily_count) as unknown_daily_count,
|
||||||
|
SUM(parent_daily_order_count) as parent_daily_order_count,
|
||||||
|
SUM(student_daily_order_count) as student_daily_order_count,
|
||||||
|
SUM(teacher_daily_order_count) as teacher_daily_order_count,
|
||||||
|
SUM(unknown_daily_order_count) as unknown_daily_order_count,
|
||||||
|
NULL as parent_order_rate,
|
||||||
|
NULL as student_order_rate,
|
||||||
|
NULL as teacher_order_rate,
|
||||||
|
NULL as unknown_order_rate
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
<choose>
|
||||||
|
<when test="tagName != null and tagName != ''">
|
||||||
|
AND data_level = 2
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
AND data_level = 1
|
||||||
|
</otherwise>
|
||||||
|
</choose>
|
||||||
|
<if test="startDate != null">
|
||||||
|
AND cur_date >= #{startDate}
|
||||||
|
</if>
|
||||||
|
<if test="endDate != null">
|
||||||
|
AND cur_date <= #{endDate}
|
||||||
|
</if>
|
||||||
|
<if test="groupName != null and groupName != ''">
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
</if>
|
||||||
|
<if test="tagName != null and tagName != ''">
|
||||||
|
AND tag_name = #{tagName}
|
||||||
|
</if>
|
||||||
|
GROUP BY group_name, tag_name, data_level
|
||||||
|
ORDER BY group_name ASC, tag_name ASC, data_level ASC, MIN(sort_no) ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询所有数据并聚合(支持按组、标签筛选) -->
|
||||||
|
<!-- 注意:比率指标需要在Java层重新计算,SQL只聚合数量指标 -->
|
||||||
|
<select id="selectAllAggregated" resultMap="BaseResultMap">
|
||||||
|
SELECT
|
||||||
|
NULL as id,
|
||||||
|
#{corpId} as corp_id,
|
||||||
|
MAX(cur_date) as cur_date,
|
||||||
|
group_name,
|
||||||
|
tag_name,
|
||||||
|
NULL as parent_id,
|
||||||
|
MIN(sort_no) as sort_no,
|
||||||
|
data_level,
|
||||||
|
SUM(order_count) as order_count,
|
||||||
|
SUM(customer_count) as customer_count,
|
||||||
|
SUM(customer_attr_count) as customer_attr_count,
|
||||||
|
SUM(parent_count) as parent_count,
|
||||||
|
SUM(student_count) as student_count,
|
||||||
|
SUM(teacher_count) as teacher_count,
|
||||||
|
SUM(unknown_attr_count) as unknown_attr_count,
|
||||||
|
SUM(timely_order_count) as timely_order_count,
|
||||||
|
SUM(non_timely_order_count) as non_timely_order_count,
|
||||||
|
NULL as conversion_rate,
|
||||||
|
NULL as timely_rate,
|
||||||
|
NULL as non_timely_rate,
|
||||||
|
NULL as parent_rate,
|
||||||
|
NULL as student_rate,
|
||||||
|
NULL as teacher_rate,
|
||||||
|
NULL as unknown_rate,
|
||||||
|
SUM(total_cost) as total_cost,
|
||||||
|
NULL as single_cost,
|
||||||
|
NULL as order_cost,
|
||||||
|
SUM(intention_count) as intention_count,
|
||||||
|
SUM(active_quote_count) as active_quote_count,
|
||||||
|
SUM(passive_quote_count) as passive_quote_count,
|
||||||
|
SUM(no_quote_count) as no_quote_count,
|
||||||
|
SUM(deleted_quote_count) as deleted_quote_count,
|
||||||
|
NULL as active_quote_rate,
|
||||||
|
NULL as passive_quote_rate,
|
||||||
|
NULL as no_quote_rate,
|
||||||
|
NULL as deleted_quote_rate,
|
||||||
|
SUM(grade_count) as grade_count,
|
||||||
|
SUM(primary_count) as primary_count,
|
||||||
|
SUM(middle_count) as middle_count,
|
||||||
|
SUM(high_count) as high_count,
|
||||||
|
NULL as primary_rate,
|
||||||
|
NULL as middle_rate,
|
||||||
|
NULL as high_rate,
|
||||||
|
SUM(parent_order_count) as parent_order_count,
|
||||||
|
SUM(student_order_count) as student_order_count,
|
||||||
|
SUM(teacher_order_count) as teacher_order_count,
|
||||||
|
SUM(unknown_order_count) as unknown_order_count,
|
||||||
|
SUM(parent_daily_count) as parent_daily_count,
|
||||||
|
SUM(student_daily_count) as student_daily_count,
|
||||||
|
SUM(teacher_daily_count) as teacher_daily_count,
|
||||||
|
SUM(unknown_daily_count) as unknown_daily_count,
|
||||||
|
SUM(parent_daily_order_count) as parent_daily_order_count,
|
||||||
|
SUM(student_daily_order_count) as student_daily_order_count,
|
||||||
|
SUM(teacher_daily_order_count) as teacher_daily_order_count,
|
||||||
|
SUM(unknown_daily_order_count) as unknown_daily_order_count,
|
||||||
|
NULL as parent_order_rate,
|
||||||
|
NULL as student_order_rate,
|
||||||
|
NULL as teacher_order_rate,
|
||||||
|
NULL as unknown_order_rate
|
||||||
|
FROM customer_statistics_data_v2
|
||||||
|
WHERE corp_id = #{corpId}
|
||||||
|
<choose>
|
||||||
|
<when test="tagName != null and tagName != ''">
|
||||||
|
AND data_level = 2
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
AND data_level = 1
|
||||||
|
</otherwise>
|
||||||
|
</choose>
|
||||||
|
<if test="groupName != null and groupName != ''">
|
||||||
|
AND group_name = #{groupName}
|
||||||
|
</if>
|
||||||
|
<if test="tagName != null and tagName != ''">
|
||||||
|
AND tag_name = #{tagName}
|
||||||
|
</if>
|
||||||
|
GROUP BY group_name, tag_name, data_level
|
||||||
|
ORDER BY group_name ASC, tag_name ASC, data_level ASC, MIN(sort_no) ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
-- 客户统计数据表V2(支持标签级成本,行列转换存储)
|
||||||
|
DROP TABLE IF EXISTS `customer_statistics_data_v2`;
|
||||||
|
|
||||||
|
CREATE TABLE `customer_statistics_data_v2` (
|
||||||
|
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`corp_id` VARCHAR(100) NOT NULL COMMENT '企业ID',
|
||||||
|
`cur_date` DATE NOT NULL COMMENT '统计日期',
|
||||||
|
|
||||||
|
-- 维度信息
|
||||||
|
`group_name` VARCHAR(50) NOT NULL COMMENT '组名(N组、O组等)',
|
||||||
|
`tag_name` VARCHAR(100) DEFAULT NULL COMMENT '标签名(NULL表示组级汇总)',
|
||||||
|
`tag_group_id` VARCHAR(100) DEFAULT NULL COMMENT '标签组ID(关联wecom_tag_group)',
|
||||||
|
`tag_id` VARCHAR(100) DEFAULT NULL COMMENT '标签ID(关联wecom_tag)',
|
||||||
|
|
||||||
|
-- 层级关系
|
||||||
|
`data_level` TINYINT DEFAULT 1 COMMENT '数据级别:1-组级汇总,2-标签级明细',
|
||||||
|
`parent_id` BIGINT(20) DEFAULT NULL COMMENT '父记录ID(标签级数据对应组级记录的ID)',
|
||||||
|
|
||||||
|
-- 成本数据(支持标签级成本)
|
||||||
|
`total_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '总成本(手工录入)',
|
||||||
|
`single_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '单条成本(计算得出)',
|
||||||
|
`order_cost` DECIMAL(12,2) DEFAULT NULL COMMENT '成单成本(计算得出)',
|
||||||
|
`cost_input_type` VARCHAR(20) DEFAULT NULL COMMENT '成本录入类型:total-总成本,single-单条成本',
|
||||||
|
|
||||||
|
-- 数量指标
|
||||||
|
`order_count` INT DEFAULT 0 COMMENT '成单数',
|
||||||
|
`customer_count` INT DEFAULT 0 COMMENT '进粉数',
|
||||||
|
`timely_order_count` INT DEFAULT 0 COMMENT '及时单数',
|
||||||
|
`non_timely_order_count` INT DEFAULT 0 COMMENT '非及时单数',
|
||||||
|
|
||||||
|
-- 比率指标
|
||||||
|
`conversion_rate` VARCHAR(10) DEFAULT '0%' COMMENT '转化率',
|
||||||
|
`timely_rate` VARCHAR(10) DEFAULT '0%' COMMENT '及时单占比',
|
||||||
|
`non_timely_rate` VARCHAR(10) DEFAULT '0%' COMMENT '非及时单占比',
|
||||||
|
|
||||||
|
-- 客户属性指标
|
||||||
|
`customer_attr_count` INT DEFAULT 0 COMMENT '客户属性数量',
|
||||||
|
`parent_count` INT DEFAULT 0 COMMENT '家长数量',
|
||||||
|
`student_count` INT DEFAULT 0 COMMENT '学生数量',
|
||||||
|
`teacher_count` INT DEFAULT 0 COMMENT '老师数量',
|
||||||
|
`unknown_attr_count` INT DEFAULT 0 COMMENT '未知属性数量',
|
||||||
|
`parent_rate` VARCHAR(10) DEFAULT '0%' COMMENT '家长占比',
|
||||||
|
`student_rate` VARCHAR(10) DEFAULT '0%' COMMENT '学生占比',
|
||||||
|
`teacher_rate` VARCHAR(10) DEFAULT '0%' COMMENT '老师占比',
|
||||||
|
`unknown_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未知占比',
|
||||||
|
|
||||||
|
-- 出单率指标
|
||||||
|
`parent_order_count` INT DEFAULT 0 COMMENT '家长出单数量',
|
||||||
|
`student_order_count` INT DEFAULT 0 COMMENT '学生出单数量',
|
||||||
|
`teacher_order_count` INT DEFAULT 0 COMMENT '老师出单数量',
|
||||||
|
`unknown_order_count` INT DEFAULT 0 COMMENT '未知出单数量',
|
||||||
|
`parent_daily_count` INT DEFAULT 0 COMMENT '家长当日数量',
|
||||||
|
`student_daily_count` INT DEFAULT 0 COMMENT '学生当日数量',
|
||||||
|
`teacher_daily_count` INT DEFAULT 0 COMMENT '老师当日数量',
|
||||||
|
`unknown_daily_count` INT DEFAULT 0 COMMENT '未知当日数量',
|
||||||
|
`parent_daily_order_count` INT DEFAULT 0 COMMENT '家长当日出单数量',
|
||||||
|
`student_daily_order_count` INT DEFAULT 0 COMMENT '学生当日出单数量',
|
||||||
|
`teacher_daily_order_count` INT DEFAULT 0 COMMENT '老师当日出单数量',
|
||||||
|
`unknown_daily_order_count` INT DEFAULT 0 COMMENT '未知当日出单数量',
|
||||||
|
`parent_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '家长出单率',
|
||||||
|
`student_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '学生出单率',
|
||||||
|
`teacher_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '老师出单率',
|
||||||
|
`unknown_order_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未知出单率',
|
||||||
|
|
||||||
|
-- 意向度指标
|
||||||
|
`intention_count` INT DEFAULT 0 COMMENT '意向度数量',
|
||||||
|
`active_quote_count` INT DEFAULT 0 COMMENT '主动报价数量',
|
||||||
|
`passive_quote_count` INT DEFAULT 0 COMMENT '被动报价数量',
|
||||||
|
`no_quote_count` INT DEFAULT 0 COMMENT '未开口报价数量',
|
||||||
|
`deleted_quote_count` INT DEFAULT 0 COMMENT '已删除报价数量',
|
||||||
|
`active_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '主动报价占比',
|
||||||
|
`passive_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '被动报价占比',
|
||||||
|
`no_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '未开口报价占比',
|
||||||
|
`deleted_quote_rate` VARCHAR(10) DEFAULT '0%' COMMENT '已删除报价占比',
|
||||||
|
|
||||||
|
-- 年级指标
|
||||||
|
`grade_count` INT DEFAULT 0 COMMENT '年级数量',
|
||||||
|
`primary_count` INT DEFAULT 0 COMMENT '小学数量',
|
||||||
|
`middle_count` INT DEFAULT 0 COMMENT '初中数量',
|
||||||
|
`high_count` INT DEFAULT 0 COMMENT '高中数量',
|
||||||
|
`primary_rate` VARCHAR(10) DEFAULT '0%' COMMENT '小学占比',
|
||||||
|
`middle_rate` VARCHAR(10) DEFAULT '0%' COMMENT '初中占比',
|
||||||
|
`high_rate` VARCHAR(10) DEFAULT '0%' COMMENT '高中占比',
|
||||||
|
|
||||||
|
`sort_no` INT DEFAULT 0 COMMENT '排序号',
|
||||||
|
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_corp_date_group_tag` (`corp_id`, `cur_date`, `group_name`, `tag_name`),
|
||||||
|
INDEX `idx_corp_date` (`corp_id`, `cur_date`),
|
||||||
|
INDEX `idx_group_name` (`group_name`),
|
||||||
|
INDEX `idx_data_level` (`data_level`),
|
||||||
|
INDEX `idx_parent_id` (`parent_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户统计数据表V2(支持标签级成本,行列转换)';
|
||||||
|
|
||||||
|
-- 成本录入记录表(用于追溯)
|
||||||
|
DROP TABLE IF EXISTS `cost_input_record_v2`;
|
||||||
|
|
||||||
|
CREATE TABLE `cost_input_record_v2` (
|
||||||
|
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`corp_id` VARCHAR(100) NOT NULL COMMENT '企业ID',
|
||||||
|
`cur_date` DATE NOT NULL COMMENT '统计日期',
|
||||||
|
`group_name` VARCHAR(50) NOT NULL COMMENT '组名',
|
||||||
|
`tag_name` VARCHAR(100) DEFAULT NULL COMMENT '标签名(NULL表示组级)',
|
||||||
|
`cost_type` VARCHAR(20) NOT NULL COMMENT 'total-总成本,single-单条成本',
|
||||||
|
`input_value` DECIMAL(12,2) NOT NULL COMMENT '录入值',
|
||||||
|
`actual_total_cost` DECIMAL(12,2) NOT NULL COMMENT '实际总成本',
|
||||||
|
`input_by` VARCHAR(50) DEFAULT NULL COMMENT '录入人',
|
||||||
|
`input_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '录入时间',
|
||||||
|
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_corp_date_group` (`corp_id`, `cur_date`, `group_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成本录入记录表V2';
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.ruoyi.web.controller.wocom;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Log;
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
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.CustomerStatisticsDataV2;
|
||||||
|
import com.ruoyi.excel.wecom.domain.dto.TagTreeDTO;
|
||||||
|
import com.ruoyi.excel.wecom.service.ICustomerStatisticsDataV2Service;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户统计数据V2 Controller
|
||||||
|
* 支持标签级成本,行列转换存储
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/wecom/customerStatisticsV2")
|
||||||
|
public class CustomerStatisticsDataV2Controller extends BaseController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CustomerStatisticsDataV2Controller.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ICustomerStatisticsDataV2Service customerStatisticsDataV2Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询客户统计数据V2列表(支持按组、标签筛选,支持天/周/月维度)
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2: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 = "groupName", required = false) String groupName,
|
||||||
|
@RequestParam(value = "tagName", required = false) String tagName) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
|
||||||
|
log.info("list接口被调用: dataType={}, year={}, week={}, yearMonth={}, groupName={}, tagName={}",
|
||||||
|
dataType, year, week, yearMonth, groupName, tagName);
|
||||||
|
|
||||||
|
List<CustomerStatisticsDataV2> list;
|
||||||
|
if ("week".equals(dataType)) {
|
||||||
|
list = customerStatisticsDataV2Service.selectByWeekAggregation(corpId, year, week, groupName, tagName);
|
||||||
|
log.info("周聚合查询结果: {}条记录", list.size());
|
||||||
|
return getDataTable(list);
|
||||||
|
} else if ("month".equals(dataType)) {
|
||||||
|
list = customerStatisticsDataV2Service.selectByMonthAggregation(corpId, yearMonth, groupName, tagName);
|
||||||
|
log.info("月聚合查询结果: {}条记录", list.size());
|
||||||
|
return getDataTable(list);
|
||||||
|
} else if ("all".equals(dataType)) {
|
||||||
|
list = customerStatisticsDataV2Service.selectAllAggregation(corpId, groupName, tagName);
|
||||||
|
log.info("全部数据查询结果: {}条记录", list.size());
|
||||||
|
return getDataTable(list);
|
||||||
|
} else {
|
||||||
|
startPage();
|
||||||
|
list = customerStatisticsDataV2Service
|
||||||
|
.selectCustomerStatisticsDataV2List(corpId, startDate, endDate, groupName, tagName);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询标签树(只返回组-标签结构,不返回统计数据)
|
||||||
|
* 用于前端左侧树状筛选面板
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:tree')")
|
||||||
|
@GetMapping("/tree")
|
||||||
|
public AjaxResult tree(
|
||||||
|
@RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
|
||||||
|
@RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
List<TagTreeDTO> treeData = customerStatisticsDataV2Service
|
||||||
|
.selectTagTree(corpId, startDate, endDate);
|
||||||
|
return success(treeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出客户统计数据V2列表
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:export')")
|
||||||
|
@Log(title = "客户统计数据V2", businessType = BusinessType.EXPORT)
|
||||||
|
@PostMapping("/export")
|
||||||
|
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) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
List<CustomerStatisticsDataV2> dataList = customerStatisticsDataV2Service
|
||||||
|
.selectCustomerStatisticsDataV2List(corpId, startDate, endDate);
|
||||||
|
ExcelUtil<CustomerStatisticsDataV2> util = new ExcelUtil<>(CustomerStatisticsDataV2.class);
|
||||||
|
util.exportExcel(response, dataList, "流量看板数据V2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户统计数据V2详细信息
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:query')")
|
||||||
|
@GetMapping(value = "/{id}")
|
||||||
|
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||||
|
return success(customerStatisticsDataV2Service.selectCustomerStatisticsDataV2ById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增客户统计数据V2
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:add')")
|
||||||
|
@Log(title = "客户统计数据V2", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult add(@RequestBody CustomerStatisticsDataV2 data) {
|
||||||
|
return toAjax(customerStatisticsDataV2Service.insertCustomerStatisticsDataV2(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改客户统计数据V2
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:edit')")
|
||||||
|
@Log(title = "客户统计数据V2", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping
|
||||||
|
public AjaxResult edit(@RequestBody CustomerStatisticsDataV2 data) {
|
||||||
|
return toAjax(customerStatisticsDataV2Service.updateCustomerStatisticsDataV2(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除客户统计数据V2
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:remove')")
|
||||||
|
@Log(title = "客户统计数据V2", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(customerStatisticsDataV2Service.deleteCustomerStatisticsDataV2ByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 录入成本(支持组级和标签级)
|
||||||
|
* @param date 日期
|
||||||
|
* @param groupName 组名
|
||||||
|
* @param tagName 标签名(为空表示组级)
|
||||||
|
* @param costValue 成本值
|
||||||
|
* @param inputType 录入类型:total-总成本,single-单条成本
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:cost')")
|
||||||
|
@Log(title = "客户统计数据V2-成本录入", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/cost")
|
||||||
|
public AjaxResult inputCost(
|
||||||
|
@RequestParam(value = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date,
|
||||||
|
@RequestParam(value = "groupName") String groupName,
|
||||||
|
@RequestParam(value = "tagName", required = false) String tagName,
|
||||||
|
@RequestParam(value = "costValue") BigDecimal costValue,
|
||||||
|
@RequestParam(value = "inputType") String inputType) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
return toAjax(customerStatisticsDataV2Service.inputCost(
|
||||||
|
corpId, date, groupName, tagName, costValue, inputType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算指定日期的统计数据
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:recalculate')")
|
||||||
|
@Log(title = "客户统计数据V2-重新计算", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/recalculate")
|
||||||
|
public AjaxResult recalculate(
|
||||||
|
@RequestParam(value = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
return toAjax(customerStatisticsDataV2Service.recalculateStatistics(corpId, date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新计算指定日期范围的统计数据
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('wecom:customerStatisticsV2:recalculate')")
|
||||||
|
@Log(title = "客户统计数据V2-重新计算", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/recalculateRange")
|
||||||
|
public AjaxResult recalculateRange(
|
||||||
|
@RequestParam(value = "startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
|
||||||
|
@RequestParam(value = "endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
|
||||||
|
String corpId = CorpContextHolder.getCurrentCorpId();
|
||||||
|
return toAjax(customerStatisticsDataV2Service.recalculateStatisticsRange(
|
||||||
|
corpId, startDate, endDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.ruoyi.quartz.task;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.ruoyi.excel.wecom.domain.CorpInfo;
|
import com.ruoyi.excel.wecom.domain.CorpInfo;
|
||||||
import com.ruoyi.excel.wecom.helper.HandleAllData;
|
import com.ruoyi.excel.wecom.helper.HandleAllData;
|
||||||
|
import com.ruoyi.excel.wecom.helper.HandleAllDataV2;
|
||||||
import com.ruoyi.excel.wecom.mapper.CorpInfoMapper;
|
import com.ruoyi.excel.wecom.mapper.CorpInfoMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
@ -18,6 +19,8 @@ public class WeComTask {
|
||||||
@Autowired
|
@Autowired
|
||||||
private HandleAllData handleAllData;
|
private HandleAllData handleAllData;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private HandleAllDataV2 handleAllDataV2;
|
||||||
|
@Autowired
|
||||||
private CorpInfoMapper corpInfoMapper;
|
private CorpInfoMapper corpInfoMapper;
|
||||||
public void initData() throws IOException {
|
public void initData() throws IOException {
|
||||||
System.out.println("初始化项目数据 包括 部门 人员");
|
System.out.println("初始化项目数据 包括 部门 人员");
|
||||||
|
|
@ -39,6 +42,11 @@ public class WeComTask {
|
||||||
handleAllData.createAllDepartmentReportData();
|
handleAllData.createAllDepartmentReportData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void createAllDepartmentReportDataV2() throws IOException {
|
||||||
|
System.out.println("计算所有流量看板数据V2");
|
||||||
|
handleAllDataV2.createAllReportDataV2();
|
||||||
|
}
|
||||||
|
|
||||||
public void createCurDateCustomerReport() throws IOException {
|
public void createCurDateCustomerReport() throws IOException {
|
||||||
Date from = Date.from(LocalDate.now().atStartOfDay()
|
Date from = Date.from(LocalDate.now().atStartOfDay()
|
||||||
.atZone(ZoneId.systemDefault()).toInstant());
|
.atZone(ZoneId.systemDefault()).toInstant());
|
||||||
|
|
@ -59,4 +67,15 @@ public class WeComTask {
|
||||||
.atZone(ZoneId.systemDefault()).toInstant()));
|
.atZone(ZoneId.systemDefault()).toInstant()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void createCurDateCustomerReportV2() throws IOException {
|
||||||
|
System.out.println("计算所有流量看板数据V2");
|
||||||
|
Date from = Date.from(LocalDate.now().atStartOfDay()
|
||||||
|
.atZone(ZoneId.systemDefault()).toInstant());
|
||||||
|
System.out.println("计算" + JSON.toJSONString(from) + "流量看板数据");
|
||||||
|
List<CorpInfo> corpInfos = corpInfoMapper.selectCorpInfoList(new CorpInfo());
|
||||||
|
corpInfos.forEach(item->{
|
||||||
|
handleAllDataV2.createReportDataV2(item.getCorpId(), from);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,93 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询流量看板V2数据列表(组级)
|
||||||
|
export function listCustomerStatisticsV2(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流量看板V2树状数据(组+标签)
|
||||||
|
export function treeCustomerStatisticsV2(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/tree',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询流量看板V2数据详细
|
||||||
|
export function getCustomerStatisticsV2(id) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增流量看板V2数据
|
||||||
|
export function addCustomerStatisticsV2(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改流量看板V2数据
|
||||||
|
export function updateCustomerStatisticsV2(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除流量看板V2数据
|
||||||
|
export function delCustomerStatisticsV2(ids) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/' + ids,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出流量看板V2数据
|
||||||
|
export function exportCustomerStatisticsV2(query) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/export',
|
||||||
|
method: 'post',
|
||||||
|
params: query,
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成本录入(支持组级和标签级)
|
||||||
|
export function inputCost(data) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/cost',
|
||||||
|
method: 'post',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算指定日期的统计数据
|
||||||
|
export function recalculateStatistics(date) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/recalculate',
|
||||||
|
method: 'post',
|
||||||
|
params: { date: date }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新计算指定日期范围的统计数据
|
||||||
|
export function recalculateStatisticsRange(startDate, endDate) {
|
||||||
|
return request({
|
||||||
|
url: '/wecom/customerStatisticsV2/recalculateRange',
|
||||||
|
method: 'post',
|
||||||
|
params: {
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -310,14 +310,85 @@ export default {
|
||||||
if (this.queryParams.year && this.queryParams.week) {
|
if (this.queryParams.year && this.queryParams.week) {
|
||||||
const year = parseInt(this.queryParams.year)
|
const year = parseInt(this.queryParams.year)
|
||||||
const week = this.queryParams.week
|
const week = this.queryParams.week
|
||||||
const startDate = this.getWeekStartDate(year, week)
|
const weekRange = this.calculateWeekRangeFixed(year, week)
|
||||||
const endDate = new Date(startDate)
|
this.weekDateRange = weekRange.startDate + ' 至 ' + weekRange.endDate
|
||||||
endDate.setDate(endDate.getDate() + 6)
|
|
||||||
this.weekDateRange = this.formatDate(startDate) + ' 至 ' + this.formatDate(endDate)
|
|
||||||
} else {
|
} else {
|
||||||
this.weekDateRange = ''
|
this.weekDateRange = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 计算周范围(修复跨年问题)
|
||||||
|
// 规则:
|
||||||
|
// 1. 周的第一天是周一(不是周日)
|
||||||
|
// 2. 第一周:从1月1日开始,到1月1日所在周的周日结束
|
||||||
|
// - 如果1月1日是周日,第一周只有1天(1月1日当天)
|
||||||
|
// 3. 最后一周:从最后一周的周一(12月31日往前推)开始,到12月31日结束
|
||||||
|
// - 如果12月31日是周一,最后一周只有1天(12月31日当天)
|
||||||
|
// 4. 不显示跨年的日期范围
|
||||||
|
calculateWeekRangeFixed(year, week) {
|
||||||
|
const jan1 = new Date(year, 0, 1)
|
||||||
|
const jan1Weekday = jan1.getDay() // 0=周日,1=周一,...,6=周六
|
||||||
|
const dec31 = new Date(year, 11, 31)
|
||||||
|
const dec31Weekday = dec31.getDay() // 0=周日,1=周一,...,6=周六
|
||||||
|
|
||||||
|
// 第一周:从1月1日开始
|
||||||
|
if (week === 1) {
|
||||||
|
const startDate = this.formatDate(jan1)
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
// 如果1月1日是周日(0),则第一周只有1天(当天)
|
||||||
|
// 否则,计算到本周日
|
||||||
|
let daysToSunday
|
||||||
|
if (jan1Weekday === 0) {
|
||||||
|
// 1月1日是周日,第一周只有1天
|
||||||
|
daysToSunday = 0
|
||||||
|
} else {
|
||||||
|
// 1月1日不是周日,计算到本周日
|
||||||
|
daysToSunday = 7 - jan1Weekday
|
||||||
|
}
|
||||||
|
const firstWeekEnd = new Date(jan1)
|
||||||
|
firstWeekEnd.setDate(jan1.getDate() + daysToSunday)
|
||||||
|
const endDate = this.formatDate(firstWeekEnd)
|
||||||
|
return { startDate, endDate }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
let firstWeekDays
|
||||||
|
if (jan1Weekday === 0) {
|
||||||
|
firstWeekDays = 0 // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
firstWeekDays = 7 - jan1Weekday // 到本周日
|
||||||
|
}
|
||||||
|
const firstWeekEnd = new Date(jan1)
|
||||||
|
firstWeekEnd.setDate(jan1.getDate() + firstWeekDays)
|
||||||
|
|
||||||
|
// 计算第二周开始日期(第一周结束后的周一)
|
||||||
|
const secondWeekStart = new Date(firstWeekEnd)
|
||||||
|
secondWeekStart.setDate(firstWeekEnd.getDate() + 1)
|
||||||
|
|
||||||
|
// 计算目标周的开始和结束
|
||||||
|
// 从第二周开始,每周都是周一到周日
|
||||||
|
const targetWeekStart = new Date(secondWeekStart)
|
||||||
|
targetWeekStart.setDate(secondWeekStart.getDate() + (week - 2) * 7)
|
||||||
|
|
||||||
|
const targetWeekEnd = new Date(targetWeekStart)
|
||||||
|
targetWeekEnd.setDate(targetWeekStart.getDate() + 6)
|
||||||
|
|
||||||
|
// 如果目标周的结束超过了12月31日,则调整到12月31日
|
||||||
|
let endDate = this.formatDate(targetWeekEnd)
|
||||||
|
if (targetWeekEnd > dec31) {
|
||||||
|
endDate = this.formatDate(dec31)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果开始日期已经跨年了(大于12月31日),则返回空
|
||||||
|
if (targetWeekStart > dec31) {
|
||||||
|
return { startDate: '', endDate: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate: this.formatDate(targetWeekStart),
|
||||||
|
endDate: endDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取指定周的开始日期(周一)- 旧方法,保留用于兼容
|
||||||
getWeekStartDate(year, week) {
|
getWeekStartDate(year, week) {
|
||||||
const date = new Date(year, 0, 1)
|
const date = new Date(year, 0, 1)
|
||||||
const dayOfWeek = date.getDay()
|
const dayOfWeek = date.getDay()
|
||||||
|
|
@ -371,12 +442,75 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleDataTypeChange() {
|
handleDataTypeChange() {
|
||||||
this.queryParams.year = undefined
|
const dataType = this.queryParams.dataType
|
||||||
this.queryParams.week = undefined
|
|
||||||
this.queryParams.yearMonth = undefined
|
if (dataType === 'day' || !dataType) {
|
||||||
this.queryParams.startDate = undefined
|
// 按天:设置默认日期为今天
|
||||||
this.queryParams.endDate = undefined
|
const today = new Date()
|
||||||
this.weekDateRange = ''
|
const year = today.getFullYear()
|
||||||
|
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(today.getDate()).padStart(2, '0')
|
||||||
|
this.queryParams.startDate = `${year}-${month}-${day}`
|
||||||
|
this.queryParams.endDate = `${year}-${month}-${day}`
|
||||||
|
// 清除其他类型的参数
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
} else if (dataType === 'week') {
|
||||||
|
// 按周:计算当前周,清除天数参数
|
||||||
|
const now = new Date()
|
||||||
|
// 使用字符串格式与 el-date-picker 的 value-format 保持一致
|
||||||
|
this.queryParams.year = String(now.getFullYear())
|
||||||
|
// 计算当前是第几周
|
||||||
|
const jan1 = new Date(now.getFullYear(), 0, 1)
|
||||||
|
const jan1Weekday = jan1.getDay() // 0=周日,1=周一,...,6=周六
|
||||||
|
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
let firstWeekDays
|
||||||
|
if (jan1Weekday === 0) {
|
||||||
|
firstWeekDays = 0 // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
firstWeekDays = 7 - jan1Weekday // 到本周日
|
||||||
|
}
|
||||||
|
const firstWeekEnd = new Date(jan1)
|
||||||
|
firstWeekEnd.setDate(jan1.getDate() + firstWeekDays)
|
||||||
|
|
||||||
|
// 计算第二周开始日期(第一周结束后的周一)
|
||||||
|
const secondWeekStart = new Date(firstWeekEnd)
|
||||||
|
secondWeekStart.setDate(firstWeekEnd.getDate() + 1)
|
||||||
|
|
||||||
|
// 计算今天是第几周
|
||||||
|
const diffTime = now - secondWeekStart
|
||||||
|
const diffWeeks = Math.floor(diffTime / (7 * 24 * 60 * 60 * 1000))
|
||||||
|
this.queryParams.week = diffWeeks + 2 // 第2周开始计数
|
||||||
|
|
||||||
|
this.updateWeekDateRange()
|
||||||
|
// 清除天数参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
} else if (dataType === 'month') {
|
||||||
|
// 按月:设置当前年月,清除天数参数
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
this.queryParams.yearMonth = `${year}-${month}`
|
||||||
|
// 清除天数参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
} else if (dataType === 'all') {
|
||||||
|
// 全部数据:清除所有日期参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleQuery() {
|
handleQuery() {
|
||||||
this.queryParams.pageNum = 1
|
this.queryParams.pageNum = 1
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,907 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-container>
|
||||||
|
<el-aside width="250px" class="tree-aside">
|
||||||
|
<div class="tree-header">
|
||||||
|
<span>组/标签筛选</span>
|
||||||
|
<el-button type="text" size="mini" @click="resetTreeFilter">重置</el-button>
|
||||||
|
</div>
|
||||||
|
<el-tree
|
||||||
|
ref="filterTree"
|
||||||
|
:data="treeFilterData"
|
||||||
|
:props="treeProps"
|
||||||
|
node-key="id"
|
||||||
|
:default-expand-all="true"
|
||||||
|
:highlight-current="true"
|
||||||
|
@node-click="handleTreeNodeClick"
|
||||||
|
class="filter-tree"
|
||||||
|
>
|
||||||
|
<template slot-scope="{ node, data }">
|
||||||
|
<span class="tree-node">
|
||||||
|
<span :class="{'group-node': data.type === 'group', 'tag-node': data.type === 'tag'}">
|
||||||
|
{{ node.label }}
|
||||||
|
</span>
|
||||||
|
<span class="node-count" v-if="data.count !== undefined">({{ data.count }})</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-aside>
|
||||||
|
|
||||||
|
<el-main>
|
||||||
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||||
|
<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"
|
||||||
|
placeholder="选择开始日期"
|
||||||
|
value-format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="queryParams.dataType === 'day' || !queryParams.dataType" label="结束日期" prop="endDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.endDate"
|
||||||
|
type="date"
|
||||||
|
placeholder="选择结束日期"
|
||||||
|
value-format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="组" prop="groupName">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.groupName"
|
||||||
|
placeholder="请输入组名"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
style="width: 120px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="标签" prop="tagName">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.tagName"
|
||||||
|
placeholder="请输入标签名"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
style="width: 120px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
plain
|
||||||
|
icon="el-icon-download"
|
||||||
|
size="mini"
|
||||||
|
@click="handleExport"
|
||||||
|
v-hasPermi="['wecom:customerStatisticsV2:export']"
|
||||||
|
>导出</el-button>
|
||||||
|
</el-col>
|
||||||
|
<!-- 重新计算按钮已隐藏
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
icon="el-icon-refresh"
|
||||||
|
size="mini"
|
||||||
|
@click="handleRecalculate"
|
||||||
|
v-hasPermi="['wecom:customerStatisticsV2:recalculate']"
|
||||||
|
>重新计算</el-button>
|
||||||
|
</el-col>
|
||||||
|
-->
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-popover
|
||||||
|
placement="bottom"
|
||||||
|
width="400"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<div class="column-config">
|
||||||
|
<div class="config-header">
|
||||||
|
<span>选择要显示的列</span>
|
||||||
|
<el-button type="text" size="mini" @click="selectAllColumns">全选</el-button>
|
||||||
|
<el-button type="text" size="mini" @click="unselectAllColumns">全不选</el-button>
|
||||||
|
<el-button type="text" size="mini" @click="resetColumns">重置</el-button>
|
||||||
|
</div>
|
||||||
|
<el-divider></el-divider>
|
||||||
|
<div class="config-body">
|
||||||
|
<div v-for="group in columnGroups" :key="group.label" class="config-group">
|
||||||
|
<div class="group-title">{{ group.label }}</div>
|
||||||
|
<el-checkbox-group v-model="selectedColumns" class="checkbox-group">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="col in group.columns"
|
||||||
|
:key="col.prop"
|
||||||
|
:label="col.prop"
|
||||||
|
class="checkbox-item"
|
||||||
|
>{{ col.label }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
slot="reference"
|
||||||
|
type="info"
|
||||||
|
plain
|
||||||
|
icon="el-icon-setting"
|
||||||
|
size="mini"
|
||||||
|
>列配置</el-button>
|
||||||
|
</el-popover>
|
||||||
|
</el-col>
|
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="dataList"
|
||||||
|
row-key="id"
|
||||||
|
border
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column label="组" align="left" prop="groupName" width="120" fixed>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span :style="{fontWeight: scope.row.dataLevel === 1 ? 'bold' : 'normal', color: '#409EFF'}">
|
||||||
|
{{ scope.row.groupName }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="标签" align="left" prop="tagName" width="180" fixed>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span v-if="scope.row.tagName" style="color: #606266; padding-left: 10px;">
|
||||||
|
{{ scope.row.tagName }}
|
||||||
|
</span>
|
||||||
|
<span v-else style="color: #909399; font-style: italic;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="日期" align="center" width="140" fixed>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<template v-for="col in allColumns">
|
||||||
|
<el-table-column
|
||||||
|
v-if="selectedColumns.includes(col.prop)"
|
||||||
|
:key="col.prop"
|
||||||
|
:label="col.label"
|
||||||
|
:align="col.align || 'center'"
|
||||||
|
:prop="col.prop"
|
||||||
|
:width="col.width"
|
||||||
|
>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<template v-if="col.editable && canEditCost(scope.row)">
|
||||||
|
<el-input-number
|
||||||
|
v-model="scope.row[col.prop]"
|
||||||
|
size="mini"
|
||||||
|
:controls="false"
|
||||||
|
:precision="col.precision || 2"
|
||||||
|
style="width: 100%"
|
||||||
|
@keyup.enter.native="handleInputCost(scope.row, scope.row[col.prop], col.costType || 'total')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row[col.prop] }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total>0"
|
||||||
|
:total="total"
|
||||||
|
:page.sync="queryParams.pageNum"
|
||||||
|
:limit.sync="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listCustomerStatisticsV2, treeCustomerStatisticsV2, exportCustomerStatisticsV2, inputCost, recalculateStatisticsRange } from "@/api/wecom/customerStatisticsV2"
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'customerStatisticsV2_columns'
|
||||||
|
|
||||||
|
const DEFAULT_COLUMNS = [
|
||||||
|
'totalCost', 'singleCost', 'orderCost', 'orderCount', 'customerCount', 'conversionRate',
|
||||||
|
'timelyRate', 'parentRate', 'studentRate', 'teacherRate'
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CustomerStatisticsV2",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
ids: [],
|
||||||
|
single: true,
|
||||||
|
multiple: true,
|
||||||
|
showSearch: true,
|
||||||
|
total: 0,
|
||||||
|
dataList: [],
|
||||||
|
treeData: [], // 树状筛选数据(从/tree接口获取)
|
||||||
|
weekDateRange: '', // 周日期范围显示
|
||||||
|
|
||||||
|
queryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
dataType: 'day', // 默认按天
|
||||||
|
startDate: undefined,
|
||||||
|
endDate: undefined,
|
||||||
|
year: new Date().getFullYear(),
|
||||||
|
week: undefined,
|
||||||
|
yearMonth: undefined,
|
||||||
|
groupName: undefined,
|
||||||
|
tagName: undefined
|
||||||
|
},
|
||||||
|
selectedColumns: [],
|
||||||
|
treeProps: {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label'
|
||||||
|
},
|
||||||
|
columnGroups: [
|
||||||
|
{
|
||||||
|
label: '成本指标',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'totalCost', label: '总成本', width: 120, editable: true, costType: 'total' },
|
||||||
|
{ prop: 'singleCost', label: '单条成本', width: 100, editable: true, costType: 'single' },
|
||||||
|
{ prop: 'orderCost', label: '成单成本', width: 100 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '核心指标',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'orderCount', label: '成单数', width: 80 },
|
||||||
|
{ prop: 'customerCount', label: '进粉数', width: 80 },
|
||||||
|
{ prop: 'conversionRate', label: '转化率', width: 80 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '及时单指标',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'timelyRate', label: '及时单占比', width: 100 },
|
||||||
|
{ prop: 'nonTimelyRate', label: '非及时单占比', width: 110 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '客户属性',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'customerAttrCount', label: '客户属性数量', width: 110 },
|
||||||
|
{ prop: 'parentRate', label: '家长占比', width: 80 },
|
||||||
|
{ prop: 'studentRate', label: '学生占比', width: 80 },
|
||||||
|
{ prop: 'teacherRate', label: '老师占比', width: 80 },
|
||||||
|
{ prop: 'unknownRate', label: '未知占比', width: 80 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '意向度',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'intentionCount', label: '意向度数量', width: 100 },
|
||||||
|
{ prop: 'activeQuoteRate', label: '主动报价占比', width: 110 },
|
||||||
|
{ prop: 'passiveQuoteRate', label: '被动报价占比', width: 110 },
|
||||||
|
{ prop: 'noQuoteRate', label: '未开口报价占比', width: 120 },
|
||||||
|
{ prop: 'deletedQuoteRate', label: '已删除报价占比', width: 120 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '年级统计',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'gradeCount', label: '年级数量', width: 80 },
|
||||||
|
{ prop: 'primaryRate', label: '小学占比', width: 80 },
|
||||||
|
{ prop: 'middleRate', label: '初中占比', width: 80 },
|
||||||
|
{ prop: 'highRate', label: '高中占比', width: 80 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '出单率',
|
||||||
|
columns: [
|
||||||
|
{ prop: 'parentOrderRate', label: '家长出单率', width: 100 },
|
||||||
|
{ prop: 'studentOrderRate', label: '学生出单率', width: 100 },
|
||||||
|
{ prop: 'teacherOrderRate', label: '老师出单率', width: 100 },
|
||||||
|
{ prop: 'unknownOrderRate', label: '未知出单率', width: 100 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
allColumns() {
|
||||||
|
const cols = []
|
||||||
|
this.columnGroups.forEach(group => {
|
||||||
|
group.columns.forEach(col => {
|
||||||
|
cols.push(col)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return cols
|
||||||
|
},
|
||||||
|
treeFilterData() {
|
||||||
|
// 直接使用/tree接口返回的数据
|
||||||
|
return this.treeData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadColumnConfig()
|
||||||
|
const today = new Date()
|
||||||
|
const year = today.getFullYear()
|
||||||
|
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(today.getDate()).padStart(2, '0')
|
||||||
|
this.queryParams.startDate = `${year}-${month}-${day}`
|
||||||
|
this.queryParams.endDate = `${year}-${month}-${day}`
|
||||||
|
// 确保数据更新后再调用
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.getTreeData() // 加载树状数据
|
||||||
|
this.getList() // 加载列表数据
|
||||||
|
})
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 当日期变化时,重新加载树状数据
|
||||||
|
'queryParams.startDate': function(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal && (this.queryParams.dataType === 'day' || !this.queryParams.dataType)) {
|
||||||
|
this.getTreeData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'queryParams.endDate': function(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal && (this.queryParams.dataType === 'day' || !this.queryParams.dataType)) {
|
||||||
|
this.getTreeData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听周相关参数变化
|
||||||
|
'queryParams.year': function(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal && this.queryParams.dataType === 'week') {
|
||||||
|
this.updateWeekDateRange()
|
||||||
|
this.getTreeData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'queryParams.week': function(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal && this.queryParams.dataType === 'week') {
|
||||||
|
this.updateWeekDateRange()
|
||||||
|
this.getTreeData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听月参数变化
|
||||||
|
'queryParams.yearMonth': function(newVal, oldVal) {
|
||||||
|
if (newVal !== oldVal && this.queryParams.dataType === 'month') {
|
||||||
|
this.getTreeData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 监听列配置变化
|
||||||
|
selectedColumns: {
|
||||||
|
handler() {
|
||||||
|
this.saveColumnConfig()
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 更新周日期范围显示(修复跨年周问题)
|
||||||
|
updateWeekDateRange() {
|
||||||
|
if (this.queryParams.year && this.queryParams.week) {
|
||||||
|
const year = parseInt(this.queryParams.year)
|
||||||
|
const week = this.queryParams.week
|
||||||
|
const weekRange = this.calculateWeekRangeFixed(year, week)
|
||||||
|
this.weekDateRange = weekRange.startDate + ' 至 ' + weekRange.endDate
|
||||||
|
} else {
|
||||||
|
this.weekDateRange = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 计算周范围(修复跨年问题)
|
||||||
|
// 规则:
|
||||||
|
// 1. 周的第一天是周一(不是周日)
|
||||||
|
// 2. 第一周:从1月1日开始,到1月1日所在周的周日结束
|
||||||
|
// - 如果1月1日是周日,第一周只有1天(1月1日当天)
|
||||||
|
// 3. 最后一周:从最后一周的周一(12月31日往前推)开始,到12月31日结束
|
||||||
|
// - 如果12月31日是周一,最后一周只有1天(12月31日当天)
|
||||||
|
// 4. 不显示跨年的日期范围
|
||||||
|
calculateWeekRangeFixed(year, week) {
|
||||||
|
const jan1 = new Date(year, 0, 1)
|
||||||
|
const jan1Weekday = jan1.getDay() // 0=周日,1=周一,...,6=周六
|
||||||
|
const dec31 = new Date(year, 11, 31)
|
||||||
|
const dec31Weekday = dec31.getDay() // 0=周日,1=周一,...,6=周六
|
||||||
|
|
||||||
|
// 第一周:从1月1日开始
|
||||||
|
if (week === 1) {
|
||||||
|
const startDate = this.formatDate(jan1)
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
// 如果1月1日是周日(0),则第一周只有1天(当天)
|
||||||
|
// 否则,计算到本周日
|
||||||
|
let daysToSunday
|
||||||
|
if (jan1Weekday === 0) {
|
||||||
|
// 1月1日是周日,第一周只有1天
|
||||||
|
daysToSunday = 0
|
||||||
|
} else {
|
||||||
|
// 1月1日不是周日,计算到本周日
|
||||||
|
daysToSunday = 7 - jan1Weekday
|
||||||
|
}
|
||||||
|
const firstWeekEnd = new Date(jan1)
|
||||||
|
firstWeekEnd.setDate(jan1.getDate() + daysToSunday)
|
||||||
|
const endDate = this.formatDate(firstWeekEnd)
|
||||||
|
return { startDate, endDate }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算第一周的结束日期(周日)
|
||||||
|
let firstWeekDays
|
||||||
|
if (jan1Weekday === 0) {
|
||||||
|
firstWeekDays = 0 // 1月1日是周日,第一周只有1天
|
||||||
|
} else {
|
||||||
|
firstWeekDays = 7 - jan1Weekday // 到本周日
|
||||||
|
}
|
||||||
|
const firstWeekEnd = new Date(jan1)
|
||||||
|
firstWeekEnd.setDate(jan1.getDate() + firstWeekDays)
|
||||||
|
|
||||||
|
// 计算第二周开始日期(第一周结束后的周一)
|
||||||
|
const secondWeekStart = new Date(firstWeekEnd)
|
||||||
|
secondWeekStart.setDate(firstWeekEnd.getDate() + 1)
|
||||||
|
|
||||||
|
// 计算目标周的开始和结束
|
||||||
|
// 从第二周开始,每周都是周一到周日
|
||||||
|
const targetWeekStart = new Date(secondWeekStart)
|
||||||
|
targetWeekStart.setDate(secondWeekStart.getDate() + (week - 2) * 7)
|
||||||
|
|
||||||
|
const targetWeekEnd = new Date(targetWeekStart)
|
||||||
|
targetWeekEnd.setDate(targetWeekStart.getDate() + 6)
|
||||||
|
|
||||||
|
// 如果目标周的结束超过了12月31日,则调整到12月31日
|
||||||
|
let endDate = this.formatDate(targetWeekEnd)
|
||||||
|
if (targetWeekEnd > dec31) {
|
||||||
|
endDate = this.formatDate(dec31)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果开始日期已经跨年了(大于12月31日),则返回空
|
||||||
|
if (targetWeekStart > dec31) {
|
||||||
|
return { startDate: '', endDate: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startDate: this.formatDate(targetWeekStart),
|
||||||
|
endDate: endDate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取指定周的开始日期(周一)- 旧方法,保留用于兼容
|
||||||
|
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
|
||||||
|
},
|
||||||
|
loadColumnConfig() {
|
||||||
|
const saved = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
this.selectedColumns = JSON.parse(saved)
|
||||||
|
} catch (e) {
|
||||||
|
this.selectedColumns = [...DEFAULT_COLUMNS]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedColumns = [...DEFAULT_COLUMNS]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveColumnConfig() {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.selectedColumns))
|
||||||
|
},
|
||||||
|
selectAllColumns() {
|
||||||
|
this.selectedColumns = this.allColumns.map(col => col.prop)
|
||||||
|
this.saveColumnConfig()
|
||||||
|
},
|
||||||
|
unselectAllColumns() {
|
||||||
|
this.selectedColumns = []
|
||||||
|
this.saveColumnConfig()
|
||||||
|
},
|
||||||
|
resetColumns() {
|
||||||
|
this.selectedColumns = [...DEFAULT_COLUMNS]
|
||||||
|
this.saveColumnConfig()
|
||||||
|
},
|
||||||
|
handleTreeNodeClick(data) {
|
||||||
|
// 自动填入筛选条件
|
||||||
|
if (data.type === 'group') {
|
||||||
|
this.queryParams.groupName = data.groupName
|
||||||
|
this.queryParams.tagName = undefined
|
||||||
|
} else if (data.type === 'tag') {
|
||||||
|
this.queryParams.groupName = data.groupName
|
||||||
|
this.queryParams.tagName = data.tagName
|
||||||
|
}
|
||||||
|
// 触发查询
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
resetTreeFilter() {
|
||||||
|
this.queryParams.groupName = undefined
|
||||||
|
this.queryParams.tagName = undefined
|
||||||
|
this.$refs.filterTree.setCurrentKey(null)
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
canEditCost(row) {
|
||||||
|
// 只有按天查询时才允许编辑成本
|
||||||
|
if (this.queryParams.dataType !== 'day') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return row.dataLevel === 1 || row.dataLevel === 2
|
||||||
|
},
|
||||||
|
// 获取树状筛选数据(从/tree接口)
|
||||||
|
getTreeData() {
|
||||||
|
let params = {}
|
||||||
|
if (this.queryParams.dataType === 'day' || !this.queryParams.dataType) {
|
||||||
|
// 按天查询,使用开始日期和结束日期
|
||||||
|
params = {
|
||||||
|
startDate: this.queryParams.startDate,
|
||||||
|
endDate: this.queryParams.endDate
|
||||||
|
}
|
||||||
|
} else if (this.queryParams.dataType === 'week') {
|
||||||
|
// 计算周的开始和结束日期
|
||||||
|
const weekRange = this.calculateWeekRange(this.queryParams.year, this.queryParams.week)
|
||||||
|
params = {
|
||||||
|
startDate: weekRange.startDate,
|
||||||
|
endDate: weekRange.endDate
|
||||||
|
}
|
||||||
|
} else if (this.queryParams.dataType === 'month') {
|
||||||
|
// 计算月的开始和结束日期
|
||||||
|
const monthRange = this.calculateMonthRange(this.queryParams.yearMonth)
|
||||||
|
params = {
|
||||||
|
startDate: monthRange.startDate,
|
||||||
|
endDate: monthRange.endDate
|
||||||
|
}
|
||||||
|
} else if (this.queryParams.dataType === 'all') {
|
||||||
|
// 全部数据,不传递日期参数
|
||||||
|
params = {}
|
||||||
|
}
|
||||||
|
console.log('getTreeData called with params:', params, 'dataType:', this.queryParams.dataType)
|
||||||
|
treeCustomerStatisticsV2(params).then(response => {
|
||||||
|
console.log('getTreeData response:', response)
|
||||||
|
this.treeData = response.data || []
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('getTreeData error:', error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 计算周的开始和结束日期
|
||||||
|
calculateWeekRange(year, week) {
|
||||||
|
const start = new Date(year, 0, 1)
|
||||||
|
const diff = (week - 1) * 7 * 24 * 60 * 60 * 1000
|
||||||
|
const startDate = new Date(start.getTime() + diff - ((start.getDay() + 6) % 7) * 24 * 60 * 60 * 1000)
|
||||||
|
const endDate = new Date(startDate.getTime() + 6 * 24 * 60 * 60 * 1000)
|
||||||
|
return {
|
||||||
|
startDate: this.parseTime(startDate, '{y}-{m}-{d}'),
|
||||||
|
endDate: this.parseTime(endDate, '{y}-{m}-{d}')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 计算月的开始和结束日期
|
||||||
|
calculateMonthRange(yearMonth) {
|
||||||
|
if (!yearMonth) {
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
yearMonth = `${year}-${month}`
|
||||||
|
}
|
||||||
|
const [year, month] = yearMonth.split('-')
|
||||||
|
const startDate = new Date(year, month - 1, 1)
|
||||||
|
const endDate = new Date(year, month, 0)
|
||||||
|
return {
|
||||||
|
startDate: this.parseTime(startDate, '{y}-{m}-{d}'),
|
||||||
|
endDate: this.parseTime(endDate, '{y}-{m}-{d}')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取列表数据(从/list接口)
|
||||||
|
getList() {
|
||||||
|
this.loading = true
|
||||||
|
listCustomerStatisticsV2(this.queryParams).then(response => {
|
||||||
|
this.dataList = response.rows || []
|
||||||
|
this.total = response.total || 0
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleQuery() {
|
||||||
|
this.queryParams.pageNum = 1
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
handleDataTypeChange() {
|
||||||
|
// 切换数据维度时,重置相关参数
|
||||||
|
if (this.queryParams.dataType === 'day') {
|
||||||
|
// 按天:设置默认日期为今天
|
||||||
|
const today = new Date()
|
||||||
|
const year = today.getFullYear()
|
||||||
|
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(today.getDate()).padStart(2, '0')
|
||||||
|
this.queryParams.startDate = `${year}-${month}-${day}`
|
||||||
|
this.queryParams.endDate = `${year}-${month}-${day}`
|
||||||
|
// 清除其他类型的参数
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
} else if (this.queryParams.dataType === 'week') {
|
||||||
|
// 按周:计算当前周,清除天数参数
|
||||||
|
const now = new Date()
|
||||||
|
// 使用字符串格式与 el-date-picker 的 value-format 保持一致
|
||||||
|
this.queryParams.year = String(now.getFullYear())
|
||||||
|
const start = new Date(now.getFullYear(), 0, 1)
|
||||||
|
const diff = now - start + ((start.getDay() + 6) % 7) * 86400000
|
||||||
|
this.queryParams.week = Math.floor(diff / 604800000) + 1
|
||||||
|
this.updateWeekDateRange()
|
||||||
|
// 清除天数参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
} else if (this.queryParams.dataType === 'month') {
|
||||||
|
// 按月:设置当前年月,清除天数参数
|
||||||
|
const now = new Date()
|
||||||
|
const year = now.getFullYear()
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||||
|
this.queryParams.yearMonth = `${year}-${month}`
|
||||||
|
// 清除天数参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
} else if (this.queryParams.dataType === 'all') {
|
||||||
|
// 全部数据:清除所有日期参数
|
||||||
|
this.queryParams.startDate = undefined
|
||||||
|
this.queryParams.endDate = undefined
|
||||||
|
this.queryParams.year = undefined
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
}
|
||||||
|
// 重新加载树状数据
|
||||||
|
this.getTreeData()
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
resetQuery() {
|
||||||
|
this.resetForm("queryForm")
|
||||||
|
this.queryParams.dataType = 'day'
|
||||||
|
this.queryParams.groupName = undefined
|
||||||
|
this.queryParams.tagName = undefined
|
||||||
|
// 使用字符串格式与 el-date-picker 的 value-format 保持一致
|
||||||
|
this.queryParams.year = String(new Date().getFullYear())
|
||||||
|
this.queryParams.week = undefined
|
||||||
|
this.queryParams.yearMonth = undefined
|
||||||
|
this.weekDateRange = ''
|
||||||
|
// 重置日期为今天
|
||||||
|
const today = new Date()
|
||||||
|
const year = today.getFullYear()
|
||||||
|
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(today.getDate()).padStart(2, '0')
|
||||||
|
this.queryParams.startDate = `${year}-${month}-${day}`
|
||||||
|
this.queryParams.endDate = `${year}-${month}-${day}`
|
||||||
|
this.$refs.filterTree.setCurrentKey(null)
|
||||||
|
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}')
|
||||||
|
this.download('/wecom/customerStatisticsV2/export', {
|
||||||
|
...this.queryParams
|
||||||
|
}, `流量看板V2数据_${formattedDate}.xlsx`)
|
||||||
|
},
|
||||||
|
handleInputCost(row, cost, inputType) {
|
||||||
|
if (cost === '' || cost === null || cost === undefined) {
|
||||||
|
this.$modal.msgWarning("请输入成本金额")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const numCost = Number(cost)
|
||||||
|
if (isNaN(numCost)) {
|
||||||
|
this.$modal.msgWarning("成本金额必须为数字")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numCost < 0) {
|
||||||
|
this.$modal.msgWarning("成本金额不能为负数")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
const costTypeName = inputType === 'single' ? '单条成本' : '总成本'
|
||||||
|
const displayName = row.tagName || row.groupName
|
||||||
|
|
||||||
|
this.$modal.confirm(`确认修改 ${formattedDate} ${displayName} 的${costTypeName}为 ${numCost}?`).then(() => {
|
||||||
|
inputCost({
|
||||||
|
date: formattedDate,
|
||||||
|
groupName: row.groupName,
|
||||||
|
tagName: row.tagName || null,
|
||||||
|
costValue: numCost,
|
||||||
|
inputType: inputType
|
||||||
|
}).then(() => {
|
||||||
|
this.$modal.msgSuccess("修改成功")
|
||||||
|
this.getList()
|
||||||
|
}).catch(() => {
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleRecalculate() {
|
||||||
|
if (!this.queryParams.startDate || !this.queryParams.endDate) {
|
||||||
|
this.$modal.msgWarning("请选择日期范围")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$modal.confirm(`确认重新计算 ${this.queryParams.startDate} 至 ${this.queryParams.endDate} 的统计数据?`).then(() => {
|
||||||
|
recalculateStatisticsRange(this.queryParams.startDate, this.queryParams.endDate).then(() => {
|
||||||
|
this.$modal.msgSuccess("重新计算成功")
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-container {
|
||||||
|
min-height: calc(100vh - 180px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-aside {
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e6e6e6;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 5px;
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-header span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tree {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-node {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-node {
|
||||||
|
color: #606266;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-count {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table .el-input-number {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-config {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-header span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-body {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #409EFF;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-left: 3px solid #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-item {
|
||||||
|
margin-right: 15px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue