From 11c21f2a33ba8078b6244066a73b7581bbcc122c Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Mon, 29 Dec 2025 10:05:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(crm):=20=E6=B7=BB=E5=8A=A0=E9=94=80?= =?UTF-8?q?=E5=94=AE=E6=8A=A5=E8=A1=A8=E5=8A=9F=E8=83=BD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增销售报表查询业务对象CrmSalesReportBo,支持多种查询条件 - 创建销售报表控制器CrmSalesReportController,提供汇总数据、订单明细、统计分析等接口 - 实现销售报表数据访问层CrmSalesReportMapper,包含销售汇总、订单明细、销售员统计等查询 - 开发销售报表服务层ICrmSalesReportService及其实现类,处理报表数据逻辑 - 设计销售报表视图对象CrmSalesReportVo,包含汇总信息、订单明细、统计分析等数据结构 - 集成Excel导出功能,支持订单明细、销售员统计、客户等级统计、行业统计的数据导出 - 实现多维度统计分析,包括销售员业绩、客户等级分布、行业分布等统计功能 --- .../controller/CrmSalesReportController.java | 127 ++++++++ .../klp/crm/domain/bo/CrmSalesReportBo.java | 108 +++++++ .../klp/crm/domain/vo/CrmSalesReportVo.java | 287 ++++++++++++++++++ .../klp/crm/mapper/CrmSalesReportMapper.java | 57 ++++ .../crm/service/ICrmSalesReportService.java | 75 +++++ .../impl/CrmSalesReportServiceImpl.java | 104 +++++++ .../mapper/klp/CrmSalesReportMapper.xml | 196 ++++++++++++ 7 files changed, 954 insertions(+) create mode 100644 klp-crm/src/main/java/com/klp/crm/controller/CrmSalesReportController.java create mode 100644 klp-crm/src/main/java/com/klp/crm/domain/bo/CrmSalesReportBo.java create mode 100644 klp-crm/src/main/java/com/klp/crm/domain/vo/CrmSalesReportVo.java create mode 100644 klp-crm/src/main/java/com/klp/crm/mapper/CrmSalesReportMapper.java create mode 100644 klp-crm/src/main/java/com/klp/crm/service/ICrmSalesReportService.java create mode 100644 klp-crm/src/main/java/com/klp/crm/service/impl/CrmSalesReportServiceImpl.java create mode 100644 klp-crm/src/main/resources/mapper/klp/CrmSalesReportMapper.xml diff --git a/klp-crm/src/main/java/com/klp/crm/controller/CrmSalesReportController.java b/klp-crm/src/main/java/com/klp/crm/controller/CrmSalesReportController.java new file mode 100644 index 00000000..3a80b21c --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/controller/CrmSalesReportController.java @@ -0,0 +1,127 @@ +package com.klp.crm.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.*; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.klp.common.annotation.Log; +import com.klp.common.core.controller.BaseController; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.core.domain.R; +import com.klp.common.enums.BusinessType; +import com.klp.common.utils.poi.ExcelUtil; +import com.klp.crm.domain.vo.CrmSalesReportVo; +import com.klp.crm.domain.bo.CrmSalesReportBo; +import com.klp.crm.service.ICrmSalesReportService; +import com.klp.common.core.page.TableDataInfo; + +/** + * 销售报表 + * + * @author klp + * @date 2025-12-29 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/crm/salesReport") +public class CrmSalesReportController extends BaseController { + + private final ICrmSalesReportService iCrmSalesReportService; + + /** + * 查询销售报表汇总数据 + */ + @GetMapping("/summary") + public R getSalesSummary(CrmSalesReportBo bo) { + CrmSalesReportVo.SalesSummary summary = iCrmSalesReportService.querySalesSummary(bo); + return R.ok(summary); + } + + /** + * 分页查询销售报表订单明细 + */ + @GetMapping("/orderDetails") + public TableDataInfo getOrderDetails(CrmSalesReportBo bo, PageQuery pageQuery) { + return iCrmSalesReportService.queryOrderDetailPageList(bo, pageQuery); + } + + /** + * 查询完整销售报表数据 + */ + @GetMapping("/fullReport") + public R getFullSalesReport(CrmSalesReportBo bo, PageQuery pageQuery) { + CrmSalesReportVo reportVo = iCrmSalesReportService.queryFullSalesReport(bo, pageQuery); + return R.ok(reportVo); + } + + /** + * 查询销售员统计数据 + */ + @GetMapping("/salesmanStats") + public R> getSalesmanStats(CrmSalesReportBo bo) { + List stats = iCrmSalesReportService.querySalesmanStats(bo); + return R.ok(stats); + } + + /** + * 查询客户等级统计数据 + */ + @GetMapping("/customerLevelStats") + public R> getCustomerLevelStats(CrmSalesReportBo bo) { + List stats = iCrmSalesReportService.queryCustomerLevelStats(bo); + return R.ok(stats); + } + + /** + * 查询行业统计数据 + */ + @GetMapping("/industryStats") + public R> getIndustryStats(CrmSalesReportBo bo) { + List stats = iCrmSalesReportService.queryIndustryStats(bo); + return R.ok(stats); + } + + /** + * 导出销售报表订单明细 + */ + @Log(title = "销售报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportOrderDetails") + public void exportOrderDetails(CrmSalesReportBo bo, HttpServletResponse response) { + List list = iCrmSalesReportService.queryOrderDetailList(bo); + ExcelUtil.exportExcel(list, "销售报表订单明细", CrmSalesReportVo.OrderDetail.class, response); + } + + /** + * 导出销售员统计数据 + */ + @Log(title = "销售报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportSalesmanStats") + public void exportSalesmanStats(CrmSalesReportBo bo, HttpServletResponse response) { + List list = iCrmSalesReportService.querySalesmanStats(bo); + ExcelUtil.exportExcel(list, "销售员统计", CrmSalesReportVo.SalesmanStat.class, response); + } + + /** + * 导出客户等级统计数据 + */ + @Log(title = "销售报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportCustomerLevelStats") + public void exportCustomerLevelStats(CrmSalesReportBo bo, HttpServletResponse response) { + List list = iCrmSalesReportService.queryCustomerLevelStats(bo); + ExcelUtil.exportExcel(list, "客户等级统计", CrmSalesReportVo.CustomerLevelStat.class, response); + } + + /** + * 导出行业统计数据 + */ + @Log(title = "销售报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportIndustryStats") + public void exportIndustryStats(CrmSalesReportBo bo, HttpServletResponse response) { + List list = iCrmSalesReportService.queryIndustryStats(bo); + ExcelUtil.exportExcel(list, "行业统计", CrmSalesReportVo.IndustryStat.class, response); + } +} \ No newline at end of file diff --git a/klp-crm/src/main/java/com/klp/crm/domain/bo/CrmSalesReportBo.java b/klp-crm/src/main/java/com/klp/crm/domain/bo/CrmSalesReportBo.java new file mode 100644 index 00000000..87e8e1fd --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/domain/bo/CrmSalesReportBo.java @@ -0,0 +1,108 @@ +package com.klp.crm.domain.bo; + +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.klp.common.core.validate.AddGroup; +import com.klp.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.validation.constraints.*; + +/** + * 销售报表查询业务对象 + * + * @author klp + * @date 2025-12-29 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class CrmSalesReportBo { + + /** + * 开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date startTime; + + /** + * 结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date endTime; + + /** + * 销售员列表 + */ + private List salesmanList; + + /** + * 客户ID列表 + */ + private List customerIdList; + + /** + * 客户等级列表 + */ + private List customerLevelList; + + /** + * 行业列表 + */ + private List industryList; + + /** + * 订单状态列表 + */ + private List orderStatusList; + + /** + * 财务状态列表 + */ + private List financeStatusList; + + /** + * 订单类型列表 + */ + private List orderTypeList; + + /** + * 最小订单金额 + */ + private java.math.BigDecimal minOrderAmount; + + /** + * 最大订单金额 + */ + private java.math.BigDecimal maxOrderAmount; + + /** + * 是否包含异议订单 + */ + private Boolean includeObjectionOrders; + + /** + * 是否只查询有未结款的订单 + */ + private Boolean onlyUnpaidOrders; + + /** + * 公司名称关键字 + */ + private String companyNameKeyword; + + /** + * 订单编号关键字 + */ + private String orderCodeKeyword; + + /** + * 排序字段 + */ + private String orderBy; + + /** + * 排序方向 ASC/DESC + */ + private String sortDirection; +} \ No newline at end of file diff --git a/klp-crm/src/main/java/com/klp/crm/domain/vo/CrmSalesReportVo.java b/klp-crm/src/main/java/com/klp/crm/domain/vo/CrmSalesReportVo.java new file mode 100644 index 00000000..a4509d19 --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/domain/vo/CrmSalesReportVo.java @@ -0,0 +1,287 @@ +package com.klp.crm.domain.vo; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +/** + * 销售报表视图对象 + * + * @author klp + * @date 2025-12-29 + */ +@Data +@ExcelIgnoreUnannotated +public class CrmSalesReportVo { + + private static final long serialVersionUID = 1L; + + /** + * 销售汇总信息 + */ + private SalesSummary salesSummary; + + /** + * 订单明细列表 + */ + private List orderDetails; + + /** + * 销售汇总内部类 + */ + @Data + public static class SalesSummary { + + /** + * 总订单数 + */ + @ExcelProperty(value = "总订单数") + private Integer totalOrderCount; + + /** + * 总销售金额 + */ + @ExcelProperty(value = "总销售金额") + private BigDecimal totalSalesAmount; + + /** + * 已完成订单数 + */ + @ExcelProperty(value = "已完成订单数") + private Integer completedOrderCount; + + /** + * 已完成销售金额 + */ + @ExcelProperty(value = "已完成销售金额") + private BigDecimal completedSalesAmount; + + /** + * 未结款总金额 + */ + @ExcelProperty(value = "未结款总金额") + private BigDecimal totalUnpaidAmount; + + /** + * 平均订单金额 + */ + @ExcelProperty(value = "平均订单金额") + private BigDecimal avgOrderAmount; + + /** + * 销售员统计 + */ + private List salesmanStats; + + /** + * 客户等级统计 + */ + private List customerLevelStats; + + /** + * 行业统计 + */ + private List industryStats; + } + + /** + * 订单明细内部类 + */ + @Data + public static class OrderDetail { + + /** + * 订单ID + */ + private String orderId; + + /** + * 订单编号 + */ + @ExcelProperty(value = "订单编号") + private String orderCode; + + /** + * 客户编码 + */ + @ExcelProperty(value = "客户编码") + private String customerCode; + + /** + * 公司名称 + */ + @ExcelProperty(value = "公司名称") + private String companyName; + + /** + * 联系人 + */ + @ExcelProperty(value = "联系人") + private String contactPerson; + + /** + * 客户等级 + */ + @ExcelProperty(value = "客户等级") + private String customerLevel; + + /** + * 所属行业 + */ + @ExcelProperty(value = "所属行业") + private String industry; + + /** + * 订单金额 + */ + @ExcelProperty(value = "订单金额") + private BigDecimal orderAmount; + + /** + * 销售员 + */ + @ExcelProperty(value = "销售员") + private String salesman; + + /** + * 交货日期 + */ + @ExcelProperty(value = "交货日期") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date deliveryDate; + + /** + * 订单状态 + */ + @ExcelProperty(value = "订单状态") + private Long orderStatus; + + /** + * 财务状态 + */ + @ExcelProperty(value = "财务状态") + private Long financeStatus; + + /** + * 未结款金额 + */ + @ExcelProperty(value = "未结款金额") + private BigDecimal unpaidAmount; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 产品明细数量 + */ + @ExcelProperty(value = "产品明细数量") + private Integer itemCount; + + /** + * 异议数量 + */ + @ExcelProperty(value = "异议数量") + private Integer objectionCount; + } + + /** + * 销售员统计内部类 + */ + @Data + public static class SalesmanStat { + + /** + * 销售员 + */ + @ExcelProperty(value = "销售员") + private String salesman; + + /** + * 订单数量 + */ + @ExcelProperty(value = "订单数量") + private Integer orderCount; + + /** + * 销售金额 + */ + @ExcelProperty(value = "销售金额") + private BigDecimal salesAmount; + + /** + * 占比 + */ + @ExcelProperty(value = "占比(%)") + private BigDecimal percentage; + } + + /** + * 客户等级统计内部类 + */ + @Data + public static class CustomerLevelStat { + + /** + * 客户等级 + */ + @ExcelProperty(value = "客户等级") + private String customerLevel; + + /** + * 客户数量 + */ + @ExcelProperty(value = "客户数量") + private Integer customerCount; + + /** + * 订单数量 + */ + @ExcelProperty(value = "订单数量") + private Integer orderCount; + + /** + * 销售金额 + */ + @ExcelProperty(value = "销售金额") + private BigDecimal salesAmount; + } + + /** + * 行业统计内部类 + */ + @Data + public static class IndustryStat { + + /** + * 行业 + */ + @ExcelProperty(value = "行业") + private String industry; + + /** + * 客户数量 + */ + @ExcelProperty(value = "客户数量") + private Integer customerCount; + + /** + * 订单数量 + */ + @ExcelProperty(value = "订单数量") + private Integer orderCount; + + /** + * 销售金额 + */ + @ExcelProperty(value = "销售金额") + private BigDecimal salesAmount; + } +} \ No newline at end of file diff --git a/klp-crm/src/main/java/com/klp/crm/mapper/CrmSalesReportMapper.java b/klp-crm/src/main/java/com/klp/crm/mapper/CrmSalesReportMapper.java new file mode 100644 index 00000000..580276c3 --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/mapper/CrmSalesReportMapper.java @@ -0,0 +1,57 @@ +package com.klp.crm.mapper; + +import com.klp.crm.domain.vo.CrmSalesReportVo; +import com.klp.crm.domain.bo.CrmSalesReportBo; +import com.klp.common.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 销售报表Mapper接口 + * + * @author klp + * @date 2025-12-29 + */ +public interface CrmSalesReportMapper { + + /** + * 查询销售汇总统计数据 + * + * @param bo 查询条件 + * @return 销售汇总统计 + */ + CrmSalesReportVo.SalesSummary selectSalesSummary(@Param("bo") CrmSalesReportBo bo); + + /** + * 查询订单明细列表 + * + * @param bo 查询条件 + * @return 订单明细列表 + */ + List selectOrderDetailList(@Param("bo") CrmSalesReportBo bo); + + /** + * 查询销售员统计数据 + * + * @param bo 查询条件 + * @return 销售员统计列表 + */ + List selectSalesmanStats(@Param("bo") CrmSalesReportBo bo); + + /** + * 查询客户等级统计数据 + * + * @param bo 查询条件 + * @return 客户等级统计列表 + */ + List selectCustomerLevelStats(@Param("bo") CrmSalesReportBo bo); + + /** + * 查询行业统计数据 + * + * @param bo 查询条件 + * @return 行业统计列表 + */ + List selectIndustryStats(@Param("bo") CrmSalesReportBo bo); +} \ No newline at end of file diff --git a/klp-crm/src/main/java/com/klp/crm/service/ICrmSalesReportService.java b/klp-crm/src/main/java/com/klp/crm/service/ICrmSalesReportService.java new file mode 100644 index 00000000..6be75d08 --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/service/ICrmSalesReportService.java @@ -0,0 +1,75 @@ +package com.klp.crm.service; + +import com.klp.crm.domain.vo.CrmSalesReportVo; +import com.klp.crm.domain.bo.CrmSalesReportBo; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; + +import java.util.List; + +/** + * 销售报表Service接口 + * + * @author klp + * @date 2025-12-29 + */ +public interface ICrmSalesReportService { + + /** + * 查询销售报表汇总数据 + * + * @param bo 查询条件 + * @return 销售报表汇总数据 + */ + CrmSalesReportVo.SalesSummary querySalesSummary(CrmSalesReportBo bo); + + /** + * 分页查询销售报表订单明细 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 订单明细分页数据 + */ + TableDataInfo queryOrderDetailPageList(CrmSalesReportBo bo, PageQuery pageQuery); + + /** + * 查询销售报表订单明细列表 + * + * @param bo 查询条件 + * @return 订单明细列表 + */ + List queryOrderDetailList(CrmSalesReportBo bo); + + /** + * 查询完整销售报表数据 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 完整销售报表数据 + */ + CrmSalesReportVo queryFullSalesReport(CrmSalesReportBo bo, PageQuery pageQuery); + + /** + * 查询销售员统计数据 + * + * @param bo 查询条件 + * @return 销售员统计列表 + */ + List querySalesmanStats(CrmSalesReportBo bo); + + /** + * 查询客户等级统计数据 + * + * @param bo 查询条件 + * @return 客户等级统计列表 + */ + List queryCustomerLevelStats(CrmSalesReportBo bo); + + /** + * 查询行业统计数据 + * + * @param bo 查询条件 + * @return 行业统计列表 + */ + List queryIndustryStats(CrmSalesReportBo bo); +} \ No newline at end of file diff --git a/klp-crm/src/main/java/com/klp/crm/service/impl/CrmSalesReportServiceImpl.java b/klp-crm/src/main/java/com/klp/crm/service/impl/CrmSalesReportServiceImpl.java new file mode 100644 index 00000000..4278082d --- /dev/null +++ b/klp-crm/src/main/java/com/klp/crm/service/impl/CrmSalesReportServiceImpl.java @@ -0,0 +1,104 @@ +package com.klp.crm.service.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import com.klp.common.core.page.TableDataInfo; +import com.klp.common.core.domain.PageQuery; +import com.klp.common.utils.StringUtils; +import com.klp.crm.domain.vo.CrmSalesReportVo; +import com.klp.crm.domain.bo.CrmSalesReportBo; +import com.klp.crm.mapper.CrmSalesReportMapper; +import com.klp.crm.service.ICrmSalesReportService; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; + +import java.util.List; + +/** + * 销售报表Service业务层处理 + * + * @author klp + * @date 2025-12-29 + */ +@RequiredArgsConstructor +@Service +public class CrmSalesReportServiceImpl implements ICrmSalesReportService { + + private final CrmSalesReportMapper baseMapper; + + /** + * 查询销售报表汇总数据 + */ + @Override + public CrmSalesReportVo.SalesSummary querySalesSummary(CrmSalesReportBo bo) { + CrmSalesReportVo.SalesSummary summary = baseMapper.selectSalesSummary(bo); + if (summary != null) { + // 查询销售员统计 + summary.setSalesmanStats(baseMapper.selectSalesmanStats(bo)); + // 查询客户等级统计 + summary.setCustomerLevelStats(baseMapper.selectCustomerLevelStats(bo)); + // 查询行业统计 + summary.setIndustryStats(baseMapper.selectIndustryStats(bo)); + } + return summary; + } + + /** + * 分页查询销售报表订单明细 + */ + @Override + public TableDataInfo queryOrderDetailPageList(CrmSalesReportBo bo, PageQuery pageQuery) { + PageHelper.startPage(pageQuery.getPageNum(), pageQuery.getPageSize()); + List list = baseMapper.selectOrderDetailList(bo); + return TableDataInfo.build(list); + } + + /** + * 查询销售报表订单明细列表 + */ + @Override + public List queryOrderDetailList(CrmSalesReportBo bo) { + return baseMapper.selectOrderDetailList(bo); + } + + /** + * 查询完整销售报表数据 + */ + @Override + public CrmSalesReportVo queryFullSalesReport(CrmSalesReportBo bo, PageQuery pageQuery) { + CrmSalesReportVo reportVo = new CrmSalesReportVo(); + + // 查询汇总数据 + reportVo.setSalesSummary(querySalesSummary(bo)); + + // 查询订单明细(分页) + TableDataInfo orderDetailPage = queryOrderDetailPageList(bo, pageQuery); + reportVo.setOrderDetails(orderDetailPage.getRows()); + + return reportVo; + } + + /** + * 查询销售员统计数据 + */ + @Override + public List querySalesmanStats(CrmSalesReportBo bo) { + return baseMapper.selectSalesmanStats(bo); + } + + /** + * 查询客户等级统计数据 + */ + @Override + public List queryCustomerLevelStats(CrmSalesReportBo bo) { + return baseMapper.selectCustomerLevelStats(bo); + } + + /** + * 查询行业统计数据 + */ + @Override + public List queryIndustryStats(CrmSalesReportBo bo) { + return baseMapper.selectIndustryStats(bo); + } +} diff --git a/klp-crm/src/main/resources/mapper/klp/CrmSalesReportMapper.xml b/klp-crm/src/main/resources/mapper/klp/CrmSalesReportMapper.xml new file mode 100644 index 00000000..aeeaff3b --- /dev/null +++ b/klp-crm/src/main/resources/mapper/klp/CrmSalesReportMapper.xml @@ -0,0 +1,196 @@ + + + + + + + + o.del_flag = 0 AND c.del_flag = 0 + + AND DATE(o.create_time) >= #{bo.startTime} + + + AND DATE(o.create_time) <= #{bo.endTime} + + + AND o.salesman IN + + #{salesman} + + + + AND o.customer_id IN + + #{customerId} + + + + AND c.customer_level IN + + #{level} + + + + AND c.industry IN + + #{industry} + + + + AND o.order_status IN + + #{status} + + + + AND o.finance_status IN + + #{status} + + + + AND o.order_type IN + + #{type} + + + + AND o.order_amount >= #{bo.minOrderAmount} + + + AND o.order_amount <= #{bo.maxOrderAmount} + + + AND o.unpaid_amount > 0 + + + AND c.company_name LIKE CONCAT('%', #{bo.companyNameKeyword}, '%') + + + AND o.order_code LIKE CONCAT('%', #{bo.orderCodeKeyword}, '%') + + + AND NOT EXISTS ( + SELECT 1 FROM crm_sales_objection obj + WHERE obj.order_id = o.order_id AND obj.del_flag = 0 + ) + + + + + + + + + + + + + + + + + + + + \ No newline at end of file