From 6241575415fbbcd2a4caada913eb580ae09a1aaa Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 16 Jul 2024 16:34:44 +0800 Subject: [PATCH] update report --- .../modules/report/service/ReportService.kt | 106 ++++++++---------- .../report/AR03_Resource Overconsumption.xlsx | Bin 13044 -> 13120 bytes 2 files changed, 45 insertions(+), 61 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 ffb9409..a013c05 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 @@ -2027,55 +2027,43 @@ open class ReportService( } open fun getProjectResourceOverconsumptionReport(args: Map): List> { - val sql = StringBuilder( - "with teamNormalConsumed as (" - + " SELECT" - + " t.projectId," - + " p.teamLead," - + " sum(t.normalConsumed) as normalConsumed," - + " sum(t.otConsumed) as otConsumed," - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed," - + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as totalBudget" - + " from timesheet t" - + " left join project p on p.id = t.projectId" - + " left join staff s on s.id = p.teamLead" - + " left join salary sal on sal.salaryPoint = s.salaryId" - + " group by p.teamLead, t.projectId, sal.hourlyRate" - + " )" - + " SELECT" - + " p.id," - + " p.code," - + " p.name," - + " t.code," - + " concat(c.code, ' - ',c.name) as client," - + " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary," - + " p.expectedTotalFee * 0.8 as plannedBudget," - + " COALESCE(tns.totalBudget, 0) as actualConsumedBudget," - + " COALESCE(p.totalManhour, 0) as plannedManhour," - + " COALESCE(tns.totalConsumed, 0) as actualConsumedManhour," - + " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) as budgetConsumptionRate," - + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) as manhourConsumptionRate," - + " CASE" - + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1" - + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1" - + " then 'Potential Overconsumption'" - + " when (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1" - + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1" - + " then 'Overconsumption'" - + " else 'Within Budget'" - + " END as status" - + " from project p" - + " left join staff s on s.id = p.teamLead" - + " left join team t on t.id = s.teamId" - + " left join customer c on c.id = p.customerId" - + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id" - + " LEFT JOIN salary sa ON s.salaryId = sa.salaryPoint" - + " left join teamNormalConsumed tns on tns.projectId = p.id" - + " WHERE p.deleted = false " - + " and p.status = 'On-going' " + val sql = StringBuilder("SELECT" + + " p.code, " + + " p.name, " + + " tm.code as team, " + + " concat(c.code, ' - ',c.name) as client, " + + " COALESCE(concat(ss.code, ' - ', ss.name), 'N/A') as subsidiary, " + + " p.expectedTotalFee * 0.8 as plannedBudget, " + + " sum(t.consumedBudget) as actualConsumedBudget, " + + " COALESCE(p.totalManhour, 0) as plannedManhour, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as actualConsumedManhour, " + + " sum(t.consumedBudget) / p.expectedTotalFee as budgetConsumptionRate, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0) as manhourConsumptionRate, " + + " case " + + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit and (sum(t.consumedBudget) / p.expectedTotalFee) <= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " + + " then 'Potential Overconsumption' " + + " when (sum(t.consumedBudget) / p.expectedTotalFee) >= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= 1 " + + " then 'Overconsumption' " + + " else 'Within Budget' " + + " END as status " + + " from " + + " (SELECT " + + " t.*, " + + " (t.normalConsumed + COALESCE(t.otConsumed, 0)) * sal.hourlyRate as consumedBudget " + + " from timesheet t " + + " left join staff s on s.id = t.staffId " + + " left join salary sal on sal.salaryPoint = s.salaryId ) t " + + " left join project p on p.id = t.projectId " + + " left join team tm on p.teamLead = tm.teamLead " + + " left join customer c on c.id = p.customerId " + + " LEFT JOIN subsidiary ss on p.customerSubsidiaryId = ss.id " + + " WHERE p.deleted = false " + + " and p.status = 'On-going' " ) - if (args != null) { var statusFilter: String = "" + if (args != null) { if (args.containsKey("teamId")) sql.append(" and t.id = :teamId") if (args.containsKey("custId")) @@ -2084,23 +2072,19 @@ open class ReportService( sql.append(" and ss.id = :subsidiaryId") if (args.containsKey("status")) statusFilter = when (args.get("status")) { - "Potential Overconsumption" -> " and " + - " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + - " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + - " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + - " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " - - "All" -> " and " + - " (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= :lowerLimit " + - " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " -// "Overconsumption" -> " and " + -// " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + -// " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " - + "Potential Overconsumption" -> " having " + + " (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " + + " and (sum(t.consumedBudget) / p.expectedTotalFee) <= 1 " + + " or (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) >= :lowerLimit " + + " and (sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) / COALESCE(p.totalManhour, 0)) <= 1 " + "All" -> " having " + + " (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " + + " or (sum(t.consumedBudget) / p.expectedTotalFee) >= :lowerLimit " else -> "" } + } + sql.append(" group by p.code, p.name, tm.code, c.code, c.name, ss.code, ss.name,p.expectedTotalFee, p.totalManhour, p.expectedTotalFee ") sql.append(statusFilter) - } return jdbcDao.queryForList(sql.toString(), args) } diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index 20503009bc676f8310a165299bd5749961ca166a..4700608072d0b307b3fa6e4c8fb2ceafb5cd05c3 100644 GIT binary patch delta 4511 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