From d94c06e90b4f32045343a4166867d0a653c6a6ec Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 11:34:27 +0800 Subject: [PATCH 01/14] Update Gen Financial Status Report from filter project to Team --- .../tsms/modules/data/service/TeamService.kt | 10 ++++ .../tsms/modules/data/web/TeamController.kt | 6 +++ .../modules/report/service/ReportService.kt | 50 +++++++++++++------ .../modules/report/web/ReportController.kt | 4 +- .../modules/report/web/model/ReportRequest.kt | 2 +- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt index 580c3aa..7ec148e 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt @@ -162,4 +162,14 @@ open class TeamService( ) return jdbcDao.queryForList(sql.toString(), args) } + + open fun combo2(): List> { + val sql = StringBuilder("select" + + " t.id as id," + + " t.name, t.code " + + " from team t" + + " where t.deleted = false " + ) + return jdbcDao.queryForList(sql.toString()) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt b/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt index 871c268..bb00181 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/TeamController.kt @@ -53,4 +53,10 @@ class TeamController(private val teamService: TeamService) { ) ) } + + @GetMapping("/combo2") + @Throws(ServletRequestBindingException::class) + fun combo2(): List> { + return teamService.combo2() + } } \ 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 1194886..9e19cb4 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 @@ -42,9 +42,9 @@ open class ReportService( // ==============================|| GENERATE REPORT ||============================== // - fun genFinancialStatusReport(projectId: Long): ByteArray { + fun genFinancialStatusReport(teamLeadId: Long): ByteArray { - val financialStatus: List> = getFinancialStatus(projectId) + val financialStatus: List> = getFinancialStatus(teamLeadId) val otFactor = BigDecimal(1) @@ -53,10 +53,10 @@ open class ReportService( for (item in financialStatus){ val normalConsumed = item.getValue("normalConsumed") as Double val hourlyRate = item.getValue("hourlyRate") as BigDecimal - println("normalConsumed------------- $normalConsumed") - println("hourlyRate------------- $hourlyRate") +// println("normalConsumed------------- $normalConsumed") +// println("hourlyRate------------- $hourlyRate") val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) - println("manHourRate------------ $manHourRate") +// println("manHourRate------------ $manHourRate") val otConsumed = item.getValue("otConsumed") as Double val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) @@ -88,9 +88,9 @@ open class ReportService( } } - println("tempList---------------------- $tempList") +// println("tempList---------------------- $tempList") - val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, tempList) + val workbook: Workbook = createFinancialStatusReport(FINANCIAL_STATUS_REPORT, tempList, teamLeadId) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) @@ -170,6 +170,7 @@ open class ReportService( private fun createFinancialStatusReport( templatePath: String, projects: List>, + teamLeadId: Long ) : Workbook { val resource = ClassPathResource(templatePath) @@ -369,8 +370,20 @@ open class ReportService( genDateCell.setCellValue(LocalDate.now().toString()) rowNum = 2 + val row2: Row = sheet.getRow(rowNum) + val row2Cell = row2.createCell(2) + if (teamLeadId < 0) { + row2Cell.setCellValue("All") + }else{ + row2Cell.apply { + cellFormula = "D15" + } + } rowNum = 4 + val row4: Row = sheet.getRow(rowNum) + val row4Cell = row4.createCell(2) + row4Cell.setCellValue(projects.size.toString()) rowNum = 5 val row5: Row = sheet.getRow(rowNum) @@ -1037,16 +1050,21 @@ open class ReportService( return workbook } - open fun getFinancialStatus(projectId: Long?): List> { + open fun getFinancialStatus(teamLeadId: Long?): List> { val sql = StringBuilder( " with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" + " from invoice i" + " left join project p on p.code = i.projectCode" + " group by p.code" - + ")" - + " Select p.code, p.description, c.name, t2.name, p.planStart , p.planEnd , p.expectedTotalFee ," - + " s.name , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," - + " cte_i.sumIssuedAmount, cte_i.sumPaidAmount" + + " )," + + " cte_teamLead as (" + + " select p.teamLead, p.code, t.name as teamName , t.code as teamCode" + + " from project p" + + " left join team t on t.id = p.teamLead" + + " )" + + " Select p.code, p.description, c.name as client, concat(cte_t.teamCode, \' - \', cte_t.teamName) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," + + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," + + " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount , IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount " + " from timesheet t" + " left join project_task pt on pt.id = t.projectTaskId" + " left join project p ON p.id = pt.project_id" @@ -1055,13 +1073,15 @@ open class ReportService( + " left join customer c on c.id = p.customerId" + " left join team t2 on t2.id = s.teamId" + " left join cte_invoice cte_i on cte_i.code = p.code" + + " left join cte_teamLead cte_t on cte_t.code = p.code" + + " where p.status = \'On-going\' " ) - if (projectId!! > 0) { - sql.append(" where p.id = :projectId ") + if (teamLeadId!! > 0) { + sql.append(" and p.teamLead = :teamLeadId ") } sql.append(" order by p.code") - val args = mapOf("projectId" to projectId) + val args = mapOf("teamLeadId" to teamLeadId) 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 4534b57..0dd06f1 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 @@ -46,7 +46,7 @@ class ReportController( fun getFinancialStatusReport(@RequestBody @Valid request: FinancialStatusReportRequest): ResponseEntity { - val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.projectId) + val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.teamLeadId) return ResponseEntity.ok() .header("filename", "Financial Status Report - " + LocalDate.now() + ".xlsx") @@ -114,7 +114,7 @@ class ReportController( @GetMapping("/financialReport/{id}") fun getFinancialReport(@PathVariable id: Long): List> { - println(excelReportService.genFinancialStatusReport(id)) +// println(excelReportService.genFinancialStatusReport(id)) return excelReportService.getFinancialStatus(id) } 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 94158bc..4d002cb 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 @@ -3,7 +3,7 @@ package com.ffii.tsms.modules.report.web.model import java.time.YearMonth data class FinancialStatusReportRequest ( - val projectId: Long + val teamLeadId: Long ) data class ProjectCashFlowReportRequest ( val projectId: Long From bbb41865689499eebe905a671dfc75a9dbe5729c Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Fri, 17 May 2024 11:44:01 +0800 Subject: [PATCH 02/14] update --- .../ffii/tsms/modules/data/service/DashboardService.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index 8d807bd..2efb46a 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -23,9 +23,7 @@ open class DashboardService( fun CustomerSubsidiary(args: Map): List> { val sql = StringBuilder("select" - + " row_number()OVER (" - + " ORDER BY c.id" - + " ) as id," + + " ROW_NUMBER() OVER (ORDER BY c.id, c.name, c.code, c.address, c.district, c.brNo, c.typeId, s.id, s.name, s.code, s.address, s.district, s.brNo, s.typeId) AS RowNum," + " c.id as customerId," + " c.name as customerName," + " c.code as customerCode," @@ -45,6 +43,7 @@ open class DashboardService( + " left join project p on c.id = p.customerId" + " left join subsidiary s on p.customerSubsidiaryId = s.id" + " where c.deleted = 0" + + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" ) if (args != null) { if (args.containsKey("customerName")) @@ -58,6 +57,7 @@ open class DashboardService( fun searchCustomerSubsidiaryProject(args: Map): List> { val sql = StringBuilder("select" + + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," + " p.id as id," + " p.id as projectId," + " p.code as projectCode," @@ -91,6 +91,7 @@ open class DashboardService( + " ) milestonePayment on 1=1" + " where p.customerId = :customerId" + " and p.customerSubsidiaryId = :subsidiaryId" + + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" ) @@ -99,6 +100,7 @@ open class DashboardService( fun searchCustomerNonSubsidiaryProject(args: Map): List> { val sql = StringBuilder("select" + + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," + " p.id as id," + " p.id as projectId," + " p.code as projectCode," @@ -132,6 +134,7 @@ open class DashboardService( + " ) milestonePayment on 1=1" + " where p.customerId = :customerId" + " and isNull(p.customerSubsidiaryId)" + + " and p.status not in (\"Pending to Start\",\"Completed\",\"Deleted\")" + " group by p.id, p.code, p.name, te.code, s.name, tg.name, p.totalManhour, milestonePayment.comingPaymentMilestone" ) From e517ce7fe82f26929e0296a6401de6fb8173d9fc Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 12:09:38 +0800 Subject: [PATCH 03/14] update formula --- .../java/com/ffii/tsms/modules/report/service/ReportService.kt | 2 +- .../java/com/ffii/tsms/modules/report/web/ReportController.kt | 2 +- 2 files changed, 2 insertions(+), 2 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 9e19cb4..5761c88 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 @@ -250,7 +250,7 @@ open class ReportService( val uninvoiceCell = row.createCell(11) uninvoiceCell.apply { - cellFormula = " IF(H${rowNum} { - + println(request.teamLeadId) val reportResult: ByteArray = excelReportService.genFinancialStatusReport(request.teamLeadId) return ResponseEntity.ok() From 8ef5c3e50179e53da639dbf433e2eba07ccf5f07 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 12:18:02 +0800 Subject: [PATCH 04/14] Update index to dynamic rowNum --- .../java/com/ffii/tsms/modules/report/service/ReportService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5761c88..9f01d30 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 @@ -250,7 +250,7 @@ open class ReportService( val uninvoiceCell = row.createCell(11) uninvoiceCell.apply { - cellFormula = " IF(H15<=I15, H15-K15, IF(AND(H15>I15, I15I15, I15>=K15), I15-K15, 0))) " + cellFormula = " IF(H${rowNum}<=I${rowNum}, H${rowNum}-K${rowNum}, IF(AND(H${rowNum}>I${rowNum}, I${rowNum}I${rowNum}, I${rowNum}>=K${rowNum}), I${rowNum}-K${rowNum}, 0))) " cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } From 36404b4cac422dce7a8c8ecddbfdcf6036abb905 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 17 May 2024 14:01:20 +0800 Subject: [PATCH 05/14] update ex02 report --- .../modules/report/service/ReportService.kt | 8 +++++--- .../modules/report/web/ReportController.kt | 2 +- .../modules/report/web/model/ReportRequest.kt | 3 ++- .../report/EX02_Project Cash Flow Report.xlsx | Bin 13299 -> 13289 bytes 4 files changed, 8 insertions(+), 5 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 9f01d30..fc8deef 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 @@ -103,10 +103,11 @@ open class ReportService( fun generateProjectCashFlowReport( project: Project, invoices: List, - timesheets: List + timesheets: List, + dateType: String ): ByteArray { // Generate the Excel report with query results - val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, PROJECT_CASH_FLOW_REPORT) + val workbook: Workbook = createProjectCashFlowReport(project, invoices, timesheets, dateType, PROJECT_CASH_FLOW_REPORT) // Write the workbook to a ByteArrayOutputStream val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() @@ -449,6 +450,7 @@ open class ReportService( project: Project, invoices: List, timesheets: List, + dateType: String, templatePath: String, ): Workbook { // please create a new function for each report template @@ -534,7 +536,7 @@ open class ReportService( rowIndex = 15 - val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY") + val dateFormatter = if (dateType == "Date") DateTimeFormatter.ofPattern("yyyy/MM/dd") else DateTimeFormatter.ofPattern("MMM YYYY") val combinedResults = (invoices.map { it.receiptDate } + timesheets.map { it.recordDate }).filterNotNull().sortedBy { it } .map { it.format(dateFormatter) }.distinct() 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 2e244bc..1173a10 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 @@ -63,7 +63,7 @@ class ReportController( val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) val timesheets = timesheetRepository.findAllByProjectTaskIn(projectTasks) - val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets) + val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices, timesheets, request.dateType) // 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/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index 4d002cb..17f67e8 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 @@ -6,7 +6,8 @@ data class FinancialStatusReportRequest ( val teamLeadId: Long ) data class ProjectCashFlowReportRequest ( - val projectId: Long + val projectId: Long, + val dateType: String, // "Date", "Month" ) data class StaffMonthlyWorkHourAnalysisReportRequest ( diff --git a/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx b/src/main/resources/templates/report/EX02_Project Cash Flow Report.xlsx index d2e48b6fbc04de132dbb67945061c3791b846157..0f63db775a5f861c774f676eed7c2971850b1afb 100644 GIT binary patch delta 3178 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 From 926e2d0a16c2f3202bb093b949bc1e45e532728f Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 15:22:28 +0800 Subject: [PATCH 06/14] EX01 return workbook when no timesheet mark in that team --- .../tsms/modules/data/service/TeamService.kt | 2 +- .../modules/report/service/ReportService.kt | 50 ++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt index 7ec148e..b75dc8c 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt @@ -165,7 +165,7 @@ open class TeamService( open fun combo2(): List> { val sql = StringBuilder("select" - + " t.id as id," + + " t.teamLead as id," + " t.name, t.code " + " from team t" + " where t.deleted = false " 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 9f01d30..c12652c 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 @@ -181,14 +181,50 @@ open class ReportService( val sheet = workbook.getSheetAt(0) + //Set Column 2, 3, 4 to auto width + sheet.setColumnWidth(2, 20*256) + sheet.setColumnWidth(3, 45*256) + sheet.setColumnWidth(4, 15*256) + val boldFont = sheet.workbook.createFont() boldFont.bold = true val boldFontCellStyle = workbook.createCellStyle() boldFontCellStyle.setFont(boldFont) + var rowNum = 14 + if (projects.isEmpty()){ + // Fill the cell in Row 2-12 with thr calculated sum + rowNum = 1 + val row1: Row = sheet.getRow(rowNum) + val genDateCell = row1.createCell(2) + genDateCell.setCellValue(LocalDate.now().toString()) + + rowNum = 2 + val row2: Row = sheet.getRow(rowNum) + val row2Cell = row2.createCell(2) + val sql = StringBuilder("select" + + " t.teamLead as id," + + " t.name, t.code " + + " from team t" + + " where t.deleted = false " + + " and t.teamLead = :teamLead " + ) + val args = mapOf("teamLead" to teamLeadId) + val team = jdbcDao.queryForMap(sql.toString(), args).get() + val code = team["code"] + val name = team["name"] + row2Cell.apply { + setCellValue("$code - $name") + } - var rowNum = 14 + rowNum = 4 + val row4: Row = sheet.getRow(rowNum) + val row4Cell = row4.createCell(2) + row4Cell.setCellValue(projects.size.toString()) + + return workbook + } for(item in projects){ val row: Row = sheet.createRow(rowNum++) @@ -200,7 +236,10 @@ open class ReportService( descriptionCell.setCellValue(if (item["description"] != null) item.getValue("description").toString() else "N/A") val clientCell = row.createCell(2) - clientCell.setCellValue(if (item["client"] != null) item.getValue("client").toString() else "N/A") + clientCell.apply { + setCellValue(if (item["client"] != null) item.getValue("client").toString() else "N/A") + } + CellUtil.setAlignment(clientCell, HorizontalAlignment.CENTER) val teamLeadCell = row.createCell(3) teamLeadCell.setCellValue(if (item["teamLead"] != null) item.getValue("teamLead").toString() else "N/A") @@ -218,7 +257,6 @@ open class ReportService( cellStyle.dataFormat = accountingStyle } - val budgetCell = row.createCell(7) budgetCell.apply { cellFormula = "G${rowNum} * 80%" @@ -267,15 +305,14 @@ open class ReportService( cellStyle.dataFormat = accountingStyle } - val unsettledAmountCell = row.createCell(14) unsettledAmountCell.apply { cellFormula = "K${rowNum}-N${rowNum}" cellStyle = boldFontCellStyle cellStyle.dataFormat = accountingStyle } - } + // Last row calculate the sum val lastRowNum = rowNum + 1 val row: Row = sheet.createRow(rowNum) @@ -364,6 +401,7 @@ open class ReportService( CellUtil.setCellStyleProperty(sumUnSettleCell,"borderTop", BorderStyle.THIN) CellUtil.setCellStyleProperty(sumUnSettleCell,"borderBottom", BorderStyle.DOUBLE) + // Fill the cell in Row 2-12 with thr calculated sum rowNum = 1 val row1: Row = sheet.getRow(rowNum) val genDateCell = row1.createCell(2) @@ -1060,7 +1098,7 @@ open class ReportService( + " cte_teamLead as (" + " select p.teamLead, p.code, t.name as teamName , t.code as teamCode" + " from project p" - + " left join team t on t.id = p.teamLead" + + " left join team t on t.teamLead = p.teamLead " + " )" + " Select p.code, p.description, c.name as client, concat(cte_t.teamCode, \' - \', cte_t.teamName) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," From a2be2c9659bb3f72728ce1f9c43c9f2a759f2378 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 15:55:17 +0800 Subject: [PATCH 07/14] [For David] - Financial Summary Report --- .../modules/data/service/DashboardService.kt | 82 +++++++++++++++++++ .../modules/data/web/DashboardController.kt | 5 ++ 2 files changed, 87 insertions(+) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index 2efb46a..e9029d1 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -10,6 +10,7 @@ import com.ffii.tsms.modules.data.web.models.SaveCustomerResponse import com.ffii.tsms.modules.project.web.models.SaveCustomerRequest import org.springframework.beans.BeanUtils import org.springframework.stereotype.Service +import java.math.BigDecimal import java.util.Optional @Service @@ -140,6 +141,87 @@ open class DashboardService( return jdbcDao.queryForList(sql.toString(), args) } + + open fun getFinancialStatus(): List> { + val sql = StringBuilder( + " with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" + + " from invoice i" + + " left join project p on p.code = i.projectCode" + + " group by p.code" + + " )," + + " cte_teamLead as (" + + " select p.teamLead, p.code, t.name as teamName , t.code as teamCode" + + " from project p" + + " left join team t on t.teamLead = p.teamLead " + + " )" + + " Select p.code, p.description, c.name as client, concat(cte_t.teamCode, \' - \', cte_t.teamName) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," + + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," + + " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount , IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount " + + " from timesheet t" + + " left join project_task pt on pt.id = t.projectTaskId" + + " left join project p ON p.id = pt.project_id" + + " left join staff s on s.id = t.staffId" + + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " left join customer c on c.id = p.customerId" + + " left join team t2 on t2.id = s.teamId" + + " left join cte_invoice cte_i on cte_i.code = p.code" + + " left join cte_teamLead cte_t on cte_t.code = p.code" + + " where p.status = \'On-going\' " + ) + + sql.append(" order by p.code") + + return jdbcDao.queryForList(sql.toString()) + } + + fun searchFinancialSummary(): List> { + val financialStatus: List> = getFinancialStatus() + + val otFactor = BigDecimal(1) + + val tempList = mutableListOf>() + + for (item in financialStatus) { + val normalConsumed = item.getValue("normalConsumed") as Double + val hourlyRate = item.getValue("hourlyRate") as BigDecimal +// println("normalConsumed------------- $normalConsumed") +// println("hourlyRate------------- $hourlyRate") + val manHourRate = normalConsumed.toBigDecimal().multiply(hourlyRate) +// println("manHourRate------------ $manHourRate") + + val otConsumed = item.getValue("otConsumed") as Double + val manOtHourRate = otConsumed.toBigDecimal().multiply(hourlyRate).multiply(otFactor) + + if (!tempList.any { it.containsValue(item.getValue("code")) }) { + + tempList.add( + mapOf( + "code" to item.getValue("code"), + "description" to item.getValue("description"), + "client" to item.getValue("client"), + "teamLead" to item.getValue("teamLead"), + "planStart" to item.getValue("planStart"), + "planEnd" to item.getValue("planEnd"), + "expectedTotalFee" to item.getValue("expectedTotalFee"), + "normalConsumed" to manHourRate, + "otConsumed" to manOtHourRate, + "issuedAmount" to item.getValue("sumIssuedAmount"), + "paidAmount" to item.getValue("sumPaidAmount"), + ) + ) + } else { + // Find the existing Map in the tempList that has the same "code" value + val existingMap = tempList.find { it.containsValue(item.getValue("code")) }!! + + // Update the existing Map with the new manHourRate and manOtHourRate values + tempList[tempList.indexOf(existingMap)] = existingMap.toMutableMap().apply { + put("normalConsumed", (get("normalConsumed") as BigDecimal).add(manHourRate)) + put("otConsumed", (get("otConsumed") as BigDecimal).add(manOtHourRate)) + } + } + } + return tempList + } } diff --git a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt index dea71c7..06d9f5f 100644 --- a/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt +++ b/src/main/java/com/ffii/tsms/modules/data/web/DashboardController.kt @@ -69,4 +69,9 @@ class DashboardController( } return result } + + @GetMapping("/searchFinancialSummary") + fun searchFinancialSummary(): List>{ + return dashboardService.searchFinancialSummary() + } } \ No newline at end of file From a74aca71eea18c2c15d3c0477981940ca20b3e36 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Fri, 17 May 2024 16:00:21 +0800 Subject: [PATCH 08/14] 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 From 6a008296358ac75d661276554e18f9aa25e46537 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 17:04:32 +0800 Subject: [PATCH 09/14] Modfiy EX01 query --- .../modules/report/service/ReportService.kt | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 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..4f3192c 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 @@ -296,7 +296,7 @@ open class ReportService( val cpiCell = row.createCell(12) cpiCell.apply { - cellFormula = "K${rowNum}/I${rowNum}" + cellFormula = "IF(K${rowNum} = 0, 0, K${rowNum}/I${rowNum})" } val receivedAmountCell = row.createCell(13) @@ -1092,31 +1092,31 @@ open class ReportService( open fun getFinancialStatus(teamLeadId: Long?): List> { val sql = StringBuilder( - " with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" + " with cte_timesheet as (" + + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate" + + " from timesheet t" + + " left join project_task pt on pt.id = t.projectTaskId" + + " left join project p ON p.id = pt.project_id" + + " left join staff s on s.id = t.staffId" + + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " left join team t2 on t2.id = s.teamId" + + " )," + + " cte_invoice as (" + + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" + " from invoice i" + " left join project p on p.code = i.projectCode" + " group by p.code" - + " )," - + " cte_teamLead as (" - + " select p.teamLead, p.code, t.name as teamName , t.code as teamCode" - + " from project p" - + " left join team t on t.teamLead = p.teamLead " + " )" - + " Select p.code, p.description, c.name as client, concat(cte_t.teamCode, \' - \', cte_t.teamName) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," - + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," - + " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount , IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount " - + " from timesheet t" - + " left join project_task pt on pt.id = t.projectTaskId" - + " left join project p ON p.id = pt.project_id" - + " left join staff s on s.id = t.staffId" - + " left join salary s2 on s.salaryId = s2.salaryPoint" + + " select p.code, p.description, c.name as client, concat(t.code, \' - \', t.name) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee," + + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed," + + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount" + + " from project p" + + " left join cte_timesheet cte_ts on p.code = cte_ts.code" + " left join customer c on c.id = p.customerId" - + " left join team t2 on t2.id = s.teamId" + + " left join tsmsdb.team t on t.teamLead = p.teamLead" + " left join cte_invoice cte_i on cte_i.code = p.code" - + " left join cte_teamLead cte_t on cte_t.code = p.code" - + " where p.status = \'On-going\' " + + " where p.status = \'On-going\'" ) - if (teamLeadId!! > 0) { sql.append(" and p.teamLead = :teamLeadId ") } From 72f69c1f05039c53861aa9265bab1ec16b782ab8 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 17 May 2024 18:29:57 +0800 Subject: [PATCH 10/14] [For David] Financial Summary --- .../modules/data/service/DashboardService.kt | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt index e9029d1..23f90c6 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/DashboardService.kt @@ -144,29 +144,30 @@ open class DashboardService( open fun getFinancialStatus(): List> { val sql = StringBuilder( - " with cte_invoice as (select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" - + " from invoice i" - + " left join project p on p.code = i.projectCode" - + " group by p.code" - + " )," - + " cte_teamLead as (" - + " select p.teamLead, p.code, t.name as teamName , t.code as teamCode" - + " from project p" - + " left join team t on t.teamLead = p.teamLead " - + " )" - + " Select p.code, p.description, c.name as client, concat(cte_t.teamCode, \' - \', cte_t.teamName) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee ," - + " s.name as staff , IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate," - + " IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount , IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount " + " with cte_timesheet as (" + + " Select p.code, s.name as staff, IFNULL(t.normalConsumed, 0) as normalConsumed, IFNULL(t.otConsumed , 0) as otConsumed, s2.hourlyRate" + " from timesheet t" + " left join project_task pt on pt.id = t.projectTaskId" + " left join project p ON p.id = pt.project_id" + " left join staff s on s.id = t.staffId" + " left join salary s2 on s.salaryId = s2.salaryPoint" - + " left join customer c on c.id = p.customerId" + " left join team t2 on t2.id = s.teamId" + + " )," + + " cte_invoice as (" + + " select p.code, sum(i.issueAmount) as sumIssuedAmount , sum(i.paidAmount) as sumPaidAmount" + + " from invoice i" + + " left join project p on p.code = i.projectCode" + + " group by p.code" + + " )" + + " select p.code, p.description, c.name as client, concat(t.code, \' - \', t.name) as teamLead, p.planStart , p.planEnd , p.expectedTotalFee," + + " IFNULL(cte_ts.normalConsumed, 0) as normalConsumed, IFNULL(cte_ts.otConsumed, 0) as otConsumed," + + " IFNULL(cte_ts.hourlyRate, 0) as hourlyRate, IFNULL(cte_i.sumIssuedAmount, 0) as sumIssuedAmount, IFNULL(cte_i.sumPaidAmount, 0) as sumPaidAmount" + + " from project p" + + " left join cte_timesheet cte_ts on p.code = cte_ts.code" + + " left join customer c on c.id = p.customerId" + + " left join tsmsdb.team t on t.teamLead = p.teamLead" + " left join cte_invoice cte_i on cte_i.code = p.code" - + " left join cte_teamLead cte_t on cte_t.code = p.code" - + " where p.status = \'On-going\' " + + " where p.status = \'On-going\'" ) sql.append(" order by p.code") From 9b369d24697af7e6dee03edf4404bde7feb979f2 Mon Sep 17 00:00:00 2001 From: Wayne Date: Sat, 18 May 2024 23:45:28 +0900 Subject: [PATCH 11/14] Add time entry for team leads --- .../modules/data/entity/TeamRepository.java | 2 + .../tsms/modules/data/service/TeamService.kt | 4 ++ .../timesheet/service/TimesheetsService.kt | 55 ++++++++++++++++++- .../timesheet/web/TimesheetsController.kt | 12 ++++ .../web/models/TeamMemberTimeEntries.kt | 7 +++ .../timesheet/web/models/TeamTimeEntry.kt | 6 ++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamMemberTimeEntries.kt create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java index 39038c0..3a3cf41 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/TeamRepository.java @@ -7,4 +7,6 @@ import java.util.List; public interface TeamRepository extends AbstractRepository { List findByDeletedFalse(); + + Team findByStaff(Staff staff); } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt index b75dc8c..b1893aa 100644 --- a/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt +++ b/src/main/java/com/ffii/tsms/modules/data/service/TeamService.kt @@ -172,4 +172,8 @@ open class TeamService( ) return jdbcDao.queryForList(sql.toString()) } + + open fun getMyTeamForStaff(staff: Staff): Team? { + return teamRepository.findByStaff(staff) + } } \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt index 04903af..48d7621 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt @@ -2,16 +2,19 @@ package com.ffii.tsms.modules.timesheet.service import com.ffii.core.exception.BadRequestException import com.ffii.tsms.modules.data.service.StaffsService +import com.ffii.tsms.modules.data.service.TeamService import com.ffii.tsms.modules.project.entity.ProjectRepository import com.ffii.tsms.modules.project.entity.ProjectTaskRepository import com.ffii.tsms.modules.project.entity.TaskRepository import com.ffii.tsms.modules.timesheet.entity.Timesheet import com.ffii.tsms.modules.timesheet.entity.TimesheetRepository +import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries import com.ffii.tsms.modules.timesheet.web.models.TimeEntry import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate import java.time.format.DateTimeFormatter +import kotlin.jvm.optionals.getOrDefault import kotlin.jvm.optionals.getOrNull @Service @@ -20,7 +23,8 @@ open class TimesheetsService( private val projectTaskRepository: ProjectTaskRepository, private val projectRepository: ProjectRepository, private val taskRepository: TaskRepository, - private val staffsService: StaffsService + private val staffsService: StaffsService, + private val teamService: TeamService ) { @Transactional open fun saveTimesheet(recordTimeEntry: Map>): Map> { @@ -52,12 +56,61 @@ open class TimesheetsService( return transformToTimeEntryMap(savedTimesheets) } + @Transactional + open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry): TimeEntry { + val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() + // Make sure current staff is a team lead + teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() + + val timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply { + val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } + val project = entry.projectId?.let { projectRepository.findById(it).getOrNull() } + val projectTask = project?.let { p -> task?.let { t -> projectTaskRepository.findByProjectAndTask(p, t) } } + + this.normalConsumed = entry.inputHours + this.otConsumed = entry.otHours + this.projectTask = projectTask + this.remark = entry.remark + } + + timesheetRepository.save(timesheet) + + return TimeEntry( + id = timesheet.id!!, + projectId = timesheet.projectTask?.project?.id, + taskId = timesheet.projectTask?.task?.id, + taskGroupId = timesheet.projectTask?.task?.taskGroup?.id, + inputHours = timesheet.normalConsumed ?: 0.0, + otHours = timesheet.otConsumed ?: 0.0, + remark = timesheet.remark + ) + } + open fun getTimesheet(): Map> { // Need to be associated with a staff val currentStaff = staffsService.currentStaff() ?: return emptyMap() return transformToTimeEntryMap(timesheetRepository.findAllByStaff(currentStaff)) } + open fun getTimeMemberTimesheet(): Map { + val currentStaff = staffsService.currentStaff() ?: return emptyMap() + // Get team where current staff is team lead + val myTeam = teamService.getMyTeamForStaff(currentStaff) ?: return emptyMap() + + val teamMembers = staffsService.findAllByTeamId(myTeam.id!!).getOrDefault(emptyList()) + + return teamMembers.associate { member -> + Pair( + member.id!!, + TeamMemberTimeEntries( + staffId = member.staffId, + name = member.name, + timeEntries = transformToTimeEntryMap(timesheetRepository.findAllByStaff(member)) + ) + ) + } + } + private fun transformToTimeEntryMap(timesheets: List): Map> { return timesheets .groupBy { timesheet -> timesheet.recordDate!!.format(DateTimeFormatter.ISO_LOCAL_DATE) } diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index 02f8f31..a24a4db 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt @@ -5,6 +5,8 @@ import com.ffii.tsms.modules.timesheet.entity.LeaveType import com.ffii.tsms.modules.timesheet.service.LeaveService import com.ffii.tsms.modules.timesheet.service.TimesheetsService import com.ffii.tsms.modules.timesheet.web.models.LeaveEntry +import com.ffii.tsms.modules.timesheet.web.models.TeamMemberTimeEntries +import com.ffii.tsms.modules.timesheet.web.models.TeamTimeEntry import com.ffii.tsms.modules.timesheet.web.models.TimeEntry import jakarta.validation.Valid import org.springframework.web.bind.annotation.GetMapping @@ -43,6 +45,16 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri return timesheetsService.getTimesheet() } + @GetMapping("/teamTimesheets") + fun getTeamMemberTimesheetEntries(): Map { + return timesheetsService.getTimeMemberTimesheet() + } + + @PostMapping("/saveMemberEntry") + fun saveMemberEntry(@Valid @RequestBody request: TeamTimeEntry): TimeEntry { + return timesheetsService.saveMemberTimeEntry(request.staffId, request.entry) + } + @GetMapping("/leaves") fun getLeaveEntry(): Map> { return leaveService.getLeaves() diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamMemberTimeEntries.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamMemberTimeEntries.kt new file mode 100644 index 0000000..084440d --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamMemberTimeEntries.kt @@ -0,0 +1,7 @@ +package com.ffii.tsms.modules.timesheet.web.models + +data class TeamMemberTimeEntries( + val timeEntries: Map>, + val staffId: String?, + val name: String?, +) diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt new file mode 100644 index 0000000..75bab5a --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt @@ -0,0 +1,6 @@ +package com.ffii.tsms.modules.timesheet.web.models + +data class TeamTimeEntry( + val staffId: Long, + val entry: TimeEntry +) From 7a9ff4c63c42c689ce21b3ec94c6f0e157789401 Mon Sep 17 00:00:00 2001 From: Wayne Date: Sun, 19 May 2024 22:31:53 +0900 Subject: [PATCH 12/14] Return full timesheet for team member --- .../timesheet/service/TimesheetsService.kt | 17 ++++++----------- .../timesheet/web/TimesheetsController.kt | 6 ++++-- .../timesheet/web/models/TeamTimeEntry.kt | 3 ++- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt index 48d7621..1324c4b 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/service/TimesheetsService.kt @@ -57,11 +57,13 @@ open class TimesheetsService( } @Transactional - open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry): TimeEntry { + open fun saveMemberTimeEntry(staffId: Long, entry: TimeEntry, recordDate: LocalDate?): Map> { val currentStaff = staffsService.currentStaff() ?: throw BadRequestException() // Make sure current staff is a team lead teamService.getMyTeamForStaff(currentStaff) ?: throw BadRequestException() + val memberStaff = staffsService.getStaff(staffId) + val timesheet = timesheetRepository.findById(entry.id).getOrDefault(Timesheet()).apply { val task = entry.taskId?.let { taskRepository.findById(it).getOrNull() } val project = entry.projectId?.let { projectRepository.findById(it).getOrNull() } @@ -71,19 +73,12 @@ open class TimesheetsService( this.otConsumed = entry.otHours this.projectTask = projectTask this.remark = entry.remark + this.recordDate = this.recordDate ?: recordDate + this.staff = this.staff ?: memberStaff } timesheetRepository.save(timesheet) - - return TimeEntry( - id = timesheet.id!!, - projectId = timesheet.projectTask?.project?.id, - taskId = timesheet.projectTask?.task?.id, - taskGroupId = timesheet.projectTask?.task?.taskGroup?.id, - inputHours = timesheet.normalConsumed ?: 0.0, - otHours = timesheet.otConsumed ?: 0.0, - remark = timesheet.remark - ) + return transformToTimeEntryMap(timesheetRepository.findAllByStaff(memberStaff)) } open fun getTimesheet(): Map> { diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index a24a4db..be9bbcc 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt @@ -51,8 +51,10 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri } @PostMapping("/saveMemberEntry") - fun saveMemberEntry(@Valid @RequestBody request: TeamTimeEntry): TimeEntry { - return timesheetsService.saveMemberTimeEntry(request.staffId, request.entry) + fun saveMemberEntry(@Valid @RequestBody request: TeamTimeEntry): Map> { + return timesheetsService.saveMemberTimeEntry(request.staffId, request.entry, runCatching { + LocalDate.parse(request.recordDate, DateTimeFormatter.ISO_LOCAL_DATE) + }.getOrNull()) } @GetMapping("/leaves") diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt index 75bab5a..9976073 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TeamTimeEntry.kt @@ -2,5 +2,6 @@ package com.ffii.tsms.modules.timesheet.web.models data class TeamTimeEntry( val staffId: Long, - val entry: TimeEntry + val entry: TimeEntry, + val recordDate: String, ) From efccb9855b01060b1a106e2cf5299840db500f49 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 20 May 2024 11:48:26 +0800 Subject: [PATCH 13/14] update --- .../data/entity/StaffSkillsetRepository.java | 5 ++++ .../modules/data/service/StaffsService.kt | 30 +++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/ffii/tsms/modules/data/entity/StaffSkillsetRepository.java b/src/main/java/com/ffii/tsms/modules/data/entity/StaffSkillsetRepository.java index f99a39a..86f2c50 100644 --- a/src/main/java/com/ffii/tsms/modules/data/entity/StaffSkillsetRepository.java +++ b/src/main/java/com/ffii/tsms/modules/data/entity/StaffSkillsetRepository.java @@ -1,6 +1,11 @@ package com.ffii.tsms.modules.data.entity; import com.ffii.core.support.AbstractRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface StaffSkillsetRepository extends AbstractRepository { + + List findByStaff(@Param("staff") Staff staff); } \ No newline at end of file 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 aaefa49..00075f3 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 @@ -95,7 +95,6 @@ open class StaffsService( @Transactional(rollbackFor = [Exception::class]) open fun saveStaff(req: NewStaffRequest): Staff { -// if (req.staffId) val checkStaffIdList: List = staffRepository.findStaffSearchInfoByAndDeletedFalse() checkStaffIdList.forEach{ s -> if (s.staffId == req.staffId) { @@ -158,31 +157,31 @@ open class StaffsService( staffSkillsetRepository.save(ss) } } - -// val skillBatchInsertValues: MutableList> -// if (!req.skillSetId.isNullOrEmpty()) { -// skillBatchInsertValues = req.skillSetId.stream() -// .map { skillId -> mutableMapOf(("staffId" to staff.id) as Pair, "skillId" to skillId) } -// .collect(Collectors.toList()) -// jdbcDao.batchUpdate( -// "INSERT IGNORE INTO staff_skillset (staffId, skillId)" -// + " VALUES (:staffId, :skillId)", -// skillBatchInsertValues); -// } - salaryEffectiveService.saveSalaryEffective(staff.id!!, salary.id!!) return staff } @Transactional(rollbackFor = [Exception::class]) open fun updateStaff(req: NewStaffRequest, staff: Staff): Staff { + val args = java.util.Map.of("staffId", staff.id) + if(!req.skillSetId.isNullOrEmpty()) { + // remove all skills of the staff + jdbcDao.executeUpdate("DELETE FROM staff_skillset WHERE staffId = :staffId", args); + // add skiils + for (skillId in req.skillSetId) { + val skill = skillRepository.findById(skillId).orElseThrow() + val ss = StaffSkillset().apply { + this.staff = staff + this.skill = skill + } + staffSkillsetRepository.save(ss) + } + } val currentPosition = positionRepository.findById(req.currentPositionId).orElseThrow() val joinPosition = positionRepository.findById(req.joinPositionId).orElseThrow() val company = companyRepository.findById(req.companyId).orElseThrow() val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null -// val skill = if (req.skillSetId != null && req.skillSetId > 0L) skillRepository.findById(req.skillSetId).orElseThrow() else null val salary = salaryRepository.findById(req.salaryId).orElseThrow() -// val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() val department = departmentRepository.findById(req.departmentId).orElseThrow() staff.apply { @@ -203,7 +202,6 @@ open class StaffsService( this.company = company this.grade = grade this.team = team -// this.skill = skill this.salary = salary this.department = department } From 74283017e3762a3bd1d70348833280dd0ec64598 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 20 May 2024 13:47:33 +0800 Subject: [PATCH 14/14] update --- .../modules/data/service/StaffsService.kt | 3 --- .../tsms/modules/user/req/UpdateUserReq.java | 2 +- .../tsms/modules/user/web/UserController.java | 25 +++++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) 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 00075f3..b9532c9 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 @@ -107,7 +107,6 @@ open class StaffsService( val grade = if (req.gradeId != null && req.gradeId > 0L) gradeRepository.findById(req.gradeId).orElseThrow() else null val team = if (req.teamId != null && req.teamId > 0L) teamRepository.findById(req.teamId).orElseThrow() else null val salary = salaryRepository.findBySalaryPoint(req.salaryId).orElseThrow() -// val salaryEffective = salaryEffectiveRepository.findById(req.salaryEffId).orElseThrow() val department = departmentRepository.findById(req.departmentId).orElseThrow() val user = userRepository.saveAndFlush( @@ -116,7 +115,6 @@ open class StaffsService( password = passwordEncoder.encode("mms1234") name = req.name phone1 = req.phone1 -// phone2 = req.phone2 ?: null email = req.email ?: null } ) @@ -141,7 +139,6 @@ open class StaffsService( this.company = company this.grade = grade this.team = team -// this.skill = skill this.salary = salary this.department = department } diff --git a/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java b/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java index 87838be..853f04b 100644 --- a/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java +++ b/src/main/java/com/ffii/tsms/modules/user/req/UpdateUserReq.java @@ -24,7 +24,7 @@ public class UpdateUserReq { private String locale; private String remarks; - @NotBlank +// @NotBlank @Email private String email; // @NotBlank diff --git a/src/main/java/com/ffii/tsms/modules/user/web/UserController.java b/src/main/java/com/ffii/tsms/modules/user/web/UserController.java index d939da1..bf27805 100644 --- a/src/main/java/com/ffii/tsms/modules/user/web/UserController.java +++ b/src/main/java/com/ffii/tsms/modules/user/web/UserController.java @@ -153,19 +153,10 @@ public class UserController{ @PatchMapping("/admin-change-password") @ResponseStatus(HttpStatus.NO_CONTENT) @PreAuthorize("hasAuthority('MAINTAIN_USER')") - public void adminChangePassword(@RequestBody @Valid ChangePwdReq req) { + public void adminChangePassword(@RequestBody @Valid AdminChangePwdReq req) { long id = req.getId(); User instance = userService.find(id).orElseThrow(NotFoundException::new); - logger.info("TEST req: "+req.getPassword()); - logger.info("TEST instance: "+instance.getPassword()); -// if (!passwordEncoder.matches(req.getPassword(), instance.getPassword())) { -// throw new BadRequestException(); -// } - PasswordRule rule = new PasswordRule(settingsService); - if (!PasswordUtils.checkPwd(req.getNewPassword(), rule)) { - throw new UnprocessableEntityException(ErrorCodes.USER_WRONG_NEW_PWD); - } instance.setPassword(passwordEncoder.encode(req.getNewPassword())); userService.save(instance); } @@ -188,6 +179,20 @@ public class UserController{ return new PasswordRule(settingsService); } + public static class AdminChangePwdReq { + private Long id; + @NotBlank + private String newPassword; + + public Long getId() { return id; } + public Long setId(Long id) { return this.id = id; } + public String getNewPassword() { + return newPassword; + } + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + } public static class ChangePwdReq { private Long id; @NotBlank