From 8f87cbd75a081177d32c577ded4dd5c92340991f Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 9 May 2025 15:54:03 +0800 Subject: [PATCH] Export Staff Current Info function --- .../modules/data/service/StaffsService.kt | 28 +++++ .../modules/report/service/ReportService.kt | 112 +++++++++++++++++- .../modules/report/web/ReportController.kt | 9 ++ .../modules/report/web/model/ReportRequest.kt | 4 + .../templates/report/Staff Information.xlsx | Bin 0 -> 11722 bytes 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/templates/report/Staff Information.xlsx diff --git a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt index a3a8218..0013733 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/StaffsService.kt @@ -9,6 +9,7 @@ import com.ffii.tsms.modules.data.entity.projections.StaffSearchInfo import com.ffii.tsms.modules.data.web.models.NewStaffRequest import com.ffii.tsms.modules.data.web.models.NewStaffResponse import com.ffii.tsms.modules.data.web.models.SalaryEffectiveInfo +import com.ffii.tsms.modules.report.web.model.ExportCurrentStaffInfoRequest import com.ffii.tsms.modules.user.entity.User import com.ffii.tsms.modules.user.entity.UserRepository import org.springframework.scheduling.annotation.Scheduled @@ -398,4 +399,31 @@ open class StaffsService( ) return jdbcDao.queryForList(sql.toString(), args) } + + open fun getCurrentStaffInfo(args: List) : List>{ + val sql = StringBuilder( + "select" + + " s.staffId," + + " COALESCE(team.code, '') as team," + + " s.name," + + " coalesce (s.joinDate, '') as employmentDate," + + " g.code as grade," + + " p.description ," + + " s.salaryId," + + " coalesce (s.email , '') as email," + + " s.departDate" + + " from staff s" + + " left join team on team.id = s.teamId" + + " left join grade g on g.id = s.gradeId" + + " left join `position` p on p.id = s.currentPosition" + + " where s.deleted = false " + ) + if (args.isNotEmpty()){ + sql.append(" and s.id in (:ids)") + } + + sql.append(" order by s.staffId") + + return jdbcDao.queryForList(sql.toString(), mapOf("ids" to args)) + } } \ No newline at end of file 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 e3923cd..2f5428f 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 @@ -4,7 +4,9 @@ import com.ffii.core.support.JdbcDao import com.ffii.tsms.modules.data.entity.* import com.ffii.tsms.modules.data.service.SalaryEffectiveService import com.ffii.tsms.modules.data.service.SalaryEffectiveService.MonthlyStaffSalaryData +import com.ffii.tsms.modules.data.service.StaffsService import com.ffii.tsms.modules.project.entity.* +import com.ffii.tsms.modules.report.web.model.ExportCurrentStaffInfoRequest import com.ffii.tsms.modules.report.web.model.costAndExpenseRequest import com.ffii.tsms.modules.timesheet.entity.Timesheet import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository @@ -54,7 +56,8 @@ open class ReportService( private val salaryRepository: SalaryRepository, private val timesheetRepository: TimesheetRepository, private val teamLogRepository: TeamLogRepository, - private val teamRepository: TeamRepository + private val teamRepository: TeamRepository, + private val staffService: StaffsService, ) { private val logger: Log = LogFactory.getLog(javaClass) private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") @@ -76,6 +79,7 @@ open class ReportService( 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 val STAFF_INFORMATION = "templates/report/Staff Information.xlsx" private fun cellBorderArgs(top: Int, bottom: Int, left: Int, right: Int): MutableMap { var cellBorderArgs = mutableMapOf() @@ -5794,4 +5798,110 @@ open class ReportService( return outputStream.toByteArray() } + + @Throws(IOException::class) + private fun createStaffCurrentInfoWorkBook( + staffIds: List, + templatePath: String + ): Workbook{ + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val result = staffService.getCurrentStaffInfo(staffIds) + 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) + } + + var startRowIndex = 4 + result.forEach { + val row = + if (sheet.getRow(startRowIndex) == null) sheet.createRow(startRowIndex) else sheet.getRow(startRowIndex) + val staffIdCell = row.createCell(0) + val teamCell = row.createCell(1) + val staffNameCell = row.createCell(2) + val employmentDateCell = row.createCell(3) + val gradeCell = row.createCell(4) + val positionCell = row.createCell(5) + val salaryPointCell = row.createCell(6) + val emailCell = row.createCell(7) + + staffIdCell.apply { + setCellValue(it.getValue("staffId") as String) + cellStyle = blackFontStyle + } + + teamCell.apply { + setCellValue(it.getValue("team") as String) + cellStyle = blackFontStyle + } + + staffNameCell.apply { + setCellValue(it.getValue("name") as String) + cellStyle = blackFontStyle + } + + employmentDateCell.apply { + setCellValue(it.getValue("employmentDate") as String) + cellStyle = blackFontStyle + } + + gradeCell.apply { + val grade = it.getValue("grade") as String + val gradeCode = "GRADE $grade" + setCellValue(gradeCode) + cellStyle = blackFontStyle + } + + positionCell.apply { + setCellValue(it.getValue("description") as String) + cellStyle = blackFontStyle + } + + salaryPointCell.apply{ + setCellValue((it.getValue("salaryId") as Int).toString()) + cellStyle = blackFontStyle + } + + emailCell.apply { + setCellValue(it.getValue("email") as String) + cellStyle = blackFontStyle + } + + startRowIndex++ + } + + return workbook + } + + fun genStaffCurrentInfoFile( + request: ExportCurrentStaffInfoRequest, + ): ByteArray{ + val workbook = createStaffCurrentInfoWorkBook(request.staffIds, STAFF_INFORMATION) + + 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 5221337..d8c62e8 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 @@ -399,6 +399,15 @@ class ReportController( .body(ByteArrayResource(reportResult)) } + @PostMapping("/export-staff-current-info") + fun exportStaffCurrentInfo(@RequestBody @Valid request: ExportCurrentStaffInfoRequest): ResponseEntity{ + val reportResult: ByteArray = excelReportService.genStaffCurrentInfoFile(request) + + return ResponseEntity.ok() + .header("filename", "Staff Information - " + 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 4f20807..a379967 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 @@ -78,4 +78,8 @@ data class ProjectMonthlyRequest ( data class LastRecordReportRequest ( val dateString: String +) + +data class ExportCurrentStaffInfoRequest ( + val staffIds: List ) \ No newline at end of file diff --git a/src/main/resources/templates/report/Staff Information.xlsx b/src/main/resources/templates/report/Staff Information.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c60fe27715c1ced9fa3a39ba3b97f259ac35218b GIT binary patch literal 11722 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 O^hXIY;~3iCKK&oipWE&L literal 0 HcmV?d00001