feat(oa): 添加项目台账报表功能

- 新增项目台账报表Service接口及实现类
- 创建项目台账报表Controller提供列表查询和导出功能
- 实现项目台账报表Mapper及XML映射文件
- 定义项目台账报表VO对象支持Excel导出
- 集成权限验证和日志记录功能
- 实现分页查询和全量导出功能
dev
Yangk 4 days ago
parent e1a87a8f71
commit bda947983b

@ -0,0 +1,66 @@
package org.dromara.oa.erp.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.idev.excel.FastExcel;
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.ProjectLedgerReportVo;
import org.dromara.oa.erp.service.IProjectLedgerReportService;
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 java.net.URLEncoder;
import java.util.List;
/**
*
*
* @author Yangk
* @date 2026-03-11
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/erp/projectLedgerReport")
public class ProjectLedgerReportController {
private final IProjectLedgerReportService reportService;
/**
*
*/
@SaCheckPermission("oa:erp:projectLedgerReport:list")
@GetMapping("/list")
public TableDataInfo<ProjectLedgerReportVo> list(ProjectLedgerReportVo bo) {
return reportService.queryProjectLedgerList(bo);
}
/**
*
*/
@SaCheckPermission("oa:erp:projectLedgerReport:export")
@Log(title = "项目台账报表", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ProjectLedgerReportVo bo, HttpServletResponse response) {
try {
List<ProjectLedgerReportVo> list = reportService.queryProjectLedgerAll(bo);
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(), ProjectLedgerReportVo.class)
.sheet("项目台账报表")
.doWrite(list);
} catch (Exception e) {
throw new RuntimeException("导出失败", e);
}
}
}

@ -0,0 +1,121 @@
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 lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* VO
*
* @author Yangk
* @date 2026-03-11
*/
@Data
@ColumnWidth(18)
@ExcelIgnoreUnannotated
public class ProjectLedgerReportVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 项目ID */
private Long projectId;
/** 合同ID */
private Long contractId;
/** 项目编号 */
@ExcelProperty(value = "项目编号")
private String projectCode;
/** 客户名称 */
@ExcelProperty(value = "客户名称")
private String customerName;
/** 项目名称 */
@ExcelProperty(value = "项目名称")
@ColumnWidth(25)
private String projectName;
/** 项目经理 */
@ExcelProperty(value = "项目经理")
private String managerName;
/** 部门 */
@ExcelProperty(value = "部门")
private String deptName;
/** 项目类型 */
@ExcelProperty(value = "项目类型")
private String typeName;
/** 产品数量 */
@ExcelProperty(value = "产品数量")
private BigDecimal productAmount;
/** 项目阶段 */
private String projectPhases;
/** 项目类别 */
private String projectCategory;
/** 项目状态 */
@ExcelProperty(value = "状态", converter = org.dromara.common.excel.convert.ExcelDictConvert.class)
@org.dromara.common.excel.annotation.ExcelDictFormat(dictType = "project_status")
private String projectStatus;
/** 实际验收时间 */
@ExcelProperty(value = "实际验收时间")
private String acceptanceDate;
/** 签订时间 */
@ExcelProperty(value = "签订时间")
private String contractDate;
/** 合同额 */
@ExcelProperty(value = "合同额")
private BigDecimal contractAmount;
/** 客户经理 */
@ExcelProperty(value = "客户经理")
private String contractManagerName;
/** 付款方式 */
@ExcelProperty(value = "付款方式")
private String paymentMethod;
/** 预算 */
@ExcelProperty(value = "预算")
private BigDecimal budgetCost;
/** 预算毛利率 */
@ExcelProperty(value = "预算毛利率")
private BigDecimal budgetRate;
/** 降成本后预算 */
@ExcelProperty(value = "降成本后预算")
private BigDecimal reduceBudgetCost;
/** 降成本后预算毛利率 */
@ExcelProperty(value = "降成本后预算毛利率")
private BigDecimal reduceBudgetRate;
/** 收入(合同额/1.13 */
@ExcelProperty(value = "收入")
private BigDecimal revenue;
/** 累计工时 */
@ExcelProperty(value = "累计工时")
private BigDecimal totalHours;
/** 业务方向(查询条件用) */
private String businessDirection;
/** 请求参数 */
private java.util.Map<String, Object> params;
}

@ -0,0 +1,25 @@
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.ProjectLedgerReportVo;
import java.util.List;
/**
* Mapper
*
* @author Yangk
* @date 2026-03-11
*/
@Mapper
public interface ProjectLedgerReportMapper {
/**
*
*
* @param bo
* @return
*/
List<ProjectLedgerReportVo> selectProjectLedgerList(@Param("bo") ProjectLedgerReportVo bo);
}

@ -0,0 +1,25 @@
package org.dromara.oa.erp.service;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.erp.domain.vo.ProjectLedgerReportVo;
import java.util.List;
/**
* Service
*
* @author Yangk
* @date 2026-03-11
*/
public interface IProjectLedgerReportService {
/**
*
*/
TableDataInfo<ProjectLedgerReportVo> queryProjectLedgerList(ProjectLedgerReportVo bo);
/**
*
*/
List<ProjectLedgerReportVo> queryProjectLedgerAll(ProjectLedgerReportVo bo);
}

