Browse Source

PANDL Report

tags/Baseline_30082024_BACKEND_UAT
MSI\2Fi 1 year ago
parent
commit
bfe70fbc8c
4 changed files with 483 additions and 13 deletions
  1. +455
    -7
      src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt
  2. +21
    -6
      src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt
  3. +7
    -0
      src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt
  4. BIN
      src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx

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

@@ -1,18 +1,13 @@
package com.ffii.tsms.modules.report.service package com.ffii.tsms.modules.report.service


import com.ffii.core.support.JdbcDao import com.ffii.core.support.JdbcDao
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.data.entity.Salary import com.ffii.tsms.modules.data.entity.Salary
import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Staff
import com.ffii.tsms.modules.data.entity.Team import com.ffii.tsms.modules.data.entity.Team
import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Invoice
import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.timesheet.entity.Leave
import com.ffii.tsms.modules.timesheet.entity.Timesheet import com.ffii.tsms.modules.timesheet.entity.Timesheet
import com.ffii.tsms.modules.timesheet.entity.projections.MonthlyLeave
import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate
import com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours
import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry
import org.apache.commons.logging.Log import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory import org.apache.commons.logging.LogFactory
import org.apache.poi.ss.usermodel.* import org.apache.poi.ss.usermodel.*
@@ -25,8 +20,8 @@ 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.math.BigDecimal
import java.sql.Time
import java.time.LocalDate import java.time.LocalDate
import java.time.YearMonth
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*


@@ -40,6 +35,7 @@ open class ReportService(
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd")
private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER)


private val PandL_REPORT = "templates/report/AR07_Project P&L Report v02.xlsx"
private val FINANCIAL_STATUS_REPORT = "templates/report/EX01_Financial Status Report.xlsx" private val FINANCIAL_STATUS_REPORT = "templates/report/EX01_Financial Status Report.xlsx"
private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx"
private val MONTHLY_WORK_HOURS_ANALYSIS_REPORT = "templates/report/AR08_Monthly Work Hours Analysis Report.xlsx" private val MONTHLY_WORK_HOURS_ANALYSIS_REPORT = "templates/report/AR08_Monthly Work Hours Analysis Report.xlsx"
@@ -1177,4 +1173,456 @@ open class ReportService(
return jdbcDao.queryForList(sql.toString(), args) return jdbcDao.queryForList(sql.toString(), args)
} }


