diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java index f17392c..c1db5a8 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/controller/admin/report/vo/ProjectOverviewExportReqVO.java @@ -3,22 +3,20 @@ 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") +@Schema(description = "Management Backend - Project overview export Request VO") @Data public class ProjectOverviewExportReqVO { - @Schema(description = "年度", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026") - @NotNull(message = "年度不能为空") + @Schema(description = "Year", example = "2026") private Integer year; - @Schema(description = "专业编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "water") - @NotBlank(message = "专业编码不能为空") - private String specialtyCode; + @Schema(description = "Office ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "专业所不能为空") + private Long officeId; - @Schema(description = "排序方式", example = "output_desc") + @Schema(description = "Sort type", example = "output_desc") private String sortType; } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java index 85ead63..f4b29b0 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/ProjectOutputReportServiceImpl.java @@ -7,7 +7,6 @@ import cn.iocoder.lyzsys.module.tjt.controller.admin.report.vo.EmployeeOutputSum 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.ProjectOverviewExcelRespVO; 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; @@ -33,8 +32,10 @@ import cn.iocoder.lyzsys.module.tjt.dal.mysql.yearkvalue.YearKValueMapper; import cn.iocoder.lyzsys.module.tjt.enums.OutputSplitBizConstants; import cn.iocoder.lyzsys.module.tjt.enums.ProjectPlanningBizTypeConstants; import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.EmployeeOutputSummaryExcelBuilder; import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectBudgetExcelBuilder; import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectLeadQuarterOutputExcelBuilder; +import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectOverviewOutputExcelBuilder; import cn.iocoder.lyzsys.module.tjt.service.report.builder.ProjectQuarterOutputExcelBuilder; import cn.iocoder.lyzsys.module.tjt.service.report.builder.SpecialtyPersonOutputExcelBuilder; import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService; @@ -61,6 +62,8 @@ 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.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; +import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.OFFICE_NOT_EXISTS; 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_PLANNING_NOT_EXISTS; @@ -108,6 +111,10 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic @Resource private ProjectLeadQuarterOutputExcelBuilder projectLeadQuarterOutputExcelBuilder; @Resource + private ProjectOverviewOutputExcelBuilder projectOverviewOutputExcelBuilder; + @Resource + private EmployeeOutputSummaryExcelBuilder employeeOutputSummaryExcelBuilder; + @Resource private SpecialtyPersonOutputExcelBuilder specialtyPersonOutputExcelBuilder; @Override @@ -180,8 +187,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic throws IOException { ProjectPlanningDO anchorPlanning = validateMajorPlanning(reqVO.getPlanningId()); ProjectDO project = validateProjectExists(anchorPlanning.getProjectId()); - Integer reportYear = resolveReportYear(anchorPlanning); - List planningList = sortPlanningList(getProjectPlanningScope(anchorPlanning, true)); + Integer reportYear = LocalDate.now().getYear(); + List planningList = projectPlanningMapper.selectListByProjectId(project.getId()).stream() + .sorted(Comparator + .comparingInt((ProjectPlanningDO item) -> projectLeadOutputTypeOrder(item.getOwnershipType())) + .thenComparingInt((ProjectPlanningDO item) -> designPartOrder(item.getDesignPart())) + .thenComparing(ProjectPlanningDO::getId)) + .collect(Collectors.toList()); + List projectRolePersons = projectRolePersonMapper.selectListByProjectId(project.getId()); Map> quarterMap = getQuarterMap(planningList); Map outputSplitMap = getOutputSplitMap(planningList); Map> roleSplitMap = getRoleSplitMap(planningList); @@ -189,13 +202,16 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic ProjectLeadQuarterOutputExcelBuilder.ExportData data = new ProjectLeadQuarterOutputExcelBuilder.ExportData(); data.setProjectName(project.getProjectName()); data.setYear(reportYear); + data.setProjectManagerNames(joinProjectRoleNames(projectRolePersons, + OutputSplitBizConstants.ROLE_PROJECT_MANAGER, "/")); + data.setEngineeringPrincipalNames(joinProjectRoleNames(projectRolePersons, + OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, "/")); + data.setCenterSignerLabel("设计中心相关负责人(签名):"); + data.setProjectSignerLabel("项目经理/工程负责人(签名):"); List rows = new ArrayList<>(); for (ProjectPlanningDO planning : planningList) { - ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); - if (outputSplit == null) { - continue; - } + ProjectOutputSplitDO outputSplit = resolveQuarterOutputSplit(planning, outputSplitMap); BigDecimal projectLeadRatio = ratio(outputSplit.getProjectLeadRatio()); QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); List roleList = roleSplitMap.getOrDefault(planning.getId(), Collections.emptyList()); @@ -210,38 +226,39 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic ProjectLeadQuarterOutputExcelBuilder.LeadQuarterRow row = new ProjectLeadQuarterOutputExcelBuilder.LeadQuarterRow(); - row.setPlanningContent(planning.getPlanningContent()); - row.setProjectManagerNames(joinRoleSplitPersonNames(projectManagerRow)); + row.setSerialNo(rows.size() + 1); + row.setOutputType(resolveBudgetCategoryLabel(planning.getOwnershipType())); + row.setDesignContent(defaultString(planning.getPlanningContent())); + row.setProjectManagerNames(joinRoleSplitPersonNames(projectManagerRow, "/")); row.setProjectManagerRatio(projectManagerRatio); - row.setEngineeringPrincipalNames(joinRoleSplitPersonNames(engineeringPrincipalRow)); + row.setEngineeringPrincipalNames(joinRoleSplitPersonNames(engineeringPrincipalRow, "/")); row.setEngineeringPrincipalRatio(engineeringPrincipalRatio); - row.setProjectManagerQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), - projectLeadRatio, projectManagerRatio)); - row.setProjectManagerQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), - projectLeadRatio, projectManagerRatio)); - row.setProjectManagerQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), - projectLeadRatio, projectManagerRatio)); - row.setProjectManagerQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), - projectLeadRatio, projectManagerRatio)); - row.setProjectManagerYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), - projectLeadRatio, projectManagerRatio)); - row.setEngineeringPrincipalQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), - projectLeadRatio, engineeringPrincipalRatio)); - row.setEngineeringPrincipalQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), - projectLeadRatio, engineeringPrincipalRatio)); - row.setEngineeringPrincipalQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), - projectLeadRatio, engineeringPrincipalRatio)); - row.setEngineeringPrincipalQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), - projectLeadRatio, engineeringPrincipalRatio)); - row.setEngineeringPrincipalYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), - projectLeadRatio, engineeringPrincipalRatio)); - row.setTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), projectLeadRatio)); + row.setProjectManagerQuarterOneAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterOneAmount(), + projectLeadRatio, projectManagerRatio))); + row.setProjectManagerQuarterTwoAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterTwoAmount(), + projectLeadRatio, projectManagerRatio))); + row.setProjectManagerQuarterThreeAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterThreeAmount(), + projectLeadRatio, projectManagerRatio))); + row.setProjectManagerQuarterFourAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterFourAmount(), + projectLeadRatio, projectManagerRatio))); + row.setProjectManagerYearTotalAmountWan(amountToWan(multiplyAmount(quarterSummary.getYearTotalAmount(), + projectLeadRatio, projectManagerRatio))); + row.setEngineeringPrincipalQuarterOneAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterOneAmount(), + projectLeadRatio, engineeringPrincipalRatio))); + row.setEngineeringPrincipalQuarterTwoAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterTwoAmount(), + projectLeadRatio, engineeringPrincipalRatio))); + row.setEngineeringPrincipalQuarterThreeAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterThreeAmount(), + projectLeadRatio, engineeringPrincipalRatio))); + row.setEngineeringPrincipalQuarterFourAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterFourAmount(), + projectLeadRatio, engineeringPrincipalRatio))); + row.setEngineeringPrincipalYearTotalAmountWan(amountToWan(multiplyAmount(quarterSummary.getYearTotalAmount(), + projectLeadRatio, engineeringPrincipalRatio))); rows.add(row); } data.setRows(rows); projectLeadQuarterOutputExcelBuilder.writeWorkbook(response, - buildFileName(project.getProjectName(), "项目负责人年度季度计取表"), + buildFileName(project.getProjectName(), "项目经理工程负责人工作量及考核产值表"), projectLeadQuarterOutputExcelBuilder.build(data)); } @@ -249,64 +266,55 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic public void exportSpecialtyPersonOutputExcel(HttpServletResponse response, SpecialtyPersonOutputExportReqVO reqVO) throws IOException { validateSpecialtyCode(reqVO.getSpecialtyCode()); - ProjectPlanningDO planning = validateMajorPlanning(reqVO.getPlanningId()); - ProjectDO project = validateProjectExists(planning.getProjectId()); - Integer reportYear = resolveReportYear(planning); - ProjectOutputSplitDO outputSplit = projectOutputSplitService.getOrCreateProjectOutputSplit(planning.getId()); - QuarterSummary quarterSummary = buildQuarterSummary(planning, - projectPlanningQuarterMapper.selectListByPlanningId(planning.getId()), reportYear); - BigDecimal specialtyBaseRatio = multiplyRatio(outputSplit.getOfficeRatio(), - getSpecialtyRatio(outputSplit, reqVO.getSpecialtyCode())); - - List roleList = specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(planning.getId()); - List specialtyRoleList = roleList.stream() - .filter(item -> Objects.equals(item.getSpecialtyCode(), reqVO.getSpecialtyCode())) + ProjectPlanningDO anchorPlanning = validateMajorPlanning(reqVO.getPlanningId()); + ProjectDO project = validateProjectExists(anchorPlanning.getProjectId()); + Integer reportYear = LocalDate.now().getYear(); + List planningList = sortPlanningList(projectPlanningMapper.selectListByProjectId(project.getId()).stream() + .filter(item -> ProjectPlanningBizTypeConstants.isMajor(item.getOwnershipType())) + .collect(Collectors.toList())); + Map> quarterMap = getQuarterMap(planningList); + List relevantPlanningList = planningList.stream() + .filter(item -> hasYearAmount(quarterMap.get(item.getId()), reportYear)) .collect(Collectors.toList()); + Map outputSplitMap = getOutputSplitMap(relevantPlanningList); + Map> roleSplitMap = getRoleSplitMap(relevantPlanningList); + List projectRolePersons = projectRolePersonMapper.selectListByProjectId(project.getId()); + List anchorRoleList = roleSplitMap.get(anchorPlanning.getId()); + if (anchorRoleList == null) { + anchorRoleList = specialtyRoleSplitService.getSpecialtyRoleSplitListByPlanningId(anchorPlanning.getId()); + } + SpecialtyRoleSplitRespVO projectManagerRow = findRoleRow(anchorRoleList, + OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD, OutputSplitBizConstants.ROLE_PROJECT_MANAGER); + SpecialtyRoleSplitRespVO engineeringPrincipalRow = findRoleRow(anchorRoleList, + OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD, OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL); + String projectManagerNames = joinRoleSplitPersonNames(projectManagerRow, "/"); + if (projectManagerNames.isEmpty()) { + projectManagerNames = joinProjectRoleNames(projectRolePersons, + OutputSplitBizConstants.ROLE_PROJECT_MANAGER, "/"); + } + String engineeringPrincipalNames = joinRoleSplitPersonNames(engineeringPrincipalRow, "/"); + if (engineeringPrincipalNames.isEmpty()) { + engineeringPrincipalNames = joinProjectRoleNames(projectRolePersons, + OutputSplitBizConstants.ROLE_ENGINEERING_PRINCIPAL, "/"); + } SpecialtyPersonOutputExcelBuilder.ExportData data = new SpecialtyPersonOutputExcelBuilder.ExportData(); + data.setProjectCode(""); data.setProjectName(project.getProjectName()); - data.setPlanningContent(planning.getPlanningContent()); data.setSpecialtyName(OutputSplitBizConstants.getSpecialtyName(reqVO.getSpecialtyCode())); + data.setProjectManagerNames(projectManagerNames); + data.setEngineeringPrincipalNames(engineeringPrincipalNames); + data.setProjectManagerRatio(projectManagerRow == null ? ZERO_RATIO : ratio(projectManagerRow.getRoleRatio())); + data.setEngineeringPrincipalRatio(engineeringPrincipalRow == null + ? ZERO_RATIO : ratio(engineeringPrincipalRow.getRoleRatio())); data.setYear(reportYear); - List rows = new ArrayList<>(); - for (SpecialtyRoleSplitRespVO roleRow : specialtyRoleList) { - BigDecimal roleRatio = ratio(roleRow.getRoleRatio()); - List persons = roleRow.getPersons() == null - ? Collections.emptyList() : roleRow.getPersons(); - if (persons.isEmpty()) { - SpecialtyPersonOutputExcelBuilder.PersonQuarterRow row = - new SpecialtyPersonOutputExcelBuilder.PersonQuarterRow(); - row.setRoleName(roleRow.getRoleName()); - row.setEmployeeName("-"); - row.setRoleRatio(roleRatio); - row.setPersonRatio(ZERO_RATIO); - row.setQuarterOneAmount(ZERO_AMOUNT); - row.setQuarterTwoAmount(ZERO_AMOUNT); - row.setQuarterThreeAmount(ZERO_AMOUNT); - row.setQuarterFourAmount(ZERO_AMOUNT); - row.setYearTotalAmount(ZERO_AMOUNT); - rows.add(row); - continue; - } - for (SpecialtyRolePersonRespVO person : persons) { - BigDecimal personRatio = ratio(person.getPersonRatio()); - BigDecimal totalRatio = multiplyRatio(specialtyBaseRatio, roleRatio, personRatio); - SpecialtyPersonOutputExcelBuilder.PersonQuarterRow row = - new SpecialtyPersonOutputExcelBuilder.PersonQuarterRow(); - row.setRoleName(roleRow.getRoleName()); - row.setEmployeeName(person.getEmployeeName()); - row.setRoleRatio(roleRatio); - row.setPersonRatio(personRatio); - row.setQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), totalRatio)); - row.setQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), totalRatio)); - row.setQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), totalRatio)); - row.setQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), totalRatio)); - row.setYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), totalRatio)); - rows.add(row); - } - } - data.setRows(rows); + data.setCenterSignerLabel("设计中心相关负责人(签名):"); + data.setProjectSignerLabel("项目经理 / 工程负责人(签名):"); + data.setRolePersonModules(buildSpecialtyRolePersonModules(relevantPlanningList, roleSplitMap, reqVO.getSpecialtyCode())); + data.setPersonAmountModules(buildSpecialtyPersonAmountModules(relevantPlanningList, roleSplitMap, reqVO.getSpecialtyCode())); + data.setRows(buildSpecialtyPlanningRows(relevantPlanningList, quarterMap, outputSplitMap, + roleSplitMap, project.getProjectName(), reqVO.getSpecialtyCode(), reportYear)); specialtyPersonOutputExcelBuilder.writeWorkbook(response, buildFileName(project.getProjectName(), @@ -317,63 +325,72 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic @Override public void exportProjectOverviewExcel(HttpServletResponse response, ProjectOverviewExportReqVO reqVO) throws IOException { - validateSpecialtyCode(reqVO.getSpecialtyCode()); + Integer reportYear = reqVO.getYear() == null ? LocalDate.now().getYear() : reqVO.getYear(); + OfficeDO office = validateOfficeExists(reqVO.getOfficeId()); + List employeeList = employeeMapper.selectList(new LambdaQueryWrapperX() + .eq(EmployeeDO::getOfficeId, reqVO.getOfficeId()) + .eq(EmployeeDO::getEnabledFlag, Boolean.TRUE) + .orderByAsc(EmployeeDO::getSortNo) + .orderByAsc(EmployeeDO::getId)); + if (employeeList.isEmpty()) { + throw invalidParamException("当前专业所下暂无员工,无法导出"); + } + + Set employeeIds = employeeList.stream() + .map(EmployeeDO::getId) + .collect(Collectors.toCollection(LinkedHashSet::new)); List majorPlanningList = projectPlanningMapper.selectList( new LambdaQueryWrapperX() .eq(ProjectPlanningDO::getOwnershipType, ProjectPlanningBizTypeConstants.OWNERSHIP_TYPE_MAJOR)); - if (majorPlanningList.isEmpty()) { - ExcelUtils.write(response, "项目总览表.xlsx", "项目总览", ProjectOverviewExcelRespVO.class, - Collections.emptyList()); - return; - } - Map projectMap = getProjectMap(majorPlanningList); Map> quarterMap = getQuarterMap(majorPlanningList); Map outputSplitMap = getOutputSplitMap(majorPlanningList); + Map> roleSplitMap = getRoleSplitMap(majorPlanningList); - List rows = new ArrayList<>(); + Map accumulatorMap = new LinkedHashMap<>(); for (ProjectPlanningDO planning : majorPlanningList) { + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); + if (quarterSummary.getYearTotalAmount().compareTo(ZERO_AMOUNT) <= 0) { + continue; + } ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); if (outputSplit == null) { continue; } - QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reqVO.getYear()); - BigDecimal combinedRatio = addRatio(outputSplit.getProjectLeadRatio(), - multiplyRatio(outputSplit.getOfficeRatio(), getSpecialtyRatio(outputSplit, reqVO.getSpecialtyCode()))); - if (combinedRatio.compareTo(ZERO_RATIO) <= 0 || quarterSummary.getYearTotalAmount().compareTo(ZERO_AMOUNT) <= 0) { + PlanningOfficeContribution contribution = buildPlanningOfficeContribution( + planning, outputSplit, quarterSummary, roleSplitMap.get(planning.getId()), employeeIds); + if (contribution.getCurrentSettlementAmount().compareTo(ZERO_AMOUNT) <= 0) { continue; } ProjectDO project = projectMap.get(planning.getProjectId()); - ProjectOverviewExcelRespVO row = new ProjectOverviewExcelRespVO(); - row.setProjectName(project == null ? "" : project.getProjectName()); - row.setPlanningContent(planning.getPlanningContent()); - row.setProgressText(buildProgressText(project, planning)); - row.setDesignStage(planning.getDesignStage()); - row.setTotalOutputAmount(multiplyAmount(amount(planning.getAssessmentOutputValue()), - defaultDistributionRatio(planning), combinedRatio)); - row.setHistoricalIssuedRatioText(percentText(quarterSummary.getHistoricalIssuedRatio())); - row.setCurrentSettlementAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), combinedRatio)); - row.setPendingRatioText(percentText(quarterSummary.getPendingRatio())); - row.setQuarterOneAmount(multiplyAmount(quarterSummary.getQuarterOneAmount(), combinedRatio)); - row.setQuarterTwoAmount(multiplyAmount(quarterSummary.getQuarterTwoAmount(), combinedRatio)); - row.setQuarterThreeAmount(multiplyAmount(quarterSummary.getQuarterThreeAmount(), combinedRatio)); - row.setQuarterFourAmount(multiplyAmount(quarterSummary.getQuarterFourAmount(), combinedRatio)); - row.setYearTotalAmount(multiplyAmount(quarterSummary.getYearTotalAmount(), combinedRatio)); - rows.add(row); + ProjectOverviewAccumulator accumulator = accumulatorMap.computeIfAbsent(planning.getProjectId(), projectId -> { + ProjectOverviewAccumulator item = new ProjectOverviewAccumulator(); + item.setProjectId(projectId); + item.setProjectName(project == null ? "" : defaultString(project.getProjectName())); + return item; + }); + appendProjectOverviewContribution(accumulator, planning, project, contribution); } - sortProjectOverviewRows(rows, reqVO.getSortType()); - for (int i = 0; i < rows.size(); i++) { - rows.get(i).setSerialNo(i + 1); - } - ExcelUtils.write(response, - buildFileName(OutputSplitBizConstants.getSpecialtyName(reqVO.getSpecialtyCode()), "项目总览表"), - "项目总览", ProjectOverviewExcelRespVO.class, rows); + List rows = buildProjectOverviewRows( + new ArrayList<>(accumulatorMap.values()), employeeList, reqVO.getSortType()); + ProjectOverviewOutputExcelBuilder.ExportData data = new ProjectOverviewOutputExcelBuilder.ExportData(); + data.setYear(reportYear); + data.setOfficeName(office.getOfficeName()); + data.setEmployeeColumns(buildProjectOverviewEmployeeColumns(employeeList)); + data.setRows(rows); + data.setTotalRow(buildProjectOverviewTotalRow(new ArrayList<>(accumulatorMap.values()), employeeList)); + + projectOverviewOutputExcelBuilder.writeWorkbook(response, + buildFileName(office.getOfficeName(), "项目总览表"), + projectOverviewOutputExcelBuilder.build(data)); } @Override public void exportEmployeeOutputSummaryExcel(HttpServletResponse response, EmployeeOutputSummaryExportReqVO reqVO) throws IOException { + YearKValueDO yearKValue = yearKValueMapper.selectEnabledByKYear(reqVO.getYear()); + BigDecimal kValue = yearKValue == null ? DEFAULT_K_VALUE : ratio(yearKValue.getKValue()); List employeeList = employeeMapper.selectList(new LambdaQueryWrapperX() .eqIfPresent(EmployeeDO::getId, reqVO.getEmployeeId()) .eqIfPresent(EmployeeDO::getOfficeId, reqVO.getOfficeId()) @@ -382,15 +399,17 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic .orderByAsc(EmployeeDO::getSortNo) .orderByAsc(EmployeeDO::getId)); if (employeeList.isEmpty()) { - ExcelUtils.write(response, "员工个人考核产值汇总.xlsx", "员工汇总", - EmployeeOutputSummaryExcelRespVO.class, Collections.emptyList()); + EmployeeOutputSummaryExcelBuilder.ExportData data = new EmployeeOutputSummaryExcelBuilder.ExportData(); + data.setYear(reqVO.getYear()); + data.setKValue(kValue); + data.setRows(Collections.emptyList()); + employeeOutputSummaryExcelBuilder.writeWorkbook(response, + buildFileName(String.valueOf(reqVO.getYear()), "员工个人考核产值汇总"), + employeeOutputSummaryExcelBuilder.build(data)); return; } Set employeeIds = employeeList.stream().map(EmployeeDO::getId).collect(Collectors.toCollection(LinkedHashSet::new)); - Map employeeMap = employeeList.stream() - .collect(Collectors.toMap(EmployeeDO::getId, item -> item, (a, b) -> a, LinkedHashMap::new)); - Map officeMap = getOfficeMap(employeeList); List majorPlanningList = projectPlanningMapper.selectList( new LambdaQueryWrapperX() @@ -439,8 +458,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic .inIfPresent(EmployeeYearCostBudgetDO::getEmployeeId, employeeIds)) .stream() .collect(Collectors.toMap(EmployeeYearCostBudgetDO::getEmployeeId, item -> item, (a, b) -> a)); - YearKValueDO yearKValue = yearKValueMapper.selectEnabledByKYear(reqVO.getYear()); - BigDecimal kValue = yearKValue == null ? DEFAULT_K_VALUE : ratio(yearKValue.getKValue()); List rows = new ArrayList<>(); for (EmployeeDO employee : employeeList) { @@ -464,18 +481,17 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic EmployeeOutputSummaryExcelRespVO row = new EmployeeOutputSummaryExcelRespVO(); row.setEmployeeName(employee.getEmployeeName()); - row.setOfficeName(getOfficeName(officeMap, employee.getOfficeId())); - row.setQuarterOneAmount(accumulator.getQuarterOneAmount()); - row.setQuarterTwoAmount(accumulator.getQuarterTwoAmount()); - row.setQuarterThreeAmount(accumulator.getQuarterThreeAmount()); - row.setQuarterFourAmount(accumulator.getQuarterFourAmount()); - row.setAnnualTotalAmount(annualTotalAmount); - row.setOfficeLeaderOrBimAmount(officeLeaderOrBimAmount); - row.setTotalAssessmentOutputAmount(totalAssessmentOutputAmount); - row.setExpectedCostAmount(expectedCostAmount); - row.setBasicAssessmentOutputAmount(basicAssessmentOutputAmount); - row.setRemainingOutputAmount(remainingOutputAmount); - row.setEstimatedYearEndPerformanceAmount(estimatedYearEndPerformanceAmount); + row.setQuarterOneAmount(amountToWan(accumulator.getQuarterOneAmount())); + row.setQuarterTwoAmount(amountToWan(accumulator.getQuarterTwoAmount())); + row.setQuarterThreeAmount(amountToWan(accumulator.getQuarterThreeAmount())); + row.setQuarterFourAmount(amountToWan(accumulator.getQuarterFourAmount())); + row.setAnnualTotalAmount(amountToWan(annualTotalAmount)); + row.setOfficeLeaderOrBimAmount(amountToWan(officeLeaderOrBimAmount)); + row.setTotalAssessmentOutputAmount(amountToWan(totalAssessmentOutputAmount)); + row.setExpectedCostAmount(amountToWan(expectedCostAmount)); + row.setBasicAssessmentOutputAmount(amountToWan(basicAssessmentOutputAmount)); + row.setRemainingOutputAmount(amountToWan(remainingOutputAmount)); + row.setEstimatedYearEndPerformanceAmount(amountToWan(estimatedYearEndPerformanceAmount)); rows.add(row); } @@ -483,8 +499,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic for (int i = 0; i < rows.size(); i++) { rows.get(i).setSerialNo(i + 1); } - ExcelUtils.write(response, buildFileName(String.valueOf(reqVO.getYear()), "员工个人考核产值汇总"), - "员工汇总", EmployeeOutputSummaryExcelRespVO.class, rows); + + EmployeeOutputSummaryExcelBuilder.ExportData data = new EmployeeOutputSummaryExcelBuilder.ExportData(); + data.setYear(reqVO.getYear()); + data.setKValue(kValue); + data.setRows(rows); + employeeOutputSummaryExcelBuilder.writeWorkbook(response, + buildFileName(String.valueOf(reqVO.getYear()), "员工个人考核产值汇总"), + employeeOutputSummaryExcelBuilder.build(data)); } private ProjectPlanningDO validatePlanningExists(Long planningId) { @@ -511,6 +533,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return project; } + private OfficeDO validateOfficeExists(Long officeId) { + OfficeDO office = officeMapper.selectById(officeId); + if (office == null) { + throw exception(OFFICE_NOT_EXISTS); + } + return office; + } + private void validateSpecialtyCode(String specialtyCode) { if (!OutputSplitBizConstants.isValidSpecialtyCode(specialtyCode) || OutputSplitBizConstants.SPECIALTY_PROJECT_LEAD.equals(specialtyCode)) { @@ -559,6 +589,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic BigDecimal totalWaterAssessment = ZERO_AMOUNT; BigDecimal totalHvacAssessment = ZERO_AMOUNT; BigDecimal totalElecAssessment = ZERO_AMOUNT; + BigDecimal totalDigitalAssessment = ZERO_AMOUNT; int serialNo = 1; for (ProjectPlanningDO planning : planningList) { @@ -593,12 +624,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic BigDecimal waterRatio = ratio(outputSplit.getWaterRatio()); BigDecimal hvacRatio = ratio(outputSplit.getHvacRatio()); BigDecimal elecRatio = ratio(outputSplit.getElecRatio()); + BigDecimal digitalRatio = ratio(outputSplit.getDigitalRatio()); BigDecimal archAssessment = multiplyAmount(officeAssessment, archRatio); BigDecimal decorAssessment = multiplyAmount(officeAssessment, decorRatio); BigDecimal structAssessment = multiplyAmount(officeAssessment, structRatio); BigDecimal waterAssessment = multiplyAmount(officeAssessment, waterRatio); BigDecimal hvacAssessment = multiplyAmount(officeAssessment, hvacRatio); BigDecimal elecAssessment = multiplyAmount(officeAssessment, elecRatio); + BigDecimal digitalAssessment = multiplyAmount(officeAssessment, digitalRatio); ProjectQuarterOutputExcelBuilder.QuarterRow row = new ProjectQuarterOutputExcelBuilder.QuarterRow(); row.setSerialNo(serialNo++); @@ -629,12 +662,14 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic row.setWaterRatio(waterRatio); row.setHvacRatio(hvacRatio); row.setElecRatio(elecRatio); + row.setDigitalRatio(digitalRatio); row.setArchAssessmentOutputWan(amountToWan(archAssessment)); row.setDecorAssessmentOutputWan(amountToWan(decorAssessment)); row.setStructAssessmentOutputWan(amountToWan(structAssessment)); row.setWaterAssessmentOutputWan(amountToWan(waterAssessment)); row.setHvacAssessmentOutputWan(amountToWan(hvacAssessment)); row.setElecAssessmentOutputWan(amountToWan(elecAssessment)); + row.setDigitalAssessmentOutputWan(amountToWan(digitalAssessment)); rows.add(row); totalAssessmentOutputValue = totalAssessmentOutputValue.add(assessmentOutputValue).setScale(2, RoundingMode.HALF_UP); @@ -667,6 +702,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic totalWaterAssessment = totalWaterAssessment.add(waterAssessment).setScale(2, RoundingMode.HALF_UP); totalHvacAssessment = totalHvacAssessment.add(hvacAssessment).setScale(2, RoundingMode.HALF_UP); totalElecAssessment = totalElecAssessment.add(elecAssessment).setScale(2, RoundingMode.HALF_UP); + totalDigitalAssessment = totalDigitalAssessment.add(digitalAssessment).setScale(2, RoundingMode.HALF_UP); } ProjectQuarterOutputExcelBuilder.QuarterRow totalRow = new ProjectQuarterOutputExcelBuilder.QuarterRow(); @@ -696,6 +732,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic totalRow.setWaterAssessmentOutputWan(amountToWan(totalWaterAssessment)); totalRow.setHvacAssessmentOutputWan(amountToWan(totalHvacAssessment)); totalRow.setElecAssessmentOutputWan(amountToWan(totalElecAssessment)); + totalRow.setDigitalAssessmentOutputWan(amountToWan(totalDigitalAssessment)); rows.add(totalRow); return rows; } @@ -868,7 +905,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic row.setQuarterThreeAmountWan(amountToWan(quarterSummary.getQuarterThreeAmount())); row.setQuarterFourAmountWan(amountToWan(quarterSummary.getQuarterFourAmount())); row.setYearTotalAmountWan(amountToWan(quarterSummary.getYearTotalAmount())); - row.setRatioRemark(buildQuarterBudgetRemark(reportYear, quarterSummary)); + row.setRatioRemark(defaultString(planning.getProgressRemark())); rows.add(row); totalQuarterOneAmount = totalQuarterOneAmount.add(quarterSummary.getQuarterOneAmount()).setScale(2, RoundingMode.HALF_UP); @@ -919,26 +956,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic .divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP); } - private String buildQuarterBudgetRemark(Integer budgetYear, QuarterSummary summary) { - List parts = new ArrayList<>(); - if (budgetYear != null) { - parts.add("预算年度:" + budgetYear); - } - if (summary == null) { - return String.join(";", parts); - } - if (summary.getCurrentYearRatio().compareTo(BigDecimal.ZERO) == 0 - && summary.getPendingRatio().compareTo(BigDecimal.ZERO) > 0) { - parts.add("未配置本年度发放比例"); - return String.join(";", parts); - } - if (summary.getPendingRatio().compareTo(BigDecimal.ZERO) > 0) { - parts.add("本年度发放比例合计 " + percentText(summary.getCurrentYearRatio())); - parts.add("未发放比例 " + percentText(summary.getPendingRatio())); - } - return String.join(";", parts); - } - private List sortPlanningList(List planningList) { return planningList.stream() .sorted(Comparator @@ -1197,6 +1214,20 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return "其他产值"; } + private int projectLeadOutputTypeOrder(String ownershipType) { + String outputType = resolveBudgetCategoryLabel(ownershipType); + if (Objects.equals(outputType, "六大专业考核产值")) { + return 1; + } + if (Objects.equals(outputType, "专业分包产值")) { + return 2; + } + if (Objects.equals(outputType, "内部协作产值")) { + return 3; + } + return 4; + } + private String buildPlanningDisplayName(ProjectPlanningDO planning) { if (planning == null) { return ""; @@ -1244,6 +1275,10 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic } private String joinProjectRoleNames(List rolePersons, String roleCode) { + return joinProjectRoleNames(rolePersons, roleCode, "、"); + } + + private String joinProjectRoleNames(List rolePersons, String roleCode, String separator) { if (rolePersons == null || rolePersons.isEmpty()) { return ""; } @@ -1251,7 +1286,7 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic .filter(item -> Objects.equals(item.getRoleCode(), roleCode)) .map(ProjectRolePersonDO::getEmployeeName) .filter(Objects::nonNull) - .collect(Collectors.joining("、")); + .collect(Collectors.joining(separator)); } private SpecialtyRoleSplitRespVO findRoleRow(List roleList, @@ -1267,13 +1302,414 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic } private String joinRoleSplitPersonNames(SpecialtyRoleSplitRespVO roleRow) { + return joinRoleSplitPersonNames(roleRow, "、"); + } + + private String joinRoleSplitPersonNames(SpecialtyRoleSplitRespVO roleRow, String separator) { if (roleRow == null || roleRow.getPersons() == null || roleRow.getPersons().isEmpty()) { return ""; } return roleRow.getPersons().stream() .map(SpecialtyRolePersonRespVO::getEmployeeName) .filter(Objects::nonNull) - .collect(Collectors.joining("、")); + .collect(Collectors.joining(separator)); + } + + private List buildSpecialtyRolePersonModules( + List planningList, Map> roleSplitMap, String specialtyCode) { + List result = new ArrayList<>(); + for (String roleCode : specialtyPersonRoleOrder()) { + Map roleModuleMap = new LinkedHashMap<>(); + for (ProjectPlanningDO planning : planningList) { + SpecialtyRoleSplitRespVO roleRow = findRoleRow(roleSplitMap.get(planning.getId()), specialtyCode, roleCode); + if (roleRow == null || roleRow.getPersons() == null) { + continue; + } + for (SpecialtyRolePersonRespVO person : roleRow.getPersons()) { + if (!isValidSpecialtyRolePerson(person)) { + continue; + } + String key = buildSpecialtyRolePersonModuleKey(roleCode, person.getEmployeeId(), person.getEmployeeName()); + roleModuleMap.computeIfAbsent(key, item -> { + SpecialtyPersonOutputExcelBuilder.RolePersonModule module = + new SpecialtyPersonOutputExcelBuilder.RolePersonModule(); + module.setKey(item); + module.setRoleCode(roleCode); + module.setRoleName(defaultString(roleRow.getRoleName())); + module.setEmployeeName(defaultString(person.getEmployeeName())); + module.setPlaceholder(Boolean.FALSE); + return module; + }); + } + } + result.addAll(roleModuleMap.values()); + } + return result; + } + + private List buildSpecialtyPersonAmountModules( + List planningList, Map> roleSplitMap, String specialtyCode) { + Map moduleMap = new LinkedHashMap<>(); + for (ProjectPlanningDO planning : planningList) { + for (String roleCode : specialtyPersonRoleOrder()) { + SpecialtyRoleSplitRespVO roleRow = findRoleRow(roleSplitMap.get(planning.getId()), specialtyCode, roleCode); + if (roleRow == null || roleRow.getPersons() == null) { + continue; + } + for (SpecialtyRolePersonRespVO person : roleRow.getPersons()) { + if (!isValidSpecialtyRolePerson(person)) { + continue; + } + String key = buildSpecialtyPersonModuleKey(person.getEmployeeId(), person.getEmployeeName()); + moduleMap.computeIfAbsent(key, item -> { + SpecialtyPersonOutputExcelBuilder.PersonAmountModule module = + new SpecialtyPersonOutputExcelBuilder.PersonAmountModule(); + module.setKey(item); + module.setEmployeeName(defaultString(person.getEmployeeName())); + module.setPlaceholder(Boolean.FALSE); + return module; + }); + } + } + } + return new ArrayList<>(moduleMap.values()); + } + + private List buildSpecialtyPlanningRows( + List planningList, + Map> quarterMap, + Map outputSplitMap, + Map> roleSplitMap, + String projectName, + String specialtyCode, + Integer reportYear) { + List rows = new ArrayList<>(); + for (ProjectPlanningDO planning : planningList) { + QuarterSummary quarterSummary = buildQuarterSummary(planning, quarterMap.get(planning.getId()), reportYear); + ProjectOutputSplitDO outputSplit = outputSplitMap.get(planning.getId()); + BigDecimal specialtyRatio = getSpecialtyRatio(outputSplit, specialtyCode); + BigDecimal officeRatio = outputSplit == null ? ZERO_RATIO : ratio(outputSplit.getOfficeRatio()); + BigDecimal specialtyBaseRatio = multiplyRatio(officeRatio, specialtyRatio); + + SpecialtyPersonOutputExcelBuilder.SpecialtyPlanningRow row = + new SpecialtyPersonOutputExcelBuilder.SpecialtyPlanningRow(); + row.setSerialNo(rows.size() + 1); + row.setProjectName(projectName); + row.setOutputType(resolveBudgetCategoryLabel(planning.getOwnershipType())); + row.setDesignContent(defaultString(planning.getPlanningContent())); + row.setSpecialtyRatio(specialtyRatio); + row.setSpecialtyQuarterOneAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterOneAmount(), specialtyBaseRatio))); + row.setSpecialtyQuarterTwoAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterTwoAmount(), specialtyBaseRatio))); + row.setSpecialtyQuarterThreeAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterThreeAmount(), specialtyBaseRatio))); + row.setSpecialtyQuarterFourAmountWan(amountToWan(multiplyAmount(quarterSummary.getQuarterFourAmount(), specialtyBaseRatio))); + row.setSpecialtyYearTotalAmountWan(amountToWan(multiplyAmount(quarterSummary.getYearTotalAmount(), specialtyBaseRatio))); + row.setAdjustedPersonRatio(ZERO_RATIO); + row.setRemark(defaultString(planning.getProgressRemark())); + + List specialtyRoleList = roleSplitMap.getOrDefault(planning.getId(), Collections.emptyList()); + for (String roleCode : specialtyPersonRoleOrder()) { + SpecialtyRoleSplitRespVO roleRow = findRoleRow(specialtyRoleList, specialtyCode, roleCode); + if (roleRow == null || roleRow.getPersons() == null) { + continue; + } + BigDecimal roleRatio = ratio(roleRow.getRoleRatio()); + row.getRoleRatioMap().put(roleCode, roleRatio); + for (SpecialtyRolePersonRespVO person : roleRow.getPersons()) { + if (!isValidSpecialtyRolePerson(person)) { + continue; + } + BigDecimal personRatio = ratio(person.getPersonRatio()); + BigDecimal personSpecialtyRatio = multiplyRatio(roleRatio, personRatio); + row.setAdjustedPersonRatio(addRatio(row.getAdjustedPersonRatio(), personSpecialtyRatio)); + String personKey = buildSpecialtyPersonModuleKey(person.getEmployeeId(), person.getEmployeeName()); + row.getAdjustedPersonRatioMap().put(personKey, + addRatio(row.getAdjustedPersonRatioMap().get(personKey), personSpecialtyRatio)); + + String rolePersonKey = buildSpecialtyRolePersonModuleKey(roleCode, person.getEmployeeId(), person.getEmployeeName()); + SpecialtyPersonOutputExcelBuilder.RolePersonRatioValue rolePersonValue = + row.getRolePersonValueMap().computeIfAbsent(rolePersonKey, key -> { + SpecialtyPersonOutputExcelBuilder.RolePersonRatioValue value = + new SpecialtyPersonOutputExcelBuilder.RolePersonRatioValue(); + value.setWorkRatio(ZERO_RATIO); + value.setSpecialtyRatio(ZERO_RATIO); + return value; + }); + rolePersonValue.setWorkRatio(addRatio(rolePersonValue.getWorkRatio(), personRatio)); + rolePersonValue.setSpecialtyRatio(addRatio(rolePersonValue.getSpecialtyRatio(), personSpecialtyRatio)); + + SpecialtyPersonOutputExcelBuilder.PersonAmountValue personAmountValue = + row.getPersonAmountMap().computeIfAbsent(personKey, key -> new SpecialtyPersonOutputExcelBuilder.PersonAmountValue()); + BigDecimal personAmountRatio = multiplyRatio(specialtyBaseRatio, roleRatio, personRatio); + accumulateSpecialtyPersonAmount(personAmountValue, quarterSummary, personAmountRatio); + } + } + rows.add(row); + } + return rows; + } + + private void accumulateSpecialtyPersonAmount(SpecialtyPersonOutputExcelBuilder.PersonAmountValue value, + QuarterSummary quarterSummary, + BigDecimal amountRatio) { + value.setQuarterOneAmountWan(addAmountValue(value.getQuarterOneAmountWan(), + amountToWan(multiplyAmount(quarterSummary.getQuarterOneAmount(), amountRatio)))); + value.setQuarterTwoAmountWan(addAmountValue(value.getQuarterTwoAmountWan(), + amountToWan(multiplyAmount(quarterSummary.getQuarterTwoAmount(), amountRatio)))); + value.setQuarterThreeAmountWan(addAmountValue(value.getQuarterThreeAmountWan(), + amountToWan(multiplyAmount(quarterSummary.getQuarterThreeAmount(), amountRatio)))); + value.setQuarterFourAmountWan(addAmountValue(value.getQuarterFourAmountWan(), + amountToWan(multiplyAmount(quarterSummary.getQuarterFourAmount(), amountRatio)))); + value.setYearTotalAmountWan(addAmountValue(value.getYearTotalAmountWan(), + amountToWan(multiplyAmount(quarterSummary.getYearTotalAmount(), amountRatio)))); + } + + private BigDecimal addAmountValue(BigDecimal left, BigDecimal right) { + return amount(left).add(amount(right)).setScale(2, RoundingMode.HALF_UP); + } + + private boolean isValidSpecialtyRolePerson(SpecialtyRolePersonRespVO person) { + return person != null && person.getEmployeeId() != null && !defaultString(person.getEmployeeName()).isEmpty(); + } + + private List specialtyPersonRoleOrder() { + List roleCodes = new ArrayList<>(); + roleCodes.add(OutputSplitBizConstants.ROLE_DIRECTOR); + roleCodes.add(OutputSplitBizConstants.ROLE_CHECK); + roleCodes.add(OutputSplitBizConstants.ROLE_DESIGN); + roleCodes.add(OutputSplitBizConstants.ROLE_REVIEW); + roleCodes.add(OutputSplitBizConstants.ROLE_APPROVE); + return roleCodes; + } + + private String buildSpecialtyRolePersonModuleKey(String roleCode, Long employeeId, String employeeName) { + return defaultString(roleCode) + ":" + buildSpecialtyPersonModuleKey(employeeId, employeeName); + } + + private String buildSpecialtyPersonModuleKey(Long employeeId, String employeeName) { + if (employeeId != null) { + return String.valueOf(employeeId); + } + return defaultString(employeeName); + } + + private PlanningOfficeContribution buildPlanningOfficeContribution(ProjectPlanningDO planning, + ProjectOutputSplitDO outputSplit, + QuarterSummary quarterSummary, + List roleList, + Set employeeIds) { + PlanningOfficeContribution contribution = new PlanningOfficeContribution(); + contribution.setTotalOutputAmount(ZERO_AMOUNT); + contribution.setHistoricalIssuedAmount(ZERO_AMOUNT); + contribution.setCurrentSettlementAmount(ZERO_AMOUNT); + contribution.setPendingAmount(ZERO_AMOUNT); + if (roleList == null || roleList.isEmpty() || employeeIds == null || employeeIds.isEmpty()) { + return contribution; + } + + BigDecimal totalAssessmentOutput = multiplyAmount(amount(planning.getAssessmentOutputValue()), + defaultDistributionRatio(planning)); + BigDecimal historicalIssuedAmount = multiplyAmount(amount(planning.getAssessmentOutputValue()), + quarterSummary.getHistoricalIssuedRatio()); + BigDecimal pendingAmount = multiplyAmount(amount(planning.getAssessmentOutputValue()), + quarterSummary.getPendingRatio()); + for (SpecialtyRoleSplitRespVO roleRow : roleList) { + if (roleRow == null || roleRow.getPersons() == null || roleRow.getPersons().isEmpty()) { + continue; + } + BigDecimal categoryRatio = getCategoryRatio(outputSplit, roleRow.getSpecialtyCode()); + BigDecimal roleRatio = ratio(roleRow.getRoleRatio()); + for (SpecialtyRolePersonRespVO person : roleRow.getPersons()) { + if (person == null || person.getEmployeeId() == null || !employeeIds.contains(person.getEmployeeId())) { + continue; + } + BigDecimal personRatio = ratio(person.getPersonRatio()); + BigDecimal amountRatio = multiplyRatio(categoryRatio, roleRatio, personRatio); + if (amountRatio.compareTo(ZERO_RATIO) <= 0) { + continue; + } + + contribution.setTotalOutputAmount(addAmountValue(contribution.getTotalOutputAmount(), + multiplyAmount(totalAssessmentOutput, amountRatio))); + contribution.setHistoricalIssuedAmount(addAmountValue(contribution.getHistoricalIssuedAmount(), + multiplyAmount(historicalIssuedAmount, amountRatio))); + contribution.setCurrentSettlementAmount(addAmountValue(contribution.getCurrentSettlementAmount(), + multiplyAmount(quarterSummary.getYearTotalAmount(), amountRatio))); + contribution.setPendingAmount(addAmountValue(contribution.getPendingAmount(), + multiplyAmount(pendingAmount, amountRatio))); + + EmployeeAccumulator accumulator = contribution.getEmployeeAmountMap() + .computeIfAbsent(person.getEmployeeId(), key -> new EmployeeAccumulator()); + accumulator.addQuarterAmount(1, multiplyAmount(quarterSummary.getQuarterOneAmount(), amountRatio)); + accumulator.addQuarterAmount(2, multiplyAmount(quarterSummary.getQuarterTwoAmount(), amountRatio)); + accumulator.addQuarterAmount(3, multiplyAmount(quarterSummary.getQuarterThreeAmount(), amountRatio)); + accumulator.addQuarterAmount(4, multiplyAmount(quarterSummary.getQuarterFourAmount(), amountRatio)); + } + } + return contribution; + } + + private void appendProjectOverviewContribution(ProjectOverviewAccumulator accumulator, + ProjectPlanningDO planning, + ProjectDO project, + PlanningOfficeContribution contribution) { + accumulator.setProjectName(defaultString(accumulator.getProjectName())); + accumulator.setTotalOutputAmount(addAmountValue(accumulator.getTotalOutputAmount(), + contribution.getTotalOutputAmount())); + accumulator.setHistoricalIssuedAmount(addAmountValue(accumulator.getHistoricalIssuedAmount(), + contribution.getHistoricalIssuedAmount())); + accumulator.setCurrentSettlementAmount(addAmountValue(accumulator.getCurrentSettlementAmount(), + contribution.getCurrentSettlementAmount())); + accumulator.setPendingAmount(addAmountValue(accumulator.getPendingAmount(), + contribution.getPendingAmount())); + + String progressText = buildProgressText(project, planning); + if (!progressText.isEmpty()) { + accumulator.getProgressTexts().add(progressText); + } + String workStage = buildProjectOverviewWorkStage(planning); + if (!workStage.isEmpty()) { + accumulator.getWorkStages().add(workStage); + } + + for (Map.Entry entry : contribution.getEmployeeAmountMap().entrySet()) { + EmployeeAccumulator target = accumulator.getEmployeeAmountMap() + .computeIfAbsent(entry.getKey(), key -> new EmployeeAccumulator()); + EmployeeAccumulator source = entry.getValue(); + target.addQuarterAmount(1, source.getQuarterOneAmount()); + target.addQuarterAmount(2, source.getQuarterTwoAmount()); + target.addQuarterAmount(3, source.getQuarterThreeAmount()); + target.addQuarterAmount(4, source.getQuarterFourAmount()); + } + } + + private String buildProjectOverviewWorkStage(ProjectPlanningDO planning) { + String planningContent = defaultString(planning == null ? null : planning.getPlanningContent()); + if (!planningContent.isEmpty()) { + return planningContent; + } + return defaultString(planning == null ? null : planning.getDesignStage()); + } + + private List buildProjectOverviewEmployeeColumns( + List employeeList) { + if (employeeList == null || employeeList.isEmpty()) { + return Collections.emptyList(); + } + List columns = new ArrayList<>(); + for (EmployeeDO employee : employeeList) { + ProjectOverviewOutputExcelBuilder.EmployeeColumn column = + new ProjectOverviewOutputExcelBuilder.EmployeeColumn(); + column.setEmployeeId(employee.getId()); + column.setEmployeeName(defaultString(employee.getEmployeeName())); + columns.add(column); + } + return columns; + } + + private List buildProjectOverviewRows( + List accumulatorList, List employeeList, String sortType) { + if (accumulatorList == null || accumulatorList.isEmpty()) { + return Collections.emptyList(); + } + sortProjectOverviewAccumulators(accumulatorList, sortType); + List rows = new ArrayList<>(); + int serialNo = 1; + for (ProjectOverviewAccumulator accumulator : accumulatorList) { + ProjectOverviewOutputExcelBuilder.ProjectRow row = new ProjectOverviewOutputExcelBuilder.ProjectRow(); + row.setSerialNo(serialNo++); + row.setProjectName(defaultString(accumulator.getProjectName())); + row.setProgressText(String.join(";", accumulator.getProgressTexts())); + row.setWorkStage(String.join(";", accumulator.getWorkStages())); + row.setTotalOutputAmount(accumulator.getTotalOutputAmount()); + row.setHistoricalIssuedRatio(divideAmountRatio( + accumulator.getHistoricalIssuedAmount(), accumulator.getTotalOutputAmount())); + row.setCurrentSettlementRatio(divideAmountRatio( + accumulator.getCurrentSettlementAmount(), accumulator.getTotalOutputAmount())); + row.setCurrentSettlementAmount(accumulator.getCurrentSettlementAmount()); + row.setPendingRatio(divideAmountRatio( + accumulator.getPendingAmount(), accumulator.getTotalOutputAmount())); + row.setEmployeeAmountMap(buildProjectOverviewEmployeeAmountMap(accumulator.getEmployeeAmountMap(), employeeList)); + rows.add(row); + } + return rows; + } + + private Map buildProjectOverviewEmployeeAmountMap( + Map accumulatorMap, List employeeList) { + Map result = new LinkedHashMap<>(); + if (employeeList == null || employeeList.isEmpty()) { + return result; + } + for (EmployeeDO employee : employeeList) { + EmployeeAccumulator accumulator = accumulatorMap == null ? null : accumulatorMap.get(employee.getId()); + if (accumulator == null) { + continue; + } + ProjectOverviewOutputExcelBuilder.EmployeeAmountValue value = + new ProjectOverviewOutputExcelBuilder.EmployeeAmountValue(); + value.setQuarterOneAmount(accumulator.getQuarterOneAmount()); + value.setQuarterTwoAmount(accumulator.getQuarterTwoAmount()); + value.setQuarterThreeAmount(accumulator.getQuarterThreeAmount()); + value.setQuarterFourAmount(accumulator.getQuarterFourAmount()); + value.setAnnualTotalAmount(accumulator.getAnnualTotalAmount()); + result.put(employee.getId(), value); + } + return result; + } + + private ProjectOverviewOutputExcelBuilder.ProjectRow buildProjectOverviewTotalRow( + List accumulatorList, List employeeList) { + ProjectOverviewOutputExcelBuilder.ProjectRow totalRow = new ProjectOverviewOutputExcelBuilder.ProjectRow(); + totalRow.setProjectName("合计"); + totalRow.setTotalOutputAmount(ZERO_AMOUNT); + totalRow.setCurrentSettlementAmount(ZERO_AMOUNT); + BigDecimal historicalIssuedAmount = ZERO_AMOUNT; + BigDecimal pendingAmount = ZERO_AMOUNT; + Map employeeAccumulatorMap = new LinkedHashMap<>(); + if (employeeList != null) { + employeeList.forEach(item -> employeeAccumulatorMap.put(item.getId(), new EmployeeAccumulator())); + } + if (accumulatorList != null) { + for (ProjectOverviewAccumulator accumulatorItem : accumulatorList) { + totalRow.setTotalOutputAmount(addAmountValue( + totalRow.getTotalOutputAmount(), accumulatorItem.getTotalOutputAmount())); + totalRow.setCurrentSettlementAmount(addAmountValue( + totalRow.getCurrentSettlementAmount(), accumulatorItem.getCurrentSettlementAmount())); + historicalIssuedAmount = addAmountValue( + historicalIssuedAmount, accumulatorItem.getHistoricalIssuedAmount()); + pendingAmount = addAmountValue(pendingAmount, accumulatorItem.getPendingAmount()); + for (Map.Entry entry : accumulatorItem.getEmployeeAmountMap().entrySet()) { + EmployeeAccumulator accumulator = employeeAccumulatorMap + .computeIfAbsent(entry.getKey(), key -> new EmployeeAccumulator()); + EmployeeAccumulator value = entry.getValue(); + accumulator.addQuarterAmount(1, value.getQuarterOneAmount()); + accumulator.addQuarterAmount(2, value.getQuarterTwoAmount()); + accumulator.addQuarterAmount(3, value.getQuarterThreeAmount()); + accumulator.addQuarterAmount(4, value.getQuarterFourAmount()); + } + } + } + totalRow.setHistoricalIssuedRatio(divideAmountRatio(historicalIssuedAmount, totalRow.getTotalOutputAmount())); + totalRow.setCurrentSettlementRatio(divideAmountRatio( + totalRow.getCurrentSettlementAmount(), totalRow.getTotalOutputAmount())); + totalRow.setPendingRatio(divideAmountRatio(pendingAmount, totalRow.getTotalOutputAmount())); + totalRow.setEmployeeAmountMap(buildProjectOverviewEmployeeAmountMap(employeeAccumulatorMap, employeeList)); + return totalRow; + } + + private void sortProjectOverviewAccumulators(List rows, String sortType) { + Comparator comparator; + if (Objects.equals(sortType, SORT_OUTPUT_ASC)) { + comparator = Comparator.comparing(ProjectOverviewAccumulator::getTotalOutputAmount, this::compareAmount) + .thenComparing(ProjectOverviewAccumulator::getProjectName, String.CASE_INSENSITIVE_ORDER); + } else if (Objects.equals(sortType, SORT_NAME_ASC)) { + comparator = Comparator.comparing(ProjectOverviewAccumulator::getProjectName, String.CASE_INSENSITIVE_ORDER); + } else { + comparator = Comparator.comparing(ProjectOverviewAccumulator::getTotalOutputAmount, this::compareAmount) + .reversed() + .thenComparing(ProjectOverviewAccumulator::getProjectName, String.CASE_INSENSITIVE_ORDER); + } + rows.sort(comparator); } private String buildProgressText(ProjectDO project, ProjectPlanningDO planning) { @@ -1295,22 +1731,6 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return defaultString(officeMap.get(officeId).getOfficeName()); } - private void sortProjectOverviewRows(List rows, String sortType) { - Comparator comparator; - if (Objects.equals(sortType, SORT_OUTPUT_ASC)) { - comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getTotalOutputAmount, this::compareAmount) - .thenComparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER); - } else if (Objects.equals(sortType, SORT_NAME_ASC)) { - comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER) - .thenComparing(ProjectOverviewExcelRespVO::getPlanningContent, String.CASE_INSENSITIVE_ORDER); - } else { - comparator = Comparator.comparing(ProjectOverviewExcelRespVO::getTotalOutputAmount, this::compareAmount) - .reversed() - .thenComparing(ProjectOverviewExcelRespVO::getProjectName, String.CASE_INSENSITIVE_ORDER); - } - rows.sort(comparator); - } - private void sortEmployeeRows(List rows, String sortType) { Comparator comparator; if (Objects.equals(sortType, SORT_NAME_ASC)) { @@ -1418,6 +1838,28 @@ public class ProjectOutputReportServiceImpl implements ProjectOutputReportServic return value == null ? "" : value.trim(); } + @Data + private static class PlanningOfficeContribution { + private BigDecimal totalOutputAmount = ZERO_AMOUNT; + private BigDecimal historicalIssuedAmount = ZERO_AMOUNT; + private BigDecimal currentSettlementAmount = ZERO_AMOUNT; + private BigDecimal pendingAmount = ZERO_AMOUNT; + private Map employeeAmountMap = new LinkedHashMap<>(); + } + + @Data + private static class ProjectOverviewAccumulator { + private Long projectId; + private String projectName; + private BigDecimal totalOutputAmount = ZERO_AMOUNT; + private BigDecimal historicalIssuedAmount = ZERO_AMOUNT; + private BigDecimal currentSettlementAmount = ZERO_AMOUNT; + private BigDecimal pendingAmount = ZERO_AMOUNT; + private Set progressTexts = new LinkedHashSet<>(); + private Set workStages = new LinkedHashSet<>(); + private Map employeeAmountMap = new LinkedHashMap<>(); + } + @Data private static class QuarterSummary { private BigDecimal historicalIssuedRatio; diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/EmployeeOutputSummaryExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/EmployeeOutputSummaryExcelBuilder.java new file mode 100644 index 0000000..e329e9a --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/EmployeeOutputSummaryExcelBuilder.java @@ -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 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 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 rows = Collections.emptyList(); + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java index 368a35b..989bd71 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectBudgetExcelBuilder.java @@ -22,7 +22,9 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder 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_SHEET_TITLE = "项目考核产值年度/季度预算计取表"; + private static final String QUARTER_BUDGET_REMARK = "注:\n" + + "1、设计阶段考核产值为总考核产值×阶段占比;本年度/季度考核产值为阶段考核产值×本年度/季度发放比例。"; + private static final String DATE_TEXT = "日期: 年 月 日"; public Workbook build(ExportData data) { Workbook workbook = createWorkbook(); @@ -154,7 +156,9 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder 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 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); @@ -165,7 +169,6 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder null, HorizontalAlignment.CENTER, false, (short) 10); int rowIndex = 0; - rowIndex = writeMergedTitleRow(sheet, rowIndex, QUARTER_BUDGET_SHEET_TITLE, titleStyle, 21); rowIndex = buildQuarterBudgetHeader(sheet, rowIndex, data, headerStyle, centeredCellStyle); for (QuarterBudgetRow rowData : data.getQuarterBudgetRows()) { @@ -198,6 +201,26 @@ public class ProjectBudgetExcelBuilder extends AbstractProjectOutputExcelBuilder setText(row, 20, textOrBlank(rowData.getYearTotalAmountWan()), cellStyle); setText(row, 21, safeText(rowData.getRatioRemark()), cellStyle); } + + 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, 6, + DATE_TEXT, infoValueStyle); + setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 7, 13, + DATE_TEXT, infoValueStyle); + setMergedRegionText(sheet, dateRow.getRowNum(), dateRow.getRowNum(), 14, 21, + DATE_TEXT, infoValueStyle); } private int buildQuarterBudgetHeader(Sheet sheet, int rowIndex, ExportData data, diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java index 113ce6b..9fdc881 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectLeadQuarterOutputExcelBuilder.java @@ -2,90 +2,393 @@ 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 OUTPUT_TYPE_ORDER = Arrays.asList( + "六大专业考核产值", + "专业分包产值", + "内部协作产值", + "其他产值" + ); + public Workbook build(ExportData data) { Workbook workbook = createWorkbook(); - Sheet sheet = createSheet(workbook, "项目负责人计取", "项目负责人计取"); - setColumnWidths(sheet, 8, 24, 14, 20, 12, 20, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12); + 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 infoStyle = createInfoStyle(workbook); - CellStyle headerStyle = createHeaderStyle(workbook); - CellStyle cellStyle = createCellStyle(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, "项目负责人年度/季度计取表", titleStyle, 16); + rowIndex = writeMergedTitleRow(sheet, rowIndex, SHEET1_TITLE, titleStyle, 5); + rowIndex = buildSheet1Header(sheet, rowIndex, data, headerStyle); - Row infoRow = sheet.createRow(rowIndex++); - writeLabelValue(infoRow, 0, 1, "项目名称", data.getProjectName(), infoStyle); - writeLabelValue(infoRow, 4, 5, "年度", data.getYear(), infoStyle); - - rowIndex = writeRow(sheet, rowIndex, headerStyle, (Object[]) new String[]{ - "序号", "规划内容", "项目经理人员", "项目经理比例", "项目负责人人员", "项目负责人比例", - "项目经理一季度", "项目经理二季度", "项目经理三季度", "项目经理四季度", "项目经理年度合计", - "项目负责人一季度", "项目负责人二季度", "项目负责人三季度", "项目负责人四季度", - "项目负责人年度合计", "项目经理/项目负责人合计" - }); - - int serialNo = 1; - for (LeadQuarterRow rowData : data.getRows()) { - Row row = sheet.createRow(rowIndex++); - setText(row, 0, serialNo++, cellStyle); - setText(row, 1, rowData.getPlanningContent(), cellStyle); - setText(row, 2, rowData.getProjectManagerNames(), cellStyle); - setText(row, 3, percentText(rowData.getProjectManagerRatio()), cellStyle); - setText(row, 4, rowData.getEngineeringPrincipalNames(), cellStyle); - setText(row, 5, percentText(rowData.getEngineeringPrincipalRatio()), cellStyle); - setText(row, 6, text(rowData.getProjectManagerQuarterOneAmount()), cellStyle); - setText(row, 7, text(rowData.getProjectManagerQuarterTwoAmount()), cellStyle); - setText(row, 8, text(rowData.getProjectManagerQuarterThreeAmount()), cellStyle); - setText(row, 9, text(rowData.getProjectManagerQuarterFourAmount()), cellStyle); - setText(row, 10, text(rowData.getProjectManagerYearTotalAmount()), cellStyle); - setText(row, 11, text(rowData.getEngineeringPrincipalQuarterOneAmount()), cellStyle); - setText(row, 12, text(rowData.getEngineeringPrincipalQuarterTwoAmount()), cellStyle); - setText(row, 13, text(rowData.getEngineeringPrincipalQuarterThreeAmount()), cellStyle); - setText(row, 14, text(rowData.getEngineeringPrincipalQuarterFourAmount()), cellStyle); - setText(row, 15, text(rowData.getEngineeringPrincipalYearTotalAmount()), cellStyle); - setText(row, 16, text(rowData.getTotalAmount()), cellStyle); + List rows = resolveDetailRows(data); + int bodyStartRow = rowIndex; + for (LeadQuarterRow rowData : rows) { + writeSheet1DetailRow(sheet, rowIndex++, data, rowData, cellStyle, leftCellStyle); } - return workbook; + + 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 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 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 resolveDetailRows(ExportData data) { + if (data == null || data.getRows() == null || data.getRows().isEmpty()) { + return new ArrayList<>(); + } + List 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 resolveSheet2Rows(ExportData data) { + List detailRows = resolveDetailRows(data); + if (detailRows.isEmpty()) { + return detailRows; + } + List rows = new ArrayList<>(detailRows); + rows.add(buildSheet2SubtotalRow(detailRows)); + return rows; + } + + private LeadQuarterRow buildSheet2SubtotalRow(List 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 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 rows; } @Data public static class LeadQuarterRow { - private String planningContent; + 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 projectManagerQuarterOneAmount; - private BigDecimal projectManagerQuarterTwoAmount; - private BigDecimal projectManagerQuarterThreeAmount; - private BigDecimal projectManagerQuarterFourAmount; - private BigDecimal projectManagerYearTotalAmount; - private BigDecimal engineeringPrincipalQuarterOneAmount; - private BigDecimal engineeringPrincipalQuarterTwoAmount; - private BigDecimal engineeringPrincipalQuarterThreeAmount; - private BigDecimal engineeringPrincipalQuarterFourAmount; - private BigDecimal engineeringPrincipalYearTotalAmount; - private BigDecimal totalAmount; + 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; } } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectOverviewOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectOverviewOutputExcelBuilder.java new file mode 100644 index 0000000..18035e6 --- /dev/null +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectOverviewOutputExcelBuilder.java @@ -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 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 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 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 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 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 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 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 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 employeeColumns = Collections.emptyList(); + private List 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 employeeAmountMap = new LinkedHashMap<>(); + } + + @Data + public static class EmployeeAmountValue { + private BigDecimal quarterOneAmount; + private BigDecimal quarterTwoAmount; + private BigDecimal quarterThreeAmount; + private BigDecimal quarterFourAmount; + private BigDecimal annualTotalAmount; + } + +} diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java index 3608506..c7e5251 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/ProjectQuarterOutputExcelBuilder.java @@ -11,28 +11,78 @@ 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 SHEET_NAME = "专业间项目考核产值年度季度计取表"; - private static final String SHEET_FALLBACK_NAME = "项目级年度季度计取"; - private static final String SHEET_TITLE = "专业间项目考核产值年度/季度计取表"; + 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 String SUMMARY_PLACEHOLDER = "/"; + 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 OUTPUT_TYPE_ORDER = Arrays.asList( + "六大专业考核产值", + "专业分包产值", + "内部协作产值", + "其他产值" + ); + + private static final List FIXED_OUTPUT_TYPES = Arrays.asList( + "六大专业考核产值", + "专业分包产值", + "内部协作产值" + ); + + private static final List SPECIALTY_NAMES = Arrays.asList( + "建筑", + "精装", + "结构", + "给排水", + "暖通", + "电气", + "数字化设计" + ); + + private static final List SHEET2_SIGNATURE_LABELS = Arrays.asList( + "建筑专业负责人(签名)", + "精装专业负责人(签名)", + "结构专业负责人(签名)", + "给排水专业负责人(签名)", + "暖通专业负责人(签名)", + "电气专业负责人(签名)", + "数字化设计专业负责人(签名)" + ); public Workbook build(ExportData data) { Workbook workbook = createWorkbook(); - Sheet sheet = createSheet(workbook, SHEET_NAME, SHEET_FALLBACK_NAME); + 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, - 12, 12, 12, 12, 12, 12); + 10, 10, 10, 10, 10, 10, 10, + 12, 12, 12, 12, 12, 12, 12); - CellStyle titleStyle = createTitleStyle(workbook); CellStyle baseHeaderStyle = createHeaderStyle(workbook); CellStyle headerStyle = createDerivedStyle(workbook, baseHeaderStyle, IndexedColors.LEMON_CHIFFON, HorizontalAlignment.CENTER, true, (short) 10); @@ -48,41 +98,34 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel null, HorizontalAlignment.LEFT, false, (short) 10); int rowIndex = 0; - rowIndex = writeMergedTitleRow(sheet, rowIndex, SHEET_TITLE, titleStyle, 34); - rowIndex = buildHeader(sheet, rowIndex, data, headerStyle, cellStyle); + rowIndex = buildSheet1Header(sheet, rowIndex, data, headerStyle, cellStyle); - if (data.getRows() != null) { - for (QuarterRow rowData : data.getRows()) { - if (rowData == null) { - continue; - } - if (rowData.isTotalRow()) { - writeTotalRow(sheet, rowIndex++, data, rowData, totalStyle, totalLeftStyle); - continue; - } - writeDetailRow(sheet, rowIndex++, data, rowData, cellStyle, leftCellStyle); + int bodyStartRow = rowIndex; + List 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); } - writeSignatureRow(sheet, rowIndex, data, signatureStyle); - return workbook; + 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 buildHeader(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); + 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); @@ -113,26 +156,23 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel setText(sheet.getRow(firstRow + 2), 21, "四季度", headerStyle); setText(sheet.getRow(firstRow + 2), 22, "本年度总计", headerStyle); - setMergedRegionText(sheet, firstRow, firstRow, 23, 34, "各专业年度 / 季度项目考核产值", headerStyle); - setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 23, 28, "各专业考核产值占比", headerStyle); - setMergedRegionText(sheet, firstRow + 1, firstRow + 1, 29, 34, "各专业考核产值", headerStyle); - setText(sheet.getRow(firstRow + 2), 23, "建筑", headerStyle); - setText(sheet.getRow(firstRow + 2), 24, "精装", headerStyle); - setText(sheet.getRow(firstRow + 2), 25, "结构", headerStyle); - setText(sheet.getRow(firstRow + 2), 26, "给排水", headerStyle); - setText(sheet.getRow(firstRow + 2), 27, "暖通", headerStyle); - setText(sheet.getRow(firstRow + 2), 28, "电气", headerStyle); - setText(sheet.getRow(firstRow + 2), 29, "建筑", headerStyle); - setText(sheet.getRow(firstRow + 2), 30, "精装", headerStyle); - setText(sheet.getRow(firstRow + 2), 31, "结构", headerStyle); - setText(sheet.getRow(firstRow + 2), 32, "给排水", headerStyle); - setText(sheet.getRow(firstRow + 2), 33, "暖通", headerStyle); - setText(sheet.getRow(firstRow + 2), 34, "电气", 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 writeDetailRow(Sheet sheet, int rowIndex, ExportData data, QuarterRow rowData, - CellStyle cellStyle, CellStyle leftCellStyle) { + 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); @@ -157,22 +197,16 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel setText(row, 20, textOrBlank(rowData.getOfficeQuarterThreeAmountWan()), cellStyle); setText(row, 21, textOrBlank(rowData.getOfficeQuarterFourAmountWan()), cellStyle); setText(row, 22, textOrBlank(rowData.getOfficeYearTotalAmountWan()), cellStyle); - setText(row, 23, percentTextOrBlank(rowData.getArchRatio()), cellStyle); - setText(row, 24, percentTextOrBlank(rowData.getDecorRatio()), cellStyle); - setText(row, 25, percentTextOrBlank(rowData.getStructRatio()), cellStyle); - setText(row, 26, percentTextOrBlank(rowData.getWaterRatio()), cellStyle); - setText(row, 27, percentTextOrBlank(rowData.getHvacRatio()), cellStyle); - setText(row, 28, percentTextOrBlank(rowData.getElecRatio()), cellStyle); - setText(row, 29, textOrBlank(rowData.getArchAssessmentOutputWan()), cellStyle); - setText(row, 30, textOrBlank(rowData.getDecorAssessmentOutputWan()), cellStyle); - setText(row, 31, textOrBlank(rowData.getStructAssessmentOutputWan()), cellStyle); - setText(row, 32, textOrBlank(rowData.getWaterAssessmentOutputWan()), cellStyle); - setText(row, 33, textOrBlank(rowData.getHvacAssessmentOutputWan()), cellStyle); - setText(row, 34, textOrBlank(rowData.getElecAssessmentOutputWan()), 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 writeTotalRow(Sheet sheet, int rowIndex, ExportData data, QuarterRow rowData, - CellStyle totalStyle, CellStyle totalLeftStyle) { + 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); @@ -182,44 +216,400 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel setText(row, 6, textOrBlank(rowData.getQuarterThreeAmountWan()), totalStyle); setText(row, 7, textOrBlank(rowData.getQuarterFourAmountWan()), totalStyle); setText(row, 8, textOrBlank(rowData.getYearTotalAmountWan()), totalStyle); - setText(row, 9, percentTextOrBlank(rowData.getProjectLeadRatio()), 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, percentTextOrBlank(rowData.getOfficeRatio()), 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, 23, 28, SUMMARY_PLACEHOLDER, totalLeftStyle); - setText(row, 29, textOrBlank(rowData.getArchAssessmentOutputWan()), totalStyle); - setText(row, 30, textOrBlank(rowData.getDecorAssessmentOutputWan()), totalStyle); - setText(row, 31, textOrBlank(rowData.getStructAssessmentOutputWan()), totalStyle); - setText(row, 32, textOrBlank(rowData.getWaterAssessmentOutputWan()), totalStyle); - setText(row, 33, textOrBlank(rowData.getHvacAssessmentOutputWan()), totalStyle); - setText(row, 34, textOrBlank(rowData.getElecAssessmentOutputWan()), 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 writeSignatureRow(Sheet sheet, int rowIndex, ExportData data, CellStyle signatureStyle) { - Row row = sheet.createRow(rowIndex); - setMergedRegionText(sheet, rowIndex, rowIndex, 0, 16, + 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, 17, 34, + 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 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 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 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 summarizeSheet2SpecialtyBlocks(List rows) { + List result = buildEmptySheet2SpecialtyBlocks(); + for (QuarterRow row : rows) { + if (row == null) { + continue; + } + List 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 buildSheet2SpecialtyBlocks(QuarterRow row) { + if (row != null && row.isPlaceholderRow()) { + return buildBlankSheet2SpecialtyBlocks(); + } + List 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 buildBlankSheet2SpecialtyBlocks() { + List result = new ArrayList<>(); + for (int i = 0; i < SPECIALTY_NAMES.size(); i++) { + result.add(new Sheet2SpecialtyBlock()); + } + return result; + } + + private List buildEmptySheet2SpecialtyBlocks() { + List 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 extractDetailRows(ExportData data) { + if (data == null || data.getRows() == null || data.getRows().isEmpty()) { + return Collections.emptyList(); + } + List 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 buildOutputTypeGroups(ExportData data) { + List detailRows = extractDetailRows(data); + int nextSerialNo = detailRows.stream() + .map(QuarterRow::getSerialNo) + .filter(item -> item != null) + .max(Integer::compareTo) + .orElse(0) + 1; + Map> groupedMap = new LinkedHashMap<>(); + for (QuarterRow row : detailRows) { + groupedMap.computeIfAbsent(safeText(row.getOutputType()), key -> new ArrayList<>()).add(row); + } + + List result = new ArrayList<>(); + for (String outputType : FIXED_OUTPUT_TYPES) { + List 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> 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 ""; @@ -245,6 +635,7 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel public static class QuarterRow { private Integer serialNo; private boolean totalRow; + private boolean placeholderRow; private String outputType; private String designContent; private BigDecimal quarterOneAmountWan; @@ -272,12 +663,29 @@ public class ProjectQuarterOutputExcelBuilder extends AbstractProjectOutputExcel 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 rows; + } + + @Data + private static class Sheet2SpecialtyBlock { + private BigDecimal quarterOneAmountWan; + private BigDecimal quarterTwoAmountWan; + private BigDecimal quarterThreeAmountWan; + private BigDecimal quarterFourAmountWan; + private BigDecimal yearTotalAmountWan; } } diff --git a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java index 88caf9a..8537e9a 100644 --- a/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java +++ b/lyzsys-module-tjt/src/main/java/cn/iocoder/lyzsys/module/tjt/service/report/builder/SpecialtyPersonOutputExcelBuilder.java @@ -1,81 +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 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(); - Sheet sheet = createSheet(workbook, "专业内人员计取", "专业内人员计取"); - setColumnWidths(sheet, 8, 20, 14, 16, 12, 12, 12, 12, 12, 12); + buildSheet1(workbook, data); + buildSheet2(workbook, data); + return workbook; + } - CellStyle titleStyle = createTitleStyle(workbook); - CellStyle infoStyle = createInfoStyle(workbook); - CellStyle headerStyle = createHeaderStyle(workbook); - CellStyle cellStyle = createCellStyle(workbook); + private void buildSheet1(Workbook workbook, ExportData data) { + List roleGroups = buildRoleGroups(data); + List 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 = writeMergedTitleRow(sheet, rowIndex, "专业内人员项目考核产值年度/季度计取表", titleStyle, 9); + rowIndex = buildSheet1Header(sheet, rowIndex, data, roleGroups, adjustedPersonModules, + headerStyle, headerValueStyle); - Row infoRowOne = sheet.createRow(rowIndex++); - writeLabelValue(infoRowOne, 0, 1, "项目名称", data.getProjectName(), infoStyle); - writeLabelValue(infoRowOne, 4, 5, "规划内容", data.getPlanningContent(), infoStyle); - - Row infoRowTwo = sheet.createRow(rowIndex++); - writeLabelValue(infoRowTwo, 0, 1, "专业", data.getSpecialtyName(), infoStyle); - writeLabelValue(infoRowTwo, 4, 5, "年度", data.getYear(), infoStyle); - - rowIndex = writeRow(sheet, rowIndex, headerStyle, (Object[]) new String[]{ - "序号", "角色", "员工姓名", "角色比例", "人员比例", - "一季度", "二季度", "三季度", "四季度", "年度合计" - }); - - int serialNo = 1; - for (PersonQuarterRow rowData : data.getRows()) { - Row row = sheet.createRow(rowIndex++); - setText(row, 0, serialNo++, cellStyle); - setText(row, 1, rowData.getRoleName(), cellStyle); - setText(row, 2, rowData.getEmployeeName(), cellStyle); - setText(row, 3, percentText(rowData.getRoleRatio()), cellStyle); - setText(row, 4, percentText(rowData.getPersonRatio()), cellStyle); - setText(row, 5, text(rowData.getQuarterOneAmount()), cellStyle); - setText(row, 6, text(rowData.getQuarterTwoAmount()), cellStyle); - setText(row, 7, text(rowData.getQuarterThreeAmount()), cellStyle); - setText(row, 8, text(rowData.getQuarterFourAmount()), cellStyle); - setText(row, 9, text(rowData.getYearTotalAmount()), cellStyle); + List rows = resolveDetailRows(data); + int bodyStartRow = rowIndex; + for (SpecialtyPlanningRow rowData : rows) { + writeSheet1DetailRow(sheet, rowIndex++, rowData, roleGroups, adjustedPersonModules, + cellStyle, leftCellStyle); } - return workbook; + mergeSheet1Body(sheet, bodyStartRow, rowIndex - 1, rows, cellStyle); + } + + private void buildSheet2(Workbook workbook, ExportData data) { + List 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 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 roleGroups, + List 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 roleGroups, List 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 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 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 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 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 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 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 buildRoleGroups(ExportData data) { + Map> 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 result = new ArrayList<>(); + for (String roleCode : ROLE_ORDER) { + List 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 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 resolveDetailRows(ExportData data) { + if (data == null || data.getRows() == null) { + return Collections.emptyList(); + } + List 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 resolveSheet2Rows(ExportData data) { + List detailRows = resolveDetailRows(data); + if (detailRows.isEmpty()) { + return detailRows; + } + List rows = new ArrayList<>(detailRows); + rows.add(buildSheet2TotalRow(detailRows)); + return rows; + } + + private SpecialtyPlanningRow buildSheet2TotalRow(List 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 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 roleGroups, List adjustedPersonModules) { + List 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 personModules) { + List 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 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 roleGroups, List 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 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 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 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 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 planningContent; private String specialtyName; + private String projectManagerNames; + private String engineeringPrincipalNames; + private BigDecimal projectManagerRatio; + private BigDecimal engineeringPrincipalRatio; private Integer year; - private List rows; + private String centerSignerLabel; + private String projectSignerLabel; + private List rolePersonModules; + private List personAmountModules; + private List rows; } @Data - public static class PersonQuarterRow { + public static class RolePersonModule { + private String key; + private String roleCode; private String roleName; private String employeeName; - private BigDecimal roleRatio; - private BigDecimal personRatio; - private BigDecimal quarterOneAmount; - private BigDecimal quarterTwoAmount; - private BigDecimal quarterThreeAmount; - private BigDecimal quarterFourAmount; - private BigDecimal yearTotalAmount; + 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 roleRatioMap = new LinkedHashMap<>(); + private Map rolePersonValueMap = new LinkedHashMap<>(); + private Map adjustedPersonRatioMap = new LinkedHashMap<>(); + private Map 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 modules; + } }