Browse Source

Financial Report Status

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 year ago
parent
commit
c393a86921
4 changed files with 332 additions and 23 deletions
  1. +14
    -14
      src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt
  2. +311
    -9
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  3. +7
    -0
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  4. BIN
      src/main/resources/templates/report/EX01_Financial Status Report.xlsx

+ 14
- 14
src/main/java/com/ffii/tsms/modules/data/entity/projections/FinancialStatusReportInfo.kt View File

@@ -3,18 +3,18 @@ package com.ffii.tsms.modules.data.entity.projections
import java.math.BigDecimal import java.math.BigDecimal
import java.time.LocalDate 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 val sumPaidAmount: BigDecimal
}
)

+ 311
- 9
src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt View File

@@ -12,11 +12,13 @@ import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.* import org.apache.poi.ss.usermodel.*
import org.apache.poi.ss.util.CellAddress import org.apache.poi.ss.util.CellAddress
import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.CellRangeAddress
import org.apache.poi.ss.util.CellUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.math.BigDecimal
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
@@ -40,7 +42,54 @@ open class ReportService (
// ==============================|| GENERATE REPORT ||============================== // // ==============================|| GENERATE REPORT ||============================== //


fun genFinancialStatusReport(projectId: Long): ByteArray { fun genFinancialStatusReport(projectId: Long): ByteArray {
val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, projectId)

val financialStatus: List<Map<String, Any>> = getFinancialStatus(projectId)

val otFactor = BigDecimal(1)

val tempList = mutableListOf<Map<String, Any>>()

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() val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream) workbook.write(outputStream)
@@ -101,12 +150,265 @@ open class ReportService (
// EX01 Financial Report // EX01 Financial Report
private fun createFinancialStatusReport( private fun createFinancialStatusReport(
templatePath: String, templatePath: String,
projectId: Long,
projects: List<Map<String, Any>>,
) : Workbook { ) : Workbook {

val resource = ClassPathResource(templatePath) val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream) 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}<I${rowNum},H${rowNum}-K${rowNum},I${rowNum}-K${rowNum}) "
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}

val cpiCell = row.createCell(12)
cpiCell.apply {
cellFormula = "K${rowNum}/I${rowNum}"
}

val receivedAmountCell = row.createCell(13)
val paidAmount = item["paidAmount"]?.let { it as BigDecimal } ?: BigDecimal(0)
receivedAmountCell.apply{
setCellValue(paidAmount.toDouble())
cellStyle.dataFormat = accountingStyle
}


val unsettledAmountCell = row.createCell(14)
unsettledAmountCell.apply {
cellFormula = "K${rowNum}-N${rowNum}"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}

}
val lastRowNum = rowNum + 1

