diff --git a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt index b95d882..f89ee29 100644 --- a/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt +++ b/src/main/java/com/ffii/tsms/modules/project/service/InvoiceService.kt @@ -395,7 +395,7 @@ open class InvoiceService( for (i in 2..sheet.lastRowNum){ val paymentMilestoneId = getMilestonePaymentId(ExcelUtils.getCell(sheet, i, 1).stringCellValue, ExcelUtils.getCell(sheet, i, 5).stringCellValue) - println("paymentMilestoneId--------------: $paymentMilestoneId") +// println("paymentMilestoneId--------------: $paymentMilestoneId") val milestonePayment = milestonePaymentRepository.findById(paymentMilestoneId).orElseThrow() val invoice = Invoice().apply { invoiceNo = ExcelUtils.getCell(sheet, i, 0).stringCellValue 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 075384c..ee8df33 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 @@ -16,6 +16,7 @@ import java.io.IOException import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.* +import org.apache.poi.ss.util.CellAddress data class DayInfo(val date: String?, val weekday: String?) @Service @@ -23,11 +24,23 @@ open class ReportService { private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) + 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" private val SALART_LIST_TEMPLATE = "templates/report/Salary Template.xlsx" + private val LATE_START_REPORT = "templates/report/AR01_Late Start Report v01.xlsx" // ==============================|| GENERATE REPORT ||============================== // + + fun genFinancialStatusReport(): ByteArray { + val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT) + + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } @Throws(IOException::class) fun generateProjectCashFlowReport(project: Project, invoices: List, timesheets: List): ByteArray { // Generate the Excel report with query results @@ -67,7 +80,28 @@ open class ReportService { return outputStream.toByteArray() } + @Throws(IOException::class) + fun generateLateStartReport(project: Project): ByteArray { + val workbook: Workbook = createLateStartReport(project,LATE_START_REPORT) + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + return outputStream.toByteArray() + } + // ==============================|| CREATE REPORT ||============================== // + + // EX01 Financial Report + private fun createFinancialStatusReport( + templatePath: String, + ) : Workbook { + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + return workbook + } + @Throws(IOException::class) private fun createProjectCashFlowReport( project: Project, @@ -83,7 +117,7 @@ open class ReportService { val sheet: Sheet = workbook.getSheetAt(0) // accounting style + comma style - val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + val accountingStyle = workbook.createDataFormat() .getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field var columnIndex = 2 @@ -529,4 +563,75 @@ open class ReportService { return workbook } + private fun createLateStartReport( + project: Project, + templatePath: String + ):Workbook{ + + project + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + val sheet = workbook.getSheetAt(0) + + // Formatting the current date to "YYYY/MM/DD" and setting it to cell C2 + val formattedToday = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + val dateCell = sheet.getRow(1)?.getCell(2) ?: sheet.getRow(1).createCell(2) + dateCell.setCellValue(formattedToday) + + // Styling for cell A1: Font size 16 and bold + val headerFont = workbook.createFont().apply { + bold = true + fontHeightInPoints = 16 + } + val headerCellStyle = workbook.createCellStyle().apply { + setFont(headerFont) + } + val headerCell = sheet.getRow(0)?.getCell(0) ?: sheet.getRow(0).createCell(0) + headerCell.cellStyle = headerCellStyle + headerCell.setCellValue("Report Title") + + // Apply styles from A2 to A4 (bold) + val boldFont = workbook.createFont().apply { bold = true } + val boldCellStyle = workbook.createCellStyle().apply { setFont(boldFont) } + listOf(1, 2, 3).forEach { rowIndex -> + val row = sheet.getRow(rowIndex) + val cell = row?.getCell(0) ?: row.createCell(0) + cell.cellStyle = boldCellStyle + } + + // Apply styles from A6 to J6 (bold, bottom border, center alignment) + val styleA6ToJ6 = workbook.createCellStyle().apply { + setFont(boldFont) + alignment = HorizontalAlignment.CENTER + borderBottom = BorderStyle.THIN + } + for (colIndex in 0..9) { + val cellAddress = CellAddress(5, colIndex) // Row 6 (0-based index), Columns A to J + val row = sheet.getRow(cellAddress.row) + val cell = row?.getCell(cellAddress.column) ?: row.createCell(cellAddress.column) + cell.cellStyle = styleA6ToJ6 + } + + // Setting column widths dynamically based on content length (example logic) + val maxContentWidths = IntArray(10) { 8 } // Initial widths for A to J + for (rowIndex in 0..sheet.lastRowNum) { + val row = sheet.getRow(rowIndex) + for (colIndex in 0..9) { + val cell = row.getCell(colIndex) + if (cell != null) { + val length = cell.toString().length + if (length > maxContentWidths[colIndex]) { + maxContentWidths[colIndex] = length + } + } + } + } + for (colIndex in 0..9) { + sheet.setColumnWidth(colIndex, (maxContentWidths[colIndex] + 2) * 256) // Set the width for each column + } + + return workbook + } + } \ No newline at end of file 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 3fa4166..d96d1b0 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 @@ -5,6 +5,7 @@ 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.timesheet.entity.LeaveRepository @@ -21,6 +22,8 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType import java.io.IOException import java.time.LocalDate @@ -37,6 +40,18 @@ class ReportController( private val leaveRepository: LeaveRepository, private val invoiceService: InvoiceService) { + @PostMapping("/fetchProjectsFinancialStatusReport") + @Throws(ServletRequestBindingException::class, IOException::class) + fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity { + + val reportResult: ByteArray = excelReportService.genFinancialStatusReport() + + return ResponseEntity.ok() + .header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } + + @PostMapping("/ProjectCashFlowReport") @Throws(ServletRequestBindingException::class, IOException::class) fun getProjectCashFlowReport(@RequestBody @Valid request: ProjectCashFlowReportRequest): ResponseEntity { @@ -80,4 +95,18 @@ class ReportController( val project = projectRepository.findById(id).orElseThrow() return invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) } + + @PostMapping("/downloadLateStartReport") + fun downloadLateStartReport(): ResponseEntity { + val reportBytes = excelReportService.generateLateStartReport(Project()) + val headers = HttpHeaders() + headers.add("Content-Disposition", "attachment; filename=Late_Start_Report_${LocalDate.now()}.xlsx") + + return ResponseEntity.ok() + .headers(headers) + .contentLength(reportBytes.size.toLong()) + .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) + .body(ByteArrayResource(reportBytes)) + } + } \ 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 bbb14da..94158bc 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 @@ -2,6 +2,9 @@ package com.ffii.tsms.modules.report.web.model import java.time.YearMonth +data class FinancialStatusReportRequest ( + val projectId: Long +) data class ProjectCashFlowReportRequest ( val projectId: Long )