From 6f3de950aebcff2c713e1b6e75232e1e40767c37 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 30 May 2024 11:40:30 +0800 Subject: [PATCH] update report --- .../modules/report/service/ReportService.kt | 76 ++++++++++++------ .../modules/report/web/ReportController.kt | 2 +- .../modules/report/web/model/ReportRequest.kt | 4 +- .../report/AR02_Delay Report v02.xlsx | Bin 12388 -> 12440 bytes .../report/EX02_Project Cash Flow Report.xlsx | Bin 13291 -> 13335 bytes 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 115ccae..621ae19 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -178,8 +178,8 @@ open class ReportService( searchedClient: String, projects: List, timesheets: List, - numberOfDays: Int, - projectCompletion: Int, + daysUntilCurrentStageEnd: Int, + resourceUtilizationPercentage: Int, ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = createProjectPotentialDelayReport( @@ -187,8 +187,8 @@ open class ReportService( searchedClient, projects, timesheets, - numberOfDays, - projectCompletion, + daysUntilCurrentStageEnd, + resourceUtilizationPercentage, PROJECT_POTENTIAL_DELAY_REPORT ) @@ -673,15 +673,30 @@ open class ReportService( rowIndex = 4 sheet.getRow(rowIndex).createCell(columnIndex).apply { - setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) + setCellValue( + if (project.customer?.code != null && project.customer?.name != null) project.customer!!.code + " - " + project.customer!!.name + else if (project.customer?.code != null) project.customer!!.code + else if (project.customer?.name != null) project.customer!!.name + else "N/A" + ) } rowIndex = 5 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue( + if (project.customerSubsidiary?.code != null && project.customerSubsidiary?.name != null) project.customerSubsidiary!!.code + " - " + project.customerSubsidiary!!.name + else if (project.customerSubsidiary?.code != null) project.customerSubsidiary!!.code + else if (project.customerSubsidiary?.name != null) project.customerSubsidiary!!.name + else "N/A" + ) + } + + rowIndex = 6 sheet.getRow(rowIndex).createCell(columnIndex).apply { setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) } - rowIndex = 9 + rowIndex = 10 sheet.getRow(rowIndex).apply { createCell(1).apply { setCellValue(project.expectedTotalFee!! * 0.8) @@ -694,7 +709,7 @@ open class ReportService( } } - rowIndex = 10 + rowIndex = 11 val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } val actualExpenditure = timesheets.sumOf { timesheet -> timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed @@ -712,21 +727,20 @@ open class ReportService( } } - rowIndex = 11 + rowIndex = 12 sheet.getRow(rowIndex).apply { createCell(1).apply { - cellFormula = "B10-B11" + cellFormula = "B11-B12" cellStyle.dataFormat = accountingStyle } createCell(2).apply { - cellFormula = "C10-C11" + cellFormula = "C11-C12" cellStyle.dataFormat = accountingStyle } } - rowIndex = 15 - + rowIndex = 16 val dateFormatter = if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") @@ -796,7 +810,7 @@ open class ReportService( createCell(3).apply { val lastRow = rowIndex - 1 - if (lastRow == 15) { + if (lastRow == 16) { cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) } else { @@ -835,7 +849,7 @@ open class ReportService( createCell(3).apply { val lastRow = rowIndex - 1 - if (lastRow == 15) { + if (lastRow == 16) { cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) } else { cellFormula = @@ -863,8 +877,8 @@ open class ReportService( searchedClient: String, projects: List, timesheets: List, - numberOfDays: Int, - projectCompletion: Int, + daysUntilCurrentStageEnd: Int, + resourceUtilizationPercentage: Int, templatePath: String, ): Workbook { // please create a new function for each report template @@ -939,15 +953,29 @@ open class ReportService( createCell(4).apply { val currentClient = project.customer - val currentSubsidiary = project.customerSubsidiary - setCellValue(if (currentSubsidiary != null) currentSubsidiary.code + " - " + currentSubsidiary.name else currentClient?.code + " - " + currentClient?.name) + setCellValue( + if (currentClient?.code != null && currentClient.name != null) currentClient.code + " - " + currentClient.name + else if (currentClient?.code != null) currentClient.code + else if (currentClient?.name != null) currentClient.name + else "N/A" + ) } createCell(5).apply { - setCellValue(project.actualStart?.format(DATE_FORMATTER)) + val currentSubsidiary = project.customerSubsidiary + setCellValue( + if (currentSubsidiary?.code != null && currentSubsidiary.name != null) currentSubsidiary.code + " - " + currentSubsidiary.name + else if (currentSubsidiary?.code != null) currentSubsidiary.code + else if (currentSubsidiary?.name != null) currentSubsidiary.name + else "N/A" + ) } createCell(6).apply { + setCellValue(project.actualStart?.format(DATE_FORMATTER)) + } + + createCell(7).apply { setCellValue(project.planEnd?.format(DATE_FORMATTER)) } } @@ -958,22 +986,22 @@ open class ReportService( val manHoursSpent = groupedTimesheets[Pair(project.id, milestone.id)]?.sum() ?: 0.0 val resourceUtilization = manHoursSpent / (milestone.stagePercentAllocation!! / 100 * project.totalManhour!!) // logger.info(project.name + " : " + milestone.taskGroup?.name + " : " + ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate)) -// logger.info(numberOfDays) - if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= numberOfDays.toLong() && resourceUtilization <= projectCompletion.toDouble() / 100.0) { +// logger.info(daysUntilCurrentStageEnd) + if (ChronoUnit.DAYS.between(LocalDate.now(), milestone.endDate) <= daysUntilCurrentStageEnd.toLong() && resourceUtilization <= resourceUtilizationPercentage.toDouble() / 100.0) { milestoneCount++ val tempRow = sheet.getRow(rowIndex) ?: sheet.createRow(rowIndex) rowIndex++ tempRow.apply { - createCell(7).apply { + createCell(8).apply { setCellValue(milestone.taskGroup?.name ?: "N/A") } - createCell(8).apply { + createCell(9).apply { setCellValue(milestone.endDate?.format(DATE_FORMATTER) ?: "N/A") } - createCell(9).apply { + createCell(10).apply { cellStyle.dataFormat = workbook.createDataFormat().getFormat("0.00%") // if (groupedTimesheets.containsKey(Pair(project.id, milestone.id))) { diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 44dc0d4..aafd24e 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -119,7 +119,7 @@ class ReportController( val projectTasks = projectTaskRepository.findAllByProjectIn(projects) val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) - val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.numberOfDays, request.projectCompletion) + val reportResult: ByteArray = excelReportService.generateProjectPotentialDelayReport(searchedTeam, searchedClient, projects, timesheets, request.daysUntilCurrentStageEnd, request.resourceUtilizationPercentage) return ResponseEntity.ok() .header("filename", "Project Potential Delay Report - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 8d3abb4..e906a38 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -27,8 +27,8 @@ data class ProjectCashFlowReportRequest ( data class ProjectPotentialDelayReportRequest ( val teamId: String, val clientId: String, - val numberOfDays: Int, - val projectCompletion: Int, + val daysUntilCurrentStageEnd: Int, + val resourceUtilizationPercentage: Int, val type: String, ) diff --git a/src/main/resources/templates/report/AR02_Delay Report v02.xlsx b/src/main/resources/templates/report/AR02_Delay Report v02.xlsx index d07a414637b4051f5a1c9282ddbdbf8bf4b085d5..1a4348a228d5f2ab2b12887afc85ae8b525e5292 100644 GIT binary patch delta 2821 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 delta 2691 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 m4lr%e|26dQR=E5nocw