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 666e109..53f6d3d 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 @@ -1,18 +1,13 @@ package com.ffii.tsms.modules.report.service 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.Staff 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.Project -import com.ffii.tsms.modules.timesheet.entity.Leave 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.LogFactory import org.apache.poi.ss.usermodel.* @@ -25,8 +20,8 @@ import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream import java.io.IOException import java.math.BigDecimal -import java.sql.Time import java.time.LocalDate +import java.time.YearMonth import java.time.format.DateTimeFormatter import java.util.* @@ -40,6 +35,7 @@ open class ReportService( private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") 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 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" @@ -1177,4 +1173,456 @@ open class ReportService( return jdbcDao.queryForList(sql.toString(), args) } + open fun getManhoursSpent(projectId: Long, startMonth: String, endMonth: String): List>{ + 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>() + + val info: MutableMap = mutableMapOf( + "reportGenerationDate" to LocalDate.now().toString(), + "startMonth" to startMonth, + "endMonth" to endMonth, + "code" to projectsCode["code"], + "description" to projectsCode["description"], + ) + + val staffInfoList = mutableListOf>() + 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> + 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>, + 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> = 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 = manhoursSpent.first() { it.containsKey("info") }["info"] as Map + val staffInfoList: List> = manhoursSpent.first() { it.containsKey("staffInfoList") }["staffInfoList"] as List> + + 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>){ + 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 + } + } 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 11f16de..df0dc8e 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 @@ -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.report.service.ReportService 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.timesheet.entity.LeaveRepository 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 com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.project.entity.Project +import com.ffii.tsms.modules.report.web.model.* @RestController @RequestMapping("/reports") @@ -165,11 +162,29 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest) .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) .body(ByteArrayResource(reportResult)) } - + + // For API TESTING @GetMapping("/financialReport/{id}") fun getFinancialReport(@PathVariable id: Long): List> { -// println(excelReportService.genFinancialStatusReport(id)) return excelReportService.getFinancialStatus(id) } + // For API TESTING + @GetMapping("/manHoursSpent/{id}") + fun getManhoursSpent(@PathVariable id: Long): List> { + 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 { + 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)) + } + } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 8a85bc5..0264e98 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -6,6 +6,13 @@ import java.time.YearMonth data class FinancialStatusReportRequest ( val teamLeadId: Long ) + +data class PandLReportRequest ( + val projectId: Long, + val startMonth: String, + val endMonth: String, +) + data class ProjectCashFlowReportRequest ( val projectId: Long, val dateType: String, // "Date", "Month" diff --git a/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx b/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx index d0e8182..7febce9 100644 Binary files a/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx and b/src/main/resources/templates/report/AR07_Project P&L Report v02.xlsx differ