月计划整体汇总表导出功能优化

This commit is contained in:
LHD_HY 2025-12-26 09:33:16 +08:00 committed by 马三炮
parent 312787de98
commit b9de1ca09b
6 changed files with 537 additions and 908 deletions

View File

@ -1,5 +1,6 @@
package com.bonus.business.controller.tool;
import com.bonus.digital.dao.BusinessTypeVo;
import com.bonus.digital.dao.ResourceSummaryVo;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
@ -17,17 +18,22 @@ import java.util.*;
import java.util.stream.Collectors;
/**
* 月计划资源统计分析Excel导出工具类
* 月计划资源统计分析Excel导出工具类适配动态业务类型
* 修复点
* 1. 运检站A列合并行数多一行问题
* 2. 单元格边框缺失问题
* 3. 第四行C列表头改为工日
* 4. 第五行B列清空移除总工日文本
*/
public class ResourceSummaryExcelExporter {
private static final Logger log = LoggerFactory.getLogger(ResourceSummaryExcelExporter.class);
// 常量定义
private static final int STATION_ROW_TOTAL = 14; // 单个运检站总行数
private static final int A_MERGE_START = 3; // A列合并起始行偏移第4行
private static final int A_MERGE_END = 13; // A列合并结束行偏移第14行
private static final int MAX_COLUMN = 5; // 最大列索引F列
private static final int REMARK_COL_INDEX = 5; // 备注列索引F列
private static final int STATION_ROW_BASE = 5; // 单个运检站基础行数标题+人员信息+子表头+总工日+分包用工
private static final int A_MERGE_START = 3; // A列合并起始行偏移第4行
private static final int MAX_COLUMN = 5; // 最大列索引F列
private static final int REMARK_COL_INDEX = 5; // 备注列索引F列
private static final int A_MERGE_ROW_REDUCE = 1; // A列合并行数减少1行修复多合并问题
// 固定文本常量
private static final String SHEET_PREFIX = "月资源统计分析表";
@ -35,10 +41,13 @@ public class ResourceSummaryExcelExporter {
private static final String SUMMARY_TEXT = "汇总";
private static final String RESOURCE_STAT_TITLE = "资源统计分析";
private static final String LONG_TERM_SUPPORT_REMARK = "长期支援不用统计";
private static final String TOTAL_WORKDAY_TEXT = "总工日";
private static final String UNARRANGE_TEXT = "未安排";
private static final String SUBCONTRACT_TEXT = "分包用工";
private static final String WORKDAY_TEXT = "工日"; // 修正后的C列子表头
// 列宽配置单位字符POI中1字符256单位
// 核心调整C列从1220D列从1015保证表头文字完整显示
private static final int[] COLUMN_WIDTHS = {20, 15, 20, 15, 10, 18}; // A-F列宽调整后
private static final int[] COLUMN_WIDTHS = {20, 15, 20, 15, 10, 18}; // A-F列宽
// 字体配置
private static final String FONT_NAME = "微软雅黑";
private static final short FONT_SIZE = 11;
@ -107,7 +116,7 @@ public class ResourceSummaryExcelExporter {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet(sheetName);
initStyles(workbook); // 初始化样式含不同数值格式
setColumnWidths(sheet); // 设置列宽已调整C/D列
setColumnWidths(sheet); // 设置列宽
// 写入数据
int currentRow = 0;
@ -140,7 +149,7 @@ public class ResourceSummaryExcelExporter {
}
/**
* 写入单个运检站/汇总的数据
* 写入单个运检站/汇总的数据适配动态类型
* @param sheet 工作表
* @param vo 数据VO
* @param name 运检站/汇总名称
@ -149,10 +158,17 @@ public class ResourceSummaryExcelExporter {
*/
private int writeStationData(Sheet sheet, ResourceSummaryVo vo, String name, int startRow) {
if (vo == null) {
return startRow + STATION_ROW_TOTAL;
// 计算默认行数基础行数+至少1行动态类型
return startRow + STATION_ROW_BASE + 1;
}
int rowIdx = startRow;
// 获取动态业务类型列表
List<BusinessTypeVo> dynamicTypeList = vo.getBusinessTypeList();
// 单个运检站总行数 = 基础行数 + 动态类型行数
int stationTotalRows = STATION_ROW_BASE + (dynamicTypeList == null ? 0 : dynamicTypeList.size());
// A列合并结束行偏移修复多合并1行问题
int aMergeEndOffset = A_MERGE_START + (stationTotalRows - 1) - A_MERGE_ROW_REDUCE;
// 1. 标题行第1行合并A-F列内容"资源统计分析"
Row titleRow = createRow(sheet, rowIdx++);
@ -170,10 +186,10 @@ public class ResourceSummaryExcelExporter {
setCellValueAndStyle(personHeaderRow, 2, "长期借调、支援人数", headerStyle);
// D列实际在站人数表头样式
setCellValueAndStyle(personHeaderRow, 3, "实际在站人数", headerStyle);
// A/E/F列A列样式兜底F列内容样式
// A/E/F列填充样式保证边框
setCellValueAndStyle(personHeaderRow, 0, "", headerStyle);
setCellValueAndStyle(personHeaderRow, 4, "", headerStyle);
setCellValueAndStyle(personHeaderRow, REMARK_COL_INDEX, "", contentStyle);
setCellValueAndStyle(personHeaderRow, REMARK_COL_INDEX, "", headerStyle);
// 3. 人员数据行第3行B/C/D列填充对应数据
Row personDataRow = createRow(sheet, rowIdx++);
@ -186,87 +202,105 @@ public class ResourceSummaryExcelExporter {
// D列实际在站人数actualStationNum
int actualStationNum = vo.getActualStationNum() == null ? 0 : vo.getActualStationNum();
setCellValueAndStyle(personDataRow, 3, actualStationNum, contentStyle);
// A/E/F列
// A/E/F列填充样式保证边框
setCellValueAndStyle(personDataRow, 0, "", contentStyle);
setCellValueAndStyle(personDataRow, 4, "", contentStyle);
setCellValueAndStyle(personDataRow, REMARK_COL_INDEX, "", contentStyle);
// 4. 子表头行第4行类型/工日/比值/人均/备注
// 4. 子表头行第4行类型/工日/比值/人均/备注修复C列表头
Row subHeaderRow = createRow(sheet, rowIdx++);
setCellValueAndStyle(subHeaderRow, 0, "", headerStyle); // A列空填充样式
setCellValueAndStyle(subHeaderRow, 1, "类型", headerStyle);
setCellValueAndStyle(subHeaderRow, 2, "总工日", headerStyle);
setCellValueAndStyle(subHeaderRow, 2, WORKDAY_TEXT, headerStyle); // 修正为工日
setCellValueAndStyle(subHeaderRow, 3, "比值", headerStyle);
setCellValueAndStyle(subHeaderRow, 4, "人均", headerStyle);
setCellValueAndStyle(subHeaderRow, REMARK_COL_INDEX, "备注", headerStyle);
fillRowStyle(subHeaderRow, headerStyle);
fillRowStyle(subHeaderRow, headerStyle); // 强制填充整行样式
// 5. 总工日行第5行C列填充totalWorkday
// 5. 总工日行第5行B列清空C列填充totalWorkday修复点
Row totalWorkdayRow = createRow(sheet, rowIdx++);
// A/B/D/E/F列
// A填充样式
setCellValueAndStyle(totalWorkdayRow, 0, "", contentStyle);
// B列清空移除原总工日文本
setCellValueAndStyle(totalWorkdayRow, 1, "", contentStyle);
setCellValueAndStyle(totalWorkdayRow, 3, "", contentStyle);
setCellValueAndStyle(totalWorkdayRow, 4, "", contentStyle);
setCellValueAndStyle(totalWorkdayRow, REMARK_COL_INDEX, "", contentStyle);
// C列总工日数据totalWorkday
int totalWorkday = vo.getTotalWorkday() == null ? 0 : vo.getTotalWorkday();
setCellValueAndStyle(totalWorkdayRow, 2, totalWorkday, workdayStyle);
// D/E/F列填充样式
setCellValueAndStyle(totalWorkdayRow, 3, "", contentStyle);
setCellValueAndStyle(totalWorkdayRow, 4, "", contentStyle);
setCellValueAndStyle(totalWorkdayRow, REMARK_COL_INDEX, "", contentStyle);
// 6. 类型行第6-13行8类数据
String[] types = {"休假", "培训", "运行", "运行(视频)", "检修", "值班", "其他", "未安排"};
Integer[] workdays = {
vo.getRestWorkday(), vo.getTrainWorkday(), vo.getRunWorkday(), vo.getRunVideoWorkday(),
vo.getMaintainWorkday(), vo.getDutyWorkday(), vo.getOtherWorkday(), vo.getUnarrangeWorkday()
};
BigDecimal[] ratios = {
vo.getRestRatio(), vo.getTrainRatio(), vo.getRunRatio(), vo.getRunVideoRatio(),
vo.getMaintainRatio(), vo.getDutyRatio(), vo.getOtherRatio(), vo.getUnarrangeRatio()
};
BigDecimal[] avgs = {
vo.getRestAvg(), vo.getTrainAvg(), vo.getRunAvg(), vo.getRunVideoAvg(),
vo.getMaintainAvg(), vo.getDutyAvg(), vo.getOtherAvg(), vo.getUnarrangeAvg()
};
for (int i = 0; i < types.length; i++) {
Row typeRow = createRow(sheet, rowIdx++);
// B列类型名称
setCellValueAndStyle(typeRow, 1, types[i], contentStyle);
// C列总工日整数格式
int workday = workdays[i] == null ? 0 : workdays[i];
setCellValueAndStyle(typeRow, 2, workday, workdayStyle);
// D列比值保留2位小数
BigDecimal ratio = ratios[i] == null ? BigDecimal.ZERO : ratios[i].setScale(2, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(typeRow, 3, ratio, ratioStyle);
// E列人均保留1位小数
BigDecimal avg = avgs[i] == null ? BigDecimal.ZERO : avgs[i].setScale(1, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(typeRow, 4, avg, avgStyle);
// F列备注"其他"行显示
if (i == 6) { // "其他"
setCellValueAndStyle(typeRow, REMARK_COL_INDEX, LONG_TERM_SUPPORT_REMARK, contentStyle);
} else {
setCellValueAndStyle(typeRow, REMARK_COL_INDEX, "", contentStyle);
// 6. 动态类型行替换原固定类型行
if (dynamicTypeList != null && !dynamicTypeList.isEmpty()) {
for (BusinessTypeVo typeVo : dynamicTypeList) {
Row typeRow = createRow(sheet, rowIdx++);
// 跳过未安排单独处理
if (UNARRANGE_TEXT.equals(typeVo.getTypeName())) {
continue;
}
// A列填充样式
setCellValueAndStyle(typeRow, 0, "", contentStyle);
// B列类型名称
String typeName = Optional.ofNullable(typeVo.getTypeName()).orElse("");
setCellValueAndStyle(typeRow, 1, typeName, contentStyle);
// C列总工日整数格式
int workday = typeVo.getWorkday() == null ? 0 : typeVo.getWorkday();
setCellValueAndStyle(typeRow, 2, workday, workdayStyle);
// D列比值保留2位小数
BigDecimal ratio = Optional.ofNullable(typeVo.getRatio()).orElse(BigDecimal.ZERO)
.setScale(2, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(typeRow, 3, ratio, ratioStyle);
// E列人均保留1位小数
BigDecimal avg = Optional.ofNullable(typeVo.getAvg()).orElse(BigDecimal.ZERO)
.setScale(1, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(typeRow, 4, avg, avgStyle);
// F列备注"其他"类型显示备注
if (LONG_TERM_SUPPORT_REMARK.contains(typeName) || "其他".equals(typeName)) {
setCellValueAndStyle(typeRow, REMARK_COL_INDEX, LONG_TERM_SUPPORT_REMARK, contentStyle);
} else {
setCellValueAndStyle(typeRow, REMARK_COL_INDEX, "", contentStyle);
}
}
// A列
setCellValueAndStyle(typeRow, 0, "", contentStyle);
}
// 7. 分包用工行第14行C列填充subcontractWorkday字体不加粗
// 7. 未安排行固定行单独处理
Row unarrangeRow = createRow(sheet, rowIdx++);
// A列填充样式
setCellValueAndStyle(unarrangeRow, 0, "", contentStyle);
// B列未安排
setCellValueAndStyle(unarrangeRow, 1, UNARRANGE_TEXT, contentStyle);
// C列未安排工日
int unarrangeWorkday = vo.getUnarrangeWorkday() == null ? 0 : vo.getUnarrangeWorkday();
setCellValueAndStyle(unarrangeRow, 2, unarrangeWorkday, workdayStyle);
// D列未安排比值
BigDecimal unarrangeRatio = Optional.ofNullable(vo.getUnarrangeRatio()).orElse(BigDecimal.ZERO)
.setScale(2, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(unarrangeRow, 3, unarrangeRatio, ratioStyle);
// E列未安排人均
BigDecimal unarrangeAvg = Optional.ofNullable(vo.getUnarrangeAvg()).orElse(BigDecimal.ZERO)
.setScale(1, BigDecimal.ROUND_HALF_UP);
setCellValueAndStyle(unarrangeRow, 4, unarrangeAvg, avgStyle);
// F列空备注填充样式
setCellValueAndStyle(unarrangeRow, REMARK_COL_INDEX, "", contentStyle);
// 8. 分包用工行固定行
Row subcontractRow = createRow(sheet, rowIdx++);
// A列
// A列填充样式
setCellValueAndStyle(subcontractRow, 0, "", contentStyle);
// B列分包用工内容样式不加粗
setCellValueAndStyle(subcontractRow, 1, "分包用工", contentStyle);
// B列分包用工
setCellValueAndStyle(subcontractRow, 1, SUBCONTRACT_TEXT, contentStyle);
// C列分包用工总工日subcontractWorkday
int subWorkday = vo.getSubcontractWorkday() == null ? 0 : vo.getSubcontractWorkday();
setCellValueAndStyle(subcontractRow, 2, subWorkday, workdayStyle);
// D/E/F列
// D/E/F列填充样式
setCellValueAndStyle(subcontractRow, 3, "", contentStyle);
setCellValueAndStyle(subcontractRow, 4, "", contentStyle);
setCellValueAndStyle(subcontractRow, REMARK_COL_INDEX, "", contentStyle);
// A列合并第4-14行并填充运检站名称
// A列合并修复多合并1行问题并填充运检站名称
int aMergeFirstRow = startRow + A_MERGE_START;
int aMergeLastRow = startRow + A_MERGE_END;
int aMergeLastRow = startRow + aMergeEndOffset;
sheet.addMergedRegion(new CellRangeAddress(aMergeFirstRow, aMergeLastRow, 0, 0));
Row aMergeRow = sheet.getRow(aMergeFirstRow);
Cell aMergeCell = createCell(aMergeRow, 0);
@ -301,21 +335,21 @@ public class ResourceSummaryExcelExporter {
// 基础内容样式普通+居中+全边框
contentStyle = createBaseCellStyle(workbook, normalFont);
// 工日列样式整数格式
// 工日列样式整数格式 + 全边框
workdayStyle = createBaseCellStyle(workbook, normalFont);
workdayStyle.setDataFormat(workbook.createDataFormat().getFormat("0"));
// 比值列样式保留2位小数
// 比值列样式保留2位小数 + 全边框
ratioStyle = createBaseCellStyle(workbook, normalFont);
ratioStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
// 人均列样式保留1位小数
// 人均列样式保留1位小数 + 全边框
avgStyle = createBaseCellStyle(workbook, normalFont);
avgStyle.setDataFormat(workbook.createDataFormat().getFormat("0.0"));
}
/**
* 创建基础单元格样式
* 创建基础单元格样式保证全边框
*/
private CellStyle createBaseCellStyle(Workbook workbook, Font font) {
CellStyle style = workbook.createCellStyle();
@ -324,7 +358,7 @@ public class ResourceSummaryExcelExporter {
// 对齐方式水平+垂直居中
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 边框全边框+黑色
// 边框全边框+黑色确保边框完整
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
@ -339,7 +373,7 @@ public class ResourceSummaryExcelExporter {
}
/**
* 设置列宽核心调整C/D列加宽
* 设置列宽
*/
private void setColumnWidths(Sheet sheet) {
for (int i = 0; i < COLUMN_WIDTHS.length; i++) {
@ -376,7 +410,7 @@ public class ResourceSummaryExcelExporter {
if (row == null) {
row = sheet.createRow(rowIdx);
}
// 设置行高
// 设置行高保证单元格显示完整
row.setHeightInPoints(20);
return row;
}

View File

@ -0,0 +1,44 @@
package com.bonus.digital.dao;
import lombok.Data;
import java.math.BigDecimal;
/**
* 动态业务类型Vo存储每个运检站实际存在的业务类型数据
*/
@Data
public class BusinessTypeVo {
// 类型名称休假培训运行视频
private String typeName;
// 总天数
private Integer totalDays;
// 参与人数
private Integer personCount;
// 工日
private Integer workday;
// 比值workday/totalWorkday
private BigDecimal ratio;
// 人均workday/actualStationNum
private BigDecimal avg;
// 以下两个字段用于计算比值和人均无需前端返回仅作中间计算
private Integer totalWorkday;
private Integer actualStationNum;
/**
* 计算比值和人均初始化时自动计算
*/
public void calculateRatioAndAvg() {
// 计算比值保留4位小数分母为0时返回0
this.ratio = BigDecimal.ZERO;
if (this.totalWorkday != null && this.totalWorkday > 0 && this.workday != null) {
this.ratio = new BigDecimal(this.workday)
.divide(new BigDecimal(this.totalWorkday), 4, BigDecimal.ROUND_HALF_UP);
}
// 计算人均保留3位小数分母为0时返回0
this.avg = BigDecimal.ZERO;
if (this.actualStationNum != null && this.actualStationNum > 0 && this.workday != null) {
this.avg = new BigDecimal(this.workday)
.divide(new BigDecimal(this.actualStationNum), 3, BigDecimal.ROUND_HALF_UP);
}
}
}

View File

@ -17,32 +17,32 @@ public class ResourceSummaryExcelVo {
@ColumnWidth(15)
private String inspectionStationName;
// B列编制人数/类型二选一用ignore避免重复映射
// B列编制人数/类型
@ExcelProperty(value = "编制人数/类型", index = 1)
@ColumnWidth(18)
private Integer compileNum;
@ExcelIgnore // 关键标记为忽略避免EasyExcel分配到G列
private String type;
// C列长期借调支援人数/工日二选一
// C列长期借调支援人数/工日
@ExcelProperty(value = "长期借调、支援人数/工日", index = 2)
@ColumnWidth(18)
private Integer secondmentNum;
@ExcelIgnore // 关键标记为忽略
private Integer workday;
// D列实际在站人数/比值二选一
// D列实际在站人数/比值
@ExcelProperty(value = "实际在站人数/比值", index = 3)
@ColumnWidth(12)
private Integer actualStationNum;
@ExcelIgnore // 关键标记为忽略
private BigDecimal ratio;
// E列人均/分包用工二选一
// E列人均/分包用工
@ExcelProperty(value = "人均/分包用工", index = 4)
@ColumnWidth(12)
private BigDecimal avg;
@ExcelIgnore // 关键标记为忽略
@ExcelIgnore // 标记为忽略
private Integer subcontractWorkday;
// F列备注

View File

@ -2,6 +2,8 @@ package com.bonus.digital.dao;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Data
public class ResourceSummaryVo {
@ -15,74 +17,53 @@ public class ResourceSummaryVo {
private Integer actualStationNum; // 实际在站人数
private Integer totalWorkday; // 总工日
// 休假
private Integer restTotalDays; // 休假总天数
private Integer restPersonCount; // 休假参与人数
private Integer restWorkday;
private BigDecimal restRatio;
private BigDecimal restAvg;
// 培训
private Integer trainTotalDays;
private Integer trainPersonCount;
private Integer trainWorkday;
private BigDecimal trainRatio;
private BigDecimal trainAvg;
// 运行
private Integer runTotalDays;
private Integer runPersonCount;
private Integer runWorkday;
private BigDecimal runRatio;
private BigDecimal runAvg;
// 运行视频
private Integer runVideoTotalDays;
private Integer runVideoPersonCount;
private Integer runVideoWorkday;
private BigDecimal runVideoRatio;
private BigDecimal runVideoAvg;
// 检修
private Integer maintainTotalDays;
private Integer maintainPersonCount;
private Integer maintainWorkday;
private BigDecimal maintainRatio;
private BigDecimal maintainAvg;
// 值班
private Integer dutyTotalDays;
private Integer dutyPersonCount;
private Integer dutyWorkday;
private BigDecimal dutyRatio;
private BigDecimal dutyAvg;
// 抢修
private Integer repairTotalDays;
private Integer repairPersonCount;
private Integer repairWorkday;
private BigDecimal repairRatio;
private BigDecimal repairAvg;
// 学习
private Integer studyTotalDays;
private Integer studyPersonCount;
private Integer studyWorkday;
private BigDecimal studyRatio;
private BigDecimal studyAvg;
// 其他
private Integer otherTotalDays;
private Integer otherPersonCount;
private Integer otherWorkday;
private BigDecimal otherRatio;
private BigDecimal otherAvg;
// 未安排
// 未安排固定展示
private Integer unarrangeWorkday;
private BigDecimal unarrangeRatio;
private BigDecimal unarrangeAvg;
// 分包用工
// 分包用工固定展示
private Integer subcontractWorkday;
// 动态业务类型列表替换原type_json为type_str存储拼接字符串
private String typeStr; // 数据库返回的拼接字符串格式类型1|类型2|...
private transient List<BusinessTypeVo> businessTypeList; // 转换后的类型列表不持久化
/**
* 获取动态业务类型列表自动解析拼接字符串
* @return 业务类型列表
*/
public List<BusinessTypeVo> getBusinessTypeList() {
if (businessTypeList == null) {
businessTypeList = new ArrayList<>();
// 解析拼接字符串
if (typeStr != null && !typeStr.isEmpty()) {
// |分割多个类型
String[] typeArray = typeStr.split("\\|");
for (String singleTypeStr : typeArray) {
if (singleTypeStr.isEmpty()) {
continue;
}
// ,分割单个类型的字段
String[] fieldArray = singleTypeStr.split(",");
// 字段顺序typeName,totalDays,personCount,workday,totalWorkday,actualStationNum
if (fieldArray.length != 6) {
continue; // 字段异常跳过该类型
}
BusinessTypeVo typeVo = new BusinessTypeVo();
// 赋值字段注意类型转换
typeVo.setTypeName(fieldArray[0].replace("", ",")); // 还原转义的逗号
typeVo.setTotalDays(Integer.parseInt(fieldArray[1]));
typeVo.setPersonCount(Integer.parseInt(fieldArray[2]));
typeVo.setWorkday(Integer.parseInt(fieldArray[3]));
typeVo.setTotalWorkday(Integer.parseInt(fieldArray[4]));
typeVo.setActualStationNum(Integer.parseInt(fieldArray[5]));
// 计算比值和人均
typeVo.calculateRatioAndAvg();
businessTypeList.add(typeVo);
}
}
}
return businessTypeList;
}
}

View File

@ -15,6 +15,7 @@ import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author 马三炮
@ -202,108 +203,175 @@ public class MonthlyPlanServiceImpl implements MonthlyPlanService {
return exportList;
}
/**
* 导出资源统计汇总数据适配Excel导出含各运检站+汇总行
* @param monthlyPlanVo 筛选条件核心monthlyPlan 统计月份
* @return 资源统计Vo列表最后一条为汇总数据
*/
@Override
public List<ResourceSummaryVo> exportResourceSummary(MonthlyPlanVo monthlyPlanVo) {
// 1. 入参用MonthlyPlanVo传递筛选条件调用Mapper查询ResourceSummaryVo统计结果
// Mapper中会从MonthlyPlanVo取monthlyPlan/inspectionStationId等筛选值
// 1. 入参校验与查询数据
if (monthlyPlanVo == null || monthlyPlanVo.getMonthlyPlan() == null) {
log.warn("资源统计查询入参无效MonthlyPlanVo或统计月份为空");
return Collections.emptyList();
}
List<ResourceSummaryVo> stationResourceList = monthlyPlanMapper.getResourceSummary(monthlyPlanVo);
if (stationResourceList.isEmpty()) {
if (stationResourceList == null || stationResourceList.isEmpty()) {
log.info("资源统计查询无数据,筛选条件:月份={}运检站ID={}",
monthlyPlanVo.getMonthlyPlan(), monthlyPlanVo.getInspectionStationId());
return Collections.emptyList();
}
log.info("资源统计查询成功,运检站原始数据量:{}", stationResourceList.size());
// 2. 计算汇总行仅用ResourceSummaryVo全程不混用
ResourceSummaryVo summaryResourceVo = new ResourceSummaryVo();
summaryResourceVo.setInspectionStationName("汇总"); // 汇总行名称
// 2. 数据去重按运检站名称去重避免重复数据
List<ResourceSummaryVo> distinctStationList = stationResourceList.stream()
.filter(Objects::nonNull)
.filter(vo -> Objects.nonNull(vo.getInspectionStationName()))
.collect(Collectors.collectingAndThen(
Collectors.toMap(
ResourceSummaryVo::getInspectionStationName,
vo -> vo,
(existing, newVo) -> {
log.warn("运检站名称重复,保留第一条数据:{}", existing.getInspectionStationName());
return existing;
}
),
map -> new ArrayList<>(map.values())
));
log.info("运检站数据去重后数量:{}", distinctStationList.size());
// 2.1 人员基础数据累加纯ResourceSummaryVo字段
summaryResourceVo.setCompileNum(stationResourceList.stream().mapToInt(ResourceSummaryVo::getCompileNum).sum());
summaryResourceVo.setSecondmentNum(stationResourceList.stream().mapToInt(ResourceSummaryVo::getSecondmentNum).sum());
summaryResourceVo.setActualStationNum(stationResourceList.stream().mapToInt(ResourceSummaryVo::getActualStationNum).sum());
summaryResourceVo.setTotalWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getTotalWorkday).sum());
// 3. 构建汇总行数据
ResourceSummaryVo summaryResourceVo = buildSummaryResourceVo(distinctStationList);
// 2.2 各类型中间字段累加TotalDays/PersonCount纯ResourceSummaryVo
// 休假
summaryResourceVo.setRestTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRestTotalDays).sum());
summaryResourceVo.setRestPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRestPersonCount).sum());
summaryResourceVo.setRestWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRestWorkday).sum());
// 培训
summaryResourceVo.setTrainTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getTrainTotalDays).sum());
summaryResourceVo.setTrainPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getTrainPersonCount).sum());
summaryResourceVo.setTrainWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getTrainWorkday).sum());
// 运行
summaryResourceVo.setRunTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunTotalDays).sum());
summaryResourceVo.setRunPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunPersonCount).sum());
summaryResourceVo.setRunWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunWorkday).sum());
// 运行视频
summaryResourceVo.setRunVideoTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunVideoTotalDays).sum());
summaryResourceVo.setRunVideoPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunVideoPersonCount).sum());
summaryResourceVo.setRunVideoWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRunVideoWorkday).sum());
// 检修原维护
summaryResourceVo.setMaintainTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getMaintainTotalDays).sum());
summaryResourceVo.setMaintainPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getMaintainPersonCount).sum());
summaryResourceVo.setMaintainWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getMaintainWorkday).sum());
// 值班
summaryResourceVo.setDutyTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getDutyTotalDays).sum());
summaryResourceVo.setDutyPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getDutyPersonCount).sum());
summaryResourceVo.setDutyWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getDutyWorkday).sum());
// 抢修
summaryResourceVo.setRepairTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRepairTotalDays).sum());
summaryResourceVo.setRepairPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRepairPersonCount).sum());
summaryResourceVo.setRepairWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getRepairWorkday).sum());
// 学习
summaryResourceVo.setStudyTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getStudyTotalDays).sum());
summaryResourceVo.setStudyPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getStudyPersonCount).sum());
summaryResourceVo.setStudyWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getStudyWorkday).sum());
// 其他
summaryResourceVo.setOtherTotalDays(stationResourceList.stream().mapToInt(ResourceSummaryVo::getOtherTotalDays).sum());
summaryResourceVo.setOtherPersonCount(stationResourceList.stream().mapToInt(ResourceSummaryVo::getOtherPersonCount).sum());
summaryResourceVo.setOtherWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getOtherWorkday).sum());
// 分包用工
summaryResourceVo.setSubcontractWorkday(stationResourceList.stream().mapToInt(ResourceSummaryVo::getSubcontractWorkday).sum());
// 2.3 未安排工日计算纯ResourceSummaryVo字段
int unarrangeWorkday = summaryResourceVo.getTotalWorkday()
- (summaryResourceVo.getRestWorkday() + summaryResourceVo.getTrainWorkday() + summaryResourceVo.getRunWorkday()
+ summaryResourceVo.getRunVideoWorkday() + summaryResourceVo.getMaintainWorkday() + summaryResourceVo.getDutyWorkday()
+ summaryResourceVo.getRepairWorkday() + summaryResourceVo.getStudyWorkday());
summaryResourceVo.setUnarrangeWorkday(unarrangeWorkday);
// 2.4 比值/人均计算纯ResourceSummaryVo字段复用通用方法
summaryResourceVo.setRestRatio(calcRatio(summaryResourceVo.getRestWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setRestAvg(calcAvg(summaryResourceVo.getRestWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setTrainRatio(calcRatio(summaryResourceVo.getTrainWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setTrainAvg(calcAvg(summaryResourceVo.getTrainWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setRunRatio(calcRatio(summaryResourceVo.getRunWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setRunAvg(calcAvg(summaryResourceVo.getRunWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setRunVideoRatio(calcRatio(summaryResourceVo.getRunVideoWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setRunVideoAvg(calcAvg(summaryResourceVo.getRunVideoWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setMaintainRatio(calcRatio(summaryResourceVo.getMaintainWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setMaintainAvg(calcAvg(summaryResourceVo.getMaintainWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setDutyRatio(calcRatio(summaryResourceVo.getDutyWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setDutyAvg(calcAvg(summaryResourceVo.getDutyWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setRepairRatio(calcRatio(summaryResourceVo.getRepairWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setRepairAvg(calcAvg(summaryResourceVo.getRepairWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setStudyRatio(calcRatio(summaryResourceVo.getStudyWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setStudyAvg(calcAvg(summaryResourceVo.getStudyWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setOtherRatio(calcRatio(summaryResourceVo.getOtherWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setOtherAvg(calcAvg(summaryResourceVo.getOtherWorkday(), summaryResourceVo.getActualStationNum()));
summaryResourceVo.setUnarrangeRatio(calcRatio(summaryResourceVo.getUnarrangeWorkday(), summaryResourceVo.getTotalWorkday()));
summaryResourceVo.setUnarrangeAvg(calcAvg(summaryResourceVo.getUnarrangeWorkday(), summaryResourceVo.getActualStationNum()));
// 3. 汇总行加入结果列表纯ResourceSummaryVo列表
List<ResourceSummaryVo> finalResourceList = new ArrayList<>(stationResourceList);
// 4. 组装最终结果列表运检站数据+汇总数据
List<ResourceSummaryVo> finalResourceList = new ArrayList<>(distinctStationList);
finalResourceList.add(summaryResourceVo);
log.info("资源统计汇总完成,最终数据量:{}(运检站{}个+汇总1条",
finalResourceList.size(), distinctStationList.size());
return finalResourceList;
}
/**
* 构建汇总行数据独立方法降低耦合
* @param distinctStationList 去重后的运检站数据列表
* @return 汇总行ResourceSummaryVo
*/
private ResourceSummaryVo buildSummaryResourceVo(List<ResourceSummaryVo> distinctStationList) {
ResourceSummaryVo summaryVo = new ResourceSummaryVo();
summaryVo.setInspectionStationName("汇总");
// 3.1 人员基础数据累加
int totalCompileNum = distinctStationList.stream()
.mapToInt(vo -> Optional.ofNullable(vo.getCompileNum()).orElse(0))
.sum();
int totalSecondmentNum = distinctStationList.stream()
.mapToInt(vo -> Optional.ofNullable(vo.getSecondmentNum()).orElse(0))
.sum();
int totalActualStationNum = distinctStationList.stream()
.mapToInt(vo -> Optional.ofNullable(vo.getActualStationNum()).orElse(0))
.sum();
int totalWorkday = distinctStationList.stream()
.mapToInt(vo -> Optional.ofNullable(vo.getTotalWorkday()).orElse(0))
.sum();
summaryVo.setCompileNum(totalCompileNum);
summaryVo.setSecondmentNum(totalSecondmentNum);
summaryVo.setActualStationNum(totalActualStationNum);
summaryVo.setTotalWorkday(totalWorkday);
// 3.2 分包用工累加
int totalSubcontractWorkday = distinctStationList.stream()
.mapToInt(vo -> Optional.ofNullable(vo.getSubcontractWorkday()).orElse(0))
.sum();
summaryVo.setSubcontractWorkday(totalSubcontractWorkday);
// 3.3 动态业务类型汇总按类型名称分组累加
Map<String, BusinessTypeVo> summaryTypeMap = aggregateDynamicTypeData(distinctStationList);
List<BusinessTypeVo> summaryTypeList = new ArrayList<>(summaryTypeMap.values());
// 3.4 处理汇总类型的比值与人均计算
summaryTypeList.forEach(typeVo -> {
typeVo.setTotalWorkday(totalWorkday);
typeVo.setActualStationNum(totalActualStationNum);
typeVo.calculateRatioAndAvg();
});
// 3.5 未安排工日计算动态扣除所有有效类型工日避免负数
int totalDynamicWorkday = summaryTypeList.stream()
.mapToInt(typeVo -> Optional.ofNullable(typeVo.getWorkday()).orElse(0))
.sum();
int unarrangeWorkday = Math.max(totalWorkday - totalDynamicWorkday, 0);
summaryVo.setUnarrangeWorkday(unarrangeWorkday);
// 3.6 未安排字段比值/人均计算
summaryVo.setUnarrangeRatio(calcRatio(unarrangeWorkday, totalWorkday));
summaryVo.setUnarrangeAvg(calcAvg(unarrangeWorkday, totalActualStationNum));
String summaryTypeStr = buildDynamicTypeStr(summaryTypeList);
summaryVo.setTypeStr(summaryTypeStr);
return summaryVo;
}
/**
* 聚合所有运检站的动态类型数据按类型名称分组
* @param stationList 运检站数据列表
* @return 分组后的汇总类型Map
*/
private Map<String, BusinessTypeVo> aggregateDynamicTypeData(List<ResourceSummaryVo> stationList) {
return stationList.stream()
.filter(Objects::nonNull)
.map(ResourceSummaryVo::getBusinessTypeList) // 获取每个运检站的动态类型列表
.filter(Objects::nonNull)
.flatMap(List::stream) // 扁平化所有类型
.filter(typeVo -> Objects.nonNull(typeVo.getTypeName()))
.filter(typeVo -> !"未安排".equals(typeVo.getTypeName())) // 排除未安排单独处理
.collect(Collectors.toMap(
BusinessTypeVo::getTypeName, // 按类型名称分组
typeVo -> {
// 新建汇总类型对象避免修改原数据
BusinessTypeVo newTypeVo = new BusinessTypeVo();
newTypeVo.setTypeName(typeVo.getTypeName());
newTypeVo.setTotalDays(Optional.ofNullable(typeVo.getTotalDays()).orElse(0));
newTypeVo.setPersonCount(Optional.ofNullable(typeVo.getPersonCount()).orElse(0));
newTypeVo.setWorkday(Optional.ofNullable(typeVo.getWorkday()).orElse(0));
return newTypeVo;
},
(existingType, newType) -> {
// 累加数据
existingType.setTotalDays(existingType.getTotalDays() + Optional.ofNullable(newType.getTotalDays()).orElse(0));
existingType.setPersonCount(existingType.getPersonCount() + Optional.ofNullable(newType.getPersonCount()).orElse(0));
existingType.setWorkday(existingType.getWorkday() + Optional.ofNullable(newType.getWorkday()).orElse(0));
return existingType;
}
));
}
/**
* 构建动态类型拼接字符串
* @param typeList 动态类型列表
* @return 拼接后的字符串格式类型1|类型2|...单个类型名称,总天数,人数,工日,总工日,在站人数
*/
private String buildDynamicTypeStr(List<BusinessTypeVo> typeList) {
if (typeList == null || typeList.isEmpty()) {
return "";
}
return typeList.stream()
.map(typeVo -> {
// 转义类型名称中的逗号避免分隔符冲突
String typeName = Optional.ofNullable(typeVo.getTypeName()).orElse("").replace(",", "");
int totalDays = Optional.ofNullable(typeVo.getTotalDays()).orElse(0);
int personCount = Optional.ofNullable(typeVo.getPersonCount()).orElse(0);
int workday = Optional.ofNullable(typeVo.getWorkday()).orElse(0);
int totalWorkday = Optional.ofNullable(typeVo.getTotalWorkday()).orElse(0);
int actualStationNum = Optional.ofNullable(typeVo.getActualStationNum()).orElse(0);
return String.join(",", typeName,
String.valueOf(totalDays),
String.valueOf(personCount),
String.valueOf(workday),
String.valueOf(totalWorkday),
String.valueOf(actualStationNum));
})
.collect(Collectors.joining("|"));
}
/**
* 获取月计划详情

View File

@ -196,105 +196,38 @@
<select id="getResourceSummary" resultType="com.bonus.digital.dao.ResourceSummaryVo">
<![CDATA[
-- 外层:仅格式化计算结果如ROUND所有数值字段做null兜底
-- 外层:基础信息+未安排+分包用工 + 动态类型聚合结果(字符串拼接格式)
SELECT
base_data.inspection_station_id,
base_data.inspection_station_name,
-- 基础人员字段兜底
IFNULL(base_data.compile_num, 0) AS compile_num,
IFNULL(base_data.secondment_num, 0) AS secondment_num,
IFNULL(base_data.actual_station_num, 0) AS actual_station_num,
IFNULL(base_data.total_workday, 0) AS total_workday,
-- 休假相关所有字段null转0比值分母为0时返回0
IFNULL(base_data.rest_total_days, 0) AS rest_total_days,
IFNULL(base_data.rest_person_count, 0) AS rest_person_count,
IFNULL(base_data.rest_workday, 0) AS rest_workday,
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS rest_ratio,
ROUND(IFNULL(base_data.rest_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS rest_avg,
-- 培训相关
IFNULL(base_data.train_total_days, 0) AS train_total_days,
IFNULL(base_data.train_person_count, 0) AS train_person_count,
IFNULL(base_data.train_workday, 0) AS train_workday,
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS train_ratio,
ROUND(IFNULL(base_data.train_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS train_avg,
-- 运行相关
IFNULL(base_data.run_total_days, 0) AS run_total_days,
IFNULL(base_data.run_person_count, 0) AS run_person_count,
IFNULL(base_data.run_workday, 0) AS run_workday,
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS run_ratio,
ROUND(IFNULL(base_data.run_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS run_avg,
-- 运行(视频)相关
IFNULL(base_data.run_video_total_days, 0) AS run_video_total_days,
IFNULL(base_data.run_video_person_count, 0) AS run_video_person_count,
IFNULL(base_data.run_video_workday, 0) AS run_video_workday,
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS run_video_ratio,
ROUND(IFNULL(base_data.run_video_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS run_video_avg,
-- 检修相关
IFNULL(base_data.maintain_total_days, 0) AS maintain_total_days,
IFNULL(base_data.maintain_person_count, 0) AS maintain_person_count,
IFNULL(base_data.maintain_workday, 0) AS maintain_workday,
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS maintain_ratio,
ROUND(IFNULL(base_data.maintain_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS maintain_avg,
-- 值班相关
IFNULL(base_data.duty_total_days, 0) AS duty_total_days,
IFNULL(base_data.duty_person_count, 0) AS duty_person_count,
IFNULL(base_data.duty_workday, 0) AS duty_workday,
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS duty_ratio,
ROUND(IFNULL(base_data.duty_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS duty_avg,
-- 抢修相关
IFNULL(base_data.repair_total_days, 0) AS repair_total_days,
IFNULL(base_data.repair_person_count, 0) AS repair_person_count,
IFNULL(base_data.repair_workday, 0) AS repair_workday,
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS repair_ratio,
ROUND(IFNULL(base_data.repair_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS repair_avg,
-- 学习相关
IFNULL(base_data.study_total_days, 0) AS study_total_days,
IFNULL(base_data.study_person_count, 0) AS study_person_count,
IFNULL(base_data.study_workday, 0) AS study_workday,
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS study_ratio,
ROUND(IFNULL(base_data.study_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS study_avg,
-- 其他相关
IFNULL(base_data.other_total_days, 0) AS other_total_days,
IFNULL(base_data.other_person_count, 0) AS other_person_count,
IFNULL(base_data.other_workday, 0) AS other_workday,
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS other_ratio,
ROUND(IFNULL(base_data.other_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS other_avg,
-- 未安排相关
IFNULL(base_data.unarrange_workday, 0) AS unarrange_workday,
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.total_workday, 0), 0), 4) AS unarrange_ratio,
ROUND(IFNULL(base_data.unarrange_workday / NULLIF(base_data.actual_station_num, 0), 0), 3) AS unarrange_avg,
base_info.inspection_station_id,
base_info.inspection_station_name,
-- 基础字段
IFNULL(base_info.compile_num, 0) AS compile_num,
IFNULL(base_info.secondment_num, 0) AS secondment_num,
IFNULL(base_info.actual_station_num, 0) AS actual_station_num,
IFNULL(base_info.total_workday, 0) AS total_workday,
-- 未安排
IFNULL(base_info.unarrange_workday, 0) AS unarrange_workday,
ROUND(IFNULL(base_info.unarrange_workday / NULLIF(base_info.total_workday, 0), 0), 4) AS unarrange_ratio,
ROUND(IFNULL(base_info.unarrange_workday / NULLIF(base_info.actual_station_num, 0), 0), 3) AS unarrange_avg,
-- 分包用工
IFNULL(base_data.subcontract_workday, 0) AS subcontract_workday
IFNULL(base_info.subcontract_workday, 0) AS subcontract_workday,
-- 动态类型数据(左关联动态类型聚合结果)
IFNULL(type_data.type_str, '') AS type_str
FROM (
-- 内层计算所有基础字段所有数值结果做IFNULL兜底
-- 子查询1运检站基础信息+未安排+分包用工(逻辑优化,统一参数)
SELECT
ista_inner.inspection_station_id,
ista_inner.inspection_station_name,
-- 1. 人员基础数据null转0
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.is_active = '1'), 0) AS compile_num,
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0) AS secondment_num,
-- 实际在站人数(减法结果兜底)
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
@ -305,8 +238,15 @@
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0) AS actual_station_num,
-- 总工日乘法结果兜底避免null
-- 总工日(先计算当月天数,避免重复查询)
IFNULL(
(SELECT DATEDIFF(
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
) + 1),
0
) AS month_days,
-- 总工日=当月天数*实际在站人数
IFNULL(
(SELECT DATEDIFF(
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
@ -325,611 +265,173 @@
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0)
), 0) AS total_workday,
-- ======================================
-- 类型1休假所有子查询结果IFNULL兜底
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) AS rest_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) AS rest_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) AS rest_workday,
-- ======================================
-- 类型2培训同休假逻辑全量IFNULL兜底
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) AS train_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) AS train_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) AS train_workday,
-- ======================================
-- 类型3运行
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) AS run_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) AS run_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) AS run_workday,
-- ======================================
-- 类型4运行视频
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) AS run_video_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) AS run_video_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) AS run_video_workday,
-- ======================================
-- 类型5检修
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) AS maintain_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) AS maintain_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) AS maintain_workday,
-- ======================================
-- 类型6值班
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) AS duty_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) AS duty_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) AS duty_workday,
-- ======================================
-- 类型7抢修
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) AS repair_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) AS repair_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) AS repair_workday,
-- ======================================
-- 类型8学习
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0) AS study_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0) AS study_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0) AS study_workday,
-- ======================================
-- 类型9其他
-- ======================================
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0'), 0) AS other_total_days,
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0'), 0) AS other_person_count,
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0'), 0) AS other_workday,
-- ======================================
-- 类型10未安排核心修改移除“其他”类型工日的扣除
-- ======================================
-- 未安排(核心:扣除所有有效类型工日,不硬编码类型)
IFNULL(
IFNULL(
(SELECT DATEDIFF(
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
) + 1),
0
) *
IFNULL((
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.is_active = '1'), 0) -
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0)
), 0) -
IFNULL((
-- 休假工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0'), 0) +
-- 培训工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0'), 0) +
-- 运行工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0'), 0) +
-- 运行(视频)工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0'), 0) +
-- 检修工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0'), 0) +
-- 值班工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0'), 0) +
-- 抢修工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0'), 0) +
-- 学习工时
IFNULL((SELECT IFNULL(SUM(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0) *
IFNULL((SELECT IFNULL(SUM(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0'), 0)
-- 移除:其他工时的累加(核心修改点)
), 0), 0) AS unarrange_workday,
-- ======================================
-- 类型11分包用工
-- ======================================
(
-- 总工日
IFNULL(
(SELECT DATEDIFF(
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
) + 1),
0
) *
IFNULL((
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.is_active = '1'), 0) -
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0)
), 0) -
-- 扣除所有有效业务类型的总工日
IFNULL((
SELECT IFNULL(SUM(
IFNULL(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1, 0) *
IFNULL(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END,
0
)
), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.category = '0'
AND pm.plan_major_name != '分包用工'
), 0)
), 0) AS unarrange_workday,
-- 分包用工
IFNULL((SELECT IFNULL(SUM(tmp.plan_skilled_worker_day + tmp.plan_auxiliary_worker_day), 0)
FROM tb_monthly_plan tmp
WHERE tmp.inspection_station_id = ista_inner.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}), 0) AS subcontract_workday
FROM tb_inspection_station ista_inner
WHERE ista_inner.category = '0'
AND ista_inner.is_active = '1'
) AS base_data
ORDER BY base_data.inspection_station_name ASC;
) AS base_info
-- 左关联动态类型聚合结果
LEFT JOIN (
-- 子查询2动态类型聚合按运检站分组拼接字符串
SELECT
tg.inspection_station_id,
-- 聚合多个类型为字符串,|分隔类型,,分隔字段
GROUP_CONCAT(
CONCAT(
REPLACE(tg.plan_major_name, ',', ''), -- 转义逗号,避免分隔符冲突
',', tg.total_days,
',', tg.person_count,
',', tg.workday,
',', bi.total_workday, -- 关联基础信息获取总工日
',', bi.actual_station_num -- 关联基础信息获取实际在站人数
)
SEPARATOR '|' -- 类型之间的分隔符
) AS type_str
FROM (
-- 子查询2.1:按运检站+专业分组,汇总单个类型数据
SELECT
tmp.inspection_station_id,
pm.plan_major_name,
-- 总天数(汇总单个计划的天数)
SUM(IFNULL(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1, 0)) AS total_days,
-- 总人数(汇总单个计划的参与人数)
SUM(
IFNULL(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END,
0
)
) AS person_count,
-- 总工日(汇总单个计划的工日)
SUM(
IFNULL(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1, 0) *
IFNULL(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END,
0
)
) AS workday
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
WHERE tmp.monthly_plan = #{monthlyPlan}
AND pm.category = '0'
AND pm.plan_major_name != '分包用工'
-- 过滤工日为0的无效数据
AND IFNULL(
IFNULL(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1, 0) *
IFNULL(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END,
0
), 0) > 0
GROUP BY tmp.inspection_station_id, pm.plan_major_name
HAVING SUM(
IFNULL(DATEDIFF(tmp.planned_end_time, tmp.planned_start_time) + 1, 0) *
IFNULL(
CASE
WHEN tmp.plan_personnel IS NULL OR tmp.plan_personnel = '' THEN 0
ELSE LENGTH(tmp.plan_personnel) - LENGTH(REPLACE(tmp.plan_personnel, ',', '')) + 1
END,
0
)
) > 0
) AS tg
-- 关联基础信息表,获取总工日和实际在站人数
INNER JOIN (
-- 复用基础信息查询,避免重复计算
SELECT
ista_inner.inspection_station_id,
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.is_active = '1'), 0) -
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0) AS actual_station_num,
IFNULL(
(SELECT DATEDIFF(
LAST_DAY(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d')),
DATE_FORMAT(STR_TO_DATE(CONCAT(#{monthlyPlan}, '-01'), '%Y-%m-%d'), '%Y-%m-01')
) + 1),
0
) *
IFNULL((
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.is_active = '1'), 0) -
IFNULL((SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista_inner.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1'), 0)
), 0) AS total_workday
FROM tb_inspection_station ista_inner
WHERE ista_inner.category = '0'
AND ista_inner.is_active = '1'
) AS bi ON tg.inspection_station_id = bi.inspection_station_id
-- 按运检站分组,聚合类型字符串
GROUP BY tg.inspection_station_id
) AS type_data ON base_info.inspection_station_id = type_data.inspection_station_id
-- 按运检站名称排序
ORDER BY base_info.inspection_station_name ASC;
]]>
</select>
<select id="getMonthlyPlanById" resultType="com.bonus.digital.dao.MonthlyPlanVo">
select tmp.monthly_plan_id,
tmp.monthly_plan,