Compare commits

...

7 Commits

Author SHA1 Message Date
lzm
1d26048bc1 0509新功能优化 2026-05-08 17:39:39 +08:00
lzm
e278ca59bd 0508优化修改 2026-05-08 09:32:53 +08:00
lzm
e3e16f73cc 优化代码 2026-04-29 17:13:18 +08:00
lzm
29005c5ee8 添加指导价法明细表 2026-04-29 15:46:24 +08:00
lzm
a319567f65 表格导出 2026-04-28 18:52:34 +08:00
lzm
7a2260fbf4 表格修改 2026-04-27 10:20:12 +08:00
lzm
2d6bbc1086 添加基础表维护 2026-04-25 18:09:36 +08:00
99 changed files with 8611 additions and 376 deletions

View File

@@ -172,6 +172,6 @@ http://127.0.0.1:48080/swagger-ui
- `doc/后端开发文档.md`
特建投框架调研补充文档:
特建投业务分析与计划文档:
- [`../doc/tejiantou_ai_requirements_20260410_v2_framework.md`](../doc/tejiantou_ai_requirements_20260410_v2_framework.md)
- [`../doc/特建投业务分析与计划.md`](../doc/特建投业务分析与计划.md)

View File

@@ -112,6 +112,7 @@ public class LyzsysWebAutoConfiguration {
config.addAllowedOriginPattern("*"); // 设置访问源地址
config.addAllowedHeader("*"); // 设置访问源请求头
config.addAllowedMethod("*"); // 设置访问源请求方法
config.addExposedHeader("Content-Disposition"); // 允许前端读取下载文件名
// 创建 UrlBasedCorsConfigurationSource 对象
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); // 对接口配置跨域设置

View File

