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 f707f3e..06fd2ab 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,12 +1,11 @@ package com.ffii.tsms.modules.report.service import com.ffii.tsms.modules.data.entity.Salary -import com.ffii.tsms.modules.data.entity.projections.SalarySearchInfo import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project +import com.ffii.tsms.modules.timesheet.entity.Timesheet import org.apache.poi.ss.usermodel.Sheet import org.apache.poi.ss.usermodel.Workbook -import org.apache.poi.xssf.usermodel.XSSFDataFormat import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service @@ -25,9 +24,9 @@ open class ReportService { // ==============================|| GENERATE REPORT ||============================== // @Throws(IOException::class) - fun generateProjectCashFlowReport(project: Project, invoices: List): ByteArray { + fun generateProjectCashFlowReport(project: Project, invoices: List, timesheets: List): ByteArray { // Generate the Excel report with query results - val workbook: Workbook = createProjectCashFlowReport(project, invoices, PROJECT_CASH_FLOW_REPORT) + val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, PROJECT_CASH_FLOW_REPORT) // Write the workbook to a ByteArrayOutputStream val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() @@ -55,6 +54,7 @@ open class ReportService { private fun createProjectCashFlowReport( project: Project, invoices: List, + timesheets: List, templatePath: String, ): Workbook { // please create a new function for each report template @@ -108,10 +108,11 @@ open class ReportService { rowIndex = 10 val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } + val actualExpenditure = timesheets.sumOf { timesheet -> timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0)) } sheet.getRow(rowIndex).apply { getCell(1).apply { // TODO: Replace by actual expenditure - setCellValue(actualIncome * 0.8) + setCellValue(actualExpenditure) cellStyle.dataFormat = accountingStyle } @@ -136,37 +137,77 @@ open class ReportService { } // TODO: Add expenditure - // formula =IF(B17>0,D16-B17,D16+C17) rowIndex = 15 + val combinedResults = (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it } + val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY") - invoices.forEach { invoice: Invoice -> - sheet.getRow(rowIndex++).apply { - getCell(0).apply { - setCellValue(invoice.receiptDate!!.format(dateFormatter)) - } + combinedResults.forEach { result: LocalDate -> + val invoice = invoices.find { invoice: Invoice -> invoice.receiptDate == result } + val timesheet = timesheets.find { timesheet: Timesheet -> timesheet.recordDate == result} - getCell(1).apply { - setCellValue(0.0) - cellStyle.dataFormat = accountingStyle - } + if (invoice != null) { + sheet.getRow(rowIndex++).apply { - getCell(2).apply { - setCellValue(invoice.paidAmount!!) - cellStyle.dataFormat = accountingStyle - } + getCell(0).apply { + setCellValue(result.format(dateFormatter)) + } + + getCell(1).apply { + setCellValue(0.0) + cellStyle.dataFormat = accountingStyle + } + + getCell(2).apply { + setCellValue(invoice.paidAmount!!) + cellStyle.dataFormat = accountingStyle + } + + getCell(3).apply { + val lastRow = rowIndex - 1 + if (lastRow == 15) { + cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) + } else { + cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString()) + } + cellStyle.dataFormat = accountingStyle + } - getCell(3).apply { - val lastRow = rowIndex - 1 - if (lastRow == 15) { - cellFormula = "C{currentRow}".replace("{currentRow}", rowIndex.toString()) - } else { - cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString()) + getCell(4).apply { + setCellValue(invoice.milestonePayment!!.description!!) } - cellStyle.dataFormat = accountingStyle } + } + + if (timesheet != null) { + sheet.getRow(rowIndex++).apply { + + getCell(0).apply { + setCellValue(result.format(dateFormatter)) + } + + getCell(1).apply { + setCellValue(timesheet.staff!!.salary.hourlyRate.toDouble() * ((timesheet.normalConsumed ?: 0.0) + (timesheet.otConsumed ?: 0.0))) + cellStyle.dataFormat = accountingStyle + } - getCell(4).apply { - setCellValue(invoice.milestonePayment!!.description!!) + getCell(2).apply { + setCellValue(0.0) + cellStyle.dataFormat = accountingStyle + } + + getCell(3).apply { + val lastRow = rowIndex - 1 + if (lastRow == 15) { + cellFormula = "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) + } else { + cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString()) + } + cellStyle.dataFormat = accountingStyle + } + + getCell(4).apply { + setCellValue("Monthly Manpower Expenditure") + } } } } 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 d2a0247..6ad099f 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 @@ -4,6 +4,7 @@ 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.ProjectCashFlowReportRequest +import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository import jakarta.validation.Valid import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource @@ -21,16 +22,21 @@ import java.time.LocalDate @RestController @RequestMapping("/reports") -class ReportController(private val invoiceRepository: InvoiceRepository, private val milestonePaymentRepository: MilestonePaymentRepository, private val excelReportService: ReportService, private val projectRepository: ProjectRepository, private val invoiceService: InvoiceService) { +class ReportController(private val invoiceRepository: InvoiceRepository, private val milestonePaymentRepository: MilestonePaymentRepository, private val excelReportService: ReportService, private val projectRepository: ProjectRepository, private val invoiceService: InvoiceService, + private val timesheetRepository: TimesheetRepository, + private val projectTaskRepository: ProjectTaskRepository +) { @PostMapping("/ProjectCashFlowReport") @Throws(ServletRequestBindingException::class, IOException::class) fun getProjectCashFlowReport(@RequestBody @Valid request: ProjectCashFlowReportRequest): ResponseEntity { val project = projectRepository.findById(request.projectId).orElseThrow() + val projectTasks = projectTaskRepository.findAllByProject(project) val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) + val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) - val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices) + val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets) // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return ResponseEntity.ok() // .contentType(mediaType) diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt index 6f20be5..7a7f40c 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/TimesheetRepository.kt @@ -4,6 +4,7 @@ import com.ffii.core.support.AbstractRepository import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours +import com.ffii.tsms.modules.project.entity.ProjectTask import org.springframework.data.jpa.repository.Query import java.time.LocalDate @@ -11,6 +12,8 @@ interface TimesheetRepository : AbstractRepository { fun findAllByStaff(staff: Staff): List + fun findAllByProjectTaskIn(projectTasks: List): List + fun deleteAllByStaffAndRecordDate(staff: Staff, recordDate: LocalDate) @Query("SELECT new com.ffii.tsms.modules.timesheet.entity.projections.TimesheetHours(IFNULL(SUM(normalConsumed), 0), IFNULL(SUM(otConsumed), 0)) FROM Timesheet t JOIN ProjectTask pt on t.projectTask = pt WHERE pt.project = ?1")