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 aa036fa..61e078a 100644 Binary files a/src/main/resources/templates/report/EX01_Financial Status Report.xlsx and b/src/main/resources/templates/report/EX01_Financial Status Report.xlsx differ