From 4e2a6df392478b950173ef2b5b4cbeb8d64ca7f3 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Wed, 22 May 2024 13:25:16 +0800 Subject: [PATCH] update Overconsumption report --- .../modules/report/service/ReportService.kt | 77 +++++++++++------- .../modules/report/web/ReportController.kt | 27 +++--- .../modules/report/web/model/ReportRequest.kt | 5 +- .../report/AR03_Resource Overconsumption.xlsx | Bin 13568 -> 13465 bytes 4 files changed, 65 insertions(+), 44 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 f66b0b7..68bb5bc 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 @@ -1,10 +1,10 @@ package com.ffii.tsms.modules.report.service import com.ffii.core.support.JdbcDao +import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.data.entity.Salary import com.ffii.tsms.modules.data.entity.Staff import com.ffii.tsms.modules.data.entity.Team -import com.ffii.tsms.modules.data.entity.Customer import com.ffii.tsms.modules.project.entity.Invoice import com.ffii.tsms.modules.project.entity.Project import com.ffii.tsms.modules.timesheet.entity.Timesheet @@ -151,13 +151,15 @@ open class ReportService( fun generateProjectResourceOverconsumptionReport( team: String, customer: String, - result: List> + result: List>, + lowerLimit: Double ): ByteArray { // Generate the Excel report with query results val workbook: Workbook = createProjectResourceOverconsumptionReport( team, customer, result, + lowerLimit, RESOURCE_OVERCONSUMPTION_REPORT ) // Write the workbook to a ByteArrayOutputStream @@ -1059,6 +1061,7 @@ open class ReportService( team: String, customer: String, result: List>, + lowerLimit: Double, templatePath: String ):Workbook { val resource = ClassPathResource(templatePath) @@ -1089,11 +1092,11 @@ open class ReportService( rowIndex = 6 columnIndex = 0 result.forEachIndexed { index, obj -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell = sheet.getRow(rowIndex).createCell(columnIndex) tempCell.setCellValue((index + 1).toDouble()) val keys = obj.keys.toList() keys.forEachIndexed { keyIndex, key -> - tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex).createCell(columnIndex + keyIndex + 1) when (obj[key]) { is Double -> tempCell.setCellValue(obj[key] as Double) else -> tempCell.setCellValue(obj[key] as String ) @@ -1101,8 +1104,21 @@ open class ReportService( } rowIndex++ } -// tempCell = sheet.getRow(rowIndex).getCell(columnIndex) -// tempCell.setCellValue() + + val sheetCF = sheet.sheetConditionalFormatting + val rule1 = sheetCF.createConditionalFormattingRule("AND(J7 >= $lowerLimit, J7 <= 1)") + val rule2 = sheetCF.createConditionalFormattingRule("J7 > 1") + var fillOrange = rule1.createPatternFormatting() + fillOrange.setFillBackgroundColor(IndexedColors.LIGHT_ORANGE.index); + fillOrange.setFillPattern(PatternFormatting.SOLID_FOREGROUND) + + var fillRed = rule2.createPatternFormatting() + fillRed.setFillBackgroundColor(IndexedColors.ROSE.index); + fillRed.setFillPattern(PatternFormatting.SOLID_FOREGROUND) + + val cfRules = arrayOf(rule1, rule2) + val regions = arrayOf(CellRangeAddress.valueOf("\$J7:\$K${rowIndex+1}")) + sheetCF.addConditionalFormatting(regions, cfRules); return workbook } @@ -1256,26 +1272,25 @@ open class ReportService( open fun getProjectResourceOverconsumptionReport(args: Map): List> { val sql = StringBuilder("WITH teamNormalConsumed AS (" + " SELECT " - + " s.teamId, " + + " s.teamId, " + " pt.project_id, " + " SUM(tns.totalConsumed) AS totalConsumed " - + " FROM ( " - + " SELECT " - + " t.staffId, " + + " FROM ( " + + " SELECT " + + " t.staffId, " + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + " t.projectTaskId AS taskId " - + " FROM timesheet t " - + " LEFT JOIN staff s ON t.staffId = s.id " - + " GROUP BY t.staffId, t.projectTaskId " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " GROUP BY t.staffId, t.projectTaskId " + " ) AS tns " - + " INNER JOIN project_task pt ON tns.taskId = pt.id " - + " JOIN staff s ON tns.staffId = s.id " - + " JOIN team t ON s.teamId = t.id " - + " GROUP BY teamId, project_id " + + " INNER JOIN project_task pt ON tns.taskId = pt.id " + + " JOIN staff s ON tns.staffId = s.id " + + " JOIN team t ON s.teamId = t.id " + + " GROUP BY teamId, project_id " + " ) " + " SELECT " + " p.code, " - + " -- p.status, " + " p.name, " + " t.code as team, " + " c.code as client, " @@ -1286,8 +1301,8 @@ open class ReportService( + " (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) >= 0.9 and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " - + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1 " + + " 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 " @@ -1311,16 +1326,18 @@ open class ReportService( sql.append("and c.id = :custId") if (args.containsKey("status")) statusFilter = when (args.get("status")) { - "Potential Overconsumption" -> " ( and " + - "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + - "and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + - "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + - "and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + - " ) " - "Overconsumption" -> " ( and " + - "((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + - "or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1)" + - " ) " + "Potential Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 0.9 " + + " and (COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) <= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 0.9 " + + " and (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 1)" + "Overconsumption" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) >= 1 " + + " or (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) >= 1) " + "Within Budget" -> "and " + + " ((COALESCE((tns.totalConsumed * sa.hourlyRate), 0) / p.expectedTotalFee) < 0.9 " + + " and " + + " (COALESCE(tns.totalConsumed, 0) / COALESCE(p.totalManhour, 0)) <= 0.9 " else -> "" } sql.append(statusFilter) 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 b4a4bee..0037ec6 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 @@ -115,20 +115,23 @@ class ReportController( @PostMapping("/ProjectResourceOverconsumptionReport") @Throws(ServletRequestBindingException::class, IOException::class) fun ProjectResourceOverconsumptionReport(@RequestBody @Valid request: ProjectResourceOverconsumptionReport): ResponseEntity { - -// val staff = staffRepository.findById(request.id).orElseThrow() - val args: Map = mutableMapOf( - "teamId" to request.teamId, - "custId" to request.custId, - "status" to request.status + val lowerLimit = request.lowerLimit + var team: String = "All" + var customer: String = "All" + val args: MutableMap = mutableMapOf( + "status" to request.status, + "lowerLimit" to lowerLimit ) - val team: String = teamService.find(request.teamId).orElseThrow().name - val customer: String = customerService.find(request.custId).orElseThrow().name + if (request.teamId != null) { + args["teamId"] = request.teamId + team = teamService.find(request.teamId).orElseThrow().name + } + if (request.custId != null) { + args["custId"] = request.custId + customer = customerService.find(request.custId).orElseThrow().name + } val result: List> = excelReportService.getProjectResourceOverconsumptionReport(args); -// val obj: ProjectResourceReport = mapper.readValue(mapper.writeValueAsBytes(result)) - - - val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result) + val reportResult: ByteArray = excelReportService.generateProjectResourceOverconsumptionReport(team, customer, result, lowerLimit) // 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 54954c7..d08d3f1 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 @@ -25,7 +25,8 @@ data class LateStartReportRequest ( val reportDate: LocalDate ) data class ProjectResourceOverconsumptionReport ( - val teamId: Long, - val custId: Long, + val teamId: Long?, + val custId: Long?, val status: String, + val lowerLimit: Double ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx b/src/main/resources/templates/report/AR03_Resource Overconsumption.xlsx index 8ef3799c357d1ba14b447f1c01177aeeefcdfb30..9d492150c45b314a574e2634712ee02d65232cdf 100644 GIT binary patch delta 2452 zcmV;F32XL%YME)U+6Dzxq7+PN?CfQDdLpme^^|z zk`%DAW<19dhWyzSB_}u$DIqe?Oxv=$W{zp0HwYfT1cR;DG$sLytDF?F0pTRW66lMR zmZC+=<4fRj%wMX~h*@3&#ENEAzSW?*mdD=RbHO-XWnlIex!RV)uM2-ZTfe~YFK#M+enQ&CPJcR)g5e-5-b_V7wP zV3}$a&xZQgTYM2<;lhsSqE)?EsN54Jj>%#^o6L+Aj$cTTECi1iDx($v$|@ccv5+J$ zAv@rWlag_{I8rS>Pm%wiTO2F5nc%9%oaX&|w}DmMYqj|xxd!id!9B!hhF>9CpowIc z%y$qD)L9lW_t5fdXW;kTf3VjvM!~>0oPN|b{E=-NPNyGOy}(CdKl}#9@U9m#T*-7N z1O+|R9f6;=a|(U6v_GNN$51P)yi_$4io2@E%J)#vxTlWhcS)TRW{Z}PH7!V@@(Zw! zM!OT1+pNg1ml0Jt6JUu~SP+$VG0tW!JQ$bKBq8dS(QiNgl-0)%SN>JkySl$Z&W(9= zAc*Xg44e=@a7}f!e|`qYvIiPqwjQVW9$TW z==Q=O9JxwS>N@lOkD^{@wAbE~ie-wqoO8%lxLv1YJpwl=Z>T&1zQ<%oZhvHTfT9zj z$Z*iWGDf4WW4J-oaeF8jhi-HrtIS`&v!wRT`fGw^1!Y5(QK5S5sW#fp?kCsH#4he9 z)xw(w%8=dHKifD1>oaoU=IHUljpNUg`Q*aA_u+i`6kWR6pNsi<0t|7wpg zf9TLA;Hs@tRl)44Xb})rx3Ct8otVvry9vfZVy$Z7x;ecRfN~*K$yr&7@{aRWjBIu zDpu*a94(p$R_K3>m1T$;IWr#4tBCnMa}J;K!rIg$fnepD1Y)?uTzw`elm)OaWF*P=6A8ZV|=yN zplayCx^>|s-`_ArVJtwG8=9O{lu$MNcPehi8(d9xHg10p@Sz(gNr1;AfGE8;PKS%9 z8>L0n=C^(B5wL3!5L-lm-5`li36SOt0lZ)d5IaKPj+1c|o`^V)h*|{17J>bDBA{NY z0%C`NlG+xW&mOl&A$CXOlRG*xmQ3k6Hq3T=d-icgV5G=f~*)%ITUGj zA0?pPI2%lz!)I7kw`mm>L6oF7L6XE}P?Xz65GJcQE$3xg#K{-THtRETgB5mNuZ}cW z$svM&?o=Q7`Y}EGi*J9{t|g0Nj*qNPWM${MOHlIeFb7K&*%##AkR1n|Zty{KV{ETF zJb6rbHD7>Y)3m=r$c_He`%ff7=O5Lgu8~I-gJ+Sy`y!_1J<8ye(++uWK0N*!RDaZ+ zf2~44VdOw}IH5oh8?>Uu@0LTSZSAuT{W973cf6}Fx4sfT_bmjXY`y`Lp#v1NTND!* z1@D_UN=>tKEG7YeL2JVx7>4hI{fFQ^F{W!*BuQZ{J*_ZU#>Q@W_ReXFvE_S^%tpSUe7LQRWX>;t%?@kE4{QfGiQ^fT;Fw^J z;T9O`fru_h!3U&1nyLc`j-dhx%2`Yzf`7UWjI@6q=#o)WmhPh&A) zqVO*4p9P delta 2573 zcmV+o3i9=tX@F|5+6D#b0|EWklivm&f8sb4{l3!vhfGvyKBbBC;1Lx@K!~XSxG0@SqrE8>! zSwf4af&OI~8a-VT5*G>1SV0E*D-rsq;qSlQY#D!EGxn?jKp_TtDrM=LrifFLe`9gY zN>aeehVdLr81g4ml$_v1q=d*kGi}T2m^r3}-XQq=5)8K4(3k`)u5wby285FgOQ0`O zT8b7ek1v7CF@LT~BW8IC5Nn!I`C5bOS|0niPX*(6oq^dq?ZVWk{~c$qQ96Jh0-Oce3gnusUyG$oge81k$Rt z;)CGKpGAaU4R`HmKNRqsS z?0`2;O2%b$q*{EQBL6|RI96^m!Bvep&HMLm1FN{-YV%%l4c>2qJBZH$zd*D=6UjcA zZy_AWSrsw&(dw(^jDwzIe3?wqiEWPZ$LaqYxfLe>EqJ%p*#5aRq91 zuN8GdjOLaDT5A-c{Ry=`hFV$WrK*`w+*LhRzK4RweRVXyP3n{|TeO61Xh9N{Ux0ly z+Mlr6Wkr6y3aQGO086~af~d5MakgmT!MKzr2~oF|YpSdDf73^n-B)oUJ9#H$-9su-a42%EQP=9bhBb9O!|nBLqvtxdF>wQX z>UF2VbX+S+U1$FPQPk^<_S<_>u}m?SOAgrzx9gm2#^5I94V6d0_n7S1>y0f3D7qmE z4Hxw-V?6G-h8Kj6*G0i(>V-=F4OwOW=A9+AXVza6EGsA*Q>qNrV_&u5e)cfAX(sk@ zKdBb}JWz)0zy8_A1z4Yv3pa-k7jB$jb0KV0iv6{+)*Q*RYJuU_lg3*8QL?AuI3*<%(hZJ>osG>146Acv^J)h z27#t)CW!WB!)ll*f29({z+bfrbfXw4+$K>4;c%D)g76w02vc_*a!V6{0w z6sw2<{d&zC-O&WS1Sg1k-ZTR$MFWT>uejMeDDniI-mPk>$g+aEcQ_{0+bQ^j@Qu1O zZv0gy!Ksp3mc|EiaM1j^{4_`dLVMEd`Y*!ZI8f|`+Xfy{f8qNi;Ze^DkFe;Su%w6C z<5JTNFEaEe4)gKNZHNbX6h(s==i|ZkIL-(0_2l+bzKCJ&_Ai>xoKNo6eAbW^dtm0N zxmRAv&7-`B0q`mc=Chz*Rd6tbJp@m&GDFvR`ZbL061U<9XIp*p>>09rU=^c=fhLT+ z-%^SPN-Flve;T%eu_0UqYx1R(&vyGAEF4;0ZktU+sNpXxXB8~}f&rp`x!m@-*JihN zI!{$B+{J*B3RaR$WpY{l4hG`1- z0F?QW%}HeiWx}r~xV_I{W3qE`dw`GpBuzs+83RO{f4#Lfbee9ok)qA-#PZ36$XDF%mHfOsRMWJun%2x z|9>`T5e$Mmhu|cd99w*P5BiGiy>he94-f-?d{SLcgFq1cE0=q3fflJYrAdo6){n%f zf8c}#E?#eOS^SLu-KDMC2jd$v*_)Z2WoA0Ym30iH2+CA1lIc|xYxPRg&K9Zu2(#=g z$)d#|ij&hp9FLcS#d38t2;<{%vK%dw#W?=d!F;v>svraBT9sqxIn|LqVE6Ko$oJEK z!SDEd*3{^MZGJIG`4<2H0RR6308mQ<1QYBvQm(t3s|(s@9$Es zDX$DXn@WRm1=zrol%-r}=-`~D7+Zb-$!z2s%7?qkNap;1((JKj{K)oz6GEm~f@6X? zh8tk0dm_3b1@DpiWXcvGIEE4=C}(jT#rV7Hz)1V&fi@X5W#KN`zv-$wWDDMfefw;S zzF60FRF7H>?;qp$&C^T!)UZ&^tT>=N6`XPd%vB~oSuiOcY&63wnROdK=UoBf@sh8= zz%f`Jm`d?T@6^*;`3?yp@H(`Uza?*@10mF$$)qR@iXS&yzng zEgWWc@t73{007n*000;O0000000000000000u7TK4H}b0G!T>Z6A%ml0000000000 z08JE=&N4p&5gU^aGdTgmlSwl{0wyApj5ALHC?k{JA}NywG!PsoUN_NB0RRAB0ssIJ j0000000000000000AMVWGBiB_<&$tUBL>7V00000)K