From a74aca71eea18c2c15d3c0477981940ca20b3e36 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Fri, 17 May 2024 16:00:21 +0800 Subject: [PATCH] update report --- .../modules/report/service/ReportService.kt | 110 +++++++++++++----- .../modules/report/web/ReportController.kt | 18 +-- .../entity/projections/MonthlyHours.kt | 13 ++- ...08_Monthly Work Hours Analysis Report.xlsx | Bin 15819 -> 14869 bytes 4 files changed, 100 insertions(+), 41 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 a940609..b38b07a 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 @@ -7,6 +7,9 @@ import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project 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 @@ -20,6 +23,7 @@ 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.format.DateTimeFormatter import java.util.* @@ -121,9 +125,8 @@ open class ReportService( fun generateStaffMonthlyWorkHourAnalysisReport( month: LocalDate, staff: Staff, - timesheets: List, - leaves: List, - projectList: List + timesheets: List>, + leaves: List>, ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = createStaffMonthlyWorkHourAnalysisReport( @@ -131,7 +134,6 @@ open class ReportService( staff, timesheets, leaves, - projectList, MONTHLY_WORK_HOURS_ANALYSIS_REPORT ) @@ -718,21 +720,35 @@ open class ReportService( private fun createStaffMonthlyWorkHourAnalysisReport( month: LocalDate, staff: Staff, - timesheets: List, - leaves: List, - projectList: List, + timesheets: List>, + leaves: List>, templatePath: String, ): Workbook { -// val yearMonth = YearMonth.of(2022, 5) // May 2022 - println("t $timesheets") - println("l $leaves") - println("p $projectList") + var projectList: List = listOf() + println("----timesheets-----") + println(timesheets) + // result = timesheet record mapped + var result: Map = mapOf() + if (timesheets.isNotEmpty()) { + projectList = timesheets.map{ "${it["code"]}\n ${it["name"]}"}.toList() + result = timesheets.groupBy( + { it["id"].toString() }, + { mapOf( + "date" to it["recordDate"], + "normalConsumed" to it["normalConsumed"], + "otConsumed" to it["otConsumed"], + ) } + ) + } + println("---result---") + println(result) + println("l $projectList") val resource = ClassPathResource(templatePath) val templateInputStream = resource.inputStream val workbook: Workbook = XSSFWorkbook(templateInputStream) val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") - val monthStyle = workbook.createDataFormat().getFormat("mmm, yyyy") + val monthStyle = workbook.createDataFormat().getFormat("MMM YYYY") val dateStyle = workbook.createDataFormat().getFormat("dd/mm/yyyy") val boldStyle = workbook.createCellStyle() @@ -748,8 +764,6 @@ open class ReportService( val sheet: Sheet = workbook.getSheetAt(0) -// sheet.forceFormulaRecalculation = true; //Calculate formulas - var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field var columnIndex = 1 @@ -766,7 +780,6 @@ open class ReportService( rowIndex = 2 sheet.getRow(rowIndex).getCell(columnIndex).apply { setCellValue(month) -// cellStyle.setFont(boldStyle) cellStyle.dataFormat = monthStyle } @@ -801,11 +814,9 @@ open class ReportService( tempCell.setCellValue(dayInfo.date) tempCell.cellStyle = boldStyle tempCell.cellStyle.dataFormat = dateStyle -// cellStyle.alignment = HorizontalAlignment.LEFT tempCell = sheet.getRow(rowIndex).createCell(1) tempCell.setCellValue(dayInfo.weekday) tempCell.cellStyle = boldStyle -// cellStyle.alignment = HorizontalAlignment.LEFT } rowIndex += 1 @@ -827,10 +838,11 @@ open class ReportService( var normalConsumed = 0.0 var otConsumed = 0.0 var leaveHours = 0.0 + // normalConsumed data if (timesheets.isNotEmpty()) { timesheets.forEach { t -> - normalConsumed += t.normalConsumed!! - otConsumed += t.otConsumed ?: 0.0 + normalConsumed += t["normalConsumed"] as Double + otConsumed += t["otConsumed"] as Double } } tempCell = sheet.getRow(rowIndex).createCell(2) @@ -858,9 +870,10 @@ open class ReportService( tempCell.cellStyle = boldStyle CellUtil.setAlignment(tempCell, HorizontalAlignment.CENTER) sheet.addMergedRegion(CellRangeAddress(rowIndex,rowIndex , 0, 1)) + // cal total leave hour if (leaves.isNotEmpty()) { leaves.forEach { l -> - leaveHours += l.leaveHours!! + leaveHours += l["leaveHours"] as Double } } tempCell = sheet.getRow(rowIndex).createCell(2) @@ -905,28 +918,30 @@ open class ReportService( tempCell.setCellValue(0.0) tempCell.cellStyle.dataFormat = accountingStyle } - timesheets.forEach { timesheet -> - dayInt = timesheet.recordDate!!.dayOfMonth - tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) - tempCell.setCellValue(timesheet.normalConsumed!!) - + result.forEach{(id, list) -> + for (i in 0 until id.toInt()) { + val temp: List> = list as List> + temp.forEachIndexed { i, _ -> + dayInt = temp[i]["date"].toString().toInt() + tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) + tempCell.setCellValue(temp[i]["normalConsumed"] as Double) + } + } } columnIndex++ } } - // dates + // leave hours data if (leaves.isNotEmpty()) { leaves.forEach { leave -> for (i in 0 until rowSize) { tempCell = sheet.getRow(8 + i).createCell(columnIndex) tempCell.setCellValue(0.0) tempCell.cellStyle.dataFormat = accountingStyle - } - dayInt = leave.recordDate!!.dayOfMonth + dayInt = leave["recordDate"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) - tempCell.setCellValue(leave.leaveHours!!) - + tempCell.setCellValue(leave["leaveHours"] as Double) } } ///////////////////////////////////////////////////////// Leave Hours //////////////////////////////////////////////////////////////////// @@ -1126,4 +1141,37 @@ open class ReportService( return jdbcDao.queryForList(sql.toString(), args) } -} \ No newline at end of file + open fun getTimesheet(args: Map): List> { + val sql = StringBuilder( + "SELECT" + + " p.id," + + " p.name," + + " p.code," + + " CAST(DATE_FORMAT(t.recordDate, '%d') AS SIGNED) AS recordDate," + + " sum(t.normalConsumed) as normalConsumed," + + " IFNULL(sum(t.otConsumed), 0.0) as otConsumed" + + " from timesheet t" + + " left join project_task pt on t.projectTaskId = pt.id" + + " left join project p on p.id = pt.project_id" + + " where t.staffId = :staffId" + + " group by p.id, t.recordDate" + + " order by p.id, t.recordDate" + + " and t.recordDate BETWEEN :startDate and :endDate" + ) + return jdbcDao.queryForList(sql.toString(), args) + } + open fun getLeaves(args: Map): List> { + val sql = StringBuilder( + " SELECT " + + " sum(leaveHours) as leaveHours, " + + " CAST(DATE_FORMAT(recordDate, '%d') AS SIGNED) AS recordDate " + + " from `leave` " + + " where staffId = :staffId " + + " and recordDate BETWEEN :startDate and :endDate " + + " group by recordDate " + + " order by recordDate " + ) + return jdbcDao.queryForList(sql.toString(), args) + } + +} 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 1173a10..a097ba4 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 @@ -11,6 +11,7 @@ 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 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 @@ -78,14 +79,15 @@ class ReportController( val nextMonth = request.yearMonth.plusMonths(1).atDay(1) val staff = staffRepository.findById(request.id).orElseThrow() - val timesheets = timesheetRepository.findByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) - val leaves = leaveRepository.findByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) - - val projects = timesheetRepository.findDistinctProjectTaskByStaffAndRecordDateBetweenOrderByRecordDate(staff, thisMonth, nextMonth) - val projectList: List = projects.map { p -> "${p.projectTask!!.project!!.code}\n ${p.projectTask!!.project!!.name}" } - - - val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves, projectList) + val args: Map = mutableMapOf( + "staffId" to request.id, + "startDate" to thisMonth, + "endDate" to nextMonth, + ) + val timesheets= excelReportService.getTimesheet(args) + val leaves= excelReportService.getLeaves(args) + + val reportResult: ByteArray = excelReportService.generateStaffMonthlyWorkHourAnalysisReport(thisMonth, staff, timesheets, leaves) // 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/projections/MonthlyHours.kt b/src/main/java/com/ffii/tsms/modules/timesheet/entity/projections/MonthlyHours.kt index ea5f40a..1509171 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/entity/projections/MonthlyHours.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/entity/projections/MonthlyHours.kt @@ -2,7 +2,16 @@ package com.ffii.tsms.modules.timesheet.entity.projections import java.time.LocalDate -data class MonthlyHours( +data class MonthlyLeave( val date: LocalDate, - val nomralConsumed: Number + val leaveHours: Double ) + +data class ProjectMonthlyHoursWithDate( + val id: Long, + val name: String, + val code: String, + val date: LocalDate, + val normalConsumed: Double, + val otConsumed: Double, + ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx b/src/main/resources/templates/report/AR08_Monthly Work Hours Analysis Report.xlsx index 001d546e5e0f9d273b547ba826e4dc62193d522f..08129d72d57569f0e1b8ae48c2173e84325e1146 100644 GIT binary patch delta 6961 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 CkCu`E