From c393a86921a8cfa1e2e01d560c23075fe5a88502 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Thu, 16 May 2024 20:39:53 +0800 Subject: [PATCH] Financial Report Status --- .../projections/FinancialStatusReportInfo.kt | 28 +- .../modules/report/service/ReportService.kt | 320 +++++++++++++++++- .../modules/report/web/ReportController.kt | 7 + .../report/EX01_Financial Status Report.xlsx | Bin 15337 -> 13083 bytes 4 files changed, 332 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt b/src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt index 51275c9..1880077 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt +++ b/src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt @@ -3,18 +3,18 @@ package com.ffii.tsms.modules.data.entity.projections import java.math.BigDecimal import java.time.LocalDate -interface FinancialStatusReportInfo { - val code: String - val description: String - val client: String - val teamLead: String - val planStart: LocalDate - val planEnd: LocalDate - val expectedTotalFee: BigDecimal - val staff: String - val normalConsumed: BigDecimal - val otConsumed: BigDecimal - val hourlyRate: BigDecimal - val sumIssuedAmount: BigDecimal +data class FinancialStatusReportInfo( + val code: String, + val description: String, + val client: String, + val teamLead: String, + val planStart: LocalDate, + val planEnd: LocalDate, + val expectedTotalFee: BigDecimal, + val staff: String, + val normalConsumed: BigDecimal, + val otConsumed: BigDecimal, + val hourlyRate: BigDecimal, + val sumIssuedAmount: BigDecimal, val sumPaidAmount: BigDecimal -} \ No newline at end of file +) \ 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 373e7ac..dad69f7 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 @@ -12,11 +12,13 @@ import org.apache.commons.logging.LogFactory import org.apache.poi.ss.usermodel.* import org.apache.poi.ss.util.CellAddress import org.apache.poi.ss.util.CellRangeAddress +import org.apache.poi.ss.util.CellUtil import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream import java.io.IOException +import java.math.BigDecimal import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.* @@ -40,7 +42,54 @@ open class ReportService ( // ==============================|| GENERATE REPORT ||============================== // fun genFinancialStatusReport(projectId: Long): ByteArray { - val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, projectId) + + val financialStatus: List> = getFinancialStatus(projectId) + + val otFactor = BigDecimal(1) + + val tempList = mutableListOf>() + + for (item in financialStatus){ + val normalConsumed = item.getValue("normalConsumed") as Double + val hourlyRate = item.getValue("hourlyRate") as BigDecimal + println("normalConsumed------------- $normalConsumed") + println("hourlyRate------------- $hourlyRate") + val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) + println("manHourRate------------ $manHourRate") + + val otConsumed = item.getValue("otConsumed") as Double + val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) + + if(!tempList.any{ it.containsValue(item.getValue("code"))}){ + + tempList.add(mapOf( + "code" to item.getValue("code"), + "description" to item.getValue("description"), + "client" to item.getValue("client"), + "teamLead" to item.getValue("teamLead"), + "planStart" to item.getValue("planStart"), + "planEnd" to item.getValue("planEnd"), + "expectedTotalFee" to item.getValue("expectedTotalFee"), + "normalConsumed" to manHourRate, + "otConsumed" to manOtHourRate, + "issuedAmount" to item.getValue("sumIssuedAmount"), + "paidAmount" to item.getValue("sumPaidAmount"), + )) + }else{ + // Find the existing Map in the tempList that has the same "code" value + val existingMap = tempList.find { it.containsValue(item.getValue("code")) }!! + + // Update the existing Map with the new manHourRate and manOtHourRate values + tempList[tempList.indexOf(existingMap)] = existingMap.toMutableMap().apply { + put("normalConsumed", (get("normalConsumed") as BigDecimal).add(manHourRate)) + put("otConsumed", (get("otConsumed") as BigDecimal).add(manOtHourRate)) + } + } + } + + println("tempList---------------------- $tempList") + + val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, tempList) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) @@ -101,12 +150,265 @@ open class ReportService ( // EX01 Financial Report private fun createFinancialStatusReport( templatePath: String, - projectId: Long, + projects: List>, ) : Workbook { + val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) + val accountingStyle = workbook.createDataFormat() .getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + + val sheet = workbook.getSheetAt(0) + + val boldFont = sheet.workbook.createFont() + boldFont.bold = true + + val boldFontCellStyle = workbook.createCellStyle() + boldFontCellStyle.setFont(boldFont) + + + var rowNum = 14 + + for(item in projects){ + val row: Row = sheet.createRow(rowNum++) + + val codeCell = row.createCell(0) + codeCell.setCellValue(if (item["code"] != null) item.getValue("code").toString() else "N/A") + + val descriptionCell = row.createCell(1) + descriptionCell.setCellValue(if (item["description"] != null) item.getValue("description").toString() else "N/A") + + val clientCell = row.createCell(2) + clientCell.setCellValue(if (item["client"] != null) item.getValue("client").toString() else "N/A") + + val teamLeadCell = row.createCell(3) + teamLeadCell.setCellValue(if (item["teamLead"] != null) item.getValue("teamLead").toString() else "N/A") + + val startDateCell = row.createCell(4) + startDateCell.setCellValue(if (item["planStart"] != null) item.getValue("planStart").toString() else "N/A") + + val endDateCell = row.createCell(5) + endDateCell.setCellValue(if (item["planEnd"] != null) item.getValue("planEnd").toString() else "N/A") + + val totalFeeCell = row.createCell(6) + val totalFee = item["expectedTotalFee"]?.let { it as Double } ?: 0.0 + totalFeeCell.apply { + setCellValue(totalFee) + cellStyle.dataFormat = accountingStyle + } + + + val budgetCell = row.createCell(7) + budgetCell.apply { + cellFormula = "G${rowNum} * 80%" + cellStyle.dataFormat = accountingStyle + } + + val cumExpenditureCell = row.createCell(8) + val normalConsumed = item["normalConsumed"]?.let { it as BigDecimal } ?: BigDecimal(0) + val otConsumed = item["otConsumed"]?.let { it as BigDecimal } ?: BigDecimal(0) + val cumExpenditure = normalConsumed.add(otConsumed) + cumExpenditureCell.apply{ + setCellValue(cumExpenditure.toDouble()) + cellStyle.dataFormat = accountingStyle + } + + val budgetVCell = row.createCell(9) + budgetVCell.apply { + cellFormula = "H${rowNum} - I${rowNum}" + cellStyle = boldFontCellStyle + cellStyle.dataFormat = accountingStyle + } + + val issuedCell = row.createCell(10) + val issuedAmount = item["issuedAmount"]?.let { it as BigDecimal } ?: BigDecimal(0) + issuedCell.apply { + setCellValue(issuedAmount.toDouble()) + cellStyle.dataFormat = accountingStyle + } + + val uninvoiceCell = row.createCell(11) + uninvoiceCell.apply { + cellFormula = " IF(H${rowNum}> { val sql = StringBuilder( - "with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" - + "from invoice i" - + "left join project p on p.code = i.projectCode" - + "group by p.code" + "with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount " + + " from invoice i" + + " left join project p on p.code = i.projectCode" + + " group by p.code" + ")" - + " Select p.code, p.description, c.name, t2.name, p.planStart , p.planEnd , p.expectedTotalFee ," - + " s.name , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," - + " cte_i.sumIssuedAmount, cte_i.sumPaidAmount" + + " Select p.code, p.description, c.name as client, t2.name as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," + + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," + + " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount " + " from timesheet t" + " left join project_task pt on pt.id = t.projectTaskId" + " left join project p ON p.id = pt.project_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 3accd6d..976dee2 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 @@ -1,6 +1,7 @@ package com.ffii.tsms.modules.report.web import com.ffii.tsms.modules.data.entity.StaffRepository +import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.report.service.ReportService @@ -110,4 +111,10 @@ class ReportController( .body(ByteArrayResource(reportBytes)) } + @GetMapping("/financialReport/{id}") + fun getFinancialReport(@PathVariable id: Long): List> { + println(excelReportService.genFinancialStatusReport(id)) + return excelReportService.getFinancialStatus(id) + } + } \ No newline at end of file diff --git a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx index aa036fae1ce3b045cf40d5f55595ac9aa6b0ece0..61e078a68ecf158a90247f410a300ef0304465e5 100644 GIT binary patch delta 5414 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

QSKvW^!_a0l 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