From c0cc24d010b173b73e4b15a28881ef3d4c06570f Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Fri, 24 May 2024 12:39:55 +0800 Subject: [PATCH] cost and expense report --- .../modules/report/service/ReportService.kt | 84 ++++++++++++++++++ .../modules/report/web/ReportController.kt | 11 +++ .../modules/report/web/model/ReportRequest.kt | 6 ++ .../AR04_Cost and Expense Report v02.xlsx | Bin 14374 -> 12774 bytes 4 files changed, 101 insertions(+) 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 42c9754..1c23451 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 @@ -44,6 +44,7 @@ open class ReportService( private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) + private val COSTANDEXPENSE_REPORT = "templates/report/AR04_Cost and Expense Report v02.xlsx" private val PandL_REPORT = "templates/report/AR07_Project P&L Report v02.xlsx" private val FINANCIAL_STATUS_REPORT = "templates/report/EX01_Financial Status Report.xlsx" private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" @@ -1986,4 +1987,87 @@ open class ReportService( return costAndExpenseList } + + fun createCostAndExpenseWorkbook( + templatePath: String, + costAndExpenseList: List>, + ): Workbook{ + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val sheet = workbook.getSheetAt(0) + var rowNum = 0 + rowNum = 1 + val row1: Row = sheet.getRow(rowNum) + val row1Cell = row1.getCell(1) + row1Cell.setCellValue(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")).toString()) + + rowNum = 6 + for(item in costAndExpenseList){ + val index = costAndExpenseList.indexOf(item) + val row: Row = sheet.getRow(rowNum) ?: sheet.createRow(rowNum) + val cell = row.getCell(0) ?: row.createCell(0) + cell.apply { + setCellValue(index.toDouble()) + } + + val cell1 = row.getCell(1) ?: row.createCell(1) + cell1.apply { + setCellValue(item["code"].toString()) + } + + val cell2 = row.getCell(2) ?: row.createCell(2) + cell2.apply { + setCellValue(item["description"].toString()) + } + + val cell3 = row.getCell(3) ?: row.createCell(3) + cell3.apply { + setCellValue(item["teamLead"].toString()) + } + + val cell4 = row.getCell(4) ?: row.createCell(4) + cell4.apply { + setCellValue(item["client"].toString()) + } + + val cell5 = row.getCell(5) ?: row.createCell(5) + val budget = item["budget"] as Double * 0.8 + cell5.apply { + setCellValue(budget) + } + + val cell6 = row.getCell(6) ?: row.createCell(6) + val manHoutsSpentCost = item["manhourExpenditure"] as Double + cell6.apply { + setCellValue(manHoutsSpentCost) + } + + val cell7 = row.getCell(7) ?: row.createCell(7) + cell7.apply { + cellFormula = "F${rowNum+1}-G${rowNum+1}" + } + + val cell8 = row.getCell(8) ?: row.createCell(8) + cell8.apply { + cellFormula = "H${rowNum+1}/G${rowNum+1}" + } + sheet.setRowBreak(rowNum++); + } + + return workbook + } + + fun genCostAndExpenseReport(): ByteArray{ + val costAndExpenseList = getCostAndExpense("All") + + val workbook: Workbook = createCostAndExpenseWorkbook(COSTANDEXPENSE_REPORT, costAndExpenseList) + + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } } 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 b6be334..a23e649 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 @@ -254,4 +254,15 @@ fun downloadLateStartReport(@RequestBody @Valid request: LateStartReportRequest) return excelReportService.getCostAndExpense("All") } + @PostMapping("/costandexpenseReport") + @Throws(ServletRequestBindingException::class, IOException::class) + fun getCostAndExpenseReport(@RequestBody @Valid request: costAndExpenseRequest): ResponseEntity { + println(request) + val reportResult: ByteArray = excelReportService.genCostAndExpenseReport() + + return ResponseEntity.ok() + .header("filename", "Cost and Expense Report - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } + } \ No newline at end of file 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 70671a7..287e301 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 @@ -13,6 +13,12 @@ data class PandLReportRequest ( val endMonth: String, ) +data class costAndExpenseRequest ( + val teamId: Long, + val clientId: String, + val budgetPercentage: String, +) + data class ProjectCashFlowReportRequest ( val projectId: Long, val dateType: String, // "Date", "Month" diff --git a/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx b/src/main/resources/templates/report/AR04_Cost and Expense Report v02.xlsx index cb9ba8ec1707870e0fbb5d4551b152ff5c5a836e..889948be48004e40b1f58aaa48f03fb2695595d4 100644 GIT binary patch delta 5126 zcmZ8lcQhQ_w;h8SBUv06Y{8 z2M|8+tXr6@&1Cbjayu-xFb0VvU~FMD=hy(h3rscTXvETSU%upW)Hg;EbaCptkDA{< zl%=hjyKkl_zv3=Gr+j!TIz%%&wtw#ghWwg%whxoBVA@Fn4<$;kx&EA4V{>qvvcMAMOed|xEbmf@Q|=+kOe7W!Aw!8!99zxa?Qd~|~)Pb7IisO=SjPc)g!JYb<^=k5`sPZe+jvVR1V zq}ijnNFzy{eE2XaXAY1NU`vxyE&YcGFPvyw$=mt+aXjN$1%_lk_q_u1(%XF!hHD@~ zL6EYp3zriYVhDRF7KIA{tRQwl^zfpGWY?kN*WliOxM6d0qZTM|4CV?m&hpqTR!_&( zK@q;I(?+;9+`Q~3OZE4W#|ls(10xXMQxw%Ks>L^}HGTGidm%4hN7P|JCAybiGIBT0O@>1bn7GBAu9q zQ(u@;x3LnRGxIBtv4gD)0lvS6se?a3;X`nw=L7x{5#8)m7!vnxJqzK+P|j4X8sCJ^ z5|k}3dkz|8hTEG(rPbD0P^w?L{KxP!z8Q_pG08=rPNz`VW7TgDuQY0Wy^RJ8x;Pih z?}RnVjB_2^+1p)?;I|qw=fGoDXCvB$nR;;f?uH~Advy{f+R$D-pa;1$Jb`otF)JUE z{$M=Z^zI>iv8XY_;&+vvCT$SqLaB`8u#0m{kqTp79ACjR<;;39UIT>XhVMR|XqWzS z2I~YoeJn^xMWydM$cJx$F}$%fYxq8q6Ztv;|EkqCLh*Olt4>B|4SINL1Dcapl3rhV zP2A#_EJ8x#murw@N8L2X%TnXUR5r_B+h#ZbPh}_;I#wI46(*_RCOEm7pg7B8jmO8s~g6`ZT9lUcj}%YH7iO*}z&8bTF&QQ)@m<%p%5OVg)z%S4!v;VqL( zh}uQ}_?3`$$FUMa zk2QDjV7O@C$d{Q)CT#)HMg2G#s8}HXJi5rfV|fy0urz(F3ZFv4(eimBSOYq}+5~&B zv8ii`%OiQ#nFf=KdvO^Ae+GU^yJ0;~YLx07VIGk4z9AttHQF_Lj8cCx@R)E_h3 zoZWw@Au9*J2}IG}G-zFKC)M1H75X!H%{PiR1ZP?Rukik1CRw;#TE^k6JhlJ<0D1rc z4kAT@zOIfCf&(|u*6h7i$UE0D!U5U-RCi~6h1j-@w@3*F>Ul8tS;az+78!V3EKR31 zg>XHigLa4e{ZAxFe&SO6>EbJoBgcC|;l3+_?>T%pj7#uzr|MpW8a@U2_U2Z_&K;60 zABtwV)Gz#bln>WVxjE@#ib~m(kDAA$#a%~){X|`TzA?EEYMU(#)vtz~zcSpeil}dD zV#(rh#NIS3VU9uh5kbsjhjb7PS>hnT*v2F)-)Wc1F~I@)(os8T#I397oCwQUp?MD} z@hBwPmnG(l8Z(?H)d%}Nv($p15KedQZ5TYW)`!<(cg9iZo zCPK^*vA{h&=Y+}n7Io(WUSH;MkemrAey0D%t8LiOh*_ZT$EncBkt&rhdwG(}1XNe2 zs=~8S-(Gwa5GshswzM=8((<T8#F(|Gwt2CfO zRlT;d;5XATq`{wD?p{d061~q2o8j;W%3SVrV*9e05VO#q93+Km;|^CQwl%xz$K8K$ z^5A+4{T$0nUk-F?_QAHxcS|vx6U0KIN0Rv}`jjEDYR}}7RIeHPi)LlJyr>cmgA&*4 zKJPph&!x@(G4!jLlkd25(lA)4Dz!rGIjLl4#g|N-oE2n5B&#n4|G*&8u-6NkO)AGO z-I2!m*@luY2jRx?yr5L!+BD}5l)-#0G6LSlT5AT2-RwTk7X zjT(-xK1NvJSZa&=l$r9oRY8}LksizC6N#BfqH9hWiYBz)7LiIL4fr8}trXFbRWO`B z@!d0N&Q0qp@`*8aU>-eQFK%~F29f#^2zO*&n1zIBG_fW--cJNf=gEGF3;c-_ZYe_o zd>sGLQC^T8_h)AqvKmSX6E&02_pa{1Gy|9j2IrU|Wg(ti``C)eDPOp0n1^{kuZaI2$la@C2EJBzv+B)h8 z=hagM@hE$Oh$IEtSd$il=NQb#24ESSZm61t`G)ms&WHCxbB0(&Ogt04DAyJ|`0(&N zX>G2esV6TegVn*d2ExAf^7&YO%*pQiEzED^DTPz*l!IrB&Qy8K`FNccTu!t-{hA#S zdNH_a*auH3QX-5=BaxCS_Ez6X7UOKM%}rA>51KcOF>EZ!Vs*}XZG$Mm8S^nzro10( zsb`}QTcD^%Vbfe5HMU|NFlOtuy!4c<*8%TPWVZ{X3B{v|$N~(hUiPcTblnFqP_RB= zKb`;kK1`Mfr%sZD>%0}5u(FfEf_i=6tlR`OFtS#@*Ha5017ZcCvx&a4N1o5fAsXA; z36EQ1X%Oa3Pj(&L9>FQ;K+fO;)cvjdEz%d@3@TPSz{_{Ozc+ zuEMi!d7{fWESPc}S@7PyI8M_?9;y5oUZb63|GCyGh`1cxo=AAKMmHSjxXC`CZXSn{ z?=LGR;d9>v-%*V0IdPqUeRAIX+=2dZ*c&VEn-9e0@(7OM!@C+-72bb4ky(tVQzvqf z;&0WLOsGuH?|61}8OLIqb5CAs^m6Xh6NX)3(q0WtrE4p2KrM9b9OfORv0iA2)RLOK zfh%$6CLK12t9=u?E-f30j|j5@uO_(HlNFiLSi`vn#Bf=ED|rfoy}LVTd~<(s@r6#q^VMX)61 z8-ux6mehLoVeVt?;E(DQSlTg0L?yf^t2G)~z<-}TM_YQ+B&KlMC_P7ImK&-bExifE zZd#qJB+W_PdTpCHGDCUgw*iwpc6D#7*Y_z)^xHKZxg#a*6enJHCF>l8sOpOko|TG^ z3H0n8iFar6a@LOrQn0!xGY>mG(Hb}F-6P|AC-92jSYu^+UPIMcTnpf8D-GALq%2hv zC@Cl*7YSC-jkAM#i#oQlm~gF#B#xKW9ysNc{9gww?Uw4J3c>Ye5M%q=xag?a3+LRWV=kvpKfoEpkOFP$+I-uNHNQdiBGreQfrkb>8(+&*KjCl3Txi93m>jX&CjR5;dM|6>kh$-Uhe^D2=gGXoOl_SSITKcbI_M0ekQ@P;n4 zYd2XK1o?=$x9qG~%MD+?Yx(=T4c8=y8<#0vn>8ULJihKOFIh4D=v}gs)FD6q^zebF zX&hjSxtSHJP_RAq%9DeLgWk6Mb&+d=z9(|fN^gLZY14p3>jz9Cx$J;Jnu0I;px4(! zLn0*(F{p?f*YwAlN}3AgI|vG(Km{|21tD7_LsB@mEw|%)Wd@s6kCeeQ zq2c3vMeKsnx^b-q`}fs*EsuJjBB5MU(@&5YtzG02-hov=rmH19e>};av}C{F59293 zg#;1@DBf${jMg!W?A^M=;?(v5B=H7(k@Bse?`#Vs7+#tmW?vM#{n0L4K7r@Q-LKz{f6Hz4_~!SYx?St5 zGj!@5RBNi6c|r}!Gn}PgRHAJC`x6_}#5Iur_oMD`PliZMZ2Q@nP;&4&<_%O3Z1qzj zSbON;u}9UIS4a=t(bDW)?}}8?XeKIS8K=Do;cGuZ`nL2BUCHd? ziE9uUi#GUn`P3ne^Q(0p8?)*cVM!1QHfh&CAxQy^rKkRY(bkL}r~8+DK5<>&BLS6C z1Oinqc-q6Ll!~)w-)1bzXpSxlJxx`2U3L7GE=QspYrzFh7ON7tS}%s0*ZN1N^C7efEIKe(!&(^s|Ibin=A2vh#AW;)w?JJ=ig zdb>Px`furUzxBjD^0%J&inQm&m{N;AQeDGfZjzL&r1u5yy|?ySu+;e$nUwQgCT+gd zz#PflVRVp|I7UJuq~LHa%QPoJ3CG#n+}W`2XV6Ou3&>uvR-vn#Fm?XMXMQ*xlga`` z&Gf0Mkuv5(n%u`4{ZRKN=TC>OmPM~MO`siq<%HI(28Kh2!sxVwaaH+e=1S-jMR~WQ z;5WbSy?f1ZARWs+5Lo_T8w}g<7B7azb8x0y{tn1dF;7$frjH6kv|eC;Z8(dOQY)N~ zJB|9d8WZboBbF302OCF}T+bZeM|z2nRSBRRvD*x5co$I}!Jwo1Y1y`ppakSEyO@+D zB;60cjgm&%qeQUNlpph^h1N}-2te$|6E&|lpj@_4uqkghjz?Y;1wl*wsld{Uevuce z+wj$FqDNA>6x49hluLmDntxhd$G{;?2&tQ~Z_&Q(w^g0B5E~9$L9<7gHhMSWrf_xP zzK0}J;f}lm_&JNIK00a@19MMJwfDa`Dcym|P$msrtPLQN?1v&NBZY(;$9dJBE9(K` z-1I+qBw7s_f07UP(}KX<@TH_9REB<@^R5tM=Q``gh-pp2iDfm+2D2G=a@@H4-tNka zGMq&pEUPjM>{M1Wo*93EBh)E%CTj)eFzR)qIFF!$oc%7 zawWeSmwhK6d~QFUmM-()TAOR0$9#{K5d$_)_}u7G^k7E<4Ub=((G|Kf?1B2#{S1rK z@){%-F4IOe*@(){ZbX}iZB2)cWE1wzqXO+=6$O)`Jsqk_q+9b~w%47QZ(Ub8=#mps zEFl#ncAd2uGJuY!5pOSnoNfGWf4DeH%y3}8CnS;?Wby7RRJ6!NYyH=&KoDiy2{wtl z9We%?s{0|b1cdw>b|F5C8{m{7XeI73{2RdB=3%6_BmVb%Md(YgG5r5s@NX3W2t_1H z2r&L#kN^PM|62y2h!7Yedc?j2FEHaSg6RP#<9}L^zi@L}gw+FP#{cBoUqCt&BJ+Vf zkc1h5d7ujQ%L?yT9)K`iDia3$dCa5QzYQ0zd-*05kv(K7(RE1OOnsmY5z9Hl~8e zh1;2VgPm!DWq4{DXSNmWr-grtU@ugIwbrzciROIbAYqOviDuNa)O7KnWK%3=^rlKr zLXs?i!Ou#UVAB&kKf!v&6?h~Zizx3~V05Y$gOy8y?d1GtMj45Xf7yb;KL3WE;Q$1R zu!`QiwItFgh=$D=L5hg&vClhUD}hOZK8UIm_FTQWPnbN79$M`czW(LAGk)rEP1gox z3MC|2bFq3QM!pwOg)gmn%Da&|97tMw)5S?E5K3IFl-`rhAVn{dVE88Sg!EjbU zjDD+W!aG^~BZhf2^i<{LdU8)b1Y+uEnaZ?ZY%>zNC7j;o377FD-(8)B?GmKL*FDN! z<2?qDTABFi5>5XZ>0sOSN;*E?1@n7+hA0ns)sqg0{(N+njSWs`t4xTVQXL;3DRD6*Y*WG>ZHDklDD zXJA5L?`-RQr&~zB9uY7L#2`J?l{%~lNGDmg>)DwWCdM545+_3Ypu#FKxQ0y`I6hR4 z&_-#BjrR_`3-wC}R7-0nlIe7n&3IExG*n-?brOPuR}(Q{GBPp*S*HpivrV&4S~~1k z^n29i7LX@sdajGNxa1~w13lItyq$sN4M;uw2fbcE^5#~4h!>uEi$=T2=t;46P1_Gi zQg?%SYFMieovd5g!v-$Byr-~3w8TKY|C0cvxGs{=)EbLf*PW*aFyt#8XA+8>S5rsP z0Jg)5#(h*4A!h3 z=<#g3>EcccOOG5xhC3aDC+k!og_THw<%+rQuKdZ*3aOrGH z*ndLS6Q#c(^p7F#eSGYqNtEez z@)V5Cop#nJ$pbm3nAA8S2I-?GHo``;<-odWoS*oVvWt0(PjtoEqBlFIf}aVMEPPQB z+p_U)#CyIVWbL?E`n5~)C56*Kh!gziFvx95*`eOsgDyV$bOb0-zzl6FEWkO&I71u#^$|UNEN2b;M)XRHrL^^``sV`fmW zODeyTIjUjc?g>;waHuQEsg6tT+cjlO+X~FP@`j!YWJ72vj}cnoWn}?HKIt17ql@mc zRdmQ=Kn5Z&?k(uH>Dnjnd^XJxjV>_{L+n{~F2Y<^i=uHAS>iAKbdqGT4_zvNiJB|B z!HaRGCtg#F%@9Osgovd!Q^)2djz2-$%ItYXgdV1nzrP^jKAr4GmJaEBrE_Y?n2fh; zbvKrAMLr=3ztbB&8+%Vu_nNfxlNgFr@5_Nw!d-s-@RqkPAr|F1n0#KL0*&uE+?wr~ zF#T9I76(Qr>babsx(DkQ_rw4Ics}qY27{*N6Y(=>SyI4gV%Ot^IraZ2|%44{(9xS%&RqTK6Gjt8Uu9xolmgquNTIz-UzAtS1Z|Z zWFnf0NmD{g*nI0*CNE~zsHL*`3Yq2E$#inx6ik%^F->^eH z=`n^h)#;!QRAqscB5|GSRwAykKoq>?W+ezB3&e5Pb4AQ5s>}w=CtYI?g!>60=b&}=h{mmm zy`$&X))*PS5y!sDn3$Oe6Q=$xE;#YTf|&s9GimAQqGN&mNvHort~Z6pcTer$5xnG9 zdKUQs`HxUekzGc>5Xx3SGWIs|I7+=tf>|2&-a7%4(PO8ly#3zGp|A`UgF5rXQQ>Bs z;qouODL6F<9kHSN^!z?)^ZGCXjL|goL8EhV=X#UG!1y>FOS)vM`tbeS<|9^E+vAb>%B4i6$nq~? zZPz^%pazS+3}53dP4ipQx0P7oD)+p#NGwoXtgqRdUV?edL*q;AoMWxxrBqhvDiC$^ z&%ej`SXRl_Z0Y){JKnL*ARwqxAUZ@vp9%mGjZM2?R)Q#t*?70i*cUV<$5uv=-oLb6 zc{vR7!oyY8;%tmShl${dRu&^{v4ezA5gnR@6y6@jwDwUsA(TbYfX8)(Z*6l$fF>Ku zpp?j-Usk)%_KDFOF*R9jC*@OMp>?_A9e&}XiifV2?EB|J`SkQ$ET5jj^;Vo#z?cuP zw62qPbV9_R;u)99KW-a>b!4 z%Uu%qfW;;*&Q<8LjT4ye=f1zf4ok0kgSVpKE)?HZcYQd=4bjbaDPVC>udWGrbx)$H z2dR=w4tbXG$5gNqME?(%?}Ejp5``RfRhAL^+ruyIr0N#Z#>MiwlQQX zrs*QcYi7ukz@rA}uKjZ4!5LEhkNl2Jx9u@MlIQR0qF4`v>kax@bC84z{goY1{Turj zM582LHOmPS$J2eQU>L911j$-fXY;P*@x&JeXAeoSyZ-Q$ zmBLpk7IKsGCQey`EBNEdj@^7Zm-9J#&H-u80OW6B@j3Jq9mF-4Ff+NcI$_K(r5m>W z8)OnjMZv+X{Mbst?v*%a1@<(G60!J%;%6%-Sw4*yA{vO}EjGTd&8A)khB|Hp&y8`V* z8QU^|;UCzM5s9+FOz9fqJKiVDWzrZ&{wC$dtwU!`B$vO)aTMBqlX7}&`UWwEfmd|t zR~Ug)r?dfGGKbV3Oj5M1A3K8B@?(3IxLO~tUaA6V&d;wOAMf^j9?sTi=f{3`M;RdG z_H&A!hpBG%a*C49E+;~gO1971F!y;Zsl%zT7St2^{ge80V5VR_#fACiQi66%?E7kz zU)_a8L=>;pH|?-9248((x~(Ftrr4>%7A5+H8v>*m#%Mn`V>K=6I(lE)nQtDt#4aSC z&(wDAEmmf@rCD}*>)v;BlL8JR6_a{I{SVm0rKmfFfpMj)&2Iq+(~uH8YVG8*^QoLy z#4a#XEFFq!D5t{AjJG_QWKqchl~7l^e&Rb|I5wZDBLNn6f+RF;QhKjTpD@mMX@V4zp?EgDBY01bvyTdhiaIWbX2Q zd12B7fJrd36h6v_P=?ZC7Oes)5XhgZm;7KgWeUW;I`kjxQuYka$NppvrL)3;_hUwD z;%+G)|8Uw0My-$K5t*-E9$Xi@AMEC=3ZImnmW2@$3hw0BI&sXBO893U}#TZRIA9_n%(Fv_A7u^AVPgI-XaRDLx$Tc3>g4uB?bVn{vLMR zJ$xOk-2WPNM)eF6SELDoWVgliOwb$AsyS=3_?7D_vh>Qd%3_V_DOoDXZBpV+87Nen z1RKj&!XO7QFjAz+PkXvkspz5tPdA6l`}?6ekuy{d=%IdTKMR0AY0F$GLARo zlF*2=Sr)2Etv}>xY6=?B^-SdxNl1?&SWd&#_2q`A<+urv_s41 z>C^I>+Vw7R+tflzj7%Z0Nah$Ih9r@QP=zs9Uv-V-uxC%ULuOfNrkY9HC#i+(frC9GLQ6d_@ zroeWM=IHU%lEwO#g0HronBr8L;VN-Ox&%)t#rH_~c)P2@`msMqiHBL~Gdi+6$9wv- zi3bToK4=|%+;$OAZDuJ;wJ2-?8@oYDS*XoFQJHFknTYaTgle{urk^TaP61u#(M3J1 zt!~+_RA52{n*NI6mtc+ci!7ZhB|#ioKtcVH8K_7)f0MQ=qnchKt{0m#+9k)d$&sX& zKjWQM7`sdL@O2-F!>I2IY{h_^$H3e0onXt&8AP|SPN((H{E&~oaYfQPm3FB0-={&P z*HkMU<*M;d5Zgp&Jk4t3GukEW)*sGbcPlwT_tzhES+yH7$yZ}fE}|)RHWj>+2wR`Nr+a3e06ZA#_UIOkdL&LYO2n9xd z(i*Xn!<=SVT6L@%$+Hp5PJ}G5i{dilh%{D|`K%5$wy{VuWygxwoRuN{&3s9Wp2#sjTz=@9ts@_A^*!P~ zHjNXebFfP=JWuM7;L@&A$%lsSMy_6=IJElnKYdMTR7}-f9d5nl)$I_4@x3A}q2jRA z=3?b&p=~kpUoSW6C!_fm`76}tC zok@+?DumYd+GR9Syh%HL<}&1;?LN(tihEn0|qZa!sCV29E!^ zzdg}pl<-v?C>-An`*vF;dm~)*N!pNO07vgad@nL2;DR~~g)*RsxqI`Q2ysBe9+`ra zJ6Wv0)cb8cC*4}5i6gH-SsvaK-=ZnM2tL-67g7RMnPY6B)@(x&W8FEaP{$T-ywjF3 zfEQt##o}^p5j6Jg47Ji>ZXWMpM$yfCClAM&BmGY8Zl|LqhJ-mSW8!#Yly!TJpVu<{ z%nsUmC9N2c(0%`KwHJydRx=$Jf6u-ch}-$2#)trbI10Ry0tAcGKKX0Ddr0VgavA20 zu`fbEWT(#E9$1!tsfnzFSQZE#l4uv;Ys{*;zi#U?H6BG34NrQvvF3lZrnvXWj&~Zn z?84Aya#_5MFsvqHnt)K!x^SnLEPV{M;f%913L|`rXO7i7HYGr}vW77orDXgO+?88o z{VFI4hRO|;q;Lx5XlYa}1KiSG9uSJk!DgTTiq6Rn6&A0#O*L^8!{`F!+UO1Cc!vj` z;UIcN(il>079sf%3HfeOTO%&=v>OC^qyCQA0-a(p*akQEwMH0Jnli#x=p^VzJJJYhg6dj|gBiuFKi@tVKXBE&lX0bu*O0vGVAp<(V)6A?*Frvyz+1sq7NYFC?-IT@ zbk+G(%c{zap!8e)^7DFsM7OL{BADeBX~h zkhXrOPkG&)%n9t%gSceMv)RNkx9M&Aw418Gq6UibT%(wk-g6E<{q+Qn^fU~ z1Iwl!6TlfNUq6abeH*;~cevG)Qwd_gKp1y&GtXVY@H6Lv`*(L>Vdh{VWou^V^jCvm znLJ|O1I7iMgxrTM`qQ$qJA&BuaG9<|9so4Xj$xD#e42+l4hD75ZOIh4P4e>09;?kX z8t#qX+k_aYau?(l7G8a^XN|-5>bsv+yA-+@$<|_pZlew)2l83~g6u@h5XU@*I$l6I zRrckQN3iwws=G{j0DQ`isuf6%2Y(%)tL?n10mv|@r_L)T_7=Uqnw zW8>=-H8!e~@W(p+(L&z7Z|(5uLWZ1-N+12Cg_QOWx5&6w&4wOAXVoJYoV;KEMKPi8 z>)Fq$KL4(1{aa>Q1el>}4>d{TiSj)T)-`*B(3hkp#o0s1hjhK2t%R=T%33wVQKu)9 zWv9k>{fa(sDJeUXIB@DPI=dF@bVy_ALJ`&4iHer3e-`OQydcmUNHx{2q?VGcvHke= zZIO|t37HRfBg&3KO$Z~iV$1mCxPUTOQvhnp(%_p^oHvchqqnf4CG)Wx#uY>rw2v0( z3FtpaoI>&_qko1I1c)DWlnMrKlCUJ@e(G~2*vY8^(jDcXwpz}n775uu6~m9y*?p+6 zkHXla%x8GjdG>kRVR0Mti4$v@JsOSm+rj5rF2h)0-OK>z?n0l2DeP+V`O1e+Zy2Ii zEFJ+0AuGry4mtAM8?^(TS0XQ^Hv?^*VJaq!-q(E?-_c`iXg)?+>h!(5&b_a4|orU20}fY zh?A1+zd85kSo`0z2v_B#CHw!x-@kQ#ef{Baob2TPP+tH5$^TUYzGA}1FhTGWP8LK= zLO2y#qJh^^5Wy?KEQnb2a26^C_~WyP!vJTa zX0}&Te1_m>gMY7f{yTa8c}bSzUt5L$mrDO_{hv5|hJ3t#OrIq=PB@H91aX`T-pK{R R#(HksEuZPo3f{ji{SWX%T0Q^(