open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List<Map<String, Any>>{
val sql = StringBuilder(
" with cte_timesheet as ("
+ " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.salaryPoint, s2.hourlyRate, t.staffId,"
+ " t.recordDate"
+ " from timesheet t"
+ " left join project_task pt on pt.id = t.projectTaskId"
+ " left join project p ON p.id = pt.project_id"
+ " left join staff s on s.id = t.staffId"
+ " left join salary s2 on s.salaryId = s2.salaryPoint"
+ " left join team t2 on t2.id = s.teamId"
+ " ),"
+ " 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 as client, concat(t.code, \" - \", t.name) as teamLead,"
+ " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed, DATE_FORMAT(cte_ts.recordDate, '%Y-%m') as recordDate, "
+ " IFNULL(cte_ts.salaryPoint, 0) as salaryPoint, "
+ " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount,"
+ " s.name, s.staffId, g.code as gradeCode, g.name as gradeName, t2.code as teamCode, t2.name as teamName"
+ " from project p"
+ " left join cte_timesheet cte_ts on p.code = cte_ts.code"
+ " left join customer c on c.id = p.customerId"
+ " left join tsmsdb.team t on t.teamLead = p.teamLead"
+ " left join cte_invoice cte_i on cte_i.code = p.code"
+ " left join staff s on s.id = cte_ts.staffId"
+ " left join grade g on g.id = s.gradeId"
+ " left join team t2 on t2.id = s.teamId"
+ " where p.deleted = false"
+ " and (DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') >= :startMonth and DATE_FORMAT(cte_ts.recordDate, \'%Y-%m\') <= :endMonth)"
+ " and p.id = :projectId"
+ " order by g.code, s.name, cte_ts.recordDate"
)

val args = mapOf(
"projectId" to projectId,
"startMonth" to startMonth,
"endMonth" to endMonth,
)

val manHoursSpent = jdbcDao.queryForList(sql.toString(), args)

val projectCodeSql = StringBuilder(
"select p.code, p.description from project p where p.deleted = false and p.id = :projectId"
)

val args2 = mapOf(
"projectId" to projectId,
)

val projectsCode = jdbcDao.queryForMap(projectCodeSql.toString(), args).get()

val otFactor = BigDecimal(1).toDouble()

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

val info: MutableMap<String, Any?> = mutableMapOf(
"reportGenerationDate" to LocalDate.now().toString(),
"startMonth" to startMonth,
"endMonth" to endMonth,
"code" to projectsCode["code"],
"description" to projectsCode["description"],
)

val staffInfoList = mutableListOf<Map<String, Any>>()
println("manHoursSpent------- ${manHoursSpent}")
for(item in manHoursSpent){
if(info["teamLead"] != item.getValue("teamLead")){
info["teamLead"] = item.getValue("teamLead")
}
if(info["client"] != item.getValue("client")){
info["client"] = item.getValue("client")
}
if(info["code"] != item.getValue("code")){
info["code"] = item.getValue("code")
}
if(info["code"] == item["code"] && "paidAmount" !in info){
info["paidAmount"] = item.getValue("sumPaidAmount")
}
if(info["description"] != item.getValue("description")){
info["description"] = item.getValue("description")
}
val hourlyRate = (item.getValue("hourlyRate") as BigDecimal).toDouble()
if(!staffInfoList.any { it["staffId"] == item["staffId"] && it["name"] == item["name"]}){
staffInfoList.add(
mapOf(
"staffId" to item.getValue("staffId"),
"name" to item.getValue("name"),
"team" to "${item.getValue("teamCode")} - ${item.getValue("teamName")}",
"grade" to item.getValue("gradeCode"),
"salaryPoint" to item.getValue("salaryPoint"),
"hourlyRate" to hourlyRate,
"hourlySpent" to mutableListOf(
mapOf(
"recordDate" to item.getValue("recordDate"),
"normalConsumed" to item.getValue("normalConsumed"),
"otConsumed" to item.getValue("otConsumed"),
"totalManHours" to item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double,
"manhourExpenditure" to (hourlyRate * item.getValue("normalConsumed") as Double )
+ (hourlyRate * item.getValue("otConsumed") as Double * otFactor)
)
)
)
)
}else{
val existingMap = staffInfoList.find { it.containsValue(item.getValue("staffId")) }!!
val updatedMap = existingMap.toMutableMap()
val hourlySpentList = updatedMap["hourlySpent"] as MutableList<Map<String, Any>>
val existingRecord = hourlySpentList.find { it["recordDate"] == item.getValue("recordDate") }
if (existingRecord != null) {
val existingIndex = hourlySpentList.indexOf(existingRecord)
val updatedRecord = existingRecord.toMutableMap()
updatedRecord["normalConsumed"] = (updatedRecord["normalConsumed"] as Double) + item.getValue("normalConsumed") as Double
updatedRecord["otConsumed"] = (updatedRecord["otConsumed"] as Double) + item.getValue("otConsumed") as Double
updatedRecord["totalManHours"] = (updatedRecord["totalManHours"] as Double) + item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double
updatedRecord["manhourExpenditure"] = (updatedRecord["manhourExpenditure"] as Double) + (hourlyRate * item.getValue("normalConsumed") as Double) + (hourlyRate * item.getValue("otConsumed") as Double * otFactor)
hourlySpentList[existingIndex] = updatedRecord
} else {
hourlySpentList.add(
mapOf(
"recordDate" to item.getValue("recordDate"),
"normalConsumed" to item.getValue("normalConsumed"),
"otConsumed" to item.getValue("otConsumed"),
"totalManHours" to item.getValue("normalConsumed") as Double + item.getValue("otConsumed") as Double,
"manhourExpenditure" to (hourlyRate * item.getValue("normalConsumed") as Double)
+ (hourlyRate * item.getValue("otConsumed") as Double * otFactor)
)
)
}
updatedMap["hourlySpent"] = hourlySpentList
val updatedIndex = staffInfoList.indexOf(existingMap)
staffInfoList[updatedIndex] = updatedMap
}
}
println("staffInfoList----------------- $staffInfoList")
println("info----------------- $info")

tempList.add(mapOf("info" to info))
tempList.add(mapOf("staffInfoList" to staffInfoList))

println("Only Staff Info List --------------------- ${ tempList.first() { it.containsKey("staffInfoList") }["staffInfoList"]}")
println("tempList----------------- $tempList")

return tempList
}

fun genPandLReport(projectId: Long, startMonth: String, endMonth: String): ByteArray {
val manhoursSpentList = getManhoursSpent(projectId, startMonth, endMonth)

val workbook: Workbook = createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth)

val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
workbook.write(outputStream)
workbook.close()

return outputStream.toByteArray()
}

