From e6ea5710a6bd932a12479add0e784190921cbf90 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 2 May 2025 17:42:08 +0800 Subject: [PATCH] Add Staff Last Modified Report --- .../modules/report/service/ReportService.kt | 142 ++++++++++++++++++ .../modules/report/web/ReportController.kt | 13 +- .../modules/report/web/model/ReportRequest.kt | 4 + .../report/AR10_Staff Last Record Report.xlsx | Bin 0 -> 14640 bytes 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/templates/report/AR10_Staff Last Record Report.xlsx 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 9ffa836..e3923cd 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 @@ -75,6 +75,7 @@ open class ReportService( private val CROSS_TEAM_CHARGE_REPORT = "templates/report/Cross Team Charge Report.xlsx" private val PROJECT_MANHOUR_SUMMARY = "templates/report/Project Manhour Summary.xlsx" private val PROJECT_MONTHLY_REPORT = "templates/report/AR09_Project Daily Work Hours Analysis Report.xlsx" + private val LAST_RECORD_REPORT = "templates/report/AR10_Staff Last Record Report.xlsx" private fun cellBorderArgs(top: Int, bottom: Int, left: Int, right: Int): MutableMap { var cellBorderArgs = mutableMapOf() @@ -5652,4 +5653,145 @@ open class ReportService( return outputStream.toByteArray() } + + data class StaffLastRecordData( + val staffId: String, + val staffName: String, + val email: String, + val team: String, + val lastRecordDate: String, + ) + private fun getStaffLastRecordDateByDate(date: LocalDate): List{ + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val sql = StringBuilder( + "with cte_lastRecordDate as ( " + + "select staffId, " + + "max( timesheet.recordDate ) as lastRecordDate " + + "from timesheet " + + "group by staffId " + + ") " + + "select " + + "s.staffId, " + + "s.name, " + + "COALESCE(s.email, '') as email, " + + "t.code as team, " + + "lmd.lastRecordDate " + + "from cte_lastRecordDate lmd " + + "left join staff s on s.id = lmd.staffid " + + "left join team t on s.teamId = t.id " + + "where s.staffId != 'B000' and s.teamId != 7 " + + "and lmd.lastrecorddate <= DATE_SUB(:searchDate, INTERVAL 7 DAY) " + + "order by lmd.lastrecorddate " + ) + + val results = jdbcDao.queryForList(sql.toString(), mapOf("searchDate" to date.format(formatter))).map { + result -> + StaffLastRecordData( + result["staffId"] as String, + result["name"] as String, + result["email"] as String, + result["team"] as String, + (result["lastRecordDate"] as java.sql.Date).toString() + ) + } + + return results + } + + @Throws(IOException::class) + private fun createLastRecordReportWorkbook( + date: LocalDate, + templatePath: String, + ): Workbook{ + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val result = getStaffLastRecordDateByDate(date) + println(result) + + var sheet: Sheet = workbook.getSheetAt(0) + var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + var columnIndex = 1 + + val blackFont = workbook.createFont().apply { + color = IndexedColors.BLACK.index + fontHeightInPoints = 12 + } + + val blackFontStyle = workbook.createCellStyle().apply { + setFont(blackFont) + borderTop = BorderStyle.THIN + borderBottom = BorderStyle.THIN + borderLeft = BorderStyle.THIN + borderRight = BorderStyle.THIN + } + + sheet.getRow(rowIndex).createCell(columnIndex+1).apply { + setCellValue(FORMATTED_TODAY) + } + + rowIndex = 2 + sheet.getRow(rowIndex).getCell(columnIndex+1).apply { + setCellValue(date.format(DATE_FORMATTER)) + } + + rowIndex = 3 + sheet.getRow(rowIndex).getCell(columnIndex+1).apply { + setCellValue( "${result.size}" ) + } + + var startRowIndex = 6 + val startColumnIndex = 1 + + result.forEach { + val row = if(sheet.getRow(startRowIndex) == null) sheet.createRow(startRowIndex) else sheet.getRow(startRowIndex) + val staffIdCell = row.createCell(0) + val staffNameCell = row.createCell(1) + val emailCell = row.createCell(2) + val teamCell = row.createCell(3) + val lastRecordDateCell = row.createCell(4) + + staffIdCell.apply { + setCellValue(it.staffId) + cellStyle =blackFontStyle + } + + staffNameCell.apply { + setCellValue(it.staffName) + cellStyle =blackFontStyle + } + + emailCell.apply { + setCellValue(it.email) + cellStyle =blackFontStyle + } + + teamCell.apply { + setCellValue(it.team) + cellStyle =blackFontStyle + } + + lastRecordDateCell.apply { + setCellValue(it.lastRecordDate) + cellStyle =blackFontStyle + } + + startRowIndex++ + } + + return workbook + } + + fun genLastRecordReport( + date: LocalDate, + ): ByteArray{ + val workbook = createLastRecordReportWorkbook(date, LAST_RECORD_REPORT) + + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } } \ 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 6131df8..5221337 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 @@ -165,7 +165,6 @@ class ReportController( .header("filename", "Monthly Work Hours Analysis Report - " + staff.name + " - " + LocalDate.now() + ".xlsx") .body(ByteArrayResource(reportResult)) } - private val mapper = ObjectMapper().registerModule(KotlinModule()) @PostMapping("/ProjectResourceOverconsumptionReport") @Throws(ServletRequestBindingException::class, IOException::class) @@ -388,6 +387,18 @@ class ReportController( .body(ByteArrayResource(reportResult)) } + @PostMapping("/gen-staff-last-record-report") + fun genLastRecordReport(@RequestBody @Valid request: LastRecordReportRequest): ResponseEntity{ + println("================= ${request.dateString}") + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val date = LocalDate.parse(request.dateString, formatter) + val reportResult: ByteArray = excelReportService.genLastRecordReport(date) + + return ResponseEntity.ok() + .header("filename", "Staff Last Record Report - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } + // API for testing data of total cumulative expenditure // @GetMapping("/test") // fun testApi(@RequestParam teamLeadId: Long): Map{ 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 9df7f6b..4f20807 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 @@ -74,4 +74,8 @@ data class ProjectMonthlyRequest ( val projectId: Long, val yearMonth: YearMonth, val holidays: List +) + +data class LastRecordReportRequest ( + val dateString: String ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR10_Staff Last Record Report.xlsx b/src/main/resources/templates/report/AR10_Staff Last Record Report.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..37601e1df6253afce1af2fdea99b99aca5d9201d GIT binary patch literal 14640 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 Zr*%acaDWa10l@)&6aY28X8(Eie*levO;rE@ literal 0 HcmV?d00001