demand: 月结客户达标统计相关需求

This commit is contained in:
aike 2026-02-12 10:02:15 +08:00
parent 6dd335a11b
commit 1335ae8890
7 changed files with 593 additions and 21 deletions

View File

@ -495,7 +495,19 @@ public class EmisCustomerUserController extends EmisBaseController
setPrivParams(emisWaybill);
List<MonthlyCustomerShipmentStatVO> list = emisCustomerUserService
.selectMonthlyCustomerShipmentStat(emisWaybill);
return getDataTable(list);
TableDataInfo dataTable = getDataTable(list);
// 如果使用了应用层过滤(complianceStatType=3/4/5/6),需要手动设置总数
if (emisWaybill.getParams() != null && emisWaybill.getParams().containsKey("_filteredTotal")) {
Object filteredTotal = emisWaybill.getParams().get("_filteredTotal");
if (filteredTotal instanceof Long) {
dataTable.setTotal((Long) filteredTotal);
} else if (filteredTotal instanceof Number) {
dataTable.setTotal(((Number) filteredTotal).longValue());
}
}
return dataTable;
}
/**

View File

@ -203,5 +203,14 @@ public class MonthlyCustomerShipmentStatVO implements Serializable {
/* 创建日期(现金客户导出使用,对应 create_time) */
private String createTime;
/* 是否达标;1:已达标,2:未达标 */
private String blCompliance;
/* 寄件月份 */
private String sendMonth;
/* 总运费 */
private Integer totalBillFee;
}

View File

@ -13,6 +13,8 @@ package com.xdadan.erp.emis.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xdadan.erp.emis.domain.EmisCustomerUser;
import com.xdadan.erp.emis.domain.EmisWaybill;
import com.xdadan.erp.emis.domain.vo.MonthlyCustomerShipmentStatVO;
import org.apache.ibatis.annotations.Param;
/**
* @ClassName EmisCustomerUserMapper
@ -134,4 +136,12 @@ public interface EmisCustomerUserMapper extends BaseMapper<EmisCustomerUser>
*/
public int clearMoneyUserIdByMoneyUserIds(@Param("moneyUserIds") Long[] moneyUserIds);
/**
* 月结客户达标统计
*
* @param emisWaybill 查询条件
* @return
*/
List<MonthlyCustomerShipmentStatVO> selectMonthlyCustomerShipmentStatIsCompliance(EmisWaybill emisWaybill);
}

View File

