From f82cb5f3ae08b157989d31fbff57cdc1cabd7024 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 11 Jul 2024 16:32:02 +0800 Subject: [PATCH] update "export financial status by proejct excel" --- .../modules/data/service/DashboardService.kt | 237 ++++++++++++++++-- .../modules/data/web/DashboardController.kt | 9 + .../web/models/ExportDashboardExcelRequest.kt | 15 ++ .../modules/report/service/ReportService.kt | 20 +- ...shboard] Financial Summary for client.xlsx | Bin 13316 -> 12751 bytes 5 files changed, 247 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index 388d397..5a03276 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -7,15 +7,13 @@ import com.ffii.tsms.modules.data.entity.CustomerType import com.ffii.tsms.modules.data.entity.CustomerRepository import com.ffii.tsms.modules.data.entity.CustomerTypeRepository import com.ffii.tsms.modules.data.web.models.FinancialSummaryByClient +import com.ffii.tsms.modules.data.web.models.FinancialSummaryByProject import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest import com.ffii.tsms.modules.timesheet.entity.Timesheet -import org.apache.poi.ss.usermodel.BorderStyle -import org.apache.poi.ss.usermodel.HorizontalAlignment -import org.apache.poi.ss.usermodel.Sheet -import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.ss.usermodel.* import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.CellUtil import org.apache.poi.xssf.usermodel.XSSFWorkbook @@ -42,7 +40,7 @@ open class DashboardService( private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) private val FINANCIAL_SUMMARY_FOR_CLIENT = "templates/report/[Dashboard] Financial Summary for client.xlsx" - private val FINANCIAL_SUMMARY_FOR_PROJET = "templates/report/[Dashboard] Financial Summary for project.xlsx" + private val FINANCIAL_SUMMARY_FOR_PROJECT = "templates/report/[Dashboard] Financial Summary for project.xlsx" fun CustomerSubsidiary(args: Map): List> { val sql = StringBuilder("select" @@ -2163,9 +2161,9 @@ open class DashboardService( fontName = "Times New Roman" } - val normalFontStyle = workbook.createCellStyle().apply { - setFont(normalFont) - } +// val normalFontStyle = workbook.createCellStyle().apply { +// setFont(normalFont) +// } var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field var columnIndex = 1 @@ -2178,19 +2176,25 @@ open class DashboardService( sheet.createRow(rowIndex++).apply { createCell(0).apply { setCellValue(financialSummaryByClient.customerCode) - cellStyle = normalFontStyle + cellStyle.apply { + setFont(normalFont) + } CellUtil.setAlignment(this, HorizontalAlignment.LEFT) } createCell(1).apply { setCellValue(financialSummaryByClient.customerName) - cellStyle = normalFontStyle + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } CellUtil.setAlignment(this, HorizontalAlignment.LEFT) } createCell(2).apply { setCellValue(financialSummaryByClient.projectNo) - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.LEFT) @@ -2198,13 +2202,16 @@ open class DashboardService( createCell(3).apply { cellFormula = "IF(E${rowIndex}>=1,\"Positive\",\"Negative\")" - cellStyle = normalFontStyle + cellStyle.apply { + setFont(normalFont) + } CellUtil.setAlignment(this, HorizontalAlignment.CENTER) } createCell(4).apply { - cellFormula = "IFERROR(IF(K${rowIndex}=1, 0, K${rowIndex}/J${rowIndex}),0)" - cellStyle = normalFontStyle.apply { + cellFormula = "IFERROR(IF(K${rowIndex}=0, 0, K${rowIndex}/J${rowIndex}),0)" + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2212,13 +2219,16 @@ open class DashboardService( createCell(5).apply { cellFormula = "IF(G${rowIndex}>=1,\"Positive\",\"Negative\")" - cellStyle = normalFontStyle + cellStyle.apply { + setFont(normalFont) + } CellUtil.setAlignment(this, HorizontalAlignment.CENTER) } createCell(6).apply { cellFormula = "IFERROR(H${rowIndex}/J${rowIndex},0)" - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2226,7 +2236,8 @@ open class DashboardService( createCell(7).apply { setCellValue(financialSummaryByClient.totalFee) - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2234,7 +2245,8 @@ open class DashboardService( createCell(8).apply { cellFormula = "H${rowIndex}*80%" - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2242,7 +2254,8 @@ open class DashboardService( createCell(9).apply { setCellValue(financialSummaryByClient.cumulativeExpenditure) - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2250,7 +2263,8 @@ open class DashboardService( createCell(10).apply { setCellValue(financialSummaryByClient.totalInvoiced) - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2258,7 +2272,8 @@ open class DashboardService( createCell(11).apply { cellFormula = "IF(H${rowIndex}-K${rowIndex}<0,0,H${rowIndex}-K${rowIndex})" - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) @@ -2266,7 +2281,185 @@ open class DashboardService( createCell(12).apply { setCellValue(financialSummaryByClient.totalReceived) - cellStyle = normalFontStyle.apply { + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + } + } + + return workbook + } + + @Throws(IOException::class) + fun exportFinancialSummaryByProjectExcel( + financialSummaryByProjects: List, + ): ByteArray { + // Generate the Excel report with query results + val workbook: Workbook = + createFinancialSummaryByProjectExcel(financialSummaryByProjects, FINANCIAL_SUMMARY_FOR_PROJECT) + + // Write the workbook to a ByteArrayOutputStream + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } + + @Throws(IOException::class) + private fun createFinancialSummaryByProjectExcel( + financialSummaryByProjects: List, + templatePath: String, + ): Workbook { + // please create a new function for each report template + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val sheet: Sheet = workbook.getSheetAt(0) + + // accounting style + comma style + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + + // normal font + val normalFont = workbook.createFont().apply { + fontName = "Times New Roman" + } + +// val normalFontStyle = workbook.createCellStyle().apply { +// setFont(normalFont) +// } + + var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + var columnIndex = 1 + sheet.getRow(rowIndex).createCell(columnIndex).apply { + setCellValue(FORMATTED_TODAY) + } + + rowIndex = 4 + financialSummaryByProjects.forEach { financialSummaryByProject: FinancialSummaryByProject -> + sheet.createRow(rowIndex++).apply { + createCell(0).apply { + setCellValue(financialSummaryByProject.projectCode) + cellStyle.apply { + setFont(normalFont) + } + CellUtil.setAlignment(this, HorizontalAlignment.LEFT) + } + + createCell(1).apply { + setCellValue(financialSummaryByProject.projectName) + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.LEFT) + } + + createCell(2).apply { + setCellValue(financialSummaryByProject.customerName) + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.LEFT) + } + + createCell(3).apply { + setCellValue(financialSummaryByProject.subsidiaryName ?: "N/A") + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.LEFT) + } + + createCell(4).apply { + cellFormula = "IF(F${rowIndex}>=1,\"Positive\",\"Negative\")" + cellStyle.apply { + setFont(normalFont) + } + CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + } + + createCell(5).apply { + cellFormula = "IFERROR(IF(L${rowIndex}=0, 0, K${rowIndex}/J${rowIndex}),0)" + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(6).apply { + cellFormula = "IF(H${rowIndex}>=1,\"Positive\",\"Negative\")" + cellStyle.apply { + setFont(normalFont) + } + CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + } + + createCell(7).apply { + cellFormula = "IFERROR(I${rowIndex}/K${rowIndex},0)" + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(8).apply { + setCellValue(financialSummaryByProject.totalFee) + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(9).apply { + cellFormula = "I${rowIndex}*80%" + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(10).apply { + setCellValue(financialSummaryByProject.cumulativeExpenditure) + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(11).apply { + setCellValue(financialSummaryByProject.totalInvoiced) + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(12).apply { + cellFormula = "IF(I${rowIndex}-L${rowIndex}<0,0,I${rowIndex}-L${rowIndex})" + cellStyle.apply { + setFont(normalFont) + dataFormat = accountingStyle + } + CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) + } + + createCell(13).apply { + setCellValue(financialSummaryByProject.totalReceived) + cellStyle.apply { + setFont(normalFont) dataFormat = accountingStyle } CellUtil.setAlignment(this, HorizontalAlignment.RIGHT) diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index 615af9f..e8a4d66 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -17,6 +17,7 @@ import com.ffii.core.response.RecordsRes import com.ffii.core.utils.CriteriaArgsBuilder import com.ffii.tsms.modules.data.service.* import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByClientExcelRequest +import com.ffii.tsms.modules.data.web.models.ExportFinancialSummaryByProjectExcelRequest import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource import org.springframework.http.ResponseEntity @@ -450,4 +451,12 @@ class DashboardController( .header("filename", "Financial Summary for Client - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) } + + @PostMapping("/exportFinancialSummaryByProjectExcel") + fun exportFinancialSummaryByProjectExcel(@Valid @RequestBody request: ExportFinancialSummaryByProjectExcelRequest): ResponseEntity { + val reportResult: ByteArray = dashboardService.exportFinancialSummaryByProjectExcel(request.financialSummaryByProjects) + return ResponseEntity.ok() + .header("filename", "Financial Summary for Project - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt b/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt index 54b71b6..bc45465 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/models/ExportDashboardExcelRequest.kt @@ -12,4 +12,19 @@ data class FinancialSummaryByClient ( data class ExportFinancialSummaryByClientExcelRequest ( val financialSummaryByClients: List +) + +data class FinancialSummaryByProject ( + val projectCode: String, + val projectName: String, + val customerName: String, + val subsidiaryName: String?, + val totalFee: Double, + val cumulativeExpenditure: Double, + val totalInvoiced: Double, + val totalReceived: Double, +) + +data class ExportFinancialSummaryByProjectExcelRequest ( + val financialSummaryByProjects: List ) \ No newline at end of file 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 86a957c..a9a6b25 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 @@ -2985,25 +2985,22 @@ open class ReportService( grade.id )]?.sumOf { it.getValue("salary") } ?: 0.0 - cellStyle = normalFontWithBorderStyle.apply { - dataFormat = accountingStyle - } + cellStyle = normalFontWithBorderStyle + cellStyle.dataFormat = accountingStyle } } createCell(columnIndex++).apply { val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" - cellStyle = boldFontWithBorderStyle.apply { - dataFormat = accountingStyle - } + cellStyle = boldFontWithBorderStyle + cellStyle.dataFormat = accountingStyle } createCell(columnIndex).apply { setCellValue(totalSalary) - cellStyle = boldFontWithBorderStyle.apply { - dataFormat = accountingStyle - } + cellStyle = boldFontWithBorderStyle + cellStyle.dataFormat = accountingStyle } } } @@ -3021,9 +3018,8 @@ open class ReportService( createCell(columnIndex++).apply { val currentCellLetter = CellReference.convertNumToColString(this.columnIndex) cellFormula = "sum(${currentCellLetter}${startRow}:${currentCellLetter}${endRow})" - cellStyle = normalFontWithBorderStyle.apply { - dataFormat = accountingStyle - } + cellStyle = normalFontWithBorderStyle + cellStyle.dataFormat = accountingStyle } } diff --git a/src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx b/src/main/resources/templates/report/[Dashboard] Financial Summary for client.xlsx index d2674ba87a9b2d7b9defc7472b31d80e9f0c9ce7..7eb2f65ede65aeb9b01635824c18df75206c4f0d 100644 GIT binary patch delta 5563 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