fun createPandLReportWorkbook(
templatePath: String,
manhoursSpent: List<Map<String, Any>>,
startMonth: String,
endMonth: String
): Workbook{
val resource = ClassPathResource(templatePath)
val templateInputStream = resource.inputStream
val workbook: Workbook = XSSFWorkbook(templateInputStream)

val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)")

val startDate = YearMonth.parse(startMonth, DateTimeFormatter.ofPattern("yyyy-MM"))
val convertStartMonth = YearMonth.of(startDate.year, startDate.month)
val endDate = YearMonth.parse(endMonth, DateTimeFormatter.ofPattern("yyyy-MM"))
val convertEndMonth = YearMonth.of(endDate.year, endDate.month)

val monthRange: MutableList<Map<String, Any>> = ArrayList()
var currentDate = startDate
while (!currentDate.isAfter(endDate)) {
monthRange.add(
mapOf(
"display" to currentDate.month.name.substring(0, 3),
"date" to currentDate
)
)
currentDate = currentDate.plusMonths(1)
}

val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH)

val info:Map<String, Any> = manhoursSpent.first() { it.containsKey("info") }["info"] as Map<String, Any>
val staffInfoList: List<Map<String, Any>> = manhoursSpent.first() { it.containsKey("staffInfoList") }["staffInfoList"] as List<Map<String, Any>>

if (staffInfoList.isEmpty()){
val sheet = workbook.getSheetAt(0)
var rowNum = 0
rowNum = 1
val row1: Row = sheet.getRow(rowNum)
val row1Cell = row1.getCell(1)
row1Cell.setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")).toString())

rowNum = 2
val row2: Row = sheet.getRow(rowNum)
val row2Cell = row2.getCell(1)
row2Cell.setCellValue("${convertStartMonth.format(monthFormat)} - ${convertEndMonth.format(monthFormat)}")

rowNum = 3
val row3: Row = sheet.getRow(rowNum)
val row3Cell = row3.getCell(1)
row3Cell.setCellValue(info.getValue("code").toString())

rowNum = 4
val row4: Row = sheet.getRow(rowNum)
val row4Cell = row4.getCell(1)
row4Cell.setCellValue(info.getValue("description").toString())

rowNum = 5
val row5: Row = sheet.getRow(rowNum)
val row5Cell = row5.getCell(1)
row5Cell.setCellValue("-")

rowNum = 6
val row6: Row = sheet.getRow(rowNum)
val row6Cell = row6.getCell(1)
row6Cell.setCellValue("-")

return workbook
}

val sheet = workbook.getSheetAt(0)
var rowNum = 0
rowNum = 1
val row1: Row = sheet.getRow(rowNum)
val row1Cell = row1.getCell(1)
row1Cell.setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")).toString())

rowNum = 2
val row2: Row = sheet.getRow(rowNum)
val row2Cell = row2.getCell(1)
row2Cell.setCellValue("${convertStartMonth.format(monthFormat)} - ${convertEndMonth.format(monthFormat)}")

rowNum = 3
val row3: Row = sheet.getRow(rowNum)
val row3Cell = row3.getCell(1)
row3Cell.setCellValue(info.getValue("code").toString())

rowNum = 4
val row4: Row = sheet.getRow(rowNum)
val row4Cell = row4.getCell(1)
row4Cell.setCellValue(info.getValue("description").toString())

rowNum = 5
val row5: Row = sheet.getRow(rowNum)
val row5Cell = row5.getCell(1)
row5Cell.setCellValue(info.getValue("teamLead").toString())

rowNum = 6
val row6: Row = sheet.getRow(rowNum)
val row6Cell = row6.getCell(1)
row6Cell.setCellValue(info.getValue("client").toString())

rowNum = 9
val row9: Row = sheet.getRow(rowNum)
val row9Cell = row9.getCell(3)
row9Cell.setCellValue("${convertStartMonth.format(monthFormat)} - ${convertEndMonth.format(monthFormat)}")

