From b1207ceeacfb3bc348bd6f6b41b1dfc6b320490f Mon Sep 17 00:00:00 2001 From: leoho2fi Date: Fri, 31 May 2024 16:15:52 +0800 Subject: [PATCH] report add subsidiary --- .../modules/report/service/ReportService.kt | 137 +++++++++--------- .../modules/report/web/ReportController.kt | 58 +++----- .../modules/report/web/model/ReportRequest.kt | 1 + 3 files changed, 85 insertions(+), 111 deletions(-) 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 664d91b..67328c6 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 @@ -5,35 +5,23 @@ 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.project.entity.GradeAllocation import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Milestone import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest -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.* -import org.apache.poi.ss.util.CellAddress 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.ss.usermodel.FormulaEvaluator -import org.apache.poi.ss.usermodel.CellStyle -import org.apache.poi.ss.usermodel.DateUtil -import org.apache.poi.ss.usermodel.CellType -import org.hibernate.jdbc.Work import org.springframework.core.io.ClassPathResource 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 @@ -41,7 +29,6 @@ import java.time.ZoneId import java.time.format.DateTimeParseException import java.time.temporal.ChronoUnit import java.util.* -import kotlin.jvm.optionals.getOrElse data class DayInfo(val date: String?, val weekday: String?) @@ -287,26 +274,22 @@ open class ReportService( fun generateLateStartReport( teamId: Long, clientId: Long, - remainedDate:LocalDate, - remainedDateTo:LocalDate, + remainedDate: LocalDate, + remainedDateTo: LocalDate, teams: List, customers: List, + searchedClient: String, + type: String, ): ByteArray { - val projectDetails = getLateStartDetails(teamId, clientId, remainedDate, remainedDateTo) - - // Find team and customer names based on teamId and clientId for the Excel report headers - // val team = teamRepository.findById(teamId).orElse(null) - // val teams = if (team == null) listOf() else listOf(team) - // val customer = customerRepository.findById(clientId).orElse(null) - // val customers = if (customer == null) listOf() else listOf(customer) - + val projectDetails = getLateStartDetails(teamId, clientId, remainedDate, remainedDateTo, type) val workbook: Workbook = createLateStartReport( teams, customers, LATE_START_REPORT, // Ensure you provide the correct path to your template teamId, clientId, - projectDetails + projectDetails, + searchedClient ) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) @@ -1448,7 +1431,8 @@ open class ReportService( templatePath: String, teamId: Long, clientId: Long, - lateStartData: List> + lateStartData: List>, + searchedClient: String ): Workbook { val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream @@ -1463,7 +1447,7 @@ open class ReportService( setDateInCellC2(workbook, sheet) // Populate team and client information based on IDs - setTeamAndClientIds(sheet, team, teamId, customer, clientId) + setTeamAndClientIds(sheet, team, teamId, searchedClient) // Process late start data, and apply data and conditional formatting to the sheet setDataAndConditionalFormatting(workbook, sheet, lateStartData, evaluator) @@ -1505,16 +1489,16 @@ open class ReportService( cell.cellStyle = boldCellStyle } - // Apply a bold, bottom-bordered, center-aligned style to header cells from A6 to J6 - val styleA6ToJ6 = workbook.createCellStyle().apply { + // Apply a bold, bottom-bordered, center-aligned style to header cells from A6 to K6 + val styleA6ToK6 = workbook.createCellStyle().apply { setFont(timesNewRoman) // Use the standard bold font for consistency alignment = HorizontalAlignment.CENTER borderBottom = BorderStyle.THIN } val rowA6 = sheet.getRow(5) ?: sheet.createRow(5) - (0..9).forEach { colIndex -> + (0..10).forEach { colIndex -> val cell = rowA6.getCell(colIndex) ?: rowA6.createCell(colIndex) - cell.cellStyle = styleA6ToJ6 + cell.cellStyle = styleA6ToK6 } } @@ -1545,32 +1529,32 @@ open class ReportService( // Only apply conditional formatting if there is data if (lateStartData.isNotEmpty()) { val sheetCF = sheet.sheetConditionalFormatting - val rule1 = sheetCF.createConditionalFormattingRule("J7 < 60") + val rule1 = sheetCF.createConditionalFormattingRule("K7 < 60") val pattern1 = rule1.createPatternFormatting().apply { fillBackgroundColor = IndexedColors.RED.index fillPattern = PatternFormatting.SOLID_FOREGROUND } - val rule2 = sheetCF.createConditionalFormattingRule("J7 >= 60") + val rule2 = sheetCF.createConditionalFormattingRule("K7 >= 60") val pattern2 = rule2.createPatternFormatting().apply { fillBackgroundColor = IndexedColors.YELLOW.index fillPattern = PatternFormatting.SOLID_FOREGROUND } - val region = CellRangeAddress(6, dataRowIndex - 1, 9, 9) // Column J + val region = CellRangeAddress(6, dataRowIndex - 1, 10, 10) // Column K sheetCF.addConditionalFormatting(arrayOf(region), arrayOf(rule1, rule2)) } } // Sets team and client IDs in the sheet to manage data visibility based on the selected team and client - private fun setTeamAndClientIds(sheet: Sheet, team: List, teamId: Long, customer: List, clientId: Long) { + private fun setTeamAndClientIds(sheet: Sheet, team: List, teamId: Long, searchedClient: String) { // Insert the team name or "All" if no specific team is selected in cell C3 val teamIdRow = sheet.getRow(2) ?: sheet.createRow(2) val teamIdCell = teamIdRow.getCell(2) ?: teamIdRow.createCell(2) teamIdCell.setCellValue(if (teamId == 0L) "All" else team.getOrNull(0)?.name ?: "Unknown") - // Insert the client name or "All" if no specific client is selecred in cell C4 + // Insert the client name or "All" directly in cell C4 val clientIdRow = sheet.getRow(3) ?: sheet.createRow(3) val clientIdCell = clientIdRow.getCell(2) ?: clientIdRow.createCell(2) - clientIdCell.setCellValue(if (clientId == 0L) "All" else customer.getOrNull(0)?.name ?: "Unknown") + clientIdCell.setCellValue(searchedClient) } // Populates a single row with data from a map and applies formatting and formulas @@ -1612,9 +1596,12 @@ open class ReportService( val customerNameCell = row.createCell(4) customerNameCell.setCellValue(data["customer_name"] as String) -// // Column F: Project Plan Start Date - // Parse the project start date and apply the date format - val projectPlanStartCell = row.createCell(5) + // NEW Column F: Subsidiary Name + val subsidiaryNameCell = row.createCell(5) + subsidiaryNameCell.setCellValue(data["subsidiary_name"] as String) + + // Column G: Project Plan Start Date + val projectPlanStartCell = row.createCell(6) try { val date = LocalDate.parse(data["project_plan_start"].toString(), DateTimeFormatter.ISO_LOCAL_DATE) projectPlanStartCell.setCellValue(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant())) @@ -1623,17 +1610,17 @@ open class ReportService( } projectPlanStartCell.cellStyle = dateCellStyle - // Column G: Calculated Date Difference (days since project start from today) - val daysDifferenceCell = row.createCell(6) - daysDifferenceCell.setCellFormula("C$2-F${row.rowNum + 1}") + // Column H: Calculated Date Difference (days since project start from today) + val daysDifferenceCell = row.createCell(7) + daysDifferenceCell.setCellFormula("C$2-G${row.rowNum + 1}") daysDifferenceCell.cellStyle = centerAlignedStyle - // Column H: Task Group Name - val taskGroupNameCell = row.createCell(7) + // Column I: Task Group Name + val taskGroupNameCell = row.createCell(8) taskGroupNameCell.setCellValue(data["task_group_name"] as String) - // Column I: Milestone End Date - val dateCell = row.createCell(8) // Milestone end date in column I + // Column J: Milestone End Date + val dateCell = row.createCell(9) // Milestone end date in column I try { val date = LocalDate.parse(data["milestone_end_date"].toString()) dateCell.setCellValue(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant())) @@ -1646,15 +1633,16 @@ open class ReportService( val today = LocalDate.now() val endDate = LocalDate.parse(data["milestone_end_date"].toString(), DateTimeFormatter.ISO_LOCAL_DATE) val daysBetween = ChronoUnit.DAYS.between(today, endDate).toInt() // Calculate the difference in days - - val daysDifferenceCellJ = row.createCell(9) - daysDifferenceCellJ.setCellValue(daysBetween.toDouble()) // Set as double for consistency + // Ensure days difference is not negative + val adjustedDaysBetween = if (daysBetween < 0) 0 else daysBetween + val daysDifferenceCellJ = row.createCell(10) + daysDifferenceCellJ.setCellValue(adjustedDaysBetween.toDouble()) // Set as double for consistency daysDifferenceCellJ.cellStyle = centerAlignedStyle } // Automatically adjusts the width if all columns in the sheet to fit their content optimally. private fun autoSizeColumns(sheet: Sheet) { - for (colIndex in 0..9) { // Iterate through columns A to J + for (colIndex in 0..10) { // Iterate through columns A to K sheet.autoSizeColumn(colIndex) // Auto-size each column based on its content } } @@ -2542,33 +2530,42 @@ open class ReportService( return outputStream.toByteArray() } - open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate): List> { + open fun getLateStartDetails(teamId: Long?, clientId: Long?, remainedDate: LocalDate, remainedDateTo: LocalDate, type: String?): List> { val sql = StringBuilder(""" - SELECT - p.code AS project_code, - p.name AS project_name, - t.name AS team_name, - c.name AS customer_name, - p.planStart AS project_plan_start, - tg.name AS task_group_name, - m.endDate AS milestone_end_date - FROM - project p - LEFT JOIN team t ON p.teamLead = t.teamLead - LEFT JOIN customer c ON p.customerId = c.id - LEFT JOIN task_group tg ON tg.id = (SELECT taskGroupId FROM milestone WHERE projectId = p.id LIMIT 1) - LEFT JOIN milestone m ON m.taskGroupId = tg.id AND m.projectId = p.id - WHERE - p.status = 'Pending to Start' - AND p.planStart < CURRENT_DATE - AND m.endDate BETWEEN :remainedDate AND :remainedDateTo + SELECT + p.code AS project_code, + p.name AS project_name, + t.name AS team_name, + c.name AS customer_name, + s.name AS subsidiary_name, + p.planStart AS project_plan_start, + tg.name AS task_group_name, + m.endDate AS milestone_end_date + FROM + project p + LEFT JOIN team t ON p.teamLead = t.teamLead + LEFT JOIN customer c ON p.customerId = c.id + LEFT JOIN subsidiary s ON p.customerSubsidiaryId = s.id + LEFT JOIN task_group tg ON tg.id = (SELECT taskGroupId FROM milestone WHERE projectId = p.id LIMIT 1) + LEFT JOIN milestone m ON m.taskGroupId = tg.id AND m.projectId = p.id + WHERE + p.status = 'Pending to Start' + AND p.planStart < CURRENT_DATE + AND m.endDate BETWEEN :remainedDate AND :remainedDateTo """.trimIndent()) if (teamId != null && teamId > 0) { sql.append(" AND t.id = :teamId") } +// if (clientId != null && clientId > 0) { +// sql.append(" AND c.id = :clientId") +// } if (clientId != null && clientId > 0) { - sql.append(" AND c.id = :clientId") + when (type) { + "subsidiary" -> sql.append(" AND s.id = :clientId") + "client" -> sql.append(" AND c.id = :clientId") + "All" -> {} // No action needed if type is "All" + } } val args = mutableMapOf( 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 02ea8fa..4d2464e 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,11 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.ffii.tsms.modules.data.entity.* //import com.ffii.tsms.modules.data.entity.projections.FinancialStatusReportInfo -import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo import com.ffii.tsms.modules.data.service.CustomerService import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.project.entity.* -import com.ffii.tsms.modules.project.entity.projections.ProjectResourceReport import com.ffii.tsms.modules.project.service.InvoiceService import com.ffii.tsms.modules.report.service.ReportService import com.ffii.tsms.modules.report.web.model.FinancialStatusReportRequest @@ -19,7 +17,6 @@ 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 -import com.ffii.tsms.modules.timesheet.entity.projections.ProjectMonthlyHoursWithDate import jakarta.validation.Valid import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource @@ -32,15 +29,11 @@ import java.io.IOException import java.time.LocalDate import java.net.URLEncoder import java.time.format.DateTimeFormatter -import org.springframework.stereotype.Controller -import org.springframework.data.jpa.repository.JpaRepository import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.project.service.SubsidiaryService import com.ffii.tsms.modules.report.web.model.* -import com.ffii.tsms.modules.timesheet.entity.Timesheet import org.springframework.data.domain.Example import org.springframework.data.domain.ExampleMatcher -import java.time.temporal.ChronoUnit @RestController @RequestMapping("/reports") @@ -160,6 +153,7 @@ class ReportController( val args: MutableMap = mutableMapOf( "status" to request.status, "lowerLimit" to lowerLimit + ) if (request.teamId != null) { args["teamId"] = request.teamId @@ -222,9 +216,24 @@ class ReportController( val customers = if (customer == null) customerRepository.findAll() else listOf(customer) val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDate, request.remainedDateTo) + println(request.clientId) + println(request.type) + + val client = if (request.clientId.equals(0) || request.type != "client") null else customerRepository.findById(request.clientId.toLong()).orElse(null) + val subsidiary = if (request.clientId.equals(0) || request.type != "subsidiary") null else subsidiaryRepository.findById(request.clientId.toLong()).orElse(null) + val searchedClient = if (client != null) { // For display use + client.code + " - " + client.name + } else if (subsidiary != null) { + subsidiary.code + " - " + subsidiary.name + } else { + "All" + } + println(searchedClient) + val reportResult: ByteArray = try { excelReportService.generateLateStartReport(request.teamId, request.clientId, - request.remainedDate, request.remainedDateTo,teams,customers) + request.remainedDate, request.remainedDateTo,teams,customers, searchedClient,request.type + ) } catch (e: Exception) { throw Exception("Error generating report", e) } @@ -245,39 +254,6 @@ class ReportController( .body(ByteArrayResource(reportResult)) } -//fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest): ResponseEntity { -// val team = teamRepository.findById(request.teamId).orElseThrow { -// Exception("Team not found with ID: ${request.teamId}") -// } -// -// val customer = customerRepository.findByName(request.customer).orElseThrow() -// // ?: throw Exception("Customer not found with name: ${request.customer}") -// -// val projects = projectRepository.findAllByPlanStartLessThanEqualAndPlanEndGreaterThanEqual(request.remainedDateFrom, request.remainedDateTo) -// if (projects.isEmpty()) { -// throw Exception("No projects found for the given date: ${request.reportDate}") -// } -// -// val reportResult: ByteArray = try { -// excelReportService.generateLateStartReport(team, customer, projects) -// } catch (e: Exception) { -// throw Exception("Error generating report", e) -// } -// -// val headers = HttpHeaders() -// val formattedDate = request.reportDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) -// val filename = "Late_Start_Report_$formattedDate.xlsx" -// headers.add("Content-Disposition", "attachment; filename=${URLEncoder.encode(filename, "UTF-8")}") -// -// println("Generated filename for download: $filename") -// -// return ResponseEntity.ok() -// .headers(headers) -// .contentLength(reportResult.size.toLong()) -// .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) -// .body(ByteArrayResource(reportResult)) -// } - // For API TESTING @GetMapping("/financialReport/{id}") fun getFinancialReport(@PathVariable id: Long): List> { 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 f302693..a99d6e7 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 @@ -42,6 +42,7 @@ data class LateStartReportRequest ( val clientId: Long, val remainedDate: LocalDate, val remainedDateTo: LocalDate, + val type: String, ) data class ProjectResourceOverconsumptionReport ( val teamId: Long?,