@@ -0,0 +1,81 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 员工")
@RestController
@RequestMapping("/tjt/employee")
@Validated
public class EmployeeController {
@Resource
private EmployeeService employeeService;
@PostMapping("/create")
@Operation(summary = "创建员工")
@PreAuthorize("@ss.hasPermission('tjt:employee:create')")
public CommonResult<Long> createEmployee(@Valid @RequestBody EmployeeSaveReqVO createReqVO) {
return success(employeeService.createEmployee(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改员工")
@PreAuthorize("@ss.hasPermission('tjt:employee:update')")
public CommonResult<Boolean> updateEmployee(@Valid @RequestBody EmployeeSaveReqVO updateReqVO) {
employeeService.updateEmployee(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除员工")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:employee:delete')")
public CommonResult<Boolean> deleteEmployee(@RequestParam("id") Long id) {
employeeService.deleteEmployee(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得员工详情")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:employee:query')")
public CommonResult<EmployeeRespVO> getEmployee(@RequestParam("id") Long id) {
return success(employeeService.getEmployee(id));
}
@GetMapping("/page")
@Operation(summary = "获得员工分页")
@PreAuthorize("@ss.hasPermission('tjt:employee:query')")
public CommonResult<PageResult<EmployeeRespVO>> getEmployeePage(@Valid EmployeePageReqVO pageReqVO) {
return success(employeeService.getEmployeePage(pageReqVO));
}
@GetMapping("/simple-list")
@Operation(summary = "获得员工精简列表")
public CommonResult<List<EmployeeSimpleRespVO>> getEmployeeSimpleList(
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "officeId", required = false) Long officeId,
@RequestParam(value = "status", required = false) String status,
@RequestParam(value = "enabledFlag", required = false) Boolean enabledFlag) {
return success(employeeService.getEmployeeSimpleList(keyword, officeId, status, enabledFlag));
}
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 员工分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class EmployeePageReqVO extends PageParam {
@Schema(description = "员工姓名", example = "")
private String employeeName;
@Schema(description = "所属专业所 ID", example = "1")
private Long officeId;
@Schema(description = "状态", example = "在职")
private String employeeStatus;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,58 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 员工 Response VO")
@Data
public class EmployeeRespVO {
@Schema(description = "员工 ID", example = "1")
private Long id;
@Schema(description = "员工姓名")
private String employeeName;
@Schema(description = "性别")
private String gender;
@Schema(description = "所属专业所 ID")
private Long officeId;
@Schema(description = "所属专业所名称")
private String officeName;
@Schema(description = "注册类型及等级")
private String registrationType;
@Schema(description = "职称")
private String jobTitle;
@Schema(description = "注册章号")
private String registrationSealNo;
@Schema(description = "入职时间")
private LocalDate entryDate;
@Schema(description = "离职时间")
private LocalDate leaveDate;
@Schema(description = "状态")
private String employeeStatus;
@Schema(description = "备注")
private String remark;
@Schema(description = "排序号")
private Integer sortNo;
@Schema(description = "是否启用")
private Boolean enabledFlag;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,73 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDate;
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 员工新增/修改 Request VO")
@Data
public class EmployeeSaveReqVO {
@Schema(description = "员工 ID", example = "1")
private Long id;
@Schema(description = "员工姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
@NotBlank(message = "员工姓名不能为空")
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
private String employeeName;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
@NotBlank(message = "性别不能为空")
@Size(max = 2, message = "性别长度不能超过 2 个字符")
private String gender;
@Schema(description = "所属专业所 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "所属专业所不能为空")
private Long officeId;
@Schema(description = "注册类型及等级", example = "一级注册建筑师")
@Size(max = 100, message = "注册类型及等级长度不能超过 100 个字符")
private String registrationType;
@Schema(description = "职称", example = "正高级")
@Size(max = 50, message = "职称长度不能超过 50 个字符")
private String jobTitle;
@Schema(description = "注册章号", example = "A123456")
@Size(max = 100, message = "注册章号长度不能超过 100 个字符")
private String registrationSealNo;
@Schema(description = "入职时间", example = "2026-01-01")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate entryDate;
@Schema(description = "离职时间", example = "2026-12-31")
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate leaveDate;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "在职")
@NotBlank(message = "状态不能为空")
@Size(max = 20, message = "状态长度不能超过 20 个字符")
private String employeeStatus;
@Schema(description = "备注")
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 员工精简 Response VO")
@Data
public class EmployeeSimpleRespVO {
@Schema(description = "员工 ID", example = "1")
private Long id;
@Schema(description = "员工姓名")
private String employeeName;
@Schema(description = "所属专业所 ID")
private Long officeId;
@Schema(description = "所属专业所名称")
private String officeName;
@Schema(description = "员工状态")
private String employeeStatus;
@Schema(description = "注册类型及等级")
private String registrationType;
@Schema(description = "职称")
private String jobTitle;
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget.EmployeeYearCostBudgetService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 员工年度成本预算")
@RestController
@RequestMapping("/tjt/employee-year-cost-budget")
@Validated
public class EmployeeYearCostBudgetController {
@Resource
private EmployeeYearCostBudgetService employeeYearCostBudgetService;
@PostMapping("/create")
@Operation(summary = "创建员工年度成本预算")
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:create')")
public CommonResult<Long> createEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO createReqVO) {
return success(employeeYearCostBudgetService.createEmployeeYearCostBudget(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改员工年度成本预算")
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:update')")
public CommonResult<Boolean> updateEmployeeYearCostBudget(@Valid @RequestBody EmployeeYearCostBudgetSaveReqVO updateReqVO) {
employeeYearCostBudgetService.updateEmployeeYearCostBudget(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除员工年度成本预算")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:delete')")
public CommonResult<Boolean> deleteEmployeeYearCostBudget(@RequestParam("id") Long id) {
employeeYearCostBudgetService.deleteEmployeeYearCostBudget(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得员工年度成本预算详情")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')")
public CommonResult<EmployeeYearCostBudgetRespVO> getEmployeeYearCostBudget(@RequestParam("id") Long id) {
return success(employeeYearCostBudgetService.getEmployeeYearCostBudget(id));
}
@GetMapping("/page")
@Operation(summary = "获得员工年度成本预算分页")
@PreAuthorize("@ss.hasPermission('tjt:employee-year-cost-budget:query')")
public CommonResult<PageResult<EmployeeYearCostBudgetRespVO>> getEmployeeYearCostBudgetPage(
@Valid EmployeeYearCostBudgetPageReqVO pageReqVO) {
return success(employeeYearCostBudgetService.getEmployeeYearCostBudgetPage(pageReqVO));
}
}

View File

@@ -0,0 +1,25 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 员工年度成本预算分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class EmployeeYearCostBudgetPageReqVO extends PageParam {
@Schema(description = "员工 ID", example = "1")
private Long employeeId;
@Schema(description = "员工姓名", example = "")
private String employeeName;
@Schema(description = "预算年度", example = "2026")
private Integer budgetYear;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,40 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 员工年度成本预算 Response VO")
@Data
public class EmployeeYearCostBudgetRespVO {
@Schema(description = "记录 ID", example = "1")
private Long id;
@Schema(description = "员工 ID")
private Long employeeId;
@Schema(description = "员工姓名")
private String employeeName;
@Schema(description = "预算年度")
private Integer budgetYear;
@Schema(description = "预计发生成本")
private BigDecimal expectedCostAmount;
@Schema(description = "备注")
private String remark;
@Schema(description = "排序号")
private Integer sortNo;
@Schema(description = "是否启用")
private Boolean enabledFlag;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,43 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 员工年度成本预算新增/修改 Request VO")
@Data
public class EmployeeYearCostBudgetSaveReqVO {
@Schema(description = "记录 ID", example = "1")
private Long id;
@Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "员工 ID 不能为空")
private Long employeeId;
@Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三")
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
private String employeeName;
@Schema(description = "预算年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "预算年度不能为空")
private Integer budgetYear;
@Schema(description = "预计发生成本", requiredMode = Schema.RequiredMode.REQUIRED, example = "100000")
@NotNull(message = "预计发生成本不能为空")
private BigDecimal expectedCostAmount;
@Schema(description = "备注")
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,77 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.office;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.service.office.OfficeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 专业所")
@RestController
@RequestMapping("/tjt/office")
@Validated
public class OfficeController {
@Resource
private OfficeService officeService;
@PostMapping("/create")
@Operation(summary = "创建专业所")
@PreAuthorize("@ss.hasPermission('tjt:office:create')")
public CommonResult<Long> createOffice(@Valid @RequestBody OfficeSaveReqVO createReqVO) {
return success(officeService.createOffice(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改专业所")
@PreAuthorize("@ss.hasPermission('tjt:office:update')")
public CommonResult<Boolean> updateOffice(@Valid @RequestBody OfficeSaveReqVO updateReqVO) {
officeService.updateOffice(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除专业所")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:office:delete')")
public CommonResult<Boolean> deleteOffice(@RequestParam("id") Long id) {
officeService.deleteOffice(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得专业所详情")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:office:query')")
public CommonResult<OfficeRespVO> getOffice(@RequestParam("id") Long id) {
return success(officeService.getOffice(id));
}
@GetMapping("/page")
@Operation(summary = "获得专业所分页")
@PreAuthorize("@ss.hasPermission('tjt:office:query')")
public CommonResult<PageResult<OfficeRespVO>> getOfficePage(@Valid OfficePageReqVO pageReqVO) {
return success(officeService.getOfficePage(pageReqVO));
}
@GetMapping("/simple-list")
@Operation(summary = "获得专业所精简列表")
public CommonResult<List<OfficeSimpleRespVO>> getOfficeSimpleList() {
return success(officeService.getOfficeSimpleList());
}
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 专业所分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class OfficePageReqVO extends PageParam {
@Schema(description = "专业所名称", example = "建筑")
private String officeName;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 专业所 Response VO")
@Data
public class OfficeRespVO {
@Schema(description = "专业所 ID", example = "1")
private Long id;
@Schema(description = "专业所名称", example = "建筑一所")
private String officeName;
@Schema(description = "专业所编码", example = "JZY")
private String officeCode;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 专业所新增/修改 Request VO")
@Data
public class OfficeSaveReqVO {
@Schema(description = "专业所 ID", example = "1")
private Long id;
@Schema(description = "专业所名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑一所")
@NotBlank(message = "专业所名称不能为空")
@Size(max = 100, message = "专业所名称长度不能超过 100 个字符")
private String officeName;
@Schema(description = "专业所编码", example = "JZY")
@Size(max = 50, message = "专业所编码长度不能超过 50 个字符")
private String officeCode;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
@Schema(description = "备注", example = "默认专业所")
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
}

View File

@@ -0,0 +1,16 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 专业所精简 Response VO")
@Data
public class OfficeSimpleRespVO {
@Schema(description = "专业所 ID", example = "1")
private Long id;
@Schema(description = "专业所名称", example = "建筑一所")
private String officeName;
}

View File

@@ -21,7 +21,7 @@ public class ProjectOutputSplitRespVO {
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "规划内容")
@Schema(description = "项目任务包")
private String planningContent;
@Schema(description = "年度")
@@ -33,20 +33,17 @@ public class ProjectOutputSplitRespVO {
@Schema(description = "项目经理")
private String projectManagerName;
@Schema(description = "项目负责人")
@Schema(description = "工程负责人")
private String engineeringLeaderName;
@Schema(description = "项目经理比例")
private BigDecimal projectManagerRatio;
@Schema(description = "项目经理/工程负责人")
private String projectLeadName;
@Schema(description = "项目经理金额")
private BigDecimal projectManagerAmount;
@Schema(description = "项目经理/工程负责人合并比例")
private BigDecimal projectLeadRatio;
@Schema(description = "项目负责人比例")
private BigDecimal engineeringLeaderRatio;
@Schema(description = "项目负责人金额")
private BigDecimal engineeringLeaderAmount;
@Schema(description = "项目经理/工程负责人合并金额")
private BigDecimal projectLeadAmount;
@Schema(description = "专业所比例")
private BigDecimal officeRatio;

View File

@@ -3,6 +3,8 @@ package cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@@ -10,48 +12,62 @@ import java.math.BigDecimal;
@Data
public class ProjectOutputSplitSaveReqVO {
@Schema(description = "规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "规划 ID 不能为空")
@Schema(description = "规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "规划 ID 不能为空")
private Long planningId;
@Schema(description = "项目经理比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0200")
@NotNull(message = "项目经理比例不能为空")
private BigDecimal projectManagerRatio;
@Schema(description = "项目负责人比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0300")
@NotNull(message = "项目负责人比例不能为空")
private BigDecimal engineeringLeaderRatio;
@Schema(description = "项目经理/工程负责人合并比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0550")
@NotNull(message = "项目经理/工程负责人合并比例不能为空")
@DecimalMin(value = "0.0000", message = "projectLeadRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "projectLeadRatio must be <= 1")
private BigDecimal projectLeadRatio;
@Schema(description = "专业所比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.9500")
@NotNull(message = "专业所比例不能为空")
@DecimalMin(value = "0.0000", message = "officeRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "officeRatio must be <= 1")
private BigDecimal officeRatio;
@Schema(description = "建筑专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000")
@NotNull(message = "建筑专业比例不能为空")
@DecimalMin(value = "0.0000", message = "archRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "archRatio must be <= 1")
private BigDecimal archRatio;
@Schema(description = "装修专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0000")
@NotNull(message = "装修专业比例不能为空")
@DecimalMin(value = "0.0000", message = "decorRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "decorRatio must be <= 1")
private BigDecimal decorRatio;
@Schema(description = "结构专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.2000")
@NotNull(message = "结构专业比例不能为空")
@DecimalMin(value = "0.0000", message = "structRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "structRatio must be <= 1")
private BigDecimal structRatio;
@Schema(description = "水专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000")
@NotNull(message = "水专业比例不能为空")
@DecimalMin(value = "0.0000", message = "waterRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "waterRatio must be <= 1")
private BigDecimal waterRatio;
@Schema(description = "电气专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.1000")
@NotNull(message = "电气专业比例不能为空")
@DecimalMin(value = "0.0000", message = "elecRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "elecRatio must be <= 1")
private BigDecimal elecRatio;
@Schema(description = "暖通专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "暖通专业比例不能为空")
@DecimalMin(value = "0.0000", message = "hvacRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "hvacRatio must be <= 1")
private BigDecimal hvacRatio;
@Schema(description = "数字化设计专业比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "数字化设计专业比例不能为空")
@DecimalMin(value = "0.0000", message = "digitalRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "digitalRatio must be <= 1")
private BigDecimal digitalRatio;
}

View File

@@ -24,7 +24,7 @@ public class ProjectPlanningPageReqVO extends PageParam {
@Schema(description = "产值计算方式", example = "指导价法")
private String calculationMethod;
@Schema(description = "规划内容,模糊匹配", example = "建筑")
@Schema(description = "项目任务包,模糊匹配", example = "建筑")
private String planningContent;
@Schema(description = "开始年度", example = "2026")

View File

@@ -16,25 +16,43 @@ public class ProjectPlanningRespVO {
@Schema(description = "项目 ID", example = "1")
private Long projectId;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "归属类型")
private String ownershipType;
@Schema(description = "产值计算方式")
private String calculationMethod;
@Schema(description = "规划内容")
@Schema(description = "项目任务包")
private String planningContent;
@Schema(description = "规划金额")
@Schema(description = "分项合同产值")
private BigDecimal planningAmount;
@Schema(description = "管理费费率")
@Schema(description = "合同产值数量")
private BigDecimal contractValueQuantity;
@Schema(description = "合同产值单价")
private BigDecimal contractValueUnitPrice;
@Schema(description = "管理费率")
private BigDecimal managementFeeRate;
@Schema(description = "管理费")
private BigDecimal managementFee;
@Schema(description = "实施团队")
@Schema(description = "增值税率")
private BigDecimal vatRate;
@Schema(description = "增值税")
private BigDecimal vatAmount;
@Schema(description = "项目预算产值")
private BigDecimal projectBudgetOutputValue;
@Schema(description = "意向实施团队")
private String implementationTeam;
@Schema(description = "开始年度")
@@ -64,25 +82,25 @@ public class ProjectPlanningRespVO {
@Schema(description = "待分配比例")
private BigDecimal pendingAmount;
@Schema(description = "Planning progress remark")
@Schema(description = "提取进度备注")
private String progressRemark;
@Schema(description = "楼栋数或户型数")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数,保留两位小数")
@Schema(description = "套图系数")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数,保留两位小数")
@Schema(description = "规模系数")
private BigDecimal scaleFactor;
@Schema(description = "修改系数,保留两位小数")
@Schema(description = "修改系数")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数/复杂等级按比例值返回100%=1.0000")
@Schema(description = "复杂系数")
private BigDecimal complexityFactor;
@Schema(description = "内部指导单价(元/")
@Schema(description = "内部指导单价(元/")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "虚拟产值计算方式")
@@ -100,13 +118,10 @@ public class ProjectPlanningRespVO {
@Schema(description = "指导总价")
private BigDecimal guidanceTotalPrice;
@Schema(description = "虚拟总价")
private BigDecimal virtualTotalPrice;
@Schema(description = "产值计算比例")
private BigDecimal calculationRatio;
@Schema(description = "合同单价(元/")
@Schema(description = "合同单价(元/")
private BigDecimal contractUnitPrice;
@Schema(description = "合计调整系数")

View File

@@ -3,6 +3,8 @@ package cn.iocoder.lyzsys.module.tjt.controller.admin.planning.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@@ -19,31 +21,50 @@ public class ProjectPlanningSaveReqVO {
@NotNull(message = "项目 ID 不能为空")
private Long projectId;
@Schema(description = "排序", example = "0")
private Integer sortNo;
@Schema(description = "归属类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "专业所")
@NotBlank(message = "归属类型不能为空")
@Size(max = 20, message = "归属类型长度不能超过 20 个字符")
private String ownershipType;
@Schema(description = "产值计算方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "指导价法")
@NotBlank(message = "产值计算方式不能为空")
@Schema(description = "产值计算方式,页面 2 维护", example = "指导价法")
@Size(max = 30, message = "产值计算方式长度不能超过 30 个字符")
private String calculationMethod;
@Schema(description = "规划内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑设计")
@NotBlank(message = "规划内容不能为空")
@Size(max = 255, message = "规划内容长度不能超过 255 个字符")
@Schema(description = "项目任务包", requiredMode = Schema.RequiredMode.REQUIRED, example = "建筑设计")
@NotBlank(message = "项目任务包不能为空")
@Size(max = 255, message = "项目任务包长度不能超过 255 个字符")
private String planningContent;
@Schema(description = "规划金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "500000")
@NotNull(message = "规划金额不能为空")
@Schema(description = "分项合同产值,系统根据合同产值数量和合同产值单价自动计算", example = "500000")
private BigDecimal planningAmount;
@Schema(description = "管理费费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "管理费费率不能为空")
@Schema(description = "合同产值数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合同产值数量不能为空")
@DecimalMin(value = "0.0000", message = "contractValueQuantity must be >= 0")
private BigDecimal contractValueQuantity;
@Schema(description = "合同产值单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "500000")
@NotNull(message = "合同产值单价不能为空")
@DecimalMin(value = "0.0000", message = "contractValueUnitPrice must be >= 0")
private BigDecimal contractValueUnitPrice;
@Schema(description = "管理费率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0500")
@NotNull(message = "管理费率不能为空")
@DecimalMin(value = "0.0000", message = "managementFeeRate must be >= 0")
@DecimalMax(value = "1.0000", message = "managementFeeRate must be <= 1")
private BigDecimal managementFeeRate;
@Schema(description = "实施团队", example = "建筑一所")
@Size(max = 100, message = "实施团队长度不能超过 100 个字符")
@Schema(description = "增值税率", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.0600")
@NotNull(message = "增值税率不能为空")
@DecimalMin(value = "0.0000", message = "vatRate must be >= 0")
@DecimalMax(value = "1.0000", message = "vatRate must be <= 1")
private BigDecimal vatRate;
@Schema(description = "意向实施团队", example = "建筑一所")
@Size(max = 100, message = "意向实施团队长度不能超过 100 个字符")
private String implementationTeam;
@Schema(description = "开始年度", example = "2026")
@@ -57,19 +78,25 @@ public class ProjectPlanningSaveReqVO {
private String designStage;
@Schema(description = "本次设计阶段比例", example = "0.3000")
@DecimalMin(value = "0.0000", message = "currentDesignStageRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "currentDesignStageRatio must be <= 1")
private BigDecimal currentDesignStageRatio;
@Schema(description = "审核审定是否外包", example = "false")
private Boolean reviewOutsourceFlag;
@Schema(description = "审核审定占比", example = "0.0600")
@DecimalMin(value = "0.0000", message = "reviewOutsourceRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "reviewOutsourceRatio must be <= 1")
private BigDecimal reviewOutsourceRatio;
@Schema(description = "总分配比例", example = "1.0000")
@DecimalMin(value = "0.0000", message = "totalDistributionAmount must be >= 0")
@DecimalMax(value = "1.0000", message = "totalDistributionAmount must be <= 1")
private BigDecimal totalDistributionAmount;
@Schema(description = "Planning progress remark", example = "Q1 extraction arrangement")
@Size(max = 500, message = "Progress remark length must be within 500 characters")
@Schema(description = "提取进度备注", example = "Q1 提取安排")
@Size(max = 500, message = "提取进度备注长度不能超过 500 个字符")
private String progressRemark;
@Schema(description = "楼栋数或户型数", example = "10")
@@ -85,12 +112,14 @@ public class ProjectPlanningSaveReqVO {
private BigDecimal modificationFactor;
@Schema(description = "复杂系数/复杂等级按比例值存储100%=1.0000", example = "1.0000")
@DecimalMin(value = "0.0000", message = "complexityFactor must be >= 0")
@DecimalMax(value = "1.0000", message = "complexityFactor must be <= 1")
private BigDecimal complexityFactor;
@Schema(description = "内部指导单价(元/", example = "80.00")
@Schema(description = "内部指导单价(元/", example = "80.00")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "虚拟产值计算方式", example = "工日法")
@Schema(description = "虚拟产值计算方式:指导单价法/指导总价法/工日法", example = "工日法")
@Size(max = 30, message = "虚拟产值计算方式长度不能超过 30 个字符")
private String virtualCalculationMethod;
@@ -106,10 +135,9 @@ public class ProjectPlanningSaveReqVO {
@Schema(description = "指导总价", example = "880000.00")
private BigDecimal guidanceTotalPrice;
@Schema(description = "虚拟总价", example = "600000.00")
private BigDecimal virtualTotalPrice;
@Schema(description = "产值计算比例", example = "0.0800")
@DecimalMin(value = "0.0000", message = "calculationRatio must be >= 0")
@DecimalMax(value = "1.0000", message = "calculationRatio must be <= 1")
private BigDecimal calculationRatio;
}

View File

@@ -0,0 +1,66 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 合约规划指导价法明细")
@RestController
@RequestMapping("/tjt/planning-guide-detail")
@Validated
public class ProjectPlanningGuideDetailController {
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@GetMapping("/list-by-planning")
@Operation(summary = "根据合约规划获得指导价法明细列表")
@Parameter(name = "planningId", description = "合约规划 ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:planning:query')")
public CommonResult<List<ProjectPlanningGuideDetailRespVO>> getProjectPlanningGuideDetailListByPlanningId(
@RequestParam("planningId") Long planningId) {
List<ProjectPlanningGuideDetailDO> list =
projectPlanningGuideDetailService.getProjectPlanningGuideDetailListByPlanningId(planningId);
return success(BeanUtils.toBean(list, ProjectPlanningGuideDetailRespVO.class));
}
@PostMapping("/batch-save")
@Operation(summary = "批量保存指导价法明细")
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
public CommonResult<Boolean> batchSaveProjectPlanningGuideDetail(
@Valid @RequestBody ProjectPlanningGuideDetailBatchSaveReqVO reqVO) {
projectPlanningGuideDetailService.batchSaveProjectPlanningGuideDetail(reqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除指导价法明细")
@Parameter(name = "id", description = "明细 ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:planning:update')")
public CommonResult<Boolean> deleteProjectPlanningGuideDetail(@RequestParam("id") Long id) {
projectPlanningGuideDetailService.deleteProjectPlanningGuideDetail(id);
return success(true);
}
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 合约规划指导价法明细批量保存 Request VO")
@Data
public class ProjectPlanningGuideDetailBatchSaveReqVO {
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
private Long planningId;
@Schema(description = "指导价法明细列表")
@Valid
private List<ProjectPlanningGuideDetailSaveReqVO> details;
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 合约规划指导价法明细 Response VO")
@Data
public class ProjectPlanningGuideDetailRespVO {
@Schema(description = "明细 ID", example = "1")
private Long id;
@Schema(description = "合约规划 ID", example = "1")
private Long planningId;
@Schema(description = "项目 ID", example = "1")
private Long projectId;
@Schema(description = "设计部位")
private String designPart;
@Schema(description = "建筑类型")
private String buildingType;
@Schema(description = "设计面积")
private BigDecimal designArea;
@Schema(description = "内部指导单价(元/m²)")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "楼栋数/户型数")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数")
private BigDecimal scaleFactor;
@Schema(description = "修改系数")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数")
private BigDecimal complexityFactor;
@Schema(description = "合计调整系数")
private BigDecimal totalAdjustmentFactor;
@Schema(description = "设计占比")
private BigDecimal designRatio;
@Schema(description = "考核面积")
private BigDecimal assessmentArea;
@Schema(description = "考核产值")
private BigDecimal assessmentOutputValue;
@Schema(description = "排序号")
private Integer sortNo;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,76 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 合约规划指导价法明细新增/修改 Request VO")
@Data
public class ProjectPlanningGuideDetailSaveReqVO {
@Schema(description = "明细 ID", example = "1")
private Long id;
@Schema(description = "设计部位", requiredMode = Schema.RequiredMode.REQUIRED, example = "地上部分")
@NotBlank(message = "设计部位不能为空")
@Size(max = 20, message = "设计部位长度不能超过 20 个字符")
private String designPart;
@Schema(description = "建筑类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "住宅")
@NotBlank(message = "建筑类型不能为空")
@Size(max = 100, message = "建筑类型长度不能超过 100 个字符")
private String buildingType;
@Schema(description = "设计面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "12000")
@NotNull(message = "设计面积不能为空")
@DecimalMin(value = "0", message = "designArea must be >= 0")
private BigDecimal designArea;
@Schema(description = "内部指导单价(元/m²)", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
@NotNull(message = "内部指导单价不能为空")
@DecimalMin(value = "0", message = "internalGuidanceUnitPrice must be >= 0")
private BigDecimal internalGuidanceUnitPrice;
@Schema(description = "楼栋数/户型数", example = "10")
private Integer buildingOrUnitCount;
@Schema(description = "套图系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "套图系数不能为空")
@DecimalMin(value = "0", message = "drawingSetFactor must be >= 0")
private BigDecimal drawingSetFactor;
@Schema(description = "规模系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "规模系数不能为空")
@DecimalMin(value = "0", message = "scaleFactor must be >= 0")
private BigDecimal scaleFactor;
@Schema(description = "修改系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "修改系数不能为空")
@DecimalMin(value = "0", message = "modificationFactor must be >= 0")
private BigDecimal modificationFactor;
@Schema(description = "复杂系数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0000")
@NotNull(message = "复杂系数不能为空")
@DecimalMin(value = "0", message = "complexityFactor must be >= 0")
private BigDecimal complexityFactor;
@Schema(description = "设计占比", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5000")
@NotNull(message = "设计占比不能为空")
@DecimalMin(value = "0", message = "designRatio must be >= 0")
@DecimalMax(value = "1", message = "designRatio must be <= 1")
private BigDecimal designRatio;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
@Schema(description = "备注", example = "样板房")
@Size(max = 500, message = "备注长度不能超过 500 个字符")
private String remark;
}

View File

@@ -6,32 +6,29 @@ import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 季度分配 Response VO")
@Schema(description = "Admin - project planning quarter Response VO")
@Data
public class ProjectPlanningQuarterRespVO {
@Schema(description = "季度分配 ID", example = "1")
@Schema(description = "Quarter distribution ID", example = "1")
private Long id;
@Schema(description = "合约规划 ID", example = "1")
@Schema(description = "Planning ID", example = "1")
private Long planningId;
@Schema(description = "分配年度")
@Schema(description = "Distribution year")
private Integer distributionYear;
@Schema(description = "季度")
@Schema(description = "Quarter number")
private Integer quarterNo;
@Schema(description = "分配比例")
@Schema(description = "Distribution ratio")
private BigDecimal distributionRatio;
@Schema(description = "分配金额")
@Schema(description = "Distribution amount")
private BigDecimal distributionAmount;
@Schema(description = "提取进度备注")
private String progressRemark;
@Schema(description = "创建时间")
@Schema(description = "Create time")
private LocalDateTime createTime;
}

View File

@@ -6,35 +6,30 @@ import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 季度分配新增/修改 Request VO")
@Schema(description = "Admin - project planning quarter save Request VO")
@Data
public class ProjectPlanningQuarterSaveReqVO {
@Schema(description = "季度分配 ID", example = "1")
@Schema(description = "Quarter distribution ID", example = "1")
private Long id;
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
@Schema(description = "Planning ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "planningId cannot be null")
private Long planningId;
@Schema(description = "分配年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "分配年度不能为空")
@Schema(description = "Distribution year", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "distributionYear cannot be null")
private Integer distributionYear;
@Schema(description = "季度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "季度不能为空")
@Min(value = 1, message = "季度必须在 1 到 4 之间")
@Max(value = 4, message = "季度必须在 1 到 4 之间")
@Schema(description = "Quarter number", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "quarterNo cannot be null")
@Min(value = 1, message = "quarterNo must be between 1 and 4")
@Max(value = 4, message = "quarterNo must be between 1 and 4")
private Integer quarterNo;
@Schema(description = "分配比例", example = "0.2500")
@Schema(description = "Distribution ratio", example = "0.2500")
private BigDecimal distributionRatio;
@Schema(description = "提取进度备注", example = "Q1 提取")
@Size(max = 500, message = "提取进度备注长度不能超过 500 个字符")
private String progressRemark;
}

View File

@@ -15,7 +15,7 @@ import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR
@EqualsAndHashCode(callSuper = true)
public class ProjectProfitPageReqVO extends PageParam {
@Schema(description = "工程名称,模糊匹配", example = "设计")
@Schema(description = "项目名称,模糊匹配", example = "设计")
private String projectName;
@Schema(description = "是否签订合同", example = "true")

View File

@@ -13,13 +13,16 @@ public class ProjectProfitRespVO {
@Schema(description = "项目 ID", example = "1")
private Long projectId;
@Schema(description = "工程名称")
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "排序")
private Integer sortNo;
@Schema(description = "是否签订合同")
private Boolean contractSignedFlag;
@Schema(description = "合同金额")
@Schema(description = "合同产值")
private BigDecimal contractAmount;
@Schema(description = "最终结算金额")
@@ -34,12 +37,18 @@ public class ProjectProfitRespVO {
@Schema(description = "专业所产值")
private BigDecimal majorOutputValue;
@Schema(description = "预计 K 值")
private BigDecimal expectedKValue;
@Schema(description = "专业所预计绩效")
private BigDecimal majorExpectedPerformance;
@Schema(description = "科创产值比例")
private BigDecimal innovationOutputRate;
@Schema(description = "科创产值")
private BigDecimal innovationOutputValue;
@Schema(description = "其他成本")
private BigDecimal otherCost;
@Schema(description = "盈亏值")
private BigDecimal profitLossValue;

View File

@@ -2,11 +2,9 @@ package cn.iocoder.lyzsys.module.tjt.controller.admin.project;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.service.project.ProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -67,8 +65,7 @@ public class ProjectController {
@Operation(summary = "获得项目分页")
@PreAuthorize("@ss.hasPermission('tjt:project:query')")
public CommonResult<PageResult<ProjectRespVO>> getProjectPage(@Valid ProjectPageReqVO pageReqVO) {
PageResult<ProjectDO> pageResult = projectService.getProjectPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProjectRespVO.class));
return success(projectService.getProjectPage(pageReqVO));
}
@GetMapping("/get")
@@ -76,7 +73,7 @@ public class ProjectController {
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:project:query')")
public CommonResult<ProjectRespVO> getProject(@RequestParam("id") Long id) {
return success(BeanUtils.toBean(projectService.getProject(id), ProjectRespVO.class));
return success(projectService.getProject(id));
}
}

View File

@@ -15,7 +15,7 @@ import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR
@EqualsAndHashCode(callSuper = true)
public class ProjectPageReqVO extends PageParam {
@Schema(description = "工程名称,模糊匹配", example = "设计")
@Schema(description = "项目名称,模糊匹配", example = "设计")
private String projectName;
@Schema(description = "是否签订合同", example = "true")
@@ -24,6 +24,9 @@ public class ProjectPageReqVO extends PageParam {
@Schema(description = "项目开始年度", example = "2026")
private Integer projectStartYear;
@Schema(description = "项目状态", example = "进行中")
private String projectStatus;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;

View File

@@ -9,6 +9,7 @@ import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 项目 Response VO")
@Data
@@ -21,16 +22,20 @@ public class ProjectRespVO {
@ExcelProperty("项目ID")
private Long id;
@Schema(description = "工程名称", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("工程名称")
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("项目名称")
private String projectName;
@Schema(description = "排序")
@ExcelProperty("排序")
private Integer sortNo;
@Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("是否签订合同")
private Boolean contractSignedFlag;
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("合同金额")
@Schema(description = "合同产值", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("合同产值")
private BigDecimal contractAmount;
@Schema(description = "工程总面积", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -41,12 +46,12 @@ public class ProjectRespVO {
@ExcelProperty("建设单位")
private String constructionUnitName;
@Schema(description = "联系人")
@ExcelProperty("联系人")
@Schema(description = "建设单位联系人")
@ExcelProperty("建设单位联系人")
private String contactName;
@Schema(description = "联系方式")
@ExcelProperty("联系方式")
@Schema(description = "建设单位联系电话")
@ExcelProperty("建设单位联系电话")
private String contactPhone;
@Schema(description = "合同签订日期")
@@ -66,20 +71,47 @@ public class ProjectRespVO {
@ExcelProperty("工程类型")
private String projectType;
@Schema(description = "工程类别")
@ExcelProperty("工程类别")
private String projectCategory;
@Schema(description = "项目开始年度")
@ExcelProperty("项目开始年度")
private Integer projectStartYear;
@Schema(description = "项目状态")
@ExcelProperty("项目状态")
private String projectStatus;
@Schema(description = "是否封档")
private Boolean archiveFlag;
@Schema(description = "封档时间")
private LocalDateTime archiveTime;
@Schema(description = "暂停原因")
private String pauseReason;
@Schema(description = "中止原因")
private String terminateReason;
@Schema(description = "最终结算金额")
@ExcelProperty("最终结算金额")
private BigDecimal finalSettlementAmount;
@Schema(description = "预计 K 值")
@ExcelProperty("预计 K 值")
private BigDecimal expectedKValue;
@Schema(description = "科创产值比例")
@ExcelProperty("科创产值比例")
private BigDecimal innovationOutputRate;
@Schema(description = "其他成本")
@ExcelProperty("其他成本")
private BigDecimal otherCost;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "项目角色人员列表")
private List<ProjectRolePersonRespVO> rolePersons;
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 项目角色人员 Response VO")
@Data
public class ProjectRolePersonRespVO {
@Schema(description = "主键 ID", example = "1")
private Long id;
@Schema(description = "项目 ID", example = "1")
private Long projectId;
@Schema(description = "角色编码", example = "project_manager")
private String roleCode;
@Schema(description = "角色名称", example = "项目经理")
private String roleName;
@Schema(description = "员工 ID", example = "1")
private Long employeeId;
@Schema(description = "员工姓名", example = "张三")
private String employeeName;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 项目角色人员新增/修改 Request VO")
@Data
public class ProjectRolePersonSaveReqVO {
@Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "project_manager")
@NotBlank(message = "角色编码不能为空")
private String roleCode;
@Schema(description = "员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "员工 ID 不能为空")
private Long employeeId;
@Schema(description = "员工姓名,后端根据 employeeId 回填", example = "张三")
@Size(max = 64, message = "员工姓名长度不能超过 64 个字符")
private String employeeName;
@Schema(description = "排序号", example = "1")
private Integer sortNo;
}

View File

@@ -5,6 +5,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.Valid;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@@ -20,17 +23,20 @@ public class ProjectSaveReqVO {
@Schema(description = "项目 ID", example = "1")
private Long id;
@Schema(description = "工程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX 设计项目")
@NotBlank(message = "工程名称不能为空")
@Size(max = 200, message = "工程名称长度不能超过 200 个字符")
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX 设计项目")
@NotBlank(message = "项目名称不能为空")
@Size(max = 200, message = "项目名称长度不能超过 200 个字符")
private String projectName;
@Schema(description = "排序", example = "0")
private Integer sortNo;
@Schema(description = "是否签订合同", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否签订合同不能为空")
private Boolean contractSignedFlag;
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000000")
@NotNull(message = "合同金额不能为空")
@Schema(description = "合同产值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000000")
@NotNull(message = "合同产值不能为空")
private BigDecimal contractAmount;
@Schema(description = "工程总面积", requiredMode = Schema.RequiredMode.REQUIRED, example = "30000")
@@ -41,12 +47,12 @@ public class ProjectSaveReqVO {
@Size(max = 200, message = "建设单位长度不能超过 200 个字符")
private String constructionUnitName;
@Schema(description = "联系人", example = "张三")
@Size(max = 64, message = "联系人长度不能超过 64 个字符")
@Schema(description = "建设单位联系人", example = "张三")
@Size(max = 64, message = "建设单位联系人长度不能超过 64 个字符")
private String contactName;
@Schema(description = "联系方式", example = "13800000000")
@Size(max = 32, message = "联系方式长度不能超过 32 个字符")
@Schema(description = "建设单位联系电话", example = "13800000000")
@Size(max = 32, message = "建设单位联系电话长度不能超过 32 个字符")
private String contactPhone;
@Schema(description = "合同签订日期", example = "2026-04-14")
@@ -54,26 +60,43 @@ public class ProjectSaveReqVO {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate contractSigningDate;
@Schema(description = "项目经理", example = "李四")
@Size(max = 64, message = "项目经理长度不能超过 64 个字符")
private String projectManagerName;
@Schema(description = "工程负责人", example = "王五")
@Size(max = 64, message = "工程负责人长度不能超过 64 个字符")
private String engineeringPrincipalName;
@Schema(description = "工程类型", example = "住宅")
@Schema(description = "工程类型", example = "建筑工程")
@Size(max = 50, message = "工程类型长度不能超过 50 个字符")
private String projectType;
@Schema(description = "工程类别", example = "住宅")
@Size(max = 50, message = "工程类别长度不能超过 50 个字符")
private String projectCategory;
@Schema(description = "项目开始年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "项目开始年度不能为空")
private Integer projectStartYear;
@Schema(description = "项目状态", example = "进行中")
@Size(max = 20, message = "项目状态长度不能超过 20 个字符")
private String projectStatus;
@Schema(description = "暂停原因")
@Size(max = 255, message = "暂停原因长度不能超过 255 个字符")
private String pauseReason;
@Schema(description = "中止原因")
@Size(max = 255, message = "中止原因长度不能超过 255 个字符")
private String terminateReason;
@Schema(description = "最终结算金额", example = "1200000")
private BigDecimal finalSettlementAmount;
@Schema(description = "预计 K 值", example = "0.8500")
private BigDecimal expectedKValue;
@Schema(description = "科创产值比例", example = "0.0100")
@DecimalMin(value = "0.0000", message = "科创产值比例不能小于 0%")
@DecimalMax(value = "1.0000", message = "科创产值比例不能大于 100%")
private BigDecimal innovationOutputRate;
@Schema(description = "其他成本", example = "20000.00")
private BigDecimal otherCost;
@Schema(description = "项目角色人员列表")
@Valid
private java.util.List<ProjectRolePersonSaveReqVO> rolePersons;
}

View File

@@ -0,0 +1,91 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report;
import cn.iocoder.lyzsys.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectBudgetExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectLeadQuarterOutputExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectQuarterOutputExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.SpecialtyPersonOutputExportReqVO;
import cn.iocoder.lyzsys.module.tjt.service.report.ProjectOutputReportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.lyzsys.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - 产值报表导出")
@RestController
@RequestMapping("/tjt/report")
@Validated
public class ProjectOutputReportController {
@Resource
private ProjectOutputReportService projectOutputReportService;
@GetMapping("/project-budget/export-excel")
@Operation(summary = "导出项目考核产值预算表")
@PreAuthorize("@ss.hasPermission('tjt:report-budget:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProjectBudgetExcel(HttpServletResponse response,
@Valid ProjectBudgetExportReqVO reqVO) throws IOException {
projectOutputReportService.exportProjectBudgetExcel(response, reqVO);
}
@GetMapping("/project-quarter-output/export-excel")
@Operation(summary = "导出项目级年度季度计取表")
@PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProjectQuarterOutputExcel(HttpServletResponse response,
@Valid ProjectQuarterOutputExportReqVO reqVO) throws IOException {
projectOutputReportService.exportProjectQuarterOutputExcel(response, reqVO);
}
@GetMapping("/project-lead-quarter-output/export-excel")
@Operation(summary = "导出工程负责人年度季度计取表")
@PreAuthorize("@ss.hasPermission('tjt:report-project-quarter:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProjectLeadQuarterOutputExcel(HttpServletResponse response,
@Valid ProjectLeadQuarterOutputExportReqVO reqVO)
throws IOException {
projectOutputReportService.exportProjectLeadQuarterOutputExcel(response, reqVO);
}
@GetMapping("/specialty-person-output/export-excel")
@Operation(summary = "导出专业内人员年度季度计取表")
@PreAuthorize("@ss.hasPermission('tjt:report-specialty-person:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportSpecialtyPersonOutputExcel(HttpServletResponse response,
@Valid SpecialtyPersonOutputExportReqVO reqVO) throws IOException {
projectOutputReportService.exportSpecialtyPersonOutputExcel(response, reqVO);
}
@GetMapping("/project-overview/export-excel")
@Operation(summary = "导出项目总览表")
@PreAuthorize("@ss.hasPermission('tjt:report-summary:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportProjectOverviewExcel(HttpServletResponse response,
@Valid ProjectOverviewExportReqVO reqVO) throws IOException {
projectOutputReportService.exportProjectOverviewExcel(response, reqVO);
}
@GetMapping("/employee-output-summary/export-excel")
@Operation(summary = "导出员工个人考核产值汇总表")
@PreAuthorize("@ss.hasPermission('tjt:report-summary:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportEmployeeOutputSummaryExcel(HttpServletResponse response,
@Valid EmployeeOutputSummaryExportReqVO reqVO)
throws IOException {
projectOutputReportService.exportEmployeeOutputSummaryExcel(response, reqVO);
}
}

View File

@@ -0,0 +1,57 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 员工个人考核产值汇总 Excel 导出 Response VO")
@Data
@ExcelIgnoreUnannotated
public class EmployeeOutputSummaryExcelRespVO {
@ExcelProperty("序号")
private Integer serialNo;
@ExcelProperty("姓名")
private String employeeName;
@ExcelProperty("所属专业所")
private String officeName;
@ExcelProperty("第一季度")
private BigDecimal quarterOneAmount;
@ExcelProperty("第二季度")
private BigDecimal quarterTwoAmount;
@ExcelProperty("第三季度")
private BigDecimal quarterThreeAmount;
@ExcelProperty("第四季度")
private BigDecimal quarterFourAmount;
@ExcelProperty("年度合计")
private BigDecimal annualTotalAmount;
@ExcelProperty("所长/BIM考核产值")
private BigDecimal officeLeaderOrBimAmount;
@ExcelProperty("年度考核产值合计")
private BigDecimal totalAssessmentOutputAmount;
@ExcelProperty("1~12月份预计发生成本")
private BigDecimal expectedCostAmount;
@ExcelProperty("基本考核产值")
private BigDecimal basicAssessmentOutputAmount;
@ExcelProperty("剩余产值")
private BigDecimal remainingOutputAmount;
@ExcelProperty("预估年底绩效")
private BigDecimal estimatedYearEndPerformanceAmount;
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 员工个人考核产值汇总导出 Request VO")
@Data
public class EmployeeOutputSummaryExportReqVO {
@Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "年度不能为空")
private Integer year;
@Schema(description = "专业所 ID", example = "1")
private Long officeId;
@Schema(description = "员工 ID", example = "1")
private Long employeeId;
@Schema(description = "员工状态", example = "在职")
private String employeeStatus;
@Schema(description = "排序方式", example = "annual_total_desc")
private String sortType;
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 项目考核产值预算表导出 Request VO")
@Data
public class ProjectBudgetExportReqVO {
@Schema(description = "项目 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "项目 ID 不能为空")
private Long projectId;
@Schema(description = "导出年度", example = "2026")
private Integer year;
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 工程负责人年度季度计取表导出 Request VO")
@Data
public class ProjectLeadQuarterOutputExportReqVO {
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
private Long planningId;
@Schema(description = "导出年度", example = "2026")
private Integer year;
}

View File

@@ -0,0 +1,57 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 项目总览表 Excel 导出 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ProjectOverviewExcelRespVO {
@ExcelProperty("序号")
private Integer serialNo;
@ExcelProperty("项目名称")
private String projectName;
@ExcelProperty("项目任务包")
private String planningContent;
@ExcelProperty("工程进度情况及其它说明")
private String progressText;
@ExcelProperty("工作阶段")
private String designStage;
@ExcelProperty("本专业+项总核算总产值")
private BigDecimal totalOutputAmount;
@ExcelProperty("往年已发放比例")
private String historicalIssuedRatioText;
@ExcelProperty("本期结算")
private BigDecimal currentSettlementAmount;
@ExcelProperty("未结算比例")
private String pendingRatioText;
@ExcelProperty("一季度")
private BigDecimal quarterOneAmount;
@ExcelProperty("二季度")
private BigDecimal quarterTwoAmount;
@ExcelProperty("三季度")
private BigDecimal quarterThreeAmount;
@ExcelProperty("四季度")
private BigDecimal quarterFourAmount;
@ExcelProperty("本年度小计")
private BigDecimal yearTotalAmount;
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "Management Backend - Project overview export Request VO")
@Data
public class ProjectOverviewExportReqVO {
@Schema(description = "Year", example = "2026")
private Integer year;
@Schema(description = "Office ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "专业所不能为空")
private Long officeId;
@Schema(description = "Sort type", example = "output_desc")
private String sortType;
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 项目级年度季度计取表导出 Request VO")
@Data
public class ProjectQuarterOutputExportReqVO {
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
private Long planningId;
@Schema(description = "导出年度", example = "2026")
private Integer year;
}

View File

@@ -0,0 +1,24 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 专业内人员年度季度计取表导出 Request VO")
@Data
public class SpecialtyPersonOutputExportReqVO {
@Schema(description = "合约规划 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "合约规划 ID 不能为空")
private Long planningId;
@Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "water")
@NotBlank(message = "专业编码不能为空")
private String specialtyCode;
@Schema(description = "导出年度", example = "2026")
private Integer year;
}

View File

@@ -9,8 +9,11 @@ import java.math.BigDecimal;
@Data
public class SpecialtyRolePersonRespVO {
@Schema(description = "人员名称")
private String personName;
@Schema(description = "员工 ID")
private Long employeeId;
@Schema(description = "员工姓名")
private String employeeName;
@Schema(description = "人员比例")
private BigDecimal personRatio;

View File

@@ -9,8 +9,11 @@ import java.math.BigDecimal;
@Data
public class SpecialtyRolePersonSaveReqVO {
@Schema(description = "人员名称", example = "张三")
private String personName;
@Schema(description = "员工 ID", example = "1")
private Long employeeId;
@Schema(description = "员工姓名", example = "张三")
private String employeeName;
@Schema(description = "人员比例", example = "0.1000")
private BigDecimal personRatio;

View File

@@ -16,13 +16,13 @@ public class SpecialtyRoleSplitRespVO {
@Schema(description = "页面4拆分 ID", example = "1")
private Long outputSplitId;
@Schema(description = "规划 ID", example = "1")
@Schema(description = "规划 ID", example = "1")
private Long planningId;
@Schema(description = "项目名称")
private String projectName;
@Schema(description = "规划内容")
@Schema(description = "项目任务包")
private String planningContent;
@Schema(description = "专业编码")

View File

@@ -0,0 +1,104 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.service.yearkvalue.YearKValueService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.Map;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 年度 K 值")
@RestController
@RequestMapping("/tjt/year-k-value")
@Validated
public class YearKValueController {
@Resource
private YearKValueService yearKValueService;
@PostMapping("/create")
@Operation(summary = "创建年度 K 值")
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:create')")
public CommonResult<Long> createYearKValue(@RequestBody Map<String, Object> requestBody) {
YearKValueSaveReqVO createReqVO = buildSaveReqVO(requestBody, false);
return success(yearKValueService.createYearKValue(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改年度 K 值")
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:update')")
public CommonResult<Boolean> updateYearKValue(@RequestBody Map<String, Object> requestBody) {
YearKValueSaveReqVO updateReqVO = buildSaveReqVO(requestBody, true);
yearKValueService.updateYearKValue(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除年度 K 值")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:delete')")
public CommonResult<Boolean> deleteYearKValue(@RequestParam("id") Long id) {
yearKValueService.deleteYearKValue(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得年度 K 值详情")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')")
public CommonResult<YearKValueRespVO> getYearKValue(@RequestParam("id") Long id) {
return success(yearKValueService.getYearKValue(id));
}
@GetMapping("/page")
@Operation(summary = "获得年度 K 值分页")
@PreAuthorize("@ss.hasPermission('tjt:year-k-value:query')")
public CommonResult<PageResult<YearKValueRespVO>> getYearKValuePage(@Valid YearKValuePageReqVO pageReqVO) {
return success(yearKValueService.getYearKValuePage(pageReqVO));
}
private YearKValueSaveReqVO buildSaveReqVO(Map<String, Object> requestBody, boolean requireId) {
YearKValueSaveReqVO reqVO = BeanUtils.toBean(requestBody, YearKValueSaveReqVO.class);
if (requireId && reqVO.getId() == null) {
throw invalidParamException("编号不能为空");
}
if (reqVO.getKYear() == null) {
throw invalidParamException("年度不能为空");
}
if (reqVO.getKValue() == null) {
throw invalidParamException("K值不能为空");
}
if (reqVO.getKValue().compareTo(BigDecimal.ZERO) < 0
|| reqVO.getKValue().compareTo(BigDecimal.ONE) > 0) {
throw invalidParamException("K值必须在 0% 到 100% 之间");
}
if (reqVO.getRemark() != null && reqVO.getRemark().length() > 255) {
throw invalidParamException("备注长度不能超过 255 个字符");
}
return reqVO;
}
}

View File

@@ -0,0 +1,19 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - 年度 K 值分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class YearKValuePageReqVO extends PageParam {
@Schema(description = "年度", example = "2026")
private Integer kYear;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 年度 K 值 Response VO")
@Data
public class YearKValueRespVO {
@Schema(description = "主键 ID", example = "1")
private Long id;
@Schema(description = "年度", example = "2026")
private Integer kYear;
@Schema(description = "K 值", example = "0.4000")
private BigDecimal kValue;
@Schema(description = "备注")
private String remark;
@Schema(description = "是否启用")
private Boolean enabledFlag;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 年度 K 值新增/修改 Request VO")
@Data
public class YearKValueSaveReqVO {
@Schema(description = "主键 ID", example = "1")
private Long id;
@Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
@NotNull(message = "年度不能为空")
private Integer kYear;
@Schema(description = "K 值", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.4000")
@NotNull(message = "K 值不能为空")
@DecimalMin(value = "0.0000", message = "K 值不能小于 0%")
@DecimalMax(value = "1.0000", message = "K 值不能大于 100%")
private BigDecimal kValue;
@Schema(description = "备注")
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
@Schema(description = "是否启用", example = "true")
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
@TableName("tjt_employee")
@KeySequence("tjt_employee_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class EmployeeDO extends TenantBaseDO {
@TableId
private Long id;
private String employeeName;
private String gender;
private Long officeId;
private String registrationType;
private String jobTitle;
private String registrationSealNo;
private LocalDate entryDate;
private LocalDate leaveDate;
private String employeeStatus;
private String remark;
private Integer sortNo;
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@TableName("tjt_employee_year_cost_budget")
@KeySequence("tjt_employee_year_cost_budget_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class EmployeeYearCostBudgetDO extends TenantBaseDO {
@TableId
private Long id;
private Long employeeId;
private String employeeName;
private Integer budgetYear;
private BigDecimal expectedCostAmount;
private String remark;
private Integer sortNo;
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.office;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@TableName("tjt_office")
@KeySequence("tjt_office_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class OfficeDO extends TenantBaseDO {
@TableId
private Long id;
private String officeName;
private String officeCode;
private Integer sortNo;
private Boolean enabledFlag;
private String remark;
}

View File

@@ -29,9 +29,7 @@ public class ProjectOutputSplitDO extends TenantBaseDO {
private Integer year;
private BigDecimal projectManagerRatio;
private BigDecimal engineeringLeaderRatio;
private BigDecimal projectLeadRatio;
private BigDecimal officeRatio;

View File

@@ -25,6 +25,8 @@ public class ProjectPlanningDO extends TenantBaseDO {
private Long projectId;
private Integer sortNo;
private String ownershipType;
private String calculationMethod;
@@ -33,10 +35,20 @@ public class ProjectPlanningDO extends TenantBaseDO {
private BigDecimal planningAmount;
private BigDecimal contractValueQuantity;
private BigDecimal contractValueUnitPrice;
private BigDecimal managementFeeRate;
private BigDecimal managementFee;
private BigDecimal vatRate;
private BigDecimal vatAmount;
private BigDecimal projectBudgetOutputValue;
private String implementationTeam;
private Integer planningStartYear;
@@ -77,8 +89,6 @@ public class ProjectPlanningDO extends TenantBaseDO {
private BigDecimal guidanceTotalPrice;
private BigDecimal virtualTotalPrice;
private BigDecimal calculationRatio;
private BigDecimal contractUnitPrice;

View File

@@ -0,0 +1,55 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@TableName("tjt_project_planning_guide_detail")
@KeySequence("tjt_project_planning_guide_detail_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectPlanningGuideDetailDO extends TenantBaseDO {
@TableId
private Long id;
private Long planningId;
private Long projectId;
private String designPart;
private String buildingType;
private BigDecimal designArea;
private BigDecimal internalGuidanceUnitPrice;
private Integer buildingOrUnitCount;
private BigDecimal drawingSetFactor;
private BigDecimal scaleFactor;
private BigDecimal modificationFactor;
private BigDecimal complexityFactor;
private BigDecimal totalAdjustmentFactor;
private BigDecimal designRatio;
private BigDecimal assessmentArea;
private BigDecimal assessmentOutputValue;
private Integer sortNo;
private String remark;
}

View File

@@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 项目 DO
@@ -27,15 +28,19 @@ public class ProjectDO extends TenantBaseDO {
@TableId
private Long id;
/**
* 工程名称
* 项目名称
*/
private String projectName;
/**
* 排序
*/
private Integer sortNo;
/**
* 是否签订合同
*/
private Boolean contractSignedFlag;
/**
* 合同金额
* 合同产值
*/
private BigDecimal contractAmount;
/**
@@ -47,40 +52,60 @@ public class ProjectDO extends TenantBaseDO {
*/
private String constructionUnitName;
/**
* 联系人
* 建设单位联系人
*/
private String contactName;
/**
* 联系方式
* 建设单位联系电话
*/
private String contactPhone;
/**
* 合同签订日期
*/
private LocalDate contractSigningDate;
/**
* 项目经理
*/
private String projectManagerName;
/**
* 工程负责人
*/
private String engineeringPrincipalName;
/**
* 工程类型
*/
private String projectType;
/**
* 工程类别
*/
private String projectCategory;
/**
* 项目开始年度
*/
private Integer projectStartYear;
/**
* 项目状态
*/
private String projectStatus;
/**
* 是否封档
*/
private Boolean archiveFlag;
/**
* 封档时间
*/
private LocalDateTime archiveTime;
/**
* 暂停原因
*/
private String pauseReason;
/**
* 中止原因
*/
private String terminateReason;
/**
* 最终结算金额
*/
private BigDecimal finalSettlementAmount;
/**
* 预计 K 值
* 科创产值比例
*/
private BigDecimal expectedKValue;
private BigDecimal innovationOutputRate;
/**
* 其他成本
*/
private BigDecimal otherCost;
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@TableName("tjt_project_role_person")
@KeySequence("tjt_project_role_person_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectRolePersonDO extends TenantBaseDO {
@TableId
private Long id;
private Long projectId;
private String roleCode;
private String roleName;
private Long employeeId;
private String employeeName;
private Integer sortNo;
private Boolean enabledFlag;
}

View File

@@ -35,7 +35,7 @@ public class SpecialtyRoleSplitDO extends TenantBaseDO {
private BigDecimal roleRatio;
private String personNames;
private String personsJson;
private Integer sortNo;

View File

@@ -0,0 +1,29 @@
package cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue;
import cn.iocoder.lyzsys.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@TableName("tjt_year_k_value")
@KeySequence("tjt_year_k_value_seq")
@Data
@EqualsAndHashCode(callSuper = true)
public class YearKValueDO extends TenantBaseDO {
@TableId
private Long id;
private Integer kYear;
private BigDecimal kValue;
private String remark;
private Boolean enabledFlag;
}

View File

@@ -0,0 +1,40 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.employee;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface EmployeeMapper extends BaseMapperX<EmployeeDO> {
default PageResult<EmployeeDO> selectPage(EmployeePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<EmployeeDO>()
.likeIfPresent(EmployeeDO::getEmployeeName, reqVO.getEmployeeName())
.eqIfPresent(EmployeeDO::getOfficeId, reqVO.getOfficeId())
.eqIfPresent(EmployeeDO::getEmployeeStatus, reqVO.getEmployeeStatus())
.eqIfPresent(EmployeeDO::getEnabledFlag, reqVO.getEnabledFlag())
.orderByAsc(EmployeeDO::getSortNo)
.orderByAsc(EmployeeDO::getId));
}
default List<EmployeeDO> selectSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag) {
return selectList(new LambdaQueryWrapperX<EmployeeDO>()
.likeIfPresent(EmployeeDO::getEmployeeName, keyword)
.eqIfPresent(EmployeeDO::getOfficeId, officeId)
.eqIfPresent(EmployeeDO::getEmployeeStatus, status)
.eqIfPresent(EmployeeDO::getEnabledFlag, enabledFlag)
.orderByAsc(EmployeeDO::getSortNo)
.orderByAsc(EmployeeDO::getId));
}
default Long selectCountByOfficeId(Long officeId) {
return selectCount(new LambdaQueryWrapperX<EmployeeDO>()
.eq(EmployeeDO::getOfficeId, officeId));
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget.EmployeeYearCostBudgetDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeYearCostBudgetMapper extends BaseMapperX<EmployeeYearCostBudgetDO> {
default PageResult<EmployeeYearCostBudgetDO> selectPage(EmployeeYearCostBudgetPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<EmployeeYearCostBudgetDO>()
.eqIfPresent(EmployeeYearCostBudgetDO::getEmployeeId, reqVO.getEmployeeId())
.likeIfPresent(EmployeeYearCostBudgetDO::getEmployeeName, reqVO.getEmployeeName())
.eqIfPresent(EmployeeYearCostBudgetDO::getBudgetYear, reqVO.getBudgetYear())
.eqIfPresent(EmployeeYearCostBudgetDO::getEnabledFlag, reqVO.getEnabledFlag())
.orderByDesc(EmployeeYearCostBudgetDO::getBudgetYear)
.orderByAsc(EmployeeYearCostBudgetDO::getSortNo)
.orderByAsc(EmployeeYearCostBudgetDO::getId));
}
default EmployeeYearCostBudgetDO selectByEmployeeIdAndBudgetYear(Long employeeId, Integer budgetYear) {
return selectOne(new LambdaQueryWrapperX<EmployeeYearCostBudgetDO>()
.eq(EmployeeYearCostBudgetDO::getEmployeeId, employeeId)
.eq(EmployeeYearCostBudgetDO::getBudgetYear, budgetYear));
}
default Long selectCountByEmployeeId(Long employeeId) {
return selectCount(new LambdaQueryWrapperX<EmployeeYearCostBudgetDO>()
.eq(EmployeeYearCostBudgetDO::getEmployeeId, employeeId));
}
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.office;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface OfficeMapper extends BaseMapperX<OfficeDO> {
default PageResult<OfficeDO> selectPage(OfficePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<OfficeDO>()
.likeIfPresent(OfficeDO::getOfficeName, reqVO.getOfficeName())
.eqIfPresent(OfficeDO::getEnabledFlag, reqVO.getEnabledFlag())
.orderByAsc(OfficeDO::getSortNo)
.orderByAsc(OfficeDO::getId));
}
default List<OfficeDO> selectSimpleList() {
return selectList(new LambdaQueryWrapperX<OfficeDO>()
.eq(OfficeDO::getEnabledFlag, Boolean.TRUE)
.orderByAsc(OfficeDO::getSortNo)
.orderByAsc(OfficeDO::getId));
}
}

View File

@@ -25,7 +25,8 @@ public interface ProjectPlanningMapper extends BaseMapperX<ProjectPlanningDO> {
.likeIfPresent(ProjectPlanningDO::getPlanningContent, reqVO.getPlanningContent())
.eqIfPresent(ProjectPlanningDO::getPlanningStartYear, reqVO.getPlanningStartYear())
.betweenIfPresent(ProjectPlanningDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(ProjectPlanningDO::getId));
.orderByAsc(ProjectPlanningDO::getSortNo)
.orderByAsc(ProjectPlanningDO::getId));
}
default List<ProjectPlanningDO> selectListByProjectId(Long projectId) {
@@ -34,4 +35,11 @@ public interface ProjectPlanningMapper extends BaseMapperX<ProjectPlanningDO> {
.orderByDesc(ProjectPlanningDO::getId));
}
default List<ProjectPlanningDO> selectDisplayListByProjectId(Long projectId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningDO>()
.eq(ProjectPlanningDO::getProjectId, projectId)
.orderByAsc(ProjectPlanningDO::getSortNo)
.orderByAsc(ProjectPlanningDO::getId));
}
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.planningguidedetail;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
public interface ProjectPlanningGuideDetailMapper extends BaseMapperX<ProjectPlanningGuideDetailDO> {
default List<ProjectPlanningGuideDetailDO> selectListByPlanningId(Long planningId) {
return selectList(new LambdaQueryWrapperX<ProjectPlanningGuideDetailDO>()
.eq(ProjectPlanningGuideDetailDO::getPlanningId, planningId)
.orderByAsc(ProjectPlanningGuideDetailDO::getSortNo)
.orderByAsc(ProjectPlanningGuideDetailDO::getId));
}
default List<ProjectPlanningGuideDetailDO> selectListByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<ProjectPlanningGuideDetailDO>()
.in(ProjectPlanningGuideDetailDO::getPlanningId, planningIds)
.orderByAsc(ProjectPlanningGuideDetailDO::getPlanningId)
.orderByAsc(ProjectPlanningGuideDetailDO::getSortNo)
.orderByAsc(ProjectPlanningGuideDetailDO::getId));
}
}

View File

@@ -20,8 +20,10 @@ public interface ProjectMapper extends BaseMapperX<ProjectDO> {
.likeIfPresent(ProjectDO::getProjectName, reqVO.getProjectName())
.eqIfPresent(ProjectDO::getContractSignedFlag, reqVO.getContractSignedFlag())
.eqIfPresent(ProjectDO::getProjectStartYear, reqVO.getProjectStartYear())
.eqIfPresent(ProjectDO::getProjectStatus, reqVO.getProjectStatus())
.betweenIfPresent(ProjectDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(ProjectDO::getId));
.orderByAsc(ProjectDO::getSortNo)
.orderByAsc(ProjectDO::getId));
}
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
public interface ProjectRolePersonMapper extends BaseMapperX<ProjectRolePersonDO> {
default List<ProjectRolePersonDO> selectListByProjectId(Long projectId) {
return selectList(new LambdaQueryWrapperX<ProjectRolePersonDO>()
.eq(ProjectRolePersonDO::getProjectId, projectId)
.eq(ProjectRolePersonDO::getEnabledFlag, Boolean.TRUE)
.orderByAsc(ProjectRolePersonDO::getRoleCode)
.orderByAsc(ProjectRolePersonDO::getSortNo)
.orderByAsc(ProjectRolePersonDO::getId));
}
default List<ProjectRolePersonDO> selectListByProjectIds(Collection<Long> projectIds) {
if (projectIds == null || projectIds.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<ProjectRolePersonDO>()
.in(ProjectRolePersonDO::getProjectId, projectIds)
.eq(ProjectRolePersonDO::getEnabledFlag, Boolean.TRUE)
.orderByAsc(ProjectRolePersonDO::getProjectId)
.orderByAsc(ProjectRolePersonDO::getRoleCode)
.orderByAsc(ProjectRolePersonDO::getSortNo)
.orderByAsc(ProjectRolePersonDO::getId));
}
default Long selectCountByEmployeeId(Long employeeId) {
return selectCount(new LambdaQueryWrapperX<ProjectRolePersonDO>()
.eq(ProjectRolePersonDO::getEmployeeId, employeeId));
}
}

View File

@@ -22,4 +22,9 @@ public interface SpecialtyRoleSplitMapper extends BaseMapperX<SpecialtyRoleSplit
.orderByAsc(SpecialtyRoleSplitDO::getId));
}
default List<SpecialtyRoleSplitDO> selectListByPersonsJsonKeyword(String keyword) {
return selectList(new LambdaQueryWrapperX<SpecialtyRoleSplitDO>()
.like(SpecialtyRoleSplitDO::getPersonsJson, keyword));
}
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
public interface YearKValueMapper extends BaseMapperX<YearKValueDO> {
default PageResult<YearKValueDO> selectPage(YearKValuePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<YearKValueDO>()
.eqIfPresent(YearKValueDO::getKYear, reqVO.getKYear())
.eqIfPresent(YearKValueDO::getEnabledFlag, reqVO.getEnabledFlag())
.orderByAsc(YearKValueDO::getKYear));
}
default YearKValueDO selectByKYear(Integer kYear) {
return selectOne(new LambdaQueryWrapperX<YearKValueDO>()
.eq(YearKValueDO::getKYear, kYear));
}
default YearKValueDO selectEnabledByKYear(Integer kYear) {
return selectOne(new LambdaQueryWrapperX<YearKValueDO>()
.eq(YearKValueDO::getKYear, kYear)
.eq(YearKValueDO::getEnabledFlag, Boolean.TRUE));
}
default List<YearKValueDO> selectEnabledListByYears(Collection<Integer> years) {
if (years == null || years.isEmpty()) {
return Collections.emptyList();
}
return selectList(new LambdaQueryWrapperX<YearKValueDO>()
.in(YearKValueDO::getKYear, years)
.eq(YearKValueDO::getEnabledFlag, Boolean.TRUE));
}
}

View File

@@ -20,6 +20,11 @@ public interface ErrorCodeConstants {
ErrorCode PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE = new ErrorCode(1_020_002_004, "归属类型保存后不允许修改");
ErrorCode PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE = new ErrorCode(1_020_002_005, "产值计算方式保存后不允许修改");
ErrorCode PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID = new ErrorCode(1_020_002_006, "虚拟产值计算方式不正确");
ErrorCode PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED = new ErrorCode(1_020_002_007, "工日法下工日不能为空");
ErrorCode PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED = new ErrorCode(1_020_002_008, "工日法下工日单价不能为空");
ErrorCode PROJECT_PLANNING_DESIGN_PART_INVALID = new ErrorCode(1_020_002_009, "设计部位不正确");
ErrorCode PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS = new ErrorCode(1_020_002_010, "指导价法明细不存在");
ErrorCode PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID = new ErrorCode(1_020_002_011, "当前合约规划不是专业所指导价法,不能维护指导价法明细");
// ========== 季度分配管理 1-020-003-000 ==========
ErrorCode PROJECT_PLANNING_QUARTER_NOT_EXISTS = new ErrorCode(1_020_003_000, "季度分配明细不存在");
@@ -28,7 +33,6 @@ public interface ErrorCodeConstants {
// ========== 页面 4 拆分管理 1-020-004-000 ==========
ErrorCode PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS = new ErrorCode(1_020_004_000, "关联合约规划不存在");
ErrorCode PROJECT_OUTPUT_SPLIT_NOT_MAJOR = new ErrorCode(1_020_004_001, "仅专业所记录允许进行页面4拆分");
ErrorCode PROJECT_OUTPUT_SPLIT_RATIO_INVALID = new ErrorCode(1_020_004_002, "页面4比例合计必须等于 100%");
// ========== 页面 5 角色拆分管理 1-020-005-000 ==========
@@ -36,8 +40,24 @@ public interface ErrorCodeConstants {
ErrorCode SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID = new ErrorCode(1_020_005_001, "页面5五类角色比例合计必须等于 100%");
ErrorCode SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID = new ErrorCode(1_020_005_002, "专业编码不正确");
ErrorCode SPECIALTY_ROLE_SPLIT_ROLE_INVALID = new ErrorCode(1_020_005_003, "角色编码不正确");
ErrorCode SPECIALTY_ROLE_SPLIT_PERSON_INVALID = new ErrorCode(1_020_005_004, "页面5人员名称和比例必须同时填写");
ErrorCode SPECIALTY_ROLE_SPLIT_PERSON_INVALID = new ErrorCode(1_020_005_004, "页面5人员和比例必须同时填写");
ErrorCode SPECIALTY_ROLE_SPLIT_DESIGN_PERSON_REQUIRED = new ErrorCode(1_020_005_005, "页面5设计角色金额大于 0 时必须配置人员");
ErrorCode SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID = new ErrorCode(1_020_005_007, "页面5同一角色下人员比例之和不能大于 100%");
// ========== 年度 K 值管理 1-020-006-000 ==========
ErrorCode YEAR_K_VALUE_NOT_EXISTS = new ErrorCode(1_020_006_000, "年度 K 值记录不存在");
ErrorCode YEAR_K_VALUE_DUPLICATE = new ErrorCode(1_020_006_001, "同一年度的 K 值记录已存在");
// ========== 专业所管理 1-020-007-000 ==========
ErrorCode OFFICE_NOT_EXISTS = new ErrorCode(1_020_007_000, "专业所记录不存在");
ErrorCode OFFICE_IN_USE = new ErrorCode(1_020_007_001, "当前专业所已被员工引用,不能删除");
// ========== 员工管理 1-020-008-000 ==========
ErrorCode EMPLOYEE_NOT_EXISTS = new ErrorCode(1_020_008_000, "员工记录不存在");
ErrorCode EMPLOYEE_IN_USE = new ErrorCode(1_020_008_001, "当前员工已被业务数据引用,不能删除");
// ========== 员工年度成本预算 1-020-009-000 ==========
ErrorCode EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS = new ErrorCode(1_020_009_000, "员工年度成本预算记录不存在");
ErrorCode EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE = new ErrorCode(1_020_009_001, "当前员工该年度预计发生成本已存在");
}

View File

@@ -15,8 +15,7 @@ import java.util.Map;
*/
public final class OutputSplitBizConstants {
public static final String OWNERSHIP_TYPE_MAJOR = "专业所";
public static final String SPECIALTY_PROJECT_LEAD = "project_lead";
public static final String SPECIALTY_ARCH = "arch";
public static final String SPECIALTY_DECOR = "decor";
public static final String SPECIALTY_STRUCT = "struct";
@@ -30,18 +29,31 @@ public final class OutputSplitBizConstants {
public static final String ROLE_REVIEW = "review";
public static final String ROLE_APPROVE = "approve";
public static final String ROLE_DESIGN = "design";
public static final String ROLE_PROJECT_MANAGER = "project_manager";
public static final String ROLE_ENGINEERING_PRINCIPAL = "engineering_principal";
public static final List<SpecialtyItem> SPECIALTY_ITEMS = Arrays.asList(
public static final List<SpecialtyItem> ASSIGNMENT_SPECIALTY_ITEMS = Arrays.asList(
new SpecialtyItem(SPECIALTY_PROJECT_LEAD, "项目经理/工程负责人", 0),
new SpecialtyItem(SPECIALTY_ARCH, "建筑", 1),
new SpecialtyItem(SPECIALTY_DECOR, "", 2),
new SpecialtyItem(SPECIALTY_DECOR, "", 2),
new SpecialtyItem(SPECIALTY_STRUCT, "结构", 3),
new SpecialtyItem(SPECIALTY_WATER, "", 4),
new SpecialtyItem(SPECIALTY_WATER, "给排", 4),
new SpecialtyItem(SPECIALTY_ELEC, "电气", 5),
new SpecialtyItem(SPECIALTY_HVAC, "暖通", 6),
new SpecialtyItem(SPECIALTY_DIGITAL, "数字化设计", 7)
);
public static final List<RoleItem> ROLE_ITEMS = Arrays.asList(
public static final List<SpecialtyItem> SPECIALTY_ITEMS = Arrays.asList(
new SpecialtyItem(SPECIALTY_ARCH, "建筑", 1),
new SpecialtyItem(SPECIALTY_DECOR, "装饰", 2),
new SpecialtyItem(SPECIALTY_STRUCT, "结构", 3),
new SpecialtyItem(SPECIALTY_WATER, "给排水", 4),
new SpecialtyItem(SPECIALTY_ELEC, "电气", 5),
new SpecialtyItem(SPECIALTY_HVAC, "暖通", 6),
new SpecialtyItem(SPECIALTY_DIGITAL, "数字化设计", 7)
);
public static final List<RoleItem> SPECIALTY_ROLE_ITEMS = Arrays.asList(
new RoleItem(ROLE_DIRECTOR, "专业负责人", 1),
new RoleItem(ROLE_CHECK, "校对", 2),
new RoleItem(ROLE_REVIEW, "审核", 3),
@@ -49,23 +61,29 @@ public final class OutputSplitBizConstants {
new RoleItem(ROLE_DESIGN, "设计", 5)
);
public static final List<RoleItem> PROJECT_LEAD_ROLE_ITEMS = Arrays.asList(
new RoleItem(ROLE_PROJECT_MANAGER, "项目经理", 1),
new RoleItem(ROLE_ENGINEERING_PRINCIPAL, "工程负责人", 2)
);
private OutputSplitBizConstants() {
}
public static boolean isMajorOwnershipType(String value) {
return OWNERSHIP_TYPE_MAJOR.equals(value);
}
public static boolean isValidSpecialtyCode(String code) {
return SPECIALTY_ITEMS.stream().anyMatch(item -> item.getCode().equals(code));
return ASSIGNMENT_SPECIALTY_ITEMS.stream().anyMatch(item -> item.getCode().equals(code));
}
public static boolean isValidRoleCode(String code) {
return ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code));
return SPECIALTY_ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code))
|| PROJECT_LEAD_ROLE_ITEMS.stream().anyMatch(item -> item.getCode().equals(code));
}
public static boolean isValidRoleCode(String specialtyCode, String roleCode) {
return getRoleItems(specialtyCode).stream().anyMatch(item -> item.getCode().equals(roleCode));
}
public static String getSpecialtyName(String code) {
return SPECIALTY_ITEMS.stream()
return ASSIGNMENT_SPECIALTY_ITEMS.stream()
.filter(item -> item.getCode().equals(code))
.findFirst()
.map(SpecialtyItem::getName)
@@ -73,7 +91,7 @@ public final class OutputSplitBizConstants {
}
public static String getRoleName(String code) {
return ROLE_ITEMS.stream()
return getAllRoleItems().stream()
.filter(item -> item.getCode().equals(code))
.findFirst()
.map(RoleItem::getName)
@@ -81,7 +99,7 @@ public final class OutputSplitBizConstants {
}
public static int getRoleSortNo(String code) {
return ROLE_ITEMS.stream()
return getAllRoleItems().stream()
.filter(item -> item.getCode().equals(code))
.findFirst()
.map(RoleItem::getSortNo)
@@ -89,7 +107,7 @@ public final class OutputSplitBizConstants {
}
public static int getSpecialtySortNo(String code) {
return SPECIALTY_ITEMS.stream()
return ASSIGNMENT_SPECIALTY_ITEMS.stream()
.filter(item -> item.getCode().equals(code))
.findFirst()
.map(SpecialtyItem::getSortNo)
@@ -104,6 +122,25 @@ public final class OutputSplitBizConstants {
return map;
}
public static List<RoleItem> getRoleItems(String specialtyCode) {
if (SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) {
return PROJECT_LEAD_ROLE_ITEMS;
}
return SPECIALTY_ROLE_ITEMS;
}
private static List<RoleItem> getAllRoleItems() {
return Arrays.asList(
PROJECT_LEAD_ROLE_ITEMS.get(0),
PROJECT_LEAD_ROLE_ITEMS.get(1),
SPECIALTY_ROLE_ITEMS.get(0),
SPECIALTY_ROLE_ITEMS.get(1),
SPECIALTY_ROLE_ITEMS.get(2),
SPECIALTY_ROLE_ITEMS.get(3),
SPECIALTY_ROLE_ITEMS.get(4)
);
}
@Getter
@AllArgsConstructor
public static class SpecialtyItem {

View File

@@ -14,13 +14,15 @@ public final class ProjectPlanningBizTypeConstants {
public static final String OWNERSHIP_TYPE_MAJOR = "专业所";
public static final String OWNERSHIP_TYPE_COMPREHENSIVE = "综合所";
public static final String OWNERSHIP_TYPE_SUBCONTRACT = "专业分包";
public static final String DESIGN_PART_REAL_ESTATE = "地上部分";
public static final String DESIGN_PART_UNDERGROUND = "地下部分";
public static final String CALCULATION_METHOD_GUIDANCE_PRICE = "指导价法";
public static final String CALCULATION_METHOD_CONTRACT_PRICE = "合同价法";
public static final String CALCULATION_METHOD_VIRTUAL_OUTPUT = "虚拟产值法";
public static final String VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE = "指导单价法";
public static final String VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE = "虚拟总价法";
public static final String VIRTUAL_CALCULATION_METHOD_GUIDANCE_TOTAL_PRICE = "指导总价法";
public static final String VIRTUAL_CALCULATION_METHOD_WORKING_DAY = "工日法";
private static final Set<String> OWNERSHIP_TYPES = new HashSet<>(Arrays.asList(
@@ -35,9 +37,14 @@ public final class ProjectPlanningBizTypeConstants {
CALCULATION_METHOD_VIRTUAL_OUTPUT
));
private static final Set<String> DESIGN_PARTS = new HashSet<>(Arrays.asList(
DESIGN_PART_REAL_ESTATE,
DESIGN_PART_UNDERGROUND
));
private static final Set<String> VIRTUAL_CALCULATION_METHODS = new HashSet<>(Arrays.asList(
VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE,
VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE,
VIRTUAL_CALCULATION_METHOD_GUIDANCE_TOTAL_PRICE,
VIRTUAL_CALCULATION_METHOD_WORKING_DAY
));
@@ -52,6 +59,10 @@ public final class ProjectPlanningBizTypeConstants {
return CALCULATION_METHODS.contains(value);
}
public static boolean isValidDesignPart(String value) {
return DESIGN_PARTS.contains(value);
}
public static boolean isValidVirtualCalculationMethod(String value) {
return VIRTUAL_CALCULATION_METHODS.contains(value);
}
@@ -72,6 +83,10 @@ public final class ProjectPlanningBizTypeConstants {
return CALCULATION_METHOD_GUIDANCE_PRICE.equals(value);
}
public static boolean isMajorGuidanceScene(String ownershipType, String calculationMethod) {
return isMajor(ownershipType) && isGuidancePrice(calculationMethod);
}
public static boolean isContractPrice(String value) {
return CALCULATION_METHOD_CONTRACT_PRICE.equals(value);
}
@@ -80,4 +95,16 @@ public final class ProjectPlanningBizTypeConstants {
return CALCULATION_METHOD_VIRTUAL_OUTPUT.equals(value);
}
public static boolean isWorkingDay(String value) {
return VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(value);
}
public static boolean isVirtualGuidancePrice(String value) {
return VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE.equals(value);
}
public static boolean isVirtualGuidanceTotalPrice(String value) {
return VIRTUAL_CALCULATION_METHOD_GUIDANCE_TOTAL_PRICE.equals(value);
}
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.lyzsys.module.tjt.service.employee;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import javax.validation.Valid;
import java.util.List;
public interface EmployeeService {
Long createEmployee(@Valid EmployeeSaveReqVO createReqVO);
void updateEmployee(@Valid EmployeeSaveReqVO updateReqVO);
void deleteEmployee(Long id);
EmployeeRespVO getEmployee(Long id);
EmployeeDO validateEmployeeExists(Long id);
PageResult<EmployeeRespVO> getEmployeePage(EmployeePageReqVO pageReqVO);
List<EmployeeSimpleRespVO> getEmployeeSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag);
}

View File

@@ -0,0 +1,151 @@
package cn.iocoder.lyzsys.module.tjt.service.employee;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
import cn.iocoder.lyzsys.framework.common.util.json.JsonUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employee.vo.EmployeeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.specialtyrolesplit.SpecialtyRoleSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget.EmployeeYearCostBudgetMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.employee.EmployeeMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.office.OfficeMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.specialtyrolesplit.SpecialtyRoleSplitMapper;
import cn.iocoder.lyzsys.module.tjt.service.office.OfficeService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_IN_USE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_NOT_EXISTS;
@Service
@Validated
public class EmployeeServiceImpl implements EmployeeService {
@Resource
private EmployeeMapper employeeMapper;
@Resource
private OfficeMapper officeMapper;
@Resource
private EmployeeYearCostBudgetMapper employeeYearCostBudgetMapper;
@Resource
private ProjectRolePersonMapper projectRolePersonMapper;
@Resource
private SpecialtyRoleSplitMapper specialtyRoleSplitMapper;
@Resource
private OfficeService officeService;
@Override
public Long createEmployee(EmployeeSaveReqVO createReqVO) {
officeService.validateOfficeExists(createReqVO.getOfficeId());
EmployeeDO employee = BeanUtils.toBean(createReqVO, EmployeeDO.class);
if (employee.getEnabledFlag() == null) {
employee.setEnabledFlag(Boolean.TRUE);
}
employeeMapper.insert(employee);
return employee.getId();
}
@Override
public void updateEmployee(EmployeeSaveReqVO updateReqVO) {
validateEmployeeExists(updateReqVO.getId());
officeService.validateOfficeExists(updateReqVO.getOfficeId());
employeeMapper.updateById(BeanUtils.toBean(updateReqVO, EmployeeDO.class));
}
@Override
public void deleteEmployee(Long id) {
validateEmployeeExists(id);
if (employeeYearCostBudgetMapper.selectCountByEmployeeId(id) > 0
|| projectRolePersonMapper.selectCountByEmployeeId(id) > 0
|| existsInSpecialtyRoleSplit(id)) {
throw exception(EMPLOYEE_IN_USE);
}
employeeMapper.deleteById(id);
}
@Override
public EmployeeRespVO getEmployee(Long id) {
EmployeeDO employee = validateEmployeeExists(id);
Map<Long, OfficeDO> officeMap = getOfficeMap(Collections.singleton(employee.getOfficeId()));
return toRespVO(employee, officeMap);
}
@Override
public EmployeeDO validateEmployeeExists(Long id) {
EmployeeDO employee = employeeMapper.selectById(id);
if (employee == null) {
throw exception(EMPLOYEE_NOT_EXISTS);
}
return employee;
}
@Override
public PageResult<EmployeeRespVO> getEmployeePage(EmployeePageReqVO pageReqVO) {
PageResult<EmployeeDO> pageResult = employeeMapper.selectPage(pageReqVO);
Map<Long, OfficeDO> officeMap = getOfficeMap(CollectionUtils.convertSet(pageResult.getList(), EmployeeDO::getOfficeId));
return BeanUtils.toBean(pageResult, EmployeeRespVO.class, respVO ->
respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId())
? officeMap.get(respVO.getOfficeId()).getOfficeName() : null));
}
@Override
public List<EmployeeSimpleRespVO> getEmployeeSimpleList(String keyword, Long officeId, String status, Boolean enabledFlag) {
List<EmployeeDO> list = employeeMapper.selectSimpleList(keyword, officeId, status, enabledFlag == null ? Boolean.TRUE : enabledFlag);
Map<Long, OfficeDO> officeMap = getOfficeMap(CollectionUtils.convertSet(list, EmployeeDO::getOfficeId));
return BeanUtils.toBean(list, EmployeeSimpleRespVO.class, respVO ->
respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId())
? officeMap.get(respVO.getOfficeId()).getOfficeName() : null));
}
private boolean existsInSpecialtyRoleSplit(Long employeeId) {
List<SpecialtyRoleSplitDO> candidateList = specialtyRoleSplitMapper.selectListByPersonsJsonKeyword("\"employeeId\":" + employeeId);
for (SpecialtyRoleSplitDO item : candidateList) {
List<SpecialtyRolePersonSaveReqVO> persons = JsonUtils.parseArray(item.getPersonsJson(), SpecialtyRolePersonSaveReqVO.class);
if (persons == null) {
continue;
}
boolean matched = persons.stream()
.filter(Objects::nonNull)
.anyMatch(person -> Objects.equals(person.getEmployeeId(), employeeId));
if (matched) {
return true;
}
}
return false;
}
private Map<Long, OfficeDO> getOfficeMap(Set<Long> officeIds) {
if (officeIds == null || officeIds.isEmpty()) {
return Collections.emptyMap();
}
Set<Long> validOfficeIds = officeIds.stream()
.filter(Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (validOfficeIds.isEmpty()) {
return Collections.emptyMap();
}
return CollectionUtils.convertMap(officeMapper.selectBatchIds(validOfficeIds), OfficeDO::getId);
}
private EmployeeRespVO toRespVO(EmployeeDO employee, Map<Long, OfficeDO> officeMap) {
return BeanUtils.toBean(employee, EmployeeRespVO.class, respVO ->
respVO.setOfficeName(officeMap.containsKey(respVO.getOfficeId())
? officeMap.get(respVO.getOfficeId()).getOfficeName() : null));
}
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO;
import javax.validation.Valid;
public interface EmployeeYearCostBudgetService {
Long createEmployeeYearCostBudget(@Valid EmployeeYearCostBudgetSaveReqVO createReqVO);
void updateEmployeeYearCostBudget(@Valid EmployeeYearCostBudgetSaveReqVO updateReqVO);
void deleteEmployeeYearCostBudget(Long id);
EmployeeYearCostBudgetRespVO getEmployeeYearCostBudget(Long id);
PageResult<EmployeeYearCostBudgetRespVO> getEmployeeYearCostBudgetPage(EmployeeYearCostBudgetPageReqVO pageReqVO);
}

View File

@@ -0,0 +1,85 @@
package cn.iocoder.lyzsys.module.tjt.service.employeeyearcostbudget;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.employeeyearcostbudget.vo.EmployeeYearCostBudgetSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employeeyearcostbudget.EmployeeYearCostBudgetDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.employeeyearcostbudget.EmployeeYearCostBudgetMapper;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Objects;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS;
@Service
@Validated
public class EmployeeYearCostBudgetServiceImpl implements EmployeeYearCostBudgetService {
@Resource
private EmployeeYearCostBudgetMapper employeeYearCostBudgetMapper;
@Resource
private EmployeeService employeeService;
@Override
public Long createEmployeeYearCostBudget(EmployeeYearCostBudgetSaveReqVO createReqVO) {
EmployeeDO employee = employeeService.validateEmployeeExists(createReqVO.getEmployeeId());
validateDuplicate(null, createReqVO.getEmployeeId(), createReqVO.getBudgetYear());
EmployeeYearCostBudgetDO budget = BeanUtils.toBean(createReqVO, EmployeeYearCostBudgetDO.class);
budget.setEmployeeName(employee.getEmployeeName());
if (budget.getEnabledFlag() == null) {
budget.setEnabledFlag(Boolean.TRUE);
}
employeeYearCostBudgetMapper.insert(budget);
return budget.getId();
}
@Override
public void updateEmployeeYearCostBudget(EmployeeYearCostBudgetSaveReqVO updateReqVO) {
validateExists(updateReqVO.getId());
EmployeeDO employee = employeeService.validateEmployeeExists(updateReqVO.getEmployeeId());
validateDuplicate(updateReqVO.getId(), updateReqVO.getEmployeeId(), updateReqVO.getBudgetYear());
EmployeeYearCostBudgetDO updateObj = BeanUtils.toBean(updateReqVO, EmployeeYearCostBudgetDO.class);
updateObj.setEmployeeName(employee.getEmployeeName());
employeeYearCostBudgetMapper.updateById(updateObj);
}
@Override
public void deleteEmployeeYearCostBudget(Long id) {
validateExists(id);
employeeYearCostBudgetMapper.deleteById(id);
}
@Override
public EmployeeYearCostBudgetRespVO getEmployeeYearCostBudget(Long id) {
return BeanUtils.toBean(validateExists(id), EmployeeYearCostBudgetRespVO.class);
}
@Override
public PageResult<EmployeeYearCostBudgetRespVO> getEmployeeYearCostBudgetPage(EmployeeYearCostBudgetPageReqVO pageReqVO) {
return BeanUtils.toBean(employeeYearCostBudgetMapper.selectPage(pageReqVO), EmployeeYearCostBudgetRespVO.class);
}
private EmployeeYearCostBudgetDO validateExists(Long id) {
EmployeeYearCostBudgetDO budget = employeeYearCostBudgetMapper.selectById(id);
if (budget == null) {
throw exception(EMPLOYEE_YEAR_COST_BUDGET_NOT_EXISTS);
}
return budget;
}
private void validateDuplicate(Long id, Long employeeId, Integer budgetYear) {
EmployeeYearCostBudgetDO duplicate = employeeYearCostBudgetMapper.selectByEmployeeIdAndBudgetYear(employeeId, budgetYear);
if (duplicate != null && !Objects.equals(duplicate.getId(), id)) {
throw exception(EMPLOYEE_YEAR_COST_BUDGET_DUPLICATE);
}
}
}

View File

@@ -0,0 +1,29 @@
package cn.iocoder.lyzsys.module.tjt.service.office;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO;
import javax.validation.Valid;
import java.util.List;
public interface OfficeService {
Long createOffice(@Valid OfficeSaveReqVO createReqVO);
void updateOffice(@Valid OfficeSaveReqVO updateReqVO);
void deleteOffice(Long id);
OfficeRespVO getOffice(Long id);
OfficeDO validateOfficeExists(Long id);
PageResult<OfficeRespVO> getOfficePage(OfficePageReqVO pageReqVO);
List<OfficeSimpleRespVO> getOfficeSimpleList();
}

View File

@@ -0,0 +1,80 @@
package cn.iocoder.lyzsys.module.tjt.service.office;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.office.vo.OfficeSimpleRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.office.OfficeDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.employee.EmployeeMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.office.OfficeMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.OFFICE_IN_USE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.OFFICE_NOT_EXISTS;
@Service
@Validated
public class OfficeServiceImpl implements OfficeService {
@Resource
private OfficeMapper officeMapper;
@Resource
private EmployeeMapper employeeMapper;
@Override
public Long createOffice(OfficeSaveReqVO createReqVO) {
OfficeDO office = BeanUtils.toBean(createReqVO, OfficeDO.class);
if (office.getEnabledFlag() == null) {
office.setEnabledFlag(Boolean.TRUE);
}
officeMapper.insert(office);
return office.getId();
}
@Override
public void updateOffice(OfficeSaveReqVO updateReqVO) {
validateOfficeExists(updateReqVO.getId());
officeMapper.updateById(BeanUtils.toBean(updateReqVO, OfficeDO.class));
}
@Override
public void deleteOffice(Long id) {
validateOfficeExists(id);
if (employeeMapper.selectCountByOfficeId(id) > 0) {
throw exception(OFFICE_IN_USE);
}
officeMapper.deleteById(id);
}
@Override
public OfficeRespVO getOffice(Long id) {
return BeanUtils.toBean(validateOfficeExists(id), OfficeRespVO.class);
}
@Override
public OfficeDO validateOfficeExists(Long id) {
OfficeDO office = officeMapper.selectById(id);
if (office == null) {
throw exception(OFFICE_NOT_EXISTS);
}
return office;
}
@Override
public PageResult<OfficeRespVO> getOfficePage(OfficePageReqVO pageReqVO) {
return BeanUtils.toBean(officeMapper.selectPage(pageReqVO), OfficeRespVO.class);
}
@Override
public List<OfficeSimpleRespVO> getOfficeSimpleList() {
return BeanUtils.toBean(officeMapper.selectSimpleList(), OfficeSimpleRespVO.class);
}
}

View File

@@ -6,9 +6,11 @@ import cn.iocoder.lyzsys.module.tjt.controller.admin.outputsplit.vo.ProjectOutpu
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.outputsplit.ProjectOutputSplitMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -18,13 +20,14 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_NOT_MAJOR;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_OUTPUT_SPLIT_RATIO_INVALID;
@@ -49,10 +52,12 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ProjectRolePersonMapper projectRolePersonMapper;
@Override
public ProjectOutputSplitRespVO getProjectOutputSplit(Long planningId) {
ProjectPlanningDO planning = validateMajorPlanning(planningId);
ProjectPlanningDO planning = validatePlanningExists(planningId);
ProjectDO project = validateProjectExists(planning.getProjectId());
ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId);
if (outputSplit == null) {
@@ -63,7 +68,7 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
@Override
public Long saveProjectOutputSplit(ProjectOutputSplitSaveReqVO reqVO) {
ProjectPlanningDO planning = validateMajorPlanning(reqVO.getPlanningId());
ProjectPlanningDO planning = validatePlanningExists(reqVO.getPlanningId());
validateOutputSplitRatios(reqVO);
ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(reqVO.getPlanningId());
if (outputSplit == null) {
@@ -86,7 +91,7 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
@Override
public ProjectOutputSplitDO getOrCreateProjectOutputSplit(Long planningId) {
ProjectPlanningDO planning = validateMajorPlanning(planningId);
ProjectPlanningDO planning = validatePlanningExists(planningId);
ProjectOutputSplitDO outputSplit = projectOutputSplitMapper.selectByPlanningId(planningId);
if (outputSplit != null) {
return outputSplit;
@@ -134,18 +139,20 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
private ProjectOutputSplitRespVO buildRespVO(ProjectOutputSplitDO outputSplit, ProjectPlanningDO planning, ProjectDO project) {
ProjectOutputSplitRespVO respVO = BeanUtils.toBean(outputSplit, ProjectOutputSplitRespVO.class);
BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue());
BigDecimal projectManagerAmount = multiplyAmount(assessmentOutputValue, outputSplit.getProjectManagerRatio());
BigDecimal engineeringLeaderAmount = multiplyAmount(assessmentOutputValue, outputSplit.getEngineeringLeaderRatio());
BigDecimal projectLeadAmount = multiplyAmount(assessmentOutputValue, outputSplit.getProjectLeadRatio());
BigDecimal officeAmount = multiplyAmount(assessmentOutputValue, outputSplit.getOfficeRatio());
List<ProjectRolePersonDO> rolePersons = projectRolePersonMapper.selectListByProjectId(project.getId());
String projectManagerName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_PROJECT_MANAGER);
String engineeringPrincipalName = joinRoleNames(rolePersons, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL);
respVO.setProjectName(project.getProjectName());
respVO.setPlanningContent(planning.getPlanningContent());
respVO.setYear(getPlanningYear(planning));
respVO.setAssessmentOutputValue(assessmentOutputValue);
respVO.setProjectManagerName(project.getProjectManagerName());
respVO.setEngineeringLeaderName(project.getEngineeringPrincipalName());
respVO.setProjectManagerAmount(projectManagerAmount);
respVO.setEngineeringLeaderAmount(engineeringLeaderAmount);
respVO.setProjectManagerName(projectManagerName);
respVO.setEngineeringLeaderName(engineeringPrincipalName);
respVO.setProjectLeadName(joinProjectLeadName(projectManagerName, engineeringPrincipalName));
respVO.setProjectLeadAmount(projectLeadAmount);
respVO.setOfficeAmount(officeAmount);
respVO.setArchAmount(multiplyAmount(officeAmount, outputSplit.getArchRatio()));
respVO.setDecorAmount(multiplyAmount(officeAmount, outputSplit.getDecorRatio()));
@@ -157,14 +164,11 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
return respVO;
}
private ProjectPlanningDO validateMajorPlanning(Long planningId) {
private ProjectPlanningDO validatePlanningExists(Long planningId) {
ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId);
if (planning == null) {
throw exception(PROJECT_OUTPUT_SPLIT_PLANNING_NOT_EXISTS);
}
if (!OutputSplitBizConstants.isMajorOwnershipType(planning.getOwnershipType())) {
throw exception(PROJECT_OUTPUT_SPLIT_NOT_MAJOR);
}
return planning;
}
@@ -181,8 +185,7 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
outputSplit.setProjectId(planning.getProjectId());
outputSplit.setPlanningId(planning.getId());
outputSplit.setYear(getPlanningYear(planning));
outputSplit.setProjectManagerRatio(ZERO_RATIO);
outputSplit.setEngineeringLeaderRatio(ZERO_RATIO);
outputSplit.setProjectLeadRatio(ZERO_RATIO);
outputSplit.setOfficeRatio(ONE_RATIO);
outputSplit.setArchRatio(ONE_RATIO);
outputSplit.setDecorRatio(ZERO_RATIO);
@@ -199,8 +202,16 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
}
private void validateOutputSplitRatios(ProjectOutputSplitSaveReqVO reqVO) {
BigDecimal projectTotal = ratio(reqVO.getProjectManagerRatio())
.add(ratio(reqVO.getEngineeringLeaderRatio()))
validateRatioRange(reqVO.getProjectLeadRatio());
validateRatioRange(reqVO.getOfficeRatio());
validateRatioRange(reqVO.getArchRatio());
validateRatioRange(reqVO.getDecorRatio());
validateRatioRange(reqVO.getStructRatio());
validateRatioRange(reqVO.getWaterRatio());
validateRatioRange(reqVO.getElecRatio());
validateRatioRange(reqVO.getHvacRatio());
validateRatioRange(reqVO.getDigitalRatio());
BigDecimal projectTotal = ratio(reqVO.getProjectLeadRatio())
.add(ratio(reqVO.getOfficeRatio()))
.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
BigDecimal specialtyTotal = ratio(reqVO.getArchRatio())
@@ -211,14 +222,20 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
.add(ratio(reqVO.getHvacRatio()))
.add(ratio(reqVO.getDigitalRatio()))
.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
if (projectTotal.compareTo(ONE_RATIO) > 0 || specialtyTotal.compareTo(ONE_RATIO) != 0) {
if (projectTotal.compareTo(ONE_RATIO) != 0 || specialtyTotal.compareTo(ONE_RATIO) != 0) {
throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID);
}
}
private void validateRatioRange(BigDecimal value) {
BigDecimal normalized = ratio(value);
if (normalized.compareTo(ZERO_RATIO) < 0 || normalized.compareTo(ONE_RATIO) > 0) {
throw exception(PROJECT_OUTPUT_SPLIT_RATIO_INVALID);
}
}
private void normalizeRatios(ProjectOutputSplitDO outputSplit) {
normalize(outputSplit::setProjectManagerRatio, outputSplit.getProjectManagerRatio());
normalize(outputSplit::setEngineeringLeaderRatio, outputSplit.getEngineeringLeaderRatio());
normalize(outputSplit::setProjectLeadRatio, outputSplit.getProjectLeadRatio());
normalize(outputSplit::setOfficeRatio, outputSplit.getOfficeRatio());
normalize(outputSplit::setArchRatio, outputSplit.getArchRatio());
normalize(outputSplit::setDecorRatio, outputSplit.getDecorRatio());
@@ -266,4 +283,26 @@ public class ProjectOutputSplitServiceImpl implements ProjectOutputSplitService
return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private String joinRoleNames(List<ProjectRolePersonDO> rolePersons, String roleCode) {
return rolePersons.stream()
.filter(item -> Objects.equals(item.getRoleCode(), roleCode))
.map(ProjectRolePersonDO::getEmployeeName)
.filter(Objects::nonNull)
.collect(Collectors.joining(""));
}
private String joinProjectLeadName(String projectManagerName, String engineeringPrincipalName) {
if ((projectManagerName == null || projectManagerName.isEmpty())
&& (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty())) {
return "";
}
if (projectManagerName == null || projectManagerName.isEmpty()) {
return engineeringPrincipalName;
}
if (engineeringPrincipalName == null || engineeringPrincipalName.isEmpty()) {
return projectManagerName;
}
return projectManagerName + " / " + engineeringPrincipalName;
}
}

View File

@@ -13,6 +13,7 @@ import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import org.springframework.stereotype.Service;
@@ -29,13 +30,14 @@ import java.util.Map;
import java.util.Objects;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_CALCULATION_METHOD_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_PROJECT_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED;
/**
* 合约规划 Service 实现类
@@ -49,6 +51,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private static final int AMOUNT_SCALE = 2;
private static final int RATIO_SCALE = 4;
private static final int FACTOR_SCALE = 2;
private static final int QUANTITY_SCALE = 4;
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
@@ -57,7 +60,6 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private static final BigDecimal DEFAULT_SUBCONTRACT_RATIO = new BigDecimal("0.0400");
private static final BigDecimal DEFAULT_MAJOR_OUTSOURCE_RATIO = new BigDecimal("0.0600");
private static final BigDecimal DEFAULT_COMMON_OUTSOURCE_RATIO = new BigDecimal("0.2500");
private static final BigDecimal DEFAULT_WORKING_DAY_UNIT_PRICE = new BigDecimal("1000.00");
private static final BigDecimal MIN_GUIDANCE_PRICE_RATIO = new BigDecimal("0.6000");
@Resource
@@ -70,6 +72,8 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private ProjectOutputSplitService projectOutputSplitService;
@Resource
private SpecialtyRoleSplitService specialtyRoleSplitService;
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@Override
public Long createProjectPlanning(ProjectPlanningSaveReqVO createReqVO) {
@@ -79,6 +83,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
prepareProjectPlanning(planning, project);
calculateProjectPlanning(planning);
projectPlanningMapper.insert(planning);
projectPlanningGuideDetailService.handlePlanningSaved(planning, null);
return planning.getId();
}
@@ -91,6 +96,11 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
prepareProjectPlanning(updateObj, project);
calculateProjectPlanning(updateObj);
projectPlanningMapper.updateById(updateObj);
projectPlanningGuideDetailService.handlePlanningSaved(updateObj, dbPlanning);
if (ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
updateObj.getOwnershipType(), updateObj.getCalculationMethod())) {
return;
}
refreshQuarterDistributionAmounts(updateObj);
}
@@ -103,6 +113,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
projectOutputSplitService.deleteByPlanningId(id);
}
projectPlanningQuarterMapper.delete(ProjectPlanningQuarterDO::getPlanningId, id);
projectPlanningGuideDetailService.deleteByPlanningId(id);
projectPlanningMapper.deleteById(id);
}
@@ -117,6 +128,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
projectOutputSplitService.deleteByPlanningIds(ids);
}
projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, ids);
projectPlanningGuideDetailService.deleteByPlanningIds(ids);
projectPlanningMapper.deleteBatchIds(ids);
}
@@ -131,7 +143,7 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
@Override
public List<ProjectPlanningDO> getProjectPlanningListByProjectId(Long projectId) {
validateProjectExists(projectId);
return projectPlanningMapper.selectListByProjectId(projectId);
return projectPlanningMapper.selectDisplayListByProjectId(projectId);
}
@Override
@@ -160,7 +172,8 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
if (!ProjectPlanningBizTypeConstants.isValidOwnershipType(reqVO.getOwnershipType())) {
throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_INVALID);
}
if (!ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) {
if (StrUtil.isNotBlank(reqVO.getCalculationMethod())
&& !ProjectPlanningBizTypeConstants.isValidCalculationMethod(reqVO.getCalculationMethod())) {
throw exception(PROJECT_PLANNING_CALCULATION_METHOD_INVALID);
}
if (ProjectPlanningBizTypeConstants.isVirtualOutput(reqVO.getCalculationMethod())
@@ -168,26 +181,39 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
&& !ProjectPlanningBizTypeConstants.isValidVirtualCalculationMethod(reqVO.getVirtualCalculationMethod())) {
throw exception(PROJECT_PLANNING_VIRTUAL_CALCULATION_METHOD_INVALID);
}
if (ProjectPlanningBizTypeConstants.isWorkingDay(reqVO.getVirtualCalculationMethod())) {
if (reqVO.getWorkingDayCount() == null) {
throw exception(PROJECT_PLANNING_WORKING_DAY_COUNT_REQUIRED);
}
if (reqVO.getWorkingDayUnitPrice() == null) {
throw exception(PROJECT_PLANNING_WORKING_DAY_UNIT_PRICE_REQUIRED);
}
}
if (dbPlanning == null) {
return;
}
if (!Objects.equals(dbPlanning.getOwnershipType(), reqVO.getOwnershipType())) {
throw exception(PROJECT_PLANNING_OWNERSHIP_TYPE_IMMUTABLE);
}
if (!Objects.equals(dbPlanning.getCalculationMethod(), reqVO.getCalculationMethod())) {
throw exception(PROJECT_PLANNING_CALCULATION_METHOD_IMMUTABLE);
}
}
private void prepareProjectPlanning(ProjectPlanningDO planning, ProjectDO project) {
if (planning.getSortNo() == null) {
planning.setSortNo(0);
}
if (planning.getPlanningStartYear() == null) {
planning.setPlanningStartYear(project.getProjectStartYear());
}
if (planning.getReviewOutsourceFlag() == null) {
planning.setReviewOutsourceFlag(Boolean.FALSE);
}
planning.setPlanningAmount(amount(planning.getPlanningAmount()));
planning.setContractValueQuantity(quantity(planning.getContractValueQuantity()));
planning.setContractValueUnitPrice(quantity(planning.getContractValueUnitPrice()));
planning.setPlanningAmount(multiplyAmount(
planning.getContractValueQuantity(),
planning.getContractValueUnitPrice()));
planning.setManagementFeeRate(ratio(planning.getManagementFeeRate()));
planning.setVatRate(ratio(planning.getVatRate()));
if (planning.getPlanningArea() == null) {
planning.setPlanningArea(project.getTotalConstructionArea());
}
@@ -219,16 +245,14 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
planning.setWorkingDayUnitPrice(amountNullable(planning.getWorkingDayUnitPrice()));
planning.setGuidanceUnitPrice(amountNullable(planning.getGuidanceUnitPrice()));
planning.setGuidanceTotalPrice(amountNullable(planning.getGuidanceTotalPrice()));
planning.setVirtualTotalPrice(amountNullable(planning.getVirtualTotalPrice()));
if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(planning.getVirtualCalculationMethod())
&& planning.getWorkingDayUnitPrice() == null) {
planning.setWorkingDayUnitPrice(DEFAULT_WORKING_DAY_UNIT_PRICE);
}
}
private void calculateProjectPlanning(ProjectPlanningDO planning) {
planning.setManagementFee(multiplyAmount(planning.getPlanningAmount(), planning.getManagementFeeRate()));
planning.setVatAmount(multiplyAmount(planning.getPlanningAmount(), planning.getVatRate()));
planning.setProjectBudgetOutputValue(amount(planning.getPlanningAmount()
.subtract(planning.getManagementFee())
.subtract(planning.getVatAmount())));
planning.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), planning.getPlanningArea()));
planning.setTotalAdjustmentFactor(ZERO_RATIO);
planning.setAssessmentArea(ZERO_AMOUNT);
@@ -251,16 +275,9 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
private void calculateMajorPlanning(ProjectPlanningDO planning) {
if (ProjectPlanningBizTypeConstants.isGuidancePrice(planning.getCalculationMethod())) {
BigDecimal totalAdjustmentFactor = calculateTotalAdjustmentFactor(planning);
planning.setTotalAdjustmentFactor(totalAdjustmentFactor);
planning.setAssessmentArea(multiplyAmount(planning.getPlanningArea(), totalAdjustmentFactor));
BigDecimal applicableUnitPrice = calculateApplicableUnitPrice(
planning.getContractUnitPrice(), planning.getInternalGuidanceUnitPrice());
planning.setAssessmentOutputValue(multiplyAmount(
applicableUnitPrice,
planning.getAssessmentArea(),
planning.getCurrentDesignStageRatio(),
oneMinus(planning.getReviewOutsourceRatio())));
planning.setTotalAdjustmentFactor(ZERO_RATIO);
planning.setAssessmentArea(ZERO_AMOUNT);
planning.setAssessmentOutputValue(ZERO_AMOUNT);
return;
}
if (ProjectPlanningBizTypeConstants.isContractPrice(planning.getCalculationMethod())) {
@@ -309,17 +326,14 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
}
private BigDecimal calculateVirtualOutputValue(ProjectPlanningDO planning) {
if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_WORKING_DAY.equals(planning.getVirtualCalculationMethod())) {
if (ProjectPlanningBizTypeConstants.isWorkingDay(planning.getVirtualCalculationMethod())) {
return multiplyAmount(planning.getWorkingDayCount(), planning.getWorkingDayUnitPrice());
}
if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_GUIDANCE_PRICE.equals(planning.getVirtualCalculationMethod())) {
if (isPositive(planning.getGuidanceTotalPrice())) {
return amount(planning.getGuidanceTotalPrice());
}
if (ProjectPlanningBizTypeConstants.isVirtualGuidancePrice(planning.getVirtualCalculationMethod())) {
return multiplyAmount(planning.getGuidanceUnitPrice(), planning.getPlanningArea());
}
if (ProjectPlanningBizTypeConstants.VIRTUAL_CALCULATION_METHOD_VIRTUAL_TOTAL_PRICE.equals(planning.getVirtualCalculationMethod())) {
return amount(planning.getVirtualTotalPrice());
if (ProjectPlanningBizTypeConstants.isVirtualGuidanceTotalPrice(planning.getVirtualCalculationMethod())) {
return amount(planning.getGuidanceTotalPrice());
}
return ZERO_AMOUNT;
}
@@ -408,6 +422,11 @@ public class ProjectPlanningServiceImpl implements ProjectPlanningService {
return value == null ? null : factor(value);
}
private BigDecimal quantity(BigDecimal value) {
return value == null ? BigDecimal.ZERO.setScale(QUANTITY_SCALE, RoundingMode.HALF_UP)
: value.setScale(QUANTITY_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal ratio(BigDecimal value) {
return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.lyzsys.module.tjt.service.planningguidedetail;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface ProjectPlanningGuideDetailService {
void batchSaveProjectPlanningGuideDetail(ProjectPlanningGuideDetailBatchSaveReqVO reqVO);
void deleteProjectPlanningGuideDetail(Long id);
List<ProjectPlanningGuideDetailDO> getProjectPlanningGuideDetailListByPlanningId(Long planningId);
Map<Long, List<ProjectPlanningGuideDetailDO>> getProjectPlanningGuideDetailMapByPlanningIds(Collection<Long> planningIds);
void deleteByPlanningId(Long planningId);
void deleteByPlanningIds(Collection<Long> planningIds);
void handlePlanningSaved(ProjectPlanningDO planning, ProjectPlanningDO dbPlanning);
}

View File

@@ -0,0 +1,289 @@
package cn.iocoder.lyzsys.module.tjt.service.planningguidedetail;
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.planningguidedetail.vo.ProjectPlanningGuideDetailSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningguidedetail.ProjectPlanningGuideDetailDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningguidedetail.ProjectPlanningGuideDetailMapper;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_DESIGN_PART_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_PLANNING_NOT_EXISTS;
@Service
@Validated
public class ProjectPlanningGuideDetailServiceImpl implements ProjectPlanningGuideDetailService {
private static final int AMOUNT_SCALE = 2;
private static final int RATIO_SCALE = 4;
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
@Resource
private ProjectPlanningGuideDetailMapper projectPlanningGuideDetailMapper;
@Resource
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
@Override
public void batchSaveProjectPlanningGuideDetail(ProjectPlanningGuideDetailBatchSaveReqVO reqVO) {
ProjectPlanningDO planning = validateGuideDetailPlanning(reqVO.getPlanningId());
List<ProjectPlanningGuideDetailSaveReqVO> reqDetails = reqVO.getDetails() == null
? Collections.emptyList() : reqVO.getDetails();
List<ProjectPlanningGuideDetailDO> existingList = projectPlanningGuideDetailMapper.selectListByPlanningId(reqVO.getPlanningId());
Map<Long, ProjectPlanningGuideDetailDO> existingMap =
CollectionUtils.convertMap(existingList, ProjectPlanningGuideDetailDO::getId);
Set<Long> retainedIds = reqDetails.stream()
.map(ProjectPlanningGuideDetailSaveReqVO::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (int i = 0; i < reqDetails.size(); i++) {
ProjectPlanningGuideDetailSaveReqVO detailReqVO = reqDetails.get(i);
validateGuideDetailForSave(detailReqVO);
ProjectPlanningGuideDetailDO detail = BeanUtils.toBean(detailReqVO, ProjectPlanningGuideDetailDO.class);
detail.setPlanningId(planning.getId());
detail.setProjectId(planning.getProjectId());
detail.setSortNo(detailReqVO.getSortNo() == null ? i + 1 : detailReqVO.getSortNo());
calculateGuideDetail(detail, planning.getReviewOutsourceRatio());
if (detail.getId() == null) {
projectPlanningGuideDetailMapper.insert(detail);
continue;
}
if (!existingMap.containsKey(detail.getId())) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS);
}
projectPlanningGuideDetailMapper.updateById(detail);
}
List<Long> deletedIds = existingList.stream()
.map(ProjectPlanningGuideDetailDO::getId)
.filter(id -> !retainedIds.contains(id))
.collect(Collectors.toList());
if (!deletedIds.isEmpty()) {
projectPlanningGuideDetailMapper.deleteBatchIds(deletedIds);
}
recalculatePlanningSummary(planning.getId());
}
@Override
public void deleteProjectPlanningGuideDetail(Long id) {
ProjectPlanningGuideDetailDO detail = projectPlanningGuideDetailMapper.selectById(id);
if (detail == null) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_NOT_EXISTS);
}
projectPlanningGuideDetailMapper.deleteById(id);
recalculatePlanningSummary(detail.getPlanningId());
}
@Override
public List<ProjectPlanningGuideDetailDO> getProjectPlanningGuideDetailListByPlanningId(Long planningId) {
validatePlanningExists(planningId);
return projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
}
@Override
public Map<Long, List<ProjectPlanningGuideDetailDO>> getProjectPlanningGuideDetailMapByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return Collections.emptyMap();
}
return CollectionUtils.convertMultiMap(
projectPlanningGuideDetailMapper.selectListByPlanningIds(planningIds),
ProjectPlanningGuideDetailDO::getPlanningId
);
}
@Override
public void deleteByPlanningId(Long planningId) {
if (planningId == null) {
return;
}
projectPlanningGuideDetailMapper.delete(ProjectPlanningGuideDetailDO::getPlanningId, planningId);
}
@Override
public void deleteByPlanningIds(Collection<Long> planningIds) {
if (planningIds == null || planningIds.isEmpty()) {
return;
}
projectPlanningGuideDetailMapper.deleteBatch(ProjectPlanningGuideDetailDO::getPlanningId, planningIds);
}
@Override
public void handlePlanningSaved(ProjectPlanningDO planning, ProjectPlanningDO dbPlanning) {
boolean currentGuideScene = isGuideDetailScene(planning);
boolean previousGuideScene = isGuideDetailScene(dbPlanning);
if (!currentGuideScene) {
if (previousGuideScene) {
deleteByPlanningId(planning.getId());
}
return;
}
recalculateGuideDetails(planning.getId(), ratio(planning.getReviewOutsourceRatio()));
recalculatePlanningSummary(planning.getId());
}
private void recalculateGuideDetails(Long planningId, BigDecimal reviewOutsourceRatio) {
List<ProjectPlanningGuideDetailDO> detailList = projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
if (detailList.isEmpty()) {
return;
}
for (ProjectPlanningGuideDetailDO detail : detailList) {
calculateGuideDetail(detail, reviewOutsourceRatio);
projectPlanningGuideDetailMapper.updateById(detail);
}
}
private void recalculatePlanningSummary(Long planningId) {
ProjectPlanningDO planning = validatePlanningExists(planningId);
List<ProjectPlanningGuideDetailDO> detailList = projectPlanningGuideDetailMapper.selectListByPlanningId(planningId);
BigDecimal totalDesignArea = ZERO_AMOUNT;
BigDecimal totalAssessmentArea = ZERO_AMOUNT;
BigDecimal totalAssessmentOutputValue = ZERO_AMOUNT;
for (ProjectPlanningGuideDetailDO detail : detailList) {
totalDesignArea = totalDesignArea.add(amount(detail.getDesignArea())).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
totalAssessmentArea = totalAssessmentArea.add(amount(detail.getAssessmentArea())).setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
totalAssessmentOutputValue = totalAssessmentOutputValue.add(amount(detail.getAssessmentOutputValue()))
.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
ProjectPlanningDO updateObj = new ProjectPlanningDO();
updateObj.setId(planningId);
updateObj.setPlanningArea(totalDesignArea);
updateObj.setContractUnitPrice(divideAmount(planning.getPlanningAmount(), totalDesignArea));
updateObj.setTotalAdjustmentFactor(ZERO_RATIO);
updateObj.setAssessmentArea(totalAssessmentArea);
updateObj.setVirtualOutputValue(ZERO_AMOUNT);
updateObj.setAssessmentOutputValue(totalAssessmentOutputValue);
projectPlanningMapper.updateById(updateObj);
ProjectPlanningDO refreshedPlanning = projectPlanningMapper.selectById(planningId);
refreshQuarterDistributionAmounts(refreshedPlanning);
}
private void validateGuideDetailForSave(ProjectPlanningGuideDetailSaveReqVO reqVO) {
if (!ProjectPlanningBizTypeConstants.isValidDesignPart(reqVO.getDesignPart())) {
throw exception(PROJECT_PLANNING_DESIGN_PART_INVALID);
}
}
private ProjectPlanningDO validateGuideDetailPlanning(Long planningId) {
ProjectPlanningDO planning = validatePlanningExists(planningId);
if (!isGuideDetailScene(planning)) {
throw exception(PROJECT_PLANNING_GUIDE_DETAIL_SCENE_INVALID);
}
return planning;
}
private ProjectPlanningDO validatePlanningExists(Long planningId) {
ProjectPlanningDO planning = projectPlanningMapper.selectById(planningId);
if (planning == null) {
throw exception(PROJECT_PLANNING_NOT_EXISTS);
}
return planning;
}
private boolean isGuideDetailScene(ProjectPlanningDO planning) {
return planning != null && ProjectPlanningBizTypeConstants.isMajorGuidanceScene(
planning.getOwnershipType(), planning.getCalculationMethod());
}
private void calculateGuideDetail(ProjectPlanningGuideDetailDO detail, BigDecimal reviewOutsourceRatio) {
detail.setDesignArea(amount(detail.getDesignArea()));
detail.setInternalGuidanceUnitPrice(amount(detail.getInternalGuidanceUnitPrice()));
detail.setDrawingSetFactor(ratio(detail.getDrawingSetFactor()));
detail.setScaleFactor(ratio(detail.getScaleFactor()));
detail.setModificationFactor(ratio(detail.getModificationFactor()));
detail.setComplexityFactor(ratio(detail.getComplexityFactor()));
detail.setDesignRatio(ratio(detail.getDesignRatio()));
detail.setTotalAdjustmentFactor(multiplyRatio(
detail.getDrawingSetFactor(),
detail.getScaleFactor(),
detail.getModificationFactor(),
detail.getComplexityFactor()
));
detail.setAssessmentArea(multiplyAmount(detail.getDesignArea(), detail.getTotalAdjustmentFactor()));
detail.setAssessmentOutputValue(multiplyAmount(
detail.getInternalGuidanceUnitPrice(),
detail.getAssessmentArea(),
detail.getDesignRatio(),
oneMinus(reviewOutsourceRatio)
));
}
private void refreshQuarterDistributionAmounts(ProjectPlanningDO planning) {
if (planning == null) {
return;
}
List<ProjectPlanningQuarterDO> quarterList = projectPlanningQuarterMapper.selectListByPlanningId(planning.getId());
for (ProjectPlanningQuarterDO quarter : quarterList) {
quarter.setDistributionAmount(multiplyAmount(
planning.getAssessmentOutputValue(),
planning.getTotalDistributionAmount(),
ratio(quarter.getDistributionRatio())
));
projectPlanningQuarterMapper.updateById(quarter);
}
}
private BigDecimal amount(BigDecimal value) {
return value == null ? ZERO_AMOUNT : value.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal ratio(BigDecimal value) {
return value == null ? ZERO_RATIO : value.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal oneMinus(BigDecimal value) {
return BigDecimal.ONE.subtract(ratio(value)).setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal multiplyAmount(BigDecimal... values) {
BigDecimal result = BigDecimal.ONE;
for (BigDecimal value : values) {
result = result.multiply(value == null ? BigDecimal.ZERO : value);
}
return result.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal multiplyRatio(BigDecimal... values) {
BigDecimal result = BigDecimal.ONE;
for (BigDecimal value : values) {
result = result.multiply(value == null ? BigDecimal.ZERO : value);
}
return result.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
}
private BigDecimal divideAmount(BigDecimal dividend, BigDecimal divisor) {
if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
return ZERO_AMOUNT;
}
return dividend.divide(divisor, AMOUNT_SCALE, RoundingMode.HALF_UP);
}
}

View File

@@ -6,9 +6,13 @@ import cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.profit.vo.ProjectProfitRespVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper;
import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -16,29 +20,34 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
/**
* 项目盈亏 Service 实现类
*
* @author Codex
*/
@Service
@Validated
public class ProjectProfitServiceImpl implements ProjectProfitService {
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP);
private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(4, RoundingMode.HALF_UP);
private static final BigDecimal DEFAULT_INNOVATION_OUTPUT_RATE = new BigDecimal("0.0100");
private static final BigDecimal DEFAULT_YEAR_K_VALUE = new BigDecimal("0.4000");
@Resource
private ProjectMapper projectMapper;
@Resource
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
@Resource
private YearKValueMapper yearKValueMapper;
@Override
public ProjectProfitRespVO getProjectProfit(Long projectId) {
@@ -55,13 +64,14 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
.eqIfPresent(ProjectDO::getContractSignedFlag, pageReqVO.getContractSignedFlag())
.eqIfPresent(ProjectDO::getProjectStartYear, pageReqVO.getProjectStartYear())
.betweenIfPresent(ProjectDO::getCreateTime, pageReqVO.getCreateTime())
.orderByDesc(ProjectDO::getId));
.orderByAsc(ProjectDO::getSortNo)
.orderByAsc(ProjectDO::getId));
if (pageResult.getList().isEmpty()) {
return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
}
Set<Long> projectIds = CollectionUtils.convertSet(pageResult.getList(), ProjectDO::getId);
Map<Long, List<ProjectPlanningDO>> planningMap = CollectionUtils.convertMultiMap(
projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId,
CollectionUtils.convertSet(pageResult.getList(), ProjectDO::getId)),
projectPlanningMapper.selectList(ProjectPlanningDO::getProjectId, projectIds),
ProjectPlanningDO::getProjectId);
List<ProjectProfitRespVO> list = CollectionUtils.convertList(pageResult.getList(),
project -> buildProjectProfit(project, planningMap.get(project.getId())));
@@ -73,6 +83,10 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
BigDecimal comprehensivePlanningAmount = ZERO_AMOUNT;
BigDecimal subcontractPlanningAmount = ZERO_AMOUNT;
BigDecimal majorOutputValue = ZERO_AMOUNT;
List<ProjectPlanningDO> majorPlanningList = safePlanningList.stream()
.filter(planning -> ProjectPlanningBizTypeConstants.isMajor(planning.getOwnershipType()))
.collect(Collectors.toList());
for (ProjectPlanningDO planning : safePlanningList) {
if (ProjectPlanningBizTypeConstants.isComprehensive(planning.getOwnershipType())) {
comprehensivePlanningAmount = comprehensivePlanningAmount.add(amount(planning.getPlanningAmount()));
@@ -86,13 +100,22 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
majorOutputValue = majorOutputValue.add(amount(planning.getAssessmentOutputValue()));
}
}
BigDecimal expectedKValue = ratio(project.getExpectedKValue());
BigDecimal majorExpectedPerformance = majorOutputValue.multiply(expectedKValue).setScale(2, RoundingMode.HALF_UP);
BigDecimal majorExpectedPerformance = calculateMajorExpectedPerformance(majorPlanningList);
BigDecimal contractAmount = amount(project.getContractAmount());
BigDecimal finalSettlementAmount = amount(project.getFinalSettlementAmount());
BigDecimal innovationOutputRate = project.getInnovationOutputRate() == null
? DEFAULT_INNOVATION_OUTPUT_RATE : ratio(project.getInnovationOutputRate());
BigDecimal innovationOutputValue = contractAmount.compareTo(BigDecimal.ZERO) <= 0
? ZERO_AMOUNT
: contractAmount.multiply(innovationOutputRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal otherCost = amount(project.getOtherCost());
BigDecimal profitLossValue = finalSettlementAmount
.subtract(comprehensivePlanningAmount)
.subtract(subcontractPlanningAmount)
.subtract(majorExpectedPerformance)
.subtract(innovationOutputValue)
.subtract(otherCost)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal profitLossRate = finalSettlementAmount.compareTo(BigDecimal.ZERO) == 0
? ZERO_RATIO
@@ -101,14 +124,17 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
ProjectProfitRespVO respVO = new ProjectProfitRespVO();
respVO.setProjectId(project.getId());
respVO.setProjectName(project.getProjectName());
respVO.setSortNo(project.getSortNo());
respVO.setContractSignedFlag(project.getContractSignedFlag());
respVO.setContractAmount(amount(project.getContractAmount()));
respVO.setContractAmount(contractAmount);
respVO.setFinalSettlementAmount(finalSettlementAmount);
respVO.setComprehensivePlanningAmount(comprehensivePlanningAmount.setScale(2, RoundingMode.HALF_UP));
respVO.setSubcontractPlanningAmount(subcontractPlanningAmount.setScale(2, RoundingMode.HALF_UP));
respVO.setMajorOutputValue(majorOutputValue.setScale(2, RoundingMode.HALF_UP));
respVO.setExpectedKValue(expectedKValue);
respVO.setMajorExpectedPerformance(majorExpectedPerformance);
respVO.setInnovationOutputRate(innovationOutputRate);
respVO.setInnovationOutputValue(innovationOutputValue);
respVO.setOtherCost(otherCost);
respVO.setProfitLossValue(profitLossValue);
respVO.setProfitLossRate(profitLossRate);
respVO.setProjectStartYear(project.getProjectStartYear());
@@ -116,6 +142,43 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
return respVO;
}
private BigDecimal calculateMajorExpectedPerformance(List<ProjectPlanningDO> majorPlanningList) {
if (majorPlanningList == null || majorPlanningList.isEmpty()) {
return ZERO_AMOUNT;
}
List<Long> planningIds = majorPlanningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList());
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = CollectionUtils.convertMultiMap(
projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId);
Set<Integer> years = quarterMap.values().stream()
.flatMap(Collection::stream)
.map(ProjectPlanningQuarterDO::getDistributionYear)
.collect(Collectors.toSet());
Map<Integer, BigDecimal> yearKValueMap = yearKValueMapper.selectEnabledListByYears(years).stream()
.collect(Collectors.toMap(YearKValueDO::getKYear, item -> ratio(item.getKValue()), (a, b) -> b));
BigDecimal total = ZERO_AMOUNT;
for (ProjectPlanningDO planning : majorPlanningList) {
BigDecimal assessmentOutputValue = amount(planning.getAssessmentOutputValue());
Map<Integer, BigDecimal> yearRatioMap = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream()
.collect(Collectors.groupingBy(ProjectPlanningQuarterDO::getDistributionYear,
Collectors.reducing(ZERO_RATIO,
item -> ratio(item.getDistributionRatio()),
BigDecimal::add)));
BigDecimal allocatedRatio = ZERO_RATIO;
for (Map.Entry<Integer, BigDecimal> entry : yearRatioMap.entrySet()) {
BigDecimal yearRatio = ratio(entry.getValue());
allocatedRatio = allocatedRatio.add(yearRatio).setScale(4, RoundingMode.HALF_UP);
BigDecimal kValue = yearKValueMap.getOrDefault(entry.getKey(), DEFAULT_YEAR_K_VALUE);
total = total.add(multiplyAmount(assessmentOutputValue, yearRatio, kValue));
}
BigDecimal unallocatedRatio = ONE_RATIO.subtract(allocatedRatio).setScale(4, RoundingMode.HALF_UP);
if (unallocatedRatio.compareTo(ZERO_RATIO) > 0) {
total = total.add(multiplyAmount(assessmentOutputValue, unallocatedRatio, DEFAULT_YEAR_K_VALUE));
}
}
return total.setScale(2, RoundingMode.HALF_UP);
}
private ProjectDO validateProjectExists(Long projectId) {
ProjectDO project = projectMapper.selectById(projectId);
if (project == null) {
@@ -132,4 +195,12 @@ public class ProjectProfitServiceImpl implements ProjectProfitService {
return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP);
}
private BigDecimal multiplyAmount(BigDecimal... values) {
BigDecimal result = BigDecimal.ONE;
for (BigDecimal value : values) {
result = result.multiply(value == null ? BigDecimal.ZERO : value);
}
return result.setScale(2, RoundingMode.HALF_UP);
}
}

View File

@@ -2,8 +2,8 @@ package cn.iocoder.lyzsys.module.tjt.service.project;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import java.util.List;
@@ -22,8 +22,8 @@ public interface ProjectService {
void deleteProjectList(List<Long> ids);
PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO);
PageResult<ProjectRespVO> getProjectPage(ProjectPageReqVO pageReqVO);
ProjectDO getProject(Long id);
ProjectRespVO getProject(Long id);
}

View File

@@ -1,16 +1,39 @@
package cn.iocoder.lyzsys.module.tjt.service.project;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
@@ -24,49 +47,267 @@ import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_
@Validated
public class ProjectServiceImpl implements ProjectService {
private static final String ROLE_CODE_PROJECT_MANAGER = "project_manager";
private static final String ROLE_CODE_ENGINEERING_PRINCIPAL = "engineering_principal";
private static final String ROLE_NAME_PROJECT_MANAGER = "项目经理";
private static final String ROLE_NAME_ENGINEERING_PRINCIPAL = "工程负责人";
private static final String STATUS_IN_PROGRESS = "进行中";
private static final String STATUS_COMPLETED = "完成";
private static final String STATUS_PAUSED = "暂停";
private static final String STATUS_TERMINATED = "中止";
@Resource
private ProjectMapper projectMapper;
@Resource
private ProjectRolePersonMapper projectRolePersonMapper;
@Resource
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
@Resource
private EmployeeService employeeService;
@Resource
private ProjectOutputSplitService projectOutputSplitService;
@Resource
private SpecialtyRoleSplitService specialtyRoleSplitService;
@Resource
private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;
@Override
public Long createProject(ProjectSaveReqVO createReqVO) {
ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class);
applyProjectDefaults(project, null);
projectMapper.insert(project);
saveProjectRolePersons(project.getId(), createReqVO.getRolePersons());
return project.getId();
}
@Override
public void updateProject(ProjectSaveReqVO updateReqVO) {
validateProjectExists(updateReqVO.getId());
ProjectDO dbProject = validateProjectExists(updateReqVO.getId());
ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class);
applyProjectDefaults(updateObj, dbProject);
projectMapper.updateById(updateObj);
saveProjectRolePersons(updateReqVO.getId(), updateReqVO.getRolePersons());
}
@Override
public void deleteProject(Long id) {
validateProjectExists(id);
deleteProjectDependencies(Collections.singletonList(id));
projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, id);
projectMapper.deleteById(id);
}
@Override
public void deleteProjectList(List<Long> ids) {
ids.forEach(this::validateProjectExists);
deleteProjectDependencies(ids);
projectRolePersonMapper.deleteBatch(ProjectRolePersonDO::getProjectId, ids);
projectMapper.deleteBatchIds(ids);
}
@Override
public PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO) {
return projectMapper.selectPage(pageReqVO);
public PageResult<ProjectRespVO> getProjectPage(ProjectPageReqVO pageReqVO) {
PageResult<ProjectDO> pageResult = projectMapper.selectPage(pageReqVO);
if (pageResult.getList().isEmpty()) {
return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
}
List<ProjectDO> projects = pageResult.getList();
List<Long> projectIds = projects.stream().map(ProjectDO::getId).collect(Collectors.toList());
Map<Long, List<ProjectRolePersonDO>> rolePersonMap = CollectionUtils.convertMultiMap(
projectRolePersonMapper.selectListByProjectIds(projectIds), ProjectRolePersonDO::getProjectId);
List<ProjectRespVO> list = projects.stream()
.map(project -> buildProjectRespVO(syncProjectStatus(project), rolePersonMap.get(project.getId())))
.collect(Collectors.toList());
return new PageResult<>(list, pageResult.getTotal());
}
@Override
public ProjectDO getProject(Long id) {
return projectMapper.selectById(id);
public ProjectRespVO getProject(Long id) {
ProjectDO project = syncProjectStatus(validateProjectExists(id));
return buildProjectRespVO(project, projectRolePersonMapper.selectListByProjectId(id));
}
private void validateProjectExists(Long id) {
if (projectMapper.selectById(id) == null) {
private ProjectDO validateProjectExists(Long id) {
ProjectDO project = projectMapper.selectById(id);
if (project == null) {
throw exception(PROJECT_NOT_EXISTS);
}
return project;
}
private void applyProjectDefaults(ProjectDO project, ProjectDO dbProject) {
if (project.getProjectStatus() == null || project.getProjectStatus().trim().isEmpty()) {
project.setProjectStatus(dbProject == null ? STATUS_IN_PROGRESS : dbProject.getProjectStatus());
}
if (project.getArchiveFlag() == null) {
project.setArchiveFlag(dbProject != null && Boolean.TRUE.equals(dbProject.getArchiveFlag()));
}
if (project.getArchiveTime() == null && dbProject != null) {
project.setArchiveTime(dbProject.getArchiveTime());
}
if (project.getSortNo() == null) {
project.setSortNo(dbProject == null || dbProject.getSortNo() == null ? 0 : dbProject.getSortNo());
}
}
private void saveProjectRolePersons(Long projectId, List<ProjectRolePersonSaveReqVO> reqList) {
projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, projectId);
if (reqList == null || reqList.isEmpty()) {
return;
}
List<ProjectRolePersonDO> createList = new ArrayList<>();
int autoSortNo = 1;
for (ProjectRolePersonSaveReqVO item : reqList) {
if (item == null || item.getEmployeeId() == null) {
continue;
}
String roleCode = item.getRoleCode() == null ? "" : item.getRoleCode().trim();
if (!Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER)
&& !Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) {
continue;
}
EmployeeDO employee = employeeService.validateEmployeeExists(item.getEmployeeId());
ProjectRolePersonDO createObj = new ProjectRolePersonDO();
createObj.setProjectId(projectId);
createObj.setRoleCode(roleCode);
createObj.setRoleName(getRoleName(roleCode));
createObj.setEmployeeId(item.getEmployeeId());
createObj.setEmployeeName(employee.getEmployeeName());
createObj.setSortNo(item.getSortNo() == null ? autoSortNo++ : item.getSortNo());
createObj.setEnabledFlag(Boolean.TRUE);
createList.add(createObj);
}
for (ProjectRolePersonDO createObj : createList) {
projectRolePersonMapper.insert(createObj);
}
}
private ProjectRespVO buildProjectRespVO(ProjectDO project, List<ProjectRolePersonDO> rolePersons) {
List<ProjectRolePersonDO> safeRolePersons = rolePersons == null ? Collections.emptyList() : rolePersons;
ProjectRespVO respVO = BeanUtils.toBean(project, ProjectRespVO.class);
respVO.setProjectManagerName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_PROJECT_MANAGER));
respVO.setEngineeringPrincipalName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_ENGINEERING_PRINCIPAL));
respVO.setRolePersons(BeanUtils.toBean(safeRolePersons, ProjectRolePersonRespVO.class));
return respVO;
}
private String joinPersonNamesByRoleCode(List<ProjectRolePersonDO> rolePersons, String roleCode) {
return rolePersons.stream()
.filter(item -> Objects.equals(item.getRoleCode(), roleCode))
.map(ProjectRolePersonDO::getEmployeeName)
.filter(Objects::nonNull)
.collect(Collectors.joining(""));
}
private String getRoleName(String roleCode) {
if (Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER)) {
return ROLE_NAME_PROJECT_MANAGER;
}
if (Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) {
return ROLE_NAME_ENGINEERING_PRINCIPAL;
}
return roleCode;
}
private ProjectDO syncProjectStatus(ProjectDO project) {
String nextStatus = calculateProjectStatus(project);
Boolean nextArchiveFlag = STATUS_COMPLETED.equals(nextStatus);
LocalDateTime nextArchiveTime = nextArchiveFlag
? (project.getArchiveTime() == null ? LocalDateTime.now() : project.getArchiveTime())
: null;
boolean needUpdate = !Objects.equals(project.getProjectStatus(), nextStatus)
|| !Objects.equals(project.getArchiveFlag(), nextArchiveFlag)
|| !Objects.equals(project.getArchiveTime(), nextArchiveTime);
if (needUpdate) {
ProjectDO updateObj = new ProjectDO();
updateObj.setId(project.getId());
updateObj.setProjectStatus(nextStatus);
updateObj.setArchiveFlag(nextArchiveFlag);
updateObj.setArchiveTime(nextArchiveTime);
if (!nextArchiveFlag) {
updateObj.setArchiveTime(null);
}
projectMapper.updateById(updateObj);
project.setProjectStatus(nextStatus);
project.setArchiveFlag(nextArchiveFlag);
project.setArchiveTime(nextArchiveTime);
}
return project;
}
private String calculateProjectStatus(ProjectDO project) {
if (STATUS_TERMINATED.equals(project.getProjectStatus())) {
return STATUS_TERMINATED;
}
if (isProjectCompleted(project.getId())) {
return STATUS_COMPLETED;
}
if (STATUS_PAUSED.equals(project.getProjectStatus())
&& project.getPauseReason() != null
&& !project.getPauseReason().trim().isEmpty()) {
return STATUS_PAUSED;
}
int currentYear = LocalDateTime.now().getYear();
if (project.getProjectStartYear() != null && currentYear - project.getProjectStartYear() >= 5) {
return STATUS_PAUSED;
}
return STATUS_IN_PROGRESS;
}
private boolean isProjectCompleted(Long projectId) {
List<ProjectPlanningDO> planningList = projectPlanningMapper.selectListByProjectId(projectId);
if (planningList == null || planningList.isEmpty()) {
return false;
}
List<Long> planningIds = planningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList());
Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = CollectionUtils.convertMultiMap(
projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId);
for (ProjectPlanningDO planning : planningList) {
java.math.BigDecimal allocatedRatio = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream()
.map(ProjectPlanningQuarterDO::getDistributionRatio)
.filter(Objects::nonNull)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add)
.setScale(4, java.math.RoundingMode.HALF_UP);
java.math.BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null
? java.math.BigDecimal.ONE.setScale(4, java.math.RoundingMode.HALF_UP)
: planning.getTotalDistributionAmount().setScale(4, java.math.RoundingMode.HALF_UP);
if (allocatedRatio.compareTo(totalDistributionAmount) < 0) {
return false;
}
}
return true;
}
private void deleteProjectDependencies(Collection<Long> projectIds) {
if (projectIds == null || projectIds.isEmpty()) {
return;
}
List<ProjectPlanningDO> planningList = projectPlanningMapper.selectList(
new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX<ProjectPlanningDO>()
.inIfPresent(ProjectPlanningDO::getProjectId, projectIds));
if (planningList == null || planningList.isEmpty()) {
return;
}
List<Long> planningIds = planningList.stream()
.map(ProjectPlanningDO::getId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (planningIds.isEmpty()) {
return;
}
Map<Long, ProjectOutputSplitDO> outputSplitMap = projectOutputSplitService.getProjectOutputSplitMap(planningIds);
if (!outputSplitMap.isEmpty()) {
specialtyRoleSplitService.deleteByOutputSplitIds(outputSplitMap.values().stream()
.map(ProjectOutputSplitDO::getId)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
projectOutputSplitService.deleteByPlanningIds(planningIds);
}
projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, planningIds);
projectPlanningGuideDetailService.deleteByPlanningIds(planningIds);
projectPlanningMapper.deleteBatchIds(planningIds);
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.lyzsys.module.tjt.service.report;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectBudgetExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectLeadQuarterOutputExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectOverviewExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.ProjectQuarterOutputExportReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.SpecialtyPersonOutputExportReqVO;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface ProjectOutputReportService {
void exportProjectBudgetExcel(HttpServletResponse response, ProjectBudgetExportReqVO reqVO)
throws IOException;
void exportProjectQuarterOutputExcel(HttpServletResponse response, ProjectQuarterOutputExportReqVO reqVO)
throws IOException;
void exportProjectLeadQuarterOutputExcel(HttpServletResponse response,
ProjectLeadQuarterOutputExportReqVO reqVO)
throws IOException;
void exportSpecialtyPersonOutputExcel(HttpServletResponse response, SpecialtyPersonOutputExportReqVO reqVO)
throws IOException;
void exportProjectOverviewExcel(HttpServletResponse response, ProjectOverviewExportReqVO reqVO)
throws IOException;
void exportEmployeeOutputSummaryExcel(HttpServletResponse response, EmployeeOutputSummaryExportReqVO reqVO)
throws IOException;
}

View File

@@ -0,0 +1,207 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import cn.iocoder.lyzsys.framework.common.util.http.HttpUtils;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
public abstract class AbstractProjectOutputExcelBuilder {
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(4, RoundingMode.HALF_UP);
protected Workbook createWorkbook() {
return new XSSFWorkbook();
}
protected Sheet createSheet(Workbook workbook, String preferredName, String fallbackName) {
return workbook.createSheet(buildSheetName(preferredName, fallbackName));
}
protected CellStyle createTitleStyle(Workbook workbook) {
CellStyle style = createBaseStyle(workbook);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFont(font);
style.setAlignment(HorizontalAlignment.CENTER);
return style;
}
protected CellStyle createInfoStyle(Workbook workbook) {
CellStyle style = createBaseStyle(workbook);
Font font = workbook.createFont();
font.setFontHeightInPoints((short) 10);
style.setFont(font);
return style;
}
protected CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = createBaseStyle(workbook);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 10);
style.setFont(font);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setAlignment(HorizontalAlignment.CENTER);
return style;
}
protected CellStyle createCellStyle(Workbook workbook) {
CellStyle style = createBaseStyle(workbook);
Font font = workbook.createFont();
font.setFontHeightInPoints((short) 10);
style.setFont(font);
return style;
}
protected CellStyle createDerivedStyle(Workbook workbook, CellStyle sourceStyle,
IndexedColors fillColor, HorizontalAlignment alignment,
boolean bold, short fontHeightInPoints) {
CellStyle style = workbook.createCellStyle();
style.cloneStyleFrom(sourceStyle);
if (fillColor != null) {
style.setFillForegroundColor(fillColor.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
}
if (alignment != null) {
style.setAlignment(alignment);
}
Font font = workbook.createFont();
font.setBold(bold);
font.setFontHeightInPoints(fontHeightInPoints);
style.setFont(font);
return style;
}
protected void setText(Row row, int cellIndex, Object value, CellStyle style) {
Cell cell = row.createCell(cellIndex);
cell.setCellValue(value == null ? "" : String.valueOf(value));
if (style != null) {
cell.setCellStyle(style);
}
}
protected Row getOrCreateRow(Sheet sheet, int rowIndex) {
Row row = sheet.getRow(rowIndex);
return row != null ? row : sheet.createRow(rowIndex);
}
protected int writeMergedTitleRow(Sheet sheet, int rowIndex, String title, CellStyle style, int lastCol) {
Row row = getOrCreateRow(sheet, rowIndex);
setText(row, 0, title, style);
merge(sheet, rowIndex, rowIndex, 0, lastCol);
return rowIndex + 1;
}
protected int writeRow(Sheet sheet, int rowIndex, CellStyle style, Object... values) {
Row row = getOrCreateRow(sheet, rowIndex);
for (int i = 0; i < values.length; i++) {
setText(row, i, values[i], style);
}
return rowIndex + 1;
}
protected void writeLabelValue(Row row, int labelCellIndex, int valueCellIndex,
Object label, Object value, CellStyle style) {
setText(row, labelCellIndex, label, style);
setText(row, valueCellIndex, value, style);
}
protected void merge(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) {
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
}
protected void setMergedRegionText(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol,
String text, CellStyle style) {
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
Row row = getOrCreateRow(sheet, rowIndex);
for (int cellIndex = firstCol; cellIndex <= lastCol; cellIndex++) {
setText(row, cellIndex, rowIndex == firstRow && cellIndex == firstCol ? text : "", style);
}
}
if (firstRow != lastRow || firstCol != lastCol) {
merge(sheet, firstRow, lastRow, firstCol, lastCol);
}
}
protected void setColumnWidths(Sheet sheet, int... widths) {
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, widths[i] * 256);
}
}
protected BigDecimal amount(BigDecimal value) {
return value == null ? ZERO_AMOUNT : value.setScale(2, RoundingMode.HALF_UP);
}
protected BigDecimal ratio(BigDecimal value) {
return value == null ? ZERO_RATIO : value.setScale(4, RoundingMode.HALF_UP);
}
protected String percentText(BigDecimal value) {
return ratio(value).multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP) + "%";
}
protected String text(BigDecimal value) {
return amount(value).toPlainString();
}
protected String safeText(String value) {
return value == null ? "" : value.trim();
}
public void writeWorkbook(HttpServletResponse response, String fileName, Workbook workbook) throws IOException {
try {
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(fileName));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
workbook.write(response.getOutputStream());
response.flushBuffer();
} finally {
workbook.close();
}
}
private CellStyle createBaseStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setWrapText(true);
return style;
}
private String buildSheetName(String preferred, String fallback) {
String sheetName = sanitizeSheetName(preferred);
if (sheetName.isEmpty()) {
sheetName = sanitizeSheetName(fallback);
}
if (sheetName.length() > 31) {
sheetName = sheetName.substring(0, 31);
}
return sheetName.isEmpty() ? "Sheet1" : sheetName;
}
private String sanitizeSheetName(String value) {
return safeText(value).replaceAll("[\\\\/?*\\[\\]:]", "_");
}
}

View File

@@ -0,0 +1,211 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSummaryExcelRespVO;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.List;
@Component
public class EmployeeOutputSummaryExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SHEET_NAME = "员工汇总";
private static final String TITLE_PREFIX = "7.1.6 ";
private static final String TITLE_SUFFIX = " 年设计中心员工个人考核产值汇总,全公司产值人员汇总表";
private static final String SUBTITLE_SUFFIX = " 年设计中心员工个人考核产值汇总(单位:万元)";
private static final int LAST_COL = 12;
private static final int DETAIL_START_ROW_NUM = 4;
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
workbook.setForceFormulaRecalculation(true);
buildSheet(workbook, data);
return workbook;
}
private void buildSheet(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET_NAME, SHEET_NAME);
setColumnWidths(sheet, 8, 14, 12, 12, 12, 12, 12, 14, 14, 18, 14, 14, 14);
CellStyle subtitleStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.CENTER, true, (short) 11);
CellStyle headerStyle = createDerivedStyle(workbook, createHeaderStyle(workbook),
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle amountStyle = createAmountStyle(workbook, false, false);
CellStyle formulaStyle = createAmountStyle(workbook, false, false);
CellStyle totalCellStyle = createDerivedStyle(workbook, createHeaderStyle(workbook),
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle totalLeftCellStyle = createDerivedStyle(workbook, totalCellStyle,
IndexedColors.GOLD, HorizontalAlignment.LEFT, true, (short) 10);
CellStyle totalStyle = createAmountStyle(workbook, true, false);
CellStyle totalLeftStyle = createDerivedStyle(workbook, totalStyle,
null, HorizontalAlignment.LEFT, true, (short) 10);
int rowIndex = 0;
rowIndex = writeSubtitleRow(sheet, rowIndex, data, subtitleStyle);
rowIndex = writeHeaderRow(sheet, rowIndex, headerStyle);
List<EmployeeOutputSummaryExcelRespVO> rows = data == null || data.getRows() == null
? Collections.emptyList() : data.getRows();
for (EmployeeOutputSummaryExcelRespVO rowData : rows) {
writeDetailRow(sheet, rowIndex++, rowData, data, cellStyle, leftCellStyle, amountStyle, formulaStyle);
}
writeTotalRow(sheet, rowIndex, rows, data, totalCellStyle, totalLeftCellStyle, totalStyle, totalLeftStyle);
}
private int writeSubtitleRow(Sheet sheet, int rowIndex, ExportData data, CellStyle style) {
Row row = sheet.createRow(rowIndex);
for (int cellIndex = 0; cellIndex <= LAST_COL; cellIndex++) {
setText(row, cellIndex, "", style);
}
setMergedRegionText(sheet, rowIndex, rowIndex, 0, LAST_COL,
safeYear(data) + SUBTITLE_SUFFIX, style);
return rowIndex + 1;
}
private int writeHeaderRow(Sheet sheet, int rowIndex, CellStyle style) {
writeRow(sheet, rowIndex, style,
"序号", "姓名", "第一季度", "第二季度", "第三季度", "第四季度", "年度合计",
"所长 / BIM 考核产值", "年度考核产值合计", "1~12 月份预计发生成本(含预计精神文明奖)",
"基本考核产值", "剩余产值", "预估年底绩效");
return rowIndex + 1;
}
private void writeDetailRow(Sheet sheet, int rowIndex, EmployeeOutputSummaryExcelRespVO rowData, ExportData data,
CellStyle cellStyle, CellStyle leftCellStyle,
CellStyle amountStyle, CellStyle formulaStyle) {
Row row = sheet.createRow(rowIndex);
int excelRowNum = rowIndex + 1;
setText(row, 0, rowData == null || rowData.getSerialNo() == null ? "" : rowData.getSerialNo(), cellStyle);
setText(row, 1, rowData == null ? "" : safeText(rowData.getEmployeeName()), leftCellStyle);
setNumericCell(row, 2, rowData == null ? null : rowData.getQuarterOneAmount(), amountStyle);
setNumericCell(row, 3, rowData == null ? null : rowData.getQuarterTwoAmount(), amountStyle);
setNumericCell(row, 4, rowData == null ? null : rowData.getQuarterThreeAmount(), amountStyle);
setNumericCell(row, 5, rowData == null ? null : rowData.getQuarterFourAmount(), amountStyle);
setNumericCell(row, 6, rowData == null ? null : rowData.getAnnualTotalAmount(), amountStyle);
setNumericCell(row, 7, rowData == null ? null : rowData.getOfficeLeaderOrBimAmount(), amountStyle);
setNumericCell(row, 8, rowData == null ? null : rowData.getTotalAssessmentOutputAmount(), amountStyle);
setNumericCell(row, 9, rowData == null ? null : rowData.getExpectedCostAmount(), amountStyle);
setFormulaCell(row, 10, buildBasicAssessmentFormula(excelRowNum, data), formulaStyle);
setFormulaCell(row, 11, buildRemainingOutputFormula(excelRowNum), formulaStyle);
setFormulaCell(row, 12, buildPerformanceFormula(excelRowNum, data), formulaStyle);
}
private void writeTotalRow(Sheet sheet, int rowIndex, List<EmployeeOutputSummaryExcelRespVO> rows, ExportData data,
CellStyle totalCellStyle, CellStyle totalLeftCellStyle,
CellStyle totalStyle, CellStyle totalLeftStyle) {
Row row = sheet.createRow(rowIndex);
int totalExcelRowNum = rowIndex + 1;
int detailEndRowNum = DETAIL_START_ROW_NUM + rows.size() - 1;
setText(row, 0, "", totalCellStyle);
setText(row, 1, "合计", totalLeftCellStyle);
for (int col = 2; col <= LAST_COL; col++) {
if (rows.isEmpty()) {
setNumericCell(row, col, BigDecimal.ZERO, totalStyle);
continue;
}
setFormulaCell(row, col, "SUM(" + columnName(col) + DETAIL_START_ROW_NUM + ":"
+ columnName(col) + detailEndRowNum + ")", totalStyle);
}
// Ensure total-row formulas survive even if Excel recalculation is disabled.
if (!rows.isEmpty()) {
row.getCell(10).setCellFormula(buildTotalBasicAssessmentFormula(totalExcelRowNum));
row.getCell(11).setCellFormula(buildTotalRemainingOutputFormula(totalExcelRowNum));
row.getCell(12).setCellFormula(buildTotalPerformanceFormula(totalExcelRowNum, data));
}
}
private String buildBasicAssessmentFormula(int excelRowNum, ExportData data) {
BigDecimal kValue = data == null ? BigDecimal.ZERO : ratio(data.getKValue());
if (kValue.compareTo(BigDecimal.ZERO) <= 0) {
return "0";
}
return "ROUND(J" + excelRowNum + "/" + kValue.stripTrailingZeros().toPlainString() + ",2)";
}
private String buildRemainingOutputFormula(int excelRowNum) {
return "ROUND(I" + excelRowNum + "-K" + excelRowNum + ",2)";
}
private String buildPerformanceFormula(int excelRowNum, ExportData data) {
BigDecimal kValue = data == null ? BigDecimal.ZERO : ratio(data.getKValue());
if (kValue.compareTo(BigDecimal.ZERO) <= 0) {
return "0";
}
return "ROUND(L" + excelRowNum + "*" + kValue.stripTrailingZeros().toPlainString() + ",2)";
}
private String buildTotalBasicAssessmentFormula(int excelRowNum) {
return "SUM(K" + DETAIL_START_ROW_NUM + ":K" + (excelRowNum - 1) + ")";
}
private String buildTotalRemainingOutputFormula(int excelRowNum) {
return "SUM(L" + DETAIL_START_ROW_NUM + ":L" + (excelRowNum - 1) + ")";
}
private String buildTotalPerformanceFormula(int excelRowNum, ExportData data) {
return "SUM(M" + DETAIL_START_ROW_NUM + ":M" + (excelRowNum - 1) + ")";
}
private String safeYear(ExportData data) {
return data == null || data.getYear() == null ? "" : String.valueOf(data.getYear());
}
private CellStyle createAmountStyle(Workbook workbook, boolean bold, boolean leftAlign) {
CellStyle base = createCellStyle(workbook);
CellStyle style = createDerivedStyle(workbook, base, null,
leftAlign ? HorizontalAlignment.LEFT : HorizontalAlignment.CENTER, bold, (short) 10);
DataFormat dataFormat = workbook.createDataFormat();
style.setDataFormat(dataFormat.getFormat("0.00"));
return style;
}
private void setNumericCell(Row row, int cellIndex, BigDecimal value, CellStyle style) {
Cell cell = row.createCell(cellIndex);
if (value != null) {
cell.setCellValue(value.setScale(2, RoundingMode.HALF_UP).doubleValue());
}
cell.setCellStyle(style);
}
private void setFormulaCell(Row row, int cellIndex, String formula, CellStyle style) {
Cell cell = row.createCell(cellIndex);
cell.setCellFormula(formula);
cell.setCellStyle(style);
}
private String columnName(int zeroBasedIndex) {
int index = zeroBasedIndex;
StringBuilder builder = new StringBuilder();
while (index >= 0) {
builder.insert(0, (char) ('A' + index % 26));
index = index / 26 - 1;
}
return builder.toString();
}
@Data
public static class ExportData {
private Integer year;
private BigDecimal kValue;
private List<EmployeeOutputSummaryExcelRespVO> rows = Collections.emptyList();
}
}

View File

@@ -0,0 +1,464 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import lombok.Data;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
@Component
public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SUMMARY_PLACEHOLDER = "/";
private static final String BUDGET_SHEET_NAME = "建筑(装饰)工程项目考核产值预算表";
private static final String BUDGET_SHEET_FALLBACK_NAME = "项目考核产值预算表";
private static final String BUDGET_SHEET_TITLE = "附件4 建筑(装饰)工程项目考核产值预算表";
private static final String QUARTER_BUDGET_SHEET_NAME = "项目考核产值年度季度预算计取表";
private static final String QUARTER_BUDGET_SHEET_FALLBACK_NAME = "年度季度预算计取表";
private static final String QUARTER_BUDGET_REMARK = "1、设计阶段考核产值为总考核产值×阶段占比本年度/季度考核产值为阶段考核产值×本年度/季度发放比例。";
private static final String DATE_TEXT = "日期: 年 月 日";
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
buildBudgetSheet(workbook, data);
buildQuarterBudgetSheet(workbook, data);
return workbook;
}
private void buildBudgetSheet(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, BUDGET_SHEET_NAME, BUDGET_SHEET_FALLBACK_NAME);
setColumnWidths(sheet, 16, 14, 18, 16, 18, 14, 12, 12, 12, 18, 12, 14, 16);
CellStyle titleStyle = createTitleStyle(workbook);
CellStyle infoStyle = createInfoStyle(workbook);
CellStyle infoValueStyle = createDerivedStyle(workbook, infoStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createCellStyle(workbook);
CellStyle subtotalStyle = headerStyle;
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
int rowIndex = 0;
rowIndex = writeMergedTitleRow(sheet, rowIndex, BUDGET_SHEET_TITLE, titleStyle, 12);
rowIndex = buildBudgetHeader(sheet, rowIndex, headerStyle);
int dataStartRow = rowIndex;
if (data.getBudgetRows() != null) {
for (BudgetRow rowData : data.getBudgetRows()) {
if (rowData == null || rowData.getRowType() == null) {
continue;
}
if (BudgetRowType.DETAIL.equals(rowData.getRowType())) {
writeBudgetDetailRow(sheet, rowIndex++, rowData, cellStyle);
continue;
}
if (BudgetRowType.PART_EMPTY.equals(rowData.getRowType())) {
writeBudgetEmptyRow(sheet, rowIndex++, rowData, cellStyle);
continue;
}
if (BudgetRowType.PART_SUBTOTAL.equals(rowData.getRowType())) {
writeBudgetSummaryRow(sheet, rowIndex++, rowData, subtotalStyle);
continue;
}
if (BudgetRowType.PLANNING_TOTAL.equals(rowData.getRowType())) {
writeBudgetSummaryRow(sheet, rowIndex++, rowData, totalStyle);
}
}
}
mergeBudgetPlanningContentColumn(sheet, data.getBudgetRows(), dataStartRow);
Row remarkRow = sheet.createRow(rowIndex++);
setMergedRegionText(sheet, remarkRow.getRowNum(), remarkRow.getRowNum(), 0, 12,
"备注:" + safeText(data.getRemark()), infoValueStyle);
Row signRow = sheet.createRow(rowIndex);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 0, 3,
"项目经理/工程负责人:", infoValueStyle);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 4, 7,
"设计中心负责人:", infoValueStyle);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 8, 12,
"市场运营中心负责人:", infoValueStyle);
}
private int buildBudgetHeader(Sheet sheet, int rowIndex, CellStyle headerStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "项目规模", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 2, "建筑工程(㎡)\n精装工程", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 3, 3, "内部指导单价(元 /㎡)", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 4, 10, "考核产值面积", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 10, 10, "小计(㎡)", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 11, 11, "设计阶段占比", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 12, 12, "考核产值小计(万元)", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 4, 4, "建筑(装饰)设计面积(㎡)", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 5, 6, "套图系数", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 7, 9, "调整系数", headerStyle);
writeRow(sheet, firstRow + 2, headerStyle, (Object[]) new String[]{
"规划内容", "设计部位", "建筑类型", "", "", "栋数 / 户型数",
"套图系数", "规模系数", "修改系数", "复杂系数 / 复杂等级", "", "", ""
});
return rowIndex + 3;
}
private void writeBudgetDetailRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
setText(row, 2, safeText(rowData.getDisplayBuildingType()), style);
setText(row, 3, textOrBlank(rowData.getInternalGuidanceUnitPrice()), style);
setText(row, 4, textOrBlank(rowData.getDesignArea()), style);
setText(row, 5, integerText(rowData.getBuildingOrUnitCount()), style);
setText(row, 6, factorTextOrBlank(rowData.getDrawingSetFactor(), 2), style);
setText(row, 7, factorTextOrBlank(rowData.getScaleFactor(), 2), style);
setText(row, 8, factorTextOrBlank(rowData.getModificationFactor(), 2), style);
setText(row, 9, factorTextOrBlank(rowData.getComplexityFactor(), 2), style);
setText(row, 10, textOrBlank(rowData.getSubtotalArea()), style);
setText(row, 11, percentTextOrBlank(rowData.getCurrentDesignStageRatio()), style);
setText(row, 12, textOrBlank(rowData.getAssessmentOutputValueWan()), style);
}
private void writeBudgetEmptyRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
for (int i = 2; i <= 12; i++) {
setText(row, i, "", style);
}
}
private void writeBudgetSummaryRow(Sheet sheet, int rowIndex, BudgetRow rowData, CellStyle style) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, safeText(rowData.getPlanningContent()), style);
setText(row, 1, safeText(rowData.getDisplayDesignPart()), style);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, SUMMARY_PLACEHOLDER, style);
setText(row, 4, text(rowData.getDesignArea()), style);
setMergedRegionText(sheet, rowIndex, rowIndex, 5, 9, SUMMARY_PLACEHOLDER, style);
setText(row, 10, text(rowData.getSubtotalArea()), style);
setText(row, 11, SUMMARY_PLACEHOLDER, style);
setText(row, 12, text(rowData.getAssessmentOutputValueWan()), style);
}
private void mergeBudgetPlanningContentColumn(Sheet sheet, List<BudgetRow> budgetRows, int dataStartRow) {
if (budgetRows == null || budgetRows.isEmpty()) {
return;
}
int mergeStartRow = dataStartRow;
Long currentPlanningId = budgetRows.get(0).getPlanningId();
for (int i = 1; i < budgetRows.size(); i++) {
Long planningId = budgetRows.get(i).getPlanningId();
if (Objects.equals(currentPlanningId, planningId)) {
continue;
}
mergeBudgetPlanningContentRange(sheet, mergeStartRow, dataStartRow + i - 1);
mergeStartRow = dataStartRow + i;
currentPlanningId = planningId;
}
mergeBudgetPlanningContentRange(sheet, mergeStartRow, dataStartRow + budgetRows.size() - 1);
}
private void mergeBudgetPlanningContentRange(Sheet sheet, int startRow, int endRow) {
if (endRow <= startRow) {
return;
}
merge(sheet, startRow, endRow, 0, 0);
}
private void buildQuarterBudgetSheet(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, QUARTER_BUDGET_SHEET_NAME, QUARTER_BUDGET_SHEET_FALLBACK_NAME);
setColumnWidths(sheet, 10, 20, 18, 24, 16, 16, 16, 12, 14, 14, 12, 12, 12, 12, 14, 14, 12, 12, 12, 12, 14, 24);
CellStyle infoStyle = createInfoStyle(workbook);
CellStyle infoValueStyle = createDerivedStyle(workbook, infoStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createCellStyle(workbook);
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle centeredCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle rightAlignedInfoStyle = createDerivedStyle(workbook, infoValueStyle,
null, HorizontalAlignment.RIGHT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildQuarterBudgetHeader(sheet, rowIndex, data, headerStyle, centeredCellStyle);
int dataStartRow = rowIndex;
for (QuarterBudgetRow rowData : data.getQuarterBudgetRows()) {
Row row = sheet.createRow(rowIndex++);
if (rowData.isTotalRow()) {
writeQuarterBudgetTotalRow(sheet, row, rowData, totalStyle);
continue;
}
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(data.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), cellStyle);
setText(row, 4, textOrBlank(rowData.getTotalDesignArea()), cellStyle);
setText(row, 5, textOrBlank(rowData.getTotalAssessmentArea()), cellStyle);
setText(row, 6, textOrBlank(rowData.getTotalAssessmentOutputWan()), cellStyle);
setText(row, 7, percentTextOrBlank(rowData.getDesignStageRatio()), cellStyle);
setText(row, 8, textOrBlank(rowData.getDesignStageOutputWan()), cellStyle);
setText(row, 9, percentTextOrBlank(rowData.getHistoricalIssuedRatio()), cellStyle);
setText(row, 10, percentTextOrBlank(rowData.getQuarterOneRatio()), cellStyle);
setText(row, 11, percentTextOrBlank(rowData.getQuarterTwoRatio()), cellStyle);
setText(row, 12, percentTextOrBlank(rowData.getQuarterThreeRatio()), cellStyle);
setText(row, 13, percentTextOrBlank(rowData.getQuarterFourRatio()), cellStyle);
setText(row, 14, percentTextOrBlank(rowData.getCurrentYearRatio()), cellStyle);
setText(row, 15, percentTextOrBlank(rowData.getPendingRatio()), cellStyle);
setText(row, 16, textOrBlank(rowData.getQuarterOneAmountWan()), cellStyle);
setText(row, 17, textOrBlank(rowData.getQuarterTwoAmountWan()), cellStyle);
setText(row, 18, textOrBlank(rowData.getQuarterThreeAmountWan()), cellStyle);
setText(row, 19, textOrBlank(rowData.getQuarterFourAmountWan()), cellStyle);
setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), cellStyle);
setText(row, 21, safeText(rowData.getRatioRemark()), cellStyle);
}
mergeQuarterBudgetProjectAndOutputType(sheet, data.getQuarterBudgetRows(), dataStartRow);
Row remarkRow = sheet.createRow(rowIndex++);
setMergedRegionText(sheet, remarkRow.getRowNum(), remarkRow.getRowNum(), 0, 21,
QUARTER_BUDGET_REMARK, infoValueStyle);
Row signRow = sheet.createRow(rowIndex++);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 0, 6,
"项目经理/工程负责人:", infoValueStyle);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 7, 13,
"设计中心负责人:", infoValueStyle);
setMergedRegionText(sheet, signRow.getRowNum(), signRow.getRowNum(), 14, 21,
"市场运营中心负责人:", infoValueStyle);
Row dateRow = sheet.createRow(rowIndex);
setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 0, 21,
DATE_TEXT, rightAlignedInfoStyle);
}
private void mergeQuarterBudgetProjectAndOutputType(Sheet sheet,
List<QuarterBudgetRow> rows,
int dataStartRow) {
if (rows == null || rows.isEmpty()) {
return;
}
int detailRowCount = 0;
for (QuarterBudgetRow row : rows) {
if (row != null && !row.isTotalRow()) {
detailRowCount++;
}
}
if (detailRowCount <= 0) {
return;
}
if (detailRowCount > 1) {
merge(sheet, dataStartRow, dataStartRow + detailRowCount - 1, 1, 1);
}
int currentStartRow = -1;
String currentOutputType = null;
int currentRowIndex = dataStartRow;
for (QuarterBudgetRow row : rows) {
if (row == null || row.isTotalRow()) {
continue;
}
String outputType = safeText(row.getOutputType());
if (currentStartRow < 0) {
currentStartRow = currentRowIndex;
currentOutputType = outputType;
} else if (!Objects.equals(currentOutputType, outputType)) {
mergeQuarterBudgetOutputTypeRange(sheet, currentStartRow, currentRowIndex - 1);
currentStartRow = currentRowIndex;
currentOutputType = outputType;
}
currentRowIndex++;
}
mergeQuarterBudgetOutputTypeRange(sheet, currentStartRow, dataStartRow + detailRowCount - 1);
}
private void mergeQuarterBudgetOutputTypeRange(Sheet sheet, int startRow, int endRow) {
if (startRow < 0 || endRow <= startRow) {
return;
}
merge(sheet, startRow, endRow, 2, 2);
}
private int buildQuarterBudgetHeader(Sheet sheet, int rowIndex, ExportData data,
CellStyle headerStyle, CellStyle valueStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "工程编号", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 1, safeText(data.getProjectCode()), valueStyle);
setText(sheet.getRow(firstRow + 2), 0, "序号", headerStyle);
setText(sheet.getRow(firstRow + 2), 1, "项目名称", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 2), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 2), 3, "设计内容", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 4, 4, "总建筑(精装)设计面积(㎡)", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 5, 5, "总考核产值面积(㎡)", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 6, 6, "总考核产值(万元)", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 7, 8, "设计阶段考核产值分配", headerStyle);
setText(sheet.getRow(firstRow + 2), 7, "阶段占比", headerStyle);
setText(sheet.getRow(firstRow + 2), 8, "考核产值", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 9, 15, "发放情况", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 9, 9, "往年已发放比例", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 10, 14, "本年度发放比例", headerStyle);
setText(sheet.getRow(firstRow + 2), 10, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 11, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 12, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 13, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 14, "本年度小计", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 2, 15, 15, "未发放比例", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 16, 20, "本年度项目考核产值(万元)", headerStyle);
setText(sheet.getRow(firstRow + 2), 16, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 17, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 18, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 19, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 20, "本年度小计", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 21, 21, "备注(本年度比例说明)", headerStyle);
return rowIndex + 3;
}
private void writeQuarterBudgetTotalRow(Sheet sheet, Row row, QuarterBudgetRow rowData, CellStyle totalStyle) {
int rowIndex = row.getRowNum();
setMergedRegionText(sheet, rowIndex, rowIndex, 0, 3, "项目合计", totalStyle);
setText(row, 4, textOrBlank(rowData.getTotalDesignArea()), totalStyle);
setText(row, 5, textOrBlank(rowData.getTotalAssessmentArea()), totalStyle);
setText(row, 6, textOrBlank(rowData.getTotalAssessmentOutputWan()), totalStyle);
setText(row, 7, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 8, textOrBlank(rowData.getDesignStageOutputWan()), totalStyle);
setText(row, 9, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 10, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 11, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 12, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 13, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 14, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 15, SUMMARY_PLACEHOLDER, totalStyle);
setText(row, 16, textOrBlank(rowData.getQuarterOneAmountWan()), totalStyle);
setText(row, 17, textOrBlank(rowData.getQuarterTwoAmountWan()), totalStyle);
setText(row, 18, textOrBlank(rowData.getQuarterThreeAmountWan()), totalStyle);
setText(row, 19, textOrBlank(rowData.getQuarterFourAmountWan()), totalStyle);
setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), totalStyle);
setText(row, 21, "", totalStyle);
}
private String factorTextOrBlank(BigDecimal value, int digits) {
if (value == null) {
return "";
}
return value.setScale(digits, RoundingMode.HALF_UP).toPlainString();
}
private String textOrBlank(BigDecimal value) {
if (value == null) {
return "";
}
return value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
private String percentTextOrBlank(BigDecimal value) {
if (value == null) {
return "";
}
return percentText(value);
}
private String integerText(Integer value) {
return value == null ? "" : String.valueOf(value);
}
@Data
public static class ExportData {
private String projectCode;
private String projectName;
private String projectManagerName;
private String engineeringPrincipalName;
private BigDecimal totalConstructionArea;
private BigDecimal contractUnitPrice;
private BigDecimal internalGuidanceUnitPrice;
private BigDecimal totalAssessmentArea;
private BigDecimal totalAssessmentOutputValue;
private BigDecimal totalAssessmentOutputValueWan;
private String remark;
private String yearRangeText;
private List<BudgetRow> budgetRows;
private List<QuarterBudgetRow> quarterBudgetRows;
}
public enum BudgetRowType {
DETAIL,
PART_EMPTY,
PART_SUBTOTAL,
PLANNING_TOTAL
}
@Data
public static class BudgetRow {
private BudgetRowType rowType;
private Long planningId;
private String planningContent;
private String displayDesignPart;
private String displayBuildingType;
private BigDecimal internalGuidanceUnitPrice;
private BigDecimal designArea;
private Integer buildingOrUnitCount;
private BigDecimal drawingSetFactor;
private BigDecimal scaleFactor;
private BigDecimal modificationFactor;
private BigDecimal complexityFactor;
private BigDecimal subtotalArea;
private BigDecimal currentDesignStageRatio;
private BigDecimal assessmentOutputValueWan;
}
@Data
public static class QuarterBudgetRow {
private Integer serialNo;
private boolean totalRow;
private String outputType;
private String designContent;
private Integer budgetYear;
private BigDecimal totalDesignArea;
private BigDecimal totalAssessmentArea;
private BigDecimal totalAssessmentOutputWan;
private BigDecimal designStageRatio;
private BigDecimal designStageOutputWan;
private BigDecimal historicalIssuedRatio;
private BigDecimal quarterOneRatio;
private BigDecimal quarterTwoRatio;
private BigDecimal quarterThreeRatio;
private BigDecimal quarterFourRatio;
private BigDecimal currentYearRatio;
private BigDecimal pendingRatio;
private BigDecimal quarterOneAmountWan;
private BigDecimal quarterTwoAmountWan;
private BigDecimal quarterThreeAmountWan;
private BigDecimal quarterFourAmountWan;
private BigDecimal yearTotalAmountWan;
private String ratioRemark;
}
}

View File

@@ -0,0 +1,394 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import lombok.Data;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
@Component
public class ProjectLeadQuarterOutputExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SHEET1_NAME = "工作量分配";
private static final String SHEET2_NAME = "季度考核产值";
private static final String SHEET1_TITLE = "项目经理/专业负责人人员工作量分配";
private static final String SHEET2_TITLE = "项目经理/工程负责人年度/季度项目考核产值";
private static final String DEFAULT_PROJECT_MANAGER_HEADER = "项目经理人员";
private static final String DEFAULT_ENGINEERING_HEADER = "工程负责人人员";
private static final String DEFAULT_CENTER_SIGNER = "设计中心相关负责人(签名):";
private static final String DEFAULT_PROJECT_SIGNER = "项目经理/工程负责人(签名):";
private static final List<String> OUTPUT_TYPE_ORDER = Arrays.asList(
"六大专业考核产值",
"专业分包产值",
"内部协作产值",
"其他产值"
);
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
buildSheet1(workbook, data);
buildSheet2(workbook, data);
return workbook;
}
private void buildSheet1(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET1_NAME, SHEET1_NAME);
setColumnWidths(sheet, 8, 26, 18, 24, 20, 20);
CellStyle titleStyle = createTitleStyle(workbook);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
int rowIndex = 0;
rowIndex = writeMergedTitleRow(sheet, rowIndex, SHEET1_TITLE, titleStyle, 5);
rowIndex = buildSheet1Header(sheet, rowIndex, data, headerStyle);
List<LeadQuarterRow> rows = resolveDetailRows(data);
int bodyStartRow = rowIndex;
for (LeadQuarterRow rowData : rows) {
writeSheet1DetailRow(sheet, rowIndex++, data, rowData, cellStyle, leftCellStyle);
}
mergeProjectNameColumn(sheet, bodyStartRow, rowIndex - 1, data, cellStyle);
mergeOutputTypeColumn(sheet, bodyStartRow, rows, cellStyle);
}
private void buildSheet2(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET2_NAME, SHEET2_NAME);
setColumnWidths(sheet, 8, 26, 18, 24, 12, 12, 12, 12, 14, 12, 12, 12, 12, 14);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle subtotalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle signatureStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.LEFT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildSheet2Header(sheet, rowIndex, data, headerStyle);
List<LeadQuarterRow> rows = resolveSheet2Rows(data);
int bodyStartRow = rowIndex;
for (LeadQuarterRow rowData : rows) {
if (Boolean.TRUE.equals(rowData.getSubtotalRow())) {
writeSheet2SubtotalRow(sheet, rowIndex++, rowData, subtotalStyle);
continue;
}
writeSheet2DetailRow(sheet, rowIndex++, data, rowData, cellStyle, leftCellStyle);
}
mergeProjectNameColumn(sheet, bodyStartRow, rowIndex - 1, data, cellStyle);
mergeOutputTypeColumn(sheet, bodyStartRow, rows, cellStyle);
writeSheet2SignatureRow(sheet, rowIndex, data, signatureStyle);
}
private int buildSheet1Header(Sheet sheet, int rowIndex, ExportData data, CellStyle headerStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "序号", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 1, "项目名称", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 1), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 1), 3, "设计内容", headerStyle);
setText(sheet.getRow(firstRow), 4,
defaultLabel(resolveProjectManagerNames(data), DEFAULT_PROJECT_MANAGER_HEADER), headerStyle);
setText(sheet.getRow(firstRow), 5,
defaultLabel(resolveEngineeringPrincipalNames(data), DEFAULT_ENGINEERING_HEADER), headerStyle);
setText(sheet.getRow(firstRow + 1), 4, "工作比例", headerStyle);
setText(sheet.getRow(firstRow + 1), 5, "工作比例", headerStyle);
return rowIndex + 2;
}
private int buildSheet2Header(Sheet sheet, int rowIndex, ExportData data, CellStyle headerStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
setMergedRegionText(sheet, firstRow, firstRow + 2, 0, 0, "序号", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 1, 1, "项目名称", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 2), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 2), 3, "设计内容", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 4, 13, SHEET2_TITLE, headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 4, 8,
defaultLabel(resolveProjectManagerNames(data), DEFAULT_PROJECT_MANAGER_HEADER), headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 9, 13,
defaultLabel(resolveEngineeringPrincipalNames(data), DEFAULT_ENGINEERING_HEADER), headerStyle);
setText(sheet.getRow(firstRow + 2), 4, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 5, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 6, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 7, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 8, "本年度小计", headerStyle);
setText(sheet.getRow(firstRow + 2), 9, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 10, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 11, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 12, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 13, "本年度小计", headerStyle);
return rowIndex + 3;
}
private void writeSheet1DetailRow(Sheet sheet, int rowIndex, ExportData data, LeadQuarterRow rowData,
CellStyle cellStyle, CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(data.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
setText(row, 4, percentTextOrBlank(rowData.getProjectManagerRatio()), cellStyle);
setText(row, 5, percentTextOrBlank(rowData.getEngineeringPrincipalRatio()), cellStyle);
}
private void writeSheet2DetailRow(Sheet sheet, int rowIndex, ExportData data, LeadQuarterRow rowData,
CellStyle cellStyle, CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(data.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
setText(row, 4, textOrBlank(rowData.getProjectManagerQuarterOneAmountWan()), cellStyle);
setText(row, 5, textOrBlank(rowData.getProjectManagerQuarterTwoAmountWan()), cellStyle);
setText(row, 6, textOrBlank(rowData.getProjectManagerQuarterThreeAmountWan()), cellStyle);
setText(row, 7, textOrBlank(rowData.getProjectManagerQuarterFourAmountWan()), cellStyle);
setText(row, 8, textOrBlank(rowData.getProjectManagerYearTotalAmountWan()), cellStyle);
setText(row, 9, textOrBlank(rowData.getEngineeringPrincipalQuarterOneAmountWan()), cellStyle);
setText(row, 10, textOrBlank(rowData.getEngineeringPrincipalQuarterTwoAmountWan()), cellStyle);
setText(row, 11, textOrBlank(rowData.getEngineeringPrincipalQuarterThreeAmountWan()), cellStyle);
setText(row, 12, textOrBlank(rowData.getEngineeringPrincipalQuarterFourAmountWan()), cellStyle);
setText(row, 13, textOrBlank(rowData.getEngineeringPrincipalYearTotalAmountWan()), cellStyle);
}
private void writeSheet2SubtotalRow(Sheet sheet, int rowIndex, LeadQuarterRow rowData, CellStyle subtotalStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", subtotalStyle);
setText(row, 1, "", subtotalStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, safeText(rowData.getDesignContent()), subtotalStyle);
setText(row, 4, textOrBlank(rowData.getProjectManagerQuarterOneAmountWan()), subtotalStyle);
setText(row, 5, textOrBlank(rowData.getProjectManagerQuarterTwoAmountWan()), subtotalStyle);
setText(row, 6, textOrBlank(rowData.getProjectManagerQuarterThreeAmountWan()), subtotalStyle);
setText(row, 7, textOrBlank(rowData.getProjectManagerQuarterFourAmountWan()), subtotalStyle);
setText(row, 8, textOrBlank(rowData.getProjectManagerYearTotalAmountWan()), subtotalStyle);
setText(row, 9, textOrBlank(rowData.getEngineeringPrincipalQuarterOneAmountWan()), subtotalStyle);
setText(row, 10, textOrBlank(rowData.getEngineeringPrincipalQuarterTwoAmountWan()), subtotalStyle);
setText(row, 11, textOrBlank(rowData.getEngineeringPrincipalQuarterThreeAmountWan()), subtotalStyle);
setText(row, 12, textOrBlank(rowData.getEngineeringPrincipalQuarterFourAmountWan()), subtotalStyle);
setText(row, 13, textOrBlank(rowData.getEngineeringPrincipalYearTotalAmountWan()), subtotalStyle);
}
private void writeSheet2SignatureRow(Sheet sheet, int rowIndex, ExportData data, CellStyle signatureStyle) {
sheet.createRow(rowIndex);
setMergedRegionText(sheet, rowIndex, rowIndex, 0, 6,
defaultLabel(data.getCenterSignerLabel(), DEFAULT_CENTER_SIGNER), signatureStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, 7, 13,
defaultLabel(data.getProjectSignerLabel(), DEFAULT_PROJECT_SIGNER), signatureStyle);
}
private void mergeProjectNameColumn(Sheet sheet, int startRow, int endRow, ExportData data, CellStyle style) {
if (startRow < 0 || endRow < startRow) {
return;
}
setMergedRegionText(sheet, startRow, endRow, 1, 1, safeText(data.getProjectName()), style);
}
private void mergeOutputTypeColumn(Sheet sheet, int bodyStartRow, List<LeadQuarterRow> rows, CellStyle style) {
if (rows == null || rows.isEmpty()) {
return;
}
int groupStartIndex = -1;
String currentOutputType = "";
for (int i = 0; i < rows.size(); i++) {
if (Boolean.TRUE.equals(rows.get(i).getSubtotalRow())) {
if (groupStartIndex >= 0) {
setMergedRegionText(sheet, bodyStartRow + groupStartIndex, bodyStartRow + i - 1,
2, 2, currentOutputType, style);
groupStartIndex = -1;
currentOutputType = "";
}
continue;
}
if (groupStartIndex < 0) {
groupStartIndex = i;
currentOutputType = safeText(rows.get(i).getOutputType());
continue;
}
String nextOutputType = safeText(rows.get(i).getOutputType());
if (!currentOutputType.equals(nextOutputType)) {
setMergedRegionText(sheet, bodyStartRow + groupStartIndex, bodyStartRow + i - 1,
2, 2, currentOutputType, style);
groupStartIndex = i;
currentOutputType = nextOutputType;
}
}
if (groupStartIndex >= 0) {
setMergedRegionText(sheet, bodyStartRow + groupStartIndex, bodyStartRow + rows.size() - 1,
2, 2, currentOutputType, style);
}
}
private List<LeadQuarterRow> resolveDetailRows(ExportData data) {
if (data == null || data.getRows() == null || data.getRows().isEmpty()) {
return new ArrayList<>();
}
List<LeadQuarterRow> rows = new ArrayList<>(data.getRows());
rows.sort(Comparator
.comparingInt((LeadQuarterRow row) -> outputTypeOrder(row.getOutputType()))
.thenComparing(LeadQuarterRow::getSerialNo, Comparator.nullsLast(Integer::compareTo)));
return rows;
}
private List<LeadQuarterRow> resolveSheet2Rows(ExportData data) {
List<LeadQuarterRow> detailRows = resolveDetailRows(data);
if (detailRows.isEmpty()) {
return detailRows;
}
List<LeadQuarterRow> rows = new ArrayList<>(detailRows);
rows.add(buildSheet2SubtotalRow(detailRows));
return rows;
}
private LeadQuarterRow buildSheet2SubtotalRow(List<LeadQuarterRow> groupRows) {
LeadQuarterRow row = new LeadQuarterRow();
row.setOutputType("");
row.setDesignContent("总计");
row.setSubtotalRow(Boolean.TRUE);
for (LeadQuarterRow detailRow : groupRows) {
row.setProjectManagerQuarterOneAmountWan(addAmount(row.getProjectManagerQuarterOneAmountWan(),
detailRow.getProjectManagerQuarterOneAmountWan()));
row.setProjectManagerQuarterTwoAmountWan(addAmount(row.getProjectManagerQuarterTwoAmountWan(),
detailRow.getProjectManagerQuarterTwoAmountWan()));
row.setProjectManagerQuarterThreeAmountWan(addAmount(row.getProjectManagerQuarterThreeAmountWan(),
detailRow.getProjectManagerQuarterThreeAmountWan()));
row.setProjectManagerQuarterFourAmountWan(addAmount(row.getProjectManagerQuarterFourAmountWan(),
detailRow.getProjectManagerQuarterFourAmountWan()));
row.setProjectManagerYearTotalAmountWan(addAmount(row.getProjectManagerYearTotalAmountWan(),
detailRow.getProjectManagerYearTotalAmountWan()));
row.setEngineeringPrincipalQuarterOneAmountWan(addAmount(row.getEngineeringPrincipalQuarterOneAmountWan(),
detailRow.getEngineeringPrincipalQuarterOneAmountWan()));
row.setEngineeringPrincipalQuarterTwoAmountWan(addAmount(row.getEngineeringPrincipalQuarterTwoAmountWan(),
detailRow.getEngineeringPrincipalQuarterTwoAmountWan()));
row.setEngineeringPrincipalQuarterThreeAmountWan(addAmount(row.getEngineeringPrincipalQuarterThreeAmountWan(),
detailRow.getEngineeringPrincipalQuarterThreeAmountWan()));
row.setEngineeringPrincipalQuarterFourAmountWan(addAmount(row.getEngineeringPrincipalQuarterFourAmountWan(),
detailRow.getEngineeringPrincipalQuarterFourAmountWan()));
row.setEngineeringPrincipalYearTotalAmountWan(addAmount(row.getEngineeringPrincipalYearTotalAmountWan(),
detailRow.getEngineeringPrincipalYearTotalAmountWan()));
}
return row;
}
private BigDecimal addAmount(BigDecimal left, BigDecimal right) {
return amount(left).add(amount(right));
}
private int outputTypeOrder(String outputType) {
int index = OUTPUT_TYPE_ORDER.indexOf(safeText(outputType));
return index >= 0 ? index : OUTPUT_TYPE_ORDER.size();
}
private String resolveProjectManagerNames(ExportData data) {
if (data == null) {
return "";
}
String displayName = safeText(data.getProjectManagerNames());
if (!displayName.isEmpty()) {
return displayName;
}
return resolveFallbackNames(data.getRows(), true);
}
private String resolveEngineeringPrincipalNames(ExportData data) {
if (data == null) {
return "";
}
String displayName = safeText(data.getEngineeringPrincipalNames());
if (!displayName.isEmpty()) {
return displayName;
}
return resolveFallbackNames(data.getRows(), false);
}
private String resolveFallbackNames(List<LeadQuarterRow> rows, boolean projectManager) {
if (rows == null || rows.isEmpty()) {
return "";
}
for (LeadQuarterRow row : rows) {
if (row == null) {
continue;
}
String current = projectManager ? row.getProjectManagerNames() : row.getEngineeringPrincipalNames();
if (!safeText(current).isEmpty()) {
return safeText(current);
}
}
return "";
}
private String defaultLabel(String value, String fallback) {
String text = safeText(value);
return text.isEmpty() ? fallback : text;
}
private String percentTextOrBlank(BigDecimal value) {
return value == null ? "" : percentText(value);
}
private String textOrBlank(BigDecimal value) {
return value == null ? "" : text(value);
}
@Data
public static class ExportData {
private String projectName;
private Integer year;
private String projectManagerNames;
private String engineeringPrincipalNames;
private String centerSignerLabel;
private String projectSignerLabel;
private List<LeadQuarterRow> rows;
}
@Data
public static class LeadQuarterRow {
private Integer serialNo;
private String outputType;
private String designContent;
private Boolean subtotalRow;
private String projectManagerNames;
private BigDecimal projectManagerRatio;
private String engineeringPrincipalNames;
private BigDecimal engineeringPrincipalRatio;
private BigDecimal projectManagerQuarterOneAmountWan;
private BigDecimal projectManagerQuarterTwoAmountWan;
private BigDecimal projectManagerQuarterThreeAmountWan;
private BigDecimal projectManagerQuarterFourAmountWan;
private BigDecimal projectManagerYearTotalAmountWan;
private BigDecimal engineeringPrincipalQuarterOneAmountWan;
private BigDecimal engineeringPrincipalQuarterTwoAmountWan;
private BigDecimal engineeringPrincipalQuarterThreeAmountWan;
private BigDecimal engineeringPrincipalQuarterFourAmountWan;
private BigDecimal engineeringPrincipalYearTotalAmountWan;
}
}

View File

@@ -0,0 +1,268 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import lombok.Data;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class ProjectOverviewOutputExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SHEET_NAME = "项目总览表";
private static final String SHEET_FALLBACK_NAME = "项目总览表";
private static final String TITLE = "7.1.5 项目总览表";
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
buildSheet(workbook, data);
return workbook;
}
private void buildSheet(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET_NAME, SHEET_FALLBACK_NAME);
List<EmployeeColumn> employeeColumns = data.getEmployeeColumns() == null
? Collections.emptyList() : data.getEmployeeColumns();
int lastCol = 8 + employeeColumns.size() * 5;
setColumnWidths(sheet, buildColumnWidths(employeeColumns.size()));
CellStyle subtitleStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.CENTER, true, (short) 11);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle bodyCenterStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle bodyLeftStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle totalLeftStyle = createDerivedStyle(workbook, totalStyle,
IndexedColors.GOLD, HorizontalAlignment.LEFT, true, (short) 10);
int rowIndex = 0;
rowIndex = writeSubtitleRow(sheet, rowIndex, data, subtitleStyle, lastCol);
rowIndex = writeHeader(sheet, rowIndex, employeeColumns, headerStyle);
if (data.getRows() != null) {
for (ProjectRow rowData : data.getRows()) {
writeDataRow(sheet, rowIndex++, rowData, employeeColumns, bodyCenterStyle, bodyLeftStyle);
}
}
writeMergedTotalRow(sheet, rowIndex, data.getTotalRow(), employeeColumns, totalStyle, totalLeftStyle);
}
private int writeSubtitleRow(Sheet sheet, int rowIndex, ExportData data, CellStyle style, int lastCol) {
Row row = getOrCreateRow(sheet, rowIndex);
for (int cellIndex = 0; cellIndex <= lastCol; cellIndex++) {
setText(row, cellIndex, "", style);
}
if (lastCol <= 0) {
setText(row, 0, buildSubtitle(data), style);
return rowIndex + 1;
}
setMergedRegionText(sheet, rowIndex, rowIndex, 0, lastCol, buildSubtitle(data), style);
return rowIndex + 1;
}
private String buildSubtitle(ExportData data) {
String yearText = data == null || data.getYear() == null ? "" : String.valueOf(data.getYear());
return safeText(yearText) + " 年度 "
+ safeText(data.getOfficeName()) + " 产值汇总(单位:万元)";
}
private int writeHeader(Sheet sheet, int rowIndex, List<EmployeeColumn> employeeColumns, CellStyle headerStyle) {
int topRow = rowIndex;
sheet.createRow(topRow);
sheet.createRow(topRow + 1);
setMergedRegionText(sheet, topRow, topRow + 1, 0, 0, "序号", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 1, 1, "项目名称", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 2, 2, "工程进度情况及其它说明", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 3, 3, "工作阶段", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 4, 4, "本专业+项目总核算总产值(万元)", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 5, 5, "往期已发放百分比", headerStyle);
setMergedRegionText(sheet, topRow, topRow, 6, 7, "本期结算", headerStyle);
setMergedRegionText(sheet, topRow, topRow + 1, 8, 8, "未结算比例", headerStyle);
setText(getOrCreateRow(sheet, topRow + 1), 6, "占比", headerStyle);
setText(getOrCreateRow(sheet, topRow + 1), 7, "考核产值(万元)", headerStyle);
int columnIndex = 9;
for (EmployeeColumn employeeColumn : employeeColumns) {
setMergedRegionText(sheet, topRow, topRow, columnIndex, columnIndex + 4,
safeText(employeeColumn.getEmployeeName()), headerStyle);
Row subRow = getOrCreateRow(sheet, topRow + 1);
setText(subRow, columnIndex, "一季度", headerStyle);
setText(subRow, columnIndex + 1, "二季度", headerStyle);
setText(subRow, columnIndex + 2, "三季度", headerStyle);
setText(subRow, columnIndex + 3, "四季度", headerStyle);
setText(subRow, columnIndex + 4, "本年度小计", headerStyle);
columnIndex += 5;
}
return rowIndex + 2;
}
private void writeDataRow(Sheet sheet, int rowIndex, ProjectRow rowData, List<EmployeeColumn> employeeColumns,
CellStyle bodyCenterStyle, CellStyle bodyLeftStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), bodyCenterStyle);
setText(row, 1, safeText(rowData.getProjectName()), bodyLeftStyle);
setText(row, 2, safeText(rowData.getProgressText()), bodyLeftStyle);
setText(row, 3, safeText(rowData.getWorkStage()), bodyLeftStyle);
setText(row, 4, amountTextOrBlank(rowData.getTotalOutputAmount()), bodyCenterStyle);
setText(row, 5, percentTextOrBlank(rowData.getHistoricalIssuedRatio()), bodyCenterStyle);
setText(row, 6, percentTextOrBlank(rowData.getCurrentSettlementRatio()), bodyCenterStyle);
setText(row, 7, amountTextOrBlank(rowData.getCurrentSettlementAmount()), bodyCenterStyle);
setText(row, 8, percentTextOrBlank(rowData.getPendingRatio()), bodyCenterStyle);
Map<Long, EmployeeAmountValue> employeeAmountMap = rowData.getEmployeeAmountMap() == null
? Collections.emptyMap() : rowData.getEmployeeAmountMap();
int columnIndex = 9;
for (EmployeeColumn employeeColumn : employeeColumns) {
EmployeeAmountValue value = employeeAmountMap.get(employeeColumn.getEmployeeId());
setText(row, columnIndex, amountTextOrBlank(value == null ? null : value.getQuarterOneAmount()), bodyCenterStyle);
setText(row, columnIndex + 1, amountTextOrBlank(value == null ? null : value.getQuarterTwoAmount()), bodyCenterStyle);
setText(row, columnIndex + 2, amountTextOrBlank(value == null ? null : value.getQuarterThreeAmount()), bodyCenterStyle);
setText(row, columnIndex + 3, amountTextOrBlank(value == null ? null : value.getQuarterFourAmount()), bodyCenterStyle);
setText(row, columnIndex + 4, amountTextOrBlank(value == null ? null : value.getAnnualTotalAmount()), bodyCenterStyle);
columnIndex += 5;
}
}
private void writeTotalRow(Sheet sheet, int rowIndex, ProjectRow totalRow, List<EmployeeColumn> employeeColumns,
CellStyle totalStyle, CellStyle totalLeftStyle) {
ProjectRow rowData = totalRow == null ? new ProjectRow() : totalRow;
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", totalStyle);
setText(row, 1, "合计", totalLeftStyle);
setText(row, 2, "", totalStyle);
setText(row, 3, "", totalStyle);
setText(row, 4, amountTextOrBlank(rowData.getTotalOutputAmount()), totalStyle);
setText(row, 5, percentTextOrBlank(rowData.getHistoricalIssuedRatio()), totalStyle);
setText(row, 6, percentTextOrBlank(rowData.getCurrentSettlementRatio()), totalStyle);
setText(row, 7, amountTextOrBlank(rowData.getCurrentSettlementAmount()), totalStyle);
setText(row, 8, percentTextOrBlank(rowData.getPendingRatio()), totalStyle);
Map<Long, EmployeeAmountValue> employeeAmountMap = rowData.getEmployeeAmountMap() == null
? Collections.emptyMap() : rowData.getEmployeeAmountMap();
int columnIndex = 9;
for (EmployeeColumn employeeColumn : employeeColumns) {
EmployeeAmountValue value = employeeAmountMap.get(employeeColumn.getEmployeeId());
setText(row, columnIndex, amountTextOrBlank(value == null ? null : value.getQuarterOneAmount()), totalStyle);
setText(row, columnIndex + 1, amountTextOrBlank(value == null ? null : value.getQuarterTwoAmount()), totalStyle);
setText(row, columnIndex + 2, amountTextOrBlank(value == null ? null : value.getQuarterThreeAmount()), totalStyle);
setText(row, columnIndex + 3, amountTextOrBlank(value == null ? null : value.getQuarterFourAmount()), totalStyle);
setText(row, columnIndex + 4, amountTextOrBlank(value == null ? null : value.getAnnualTotalAmount()), totalStyle);
columnIndex += 5;
}
}
private void writeMergedTotalRow(Sheet sheet, int rowIndex, ProjectRow totalRow, List<EmployeeColumn> employeeColumns,
CellStyle totalStyle, CellStyle totalLeftStyle) {
ProjectRow rowData = totalRow == null ? new ProjectRow() : totalRow;
Row row = sheet.createRow(rowIndex);
setMergedRegionText(sheet, rowIndex, rowIndex, 0, 3, "\u5408\u8ba1", totalStyle);
setText(row, 4, amountTextOrBlank(rowData.getTotalOutputAmount()), totalStyle);
setText(row, 5, "", totalStyle);
setText(row, 6, "", totalStyle);
setText(row, 7, amountTextOrBlank(rowData.getCurrentSettlementAmount()), totalStyle);
setText(row, 8, "", totalStyle);
Map<Long, EmployeeAmountValue> employeeAmountMap = rowData.getEmployeeAmountMap() == null
? Collections.emptyMap() : rowData.getEmployeeAmountMap();
int columnIndex = 9;
for (EmployeeColumn employeeColumn : employeeColumns) {
EmployeeAmountValue value = employeeAmountMap.get(employeeColumn.getEmployeeId());
setText(row, columnIndex, amountTextOrBlank(value == null ? null : value.getQuarterOneAmount()), totalStyle);
setText(row, columnIndex + 1, amountTextOrBlank(value == null ? null : value.getQuarterTwoAmount()), totalStyle);
setText(row, columnIndex + 2, amountTextOrBlank(value == null ? null : value.getQuarterThreeAmount()), totalStyle);
setText(row, columnIndex + 3, amountTextOrBlank(value == null ? null : value.getQuarterFourAmount()), totalStyle);
setText(row, columnIndex + 4, amountTextOrBlank(value == null ? null : value.getAnnualTotalAmount()), totalStyle);
columnIndex += 5;
}
}
private String amountTextOrBlank(BigDecimal value) {
if (value == null || amount(value).compareTo(BigDecimal.ZERO) == 0) {
return "";
}
return amount(value).divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP).toPlainString();
}
private String percentTextOrBlank(BigDecimal value) {
if (value == null || ratio(value).compareTo(BigDecimal.ZERO) == 0) {
return "";
}
return percentText(value);
}
private int[] buildColumnWidths(int employeeCount) {
int[] widths = new int[9 + employeeCount * 5];
widths[0] = 8;
widths[1] = 24;
widths[2] = 28;
widths[3] = 20;
widths[4] = 18;
widths[5] = 14;
widths[6] = 12;
widths[7] = 14;
widths[8] = 12;
for (int i = 9; i < widths.length; i += 5) {
widths[i] = 12;
widths[i + 1] = 12;
widths[i + 2] = 12;
widths[i + 3] = 12;
widths[i + 4] = 14;
}
return widths;
}
@Data
public static class ExportData {
private Integer year;
private String officeName;
private List<EmployeeColumn> employeeColumns = Collections.emptyList();
private List<ProjectRow> rows = Collections.emptyList();
private ProjectRow totalRow;
}
@Data
public static class EmployeeColumn {
private Long employeeId;
private String employeeName;
}
@Data
public static class ProjectRow {
private Integer serialNo;
private String projectName;
private String progressText;
private String workStage;
private BigDecimal totalOutputAmount;
private BigDecimal historicalIssuedRatio;
private BigDecimal currentSettlementRatio;
private BigDecimal currentSettlementAmount;
private BigDecimal pendingRatio;
private Map<Long, EmployeeAmountValue> employeeAmountMap = new LinkedHashMap<>();
}
@Data
public static class EmployeeAmountValue {
private BigDecimal quarterOneAmount;
private BigDecimal quarterTwoAmount;
private BigDecimal quarterThreeAmount;
private BigDecimal quarterFourAmount;
private BigDecimal annualTotalAmount;
}
}

View File

@@ -0,0 +1,691 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import lombok.Data;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SHEET1_NAME = "专业间项目考核产值年度季度计取表";
private static final String SHEET1_FALLBACK_NAME = "项目级年度季度计取表";
private static final String SHEET2_NAME = "专业维度统计表";
private static final String SHEET2_FALLBACK_NAME = "专业维度统计";
private static final String TOTAL_LABEL = "合计";
private static final int SHEET1_SPECIALTY_RATIO_START_COL = 23;
private static final int SHEET1_SPECIALTY_AMOUNT_START_COL = 30;
private static final int SHEET2_BASE_COL_COUNT = 4;
private static final int SHEET2_SPECIALTY_BLOCK_WIDTH = 5;
private static final List<String> OUTPUT_TYPE_ORDER = Arrays.asList(
"六大专业考核产值",
"专业分包产值",
"内部协作产值",
"其他产值"
);
private static final List<String> FIXED_OUTPUT_TYPES = Arrays.asList(
"六大专业考核产值",
"专业分包产值",
"内部协作产值"
);
private static final List<String> SPECIALTY_NAMES = Arrays.asList(
"建筑",
"精装",
"结构",
"给排水",
"暖通",
"电气",
"数字化设计"
);
private static final List<String> SHEET2_SIGNATURE_LABELS = Arrays.asList(
"建筑专业负责人(签名)",
"精装专业负责人(签名)",
"结构专业负责人(签名)",
"给排水专业负责人(签名)",
"暖通专业负责人(签名)",
"电气专业负责人(签名)",
"数字化设计专业负责人(签名)"
);
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
buildSheet1(workbook, data);
buildSheet2(workbook, data);
return workbook;
}
private void buildSheet1(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET1_NAME, SHEET1_FALLBACK_NAME);
setColumnWidths(sheet,
10, 24, 16, 20, 12, 12, 12, 12, 14,
10, 14, 12, 12, 12, 12, 14,
10, 14, 12, 12, 12, 12, 14,
10, 10, 10, 10, 10, 10, 10,
12, 12, 12, 12, 12, 12, 12);
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle totalLeftStyle = createDerivedStyle(workbook, totalStyle,
IndexedColors.GOLD, HorizontalAlignment.LEFT, true, (short) 10);
CellStyle signatureStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.LEFT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildSheet1Header(sheet, rowIndex, data, headerStyle, cellStyle);
int bodyStartRow = rowIndex;
List<OutputTypeGroup> groups = buildOutputTypeGroups(data);
for (OutputTypeGroup group : groups) {
int groupStartRow = rowIndex;
for (QuarterRow rowData : group.getRows()) {
writeSheet1DetailRow(sheet, rowIndex++, data, rowData, cellStyle, leftCellStyle);
}
mergeOutputTypeColumn(sheet, groupStartRow, rowIndex - 1, group.getOutputType(), cellStyle);
}
QuarterRow totalRow = resolveProjectTotalRow(data);
int totalRowIndex = -1;
if (totalRow != null) {
totalRowIndex = rowIndex;
writeSheet1TotalRow(sheet, rowIndex++, data, totalRow, totalStyle, totalLeftStyle);
}
mergeProjectNameColumn(sheet, bodyStartRow, totalRowIndex >= 0 ? totalRowIndex : rowIndex - 1,
safeText(data.getProjectName()), cellStyle);
writeSheet1SignatureRow(sheet, rowIndex, data, signatureStyle);
}
private int buildSheet1Header(Sheet sheet, int rowIndex, ExportData data,
CellStyle headerStyle, CellStyle valueStyle) {
int firstRow = buildCommonLeftHeader(sheet, rowIndex, data, headerStyle, valueStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 4, 8, "本年度项目考核产值(万元)", headerStyle);
setText(sheet.getRow(firstRow + 2), 4, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 5, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 6, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 7, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 8, "本年度小计", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 9, 15, "项目经理 / 工程负责人年度 / 季度项目考核产值", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 9, 10, "项总 / 工程负责人", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 11, 15, "考核产值(万元)", headerStyle);
setText(sheet.getRow(firstRow + 2), 9, "占比", headerStyle);
setText(sheet.getRow(firstRow + 2), 10, "总考核产值", headerStyle);
setText(sheet.getRow(firstRow + 2), 11, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 12, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 13, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 14, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 15, "本年度总计", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, 16, 22, "六大专业年度 / 季度项目考核产值合计", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 16, 17, "六大专业", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 18, 22, "考核产值(万元)", headerStyle);
setText(sheet.getRow(firstRow + 2), 16, "占比", headerStyle);
setText(sheet.getRow(firstRow + 2), 17, "总考核产值", headerStyle);
setText(sheet.getRow(firstRow + 2), 18, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 19, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 20, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 21, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), 22, "本年度总计", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow, SHEET1_SPECIALTY_RATIO_START_COL, sheet1LastCol(),
"各专业年度 / 季度项目考核产值", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1,
SHEET1_SPECIALTY_RATIO_START_COL, SHEET1_SPECIALTY_AMOUNT_START_COL - 1,
"各专业考核产值占比", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1,
SHEET1_SPECIALTY_AMOUNT_START_COL, sheet1LastCol(),
"各专业考核产值", headerStyle);
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
setText(sheet.getRow(firstRow + 2), SHEET1_SPECIALTY_RATIO_START_COL + i, SPECIALTY_NAMES.get(i), headerStyle);
setText(sheet.getRow(firstRow + 2), SHEET1_SPECIALTY_AMOUNT_START_COL + i, SPECIALTY_NAMES.get(i), headerStyle);
}
return rowIndex + 3;
}
private void writeSheet1DetailRow(Sheet sheet, int rowIndex, ExportData data, QuarterRow rowData,
CellStyle cellStyle, CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(data.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
setText(row, 4, textOrBlank(rowData.getQuarterOneAmountWan()), cellStyle);
setText(row, 5, textOrBlank(rowData.getQuarterTwoAmountWan()), cellStyle);
setText(row, 6, textOrBlank(rowData.getQuarterThreeAmountWan()), cellStyle);
setText(row, 7, textOrBlank(rowData.getQuarterFourAmountWan()), cellStyle);
setText(row, 8, textOrBlank(rowData.getYearTotalAmountWan()), cellStyle);
setText(row, 9, percentTextOrBlank(rowData.getProjectLeadRatio()), cellStyle);
setText(row, 10, textOrBlank(rowData.getProjectLeadAssessmentOutputWan()), cellStyle);
setText(row, 11, textOrBlank(rowData.getProjectLeadQuarterOneAmountWan()), cellStyle);
setText(row, 12, textOrBlank(rowData.getProjectLeadQuarterTwoAmountWan()), cellStyle);
setText(row, 13, textOrBlank(rowData.getProjectLeadQuarterThreeAmountWan()), cellStyle);
setText(row, 14, textOrBlank(rowData.getProjectLeadQuarterFourAmountWan()), cellStyle);
setText(row, 15, textOrBlank(rowData.getProjectLeadYearTotalAmountWan()), cellStyle);
setText(row, 16, percentTextOrBlank(rowData.getOfficeRatio()), cellStyle);
setText(row, 17, textOrBlank(rowData.getOfficeAssessmentOutputWan()), cellStyle);
setText(row, 18, textOrBlank(rowData.getOfficeQuarterOneAmountWan()), cellStyle);
setText(row, 19, textOrBlank(rowData.getOfficeQuarterTwoAmountWan()), cellStyle);
setText(row, 20, textOrBlank(rowData.getOfficeQuarterThreeAmountWan()), cellStyle);
setText(row, 21, textOrBlank(rowData.getOfficeQuarterFourAmountWan()), cellStyle);
setText(row, 22, textOrBlank(rowData.getOfficeYearTotalAmountWan()), cellStyle);
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
setText(row, SHEET1_SPECIALTY_RATIO_START_COL + i,
percentTextOrBlank(resolveSpecialtyRatio(rowData, i)), cellStyle);
setText(row, SHEET1_SPECIALTY_AMOUNT_START_COL + i,
textOrBlank(resolveSpecialtyAssessmentOutputWan(rowData, i)), cellStyle);
}
}
private void writeSheet1TotalRow(Sheet sheet, int rowIndex, ExportData data, QuarterRow rowData,
CellStyle totalStyle, CellStyle totalLeftStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", totalStyle);
setText(row, 1, safeText(data.getProjectName()), totalStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, TOTAL_LABEL, totalStyle);
setText(row, 4, textOrBlank(rowData.getQuarterOneAmountWan()), totalStyle);
setText(row, 5, textOrBlank(rowData.getQuarterTwoAmountWan()), totalStyle);
setText(row, 6, textOrBlank(rowData.getQuarterThreeAmountWan()), totalStyle);
setText(row, 7, textOrBlank(rowData.getQuarterFourAmountWan()), totalStyle);
setText(row, 8, textOrBlank(rowData.getYearTotalAmountWan()), totalStyle);
setText(row, 9, "", totalStyle);
setText(row, 10, textOrBlank(rowData.getProjectLeadAssessmentOutputWan()), totalStyle);
setText(row, 11, textOrBlank(rowData.getProjectLeadQuarterOneAmountWan()), totalStyle);
setText(row, 12, textOrBlank(rowData.getProjectLeadQuarterTwoAmountWan()), totalStyle);
setText(row, 13, textOrBlank(rowData.getProjectLeadQuarterThreeAmountWan()), totalStyle);
setText(row, 14, textOrBlank(rowData.getProjectLeadQuarterFourAmountWan()), totalStyle);
setText(row, 15, textOrBlank(rowData.getProjectLeadYearTotalAmountWan()), totalStyle);
setText(row, 16, "", totalStyle);
setText(row, 17, textOrBlank(rowData.getOfficeAssessmentOutputWan()), totalStyle);
setText(row, 18, textOrBlank(rowData.getOfficeQuarterOneAmountWan()), totalStyle);
setText(row, 19, textOrBlank(rowData.getOfficeQuarterTwoAmountWan()), totalStyle);
setText(row, 20, textOrBlank(rowData.getOfficeQuarterThreeAmountWan()), totalStyle);
setText(row, 21, textOrBlank(rowData.getOfficeQuarterFourAmountWan()), totalStyle);
setText(row, 22, textOrBlank(rowData.getOfficeYearTotalAmountWan()), totalStyle);
setMergedRegionText(sheet, rowIndex, rowIndex,
SHEET1_SPECIALTY_RATIO_START_COL, SHEET1_SPECIALTY_AMOUNT_START_COL - 1,
"", totalLeftStyle);
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
setText(row, SHEET1_SPECIALTY_AMOUNT_START_COL + i,
textOrBlank(resolveSpecialtyAssessmentOutputWan(rowData, i)), totalStyle);
}
}
private void writeSheet1SignatureRow(Sheet sheet, int rowIndex, ExportData data, CellStyle signatureStyle) {
sheet.createRow(rowIndex);
int totalColumnCount = sheet1LastCol() + 1;
int leftEndCol = (totalColumnCount / 2) - 1;
setMergedRegionText(sheet, rowIndex, rowIndex, 0, leftEndCol,
safeText(defaultSignatureLabel(data.getCenterSignerLabel(), "设计中心相关负责人(签名):")),
signatureStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, leftEndCol + 1, sheet1LastCol(),
safeText(defaultSignatureLabel(data.getProjectSignerLabel(), "项目经理/工程负责人(签名):")),
signatureStyle);
}
private void buildSheet2(Workbook workbook, ExportData data) {
Sheet sheet = createSheet(workbook, SHEET2_NAME, SHEET2_FALLBACK_NAME);
setColumnWidths(sheet, buildSheet2ColumnWidths());
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle signatureStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
int rowIndex = 0;
rowIndex = buildSheet2Header(sheet, rowIndex, data, headerStyle, cellStyle);
int bodyStartRow = rowIndex;
List<OutputTypeGroup> groups = buildOutputTypeGroups(data);
for (OutputTypeGroup group : groups) {
int groupStartRow = rowIndex;
for (QuarterRow detailRow : group.getRows()) {
writeSheet2DetailRow(sheet, rowIndex++, data, detailRow, cellStyle, leftCellStyle);
}
mergeOutputTypeColumn(sheet, groupStartRow, rowIndex - 1, group.getOutputType(), cellStyle);
}
int totalRowIndex = rowIndex;
writeSheet2ProjectTotalRow(sheet, rowIndex++, data,
summarizeSheet2SpecialtyBlocks(extractDetailRows(data)), totalStyle);
mergeProjectNameColumn(sheet, bodyStartRow, totalRowIndex, safeText(data.getProjectName()), cellStyle);
writeSheet2SignatureRow(sheet, rowIndex, signatureStyle);
}
private int buildSheet2Header(Sheet sheet, int rowIndex, ExportData data,
CellStyle headerStyle, CellStyle valueStyle) {
int firstRow = buildCommonLeftHeader(sheet, rowIndex, data, headerStyle, valueStyle);
int startCol = SHEET2_BASE_COL_COUNT;
int endCol = sheet2LastCol();
setMergedRegionText(sheet, firstRow, firstRow, startCol, endCol,
"各专业年度 / 季度项目考核产值", headerStyle);
for (String specialtyName : SPECIALTY_NAMES) {
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, startCol, startCol + SHEET2_SPECIALTY_BLOCK_WIDTH - 1,
specialtyName + "专业项目考核产值(万元)", headerStyle);
setText(sheet.getRow(firstRow + 2), startCol, "一季度", headerStyle);
setText(sheet.getRow(firstRow + 2), startCol + 1, "二季度", headerStyle);
setText(sheet.getRow(firstRow + 2), startCol + 2, "三季度", headerStyle);
setText(sheet.getRow(firstRow + 2), startCol + 3, "四季度", headerStyle);
setText(sheet.getRow(firstRow + 2), startCol + 4, "本年度总计", headerStyle);
startCol += SHEET2_SPECIALTY_BLOCK_WIDTH;
}
return rowIndex + 3;
}
private void writeSheet2DetailRow(Sheet sheet, int rowIndex, ExportData data, QuarterRow rowData,
CellStyle cellStyle, CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(data.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
writeSheet2SpecialtyBlocks(row, buildSheet2SpecialtyBlocks(rowData), cellStyle);
}
private void writeSheet2ProjectTotalRow(Sheet sheet, int rowIndex, ExportData data,
List<Sheet2SpecialtyBlock> specialtyBlocks,
CellStyle totalStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", totalStyle);
setText(row, 1, safeText(data.getProjectName()), totalStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, TOTAL_LABEL, totalStyle);
writeSheet2SpecialtyBlocks(sheet.getRow(rowIndex), specialtyBlocks, totalStyle);
}
private void writeSheet2SignatureRow(Sheet sheet, int rowIndex, CellStyle signatureStyle) {
sheet.createRow(rowIndex);
setMergedRegionText(sheet, rowIndex, rowIndex, 0, SHEET2_BASE_COL_COUNT - 1, "", signatureStyle);
int startCol = SHEET2_BASE_COL_COUNT;
for (String label : SHEET2_SIGNATURE_LABELS) {
int endCol = startCol + SHEET2_SPECIALTY_BLOCK_WIDTH - 1;
setMergedRegionText(sheet, rowIndex, rowIndex, startCol, endCol, label, signatureStyle);
startCol = endCol + 1;
}
}
private void writeSheet2SpecialtyBlocks(Row row, List<Sheet2SpecialtyBlock> specialtyBlocks, CellStyle cellStyle) {
int startCol = SHEET2_BASE_COL_COUNT;
for (Sheet2SpecialtyBlock block : specialtyBlocks) {
setText(row, startCol, textOrBlank(block.getQuarterOneAmountWan()), cellStyle);
setText(row, startCol + 1, textOrBlank(block.getQuarterTwoAmountWan()), cellStyle);
setText(row, startCol + 2, textOrBlank(block.getQuarterThreeAmountWan()), cellStyle);
setText(row, startCol + 3, textOrBlank(block.getQuarterFourAmountWan()), cellStyle);
setText(row, startCol + 4, textOrBlank(block.getYearTotalAmountWan()), cellStyle);
startCol += SHEET2_SPECIALTY_BLOCK_WIDTH;
}
}
private List<Sheet2SpecialtyBlock> summarizeSheet2SpecialtyBlocks(List<QuarterRow> rows) {
List<Sheet2SpecialtyBlock> result = buildEmptySheet2SpecialtyBlocks();
for (QuarterRow row : rows) {
if (row == null) {
continue;
}
List<Sheet2SpecialtyBlock> currentBlocks = buildSheet2SpecialtyBlocks(row);
for (int i = 0; i < currentBlocks.size(); i++) {
Sheet2SpecialtyBlock totalBlock = result.get(i);
Sheet2SpecialtyBlock currentBlock = currentBlocks.get(i);
totalBlock.setQuarterOneAmountWan(addAmount(totalBlock.getQuarterOneAmountWan(), currentBlock.getQuarterOneAmountWan()));
totalBlock.setQuarterTwoAmountWan(addAmount(totalBlock.getQuarterTwoAmountWan(), currentBlock.getQuarterTwoAmountWan()));
totalBlock.setQuarterThreeAmountWan(addAmount(totalBlock.getQuarterThreeAmountWan(), currentBlock.getQuarterThreeAmountWan()));
totalBlock.setQuarterFourAmountWan(addAmount(totalBlock.getQuarterFourAmountWan(), currentBlock.getQuarterFourAmountWan()));
totalBlock.setYearTotalAmountWan(addAmount(totalBlock.getYearTotalAmountWan(), currentBlock.getYearTotalAmountWan()));
}
}
return result;
}
private List<Sheet2SpecialtyBlock> buildSheet2SpecialtyBlocks(QuarterRow row) {
if (row != null && row.isPlaceholderRow()) {
return buildBlankSheet2SpecialtyBlocks();
}
List<Sheet2SpecialtyBlock> result = new ArrayList<>();
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
BigDecimal specialtyRatio = resolveSpecialtyRatio(row, i);
Sheet2SpecialtyBlock block = new Sheet2SpecialtyBlock();
block.setQuarterOneAmountWan(multiplyAmountByRatio(row.getOfficeQuarterOneAmountWan(), specialtyRatio));
block.setQuarterTwoAmountWan(multiplyAmountByRatio(row.getOfficeQuarterTwoAmountWan(), specialtyRatio));
block.setQuarterThreeAmountWan(multiplyAmountByRatio(row.getOfficeQuarterThreeAmountWan(), specialtyRatio));
block.setQuarterFourAmountWan(multiplyAmountByRatio(row.getOfficeQuarterFourAmountWan(), specialtyRatio));
block.setYearTotalAmountWan(addAmount(block.getQuarterOneAmountWan(),
addAmount(block.getQuarterTwoAmountWan(),
addAmount(block.getQuarterThreeAmountWan(), block.getQuarterFourAmountWan()))));
result.add(block);
}
return result;
}
private List<Sheet2SpecialtyBlock> buildBlankSheet2SpecialtyBlocks() {
List<Sheet2SpecialtyBlock> result = new ArrayList<>();
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
result.add(new Sheet2SpecialtyBlock());
}
return result;
}
private List<Sheet2SpecialtyBlock> buildEmptySheet2SpecialtyBlocks() {
List<Sheet2SpecialtyBlock> result = new ArrayList<>();
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
Sheet2SpecialtyBlock block = new Sheet2SpecialtyBlock();
block.setQuarterOneAmountWan(zeroAmount());
block.setQuarterTwoAmountWan(zeroAmount());
block.setQuarterThreeAmountWan(zeroAmount());
block.setQuarterFourAmountWan(zeroAmount());
block.setYearTotalAmountWan(zeroAmount());
result.add(block);
}
return result;
}
private int outputTypeOrder(String outputType) {
int index = OUTPUT_TYPE_ORDER.indexOf(safeText(outputType));
return index >= 0 ? index : OUTPUT_TYPE_ORDER.size();
}
private int buildCommonLeftHeader(Sheet sheet, int rowIndex, ExportData data,
CellStyle headerStyle, CellStyle valueStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
String projectCode = data == null ? "" : safeText(data.getProjectCode());
setMergedRegionText(sheet, firstRow, firstRow + 1, 0, 0, "工程编号", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 1, 1, projectCode, valueStyle);
setMergedRegionText(sheet, firstRow, firstRow + 1, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 2), 0, "序号", headerStyle);
setText(sheet.getRow(firstRow + 2), 1, "项目名称", headerStyle);
setText(sheet.getRow(firstRow + 2), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 2), 3, "设计内容", headerStyle);
return firstRow;
}
private void mergeProjectNameColumn(Sheet sheet, int startRow, int endRow, String projectName, CellStyle style) {
if (startRow < 0 || endRow < startRow) {
return;
}
setMergedRegionText(sheet, startRow, endRow, 1, 1, safeText(projectName), style);
}
private void mergeOutputTypeColumn(Sheet sheet, int startRow, int endRow, String outputType, CellStyle style) {
if (startRow < 0 || endRow < startRow) {
return;
}
setMergedRegionText(sheet, startRow, endRow, 2, 2, safeText(outputType), style);
}
private List<QuarterRow> extractDetailRows(ExportData data) {
if (data == null || data.getRows() == null || data.getRows().isEmpty()) {
return Collections.emptyList();
}
List<QuarterRow> detailRows = new ArrayList<>();
for (QuarterRow row : data.getRows()) {
if (row != null && !row.isTotalRow()) {
detailRows.add(row);
}
}
detailRows.sort(Comparator
.comparingInt((QuarterRow row) -> outputTypeOrder(row.getOutputType()))
.thenComparing(QuarterRow::getSerialNo, Comparator.nullsLast(Integer::compareTo)));
return detailRows;
}
private QuarterRow resolveProjectTotalRow(ExportData data) {
if (data == null || data.getRows() == null) {
return null;
}
for (QuarterRow row : data.getRows()) {
if (row != null && row.isTotalRow()) {
return row;
}
}
return null;
}
private List<OutputTypeGroup> buildOutputTypeGroups(ExportData data) {
List<QuarterRow> detailRows = extractDetailRows(data);
int nextSerialNo = detailRows.stream()
.map(QuarterRow::getSerialNo)
.filter(item -> item != null)
.max(Integer::compareTo)
.orElse(0) + 1;
Map<String, List<QuarterRow>> groupedMap = new LinkedHashMap<>();
for (QuarterRow row : detailRows) {
groupedMap.computeIfAbsent(safeText(row.getOutputType()), key -> new ArrayList<>()).add(row);
}
List<OutputTypeGroup> result = new ArrayList<>();
for (String outputType : FIXED_OUTPUT_TYPES) {
List<QuarterRow> rows = groupedMap.remove(outputType);
if (rows == null || rows.isEmpty()) {
rows = new ArrayList<>();
rows.add(buildPlaceholderRow(outputType, nextSerialNo++));
}
OutputTypeGroup group = new OutputTypeGroup();
group.setOutputType(outputType);
group.setRows(rows);
result.add(group);
}
for (Map.Entry<String, List<QuarterRow>> entry : groupedMap.entrySet()) {
OutputTypeGroup group = new OutputTypeGroup();
group.setOutputType(entry.getKey());
group.setRows(entry.getValue());
result.add(group);
}
return result;
}
private QuarterRow buildPlaceholderRow(String outputType, Integer serialNo) {
QuarterRow row = new QuarterRow();
row.setSerialNo(serialNo);
row.setOutputType(outputType);
row.setDesignContent("");
row.setPlaceholderRow(true);
return row;
}
private String defaultSignatureLabel(String value, String fallback) {
String text = safeText(value);
return text.isEmpty() ? fallback : text;
}
private BigDecimal addAmount(BigDecimal left, BigDecimal right) {
return amountOrZero(left).add(amountOrZero(right)).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal amountOrZero(BigDecimal value) {
return value == null ? zeroAmount() : value.setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal zeroAmount() {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal multiplyAmountByRatio(BigDecimal amount, BigDecimal ratioValue) {
return amountOrZero(amount).multiply(ratio(ratioValue)).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal resolveSpecialtyRatio(QuarterRow row, int index) {
switch (index) {
case 0:
return row.getArchRatio();
case 1:
return row.getDecorRatio();
case 2:
return row.getStructRatio();
case 3:
return row.getWaterRatio();
case 4:
return row.getHvacRatio();
case 5:
return row.getElecRatio();
case 6:
return row.getDigitalRatio();
default:
return null;
}
}
private BigDecimal resolveSpecialtyAssessmentOutputWan(QuarterRow row, int index) {
switch (index) {
case 0:
return row.getArchAssessmentOutputWan();
case 1:
return row.getDecorAssessmentOutputWan();
case 2:
return row.getStructAssessmentOutputWan();
case 3:
return row.getWaterAssessmentOutputWan();
case 4:
return row.getHvacAssessmentOutputWan();
case 5:
return row.getElecAssessmentOutputWan();
case 6:
return row.getDigitalAssessmentOutputWan();
default:
return null;
}
}
private int[] buildSheet2ColumnWidths() {
int[] widths = new int[SHEET2_BASE_COL_COUNT + SPECIALTY_NAMES.size() * SHEET2_SPECIALTY_BLOCK_WIDTH];
widths[0] = 10;
widths[1] = 22;
widths[2] = 16;
widths[3] = 16;
int index = SHEET2_BASE_COL_COUNT;
for (int i = 0; i < SPECIALTY_NAMES.size(); i++) {
widths[index++] = 12;
widths[index++] = 12;
widths[index++] = 12;
widths[index++] = 12;
widths[index++] = 14;
}
return widths;
}
private int sheet1LastCol() {
return SHEET1_SPECIALTY_AMOUNT_START_COL + SPECIALTY_NAMES.size() - 1;
}
private int sheet2LastCol() {
return SHEET2_BASE_COL_COUNT + SPECIALTY_NAMES.size() * SHEET2_SPECIALTY_BLOCK_WIDTH - 1;
}
private String textOrBlank(BigDecimal value) {
if (value == null) {
return "";
}
return value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
private String percentTextOrBlank(BigDecimal value) {
return value == null ? "" : percentText(value);
}
@Data
public static class ExportData {
private String projectCode;
private String projectName;
private Integer year;
private String centerSignerLabel;
private String projectSignerLabel;
private List<QuarterRow> rows;
}
@Data
public static class QuarterRow {
private Integer serialNo;
private boolean totalRow;
private boolean placeholderRow;
private String outputType;
private String designContent;
private BigDecimal quarterOneAmountWan;
private BigDecimal quarterTwoAmountWan;
private BigDecimal quarterThreeAmountWan;
private BigDecimal quarterFourAmountWan;
private BigDecimal yearTotalAmountWan;
private BigDecimal projectLeadRatio;
private BigDecimal projectLeadAssessmentOutputWan;
private BigDecimal projectLeadQuarterOneAmountWan;
private BigDecimal projectLeadQuarterTwoAmountWan;
private BigDecimal projectLeadQuarterThreeAmountWan;
private BigDecimal projectLeadQuarterFourAmountWan;
private BigDecimal projectLeadYearTotalAmountWan;
private BigDecimal officeRatio;
private BigDecimal officeAssessmentOutputWan;
private BigDecimal officeQuarterOneAmountWan;
private BigDecimal officeQuarterTwoAmountWan;
private BigDecimal officeQuarterThreeAmountWan;
private BigDecimal officeQuarterFourAmountWan;
private BigDecimal officeYearTotalAmountWan;
private BigDecimal archRatio;
private BigDecimal decorRatio;
private BigDecimal structRatio;
private BigDecimal waterRatio;
private BigDecimal hvacRatio;
private BigDecimal elecRatio;
private BigDecimal digitalRatio;
private BigDecimal archAssessmentOutputWan;
private BigDecimal decorAssessmentOutputWan;
private BigDecimal structAssessmentOutputWan;
private BigDecimal waterAssessmentOutputWan;
private BigDecimal hvacAssessmentOutputWan;
private BigDecimal elecAssessmentOutputWan;
private BigDecimal digitalAssessmentOutputWan;
}
@Data
private static class OutputTypeGroup {
private String outputType;
private List<QuarterRow> rows;
}
@Data
private static class Sheet2SpecialtyBlock {
private BigDecimal quarterOneAmountWan;
private BigDecimal quarterTwoAmountWan;
private BigDecimal quarterThreeAmountWan;
private BigDecimal quarterFourAmountWan;
private BigDecimal yearTotalAmountWan;
}
}

View File

@@ -0,0 +1,653 @@
package cn.iocoder.lyzsys.module.tjt.service.report.builder;
import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants;
import lombok.Data;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
public class SpecialtyPersonOutputExcelBuilder extends AbstractProjectOutputExcelBuilder {
private static final String SHEET1_NAME = "工作量分配";
private static final String SHEET2_NAME = "季度考核产值";
private static final String SHEET1_TITLE = "7.1.4 专业内人员项目考核产值年度 / 季度预算计取表(以一个专业作为参考,各专业相同)";
private static final String SHEET2_TITLE = "专业内人员年度 / 季度项目考核产值(万元)";
private static final String SHEET1_SECTION_TITLE = "专业内人员工作量分配";
private static final String SHEET2_SECTION_TITLE = "专业项目考核产值(万元)";
private static final String SHEET2_PERSON_SECTION_TITLE = "专业内人员年度 / 季度项目考核产值(万元)";
private static final String DEFAULT_CENTER_SIGNER = "设计中心相关负责人(签名):";
private static final String DEFAULT_PROJECT_SIGNER = "项目经理 / 工程负责人(签名):";
private static final String DEFAULT_SPECIALTY_SIGNER = "专业负责人(签名):";
private static final String DEFAULT_DESIGNER_SIGNER = "参与设计人员(签名):";
private static final int SHEET1_FIXED_COL_COUNT = 4;
private static final int SHEET1_ROLE_RATIO_WIDTH = 1;
private static final int SHEET1_PERSON_RATIO_WIDTH = 2;
private static final int SHEET1_ADJUSTED_PERSON_WIDTH = 1;
private static final int SHEET1_REMARK_COL_COUNT = 1;
private static final int SHEET2_FIXED_COL_COUNT = 4;
private static final int SHEET2_SPECIALTY_BLOCK_WIDTH = 5;
private static final int SHEET2_PERSON_BLOCK_WIDTH = 5;
private static final List<String> ROLE_ORDER = Arrays.asList(
OutputSplitBizConstants.ROLE_DIRECTOR,
OutputSplitBizConstants.ROLE_CHECK,
OutputSplitBizConstants.ROLE_DESIGN,
OutputSplitBizConstants.ROLE_REVIEW,
OutputSplitBizConstants.ROLE_APPROVE
);
public Workbook build(ExportData data) {
Workbook workbook = createWorkbook();
buildSheet1(workbook, data);
buildSheet2(workbook, data);
return workbook;
}
private void buildSheet1(Workbook workbook, ExportData data) {
List<RoleGroup> roleGroups = buildRoleGroups(data);
List<PersonAmountModule> adjustedPersonModules = resolvePersonAmountModules(data);
Sheet sheet = createSheet(workbook, SHEET1_NAME, SHEET1_NAME);
setColumnWidths(sheet, buildSheet1ColumnWidths(roleGroups, adjustedPersonModules));
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle headerValueStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildSheet1Header(sheet, rowIndex, data, roleGroups, adjustedPersonModules,
headerStyle, headerValueStyle);
List<SpecialtyPlanningRow> rows = resolveDetailRows(data);
int bodyStartRow = rowIndex;
for (SpecialtyPlanningRow rowData : rows) {
writeSheet1DetailRow(sheet, rowIndex++, rowData, roleGroups, adjustedPersonModules,
cellStyle, leftCellStyle);
}
mergeSheet1Body(sheet, bodyStartRow, rowIndex - 1, rows, cellStyle);
}
private void buildSheet2(Workbook workbook, ExportData data) {
List<PersonAmountModule> personModules = resolvePersonAmountModules(data);
int lastCol = sheet2LastCol(personModules);
Sheet sheet = createSheet(workbook, SHEET2_NAME, SHEET2_NAME);
setColumnWidths(sheet, buildSheet2ColumnWidths(personModules));
CellStyle baseHeaderStyle = createHeaderStyle(workbook);
CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle cellStyle = createDerivedStyle(workbook, createCellStyle(workbook),
null, HorizontalAlignment.CENTER, false, (short) 10);
CellStyle leftCellStyle = createDerivedStyle(workbook, cellStyle,
null, HorizontalAlignment.LEFT, false, (short) 10);
CellStyle totalStyle = createDerivedStyle(workbook, baseHeaderStyle,
IndexedColors.GOLD, HorizontalAlignment.CENTER, true, (short) 10);
CellStyle signatureStyle = createDerivedStyle(workbook, createInfoStyle(workbook),
null, HorizontalAlignment.LEFT, false, (short) 10);
int rowIndex = 0;
rowIndex = buildSheet2Header(sheet, rowIndex, data, personModules, headerStyle);
List<SpecialtyPlanningRow> rows = resolveSheet2Rows(data);
int bodyStartRow = rowIndex;
for (SpecialtyPlanningRow rowData : rows) {
if (Boolean.TRUE.equals(rowData.getTotalRow())) {
writeSheet2TotalRow(sheet, rowIndex++, rowData, personModules, totalStyle);
continue;
}
writeSheet2DetailRow(sheet, rowIndex++, rowData, personModules, cellStyle, leftCellStyle);
}
mergeSheet2Body(sheet, bodyStartRow, rowIndex - 1, rows, cellStyle);
writeSheet2SignatureRow(sheet, rowIndex, lastCol, signatureStyle);
}
private int buildSheet1Header(Sheet sheet, int rowIndex, ExportData data, List<RoleGroup> roleGroups,
List<PersonAmountModule> adjustedPersonModules,
CellStyle headerStyle, CellStyle headerValueStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
sheet.createRow(firstRow + 3);
setText(sheet.getRow(firstRow), 0, "工程编号", headerStyle);
setText(sheet.getRow(firstRow), 1, safeText(data == null ? "" : data.getProjectCode()), headerValueStyle);
setText(sheet.getRow(firstRow), 2, "工程负责人", headerStyle);
setText(sheet.getRow(firstRow), 3, safeText(data == null ? "" : data.getEngineeringPrincipalNames()), headerValueStyle);
int remarkCol = sheet1LastCol(roleGroups, adjustedPersonModules);
int adjustedStartCol = sheet1AdjustedStartCol(roleGroups);
int adjustedEndCol = remarkCol - 1;
setMergedRegionText(sheet, firstRow, firstRow, 4, adjustedEndCol, SHEET1_SECTION_TITLE, headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 3, remarkCol, remarkCol, "备注", headerStyle);
setText(sheet.getRow(firstRow + 1), 0, "专业", headerStyle);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 1, 3,
safeText(data == null ? "" : data.getSpecialtyName()), headerValueStyle);
setMergedRegionText(sheet, firstRow + 2, firstRow + 3, 0, 0, "序号", headerStyle);
setMergedRegionText(sheet, firstRow + 2, firstRow + 3, 1, 1, "项目名称", headerStyle);
setMergedRegionText(sheet, firstRow + 2, firstRow + 2, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 3), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 3), 3, "设计内容", headerStyle);
int colIndex = SHEET1_FIXED_COL_COUNT;
for (RoleGroup roleGroup : roleGroups) {
int roleWidth = sheet1RoleWidth(roleGroup);
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, colIndex, colIndex + roleWidth - 1,
roleGroup.getRoleName(), headerStyle);
setMergedRegionText(sheet, firstRow + 2, firstRow + 3, colIndex, colIndex,
"岗位整体占专业比例", headerStyle);
colIndex++;
for (RolePersonModule module : roleGroup.getModules()) {
setMergedRegionText(sheet, firstRow + 2, firstRow + 2, colIndex, colIndex + 1,
safeText(module.getEmployeeName()), headerStyle);
setText(sheet.getRow(firstRow + 3), colIndex, "工作量比例", headerStyle);
setText(sheet.getRow(firstRow + 3), colIndex + 1, "占专业比例", headerStyle);
colIndex += SHEET1_PERSON_RATIO_WIDTH;
}
}
setMergedRegionText(sheet, firstRow + 1, firstRow + 1, adjustedStartCol, adjustedEndCol,
"调整后人员比例", headerStyle);
for (PersonAmountModule module : adjustedPersonModules) {
setMergedRegionText(sheet, firstRow + 2, firstRow + 3, colIndex, colIndex,
safeText(module.getEmployeeName()), headerStyle);
colIndex += SHEET1_ADJUSTED_PERSON_WIDTH;
}
return rowIndex + 4;
}
private void writeSheet1DetailRow(Sheet sheet, int rowIndex, SpecialtyPlanningRow rowData,
List<RoleGroup> roleGroups, List<PersonAmountModule> adjustedPersonModules,
CellStyle cellStyle, CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(rowData.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
int colIndex = SHEET1_FIXED_COL_COUNT;
for (RoleGroup roleGroup : roleGroups) {
setText(row, colIndex++, percentTextOrBlank(rowData.getRoleRatioMap().get(roleGroup.getRoleCode())), cellStyle);
for (RolePersonModule module : roleGroup.getModules()) {
RolePersonRatioValue value = rowData.getRolePersonValueMap().get(module.getKey());
setText(row, colIndex, value == null ? "" : percentTextOrBlank(value.getWorkRatio()), cellStyle);
setText(row, colIndex + 1, value == null ? "" : percentTextOrBlank(value.getSpecialtyRatio()), cellStyle);
colIndex += SHEET1_PERSON_RATIO_WIDTH;
}
}
for (PersonAmountModule module : adjustedPersonModules) {
setText(row, colIndex++, percentTextOrBlank(rowData.getAdjustedPersonRatioMap().get(module.getKey())), cellStyle);
}
setText(row, colIndex, safeText(rowData.getRemark()), leftCellStyle);
}
private int buildSheet2Header(Sheet sheet, int rowIndex, ExportData data,
List<PersonAmountModule> personModules, CellStyle headerStyle) {
int firstRow = rowIndex;
sheet.createRow(firstRow);
sheet.createRow(firstRow + 1);
sheet.createRow(firstRow + 2);
sheet.createRow(firstRow + 3);
setMergedRegionText(sheet, firstRow, firstRow + 3, 0, 0, "序号", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 3, 1, 1, "项目名称", headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, 2, 3, "类别", headerStyle);
setText(sheet.getRow(firstRow + 3), 2, "产值类型", headerStyle);
setText(sheet.getRow(firstRow + 3), 3, "设计内容", headerStyle);
int specialtyStartCol = SHEET2_FIXED_COL_COUNT;
setMergedRegionText(sheet, firstRow, firstRow + 2, specialtyStartCol, specialtyStartCol,
safeText(data == null ? "" : data.getSpecialtyName()), headerStyle);
setMergedRegionText(sheet, firstRow, firstRow + 2, specialtyStartCol + 1,
specialtyStartCol + SHEET2_SPECIALTY_BLOCK_WIDTH - 1, SHEET2_SECTION_TITLE, headerStyle);
writeQuarterHeader(sheet.getRow(firstRow + 3), specialtyStartCol, headerStyle);
int personStartCol = specialtyStartCol + SHEET2_SPECIALTY_BLOCK_WIDTH;
int lastCol = sheet2LastCol(personModules);
setMergedRegionText(sheet, firstRow, firstRow, personStartCol, lastCol, SHEET2_PERSON_SECTION_TITLE, headerStyle);
int colIndex = personStartCol;
for (PersonAmountModule module : personModules) {
setMergedRegionText(sheet, firstRow + 1, firstRow + 2, colIndex, colIndex + SHEET2_PERSON_BLOCK_WIDTH - 1,
safeText(module.getEmployeeName()), headerStyle);
writeQuarterHeader(sheet.getRow(firstRow + 3), colIndex, headerStyle);
colIndex += SHEET2_PERSON_BLOCK_WIDTH;
}
return rowIndex + 4;
}
private void writeSheet2DetailRow(Sheet sheet, int rowIndex, SpecialtyPlanningRow rowData,
List<PersonAmountModule> personModules, CellStyle cellStyle,
CellStyle leftCellStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, rowData.getSerialNo(), cellStyle);
setText(row, 1, safeText(rowData.getProjectName()), cellStyle);
setText(row, 2, safeText(rowData.getOutputType()), cellStyle);
setText(row, 3, safeText(rowData.getDesignContent()), leftCellStyle);
int colIndex = SHEET2_FIXED_COL_COUNT;
setText(row, colIndex, textOrBlank(rowData.getSpecialtyQuarterOneAmountWan()), cellStyle);
setText(row, colIndex + 1, textOrBlank(rowData.getSpecialtyQuarterTwoAmountWan()), cellStyle);
setText(row, colIndex + 2, textOrBlank(rowData.getSpecialtyQuarterThreeAmountWan()), cellStyle);
setText(row, colIndex + 3, textOrBlank(rowData.getSpecialtyQuarterFourAmountWan()), cellStyle);
setText(row, colIndex + 4, textOrBlank(rowData.getSpecialtyYearTotalAmountWan()), cellStyle);
colIndex += SHEET2_SPECIALTY_BLOCK_WIDTH;
for (PersonAmountModule module : personModules) {
PersonAmountValue value = rowData.getPersonAmountMap().get(module.getKey());
setText(row, colIndex, value == null ? "" : textOrBlank(value.getQuarterOneAmountWan()), cellStyle);
setText(row, colIndex + 1, value == null ? "" : textOrBlank(value.getQuarterTwoAmountWan()), cellStyle);
setText(row, colIndex + 2, value == null ? "" : textOrBlank(value.getQuarterThreeAmountWan()), cellStyle);
setText(row, colIndex + 3, value == null ? "" : textOrBlank(value.getQuarterFourAmountWan()), cellStyle);
setText(row, colIndex + 4, value == null ? "" : textOrBlank(value.getYearTotalAmountWan()), cellStyle);
colIndex += SHEET2_PERSON_BLOCK_WIDTH;
}
}
private void writeSheet2TotalRow(Sheet sheet, int rowIndex, SpecialtyPlanningRow rowData,
List<PersonAmountModule> personModules, CellStyle totalStyle) {
Row row = sheet.createRow(rowIndex);
setText(row, 0, "", totalStyle);
setText(row, 1, "", totalStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, 2, 3, "总计", totalStyle);
int colIndex = SHEET2_FIXED_COL_COUNT;
setText(row, colIndex, textOrBlank(rowData.getSpecialtyQuarterOneAmountWan()), totalStyle);
setText(row, colIndex + 1, textOrBlank(rowData.getSpecialtyQuarterTwoAmountWan()), totalStyle);
setText(row, colIndex + 2, textOrBlank(rowData.getSpecialtyQuarterThreeAmountWan()), totalStyle);
setText(row, colIndex + 3, textOrBlank(rowData.getSpecialtyQuarterFourAmountWan()), totalStyle);
setText(row, colIndex + 4, textOrBlank(rowData.getSpecialtyYearTotalAmountWan()), totalStyle);
colIndex += SHEET2_SPECIALTY_BLOCK_WIDTH;
for (PersonAmountModule module : personModules) {
PersonAmountValue value = rowData.getPersonAmountMap().get(module.getKey());
setText(row, colIndex, value == null ? "" : textOrBlank(value.getQuarterOneAmountWan()), totalStyle);
setText(row, colIndex + 1, value == null ? "" : textOrBlank(value.getQuarterTwoAmountWan()), totalStyle);
setText(row, colIndex + 2, value == null ? "" : textOrBlank(value.getQuarterThreeAmountWan()), totalStyle);
setText(row, colIndex + 3, value == null ? "" : textOrBlank(value.getQuarterFourAmountWan()), totalStyle);
setText(row, colIndex + 4, value == null ? "" : textOrBlank(value.getYearTotalAmountWan()), totalStyle);
colIndex += SHEET2_PERSON_BLOCK_WIDTH;
}
}
private void writeSheet2SignatureRow(Sheet sheet, int rowIndex, int lastCol, CellStyle signatureStyle) {
sheet.createRow(rowIndex);
int totalColCount = lastCol + 1;
int segmentWidth = Math.max(1, totalColCount / 4);
int col1End = Math.min(lastCol, segmentWidth - 1);
int col2End = Math.min(lastCol, col1End + segmentWidth);
int col3End = Math.min(lastCol, col2End + segmentWidth);
setMergedRegionText(sheet, rowIndex, rowIndex, 0, col1End, DEFAULT_CENTER_SIGNER, signatureStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, col1End + 1, col2End, DEFAULT_PROJECT_SIGNER, signatureStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, col2End + 1, col3End, DEFAULT_SPECIALTY_SIGNER, signatureStyle);
setMergedRegionText(sheet, rowIndex, rowIndex, col3End + 1, lastCol, DEFAULT_DESIGNER_SIGNER, signatureStyle);
}
private void mergeSheet1Body(Sheet sheet, int startRow, int endRow, List<SpecialtyPlanningRow> rows, CellStyle style) {
if (startRow < 0 || endRow < startRow || rows.isEmpty()) {
return;
}
setMergedRegionText(sheet, startRow, endRow, 1, 1, safeText(rows.get(0).getProjectName()), style);
mergeOutputTypeColumn(sheet, startRow, rows, style);
}
private void mergeSheet2Body(Sheet sheet, int startRow, int endRow, List<SpecialtyPlanningRow> rows,
CellStyle style) {
if (startRow < 0 || endRow < startRow || rows.isEmpty()) {
return;
}
int detailEndIndex = lastDetailIndex(rows);
if (detailEndIndex < 0) {
return;
}
setMergedRegionText(sheet, startRow, endRow, 1, 1, safeText(rows.get(0).getProjectName()), style);
mergeOutputTypeColumn(sheet, startRow, rows.subList(0, detailEndIndex + 1), style);
}
private void mergeOutputTypeColumn(Sheet sheet, int bodyStartRow, List<SpecialtyPlanningRow> rows, CellStyle style) {
if (rows == null || rows.isEmpty()) {
return;
}
int groupStartIndex = 0;
String currentOutputType = safeText(rows.get(0).getOutputType());
for (int i = 1; i < rows.size(); i++) {
String nextOutputType = safeText(rows.get(i).getOutputType());
if (!Objects.equals(currentOutputType, nextOutputType)) {
setMergedRegionText(sheet, bodyStartRow + groupStartIndex, bodyStartRow + i - 1,
2, 2, currentOutputType, style);
groupStartIndex = i;
currentOutputType = nextOutputType;
}
}
setMergedRegionText(sheet, bodyStartRow + groupStartIndex, bodyStartRow + rows.size() - 1,
2, 2, currentOutputType, style);
}
private List<RoleGroup> buildRoleGroups(ExportData data) {
Map<String, List<RolePersonModule>> groupedMap = new LinkedHashMap<>();
if (data != null && data.getRolePersonModules() != null) {
for (RolePersonModule module : data.getRolePersonModules()) {
if (module == null) {
continue;
}
groupedMap.computeIfAbsent(safeText(module.getRoleCode()), key -> new ArrayList<>()).add(module);
}
}
List<RoleGroup> result = new ArrayList<>();
for (String roleCode : ROLE_ORDER) {
List<RolePersonModule> modules = groupedMap.get(roleCode);
if (modules == null || modules.isEmpty()) {
modules = new ArrayList<>();
RolePersonModule placeholder = new RolePersonModule();
placeholder.setKey(roleCode + "#placeholder");
placeholder.setRoleCode(roleCode);
placeholder.setRoleName(OutputSplitBizConstants.getRoleName(roleCode));
placeholder.setEmployeeName("");
placeholder.setPlaceholder(Boolean.TRUE);
modules.add(placeholder);
}
RoleGroup group = new RoleGroup();
group.setRoleCode(roleCode);
group.setRoleName(resolveRoleName(modules, roleCode));
group.setModules(modules);
result.add(group);
}
return result;
}
private List<PersonAmountModule> resolvePersonAmountModules(ExportData data) {
if (data == null || data.getPersonAmountModules() == null || data.getPersonAmountModules().isEmpty()) {
PersonAmountModule placeholder = new PersonAmountModule();
placeholder.setKey("person#placeholder");
placeholder.setEmployeeName("");
placeholder.setPlaceholder(Boolean.TRUE);
return Collections.singletonList(placeholder);
}
return data.getPersonAmountModules().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private List<SpecialtyPlanningRow> resolveDetailRows(ExportData data) {
if (data == null || data.getRows() == null) {
return Collections.emptyList();
}
List<SpecialtyPlanningRow> rows = data.getRows().stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
rows.sort(Comparator
.comparingInt((SpecialtyPlanningRow row) -> outputTypeOrder(row.getOutputType()))
.thenComparing(SpecialtyPlanningRow::getSerialNo, Comparator.nullsLast(Integer::compareTo)));
return rows;
}
private List<SpecialtyPlanningRow> resolveSheet2Rows(ExportData data) {
List<SpecialtyPlanningRow> detailRows = resolveDetailRows(data);
if (detailRows.isEmpty()) {
return detailRows;
}
List<SpecialtyPlanningRow> rows = new ArrayList<>(detailRows);
rows.add(buildSheet2TotalRow(detailRows));
return rows;
}
private SpecialtyPlanningRow buildSheet2TotalRow(List<SpecialtyPlanningRow> detailRows) {
SpecialtyPlanningRow row = new SpecialtyPlanningRow();
row.setTotalRow(Boolean.TRUE);
row.setDesignContent("总计");
for (SpecialtyPlanningRow detailRow : detailRows) {
row.setSpecialtyQuarterOneAmountWan(addAmount(row.getSpecialtyQuarterOneAmountWan(),
detailRow.getSpecialtyQuarterOneAmountWan()));
row.setSpecialtyQuarterTwoAmountWan(addAmount(row.getSpecialtyQuarterTwoAmountWan(),
detailRow.getSpecialtyQuarterTwoAmountWan()));
row.setSpecialtyQuarterThreeAmountWan(addAmount(row.getSpecialtyQuarterThreeAmountWan(),
detailRow.getSpecialtyQuarterThreeAmountWan()));
row.setSpecialtyQuarterFourAmountWan(addAmount(row.getSpecialtyQuarterFourAmountWan(),
detailRow.getSpecialtyQuarterFourAmountWan()));
row.setSpecialtyYearTotalAmountWan(addAmount(row.getSpecialtyYearTotalAmountWan(),
detailRow.getSpecialtyYearTotalAmountWan()));
for (Map.Entry<String, PersonAmountValue> entry : detailRow.getPersonAmountMap().entrySet()) {
PersonAmountValue totalValue = row.getPersonAmountMap().computeIfAbsent(entry.getKey(),
key -> new PersonAmountValue());
PersonAmountValue currentValue = entry.getValue();
totalValue.setQuarterOneAmountWan(addAmount(totalValue.getQuarterOneAmountWan(),
currentValue == null ? null : currentValue.getQuarterOneAmountWan()));
totalValue.setQuarterTwoAmountWan(addAmount(totalValue.getQuarterTwoAmountWan(),
currentValue == null ? null : currentValue.getQuarterTwoAmountWan()));
totalValue.setQuarterThreeAmountWan(addAmount(totalValue.getQuarterThreeAmountWan(),
currentValue == null ? null : currentValue.getQuarterThreeAmountWan()));
totalValue.setQuarterFourAmountWan(addAmount(totalValue.getQuarterFourAmountWan(),
currentValue == null ? null : currentValue.getQuarterFourAmountWan()));
totalValue.setYearTotalAmountWan(addAmount(totalValue.getYearTotalAmountWan(),
currentValue == null ? null : currentValue.getYearTotalAmountWan()));
}
}
return row;
}
private int[] buildSheet1ColumnWidths(List<RoleGroup> roleGroups, List<PersonAmountModule> adjustedPersonModules) {
List<Integer> widths = new ArrayList<>();
widths.add(8);
widths.add(24);
widths.add(16);
widths.add(24);
for (RoleGroup roleGroup : roleGroups) {
widths.add(16);
for (int i = 0; i < roleGroup.getModules().size(); i++) {
widths.add(12);
widths.add(12);
}
}
for (int i = 0; i < adjustedPersonModules.size(); i++) {
widths.add(14);
}
widths.add(20);
return widths.stream().mapToInt(Integer::intValue).toArray();
}
private int[] buildSheet2ColumnWidths(List<PersonAmountModule> personModules) {
List<Integer> widths = new ArrayList<>();
widths.add(8);
widths.add(24);
widths.add(16);
widths.add(24);
appendQuarterBlockWidths(widths);
for (int i = 0; i < personModules.size(); i++) {
appendQuarterBlockWidths(widths);
}
return widths.stream().mapToInt(Integer::intValue).toArray();
}
private void appendQuarterBlockWidths(List<Integer> widths) {
widths.add(12);
widths.add(12);
widths.add(12);
widths.add(12);
widths.add(14);
}
private void writeQuarterHeader(Row row, int startCol, CellStyle style) {
setText(row, startCol, "一季度", style);
setText(row, startCol + 1, "二季度", style);
setText(row, startCol + 2, "三季度", style);
setText(row, startCol + 3, "四季度", style);
setText(row, startCol + 4, "本年度小计", style);
}
private int sheet1LastCol(List<RoleGroup> roleGroups, List<PersonAmountModule> adjustedPersonModules) {
int totalColumns = SHEET1_FIXED_COL_COUNT + SHEET1_REMARK_COL_COUNT
+ adjustedPersonModules.size() * SHEET1_ADJUSTED_PERSON_WIDTH;
for (RoleGroup roleGroup : roleGroups) {
totalColumns += sheet1RoleWidth(roleGroup);
}
return totalColumns - 1;
}
private int sheet1AdjustedStartCol(List<RoleGroup> roleGroups) {
int col = SHEET1_FIXED_COL_COUNT;
for (RoleGroup roleGroup : roleGroups) {
col += sheet1RoleWidth(roleGroup);
}
return col;
}
private int sheet1RoleWidth(RoleGroup roleGroup) {
int personCount = roleGroup == null || roleGroup.getModules() == null ? 1 : roleGroup.getModules().size();
return SHEET1_ROLE_RATIO_WIDTH + personCount * SHEET1_PERSON_RATIO_WIDTH;
}
private int sheet2LastCol(List<PersonAmountModule> personModules) {
return SHEET2_FIXED_COL_COUNT + SHEET2_SPECIALTY_BLOCK_WIDTH
+ personModules.size() * SHEET2_PERSON_BLOCK_WIDTH - 1;
}
private int outputTypeOrder(String outputType) {
String current = safeText(outputType);
if (Objects.equals(current, "六大专业考核产值")) {
return 1;
}
if (Objects.equals(current, "专业分包产值")) {
return 2;
}
if (Objects.equals(current, "内部协作产值")) {
return 3;
}
return 9;
}
private int lastDetailIndex(List<SpecialtyPlanningRow> rows) {
for (int i = rows.size() - 1; i >= 0; i--) {
if (!Boolean.TRUE.equals(rows.get(i).getTotalRow())) {
return i;
}
}
return -1;
}
private BigDecimal addAmount(BigDecimal left, BigDecimal right) {
return amount(left).add(amount(right)).setScale(2, RoundingMode.HALF_UP);
}
private String resolveRoleName(List<RolePersonModule> modules, String roleCode) {
if (modules != null) {
for (RolePersonModule module : modules) {
if (module != null && !safeText(module.getRoleName()).isEmpty()) {
return safeText(module.getRoleName());
}
}
}
return OutputSplitBizConstants.getRoleName(roleCode);
}
private String textOrBlank(BigDecimal value) {
return value == null ? "" : value.setScale(2, RoundingMode.HALF_UP).toPlainString();
}
private String percentTextOrBlank(BigDecimal value) {
return value == null ? "" : percentText(value);
}
@Data
public static class ExportData {
private String projectCode;
private String projectName;
private String specialtyName;
private String projectManagerNames;
private String engineeringPrincipalNames;
private BigDecimal projectManagerRatio;
private BigDecimal engineeringPrincipalRatio;
private Integer year;
private String centerSignerLabel;
private String projectSignerLabel;
private List<RolePersonModule> rolePersonModules;
private List<PersonAmountModule> personAmountModules;
private List<SpecialtyPlanningRow> rows;
}
@Data
public static class RolePersonModule {
private String key;
private String roleCode;
private String roleName;
private String employeeName;
private Boolean placeholder;
}
@Data
public static class PersonAmountModule {
private String key;
private String employeeName;
private Boolean placeholder;
}
@Data
public static class SpecialtyPlanningRow {
private Integer serialNo;
private String projectName;
private String outputType;
private String designContent;
private BigDecimal specialtyRatio;
private BigDecimal specialtyQuarterOneAmountWan;
private BigDecimal specialtyQuarterTwoAmountWan;
private BigDecimal specialtyQuarterThreeAmountWan;
private BigDecimal specialtyQuarterFourAmountWan;
private BigDecimal specialtyYearTotalAmountWan;
private BigDecimal adjustedPersonRatio;
private String remark;
private Boolean totalRow;
private Map<String, BigDecimal> roleRatioMap = new LinkedHashMap<>();
private Map<String, RolePersonRatioValue> rolePersonValueMap = new LinkedHashMap<>();
private Map<String, BigDecimal> adjustedPersonRatioMap = new LinkedHashMap<>();
private Map<String, PersonAmountValue> personAmountMap = new LinkedHashMap<>();
}
@Data
public static class RolePersonRatioValue {
private BigDecimal workRatio;
private BigDecimal specialtyRatio;
}
@Data
public static class PersonAmountValue {
private BigDecimal quarterOneAmountWan;
private BigDecimal quarterTwoAmountWan;
private BigDecimal quarterThreeAmountWan;
private BigDecimal quarterFourAmountWan;
private BigDecimal yearTotalAmountWan;
}
@Data
private static class RoleGroup {
private String roleCode;
private String roleName;
private List<RolePersonModule> modules;
}
}

View File

@@ -1,6 +1,5 @@
package cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.lyzsys.framework.common.util.json.JsonUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRolePersonRespVO;
@@ -8,16 +7,19 @@ import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.Speci
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitBatchSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.specialtyrolesplit.vo.SpecialtyRoleSplitSaveItemReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.specialtyrolesplit.SpecialtyRoleSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.specialtyrolesplit.SpecialtyRoleSplitMapper;
import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -25,9 +27,11 @@ import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -40,21 +44,15 @@ import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_RO
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID;
/**
* 页面5角色比例 Service 实现类
*
* @author Codex
*/
@Service
@Validated
public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService {
private static final int RATIO_SCALE = 4;
private static final int AMOUNT_SCALE = 2;
private static final TypeReference<List<SpecialtyRolePersonSaveReqVO>> PERSON_LIST_TYPE =
new TypeReference<List<SpecialtyRolePersonSaveReqVO>>() {};
private static final BigDecimal ZERO_RATIO = BigDecimal.ZERO.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
private static final BigDecimal ONE_RATIO = BigDecimal.ONE.setScale(RATIO_SCALE, RoundingMode.HALF_UP);
private static final BigDecimal HALF_RATIO = new BigDecimal("0.5000");
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO.setScale(AMOUNT_SCALE, RoundingMode.HALF_UP);
@Resource
@@ -65,6 +63,10 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
private ProjectPlanningMapper projectPlanningMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ProjectRolePersonMapper projectRolePersonMapper;
@Resource
private EmployeeService employeeService;
@Override
public List<SpecialtyRoleSplitRespVO> getSpecialtyRoleSplitListByPlanningId(Long planningId) {
@@ -74,22 +76,30 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
List<SpecialtyRoleSplitDO> dbList = specialtyRoleSplitMapper.selectListByOutputSplitId(outputSplit.getId());
Map<String, SpecialtyRoleSplitDO> dbMap = dbList.stream().collect(Collectors.toMap(
item -> item.getSpecialtyCode() + ":" + item.getRoleCode(), item -> item, (a, b) -> b));
Map<String, List<ProjectRolePersonDO>> projectRolePersonMap = projectRolePersonMapper
.selectListByProjectId(project.getId()).stream()
.collect(Collectors.groupingBy(ProjectRolePersonDO::getRoleCode, LinkedHashMap::new, Collectors.toList()));
List<SpecialtyRoleSplitRespVO> result = new ArrayList<>();
BigDecimal assessmentOutputValue = planning.getAssessmentOutputValue();
for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.SPECIALTY_ITEMS) {
BigDecimal specialtyAmount = projectOutputSplitService.getSpecialtyAmount(
outputSplit, assessmentOutputValue, specialtyItem.getCode());
for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) {
for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.ASSIGNMENT_SPECIALTY_ITEMS) {
BigDecimal specialtyAmount = getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyItem.getCode());
Map<String, BigDecimal> defaultRoleRatioMap = buildDefaultRoleRatioMap(
specialtyItem.getCode(), projectRolePersonMap);
for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.getRoleItems(specialtyItem.getCode())) {
SpecialtyRoleSplitDO dbItem = dbMap.get(specialtyItem.getCode() + ":" + roleItem.getCode());
BigDecimal roleRatio = getStoredRoleRatio(dbItem, roleItem.getCode());
BigDecimal roleRatio = getStoredRoleRatio(
dbItem, specialtyItem.getCode(), roleItem.getCode(), defaultRoleRatioMap);
BigDecimal roleAmount = multiplyAmount(specialtyAmount, roleRatio);
List<SpecialtyRolePersonRespVO> persons = buildRespPersons(dbItem, roleAmount, roleRatio);
List<SpecialtyRolePersonRespVO> persons = buildRespPersons(
dbItem, specialtyItem.getCode(), roleItem.getCode(), roleAmount, roleRatio, projectRolePersonMap);
SpecialtyRoleSplitRespVO respVO = dbItem == null
? new SpecialtyRoleSplitRespVO()
: BeanUtils.toBean(dbItem, SpecialtyRoleSplitRespVO.class);
if (respVO.getId() == null) {
respVO.setOutputSplitId(outputSplit.getId());
respVO.setSortNo(roleItem.getSortNo());
respVO.setSortNo(OutputSplitBizConstants.getSpecialtySortNo(specialtyItem.getCode()) * 10
+ OutputSplitBizConstants.getRoleSortNo(roleItem.getCode()));
}
respVO.setPlanningId(planning.getId());
respVO.setProjectName(project.getProjectName());
@@ -117,30 +127,25 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
item -> item.getSpecialtyCode() + ":" + item.getRoleCode(), item -> item, (a, b) -> b));
Map<String, Map<String, SpecialtyRoleSplitSaveItemReqVO>> groupedMap = buildGroupedInput(reqVO.getItems());
BigDecimal assessmentOutputValue = planning.getAssessmentOutputValue();
for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.SPECIALTY_ITEMS) {
for (OutputSplitBizConstants.SpecialtyItem specialtyItem : OutputSplitBizConstants.ASSIGNMENT_SPECIALTY_ITEMS) {
Map<String, SpecialtyRoleSplitSaveItemReqVO> roleMap = groupedMap.getOrDefault(
specialtyItem.getCode(), new LinkedHashMap<>());
Map<String, BigDecimal> ratioMap = new LinkedHashMap<>();
Map<String, List<SpecialtyRolePersonSaveReqVO>> personMap = new LinkedHashMap<>();
BigDecimal roleTotal = ZERO_RATIO;
BigDecimal specialtyAmount = projectOutputSplitService.getSpecialtyAmount(
outputSplit, assessmentOutputValue, specialtyItem.getCode());
for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) {
BigDecimal roleRatio = getSaveRoleRatio(roleMap, roleItem.getCode());
BigDecimal specialtyAmount = getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyItem.getCode());
for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.getRoleItems(specialtyItem.getCode())) {
BigDecimal roleRatio = getSaveRoleRatio(roleMap, specialtyItem.getCode(), roleItem.getCode());
BigDecimal roleAmount = multiplyAmount(specialtyAmount, roleRatio);
List<SpecialtyRolePersonSaveReqVO> persons = normalizePersons(
getSavePersons(roleMap, roleItem.getCode()));
validateRolePersons(roleItem.getCode(), roleAmount, persons);
ratioMap.put(roleItem.getCode(), roleRatio);
personMap.put(roleItem.getCode(), persons);
roleTotal = roleTotal.add(roleRatio).setScale(RATIO_SCALE, RoundingMode.HALF_UP);
upsertRole(dbMap, outputSplit.getId(), specialtyItem.getCode(), specialtyItem.getName(),
roleItem.getCode(), roleRatio,
OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyItem.getCode())
? null : toPersonJson(persons));
}
validateRoleTotal(roleTotal);
for (OutputSplitBizConstants.RoleItem roleItem : OutputSplitBizConstants.ROLE_ITEMS) {
upsertRole(dbMap, outputSplit.getId(), specialtyItem.getCode(), specialtyItem.getName(),
roleItem.getCode(), ratioMap.get(roleItem.getCode()),
toPersonJson(personMap.get(roleItem.getCode())));
}
}
}
@@ -160,11 +165,14 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
private Map<String, Map<String, SpecialtyRoleSplitSaveItemReqVO>> buildGroupedInput(List<SpecialtyRoleSplitSaveItemReqVO> items) {
Map<String, Map<String, SpecialtyRoleSplitSaveItemReqVO>> result = new LinkedHashMap<>();
if (items == null || items.isEmpty()) {
return result;
}
for (SpecialtyRoleSplitSaveItemReqVO item : items) {
if (!OutputSplitBizConstants.isValidSpecialtyCode(item.getSpecialtyCode())) {
throw exception(SPECIALTY_ROLE_SPLIT_SPECIALTY_INVALID);
}
if (!OutputSplitBizConstants.isValidRoleCode(item.getRoleCode())) {
if (!OutputSplitBizConstants.isValidRoleCode(item.getSpecialtyCode(), item.getRoleCode())) {
throw exception(SPECIALTY_ROLE_SPLIT_ROLE_INVALID);
}
result.computeIfAbsent(item.getSpecialtyCode(), key -> new LinkedHashMap<>())
@@ -174,7 +182,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
}
private void upsertRole(Map<String, SpecialtyRoleSplitDO> dbMap, Long outputSplitId, String specialtyCode,
String specialtyName, String roleCode, BigDecimal roleRatio, String personNames) {
String specialtyName, String roleCode, BigDecimal roleRatio, String personsJson) {
String key = specialtyCode + ":" + roleCode;
SpecialtyRoleSplitDO dbItem = dbMap.get(key);
Integer sortNo = dbItem != null && dbItem.getSortNo() != null
@@ -189,7 +197,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
createObj.setRoleCode(roleCode);
createObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode));
createObj.setRoleRatio(ratio(roleRatio));
createObj.setPersonNames(personNames);
createObj.setPersonsJson(personsJson);
createObj.setSortNo(sortNo);
specialtyRoleSplitMapper.insert(createObj);
return;
@@ -202,7 +210,7 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
updateObj.setRoleCode(roleCode);
updateObj.setRoleName(OutputSplitBizConstants.getRoleName(roleCode));
updateObj.setRoleRatio(ratio(roleRatio));
updateObj.setPersonNames(personNames);
updateObj.setPersonsJson(personsJson);
updateObj.setSortNo(sortNo);
specialtyRoleSplitMapper.updateById(updateObj);
}
@@ -213,9 +221,13 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
return item == null ? new ArrayList<>() : item.getPersons();
}
private BigDecimal getSaveRoleRatio(Map<String, SpecialtyRoleSplitSaveItemReqVO> roleMap, String roleCode) {
private BigDecimal getSaveRoleRatio(Map<String, SpecialtyRoleSplitSaveItemReqVO> roleMap,
String specialtyCode, String roleCode) {
SpecialtyRoleSplitSaveItemReqVO item = roleMap.get(roleCode);
BigDecimal roleRatio = item == null ? ZERO_RATIO : ratio(item.getRoleRatio());
if (!OutputSplitBizConstants.isValidRoleCode(specialtyCode, roleCode)) {
throw exception(SPECIALTY_ROLE_SPLIT_ROLE_INVALID);
}
if (roleRatio.compareTo(ZERO_RATIO) < 0 || roleRatio.compareTo(ONE_RATIO) > 0) {
throw exception(SPECIALTY_ROLE_SPLIT_ROLE_RATIO_INVALID);
}
@@ -231,18 +243,20 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
if (person == null) {
continue;
}
String personName = StrUtil.trim(person.getPersonName());
Long employeeId = person.getEmployeeId();
BigDecimal personRatio = person.getPersonRatio();
boolean hasName = StrUtil.isNotBlank(personName);
boolean hasEmployee = employeeId != null;
boolean hasRatio = personRatio != null;
if (!hasName && !hasRatio) {
if (!hasEmployee && !hasRatio) {
continue;
}
if (!hasName || !hasRatio) {
if (!hasEmployee || !hasRatio) {
throw exception(SPECIALTY_ROLE_SPLIT_PERSON_INVALID);
}
EmployeeDO employee = employeeService.validateEmployeeExists(employeeId);
SpecialtyRolePersonSaveReqVO normalized = new SpecialtyRolePersonSaveReqVO();
normalized.setPersonName(personName);
normalized.setEmployeeId(employeeId);
normalized.setEmployeeName(employee.getEmployeeName());
BigDecimal normalizedRatio = ratio(personRatio);
if (normalizedRatio.compareTo(ZERO_RATIO) < 0 || normalizedRatio.compareTo(ONE_RATIO) > 0) {
throw exception(SPECIALTY_ROLE_SPLIT_PERSON_RATIO_INVALID);
@@ -271,19 +285,32 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
}
}
private BigDecimal getStoredRoleRatio(SpecialtyRoleSplitDO dbItem, String roleCode) {
if (dbItem == null) {
return OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) ? ONE_RATIO : ZERO_RATIO;
private BigDecimal getStoredRoleRatio(SpecialtyRoleSplitDO dbItem, String specialtyCode, String roleCode,
Map<String, BigDecimal> defaultRoleRatioMap) {
if (dbItem != null) {
return ratio(dbItem.getRoleRatio());
}
return ratio(dbItem.getRoleRatio());
if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) {
return ratio(defaultRoleRatioMap.get(roleCode));
}
return OutputSplitBizConstants.ROLE_DESIGN.equals(roleCode) ? ONE_RATIO : ZERO_RATIO;
}
private List<SpecialtyRolePersonRespVO> buildRespPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleAmount,
BigDecimal roleRatio) {
private List<SpecialtyRolePersonRespVO> buildRespPersons(SpecialtyRoleSplitDO dbItem, String specialtyCode,
String roleCode, BigDecimal roleAmount,
BigDecimal roleRatio,
Map<String, List<ProjectRolePersonDO>> projectRolePersonMap) {
List<SpecialtyRolePersonSaveReqVO> sourcePersons;
if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) {
sourcePersons = buildDefaultProjectLeadPersons(projectRolePersonMap.get(roleCode));
} else {
sourcePersons = parseStoredPersons(dbItem);
}
List<SpecialtyRolePersonRespVO> result = new ArrayList<>();
for (SpecialtyRolePersonSaveReqVO person : parseStoredPersons(dbItem, roleRatio)) {
for (SpecialtyRolePersonSaveReqVO person : sourcePersons) {
SpecialtyRolePersonRespVO respVO = new SpecialtyRolePersonRespVO();
respVO.setPersonName(person.getPersonName());
respVO.setEmployeeId(person.getEmployeeId());
respVO.setEmployeeName(person.getEmployeeName());
respVO.setPersonRatio(ratio(person.getPersonRatio()));
respVO.setPersonAmount(multiplyAmount(roleAmount, respVO.getPersonRatio()));
result.add(respVO);
@@ -291,66 +318,87 @@ public class SpecialtyRoleSplitServiceImpl implements SpecialtyRoleSplitService
return result;
}
private List<SpecialtyRolePersonSaveReqVO> parseStoredPersons(SpecialtyRoleSplitDO dbItem, BigDecimal roleRatio) {
private List<SpecialtyRolePersonSaveReqVO> parseStoredPersons(SpecialtyRoleSplitDO dbItem) {
if (dbItem == null || dbItem.getPersonsJson() == null || dbItem.getPersonsJson().trim().isEmpty()) {
return new ArrayList<>();
}
List<SpecialtyRolePersonSaveReqVO> persons = JsonUtils.parseArray(dbItem.getPersonsJson(), SpecialtyRolePersonSaveReqVO.class);
if (persons == null) {
return new ArrayList<>();
}
return persons.stream()
.filter(Objects::nonNull)
.map(person -> {
SpecialtyRolePersonSaveReqVO normalized = new SpecialtyRolePersonSaveReqVO();
normalized.setEmployeeId(person.getEmployeeId());
normalized.setEmployeeName(person.getEmployeeName());
normalized.setPersonRatio(ratio(person.getPersonRatio()));
return normalized;
})
.filter(person -> person.getEmployeeId() != null
&& person.getEmployeeName() != null
&& !person.getEmployeeName().trim().isEmpty())
.collect(Collectors.toList());
}
private List<SpecialtyRolePersonSaveReqVO> buildDefaultProjectLeadPersons(List<ProjectRolePersonDO> rolePersons) {
if (rolePersons == null || rolePersons.isEmpty()) {
return new ArrayList<>();
}
List<SpecialtyRolePersonSaveReqVO> result = new ArrayList<>();
if (dbItem == null) {
return result;
}
String rawPersons = StrUtil.trimToEmpty(dbItem.getPersonNames());
BigDecimal storedRoleRatio = ratio(roleRatio);
if (StrUtil.isNotBlank(rawPersons) && JsonUtils.isJson(rawPersons)) {
List<SpecialtyRolePersonSaveReqVO> jsonPersons = JsonUtils.parseObjectQuietly(rawPersons, PERSON_LIST_TYPE);
if (jsonPersons != null) {
for (SpecialtyRolePersonSaveReqVO jsonPerson : jsonPersons) {
if (jsonPerson == null) {
continue;
}
String personName = StrUtil.trimToEmpty(jsonPerson.getPersonName());
BigDecimal personRatio = jsonPerson.getPersonRatio();
if (StrUtil.isBlank(personName) && personRatio == null) {
continue;
}
SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO();
person.setPersonName(personName);
person.setPersonRatio(ratio(personRatio));
result.add(person);
}
if (!result.isEmpty()) {
if (storedRoleRatio.compareTo(ZERO_RATIO) > 0
&& sumPersonRatios(result).compareTo(storedRoleRatio) == 0) {
return convertLegacyPersons(result, storedRoleRatio);
}
return result;
}
}
}
if (StrUtil.isNotBlank(rawPersons)) {
result.add(buildStoredPerson(rawPersons, storedRoleRatio.compareTo(ZERO_RATIO) > 0 ? ONE_RATIO : ZERO_RATIO));
return result;
BigDecimal perRatio = BigDecimal.ONE.divide(BigDecimal.valueOf(rolePersons.size()), RATIO_SCALE, RoundingMode.HALF_UP);
BigDecimal accumulated = ZERO_RATIO;
for (int i = 0; i < rolePersons.size(); i++) {
ProjectRolePersonDO rolePerson = rolePersons.get(i);
SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO();
person.setEmployeeId(rolePerson.getEmployeeId());
person.setEmployeeName(rolePerson.getEmployeeName());
BigDecimal personRatio = i == rolePersons.size() - 1
? ONE_RATIO.subtract(accumulated).setScale(RATIO_SCALE, RoundingMode.HALF_UP)
: perRatio;
person.setPersonRatio(personRatio);
accumulated = accumulated.add(personRatio).setScale(RATIO_SCALE, RoundingMode.HALF_UP);
result.add(person);
}
return result;
}
private List<SpecialtyRolePersonSaveReqVO> convertLegacyPersons(List<SpecialtyRolePersonSaveReqVO> persons,
BigDecimal roleRatio) {
List<SpecialtyRolePersonSaveReqVO> result = new ArrayList<>();
if (roleRatio.compareTo(ZERO_RATIO) <= 0) {
return persons;
private Map<String, BigDecimal> buildDefaultRoleRatioMap(String specialtyCode,
Map<String, List<ProjectRolePersonDO>> projectRolePersonMap) {
if (!OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) {
return Collections.emptyMap();
}
for (SpecialtyRolePersonSaveReqVO person : persons) {
SpecialtyRolePersonSaveReqVO converted = new SpecialtyRolePersonSaveReqVO();
converted.setPersonName(person.getPersonName());
converted.setPersonRatio(ratio(person.getPersonRatio().divide(roleRatio, RATIO_SCALE, RoundingMode.HALF_UP)));
result.add(converted);
boolean hasProjectManager = projectRolePersonMap.containsKey(OutputSplitBizConstants.ROLE_PROJECT_MANAGER)
&& !projectRolePersonMap.get(OutputSplitBizConstants.ROLE_PROJECT_MANAGER).isEmpty();
boolean hasEngineeringPrincipal = projectRolePersonMap.containsKey(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL)
&& !projectRolePersonMap.get(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL).isEmpty();
Map<String, BigDecimal> result = new LinkedHashMap<>();
if (hasProjectManager && hasEngineeringPrincipal) {
result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, HALF_RATIO);
result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, HALF_RATIO);
return result;
}
if (hasProjectManager) {
result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, ONE_RATIO);
result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, ZERO_RATIO);
return result;
}
if (hasEngineeringPrincipal) {
result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, ZERO_RATIO);
result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, ONE_RATIO);
return result;
}
result.put(OutputSplitBizConstants.ROLE_PROJECT_MANAGER, HALF_RATIO);
result.put(OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, HALF_RATIO);
return result;
}
private SpecialtyRolePersonSaveReqVO buildStoredPerson(String personName, BigDecimal personRatio) {
SpecialtyRolePersonSaveReqVO person = new SpecialtyRolePersonSaveReqVO();
person.setPersonName(personName);
person.setPersonRatio(ratio(personRatio));
return person;
private BigDecimal getSpecialtyAmount(ProjectOutputSplitDO outputSplit, BigDecimal assessmentOutputValue,
String specialtyCode) {
if (OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) {
return multiplyAmount(assessmentOutputValue, outputSplit.getProjectLeadRatio());
}
return projectOutputSplitService.getSpecialtyAmount(outputSplit, assessmentOutputValue, specialtyCode);
}
private String toPersonJson(List<SpecialtyRolePersonSaveReqVO> persons) {

View File

@@ -0,0 +1,20 @@
package cn.iocoder.lyzsys.module.tjt.service.yearkvalue;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO;
public interface YearKValueService {
Long createYearKValue(YearKValueSaveReqVO createReqVO);
void updateYearKValue(YearKValueSaveReqVO updateReqVO);
void deleteYearKValue(Long id);
YearKValueRespVO getYearKValue(Long id);
PageResult<YearKValueRespVO> getYearKValuePage(YearKValuePageReqVO pageReqVO);
}

View File

@@ -0,0 +1,79 @@
package cn.iocoder.lyzsys.module.tjt.service.yearkvalue;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValuePageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.yearkvalue.vo.YearKValueSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.yearkvalue.YearKValueDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.YEAR_K_VALUE_DUPLICATE;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.YEAR_K_VALUE_NOT_EXISTS;
@Service
@Validated
public class YearKValueServiceImpl implements YearKValueService {
@Resource
private YearKValueMapper yearKValueMapper;
@Override
public Long createYearKValue(YearKValueSaveReqVO createReqVO) {
validateYearUnique(null, createReqVO.getKYear());
YearKValueDO yearKValue = BeanUtils.toBean(createReqVO, YearKValueDO.class);
if (yearKValue.getEnabledFlag() == null) {
yearKValue.setEnabledFlag(Boolean.TRUE);
}
yearKValueMapper.insert(yearKValue);
return yearKValue.getId();
}
@Override
public void updateYearKValue(YearKValueSaveReqVO updateReqVO) {
validateYearKValueExists(updateReqVO.getId());
validateYearUnique(updateReqVO.getId(), updateReqVO.getKYear());
YearKValueDO updateObj = BeanUtils.toBean(updateReqVO, YearKValueDO.class);
yearKValueMapper.updateById(updateObj);
}
@Override
public void deleteYearKValue(Long id) {
validateYearKValueExists(id);
yearKValueMapper.deleteById(id);
}
@Override
public YearKValueRespVO getYearKValue(Long id) {
return BeanUtils.toBean(validateYearKValueExists(id), YearKValueRespVO.class);
}
@Override
public PageResult<YearKValueRespVO> getYearKValuePage(YearKValuePageReqVO pageReqVO) {
return BeanUtils.toBean(yearKValueMapper.selectPage(pageReqVO), YearKValueRespVO.class);
}
private void validateYearUnique(Long id, Integer kYear) {
YearKValueDO yearKValue = yearKValueMapper.selectByKYear(kYear);
if (yearKValue == null) {
return;
}
if (id == null || !yearKValue.getId().equals(id)) {
throw exception(YEAR_K_VALUE_DUPLICATE);
}
}
private YearKValueDO validateYearKValueExists(Long id) {
YearKValueDO yearKValue = yearKValueMapper.selectById(id);
if (yearKValue == null) {
throw exception(YEAR_K_VALUE_NOT_EXISTS);
}
return yearKValue;
}
}

View File

@@ -48,42 +48,21 @@ spring:
primary: master
datasource:
master:
# url: jdbc:mysql://123.60.156.158:3306/lyz_bus?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
url: jdbc:sqlserver://192.168.1.34:1433;DatabaseName=tjt_czjs;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
# # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
username: sa # SQL Server 连接的示例
password: sql123456 # SQL Server 连接的示例
# username: sa # SQL Server 连接的示例
# password: Lyzsys@2024 # SQL Server 连接的示例
# username: SYSDBA # DM 连接的示例
# password: SYSDBA001 # DM 连接的示例
# username: root # OpenGauss 连接的示例
# password: Lyzsys@2024 # OpenGauss 连接的示例
# slave: # 模拟从库,可根据自己需要修改
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
# username: root
# password: 123456
# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!)
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true
# driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
# username: root
# password: taosdata
# druid:
# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL
# url: jdbc:sqlserver://47.106.184.244:14333;DatabaseName=tjt_czjs;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8
# username: sa # SQL Server 连接的示例
# password: sql123456 # SQL Server 连接的示例
url: jdbc:sqlserver://localhost:1433;DatabaseName=tjt_czjs;SelectMethod=cursor;encrypt=false;trustServerCertificate=true;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8
username: tjt_user # SQL Server 连接的示例
password: tjt@Admin2026 # SQL Server 连接的示例
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 8.163.1.126 # 地址
host: 127.0.0.1 # 地址
# host: 8.163.1.126
port: 6379 # 端口
database: 8 # 数据库索引
password: lyzbim2016
password: lyzsys@2026
# password: lyzbim2016
--- #################### 定时任务相关配置 ####################