@ -70,9 +70,31 @@ public class MonthlyShipmentStatExcelUtil {
MonthlyCustomerShipmentStatVO::getOtherWeightCompareFlag)
);
private static final String[] BASE_HEADERS = {"客户名称", "首次签约日期", "总票数", "总重量", "销售联系人"};
/**
* 默认(月结/现金)出货统计基础列(原有导出保持不变)
* 客户名称、首次签约日期(或创建日期)、总票数、总重量、销售联系人
*/
private static final String[] BASE_HEADERS = {
"客户名称", "首次签约日期", "总票数", "总重量", "销售联系人"
};
private static final int[] BASE_WIDTHS = {28, 18, 12, 16, 24};
/**
* 月结客户达标统计基础列(仅 statType=5 使用)
*
* 列顺序要求:
* 1)寄件月份 字段放在“首次签约日期”后边;
* 2)总运费 字段放在“总重量”后边;
* 3)是否达标 字段放在“寄件月份”后边;
*
* 最终基础表头顺序:
* 客户名称、首次签约日期、寄件月份、是否达标、总票数、总重量、总运费、销售联系人
*/
private static final String[] COMPLIANCE_HEADERS = {
"客户名称", "首次签约日期", "寄件月份", "是否达标", "总票数", "总重量", "总运费", "销售联系人"
};
private static final int[] COMPLIANCE_BASE_WIDTHS = {28, 18, 16, 12, 12, 16, 16, 24};
private MonthlyShipmentStatExcelUtil() {
}
@ -94,21 +116,36 @@ public class MonthlyShipmentStatExcelUtil {
private static void exportInternal(HttpServletResponse response, List<MonthlyCustomerShipmentStatVO> dataList,
int statType, boolean moneyCustomer) {
boolean transportLayout = statType == 1 || statType == 3;
boolean transportLayout = statType == 1 || statType == 3 || statType == 5;
boolean compareMode = statType == 3 || statType == 4;
// 是否为月结达标统计(仅 statType=5)
boolean complianceLayout = !moneyCustomer && statType == 5;
List<MonthlyCustomerShipmentStatVO> safeList = dataList == null ? new ArrayList<>() : dataList;
// 为保证 Excel 导出稳定排序:先按总重量降序,再按总票数降序,最后按客户名称升序
sortForExport(safeList);
if(!complianceLayout){
sortForExport(safeList);
}
List<MonthlyCustomerShipmentStatVO> exportList = new ArrayList<>(safeList);
exportList.add(buildTotalRow(safeList));
String sheetName = moneyCustomer ? "现金客户出货统计" : "月结客户出货统计";
String[] headers = moneyCustomer
? new String[]{"客户名称", "创建日期", "总票数", "总重量", "销售联系人"}
: BASE_HEADERS;
String[] headers;
int[] baseWidths;
if (moneyCustomer) {
headers = new String[]{"客户名称", "创建日期", "总票数", "总重量", "销售联系人"};
baseWidths = BASE_WIDTHS;
} else if (complianceLayout) {
headers = COMPLIANCE_HEADERS;
baseWidths = COMPLIANCE_BASE_WIDTHS;
} else {
headers = BASE_HEADERS;
baseWidths = BASE_WIDTHS;
}
try {
SXSSFWorkbook workbook = new SXSSFWorkbook(200);
@ -131,7 +168,7 @@ public class MonthlyShipmentStatExcelUtil {
// 样式缓存:避免在对比模式下创建过多样式导致超过Excel样式限制
Map<String, CellStyle> styleCache = new HashMap<>();
createHeader(sheet, headers, transportLayout ? TRANSPORT_GROUPS : COUNTRY_GROUPS, headerStyle);
createHeader(sheet, headers, baseWidths, transportLayout ? TRANSPORT_GROUPS : COUNTRY_GROUPS, headerStyle);
int dataStartRow = 2;
for (int i = 0; i < exportList.size(); i++) {
@ -143,17 +180,64 @@ public class MonthlyShipmentStatExcelUtil {
int col = 0;
// 客户名称:月结用 customerName;现金用 company
String customerName = moneyCustomer ? vo.getCompany() : vo.getCustomerName();
String dateValue = moneyCustomer ? vo.getCreateTime() : vo.getFirstSigningDay();
col = writeTextCell(row, col, customerName, totalRow ? totalTextStyle : textStyle);
col = writeTextCell(row, col, dateValue, totalRow ? totalTextStyle : textStyle);
col = writeIntegerCell(row, col, vo.getTotalTickets(), vo.getTotalTicketsCompareFlag(),
totalRow ? totalNumberStyle : numberStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeDecimalCell(row, col, vo.getTotalWeight(), vo.getTotalWeightCompareFlag(),
totalRow ? totalDecimalStyle : decimalStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeTextCell(row, col, vo.getSalesmen(), totalRow ? totalTextStyle : textStyle);
if (moneyCustomer) {
// 现金客户出货统计:保持原有列顺序不变
String dateValue = vo.getCreateTime();
col = writeTextCell(row, col, dateValue, totalRow ? totalTextStyle : textStyle);
col = writeIntegerCell(row, col, vo.getTotalTickets(), vo.getTotalTicketsCompareFlag(),
totalRow ? totalNumberStyle : numberStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeDecimalCell(row, col, vo.getTotalWeight(), vo.getTotalWeightCompareFlag(),
totalRow ? totalDecimalStyle : decimalStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeTextCell(row, col, vo.getSalesmen(), totalRow ? totalTextStyle : textStyle);
} else if (complianceLayout) {
// 月结客户达标统计:使用新增三列
String dateValue = vo.getFirstSigningDay();
// 首次签约日期
col = writeTextCell(row, col, dateValue, totalRow ? totalTextStyle : textStyle);
// 寄件月份
col = writeTextCell(row, col, vo.getSendMonth(), totalRow ? totalTextStyle : textStyle);
// 是否达标(1=已达标,2=未达标)
String complianceText;
if (vo.getBlCompliance() == null || vo.getBlCompliance().isEmpty()) {
complianceText = "";
} else if ("1".equals(vo.getBlCompliance())) {
complianceText = "已达标";
} else if ("2".equals(vo.getBlCompliance())) {
complianceText = "未达标";
} else {
complianceText = vo.getBlCompliance();
}
col = writeTextCell(row, col, complianceText, totalRow ? totalTextStyle : textStyle);
// 总票数、总重量
col = writeIntegerCell(row, col, vo.getTotalTickets(), vo.getTotalTicketsCompareFlag(),
totalRow ? totalNumberStyle : numberStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeDecimalCell(row, col, vo.getTotalWeight(), vo.getTotalWeightCompareFlag(),
totalRow ? totalDecimalStyle : decimalStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
// 总运费(达标统计专用)
col = writeIntegerCell(row, col, vo.getTotalBillFee(), null,
totalRow ? totalNumberStyle : numberStyle, false, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
// 销售联系人
col = writeTextCell(row, col, vo.getSalesmen(), totalRow ? totalTextStyle : textStyle);
} else {
// 普通月结出货统计:保持原有 5 列
String dateValue = vo.getFirstSigningDay();
col = writeTextCell(row, col, dateValue, totalRow ? totalTextStyle : textStyle);
col = writeIntegerCell(row, col, vo.getTotalTickets(), vo.getTotalTicketsCompareFlag(),
totalRow ? totalNumberStyle : numberStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeDecimalCell(row, col, vo.getTotalWeight(), vo.getTotalWeightCompareFlag(),
totalRow ? totalDecimalStyle : decimalStyle, compareMode && !totalRow, workbook,
redArrowFont, greenArrowFont, blueArrowFont, styleCache);
col = writeTextCell(row, col, vo.getSalesmen(), totalRow ? totalTextStyle : textStyle);
}
List<MetricGroup> groups = transportLayout ? TRANSPORT_GROUPS : COUNTRY_GROUPS;
for (MetricGroup group : groups) {
@ -174,7 +258,8 @@ public class MonthlyShipmentStatExcelUtil {
}
}
private static void createHeader(Sheet sheet, String[] baseHeaders, List<MetricGroup> groups, CellStyle headerStyle) {
private static void createHeader(Sheet sheet, String[] baseHeaders, int[] baseWidths,
List<MetricGroup> groups, CellStyle headerStyle) {
Row topRow = sheet.createRow(0);
Row subRow = sheet.createRow(1);
topRow.setHeightInPoints(22);
@ -186,7 +271,7 @@ public class MonthlyShipmentStatExcelUtil {
cell.setCellValue(baseHeaders[i]);
cell.setCellStyle(headerStyle);
sheet.addMergedRegion(new CellRangeAddress(0, 1, col, col));
sheet.setColumnWidth(col, BASE_WIDTHS[i] * 256);
sheet.setColumnWidth(col, baseWidths[i] * 256);
col++;
}
@ -400,9 +485,12 @@ public class MonthlyShipmentStatExcelUtil {
total.setCustomerName("合计");
total.setFirstSigningDay("");
total.setSalesmen("");
total.setSendMonth("");
total.setBlCompliance("");
total.setTotalTickets(sumInt(source, MonthlyCustomerShipmentStatVO::getTotalTickets));
total.setTotalWeight(sumDecimal(source, MonthlyCustomerShipmentStatVO::getTotalWeight));
total.setTotalBillFee(sumInt(source, MonthlyCustomerShipmentStatVO::getTotalBillFee));
total.setExpressTickets(sumInt(source, MonthlyCustomerShipmentStatVO::getExpressTickets));
total.setExpressWeight(sumDecimal(source, MonthlyCustomerShipmentStatVO::getExpressWeight));

View File

@ -11,7 +11,15 @@
package com.xdadan.erp.emis.service.impl;
import java.util.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.stream.Collectors;
import com.github.pagehelper.PageHelper;
import com.xdadan.erp.common.core.page.PageDomain;
import com.xdadan.erp.common.core.page.TableSupport;
import com.xdadan.erp.common.utils.DateUtils;
import com.xdadan.erp.common.utils.SecurityUtils;
import com.xdadan.erp.emis.domain.exception.EmisBizError;
import com.xdadan.erp.emis.domain.enumtype.EmisBizErrorType;
@ -175,14 +183,119 @@ public class EmisCustomerUserServiceImpl extends EmisBaseService implements IEmi
} else if ("4".equals(emisWaybill.getParams().get("statType"))) {
// 按目的国家统计,增加与上一个时间段的票数、重量对比标记(根据客户类型自动选择 key)
result = buildShipmentStatWithCompare(emisWaybill, false);
} else if ("5".equals(emisWaybill.getParams().get("statType"))) {
// 月结客户达标统计
// 校验:当complianceStatType=3/4/5/6时,寄件时间段需大于等于3个月
checkDateForComplianceStat(emisWaybill);
if(Objects.isNull(emisWaybill.getParams().get("beginSendDate"))){
emisWaybill.getParams().put("beginSendDate","2024-01-01 00:00:00");
}
if(Objects.isNull(emisWaybill.getParams().get("endSendDate"))){
emisWaybill.getParams().put("endSendDate", DateUtils.getDate("yyyy-MM-dd HH:mm:ss"));
}
Object complianceStatTypeObj = emisWaybill.getParams().get("complianceStatType");
String complianceStatType = complianceStatTypeObj != null ? String.valueOf(complianceStatTypeObj) : null;
// 统一使用应用层过滤:先清除 PageHelper 分页,查询所有数据
PageHelper.clearPage();
List<MonthlyCustomerShipmentStatVO> allResults = emisCustomerUserMapper.selectMonthlyCustomerShipmentStatIsCompliance(emisWaybill);
// 先根据 totalTickets / totalBillFee 在代码中计算 blCompliance(1=达标,2=未达标)
if (allResults != null) {
for (MonthlyCustomerShipmentStatVO vo : allResults) {
if (vo == null) {
continue;
}
Integer tickets = vo.getTotalTickets();
Integer fee = vo.getTotalBillFee();
int safeTickets = tickets == null ? 0 : tickets;
int safeFee = fee == null ? 0 : fee;
// 规则:票数>=5 或 总运费>=3000 视为达标
vo.setBlCompliance((safeTickets >= 5 || safeFee >= 3000) ? "1" : "2");
}
}
// 按 complianceStatType 在应用层过滤(1~6 全走代码逻辑)
if (complianceStatType != null && !complianceStatType.trim().isEmpty()) {
result = filterComplianceStat(allResults, complianceStatType.trim());
} else {
result = allResults;
}
// 保存过滤后的总数到 params 中,供 Controller 使用
if (emisWaybill.getParams() == null) {
emisWaybill.setParams(new HashMap<>());
}
emisWaybill.getParams().put("_filteredTotal", result == null ? 0L : (long) result.size());
// 手动分页
if (result == null) {
result = Collections.emptyList();
}
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (pageNum != null && pageSize != null && pageNum > 0 && pageSize > 0) {
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, result.size());
if (start < result.size()) {
result = result.subList(start, end);
} else {
result = Collections.emptyList();
}
}
} else {
result = Collections.emptyList();
}
sortShipmentStat(result);
if(!"5".equals(emisWaybill.getParams().get("statType"))){
sortShipmentStat(result);
}
return result;
}
private void checkDateForComplianceStat(EmisWaybill emisWaybill) throws EmisBizError {
Object complianceStatTypeObj = emisWaybill.getParams().get("complianceStatType");
if (complianceStatTypeObj != null) {
String complianceStatType = String.valueOf(complianceStatTypeObj);
if ("3".equals(complianceStatType) || "4".equals(complianceStatType)
|| "5".equals(complianceStatType) || "6".equals(complianceStatType)) {
// 验证时间段必须大于等于3个月
Object beginSendObj = emisWaybill.getParams().get("beginSendDate");
Object endSendObj = emisWaybill.getParams().get("endSendDate");
if (beginSendObj == null || endSendObj == null) {
throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "请选择寄件日期");
}
try {
String beginSendDate = String.valueOf(beginSendObj).trim();
String endSendDate = String.valueOf(endSendObj).trim();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDate beginDate = LocalDate.parse(beginSendDate, formatter);
LocalDate endDate = LocalDate.parse(endSendDate, formatter);
long monthsBetween = ChronoUnit.MONTHS.between(
beginDate.withDayOfMonth(1),
endDate.withDayOfMonth(1)
);
if (monthsBetween < 3) {
throw new EmisBizError(EmisBizErrorType.PARAM_ERROR,
"当达标统计类型为" + getComplianceStatTypeName(complianceStatType) + "时,寄件时间段需大于等于3个月");
}
} catch (Exception e) {
if (e instanceof EmisBizError) {
throw e;
}
throw new EmisBizError(EmisBizErrorType.PARAM_ERROR, "日期格式错误,请使用yyyy-MM-dd HH:mm:ss格式");
}
}
}
}
/**
* 查询现金客户出货统计
*
@ -429,6 +542,184 @@ public class EmisCustomerUserServiceImpl extends EmisBaseService implements IEmi
return v == null ? 0 : v;
}
/**
* 获取达标统计类型名称
*/
private String getComplianceStatTypeName(String complianceStatType) {
switch (complianceStatType) {
case "3": return "连续3个月未出货";
case "4": return "累计3个月未出货";
case "5": return "连续3个月出货未达标";
case "6": return "累计3个月出货未达标";
default: return complianceStatType;
}
}
/**
* 应用层过滤:根据 complianceStatType 过滤统计结果
* 优化:将复杂的 SQL EXISTS 子查询移到应用层处理,提升查询性能
*
* @param allResults 所有查询结果(已按客户+月份分组)
* @param complianceStatType 达标统计类型:3-连续3个月未出货,4-累计3个月未出货,5-连续3个月未达标,6-累计3个月未达标
* @return 过滤后的结果列表
*/
private List<MonthlyCustomerShipmentStatVO> filterComplianceStat(
List<MonthlyCustomerShipmentStatVO> allResults, String complianceStatType) {
if (allResults == null || allResults.isEmpty()) {
return Collections.emptyList();
}
// 1/2:简单按 blCompliance 过滤(1=已达标,2=未达标)
if ("1".equals(complianceStatType)) {
return allResults.stream()
.filter(m -> "1".equals(m.getBlCompliance()))
.collect(Collectors.toList());
}
if ("2".equals(complianceStatType)) {
return allResults.stream()
.filter(m -> "2".equals(m.getBlCompliance()))
.collect(Collectors.toList());
}
// 按客户分组,便于后续判断连续/累计
Map<String, List<MonthlyCustomerShipmentStatVO>> customerMap = allResults.stream()
.collect(Collectors.groupingBy(
item -> item.getCustNo() != null ? item.getCustNo() : "",
LinkedHashMap::new,
Collectors.toList()
));
List<MonthlyCustomerShipmentStatVO> filteredResults = new ArrayList<>();
for (Map.Entry<String, List<MonthlyCustomerShipmentStatVO>> entry : customerMap.entrySet()) {
List<MonthlyCustomerShipmentStatVO> customerMonths = entry.getValue();
// 按月份排序(升序)
customerMonths.sort(Comparator.comparing(MonthlyCustomerShipmentStatVO::getSendMonth));
switch (complianceStatType) {
case "3":
// 连续3个月以上出货为0
filteredResults.addAll(filterConsecutiveZeroTickets(customerMonths));
break;
case "4":
// 累计超过3个月出货为0
filteredResults.addAll(filterAccumulatedZeroTickets(customerMonths, 3));
break;
case "5":
// 连续3个月以上出货未达标
filteredResults.addAll(filterConsecutiveNonCompliance(customerMonths));
break;
case "6":
// 累计3个月以上出货未达标
filteredResults.addAll(filterAccumulatedNonCompliance(customerMonths, 3));
break;
default:
filteredResults.addAll(customerMonths);
break;
}
}
return filteredResults;
}
/**
* 过滤连续3个月以上出货为0的月份
*/
private List<MonthlyCustomerShipmentStatVO> filterConsecutiveZeroTickets(
List<MonthlyCustomerShipmentStatVO> customerMonths) {
List<MonthlyCustomerShipmentStatVO> result = new ArrayList<>();
for (int i = 0; i < customerMonths.size(); i++) {
MonthlyCustomerShipmentStatVO current = customerMonths.get(i);
if (current.getTotalTickets() == null || current.getTotalTickets() == 0) {
// 检查前两个月是否也为0
boolean prev1Zero = (i >= 1 && (customerMonths.get(i - 1).getTotalTickets() == null
|| customerMonths.get(i - 1).getTotalTickets() == 0));
boolean prev2Zero = (i >= 2 && (customerMonths.get(i - 2).getTotalTickets() == null
|| customerMonths.get(i - 2).getTotalTickets() == 0));
// 检查后两个月是否也为0
boolean next1Zero = (i < customerMonths.size() - 1
&& (customerMonths.get(i + 1).getTotalTickets() == null
|| customerMonths.get(i + 1).getTotalTickets() == 0));
boolean next2Zero = (i < customerMonths.size() - 2
&& (customerMonths.get(i + 2).getTotalTickets() == null
|| customerMonths.get(i + 2).getTotalTickets() == 0));
// 连续口径:某月在连续>=3的窗口内即可返回(包含窗口的前两个月或后两个月)
if ((prev1Zero && prev2Zero) || (prev1Zero && next1Zero) || (next1Zero && next2Zero)) {
result.add(current);
}
}
}
return result;
}
/**
* 过滤累计超过指定月数出货为0的月份
*/
private List<MonthlyCustomerShipmentStatVO> filterAccumulatedZeroTickets(
List<MonthlyCustomerShipmentStatVO> customerMonths, int minMonths) {
// 统计该客户出货为0的月份总数
long zeroTicketMonths = customerMonths.stream()
.filter(m -> m.getTotalTickets() == null || m.getTotalTickets() == 0)
.count();
if (zeroTicketMonths >= minMonths) {
// 返回所有出货为0的月份
return customerMonths.stream()
.filter(m -> m.getTotalTickets() == null || m.getTotalTickets() == 0)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
/**
* 过滤连续3个月以上出货未达标的月份
*/
private List<MonthlyCustomerShipmentStatVO> filterConsecutiveNonCompliance(
List<MonthlyCustomerShipmentStatVO> customerMonths) {
List<MonthlyCustomerShipmentStatVO> result = new ArrayList<>();
for (int i = 0; i < customerMonths.size(); i++) {
MonthlyCustomerShipmentStatVO current = customerMonths.get(i);
if ("2".equals(current.getBlCompliance())) {
// 检查前两个月是否也未达标
boolean prev1NonCompliance = (i >= 1 && "2".equals(customerMonths.get(i - 1).getBlCompliance()));
boolean prev2NonCompliance = (i >= 2 && "2".equals(customerMonths.get(i - 2).getBlCompliance()));
// 检查后两个月是否也未达标
boolean next1NonCompliance = (i < customerMonths.size() - 1
&& "2".equals(customerMonths.get(i + 1).getBlCompliance()));
boolean next2NonCompliance = (i < customerMonths.size() - 2
&& "2".equals(customerMonths.get(i + 2).getBlCompliance()));
// 连续口径:某月在连续>=3的窗口内即可返回
if ((prev1NonCompliance && prev2NonCompliance)
|| (prev1NonCompliance && next1NonCompliance)
|| (next1NonCompliance && next2NonCompliance)) {
result.add(current);
}
}
}
return result;
}
/**
* 过滤累计超过指定月数出货未达标的月份
*/
private List<MonthlyCustomerShipmentStatVO> filterAccumulatedNonCompliance(
List<MonthlyCustomerShipmentStatVO> customerMonths, int minMonths) {
// 统计该客户未达标的月份总数
long nonComplianceMonths = customerMonths.stream()
.filter(m -> "2".equals(m.getBlCompliance()))
.count();
if (nonComplianceMonths >= minMonths) {
// 返回所有未达标的月份
return customerMonths.stream()
.filter(m -> "2".equals(m.getBlCompliance()))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
/**
* 查询月结客户出货明细
*

View File

@ -674,4 +674,160 @@
AND del_flag = '0'
</update>
<!-- 月结客户达标统计 -->
<select id="selectMonthlyCustomerShipmentStatIsCompliance" parameterType="EmisWaybill" resultType="com.xdadan.erp.emis.domain.vo.MonthlyCustomerShipmentStatVO">
SELECT
ms.id,
ms.customerCode,
ms.customerName,
ms.custNo,
ms.salesmen,
ms.firstSigningDay,
ms.sendMonth,
ms.totalTickets,
ms.totalWeight,
ms.totalBillFee,
ms.expressTickets,
ms.expressWeight,
ms.airTickets,
ms.airWeight,
ms.landTickets,
ms.landWeight,
ms.seaTickets,
ms.seaWeight,
ms.importTickets,
ms.importWeight,
ms.otherTickets,
ms.otherWeight
FROM (
/*
先生成查询区间内的月份维表(m),再做 客户×月份 的骨架,
最后 LEFT JOIN 运单并按“客户+月份”聚合,才能产出 totalTickets=0 且 sendMonth 有值的行。
用数字表(0..36)模拟月份递增。
*/
SELECT
cu.id AS id,
cu.customer_code AS customerCode,
cu.customer_name AS customerName,
cu.monthly_pay_code AS custNo,
cu.salesmen AS salesmen,
cu.first_signing_day AS firstSigningDay,
m.sendMonth AS sendMonth,
COUNT(DISTINCT a.bill_code) AS totalTickets,
IFNULL(SUM(a.bill_weight), 0) AS totalWeight,
CAST(IFNULL(SUM(IFNULL(a.freight, 0)), 0) AS UNSIGNED) AS totalBillFee,
SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '1' THEN 1 ELSE 0 END) AS expressTickets,
IFNULL(SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '1' THEN a.bill_weight ELSE 0 END), 0) AS expressWeight,
SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '2' THEN 1 ELSE 0 END) AS airTickets,
IFNULL(SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '2' THEN a.bill_weight ELSE 0 END), 0) AS airWeight,
SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type in ('3','5') THEN 1 ELSE 0 END) AS landTickets,
IFNULL(SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type in ('3','5') THEN a.bill_weight ELSE 0 END), 0) AS landWeight,
SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '4' THEN 1 ELSE 0 END) AS seaTickets,
IFNULL(SUM(CASE WHEN a.send_country = '0086' AND tl.trans_type = '4' THEN a.bill_weight ELSE 0 END), 0) AS seaWeight,
SUM(CASE WHEN a.receive_country = '0086' THEN 1 ELSE 0 END) AS importTickets,
IFNULL(SUM(CASE WHEN a.receive_country = '0086' THEN a.bill_weight ELSE 0 END), 0) AS importWeight,
SUM(CASE WHEN a.receive_country != '0086' AND NOT (a.send_country = '0086' AND tl.trans_type IN ('1','2','3','4','5')) THEN 1 ELSE 0 END) AS otherTickets,
IFNULL(SUM(CASE WHEN a.receive_country != '0086' AND NOT (a.send_country = '0086' AND tl.trans_type IN ('1','2','3','4','5')) THEN a.bill_weight ELSE 0 END), 0) AS otherWeight
FROM emis_customer_user cu
JOIN (
SELECT
DATE_FORMAT(DATE_ADD(STR_TO_DATE(#{params.beginSendDate}, '%Y-%m-%d %H:%i:%s'), INTERVAL n.n MONTH), '%Y-%m') AS sendMonth,
DATE_FORMAT(DATE_ADD(STR_TO_DATE(#{params.beginSendDate}, '%Y-%m-%d %H:%i:%s'), INTERVAL n.n MONTH), '%Y-%m-01') AS monthStart
FROM (
SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL
SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL
SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL
SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL
SELECT 24 UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 UNION ALL
SELECT 30 UNION ALL SELECT 31 UNION ALL SELECT 32 UNION ALL SELECT 33 UNION ALL SELECT 34 UNION ALL SELECT 35 UNION ALL
SELECT 36
) n
WHERE DATE_ADD(STR_TO_DATE(#{params.beginSendDate}, '%Y-%m-%d %H:%i:%s'), INTERVAL n.n MONTH)
<![CDATA[ <= ]]> STR_TO_DATE(#{params.endSendDate}, '%Y-%m-%d %H:%i:%s')
) m
LEFT JOIN emis_waybill a
ON cu.monthly_pay_code = a.cust_no
AND a.del_flag = '0'
AND a.order_status != '0'
AND a.order_status != '2'
AND a.cust_no IS NOT NULL
AND a.cust_no != ''
<!-- 剔除问题件类型为面单删除、出口虚拟单、返货虚拟单的运单 -->
AND NOT EXISTS (
SELECT 1 FROM emis_waybill_problem_info ewpi
WHERE ewpi.bill_code = a.bill_code
AND ewpi.del_flag = '0'
AND ewpi.problem_type IN (151, 170, 173)
)
-- 只统计该月份内的运单
AND a.send_date <![CDATA[ >= ]]> STR_TO_DATE(m.monthStart, '%Y-%m-%d')
AND a.send_date <![CDATA[ < ]]> DATE_ADD(STR_TO_DATE(m.monthStart, '%Y-%m-%d'), INTERVAL 1 MONTH)
<!-- 寄件国家 -->
<if test="sendCountry != null and sendCountry != ''">
AND a.send_country = #{sendCountry}
</if>
<!-- 目的国家 -->
<if test="receiveCountry != null and receiveCountry != ''">
<choose>
<when test="receiveCountry == 'other'">
AND a.receive_country NOT IN ('0086','0084','0855','0095','0880','0856','0066')
</when>
<otherwise>
AND a.receive_country = #{receiveCountry}
</otherwise>
</choose>
</if>
LEFT JOIN emis_trans_line tl ON a.trans_line_type = tl.line_code AND tl.del_flag = '0'
<where>
cu.del_flag = '0'
AND cu.customer_type = '2'
<!-- 客户名称 -->
<if test="customerName != null and customerName != ''">
AND (cu.customer_name LIKE CONCAT('%', #{custName}, '%') OR a.cust_name LIKE CONCAT('%', #{custName}, '%'))
</if>
<if test="customerCode != null and customerCode != ''">
AND (cu.customer_code = #{customerCode} OR a.customer_code = #{customerCode})
</if>
<if test="custNo != null and custNo != ''">
AND cu.monthly_pay_code = #{custNo}
</if>
<!-- 销售主管 -->
<if test="salesExecutive != null and salesExecutive != ''">
AND cu.sales_executive LIKE CONCAT('%', #{salesExecutive}, '%')
</if>
<!-- 销售支持 -->
<if test="salesSupport != null and salesSupport != ''">
AND cu.sales_support LIKE CONCAT('%', #{salesSupport}, '%')
</if>
<!-- 销售联系人 -->
<if test="salesmen != null and salesmen != ''">
AND cu.salesmen LIKE CONCAT('%', #{salesmen}, '%')
</if>
<!-- 权限控制 -->
<if test="params.privSiteCode != null and params.privSiteCode != '88888'">
AND (
cu.sales_executive = #{params.privEmpName}
OR cu.sales_support = #{params.privEmpName}
OR cu.salesmen = #{params.privEmpName}
OR cu.salesmen IN (
SELECT salesmen FROM emis_salesmen_rel esr
WHERE esr.del_flag = '0'
AND esr.bl_open = '1'
AND NOW() <![CDATA[ >= ]]> esr.start_date
AND NOW() <![CDATA[ <= ]]> esr.end_date
AND esr.sales_ass = #{params.privEmpName}
)
)
</if>
<!-- 客户状态 -->
<if test="params.status != null and params.status != ''">
AND cu.status = #{params.status}
</if>
</where>
GROUP BY cu.id, cu.customer_code, cu.customer_name, cu.monthly_pay_code, cu.salesmen, cu.first_signing_day, m.sendMonth
) ms
ORDER BY ms.customerName ASC,ms.sendMonth DESC
</select>
</mapper>

View File

@ -3183,6 +3183,9 @@
or a.take_piece_employee_code=#{params.privEmpCode}
)
</if>
<if test="params.billMonth != null and params.billMonth != ''">
and DATE_FORMAT(a.send_date,'%Y%m')= #{params.billMonth}
</if>
</where>
@ -5083,6 +5086,9 @@
)
</if>
</if>
<if test="params.sendMonth != null and params.sendMonth != ''">
and DATE_FORMAT(a.send_date,'%Y-%m')= #{params.sendMonth}
</if>
<!-- 权限控制 -->
<if test="params.privSiteCode != null and params.privSiteCode != '88888'">
AND EXISTS (