@ -0,0 +1,34 @@
package org.dromara.oa.erp.service.impl;
import lombok.RequiredArgsConstructor;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.oa.erp.domain.vo.ProjectLedgerReportVo;
import org.dromara.oa.erp.mapper.ProjectLedgerReportMapper;
import org.dromara.oa.erp.service.IProjectLedgerReportService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Service
*
* @author Yangk
* @date 2026-03-11
*/
@RequiredArgsConstructor
@Service
public class ProjectLedgerReportServiceImpl implements IProjectLedgerReportService {
private final ProjectLedgerReportMapper reportMapper;
@Override
public TableDataInfo<ProjectLedgerReportVo> queryProjectLedgerList(ProjectLedgerReportVo bo) {
List<ProjectLedgerReportVo> list = reportMapper.selectProjectLedgerList(bo);
return TableDataInfo.build(list);
}
@Override
public List<ProjectLedgerReportVo> queryProjectLedgerAll(ProjectLedgerReportVo bo) {
return reportMapper.selectProjectLedgerList(bo);
}
}

@ -0,0 +1,95 @@
<?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.ProjectLedgerReportMapper">
<select id="selectProjectLedgerList" resultType="org.dromara.oa.erp.domain.vo.ProjectLedgerReportVo">
SELECT
p.project_id,
p.project_code,
p.project_name,
p.project_category,
p.project_status,
p.payment_method,
p.contract_id,
u1.nick_name AS manager_name,
d.dept_name,
CONCAT(t2.type_name, '/', t1.type_name) AS type_name,
<!-- 合同信息 -->
c.contract_date,
c.total_price AS contract_amount,
cust.customer_name,
u2.nick_name AS contract_manager_name,
<!-- 预算信息 -->
b.budget_cost,
b.budget_rate,
b.reduce_budget_cost,
b.reduce_budget_rate,
CASE WHEN b.contract_amount IS NOT NULL THEN ROUND(b.contract_amount / 1.13, 2) ELSE NULL END AS revenue,
<!-- 验收信息 -->
acc.acceptance_date,
<!-- 累计工时 -->
hours_sub.total_hours,
<!-- 产品数量 -->
mat_sub.product_amount
FROM
erp_project_info p
LEFT JOIN erp_project_type t1 ON t1.project_type_id = p.project_type_id
LEFT JOIN erp_project_type t2 ON t1.parent_id = t2.project_type_id
LEFT JOIN sys_dept d ON d.dept_id = p.dept_id
LEFT JOIN sys_user u1 ON u1.user_id = p.manager_id
<!-- 合同 + 客户 + 客户经理 -->
LEFT JOIN erp_contract_info c ON p.contract_id = c.contract_id AND c.del_flag = '0'
LEFT JOIN crm_customer_info cust ON c.one_customer_id = cust.customer_id AND cust.del_flag = '0'
LEFT JOIN sys_user u2 ON c.contract_manager_id = u2.user_id
<!-- 预算(取已审批的) -->
LEFT JOIN erp_budget_info b ON p.project_id = b.project_id AND b.budget_status = '3' AND b.del_flag = '0'
<!-- 验收(取已完成审批的) -->
LEFT JOIN (
SELECT project_id, MAX(acceptance_date) AS acceptance_date
FROM erp_project_acceptance
WHERE del_flag = '0' AND flow_status = 'finish'
GROUP BY project_id
) acc ON p.project_id = acc.project_id
<!-- 累计工时 -->
LEFT JOIN (
SELECT tp.project_id, COALESCE(SUM(tp.hours), 0) AS total_hours
FROM erp_timesheet_project tp
JOIN erp_timesheet_info ti ON tp.timesheet_id = ti.timesheet_id AND ti.del_flag = '0'
WHERE tp.del_flag = '0' AND ti.timesheet_status = '3'
GROUP BY tp.project_id
) hours_sub ON p.project_id = hours_sub.project_id
<!-- 产品数量(按合同聚合) -->
LEFT JOIN (
SELECT contract_id, SUM(amount) AS product_amount
FROM erp_contract_material
WHERE del_flag = '0'
GROUP BY contract_id
) mat_sub ON p.contract_id = mat_sub.contract_id
<where>
p.del_flag = '0'
AND p.project_category &lt;&gt; '9'
<if test="bo.params != null and bo.params.projectIds != null and bo.params.projectIds != ''">
AND p.project_id IN
<foreach collection="bo.params.projectIds.split(',')" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
<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.businessDirection != null and bo.businessDirection != ''">
AND p.business_direction = #{bo.businessDirection}
</if>
<if test="bo.projectStatus != null and bo.projectStatus != ''">
AND p.project_status = #{bo.projectStatus}
</if>
</where>
ORDER BY p.create_time DESC
</select>
</mapper>
Loading…
Cancel
Save