|
|
|
|
@ -0,0 +1,179 @@
|
|
|
|
|
package org.dromara.oa.erp.controller;
|
|
|
|
|
|
|
|
|
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
|
|
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import org.dromara.common.log.annotation.Log;
|
|
|
|
|
import org.dromara.common.log.enums.BusinessType;
|
|
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
|
|
import org.dromara.oa.erp.domain.vo.ProjectManHourReportVo;
|
|
|
|
|
import org.dromara.oa.erp.service.IErpTimesheetReportService;
|
|
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
|
|
|
import cn.idev.excel.FastExcel;
|
|
|
|
|
import cn.idev.excel.write.handler.CellWriteHandler;
|
|
|
|
|
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
|
|
|
|
import cn.idev.excel.write.metadata.holder.WriteTableHolder;
|
|
|
|
|
import cn.idev.excel.metadata.data.WriteCellData;
|
|
|
|
|
import cn.idev.excel.metadata.Head;
|
|
|
|
|
import org.apache.poi.ss.usermodel.Cell;
|
|
|
|
|
import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 工时报表
|
|
|
|
|
*
|
|
|
|
|
* @author Yangk
|
|
|
|
|
* @date 2025-12-26
|
|
|
|
|
*/
|
|
|
|
|
@Validated
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
@RestController
|
|
|
|
|
@RequestMapping("/erp/timesheetReport")
|
|
|
|
|
public class ErpTimesheetReportController {
|
|
|
|
|
|
|
|
|
|
private final IErpTimesheetReportService reportService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取项目工时统计列表
|
|
|
|
|
*/
|
|
|
|
|
@SaCheckPermission("oa:erp:timesheetReport:projectManHour:list")
|
|
|
|
|
@GetMapping("/projectManHourList")
|
|
|
|
|
public TableDataInfo<ProjectManHourReportVo> getProjectManHourList(ProjectManHourReportVo bo, String startTime,
|
|
|
|
|
String endTime) {
|
|
|
|
|
return reportService.queryProjectManHourList(bo, startTime, endTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 导出项目工时统计列表
|
|
|
|
|
*/
|
|
|
|
|
@SaCheckPermission("oa:erp:timesheetReport:projectManHour:export")
|
|
|
|
|
@Log(title = "项目工时统计报表", businessType = BusinessType.EXPORT)
|
|
|
|
|
@PostMapping("/exportProjectManHour")
|
|
|
|
|
public void exportProjectManHour(ProjectManHourReportVo bo, String startTime, String endTime,
|
|
|
|
|
HttpServletResponse response) {
|
|
|
|
|
try {
|
|
|
|
|
List<ProjectManHourReportVo> list = reportService.queryProjectManHourAll(bo, startTime, endTime);
|
|
|
|
|
|
|
|
|
|
// 处理数据:如果跨部门工时 等于 当月工时(说明没有跨部门协作),则置空
|
|
|
|
|
BigDecimal totalHoursSum = BigDecimal.ZERO;
|
|
|
|
|
if (list != null) {
|
|
|
|
|
for (ProjectManHourReportVo vo : list) {
|
|
|
|
|
// 累加总工时
|
|
|
|
|
if (vo.getTotalHours() != null) {
|
|
|
|
|
totalHoursSum = totalHoursSum.add(vo.getTotalHours());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (vo.getTotalHours() != null && vo.getCrossDeptHours() != null
|
|
|
|
|
&& vo.getTotalHours().compareTo(vo.getCrossDeptHours()) == 0) {
|
|
|
|
|
vo.setCrossDeptHours(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加“合计”行
|
|
|
|
|
ProjectManHourReportVo totalVo = new ProjectManHourReportVo();
|
|
|
|
|
totalVo.setDeptName("总计");
|
|
|
|
|
totalVo.setTotalHours(totalHoursSum);
|
|
|
|
|
list.add(totalVo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建动态表头
|
|
|
|
|
List<List<String>> heads = new ArrayList<>();
|
|
|
|
|
heads.add(Collections.singletonList("部门"));
|
|
|
|
|
heads.add(Collections.singletonList("项目经理"));
|
|
|
|
|
heads.add(Collections.singletonList("项目名称"));
|
|
|
|
|
heads.add(Collections.singletonList("项目编号"));
|
|
|
|
|
heads.add(Collections.singletonList("项目类别"));
|
|
|
|
|
|
|
|
|
|
String totalHoursTitle = "当月工时";
|
|
|
|
|
if (startTime != null && !startTime.isEmpty() && endTime != null && !endTime.isEmpty()) {
|
|
|
|
|
totalHoursTitle = totalHoursTitle + " (" + startTime + " 至 " + endTime + ")";
|
|
|
|
|
}
|
|
|
|
|
heads.add(Collections.singletonList(totalHoursTitle));
|
|
|
|
|
|
|
|
|
|
heads.add(Collections.singletonList("跨部门工时"));
|
|
|
|
|
|
|
|
|
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
|
|
response.setCharacterEncoding("utf-8");
|
|
|
|
|
String fileName = URLEncoder.encode("项目工时统计报表", "UTF-8").replaceAll("\\+", "%20");
|
|
|
|
|
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
|
|
|
|
|
|
|
|
|
|
FastExcel.write(response.getOutputStream(), ProjectManHourReportVo.class)
|
|
|
|
|
.head(heads)
|
|
|
|
|
.sheet("项目工时统计报表")
|
|
|
|
|
.registerWriteHandler(new ProjectCellMergeStrategy(list))
|
|
|
|
|
.doWrite(list);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
throw new RuntimeException("导出失败");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 自定义单元格合并策略(按项目ID合并跨部门工时列)
|
|
|
|
|
*/
|
|
|
|
|
public static class ProjectCellMergeStrategy implements CellWriteHandler {
|
|
|
|
|
private final List<ProjectManHourReportVo> list;
|
|
|
|
|
private final Map<Integer, Integer> mergeMap = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
public ProjectCellMergeStrategy(List<ProjectManHourReportVo> list) {
|
|
|
|
|
this.list = list;
|
|
|
|
|
calculateMerge();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void calculateMerge() {
|
|
|
|
|
if (list == null || list.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
for (int i = 0; i < list.size();) {
|
|
|
|
|
Long currentId = list.get(i).getProjectId();
|
|
|
|
|
int count = 1;
|
|
|
|
|
for (int j = i + 1; j < list.size(); j++) {
|
|
|
|
|
Long nextId = list.get(j).getProjectId();
|
|
|
|
|
if (currentId != null && currentId.equals(nextId)) {
|
|
|
|
|
count++;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (count > 1) {
|
|
|
|
|
mergeMap.put(i, count);
|
|
|
|
|
}
|
|
|
|
|
i += count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
|
|
|
|
|
List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
|
|
|
|
|
if (isHead || relativeRowIndex == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// 仅合并“跨部门工时”列
|
|
|
|
|
if (head.getHeadNameList().contains("跨部门工时")) {
|
|
|
|
|
if (mergeMap.containsKey(relativeRowIndex)) {
|
|
|
|
|
Integer rowspan = mergeMap.get(relativeRowIndex);
|
|
|
|
|
if (rowspan > 1) {
|
|
|
|
|
// 合并单元格: firstRow, lastRow, firstCol, lastCol
|
|
|
|
|
CellRangeAddress cellRangeAddress = new CellRangeAddress(
|
|
|
|
|
cell.getRowIndex(),
|
|
|
|
|
cell.getRowIndex() + rowspan - 1,
|
|
|
|
|
cell.getColumnIndex(),
|
|
|
|
|
cell.getColumnIndex());
|
|
|
|
|
writeSheetHolder.getSheet().addMergedRegionUnsafe(cellRangeAddress);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|