月计划整体汇总导出接口

月计划工作量导出接口
This commit is contained in:
LHD_HY 2025-12-19 17:38:19 +08:00
parent 1f19a6d852
commit 8b3dcf419b
14 changed files with 1372 additions and 4 deletions

View File

@ -0,0 +1,232 @@
package com.bonus.business.controller.tool;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.bonus.digital.dao.ResourceSummaryExcelVo;
import com.bonus.digital.dao.ResourceSummaryVo;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 月计划资源统计分析导出工具类补全TotalDays/PersonCount兼容新增运行视频/值班
*/
public class ResourceSummaryExcelExporter {
public static void export(HttpServletResponse response,
List<ResourceSummaryVo> dataList,
String month) throws IOException {
// 1. 拆分运检站列表汇总行最后一条是汇总
List<ResourceSummaryVo> stationList = new ArrayList<>();
ResourceSummaryVo summaryVo = null;
if (!dataList.isEmpty()) {
summaryVo = dataList.get(dataList.size() - 1);
if (dataList.size() > 1) {
stationList = dataList.subList(0, dataList.size() - 1);
}
}
// 2. 按运检站分组
Map<String, ResourceSummaryVo> stationMap = stationList.stream()
.collect(Collectors.toMap(ResourceSummaryVo::getInspectionStationName, vo -> vo));
// 3. 构造导出数据列表
List<Object> exportList = new ArrayList<>();
// 3.1 运检站表格
for (ResourceSummaryVo stationData : stationMap.values()) {
exportList.addAll(buildStationRows(stationData));
exportList.add(new ResourceSummaryExcelVo()); // 空行分隔
}
// 3.2 汇总表最后添加
if (summaryVo != null) {
exportList.addAll(buildStationRows(summaryVo));
}
// 4. 响应头设置
String sheetName = month + "月资源统计分析表";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(sheetName, "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 5. EasyExcel导出
EasyExcel.write(response.getOutputStream(), ResourceSummaryExcelVo.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new MergeStrategy(stationMap.size() + (summaryVo != null ? 1 : 0)))
.sheet(sheetName)
.doWrite(exportList);
}
/**
* 构建单个运检站/汇总的表格行保留TotalDays/PersonCount兼容新增运行视频/值班
*/
private static List<ResourceSummaryExcelVo> buildStationRows(ResourceSummaryVo data) {
List<ResourceSummaryExcelVo> rows = new ArrayList<>();
// 1. 标题行
ResourceSummaryExcelVo titleVo = new ResourceSummaryExcelVo();
titleVo.setInspectionStationName(data.getInspectionStationName());
rows.add(titleVo);
// 2. 统计表头行F列备注
ResourceSummaryExcelVo statHeaderVo = new ResourceSummaryExcelVo();
statHeaderVo.setRemark("备注");
rows.add(statHeaderVo);
// 3. 统计数值行
ResourceSummaryExcelVo statValueVo = new ResourceSummaryExcelVo();
statValueVo.setCompileNum(data.getCompileNum());
statValueVo.setSecondmentNum(data.getSecondmentNum());
statValueVo.setActualStationNum(data.getActualStationNum());
rows.add(statValueVo);
// 4. 子表头行
ResourceSummaryExcelVo subHeaderVo = new ResourceSummaryExcelVo();
rows.add(subHeaderVo);
// 5. 类型行按表格顺序补全所有类型
rows.add(buildTypeVo("休假", data.getRestWorkday(), data.getRestRatio(), data.getRestAvg(), null));
rows.add(buildTypeVo("培训", data.getTrainWorkday(), data.getTrainRatio(), data.getTrainAvg(), null));
rows.add(buildTypeVo("运行", data.getRunWorkday(), data.getRunRatio(), data.getRunAvg(), null));
rows.add(buildTypeVo("运行(视频)", data.getRunVideoWorkday(), data.getRunVideoRatio(), data.getRunVideoAvg(), null));
rows.add(buildTypeVo("检修", data.getMaintainWorkday(), data.getMaintainRatio(), data.getMaintainAvg(), null));
rows.add(buildTypeVo("值班", data.getDutyWorkday(), data.getDutyRatio(), data.getDutyAvg(), null));
rows.add(buildTypeVo("抢修", data.getRepairWorkday(), data.getRepairRatio(), data.getRepairAvg(), null));
rows.add(buildTypeVo("学习", data.getStudyWorkday(), data.getStudyRatio(), data.getStudyAvg(), null));
// 其他类型F列固定写长期支援不用统计
rows.add(buildTypeVo("其他", data.getOtherWorkday(), data.getOtherRatio(), data.getOtherAvg(), "长期支援不用统计"));
rows.add(buildTypeVo("未安排", data.getUnarrangeWorkday(), data.getUnarrangeRatio(), data.getUnarrangeAvg(), null));
// 6. 分包用工行
ResourceSummaryExcelVo subcontractVo = new ResourceSummaryExcelVo();
subcontractVo.setType("分包用工");
subcontractVo.setSubcontractWorkday(data.getSubcontractWorkday());
rows.add(subcontractVo);
return rows;
}
/**
* 构建单个类型Vo含备注兼容原有字段
*/
private static ResourceSummaryExcelVo buildTypeVo(String type, Integer workday, BigDecimal ratio, BigDecimal avg, String remark) {
ResourceSummaryExcelVo vo = new ResourceSummaryExcelVo();
vo.setType(type);
vo.setWorkday(workday);
vo.setRatio(ratio);
vo.setAvg(avg);
vo.setRemark(remark);
return vo;
}
/**
* 自定义合并策略适配汇总+备注+新增类型
*/
static class MergeStrategy extends AbstractMergeStrategy {
private final int totalStationCount; // 运检站+汇总总数
private final int stationRowSize = 14; // 新增类型后每个表格占用14行原13行+运行视频
public MergeStrategy(int totalStationCount) {
this.totalStationCount = totalStationCount;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
int currentRow = cell.getRowIndex();
int currentCol = cell.getColumnIndex();
for (int i = 0; i < totalStationCount; i++) {
int startRow = i * stationRowSize;
int endRow = startRow + 12; // 调整合并行范围
// 1. A列合并运检站/汇总
if (currentRow == startRow && currentCol == 0) {
CellRangeAddress merge = new CellRangeAddress(startRow, endRow, 0, 0);
sheet.addMergedRegion(merge);
setTitleStyle(cell, sheet.getWorkbook());
}
// 2. 资源统计分析标题合并B1-D1
if (currentRow == startRow + 1 && currentCol == 1) {
CellRangeAddress titleMerge = new CellRangeAddress(startRow + 1, startRow + 1, 1, 3);
sheet.addMergedRegion(titleMerge);
cell.setCellValue("资源统计分析");
setTitleStyle(cell, sheet.getWorkbook());
}
// 3. 统计表头填充
if (currentRow == startRow + 2) {
if (currentCol == 1) cell.setCellValue("编制人数");
else if (currentCol == 2) cell.setCellValue("长期借调、支援人数");
else if (currentCol == 3) cell.setCellValue("实际在站人数");
setHeaderStyle(cell, sheet.getWorkbook());
}
// 4. 子表头填充
if (currentRow == startRow + 4) {
if (currentCol == 1) cell.setCellValue("类型");
else if (currentCol == 2) cell.setCellValue("工日");
else if (currentCol == 3) cell.setCellValue("比值");
else if (currentCol == 4) cell.setCellValue("人均");
setHeaderStyle(cell, sheet.getWorkbook());
}
// 5. 分包用工合并
if (currentRow == startRow + 13 && currentCol == 2) {
CellRangeAddress subMerge = new CellRangeAddress(startRow + 13, startRow + 13, 2, 4);
sheet.addMergedRegion(subMerge);
setTotalStyle(cell, sheet.getWorkbook());
}
// 6. 汇总表A列合并第四行到第十四行
if ("汇总".equals(sheet.getRow(startRow).getCell(0).getStringCellValue())
&& currentRow == startRow + 3 && currentCol == 0) {
CellRangeAddress summaryMerge = new CellRangeAddress(startRow + 3, startRow + 13, 0, 0);
sheet.addMergedRegion(summaryMerge);
setTitleStyle(cell, sheet.getWorkbook());
}
}
}
// 样式方法复用原逻辑
private void setTitleStyle(Cell cell, Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(style);
}
private void setHeaderStyle(Cell cell, Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(style);
}
private void setTotalStyle(Cell cell, Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(style);
}
}
}

View File

@ -0,0 +1,195 @@
package com.bonus.business.controller.tool;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.bonus.digital.dao.CarUseSummaryExcelVo;
import com.bonus.digital.dao.CarUseTotalExcelVo;
import com.bonus.digital.dao.WorkloadSummaryExcelVo;
import com.bonus.digital.dao.WorkloadTotalExcelVo;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
/**
* 工作量+车辆清单合并导出工具类匹配模板左右分栏+合并单元格+自动求和
* @author 马三炮
* @date 2025/12/19
*/
public class WorkloadAndCarSummaryExcelExporter {
/**
* 导出合并Excel左侧工作量清单+右侧车辆清单
* @param response 响应流
* @param workloadData 工作量数据列表
* @param carData 车辆数据列表
* @param month 计划月份2025-11 11月份
* @throws IOException 导出异常
*/
public static void exportMergeSummary(HttpServletResponse response,
List<WorkloadSummaryExcelVo> workloadData,
List<CarUseSummaryExcelVo> carData,
String month) throws IOException {
// 1. 计算工作量总计合计列求和
int workloadTotalAmount = workloadData.stream()
.mapToInt(WorkloadSummaryExcelVo::getTotalAmount)
.sum();
// 2. 计算车辆总计管理/分包用车天数求和
int carTotalManage = carData.stream()
.mapToInt(CarUseSummaryExcelVo::getTotalManageCarDays)
.sum();
int carTotalSub = carData.stream()
.mapToInt(CarUseSummaryExcelVo::getTotalSubCarDays)
.sum();
// 3. 构造最终导出列表工作量数据+工作量合计+车辆数据+车辆合计
List<Object> exportList = new ArrayList<>();
// 3.1 工作量数据行
exportList.addAll(workloadData);
// 3.2 工作量合计行
WorkloadTotalExcelVo workloadTotalVo = new WorkloadTotalExcelVo();
workloadTotalVo.setTotalLabel("合计");
workloadTotalVo.setTotalAmountSum(workloadTotalAmount);
exportList.add(workloadTotalVo);
// 3.3 车辆数据行补空行对齐行数
int emptyRowCount = workloadData.size() + 1 - carData.size();
for (int i = 0; i < emptyRowCount; i++) {
exportList.add(new CarUseSummaryExcelVo()); // 空行占位
}
exportList.addAll(carData);
// 3.4 车辆合计行
CarUseTotalExcelVo carTotalVo = new CarUseTotalExcelVo();
carTotalVo.setTotalLabel("合计");
carTotalVo.setTotalManageCarDaysSum(carTotalManage);
carTotalVo.setTotalSubCarDaysSum(carTotalSub);
exportList.add(carTotalVo);
// 4. 设置响应头下载Excel
String sheetName = month + "月份工作量及用车清单";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(sheetName, "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 5. EasyExcel核心导出自定义合并策略
EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 列宽自适应
.registerWriteHandler(new MergeStrategy(workloadData.size(), carData.size())) // 合并策略
.head(WorkloadSummaryExcelVo.class) // 主表头工作量
.includeColumnFiledNames(getAllColumnFields()) // 包含所有列工作量+车辆
.sheet(sheetName)
.doWrite(exportList);
}
/**
* 获取所有列的字段名工作量+车辆
*/
private static List<String> getAllColumnFields() {
List<String> fields = new ArrayList<>();
// 工作量列字段
fields.add("serialNumber");
fields.add("inspectionStationName");
fields.add("workloadCategoryName");
fields.add("totalWorkload");
fields.add("unitPrice");
fields.add("totalAmount");
// 车辆列字段从H列开始
fields.add("serialNumber"); // 车辆序号H列
fields.add("inspectionStationName"); // 车辆运检站I列
fields.add("totalManageCarDays"); // 管理用车天数J列
fields.add("totalSubCarDays"); // 分包用车天数K列
return fields;
}
/**
* 自定义合并策略同时处理工作量+车辆清单的合并
*/
static class MergeStrategy extends AbstractMergeStrategy {
private final int workloadDataRowCount; // 工作量数据行数
private final int carDataRowCount; // 车辆数据行数
public MergeStrategy(int workloadDataRowCount, int carDataRowCount) {
this.workloadDataRowCount = workloadDataRowCount;
this.carDataRowCount = carDataRowCount;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
int currentRow = cell.getRowIndex();
int currentCol = cell.getColumnIndex();
// ========== 左侧工作量清单合并逻辑 ==========
// 1. 工作量标题行A1-F1
if (currentRow == 0 && currentCol == 0) {
CellRangeAddress workloadTitle = new CellRangeAddress(0, 0, 0, 5);
sheet.addMergedRegion(workloadTitle);
// 标题样式+内容
Row titleRow = sheet.getRow(0);
Cell workloadCell = titleRow.getCell(0);
workloadCell.setCellValue(sheet.getSheetName().replace("及用车", "") + "工作量清单");
setTitleStyle(workloadCell, sheet.getWorkbook());
}
// 2. 工作量合计行A[合计行]-E[合计行]
int workloadTotalRow = 1 + workloadDataRowCount;
if (currentRow == workloadTotalRow && currentCol == 0) {
CellRangeAddress workloadTotal = new CellRangeAddress(workloadTotalRow, workloadTotalRow, 0, 4);
sheet.addMergedRegion(workloadTotal);
// 合计行样式
setTotalStyle(sheet.getRow(currentRow).getCell(0), sheet.getWorkbook());
}
// ========== 右侧车辆清单合并逻辑 ==========
// 1. 车辆标题行H1-K1
if (currentRow == 0 && currentCol == 7) {
CellRangeAddress carTitle = new CellRangeAddress(0, 0, 7, 10);
sheet.addMergedRegion(carTitle);
// 标题样式+内容
Row titleRow = sheet.getRow(0);
Cell carCell = titleRow.createCell(7); // 手动创建H1单元格
carCell.setCellValue(sheet.getSheetName().replace("工作量及", "") + "用车清单");
setTitleStyle(carCell, sheet.getWorkbook());
}
// 2. 车辆合计行H[合计行]-J[合计行]
int carTotalRow = 1 + carDataRowCount;
if (currentRow == carTotalRow && currentCol == 7) {
CellRangeAddress carTotal = new CellRangeAddress(carTotalRow, carTotalRow, 7, 9);
sheet.addMergedRegion(carTotal);
// 合计行样式
setTotalStyle(sheet.getRow(currentRow).getCell(7), sheet.getWorkbook());
}
}
/**
* 设置标题样式加粗+居中
*/
private void setTitleStyle(Cell cell, Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(style);
}
/**
* 设置合计行样式加粗+居中
*/
private void setTotalStyle(Cell cell, Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(style);
}
}
}

View File

@ -1,13 +1,14 @@
package com.bonus.digital.controller;
import com.bonus.business.controller.tool.MonthPlanExcelExporter;
import com.bonus.business.controller.tool.ResourceSummaryExcelExporter;
import com.bonus.business.controller.tool.WorkloadAndCarSummaryExcelExporter;
import com.bonus.common.annotation.Log;
import com.bonus.common.core.controller.BaseController;
import com.bonus.common.core.domain.AjaxResult;
import com.bonus.common.core.page.TableDataInfo;
import com.bonus.common.enums.BusinessType;
import com.bonus.digital.dao.MonthlyPlanVo;
import com.bonus.digital.dao.ExportMonthPlanPersonVo;
import com.bonus.digital.dao.*;
import com.bonus.digital.service.MonthlyPlanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
@ -120,4 +122,39 @@ public class MonthlyPlanController extends BaseController {
List<ExportMonthPlanPersonVo> list = monthlyPlanService.exportMonthlyPlanPerson(monthlyPlanVo);
MonthPlanExcelExporter.exportToExcel(response, list, 2025, 10, "10月运检人员安排");
}
@Log(title = "导出工作量汇总表", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('monthly:plan:export')") // 复用原有导出权限无需新增
@PostMapping("/exportWorkloadSummary")
public void exportWorkloadSummary(HttpServletResponse response,
@RequestBody MonthlyPlanVo monthlyPlanVo) throws IOException {
// 1. 获取工作量导出数据原有逻辑保留
List<WorkloadSummaryExcelVo> workloadList = monthlyPlanService.exportWorkloadSummary(monthlyPlanVo);
// 2. 新增获取车辆导出数据复用相同筛选条件月份/运检站/专业等
List<CarUseSummaryExcelVo> carList = monthlyPlanService.exportCarUseSummary(monthlyPlanVo);
// 3. 生成sheet名称调整为包含车辆的名称
String month = monthlyPlanVo.getMonthlyPlan(); // 格式如2025-11
String sheetName;
if (month == null || month.isEmpty()) {
sheetName = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月")) + "工作量及用车清单";
} else {
sheetName = month.replace("-", "") + "月工作量及用车清单";
}
// 4. 调用合并导出工具类替换原有仅工作量的导出方法
WorkloadAndCarSummaryExcelExporter.exportMergeSummary(response, workloadList, carList, sheetName);
}
@Log(title = "导出月计划资源汇总表", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('monthly:plan:export')")
@PostMapping("/exportResourceSummary")
public void exportResourceSummary(HttpServletResponse response,
@RequestBody MonthlyPlanVo monthlyPlanVo) throws IOException {
// 1. 查询汇总数据
List<ResourceSummaryVo> dataList = monthlyPlanService.exportResourceSummary(monthlyPlanVo);
// 2. 调用工具类导出
ResourceSummaryExcelExporter.export(response, dataList, monthlyPlanVo.getMonthlyPlan());
}
}

View File

@ -0,0 +1,28 @@
package com.bonus.digital.dao;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
/**
* 车辆使用清单导出Vo数据行
* 匹配模板H列(序号)I列(运检站)J列(管理用车天数)K列(分包用车天数)
*/
@Data
public class CarUseSummaryExcelVo {
@ExcelProperty(value = "序号", index = 7) // H列索引7
@ColumnWidth(8)
private Integer serialNumber; // 序号H列
@ExcelProperty(value = "运检站", index = 8) // I列索引8
@ColumnWidth(15)
private String inspectionStationName; // 运检站I列
@ExcelProperty(value = "管理用车天数", index = 9) // J列索引9
@ColumnWidth(20)
private Integer totalManageCarDays; // 管理用车天数J列
@ExcelProperty(value = "分包用车天数", index = 10) // K列索引10
@ColumnWidth(20)
private Integer totalSubCarDays; // 分包用车天数K列
}

View File

@ -0,0 +1,26 @@
package com.bonus.digital.dao;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 车辆使用清单合计行Vo表尾合计
* 匹配模板H列-K列合并逻辑
*/
@Data
public class CarUseTotalExcelVo {
@ExcelProperty(value = "合计", index = 7) // H列合并H-J列显示合计
private String totalLabel;
@ExcelProperty(value = "", index = 8) // I列占位
private String empty1;
@ExcelProperty(value = "", index = 9) // J列占位
private String empty2;
@ExcelProperty(value = "", index = 10) // K列总计分包用车天数
private Integer totalSubCarDaysSum;
// 管理用车天数总计用于Excel合计行计算
private Integer totalManageCarDaysSum;
}

View File

@ -137,4 +137,29 @@ public class MonthlyPlanVo {
*/
private List<WorkloadVo> workloadList;
/**
* 单价
*/
private Integer unitPrice;
/**
* 工作量类别
*/
private String workloadCategoryName;
/**
* 总工作量
*/
private Integer totalWorkload;
/**
* 合计金额
*/
private Integer totalAmount;
private Integer totalManageCarDays;
private Integer totalSubCarDays;
}

View File

@ -0,0 +1,47 @@
package com.bonus.digital.dao;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资源统计分析导出专用VO新增F列备注
* 列索引对应A(0)B(1)C(2)D(3)E(4)F(5)
*/
@Data
public class ResourceSummaryExcelVo {
// A列运检站名称/汇总合并单元格
@ExcelProperty(value = "运检站/汇总", index = 0)
@ColumnWidth(15)
private String inspectionStationName;
// B列编制人数/类型
@ExcelProperty(value = "编制人数/类型", index = 1)
@ColumnWidth(18)
private Integer compileNum;
private String type;
// C列长期借调支援人数/工日
@ExcelProperty(value = "长期借调、支援人数/工日", index = 2)
@ColumnWidth(18)
private Integer secondmentNum;
private Integer workday;
// D列实际在站人数/比值
@ExcelProperty(value = "实际在站人数/比值", index = 3)
@ColumnWidth(12)
private Integer actualStationNum;
private BigDecimal ratio;
// E列人均/分包用工
@ExcelProperty(value = "人均/分包用工", index = 4)
@ColumnWidth(12)
private BigDecimal avg;
private Integer subcontractWorkday;
// F列备注
@ExcelProperty(value = "备注", index = 5)
@ColumnWidth(15)
private String remark;
}

View File

@ -0,0 +1,88 @@
package com.bonus.digital.dao;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class ResourceSummaryVo {
// 运检站基础信息
private String inspectionStationId;
private String inspectionStationName;
// 人员基础数据
private Integer compileNum; // 编制人数
private Integer secondmentNum; // 借调人数
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;
}

View File

@ -0,0 +1,32 @@
package com.bonus.digital.dao;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
@Data
public class WorkloadSummaryExcelVo {
@ExcelProperty(value = "序号", index = 0)
@ColumnWidth(8)
private Integer serialNumber;
@ExcelProperty(value = "运检站", index = 1)
@ColumnWidth(15)
private String inspectionStationName;
@ExcelProperty(value = "工作量类型", index = 2)
@ColumnWidth(18)
private String workloadCategoryName;
@ExcelProperty(value = "工作量", index = 3)
@ColumnWidth(10)
private Integer totalWorkload;
@ExcelProperty(value = "单价", index = 4)
@ColumnWidth(10)
private Integer unitPrice;
@ExcelProperty(value = "合计", index = 5)
@ColumnWidth(12)
private Integer totalAmount;
}

View File

@ -0,0 +1,25 @@
package com.bonus.digital.dao;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class WorkloadTotalExcelVo {
@ExcelProperty(value = "合计", index = 0)
private String totalLabel;
@ExcelProperty(value = "", index = 1)
private String empty1;
@ExcelProperty(value = "", index = 2)
private String empty2;
@ExcelProperty(value = "", index = 3)
private String empty3;
@ExcelProperty(value = "", index = 4)
private String empty4;
@ExcelProperty(value = "", index = 5)
private Integer totalAmountSum;
}

View File

@ -2,6 +2,7 @@ package com.bonus.digital.mapper;
import com.bonus.digital.dao.MonthlyPlanVo;
import com.bonus.digital.dao.PersonnelArrangementVo;
import com.bonus.digital.dao.ResourceSummaryVo;
import com.bonus.digital.dao.WorkloadVo;
import java.util.List;
@ -12,6 +13,21 @@ public interface MonthlyPlanMapper {
*/
List<MonthlyPlanVo> getPlanMajorList(MonthlyPlanVo monthlyPlanVo);
/**
* 月计划总工作量汇总
*/
List<MonthlyPlanVo> getWorkloadSummary(MonthlyPlanVo monthlyPlanVo);
/**
* 用车总量汇总
*/
List<MonthlyPlanVo> getCarUseSummary(MonthlyPlanVo monthlyPlanVo);
/**
* 月计划汇总
*/
List<ResourceSummaryVo> getResourceSummary(MonthlyPlanVo monthlyPlanVo);
/**
* 人员列表
*/

View File

@ -1,7 +1,6 @@
package com.bonus.digital.service;
import com.bonus.digital.dao.MonthlyPlanVo;
import com.bonus.digital.dao.ExportMonthPlanPersonVo;
import com.bonus.digital.dao.*;
import java.util.List;
@ -27,4 +26,11 @@ public interface MonthlyPlanService {
int updateMonthlyPlan(MonthlyPlanVo monthlyPlanVo);
List<ExportMonthPlanPersonVo> exportMonthlyPlanPerson(MonthlyPlanVo monthlyPlanVo);
List<WorkloadSummaryExcelVo> exportWorkloadSummary(MonthlyPlanVo monthlyPlanVo);
List<CarUseSummaryExcelVo> exportCarUseSummary(MonthlyPlanVo monthlyPlanVo);
List<ResourceSummaryVo> exportResourceSummary(MonthlyPlanVo monthlyPlanVo);
}

View File

@ -10,6 +10,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
/**
@ -138,4 +140,191 @@ public class MonthlyPlanServiceImpl implements MonthlyPlanService {
}
return plannedList;
}
/**
* 导出工作量汇总Excel
* @param monthlyPlanVo 筛选条件
* @return 导出专用Vo列表
*/
@Override
public List<WorkloadSummaryExcelVo> exportWorkloadSummary(MonthlyPlanVo monthlyPlanVo) {
// 1. 查询汇总数据
List<MonthlyPlanVo> workloadSummaryList = monthlyPlanMapper.getWorkloadSummary(monthlyPlanVo);
// 2. 转换为导出专用Vo解耦业务Vo和导出Vo
List<WorkloadSummaryExcelVo> exportList = new ArrayList<>();
int serialNum = 1; // Excel序号
for (MonthlyPlanVo vo : workloadSummaryList) {
WorkloadSummaryExcelVo exportVo = new WorkloadSummaryExcelVo();
// 映射核心字段和Excel模板列一一对应
exportVo.setSerialNumber(serialNum++); // 序号
exportVo.setInspectionStationName(vo.getInspectionStationName()); // 运检站
exportVo.setWorkloadCategoryName(vo.getWorkloadCategoryName()); // 工作量类别
exportVo.setTotalWorkload(vo.getTotalWorkload()); // 总工作量
exportVo.setUnitPrice(vo.getUnitPrice()); // 单价
exportVo.setTotalAmount(vo.getTotalAmount()); // 合计金额
exportList.add(exportVo);
}
return exportList;
}
/**
* 导出车辆使用清单Excel
* @param monthlyPlanVo 筛选条件
* @return 车辆汇总导出专用Vo列表
*/
@Override
public List<CarUseSummaryExcelVo> exportCarUseSummary(MonthlyPlanVo monthlyPlanVo) {
// 1. 调用Mapper查询车辆汇总数据
List<MonthlyPlanVo> carUseSummaryList = monthlyPlanMapper.getCarUseSummary(monthlyPlanVo);
// 2. 转换为导出专用Vo适配H列开始的列索引
List<CarUseSummaryExcelVo> exportList = new ArrayList<>();
int serialNum = 1; // Excel序号从1开始
for (MonthlyPlanVo vo : carUseSummaryList) {
CarUseSummaryExcelVo exportVo = new CarUseSummaryExcelVo();
// 映射核心字段适配H-K列索引
exportVo.setSerialNumber(serialNum++); // 序号H列index=7
exportVo.setInspectionStationName(vo.getInspectionStationName()); // 运检站I列index=8
exportVo.setTotalManageCarDays(vo.getTotalManageCarDays());
exportVo.setTotalSubCarDays(vo.getTotalSubCarDays());
exportList.add(exportVo);
}
return exportList;
}
/**
* 导出资源统计汇总数据适配Excel导出含各运检站+汇总行
* @param monthlyPlanVo 筛选条件核心monthlyPlan 统计月份
* @return 资源统计Vo列表最后一条为汇总数据
*/
@Override
public List<ResourceSummaryVo> exportResourceSummary(MonthlyPlanVo monthlyPlanVo) {
// 1. 入参用MonthlyPlanVo传递筛选条件调用Mapper查询ResourceSummaryVo统计结果
// Mapper中会从MonthlyPlanVo取monthlyPlan/inspectionStationId等筛选值
List<ResourceSummaryVo> stationResourceList = monthlyPlanMapper.getResourceSummary(monthlyPlanVo);
if (stationResourceList.isEmpty()) {
log.info("资源统计查询无数据,筛选条件:月份={}运检站ID={}",
monthlyPlanVo.getMonthlyPlan(), monthlyPlanVo.getInspectionStationId());
return Collections.emptyList();
}
// 2. 计算汇总行仅用ResourceSummaryVo全程不混用
ResourceSummaryVo summaryResourceVo = new ResourceSummaryVo();
summaryResourceVo.setInspectionStationName("汇总"); // 汇总行名称
// 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());
// 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);
finalResourceList.add(summaryResourceVo);
return finalResourceList;
}
/**
* 通用计算比值工日/总工日 ×100%保留2位小数
* @param workday 某类型工日ResourceSummaryVo字段
* @param totalWorkday 总工日ResourceSummaryVo字段
* @return 比值BigDecimal保留2位小数
*/
private BigDecimal calcRatio(Integer workday, Integer totalWorkday) {
if (totalWorkday == 0 || workday == 0) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
return BigDecimal.valueOf(workday * 100.0 / totalWorkday)
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 通用计算人均工日/实际在站人数保留1位小数
* @param workday 某类型工日ResourceSummaryVo字段
* @param actualStationNum 实际在站人数ResourceSummaryVo字段
* @return 人均BigDecimal保留1位小数
*/
private BigDecimal calcAvg(Integer workday, Integer actualStationNum) {
if (actualStationNum == 0 || workday == 0) {
return BigDecimal.ZERO.setScale(1, RoundingMode.HALF_UP);
}
return BigDecimal.valueOf(workday * 1.0 / actualStationNum)
.setScale(1, RoundingMode.HALF_UP);
}
}

View File

@ -138,4 +138,426 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from tb_monthly_plan tmp
where tmp.is_active = '1' and tmp.monthly_plan = #{monthlyPlan}
</select>
<select id="getWorkloadSummary" resultType="com.bonus.digital.dao.MonthlyPlanVo">
select
tmp.inspection_station_id,
tmp.inspection_station_name,
tw.workload_category_name,
IFNULL(tw.unit_price, 0) as unit_price,
IFNULL(SUM(tw.workload_num), 0) as total_workload,
IFNULL(SUM(tw.workload_num), 0) * IFNULL(tw.unit_price, 0) as total_amount
from tb_monthly_plan tmp
left join tb_workload tw
on tw.plan_id = tmp.monthly_plan_id
and tw.data_source = '0'
left join tb_plan_management pm
on pm.plan_management_id = tmp.plan_management_id
where tmp.is_active = '1'
<!-- 计划月份筛选 -->
<if test="monthlyPlan != null and monthlyPlan != ''">
AND tmp.monthly_plan = #{monthlyPlan}
</if>
<!-- 运检站筛选 -->
<if test="inspectionStationId != null and inspectionStationId != ''">
AND tmp.inspection_station_id = #{inspectionStationId}
</if>
<!-- 计划专业筛选 -->
<if test="planMajorId != null and planMajorId != ''">
AND tmp.plan_major_id = #{planMajorId}
</if>
<!-- 业务类型筛选 -->
<if test="businessTypeId != null and businessTypeId != ''">
AND tmp.business_type_id = #{businessTypeId}
</if>
<!-- 风险等级筛选 -->
<if test="riskLevel != null and riskLevel != ''">
AND pm.risk_level = #{riskLevel}
</if>
GROUP BY
tmp.inspection_station_id,
tmp.inspection_station_name,
tw.workload_category_name,
tw.unit_price
ORDER BY tmp.inspection_station_name ASC;
</select>
<select id="getCarUseSummary" resultType="com.bonus.digital.dao.MonthlyPlanVo">
select
tmp.inspection_station_id,
tmp.inspection_station_name,
-- 汇总管理用车天数空则补0
IFNULL(SUM(tmp.plan_car_num), 0) as total_manage_car_days,
-- 汇总分包用车天数空则补0
IFNULL(SUM(tmp.plan_sub_car_num), 0) as total_sub_car_days
from tb_monthly_plan tmp
-- 关联计划管理表
left join tb_plan_management pm
on pm.plan_management_id = tmp.plan_management_id
where tmp.is_active = '1'
<!-- 计划月份筛选 -->
<if test="monthlyPlan != null and monthlyPlan != ''">
AND tmp.monthly_plan = #{monthlyPlan}
</if>
<!-- 运检站筛选 -->
<if test="inspectionStationId != null and inspectionStationId != ''">
AND tmp.inspection_station_id = #{inspectionStationId}
</if>
<!-- 计划专业筛选 -->
<if test="planMajorId != null and planMajorId != ''">
AND tmp.plan_major_id = #{planMajorId}
</if>
<!-- 业务类型筛选 -->
<if test="businessTypeId != null and businessTypeId != ''">
AND tmp.business_type_id = #{businessTypeId}
</if>
<!-- 风险等级筛选 -->
<if test="riskLevel != null and riskLevel != ''">
AND pm.risk_level = #{riskLevel}
</if>
GROUP BY
tmp.inspection_station_id,
tmp.inspection_station_name
ORDER BY tmp.inspection_station_name ASC;
</select>
<select id="getOverallSummary" resultType="com.bonus.digital.dao.MonthlyPlanVo">
select
tis.inspection_station_id,
tis.inspection_station_name,
from tb_inspection_station tis
left join tb_monthly_plan tmp
on tis.inspection_station_id = tmp.inspection_station_id
where tis.category = '0'
-- 关联计划管理表
left join tb_plan_management pm
on pm.plan_management_id = tmp.plan_management_id
where tmp.is_active = '1'
<!-- 计划月份筛选 -->
<if test="monthlyPlan != null and monthlyPlan != ''">
AND tmp.monthly_plan = #{monthlyPlan}
</if>
<!-- 运检站筛选 -->
<if test="inspectionStationId != null and inspectionStationId != ''">
AND tmp.inspection_station_id = #{inspectionStationId}
</if>
<!-- 计划专业筛选 -->
<if test="planMajorId != null and planMajorId != ''">
AND tmp.plan_major_id = #{planMajorId}
</if>
<!-- 业务类型筛选 -->
<if test="businessTypeId != null and businessTypeId != ''">
AND tmp.business_type_id = #{businessTypeId}
</if>
<!-- 风险等级筛选 -->
<if test="riskLevel != null and riskLevel != ''">
AND pm.risk_level = #{riskLevel}
</if>
GROUP BY
tis.inspection_station_id,
tis.inspection_station_name
ORDER BY tmp.inspection_station_name ASC;
</select>
<select id="getResourceSummary" resultType="com.bonus.digital.dao.ResourceSummaryVo">
<![CDATA[
SELECT
-- 运检站基础信息
ista.inspection_station_id,
ista.inspection_station_name,
-- 1. 人员基础数据(编制/借调/实际在站)
(SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista.inspection_station_id
AND p.is_active = '1') AS compile_num, -- 编制人数
(SELECT COUNT(p.id)
FROM tb_personnel p
WHERE p.inspection_station_id = ista.inspection_station_id
AND p.long_term_secondment = '1'
AND p.is_active = '1') AS secondment_num, -- 长期借调人数
-- 实际在站人数 = 编制 - 借调
compile_num - secondment_num AS actual_station_num,
-- 2. 总工日 = 月份天数 × 实际在站人数
(SELECT DATEDIFF(LAST_DAY(STR_TO_DATE(#{monthlyPlan}, '%Y-%m')),
DATE_FORMAT(STR_TO_DATE(#{monthlyPlan}, '%Y-%m'), '%Y-%m-01')) + 1)
* actual_station_num AS total_workday,
-- ======================================
-- 类型1休假
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0') AS rest_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '休假'
AND pm.category = '0') AS rest_person_count,
rest_total_days * rest_person_count AS rest_workday,
ROUND(rest_workday / total_workday, 2) AS rest_ratio,
ROUND(rest_workday / actual_station_num, 1) AS rest_avg,
-- ======================================
-- 类型2培训
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0') AS train_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '培训'
AND pm.category = '0') AS train_person_count,
train_total_days * train_person_count AS train_workday,
ROUND(train_workday / total_workday, 2) AS train_ratio,
ROUND(train_workday / actual_station_num, 1) AS train_avg,
-- ======================================
-- 类型3运行
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0') AS run_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行'
AND pm.category = '0') AS run_person_count,
run_total_days * run_person_count AS run_workday,
ROUND(run_workday / total_workday, 2) AS run_ratio,
ROUND(run_workday / actual_station_num, 1) AS run_avg,
-- ======================================
-- 新增类型4运行视频
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0') AS run_video_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '运行(视频)'
AND pm.category = '0') AS run_video_person_count,
run_video_total_days * run_video_person_count AS run_video_workday,
ROUND(run_video_workday / total_workday, 2) AS run_video_ratio,
ROUND(run_video_workday / actual_station_num, 1) AS run_video_avg,
-- ======================================
-- 类型5检修原维护补全TotalDays/PersonCount
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0') AS maintain_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '检修'
AND pm.category = '0') AS maintain_person_count,
maintain_total_days * maintain_person_count AS maintain_workday,
ROUND(maintain_workday / total_workday, 2) AS maintain_ratio,
ROUND(maintain_workday / actual_station_num, 1) AS maintain_avg,
-- ======================================
-- 新增类型6值班
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0') AS duty_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '值班'
AND pm.category = '0') AS duty_person_count,
duty_total_days * duty_person_count AS duty_workday,
ROUND(duty_workday / total_workday, 2) AS duty_ratio,
ROUND(duty_workday / actual_station_num, 1) AS duty_avg,
-- ======================================
-- 类型7抢修
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0') AS repair_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '抢修'
AND pm.category = '0') AS repair_person_count,
repair_total_days * repair_person_count AS repair_workday,
ROUND(repair_workday / total_workday, 2) AS repair_ratio,
ROUND(repair_workday / actual_station_num, 1) AS repair_avg,
-- ======================================
-- 类型8学习
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0') AS study_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '学习'
AND pm.category = '0') AS study_person_count,
study_total_days * study_person_count AS study_workday,
ROUND(study_workday / total_workday, 2) AS study_ratio,
ROUND(study_workday / actual_station_num, 1) AS study_avg,
-- ======================================
-- 类型9其他
-- ======================================
(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.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0') AS other_total_days,
(SELECT IFNULL(COUNT(DISTINCT t.person_id), 0)
FROM tb_monthly_plan tmp
LEFT JOIN tb_plan_major pm ON tmp.plan_major_id = pm.plan_major_id
LEFT JOIN JSON_TABLE(
IF(tmp.plan_personnel IS NULL OR tmp.plan_personnel = '', '[]',
CONCAT('["', REPLACE(tmp.plan_personnel, ',', '","'), '"]')),
'$[*]' COLUMNS (person_id VARCHAR(50) PATH '$')
) t
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}
AND pm.plan_major_name = '其他'
AND pm.category = '0') AS other_person_count,
other_total_days * other_person_count AS other_workday,
ROUND(other_workday / total_workday, 2) AS other_ratio,
ROUND(other_workday / actual_station_num, 1) AS other_avg,
-- ======================================
-- 类型10未安排修正计算逻辑包含新增类型
-- ======================================
total_workday - (rest_workday + train_workday + run_workday + run_video_workday + maintain_workday + duty_workday + repair_workday + study_workday) AS unarrange_workday,
ROUND(unarrange_workday / total_workday, 2) AS unarrange_ratio,
ROUND(unarrange_workday / actual_station_num, 1) AS unarrange_avg,
-- ======================================
-- 类型11分包用工
-- ======================================
(SELECT IFNULL(SUM(tmp.plan_skilled_worker_num + tmp.plan_auxiliary_worker_num), 0)
FROM tb_monthly_plan tmp
WHERE tmp.inspection_station_id = ista.inspection_station_id
AND tmp.monthly_plan = #{monthlyPlan}) AS subcontract_workday
FROM tb_inspection_station ista
WHERE ista.category = '0' -- 只统计category为0的运检站
AND ista.is_active = '1'
ORDER BY ista.inspection_station_name ASC;
]]>
</select>
</mapper>