feat(erp/TimesheetInfo): 添加工时填报中的部门信息显示

- 在工时填报查询中增加部门名称字段
- 修改数据库查询语句添加部门表关联
- 更新视图对象添加部门名称属性
- 配置部门名称导出到Excel功能
dev
Yangk 3 days ago
parent 6560c1346f
commit 9dcbb9e3ee

@ -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);
}
}
}
}
}
}

@ -0,0 +1,75 @@
package org.dromara.oa.erp.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* VO
*
* @author Yangk
* @date 2025-12-26
*/
@Data
@ColumnWidth(20)
@ExcelIgnoreUnannotated
public class ProjectManHourReportVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long projectId;
/**
*
*/
@ExcelProperty(value = "部门")
private String deptName;
/**
*
*/
@ExcelProperty(value = "项目经理")
private String managerName;
/**
*
*/
@ExcelProperty(value = "项目名称")
private String projectName;
/**
*
*/
@ExcelProperty(value = "项目编号")
private String projectCode;
/**
*
*/
@ExcelProperty(value = "项目类别", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "project_category")
private String projectCategory;
/**
* /
*/
@ExcelProperty(value = "当月工时")
private BigDecimal totalHours;
/**
*
*/
@ExcelProperty(value = "跨部门工时")
private BigDecimal crossDeptHours;
}

@ -0,0 +1,29 @@
package org.dromara.oa.erp.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.oa.erp.domain.vo.ProjectManHourReportVo;
import java.util.List;
/**
* Mapper
*
* @author Yangk
* @date 2025-12-26
*/
@Mapper
public interface ErpTimesheetReportMapper {
/**
*
*
* @param bo
* @param startTime
* @param endTime
* @return
*/
List<ProjectManHourReportVo> selectProjectManHourList(@Param("bo") ProjectManHourReportVo bo,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}

@ -0,0 +1,36 @@
package org.dromara.oa.erp.service;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.erp.domain.vo.ProjectManHourReportVo;
import java.util.List;
/**
* Service
*
* @author Yangk
* @date 2025-12-26
*/
public interface IErpTimesheetReportService {
/**
*
*
* @param bo
* @param startTime
* @param endTime
* @return
*/
TableDataInfo<ProjectManHourReportVo> queryProjectManHourList(ProjectManHourReportVo bo, String startTime,
String endTime);
/**
* ()
*
* @param bo
* @param startTime
* @param endTime
* @return
*/
List<ProjectManHourReportVo> queryProjectManHourAll(ProjectManHourReportVo bo, String startTime, String endTime);
}

@ -0,0 +1,39 @@
package org.dromara.oa.erp.service.impl;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.erp.domain.vo.ProjectManHourReportVo;
import org.dromara.oa.erp.mapper.ErpTimesheetReportMapper;
import org.dromara.oa.erp.service.IErpTimesheetReportService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* Service
*
* @author Yangk
* @date 2025-12-26
*/
@RequiredArgsConstructor
@Service
public class ErpTimesheetReportServiceImpl implements IErpTimesheetReportService {
private final ErpTimesheetReportMapper reportMapper;
@Override
public TableDataInfo<ProjectManHourReportVo> queryProjectManHourList(ProjectManHourReportVo bo, String startTime,
String endTime) {
List<ProjectManHourReportVo> list = reportMapper.selectProjectManHourList(bo, startTime, endTime);
return TableDataInfo.build(list);
}
@Override
public List<ProjectManHourReportVo> queryProjectManHourAll(ProjectManHourReportVo bo, String startTime,
String endTime) {
return reportMapper.selectProjectManHourList(bo, startTime, endTime);
}
}

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.oa.erp.mapper.ErpTimesheetReportMapper">
<select id="selectProjectManHourList" resultType="org.dromara.oa.erp.domain.vo.ProjectManHourReportVo">
SELECT
t_dept.dept_name,
u.nick_name AS manager_name,
p.project_id,
p.project_name,
p.project_code,
p.project_category,
COALESCE(SUM(tp.hours), 0) AS total_hours,
<!-- 计算该项目的总工时,用于最后一列展示 -->
SUM(SUM(tp.hours)) OVER(PARTITION BY p.project_id) AS cross_dept_hours
FROM
erp_project_info p
LEFT JOIN sys_user u ON p.manager_id = u.user_id
<!-- 关联工时明细 -->
LEFT JOIN erp_timesheet_project tp ON p.project_id = tp.project_id AND tp.del_flag = '0'
<!-- 关联工时主表以获取日期和人员(从而获取填报部门) -->
LEFT JOIN erp_timesheet_info ti ON tp.timesheet_id = ti.timesheet_id AND ti.del_flag = '0'
<!-- 关联填报部门 -->
LEFT JOIN sys_dept t_dept ON ti.dept_id = t_dept.dept_id
<!-- 关联项目所属部门,用于部门筛选 -->
LEFT JOIN sys_dept p_dept ON p.dept_id = p_dept.dept_id
<where>
p.del_flag = '0'
<if test="bo.projectName != null and bo.projectName != ''">
AND p.project_name LIKE concat('%', #{bo.projectName}, '%')
</if>
<if test="bo.projectCode != null and bo.projectCode != ''">
AND p.project_code LIKE concat('%', #{bo.projectCode}, '%')
</if>
<if test="bo.projectCategory != null and bo.projectCategory != ''">
AND p.project_category = #{bo.projectCategory}
</if>
<if test="bo.deptName != null and bo.deptName != ''">
AND t_dept.dept_name LIKE concat('%', #{bo.deptName}, '%')
</if>
<!-- 日期范围筛选 -->
<if test="startTime != null and startTime != ''">
AND ti.start_time >= #{startTime}
</if>
<if test="endTime != null and endTime != ''">
AND ti.end_time &lt;= #{endTime}
</if>
<!-- 仅统计已审批的工时 (3=已审批) -->
AND ti.timesheet_status = '3'
</where>
<!-- 按项目ID 和 填报部门ID 分组 -->
GROUP BY p.project_id, ti.dept_id
<!-- 仅显示有工时的记录 -->
HAVING SUM(tp.hours) > 0
ORDER BY p.project_code, t_dept.dept_id
</select>
</mapper>
Loading…
Cancel
Save