rowNum = 10
for(staff in staffInfoList){
// val row: Row = sheet.getRow(rowNum++) ?: sheet.createRow(rowNum++)
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val staffCell = row.getCell(0) ?: row.createCell(0)
staffCell.apply {
setCellValue("${staff.getValue("staffId")} - ${staff.getValue("name")}")
}

val gradeCell = row.getCell(1) ?: row.createCell(1)
gradeCell.apply {
setCellValue("G${staff.getValue("grade")}")
}
CellUtil.setAlignment(gradeCell, HorizontalAlignment.CENTER);

val salaryPointCell = row.getCell(2) ?: row.createCell(2)
salaryPointCell.apply {
setCellValue((staff.getValue("salaryPoint") as Long).toDouble())
}
CellUtil.setAlignment(salaryPointCell, HorizontalAlignment.CENTER);

sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 3, 4))
val hourlyRateCell = row.getCell(3) ?: row.createCell(3)
hourlyRateCell.apply {
setCellValue((staff.getValue("hourlyRate") as Double))
}
CellUtil.setAlignment(hourlyRateCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(hourlyRateCell, VerticalAlignment.CENTER);

sheet.setRowBreak(rowNum++);
}
rowNum += 2
val titleRow = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
sheet.addMergedRegion(CellRangeAddress(rowNum, rowNum, 0, monthRange.size+3))
val titleCell = titleRow.getCell(0) ?: titleRow.createCell(0)
titleCell.apply {
setCellValue("Manhours Spent per Month")
}
CellUtil.setAlignment(titleCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(titleCell, VerticalAlignment.CENTER);


rowNum += 1
val manhoursSpentRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val staffCell = manhoursSpentRow.getCell(0) ?: manhoursSpentRow.createCell(0)
staffCell.apply {
setCellValue("Staff No. and Name")
}
CellUtil.setAlignment(staffCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(staffCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(staffCell,"borderBottom", BorderStyle.THIN)

val teamCell = manhoursSpentRow.getCell(1) ?: manhoursSpentRow.createCell(1)
teamCell.apply {
setCellValue("Team")
}
CellUtil.setAlignment(teamCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(teamCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(teamCell,"borderBottom", BorderStyle.THIN)

for ((column, month) in monthRange.withIndex()){
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val monthCell = row.getCell(2+column) ?: row.createCell(2+column)
monthCell.apply {
setCellValue(month.getValue("display").toString())
}
CellUtil.setAlignment(monthCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(monthCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(monthCell,"borderBottom", BorderStyle.THIN)
}
val monthColumnEnd = monthRange.size
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val manhourCell = row.getCell(2+monthColumnEnd) ?: row.createCell(2+monthColumnEnd)
manhourCell.apply {
setCellValue("Manhour Sub-total")
}
CellUtil.setAlignment(manhourCell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(manhourCell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(manhourCell, CellUtil.WRAP_TEXT, true)
CellUtil.setCellStyleProperty(manhourCell,"borderBottom", BorderStyle.THIN)

val manhourECell = row.getCell(2+monthColumnEnd+1) ?: row.createCell(2+monthColumnEnd+1)
manhourECell.apply {
setCellValue("Manhour Expenditure (HKD)")
}
CellUtil.setAlignment(manhourECell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(manhourECell, VerticalAlignment.CENTER);
CellUtil.setCellStyleProperty(manhourECell, CellUtil.WRAP_TEXT, true)
CellUtil.setCellStyleProperty(manhourECell,"borderBottom", BorderStyle.THIN)
sheet.setColumnWidth(2+monthColumnEnd+1, 20*256)


// Manhour Spent LOOP
println("monthRange--------------- ${monthRange}\n")
rowNum += 1
for(staff in staffInfoList){
val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val staffCell = row.getCell(0) ?: row.createCell(0)
var manhourExpenditure = 0.0
staffCell.apply {
setCellValue("${staff.getValue("staffId")} - ${staff.getValue("name")}")
}

val teamCell = row.getCell(1) ?: row.createCell(1)
teamCell.apply {
setCellValue(staff.getValue("team").toString())
}
CellUtil.setCellStyleProperty(teamCell, CellUtil.WRAP_TEXT, true)

for(i in 0 until monthRange.size){
val cell = row.getCell(i+2) ?: row.createCell(i+2)
cell.setCellValue(0.0)
for(hourlySpent in staff.getValue("hourlySpent") as List<Map<String, Any>>){
cell.apply {
if (hourlySpent.getValue("recordDate") == monthRange[i].getValue("date").toString()) {
setCellValue(hourlySpent.getValue("totalManHours") as Double)
manhourExpenditure += hourlySpent.getValue("manhourExpenditure") as Double
}
cellStyle.dataFormat = accountingStyle
}
}
CellUtil.setAlignment(cell, HorizontalAlignment.CENTER);
CellUtil.setVerticalAlignment(cell, VerticalAlignment.CENTER);
}

val manhourCell = row.getCell(2+monthRange.size) ?: row.createCell(2+monthRange.size)
manhourCell.apply {
cellFormula="SUM(C${rowNum+1}:${getColumnAlphabet(1+monthRange.size)}${rowNum+1})"
cellStyle.dataFormat = accountingStyle
}

val manhourECell = row.getCell(3+monthRange.size) ?: row.createCell(3+monthRange.size)
manhourECell.apply {
setCellValue(manhourExpenditure)
cellStyle.dataFormat = accountingStyle
}
sheet.setRowBreak(rowNum++);
}

rowNum += 3
val totalRevenueRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val totalRevenueTitleCell = totalRevenueRow.getCell(0) ?: totalRevenueRow.createCell(0)
totalRevenueTitleCell.apply {
setCellValue("Total Revenue (HKD)")
}
val totalRevenueCell = totalRevenueRow.getCell(1) ?: totalRevenueRow.createCell(1)
totalRevenueCell.apply {
setCellValue((info.getValue("paidAmount") as BigDecimal).toDouble())
cellStyle.dataFormat = accountingStyle
}

rowNum += 1
val totalManhourERow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val startRow = 15+staffInfoList.size
val lastColumnIndex = getColumnAlphabet(3+monthRange.size)
val totalManhourETitleCell = totalManhourERow.getCell(0) ?: totalManhourERow.createCell(0)
totalManhourETitleCell.apply {
setCellValue("Total Manhour Expenditure (HKD)")
}
val totalManhourECell = totalManhourERow.getCell(1) ?: totalManhourERow.createCell(1)
totalManhourECell.apply {
cellFormula = "SUM(${lastColumnIndex}${startRow}:${lastColumnIndex}${startRow+staffInfoList.size})"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(totalManhourETitleCell,"borderBottom", BorderStyle.THIN)
CellUtil.setCellStyleProperty(totalManhourECell,"borderBottom", BorderStyle.THIN)

rowNum += 1
val pandlRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum)
val panlCellTitle = pandlRow.getCell(0) ?: pandlRow.createCell(0)
panlCellTitle.apply {
setCellValue("Profit / (Loss)")
}
val panlCell = pandlRow.getCell(1) ?: pandlRow.createCell(1)
panlCell.apply {
cellFormula = "B${rowNum-1}-B${rowNum}"
cellStyle.dataFormat = accountingStyle
}
CellUtil.setCellStyleProperty(panlCellTitle,"borderBottom", BorderStyle.DOUBLE)
CellUtil.setCellStyleProperty(panlCell,"borderBottom", BorderStyle.DOUBLE)

return workbook
}

} }

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

@@ -6,10 +6,6 @@ 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
import com.ffii.tsms.modules.project.service.InvoiceService import com.ffii.tsms.modules.project.service.InvoiceService
import com.ffii.tsms.modules.report.web.model.FinancialStatusReportRequest
import com.ffii.tsms.modules.report.web.model.ProjectCashFlowReportRequest
import com.ffii.tsms.modules.report.web.model.StaffMonthlyWorkHourAnalysisReportRequest
import com.ffii.tsms.modules.report.web.model.LateStartReportRequest
import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.project.entity.ProjectRepository
import com.ffii.tsms.modules.timesheet.entity.LeaveRepository import com.ffii.tsms.modules.timesheet.entity.LeaveRepository
import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository
@@ -38,6 +34,7 @@ import com.ffii.tsms.modules.data.entity.CustomerRepository
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Customer
import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.entity.Project
import com.ffii.tsms.modules.report.web.model.*


@RestController @RestController
@RequestMapping("/reports") @RequestMapping("/reports")
@@ -165,11 +162,29 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(ByteArrayResource(reportResult)) .body(ByteArrayResource(reportResult))
} }

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


// For API TESTING
@GetMapping("/manHoursSpent/{id}")
fun getManhoursSpent(@PathVariable id: Long): List<Map<String, Any>> {
return excelReportService.getManhoursSpent(id, "2024-05", "2025-05")
}

// P&L Report
@PostMapping("/projectpandlreport")
@Throws(ServletRequestBindingException::class, IOException::class)
fun getProjectPandLReport(@RequestBody @Valid request: PandLReportRequest): ResponseEntity<Resource> {
println(request)
val reportResult: ByteArray = excelReportService.genPandLReport(request.projectId, request.startMonth, request.endMonth)

return ResponseEntity.ok()
.header("filename", "Project P&L Report - " + LocalDate.now() + ".xlsx")
.body(ByteArrayResource(reportResult))
}

} }

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

@@ -6,6 +6,13 @@ import java.time.YearMonth
data class FinancialStatusReportRequest ( data class FinancialStatusReportRequest (
val teamLeadId: Long val teamLeadId: Long
) )

data class PandLReportRequest (
val projectId: Long,
val startMonth: String,
val endMonth: String,
)

data class ProjectCashFlowReportRequest ( data class ProjectCashFlowReportRequest (
val projectId: Long, val projectId: Long,
val dateType: String, // "Date", "Month" val dateType: String, // "Date", "Month"


BIN
src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx View File


Loading…
Cancel
Save