val row: Row = sheet.createRow(rowNum)
for(i in 0..4){
val cell = row.createCell(i)
CellUtil.setCellStyleProperty(cell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(cell,"borderBottom", BorderStyle.DOUBLE)
}

val subTotalCell = row.createCell(5)
subTotalCell.apply {
setCellValue("Sub-total:")
}
CellUtil.setCellStyleProperty(subTotalCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(subTotalCell,"borderBottom", BorderStyle.DOUBLE)


val sumTotalFeeCell = row.createCell(6)
sumTotalFeeCell.apply {
cellFormula = "SUM(G15:G${rowNum})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumTotalFeeCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumTotalFeeCell,"borderBottom", BorderStyle.DOUBLE)

val sumBudgetCell = row.createCell(7)
sumBudgetCell.apply {
cellFormula = "SUM(H15:H${rowNum})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumBudgetCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumBudgetCell,"borderBottom", BorderStyle.DOUBLE)

val sumCumExpenditureCell = row.createCell(8)
sumCumExpenditureCell.apply {
cellFormula = "SUM(I15:I${rowNum})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumCumExpenditureCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumCumExpenditureCell,"borderBottom", BorderStyle.DOUBLE)

val sumBudgetVCell = row.createCell(9)
sumBudgetVCell.apply {
cellFormula = "SUM(J15:J${rowNum})"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumBudgetVCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumBudgetVCell,"borderBottom", BorderStyle.DOUBLE)

val sumIInvoiceCell = row.createCell(10)
sumIInvoiceCell.apply {
cellFormula = "SUM(K15:K${rowNum})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumIInvoiceCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumIInvoiceCell,"borderBottom", BorderStyle.DOUBLE)

val sumUInvoiceCell = row.createCell(11)
sumUInvoiceCell.apply {
cellFormula = "SUM(L15:L${rowNum})"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumUInvoiceCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumUInvoiceCell,"borderBottom", BorderStyle.DOUBLE)

val lastCpiCell = row.createCell(12)
CellUtil.setCellStyleProperty(lastCpiCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(lastCpiCell,"borderBottom", BorderStyle.DOUBLE)

val sumRAmountCell = row.createCell(13)
sumRAmountCell.apply {
cellFormula = "SUM(N15:N${rowNum})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumRAmountCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumRAmountCell,"borderBottom", BorderStyle.DOUBLE)

val sumUnSettleCell = row.createCell(14)
sumUnSettleCell.apply {
cellFormula = "SUM(O15:O${rowNum})"
cellStyle = boldFontCellStyle
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN)
CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE)

rowNum = 1
val row1: Row = sheet.getRow(rowNum)
val genDateCell = row1.createCell(2)
genDateCell.setCellValue(LocalDate.now().toString())

rowNum = 2

rowNum = 4

rowNum = 5
val row5: Row = sheet.getRow(rowNum)
val cell1 = row5.createCell(2)
cell1.apply {
cellFormula = "H${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 6
val row6: Row = sheet.getRow(rowNum)
val cell2 = row6.createCell(2)
cell2.apply {
cellFormula = "I${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 7
val row7: Row = sheet.getRow(rowNum)
val cell3 = row7.createCell(2)
cell3.apply {
cellFormula = "J${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 8
val row8: Row = sheet.getRow(rowNum)
val cell4 = row8.createCell(2)
cell4.apply {
cellFormula = "K${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 9
val row9: Row = sheet.getRow(rowNum)
val cell5 = row9.createCell(2)
cell5.apply {
cellFormula = "L${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 10
val row10: Row = sheet.getRow(rowNum)
val cell6 = row10.createCell(2)
cell6.apply {
cellFormula = "N${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

rowNum = 11
val row11: Row = sheet.getRow(rowNum)
val cell7 = row11.createCell(2)
cell7.apply {
cellFormula = "O${lastRowNum}"
cellStyle.dataFormat = accountingStyle
}

return workbook return workbook
} }


@@ -658,14 +960,14 @@ open class ReportService (


open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> { open fun getFinancialStatus(projectId: Long?): List<Map<String, Any>> {
val sql = StringBuilder( 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" + " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId" + " left join project_task pt on pt.id = t.projectTaskId"
+ " left join project p ON p.id = pt.project_id" + " left join project p ON p.id = pt.project_id"


+ 7
- 0
src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt View File

@@ -1,6 +1,7 @@
package com.ffii.tsms.modules.report.web package com.ffii.tsms.modules.report.web


import com.ffii.tsms.modules.data.entity.StaffRepository 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.data.entity.projections.StaffSearchInfo
import com.ffii.tsms.modules.project.entity.* import com.ffii.tsms.modules.project.entity.*
import com.ffii.tsms.modules.report.service.ReportService import com.ffii.tsms.modules.report.service.ReportService
@@ -110,4 +111,10 @@ class ReportController(
.body(ByteArrayResource(reportBytes)) .body(ByteArrayResource(reportBytes))
} }


@GetMapping("/financialReport/{id}")
fun getFinancialReport(@PathVariable id: Long): List<Map<String, Any>> {
println(excelReportService.genFinancialStatusReport(id))
return excelReportService.getFinancialStatus(id)
}

} }

BIN
src/main/resources/templates/report/EX01_Financial Status Report.xlsx View File


Loading…
Cancel
Save