From 164e387b69cf6359e21376470b556a6d75128803 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 11 Jul 2024 18:14:18 +0800 Subject: [PATCH 1/8] fix for backgroud black (cross team report) --- .../modules/report/service/ReportService.kt | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 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 a9a6b25..d429f5f 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 @@ -2921,8 +2921,12 @@ open class ReportService( createCell(1).apply { setCellValue(team.code) - cellStyle = normalFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } } @@ -2939,8 +2943,11 @@ open class ReportService( sortedGrades.forEach { grade: Grade -> createCell(columnIndex++).apply { setCellValue(grade.name) - cellStyle = boldFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } } @@ -2965,8 +2972,11 @@ open class ReportService( columnIndex = 0 createCell(columnIndex++).apply { setCellValue(chargedTeam.code) - cellStyle = normalFontWithBorderStyle - CellUtil.setAlignment(this, HorizontalAlignment.CENTER) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } } var totalSalary = 0.0 @@ -2993,14 +3003,21 @@ open class ReportService( createCell(columnIndex++).apply { val lastCellLetter = CellReference.convertNumToColString(this.columnIndex - 1) cellFormula = "sum(B${this.rowIndex + 1}:${lastCellLetter}${this.rowIndex + 1})" - cellStyle = boldFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } } createCell(columnIndex).apply { setCellValue(totalSalary) - cellStyle = boldFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } } } } From 4222a1b551ee6bf0da7d9526c5852399f15d45e3 Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Fri, 12 Jul 2024 12:01:19 +0800 Subject: [PATCH 2/8] add color order api --- .../modules/data/service/DashboardService.kt | 49 +++++++++++++++++++ .../modules/data/web/DashboardController.kt | 16 ++++++ 2 files changed, 65 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 5a03276..681013c 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 @@ -463,6 +463,55 @@ open class DashboardService( return jdbcDao.queryForList(sql.toString(), args) } + fun searchTeamConsumptionColorOrder(args: Map): List> { + val sql = StringBuilder( + "select" + + " ROW_NUMBER() OVER (ORDER BY p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone) AS id," + + " p.id as id," + + " p.id as projectId," + + " p.code as projectCode," + + " p.name as projectName," + + " te.code as team," + + " s.name as teamLead," + + " GROUP_CONCAT(DISTINCT tg.name ORDER BY tg.name) as expectedStage," + + " p.totalManhour as budgetedManhour," + + " COALESCE (sum(t.normalConsumed) + sum(t.otConsumed),0) as spentManhour," + + " COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) as remainedManhour," + + " coalesce (round(((sum(t.normalConsumed) + sum(t.otConsumed))/p.totalManhour)*100,2),0) as manhourConsumptionPercentage," + + " COALESCE (DATE_FORMAT(milestonePayment.comingPaymentMilestone, '%Y-%m-%d'),'NA') as comingPaymentMilestone," + + " case" + + " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) > 0 then 0" + + " when COALESCE (p.totalManhour - sum(t.normalConsumed) - sum(t.otConsumed),0) <= 0 then 1" + + " end as alert" + + " from project p" + + " left join project_task pt on p.id = pt.project_id" + + " left join timesheet t on pt.id = t.projectTaskId" + + " left join team te on p.teamLead = te.teamLead" + + " left join staff s on te.teamLead = s.id" + + " left join milestone m on p.id = m.projectId and curdate() >= m.startDate and curdate() <= m.endDate" + + " left join task_group tg on m.taskGroupId = tg.id" + + " left join (" + + " SELECT pid, MIN(comingPaymentMilestone) AS comingPaymentMilestone" + + " FROM (" + + " SELECT p.id AS pid, mp.date AS comingPaymentMilestone" + + " FROM project p" + + " LEFT JOIN milestone m ON p.id = m.projectId" + + " LEFT JOIN milestone_payment mp ON m.id = mp.milestoneId" + + " WHERE p.teamLead in (:teamIds)" + + " AND p.status NOT IN ('Pending to Start', 'Completed', 'Deleted')" + + " AND mp.date >= CURDATE()" + + " ) AS subquery" + + " GROUP BY pid" + + " ORDER BY comingPaymentMilestone ASC" + + " ) milestonePayment on milestonePayment.pid = p.id" + + " where p.teamLead in (:teamIds)" + + " and p.status not in ('Pending to Start','Completed','Deleted')" + + " group by p.id, p.code, p.name, te.code, s.name, p.totalManhour, milestonePayment.comingPaymentMilestone" + + " ORDER BY te.code ASC" + ) + return jdbcDao.queryForList(sql.toString(), args) + } + fun searchFinancialSummaryCard(args: Map): List> { val sql = StringBuilder( "select" 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 e8a4d66..5820575 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 @@ -117,6 +117,22 @@ class DashboardController( result = dashboardService.searchTeamConsumption(args) return result } + @GetMapping("/searchTeamConsumptionColorOrder") + fun searchTeamConsumptionColorOrder(request: HttpServletRequest?): List> { + val args = mutableMapOf() + val teamIdList = request?.getParameter("teamIdList") + val tableSorting = request?.getParameter("tableSorting") + val teamIds = teamIdList?.split(",")?.map { it.toInt() }?.toList() + var result: List> = emptyList() + if (teamIds != null) { + args["teamIds"] = teamIds + } + if (tableSorting != null) { + args["tableSorting"] = tableSorting + } + result = dashboardService.searchTeamConsumptionColorOrder(args) + return result + } @GetMapping("/searchFinancialSummaryCard") fun searchFinancialSummaryCard(request: HttpServletRequest?): List> { val authority = dashboardService.viewDashboardAuthority() From b0e0f5645375cacf7705b61b3dde1b1243a07f94 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 12 Jul 2024 16:58:14 +0800 Subject: [PATCH 3/8] update cross team report --- .../modules/report/service/ReportService.kt | 42 ++++++++++++++++-- .../report/Cross Team Charge Report.xlsx | Bin 18521 -> 18555 bytes 2 files changed, 39 insertions(+), 3 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 f37d223..fdb23b2 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 @@ -2975,10 +2975,11 @@ open class ReportService( } // if (timesheets.isNotEmpty()) { - val combinedTeamCodeColNumber = grades.size + val combinedTeamCodeColNumber = grades.size * 2 val sortedGrades = grades.sortedBy { it.id } val sortedTeams = teams.sortedBy { it.id } + logger.info(timesheets.filter { it.project?.teamLead?.team?.id != it.staff?.team?.id }.size) val groupedTimesheets = timesheets .filter { it.project?.teamLead?.team?.id != it.staff?.team?.id } .groupBy { timesheetEntry -> @@ -3060,6 +3061,16 @@ open class ReportService( alignment = HorizontalAlignment.CENTER } } + + createCell(columnIndex++).apply { + val cellValue = grade.name.trim().substring(0,1) + grade.name.trim().substring(grade.name.length - 1) + " - Cost Adjusted by Salary Point" + setCellValue(cellValue) + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) + cellStyle = cloneStyle.apply { + alignment = HorizontalAlignment.CENTER + } + } } createCell(columnIndex++).apply { @@ -3106,8 +3117,26 @@ open class ReportService( grade.id )]?.sumOf { it.getValue("salary") } ?: 0.0 - cellStyle = normalFontWithBorderStyle - cellStyle.dataFormat = accountingStyle + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } + } + + createCell(columnIndex++).apply { + setCellValue( + groupedTimesheets[Triple( + team.id, + chargedTeam.id, + grade.id + )]?.sumOf { it.getValue("manHour") * it.getValue("salary") } ?: 0.0) + + val cloneStyle = workbook.createCellStyle() + cloneStyle.cloneStyleFrom(normalFontWithBorderStyle) + cellStyle = cloneStyle.apply { + dataFormat = accountingStyle + } } } @@ -3149,6 +3178,13 @@ open class ReportService( cellStyle = normalFontWithBorderStyle cellStyle.dataFormat = accountingStyle } + + createCell(columnIndex++).apply { + val currentCellLetter = CellReference.convertNumToColString(this.columnIndex) + cellFormula = "sum(${currentCellLetter}${startRow}:${currentCellLetter}${endRow})" + cellStyle = normalFontWithBorderStyle + cellStyle.dataFormat = accountingStyle + } } createCell(columnIndex++).apply { diff --git a/src/main/resources/templates/report/Cross Team Charge Report.xlsx b/src/main/resources/templates/report/Cross Team Charge Report.xlsx index e3c005a23f087aa5389b235725ec40af2280a760..1a4889b5f76dbfa6621382f37ca1a82bcb1d7c7b 100644 GIT binary patch delta 2147 zcmV-p2%PuXkOBLU0kG@_e`3=@)0zVS02v7Y01p5F0C;RKcW-iQVsCG2E_iKhtW{la zqc{|OUupk?#na9M1rs0%YB~ym>{Mz-QIc(Uq)3$mPO%D@u}za`H2;0C$!D9hKO+DrWzWczsshWBNk$7IuXsgESXpvW5Cuc=WXVcUlFE!yRpgdq+rCv0 zRvKG`KwN^smrIt=h$nSHOSJ_NG$#t^%ZycWk5(j?z!gM1*Oi&@q5_Bo%bD6Vpav?E z;P$EHf-G_{dyPG`e-`lb;g4--oc&F(baqw262T>3sw;rDwxrMO$F_y>D;KQKeQ+6) zYYF(MkZ$dncNA;N}=N*Grbu`>k{lsjAPU(2>dw zloP4qlqs6_j5ZAUiXJDxDp6Nq&2m_E+HJ=-teb_xGX_?NY zNai|w<^aHJkx)5Tw5T8x;7!nq3pGDcC^=7u|DjQmXct-FmM!(|`&$=*1tQq%aI6G^ z_uJ?WVlgEze-Ignf4WQDTW~#YJ(h_G@Z&e9)9LuWZJUncyQYgnY<6O&WqP*P3FEHg zMp64a7$f{3;iOjCF6bJ%XSfjIv!xMP?^m!L)GR&H_-5}OQ}=J0{n~eJur6=+jIQKS z9(st@53Ef2s%Mx^r|W=Oo5Mu61;bUd{J>I`fx^1Be|wnwMA=gYoUzx|=O7U@x1RBe zQ?%tYgvZp)D^Au4siu?yxu!>DBmZ!N{uU&q=&vb^5s@gVg#ZD6fI6;AuXa7uE)nQ^ z#chhUrEd_XBqw5n#+;Sv*ckHV;b`TqW7(*H7x|J`3<{m`M?fa|uAskN8M&3}^-K^2p_L=LmV3OoW16)Tin zxdQ+I`3V337yy$VMH+vsl+kY6AQXo0C+!_9?qeIC6Cq?kWwBf2Q5DsG$u$rop*Q)|OJAxL077jFw6?aHM3JFuCW#4E!)kx{QYkHog;%|bOrseo z{Uj-(B#!e)5?-S{VWN*=sH)aFXcfO*5cMHh@@i z!R@PuqCnEg{i;@)ECuAf!x^E$PQx#OC+(K;{U276oN8rMWkVoGd(9uyA4lVe(4O=$ z|D!NCi!^)T?f`!esN^(Bc-XU&11voemiI8XU3#+NWr5z(;V6x-^LUWsD?G^N+0Ed~ z=oSxdGo0OKahhdA{2R@tzD^c;Hf_j?Juv&!EObzD`=}Ps0kw*v*)-}$l^hDfZ9Fyr5=Ox{=%-AfZz##d_#s)k|i*i;Ef2mS-eoo%67$Pi@2pnz_S zRkB^l?_hrqfdlsth%ICkCV1q8bO01AnXz;3l>Czg=j~Y#TUMH$49i*=#IuD#GF}+O z^M&Ot4C2|s;B34wi02D~JKMt`o-J(LvLK!>41=lmmFnEJFV)kl4EJ>pjsGC7a{xYA zAW8q?qoZwrJ0G}4D=&iVqhr1Q={v zOtYmvC;@*@gD@C|?#t@<8!QNVQMVYrgT9$TOBH!HP;jidryemEGPXyr#U;@T{vE zw3T2yM^c9JIzjx6~YaTr>X3_JAWo=C}mQ1hWixK))V| z=!_J6MAk=>Xh0o*opq;d!G~~cpNt%fP16L;tk-b*WU|2+!%zLH?4w&DM|k24xWm zW1q}@66fKPh6@^zkM8oT^PS$XQJxk4B}R8XS@>a;lNpT;V;bL2h!;TCdtZNv>>HDz z3KX*@L01C_V$(v?ngaj;8Iy)YP64Hp)kG~D6)TinxdQ+I`3V337ytkO0000000000 z008fk9YsX}-jicRMFEkMsYO5mRg>yPIsv1TB}PF404bCFDI1f6Ml=EfDwF&vC6nGp zDgp&7lQ1+SlP*Uu0yZ?0Ff=KXh({^`XOpu>BO4fOTTD&?003VC000pH0000000000 Z00000wUhBjJ^?zDFi0Z?6+{34001PI?*{+? delta 2117 zcmV-L2)g(CkOA3{0kG@_f5)WEa-0JI02m1X01p5F0C;RKcW-iQVsCG2E_iKhtW{la z;y4t2Uupk?m8ac@G~XeKN=Jl{ol0edfNggrq{<|wSWV)r?G(^x{`+1B0<;~C*v>{^ z$6v?i-h1xN^`~uFpcldgWmR9Zu1pP)Dq}gVp8DFK^HdKsBqXkKe_XJN^tBxk+NYae zf4SZ;{=8)D838~g`r2B`Ix-BAtx1W+6{|@FD=WrJEMdr>3{i7}bFn5wmW5%Prf-y( zR@xpR;+J5s)rw{$VOdj>O71~8DX;|kVoht&p_SPsa3$u?O|56FtN~(43o3UlsD{cc zx_zn`$IAlD-dY~oe{%Tw@V888?CvI5dUsVxGtLBC$t!?1_N3q0Z<&T=y>h|!y$>!! zat%&isEXl{756{L%0FWHCos$WX*3I@)oLXI=Y0^(J94wNo9h)V$o*cr2-o!&T&hSF z8Y-}mqnt{T_q84j*@m1Zz$)I{$Q+Yj{S)LXxtEOn^5|YR2W_OriLFI{XKX;!L^70JrR^@893L2rS@Hr^AWl z2;Og#JBY;$f4@LvApZFwac{x(mh)I;JhC3Y_5v^I^~^+1UC-BDXB6vU8oRm``k`-I zo-+)bZ(t1fqm1E3t`9+1(0$E?2)|nz<88NMnNdUY6OFIt;n7w9rs>wcDT7scyQgF$ zPV!Jgw0)pe&Nh8bw>=L+hj!hB>jz-vrd@uZxm<(7f1J=g&V45IX$_o%Fo0u_2Z~x> zdqpYPQ%d2{Rr89HamJ~wq`D$)N@THCyey04XDxyD@1Iph(1 z4~f|fAzt_78vbm zf@K2UuktvWCd%)JJkvkgI0Neoa^dFm{=$vJFXQ?6!o7E+ z`Qt-+>E^*WP6n6nA55o%zvrXBx+{$DI&>d!)wVJ?LSz4Cy5g$1w^R2wy1rXsTS z=&JqqT^jsVW{ zMz^BGFT&#U>DMolwKh+7DTG5nXl;p?&UNEBwtOiRvytvZ3x7{)Z4`6x*DPY&8NsSB zNi}hrBt@*4Z1I*bHpeh@T}v*mG+!yvx-Gz4`l->sx1H`?mjML>?Xn%}oSibpZyd(!*< zkHQcd8}Tgt27ew<>1mQkzGtNeSau?;*u%VCX1tPBiC?pPm?f7*GAPIe8PFNM8hjgG zlfgA5^qM9ar8)VFr<0JAJ2RbhY$5K2d+hEEs-=6-cW?u;isR`d-jAvz1j3tujHpX| zLB=wZz&v;-(h9A+zay)dy#K7%)+UFekxJ3RpTyo^GjB4S>jPdl2LY+ zs*j|$J`x`Afk7b)ovEo7B#LS1hm=~4hI{Rhci6K!ovPz&qmX@$WY8@q+%Ya6Cf zl62O8zo@OVZ0wl4Z=O8)NU}bch4%&8$V#Os3<&Z-@k&T_NYQJ)^;gIcu2 zBSIFq1j_`o40k}k9*O9J6nsS1CzFam9e-VPr)Ci#`n!L?6kNFm55eZ|TEPN8@;fjV!8j+9g@|*LW-mp<#6#gYfbDu1IGS5j&V-7;e zd_ueevflglOJv^wlVLp+vq(W#0}02Z%yOIq000=1s6FlMF^W0)Z!! zzb6)xUPdYc-YAnEDjSofMl=H8DU%*5C6Y%f0^=%^AuB19RYxiT6qA5QQ~^bk)kh;6 vcP5}S_UjhIC5dZ)H0000000000005Jd6G%P*7?V;+BL?$B00000_BZS4 From 7562edd36db0131c1f28558f1373916fbd9721f4 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 12 Jul 2024 17:33:55 +0800 Subject: [PATCH 4/8] update cash flow report --- .../modules/report/service/ReportService.kt | 199 +++++++++++------- 1 file changed, 125 insertions(+), 74 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 fdb23b2..e9c98bc 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 @@ -855,6 +855,46 @@ open class ReportService( combinedResults.forEach { result: String -> if (groupedInvoices.containsKey(result)) { + if (dateType == "Date") { + groupedInvoices[result]!!.forEachIndexed { _, invoice -> + sheet.createRow(rowIndex++).apply { + createCell(0).apply { + setCellValue(result) + } + + createCell(1).apply { + setCellValue(0.0) + cellStyle.dataFormat = accountingStyle + } + + createCell(2).apply { + setCellValue(invoice["paidAmount"] as Double? ?: 0.0) + cellStyle.dataFormat = accountingStyle + } + + createCell(3).apply { + val lastRow = rowIndex - 1 + if (lastRow == 16) { + cellFormula = + "C{currentRow}-B{currentRow}".replace("{currentRow}", rowIndex.toString()) + } else { + cellFormula = + "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace( + "{currentRow}", + rowIndex.toString() + ).replace("{lastRow}", lastRow.toString()) + } + cellStyle.dataFormat = accountingStyle + } + + createCell(4)?.apply { + setCellValue( + "Invoice Receipt: " + (invoice["invoiceNo"] as String? ?: "N/A").toString() + ) + } + } + } + } else { // groupedInvoices[result]!!.forEachIndexed { _, invoice -> sheet.createRow(rowIndex++).apply { createCell(0).apply { @@ -888,9 +928,16 @@ open class ReportService( createCell(4)?.apply { // setCellValue(invoice["description"].toString()) - setCellValue("Invoice Receipt: " + (groupedInvoices[result]?.map { it["invoiceNo"] }?.joinToString() ?: "N/A").toString()) + val invoiceNos = groupedInvoices[result]?.map { it["invoiceNo"] } + if (invoiceNos?.size != null && invoiceNos.size > 1) { +// setCellValue("Invoice Receipt: " + (groupedInvoices[result]?.map { it["invoiceNo"] }?.joinToString() ?: "N/A").toString()) + setCellValue("Multiple Invoices") + } else { + setCellValue("Invoice Receipt: " + (invoiceNos?.joinToString() ?: "N/A").toString()) + } } // } + } } } @@ -1353,7 +1400,7 @@ open class ReportService( for (i in 0 until rowSize) { tempCell = sheet.getRow(8 + i).createCell(columnIndex + index) tempCell.setCellValue(0.0) - if ( 8+i in holidayIndexList) { + if (8 + i in holidayIndexList) { tempCell.cellStyle = fillOrange } tempCell.cellStyle.dataFormat = accountingStyle @@ -1365,7 +1412,7 @@ open class ReportService( dayInt = temp[index]["date"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) tempCell.setCellValue((temp[index]["normalConsumed"] as Double) + (temp[index]["otConsumed"] as Double)) - if ( dayInt.plus(7) in holidayIndexList) { + if (dayInt.plus(7) in holidayIndexList) { tempCell.cellStyle = fillOrange tempCell.cellStyle.dataFormat = accountingStyle } @@ -1377,7 +1424,7 @@ open class ReportService( for (i in 0 until rowSize) { tempCell = sheet.getRow(8 + i).createCell(columnIndex) tempCell.setCellValue(0.0) - if ( 8+i in holidayIndexList) { + if (8 + i in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1388,7 +1435,7 @@ open class ReportService( dayInt = leave["recordDate"].toString().toInt() tempCell = sheet.getRow(dayInt.plus(7)).createCell(columnIndex) tempCell.setCellValue(leave["leaveHours"] as Double) - if ( dayInt.plus(7) in holidayIndexList) { + if (dayInt.plus(7) in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1420,7 +1467,7 @@ open class ReportService( tempCell = sheet.getRow(rowIndex).createCell(columnIndex) tempCell.cellFormula = "SUM(${getColumnAlphabet(2)}${rowIndex + 1}:${getColumnAlphabet(columnIndex - 2)}${rowIndex + 1})" // should columnIndex - 2 - if ( rowIndex in holidayIndexList) { + if (rowIndex in holidayIndexList) { tempCell.cellStyle = fillOrange } @@ -1495,21 +1542,21 @@ open class ReportService( } val cell2 = getCell(2) ?: createCell(2) - cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex+1).toString()) + cell2.cellFormula = "(B{currentRow}+D{currentRow})-1".replace("{currentRow}", (rowIndex + 1).toString()) CellUtil.setAlignment(cell2, HorizontalAlignment.CENTER) cell2.cellStyle.dataFormat = defaultStyle // getCell(2).cellStyle.dataFormat = accountingStyle - val cell3 = getCell(3)?:createCell(3) + val cell3 = getCell(3) ?: createCell(3) cell3.setCellValue(salary.increment.toDouble()) cell3.cellStyle = fillOrange cell3.cellStyle.dataFormat = defaultStyle CellUtil.setAlignment(cell3, HorizontalAlignment.CENTER) - val cell4 = getCell(4)?:createCell(4) + val cell4 = getCell(4) ?: createCell(4) cell4.cellFormula = - "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex+1).toString()) + "(((C{currentRow}+B{currentRow})/2)/20)/8".replace("{currentRow}", (rowIndex + 1).toString()) // getCell(4).cellStyle.dataFormat = accountingStyle cell4.cellStyle.dataFormat = accountingStyle CellUtil.setAlignment(cell4, HorizontalAlignment.CENTER) @@ -1979,52 +2026,53 @@ 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," - + " tns.totalConsumed," - + " 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( + "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," + + " tns.totalConsumed," + + " 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' " ) if (args != null) { var statusFilter: String = "" @@ -2041,6 +2089,7 @@ open class ReportService( " 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 " @@ -2105,17 +2154,17 @@ open class ReportService( + " order by g.code, s.name, cte_ts.recordDate" ) val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId) - val compareStartMonth= YearMonth.parse(startMonth) - val compareEndMonth= YearMonth.parse(endMonth) + val compareStartMonth = YearMonth.parse(startMonth) + val compareEndMonth = YearMonth.parse(endMonth) val actualStartMonth: YearMonth - (if (projectPlanStartEndMonth.actualStart == null){ + (if (projectPlanStartEndMonth.actualStart == null) { YearMonth.now().plusMonths(1) - }else{ + } else { YearMonth.from(projectPlanStartEndMonth.actualStart) }).also { actualStartMonth = it } - val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth - val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth + val queryStartMonth = if (compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth + val queryEndMonth = if (compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth val args = mapOf( "projectId" to projectId, @@ -2251,7 +2300,8 @@ open class ReportService( val manhoursSpentList = getManhoursSpent(projectId, startMonth, endMonth) - val workbook: Workbook = createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth, projectId) + val workbook: Workbook = + createPandLReportWorkbook(PandL_REPORT, manhoursSpentList, startMonth, endMonth, projectId) val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() workbook.write(outputStream) @@ -2274,17 +2324,17 @@ open class ReportService( val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") val projectPlanStartEndMonth = projectRepository.findProjectPlanStartEndByIdAndDeletedFalse(projectId) - val compareStartMonth= YearMonth.parse(startMonth) - val compareEndMonth= YearMonth.parse(endMonth) + val compareStartMonth = YearMonth.parse(startMonth) + val compareEndMonth = YearMonth.parse(endMonth) val actualStartMonth: YearMonth - (if (projectPlanStartEndMonth.actualStart == null){ + (if (projectPlanStartEndMonth.actualStart == null) { YearMonth.now().plusMonths(1) - }else{ + } else { YearMonth.from(projectPlanStartEndMonth.actualStart) }).also { actualStartMonth = it } - val queryStartMonth = if(compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth - val queryEndMonth = if(compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth + val queryStartMonth = if (compareStartMonth.isAfter(actualStartMonth)) compareStartMonth else actualStartMonth + val queryEndMonth = if (compareEndMonth.isAfter(YearMonth.now())) YearMonth.now() else compareEndMonth val monthFormat = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH) val startDate = YearMonth.parse(queryStartMonth.toString(), DateTimeFormatter.ofPattern("yyyy-MM")) @@ -2294,7 +2344,7 @@ open class ReportService( val monthRange: MutableList> = ArrayList() var currentDate = startDate - if (currentDate == endDate){ + if (currentDate == endDate) { monthRange.add( mapOf( "display" to YearMonth.of(currentDate.year, currentDate.month).format(monthFormat), @@ -2575,7 +2625,7 @@ open class ReportService( } // CellUtil.setCellStyleProperty(panlCellTitle, "borderBottom", BorderStyle.DOUBLE) // CellUtil.setCellStyleProperty(panlCell, "borderBottom", BorderStyle.DOUBLE) - val profitAndLossRowNum = rowNum+1 + val profitAndLossRowNum = rowNum + 1 rowNum += 1 val uninvoicedAmountRow: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) @@ -2585,7 +2635,7 @@ open class ReportService( } val uninvoicedAmountCell = uninvoicedAmountRow.getCell(1) ?: uninvoicedAmountRow.createCell(1) uninvoicedAmountCell.apply { - setCellValue(if ((info.getValue("expectedTotalFee") as Double) < 0.0 ) 0.0 else info.getValue("expectedTotalFee") as Double) + setCellValue(if ((info.getValue("expectedTotalFee") as Double) < 0.0) 0.0 else info.getValue("expectedTotalFee") as Double) cellStyle.dataFormat = accountingStyle } @@ -2597,7 +2647,7 @@ open class ReportService( } val projectedPnLCell = projectedPnLRow.getCell(1) ?: projectedPnLRow.createCell(1) projectedPnLCell.apply { - cellFormula = "B${profitAndLossRowNum}+B${profitAndLossRowNum+1}" + cellFormula = "B${profitAndLossRowNum}+B${profitAndLossRowNum + 1}" cellStyle.dataFormat = accountingStyle } @@ -3063,7 +3113,8 @@ open class ReportService( } createCell(columnIndex++).apply { - val cellValue = grade.name.trim().substring(0,1) + grade.name.trim().substring(grade.name.length - 1) + " - Cost Adjusted by Salary Point" + val cellValue = grade.name.trim().substring(0, 1) + grade.name.trim() + .substring(grade.name.length - 1) + " - Cost Adjusted by Salary Point" setCellValue(cellValue) val cloneStyle = workbook.createCellStyle() cloneStyle.cloneStyleFrom(boldFontWithBorderStyle) From 407e934e050ae55f0ac6a4c8afc1c3933e482493 Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Sun, 14 Jul 2024 21:51:27 +0800 Subject: [PATCH 5/8] update api --- .../java/com/ffii/tsms/modules/data/service/DashboardService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 681013c..f50aeec 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 @@ -1796,7 +1796,7 @@ open class DashboardService( + " WHERE" + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN" + " :startdate AND" - + " LAST_DAY(:startdate)" + + " CURDATE()" + " AND DAYOFWEEK(DATE(:startdate + INTERVAL numbers.num DAY)) BETWEEN 2 AND 6" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (select ch.date from company_holiday ch where ch.deleted = 0)" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (:publicHolidayList)" From d36ab710dd55e3ab2c163bd5bd5db7aa492e9400 Mon Sep 17 00:00:00 2001 From: "Mac\\David" Date: Mon, 15 Jul 2024 10:32:03 +0800 Subject: [PATCH 6/8] unsubmit api --- .../tsms/modules/data/service/DashboardService.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 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 f50aeec..6c210c4 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 @@ -1717,7 +1717,11 @@ open class DashboardService( + " SELECT 5 as num" + " ) numbers" + " WHERE" - + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN :startdate AND DATE_ADD(:startdate, INTERVAL 6 DAY)" + + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN :startdate AND" + + " case" + + " when curdate() < DATE_ADD(:startdate, INTERVAL 6 DAY) then curdate()" + + " else DATE_ADD(:startdate, INTERVAL 6 DAY)" + + " end" + " AND DAYOFWEEK(DATE(:startdate + INTERVAL numbers.num DAY)) BETWEEN 2 AND 6" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (select ch.date from company_holiday ch where ch.deleted = 0)" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (:publicHolidayList)" @@ -1796,7 +1800,10 @@ open class DashboardService( + " WHERE" + " DATE(:startdate + INTERVAL numbers.num DAY) BETWEEN" + " :startdate AND" - + " CURDATE()" + + " case" + + " when month(:startdate) >= month(curdate()) then curdate()" + + " else LAST_DAY(:startdate)" + + " end" + " AND DAYOFWEEK(DATE(:startdate + INTERVAL numbers.num DAY)) BETWEEN 2 AND 6" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (select ch.date from company_holiday ch where ch.deleted = 0)" + " AND DATE(:startdate + INTERVAL numbers.num DAY) not in (:publicHolidayList)" From c3004245008ae291d8466b843b99a07d2235efc3 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Mon, 15 Jul 2024 11:12:25 +0800 Subject: [PATCH 7/8] overconsumption report --- .../modules/report/service/ReportService.kt | 92 +++++++++---------- 1 file changed, 45 insertions(+), 47 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 e9c98bc..eea7618 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 @@ -2026,53 +2026,51 @@ 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," - + " tns.totalConsumed," - + " 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("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' " ) if (args != null) { var statusFilter: String = "" From 0cb16c2b10bcf5b7d39947f82b2e14eba8e27b4a Mon Sep 17 00:00:00 2001 From: Wayne Date: Mon, 15 Jul 2024 17:08:59 +0900 Subject: [PATCH 8/8] Endpoint for add time and leave at the same time --- .../timesheet/web/TimesheetsController.kt | 29 +++++++++++ .../timesheet/web/models/LeaveEntry.kt | 16 +++++- .../modules/timesheet/web/models/TimeEntry.kt | 16 +++++- .../timesheet/web/models/TimeLeaveEntry.kt | 49 +++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt 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 29d1c28..68ccecf 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 @@ -43,6 +43,35 @@ class TimesheetsController(private val timesheetsService: TimesheetsService, pri return leaveService.saveLeave(parsedEntries) } + @PostMapping("saveTimeLeave") + fun newTimeLeave(@Valid @RequestBody recordTimeLeave: Map>): Map> { + val parsedEntries = kotlin.runCatching { + recordTimeLeave.mapKeys { (dateString) -> LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE) } + }.getOrElse { throw BadRequestException() } + + val timesheets = parsedEntries.mapValues { (_, entries) -> entries.mapNotNull { timeLeaveEntry -> timeLeaveEntry.toTimeEntry() } } + val leaves = parsedEntries.mapValues { (_, entries) -> entries.mapNotNull { timeLeaveEntry -> timeLeaveEntry.toLeaveEntry() } } + + val savedTimesheets = timesheetsService.saveTimesheet(timesheets) + val savedLeaves = leaveService.saveLeave(leaves) + + val newMap = mutableMapOf>() + savedTimesheets.forEach { (date, entries) -> + if (!newMap.containsKey(date)) { + newMap[date] = mutableListOf() + } + newMap[date]!!.addAll(entries.map { e -> e.toTimeLeaveEntry() }) + } + savedLeaves.forEach { (date, entries) -> + if (!newMap.containsKey(date)) { + newMap[date] = mutableListOf() + } + newMap[date]!!.addAll(entries.map { e -> e.toTimeLeaveEntry() }) + } + + return newMap + } + @GetMapping fun getTimesheetEntry(): Map> { return timesheetsService.getTimesheet() diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt index af3a0c5..5b5581f 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/LeaveEntry.kt @@ -5,4 +5,18 @@ data class LeaveEntry( val leaveTypeId: Long?, val inputHours: Double, val remark: String? -) +) { + fun toTimeLeaveEntry(): TimeLeaveEntry { + return TimeLeaveEntry( + type = "leaveEntry", + id = this.id, + leaveTypeId = this.leaveTypeId, + inputHours = this.inputHours, + remark = this.remark, + projectId = null, + taskGroupId = null, + taskId = null, + otHours = null, + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt index 6af3c26..8812f75 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeEntry.kt @@ -9,4 +9,18 @@ data class TimeEntry( val inputHours: Double?, val otHours: Double?, val remark: String? -) \ No newline at end of file +) { + fun toTimeLeaveEntry(): TimeLeaveEntry { + return TimeLeaveEntry( + type = "timeEntry", + id = this.id, + projectId = this.projectId, + taskGroupId = this.taskGroupId, + taskId = this.taskId, + inputHours = this.inputHours, + otHours = this.otHours, + remark = this.remark, + leaveTypeId = null + ) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt new file mode 100644 index 0000000..e929bfa --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/models/TimeLeaveEntry.kt @@ -0,0 +1,49 @@ +package com.ffii.tsms.modules.timesheet.web.models + +import jakarta.validation.constraints.AssertTrue + +data class TimeLeaveEntry( + val id: Long, + val type: String, + val projectId: Long?, + val leaveTypeId: Long?, + val taskGroupId: Long?, + val taskId: Long?, + val inputHours: Double?, + val otHours: Double?, + val remark: String?, +) { + @AssertTrue + private fun isValid(): Boolean { + return type == "timeEntry" || (type == "leaveEntry" && leaveTypeId != null) + } + + fun toTimeEntry(): TimeEntry? { + if (type == "leaveEntry") { + return null + } + + return TimeEntry( + id, + projectId, + taskGroupId, + taskId, + inputHours, + otHours, + remark, + ) + } + + fun toLeaveEntry(): LeaveEntry? { + if (type == "timeEntry" || inputHours == null) { + return null + } + + return LeaveEntry( + id, + leaveTypeId, + inputHours, + remark, + ) + } +} \ No newline at end of file