From 36b8744143bb98b2a4ae364983d6d4e39c282958 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 15 Feb 2026 17:59:20 +0000 Subject: [PATCH] Password changes --- .../images/logo/company_logo_1771177204.png | Bin 0 -> 52263 bytes company_settings.php | 2 + db/migrations/005_add_wage_to_labour.sql | 16 + db/migrations/006_auth_enhancements.sql | 27 + employee_detail.php | 137 ++++- employees.php | 4 +- expense_files.php | 4 +- expenses.php | 4 +- export_pdf.php | 4 +- files.php | 4 +- forgot_password.php | 88 +++ includes/auth_helper.php | 93 ++++ includes/header.php | 18 +- index.php | 4 +- labour.php | 4 +- labour_files.php | 4 +- login.php | 78 +++ logout.php | 4 + projects.php | 4 +- reports.php | 7 +- reports_media.php | 4 +- reset_password.php | 105 ++++ settings.php | 4 +- sred_claim_report.php | 510 ++++++++++++++++++ sred_claim_report_selector.php | 66 +++ system_preferences.php | 4 +- 26 files changed, 1176 insertions(+), 23 deletions(-) create mode 100644 assets/images/logo/company_logo_1771177204.png create mode 100644 db/migrations/005_add_wage_to_labour.sql create mode 100644 db/migrations/006_auth_enhancements.sql create mode 100644 forgot_password.php create mode 100644 includes/auth_helper.php create mode 100644 login.php create mode 100644 logout.php create mode 100644 reset_password.php create mode 100644 sred_claim_report.php create mode 100644 sred_claim_report_selector.php diff --git a/assets/images/logo/company_logo_1771177204.png b/assets/images/logo/company_logo_1771177204.png new file mode 100644 index 0000000000000000000000000000000000000000..5802d9ff7c1af7bf21c1497382253cb0de0f2be6 GIT binary patch literal 52263 zcmZU*2|QHa|37|3tB`kT${Lx7LZxhDiO^6am6ENU7L0uiLyIMAMkvdmrc{cGvJZuj z%xEZtvF4f>V~mVp@Vm40`9A)Se~(9x_j}HHo!9Gip0D+sdvEto*jY<#klg?PfCTdB zk&^%*MuGkwS+@ond8>m+g#IJqf703>)7>MO5Ao1%gB_B z9|ZSYZIGAVe05V}g37l5dlehq3tXKY>*BUs7O%fen>O|-xzShh{OTjTi41v#jhNWi zNu&L7^eR4WbQBWZj|xN@+59?A35OdWqhN9P9kiD?{9c+RPV&L5A<`#()(*)?oxOmJ zK`y)|Xxkc|p=co0Yz$o~W$>jF6nl7%HmwY|tV(0xw9<4lmOEB@S8B@^{g&oJ?@pAZb&GHgeijmB2T?ChAmqTl7R+f#p?0Lq_09e}*QM}>}sRR8xw9biiv)QmWwLHk1v>Yex;ODUI+bm5cL3jGh7ZW7;Ac zHO-txF_}!%408tcbed;00MI$oWgnHHW)oUNsE6mM((d3?>}2-srAgtiduR_&$uGna z$OS6jn^ePh$i!1MXlBS_-0Xiy^6|!}YRJ%I8Y;#|C`*)q(ilQ7vho>0uAny1g%SvN zJ+QR2Y`@~OvO=9Ab6Cp;E7dCsE12crCB`Cqp3Pw|ura39rc`Z4F1urKVrg!9kug0m zQrjAlyJELevs^V2>|i;szYwxezA(R_zvweFz;inQi2OE(n|ctvo_LHBL(wsZ1!(Qw zUA<@5)ee_S_)C+z$Rg!AFCwNngRIYB40&^|wuuj47!dkf7U!)@`}8vAA?AnzoMwbu zb~%YS=gHNO80hQMF<-&5$Ibt8jTaY*?}t8`cr(vx>4q)OBW5_G5d&Zb`M?S`H(0>x zO=%7RV-rdwB-Wm@O4fGr%dT(#E#f8j_pHP2Zb3ntn8OHT5+0 zHW}l^#U*khOr*k17OH1CG}abAySzh+Q*3plk4^nz_4`YEF5dLw+ERLOyGn*IZpT@3 zTa2yn$+cwBT&c#l?zw6v?qSphe=pm5u#kK{x7H*%+(eYmX3y8ZlugD`j~6{mG~g533MQCiXtD#^$SRvk8?L39L>QIBWueDe-@#{Oa#WGkpNYDz zyT#(}N<=$8le*Y)yFZO11}{yr&$x2WBSkLwGmVfpC}NC7+h4Afh(>p@(g$S6++q`- z@Z|->97mqaY>O&}x$6@AH#NeRIEJ%-$0z>duhkuw#Z76_0&!>ddc=-N z!Nbo`G~vc)DSt;yH+rOo)y!8i|Bg&FcR5yn`TDu5pxDv9nCKlY2;<1O1l!PZLNL;> zM%6((zV>J)4$21X^wL{gYmq&f#hL;&IhFBU7thTb&bizo7KMlNO&IwmAXRtwLX+y! z3)S`7@$NPYcL<821&vvL2feN~xQDV4t^U zhF}y6Tt`gT*)V9IyAxaQH2` z6=k5?oER~0GQ4=7p~-YhjbYdlO*tmN7iIW6_%O2}v=|p~5=oJ*&t+aa7bsZwnq6vVTk_|}Tx}_-@V(kdRn~kFq0F{2n7bCpdm3iS z3U6bVg^*k8+13MU;k9Bs>V!vyM?*^q+DK<7obJ{k#qGIirM5i!Yhk&pC2xOYzwp}G z@D8^9fHhxfAUm}9j?qdqZNhx4XZsIyg-7t6c;hEW)f^XGqztN*pmwzKD)%s8 zra8Ynxme2RVk)}bW3Y$={8-!CtXW^<1eMj6^m@%NHRhBZ%R7L)A7KM?OBhGZhy@~J z(Nt4|M_QK_f+LLB{#U3%+(Em|i~)e0(ZveJ1k=s^UhZ0~7Eeb2ciuL%j9{9s>X4Lc z6Z(Omz|%4G6FYbS46jWl12MT$Si5;N->N%lDBRD7s>dwD&QYj5el=r@M8*p9@rir6 za#$>nof)IwoYD(lDpED@@ zDc*y;DqoA6y^5^*qO=O|kmkBDS&ih!^OLCgiiXbz`E(;l9MJ(+t-lLShe6yUH_W3) zK)bN52Hfl!7QYL)9xTJl(?75Z06ES-;X|Sir>i_dlr|kWx5xsBA+q$SuGY z@$%;QW6-8DPjx+%iPRdUM1jb}c{B~Y8kWAm`2+5O2qKd(aRj9d-Gzu}0-`j%wK@k zZO0}pq7`$Q*r`R16hz@6&bbwMZe+m-Hy6*_e9mvMGMB-8%m@N%czzYx*6l!1vyttn zeK7lw&p{4LT0|@L0^YFlaGvlTEI;JR9xt<611#iFv+22u@AeMP-R|W%LtQiG5rL7W zn^~($+sd;~9-GSzHrnw~qR-8@3StjzGtkSJZNyp4u)S0v19vL=R_*vG(}(pn;yJ*! z^=?#?Lvbr`nhf(Ur0i%#_+XIvO`~#S-rCupo8dW5lucJvpq;%n$Z~$rOy5wZ4b4PS z%S#Ap@WcVfv%eGpU{7SPgtuwKp{U;N-u&)4C(szl(Da&IcB7OG$3VP3KIoP9wr@BT z-K*fuvoDY{Bae*O<29(bhxUu7I3PYccdu9G-J7Agm|kV*x7kF$J;e*2_zQ^)j^!bD zR<%sKj}&`Ls?54ii>IFRH8o^RhVDgj%4|6gxkzPyOg%lRdn;SEF-|YMUGn51Z8ia^j`1ocl6=8gkD}+ zF@w9Er>t9gPQWzqClh)#y~`Rte!IcfC9<*nhO@cJA;h_35N*Kmo{M7MU>}@kVc_fw zy)@lJwOTY1&nAGvP*6hefp>``K|FtY>%mT+&B3i1E9CPr{7TR4{)t*_qVIqvJ>shi z2@Vx!DBgmV;9O#_!8`iM9FOf=q!e!UX?v`T8m*B(Z9FoU)N713YNo4{B|`*}e~Ywe zOK_iy8Mc+@c%f`&U(r7@XMb~ZMNYs69>PEZekEm=)qa*6L%7D@bHci^yM2mZd#$~- zEBc|HB~m&-0jZ>k9tnL&$l;@EmC$S94Qu>}>$UghbtxW45LT3^e24jK$Q3zLs5Y2(ROpvkjH^54U49B% z8)<)E3DUXlPPy^#pwm>6UsWreXZV0`JK;;px>I%!0$6-kxtVtUsNufSvc6?MBVKV) zVB&E@YkU%++5^;`D9@4Ub@NV!_LkQelp5gd*|20ou^8dGoX)95LXZcGEci&(7gz@LLDUy83we z&CCKiWT@q*%o!tt6Zy;E^eOsR?eQt7=nu-})1P7-g!>qp%0pEhh_nhP5ZcCqBMJ-jB3C5~Nezrb#VYLVI_m8PuEb+;v>6Y@`q0+8gZ6sOX1! ztjIUB$B+l|Q~p{aZTT+SzT2Bz;hIs#>YMUkMVi3;XHHih)RXbGTVMT z=m6!J7##iOYY%QNmcQ`&V9<-+r|88_I&?yb{1UFAdol^Syy08tMG8;Y82wb)c_^Y} zhmfuX<|Cv2e4Cox?K5fXE%dH^zium3{(mIG!;N%pBn8?y*U>wx`~L*J?k$0IORI_} zxJQ3!E*#-GM&wo$=Ach)rSU}2UdT3A6nD{Z?d#CaeE8bUz01bvXUlH%6CqW%RTjiV zH`S`rH}ml;ebig*-84pbF7t=fi5@$c{|1*_p-cubuF$eWVbx&%vb@vnD zG%JCwG3Ai7TU1hD6%2Cj!f+E^tzB?eP5vgwckraO^;UVWKtS$k{twv{W7Rk$SSVGV zlU0W4mkh!{RcFu!YW1ZoE)6RPCX>r+vdSX*6@#jvNsrViIE|ez&7=r0kLjCg>;2>n zs|ttG*4q?oWaj$B7_~1@%Lo9*V{<-K#h}G*`eg`@zj&DJ#)y>JNd7_(v2umUIbW(t z5C%G_nXQeo`b<`(EAjzI*i;tNuSM!@@z#SH3h6m~yT1Nu=*EZ_V|DBNs=DbT)islI z2O#7QGo%vl6x&o@=fkS=xz8lX$iPRhN&)KUteW%l_f6C0d8z;t6*bvqx&3&k%G2Zezeb?&5dD}I;1cqv_7LBQ zb?xOfP$2dz23J8jx3oJDo+Ga~ytaW4_cp__DppW=?{Dv1i0M{m4chGVG#1H6Ch}|f zQchV@ze%u>0<^GsKXPD$6di2hFMN^RJA|&{X1%O|j7h*gJw= z?4t*k^+FrF>#a(&9#UQ^6=?dwzY5_~P}M&*2PJUNx;E7%T|_oJnICZN+seqSxyoLK zcN3(IY`-(5VknV-7F=q@dsyl?UlaLw+=Z6K9CVClY43&;4V~GHD58iB$a@`8W zD*#lgX_*ZMnLw`cL^~#i?mq$*45WIa}#7t@m$CG;hqKyXV(2@7; zI);=~mtGU!xZ``u7_2#B?+Rp4&FX`QQ&Ivewxhj4kd6f*q&!62WE7w0Ch)D>F6Yev z=@B60G9KaxQ^b+lZG3OY3+|gDvj7kZdXEG6{}l0#0WYe#+H{ksId)QoZ)DcmpQTjv++{Lw zQ~&Y#dt5d!^~IkU-AGc(wuXQ@`2`+sirGQ3sf-Xx01=-!<|#({$-X8K>PNe+C@*{N zw!ib?PXnqX*bMg*=N|7=9qMyBWZje!9|YhwD;kmP>jwE#ariC-7cVCm&Kl$@lhv}_ zp}?~BSua&1I?T7#ppAZVGiw6~rHR?EC7qBvo=O`&5RiY+vj=BevN;6X6PROl3 z7k;*w3>4T@=Q7D?2GM|qPEl{>=BK9@Lb!bWPK{ntRzhJ{LDxY27L=QF?anfS{mb|( zo*Y#r*=H9(2xu7DE?k+bGdxeh2}0(@Bi1Hxr=|BWqLBju`Ud4Zm$#5b=odI{-Y$}L zcE%QiIF<|)!b7YJig&^V@a0`4+0Gyu(U6ZMhF%NNH`W$xMs}F1N_NVwg=DQ$wBc#? zYf7OSy86?lUkFHrKn2>RQHHW0lw~{b+&ALof&d9|f(UTE=_K@A4vMU1bUZ6VB?IL) z!d(MIetrE_5mADhQWR`v?{97+`dCVbel3L7e+&uM284+~urmpL4Bw?s76Ck7;N=w$ z6$?TpGRfB@71Sd^_p{sLdTlEanC37o!3x7K=;J9P`U5DcC{*&e83e{ZCRIu=oe(KjB*~ZAm^|#Mj&*lvI2OFL4-OvK3h!$czat8 zS*eaWdrVM9>SBFOPK{cTQnR(8IqLeJyTj&4qXvE!;?0d#zi3Y3&FL^|@+W3Q1?Kl? z(AWOTQU#Ag<&yny+E;&v06D9NtfvTfzhyCCn*(%`M9-a=cM$-PpbsJ~Nu%5r^cKLQ zL4fJK|HITO8{%ebIW>y-%7F;($tzZ>55NUXb;z8PcvFMiRX{>4EGnk%fItU8&X@WS zbRZQ%g+Dmgmu0FeAV2Yyy=8AAMtlJ#8A||)qC1Bqx86t;o)p2gIW#0y`U@a(g`*0#}y`$x-%$j8hD;m3mv;Y zV9Hcsic~d&_=esNmD{DXK1w^KA&zP;6yc&L4u*JNs zq^7vOZ|F?cLBK3NOu5RzUEOO@fSTFf>|_INO+Q`m2DC2Z%(!8-k8sr)QtJ$&4J`-V zf0aE5@Cx$BMhIu5N#r<}HgpzC4=VjXuj-94niBb8Vr5{j>30b9fo?3ScLm50orDV< zNF6iPZD@HQ?xBU` zpV5vvedrc`W6N@n#LrLn9`abW-$E%HfZ@;$TxAkr;Idv9Td0Cz3&%BO!hqqwycRfn z&!R+bLK}W-vmma=Rl#5qD=wSNQGz;!e$#-z;bB5-IXn~Xzc`R#gJ)unw^UPNI@YrjDf zjn8i9u}QC`G;* zOcTV{Jq>`rxWM43b3o& zq(2ZqJvW+1vKGlZP_&np+;e>LpitY5-wR9RJ2@{{CxP*M!B_R(<_dBKaH@`TEhXMi zdv)Z`D$Ae|ze!;?Xtp{#Vho~p1Ux^Gujsu=9Tq67F^VW{M&{(MGP|)9{9<`PPYBfp z1F1IFN$@4mZsSh&K4UGkb7vjLGbPK=9%7HSd9g6CM@TNKuhhnJ1Ph^->e@BEmt{h2 zI-Aj6DNhaEA@UICpt}o&gd2^540B>9_>5#9@ACI0w4?Ti)q1}sgz(7@av!>R0nTtn z;v5XwD=65|BOkDUzdNMGH(46X z`l%8VXd(`LXu&*PdQA1?(MG2GQ~z*3G+)W-T4lBuo7o@cwprky?dG#Kq9>}ZUDd;z zBB5uGJZh-ajB?YUeO$Nn(dGR#b23QY>*oxXLC-rvpYlswujni6Ke@oD{@TDX8jK|TNs>o~7jzf_t6iQ<68Kd$cj#e4l+z$!pTmn>QH zy-4VrW-m#A8i+8!MRH)6dJAl0?9vzv9+R@k7lO%rCjxv|TltEdP2M-ab1C4atbd&7+2qsEbtUwt8v(XjPE8_Y>tZW+ zRBiuPt^gLtFU80xd5zAUIkLA{ZIVE2T_`ClUj7$HH&WU^G-z{3xBv&>r$5zYwEbE` zBCtTm_DFb~p$Jb@z*#c7gaxW(1i49g9Fb3RJP$WL|OwgxX zq3cF!{tl~bev@-ebo`}bN&FIA$g2D#sUW_ON3K{I9+YoX9iYv};_NoC1C6DJ$(fPm zvt38sg-vDMR)06w(QZm_&%R~7cj-mzMdHum zUHg25_Nu#MP^;y43+x9%Ju)Ug2lWYaAh72VYXUJ4v@DS~;+Ems`OhWZ{>Z)tGbm&E z01E7m$tT#0LZClpvL;nP9(ldu1J`z#FlBmt&-Q^;l(BpPg}=1Pi_0ED@~w2yb5nGL zyxCe;drCmiK8E1t3p$D4O5Vc`Qk)8tH)}#@S3RiRE^yJvd=%Anbdb~}67cPDk0tkx zAON99vaBhUwZSHmc@|UIu5~52kI71W%m9m#>RwK8O1zQw+IDFMS&K>*CPa_%q;GyG z@Hb#MY~RzkSoe=p4vV1z3wD#t6IIN0Ee9c=;r+?mPpXg4?&BClrk{W8TDn%qn$yG}zA@l1Xx4QX9PMN6Zeez`(*!z!*5eQlqEz`=S&HR_sU9&O&b{a$? z$Gh~SjUu;(galn!))lljKq3n7HabZf5Ye5CoqQ5zE#xjcf~aZk$*C3f>~=4S_ah5o z)ct}EO(`*oSlgbr(xTOzE9mopJQS%bzHjLQlouAlG^(}@`~&8TfxEUJyB^9oe>t{6 z`>4XU6iDg1KMcqLG-lpSUNlk_;{FB< zBR11{d4C(;^fIis<{z6-5*XjN%!Z&G3h@ZaU)nERkTX@>U zt%&TQG`)}iv-h9WJs`)D#3#?ma6QEqrYEy&24rnNEVu$}9Xiyr`ftQPas;?&ImIyK zJ;)oLytrT^MBb25cup{^``GmuSy&y+K!X8ozlbgo=s8p2(MsK_M?r$S8|V=g7*^Z7 zw372j1lT%$yC*6jNXRL)9v$Hz>T3+tt*SAqilr(EO-}Wtx_1IDQqa+BfUNZ-{2$12 zq{VpI$O(weV06!F-JM=p!bYGSn#oy#eE_;v12^*vuE5B@gwV|hl{YT3PWl@y4C_N_n|5?#{7rf-Y${2%(~r>|47`*b=65a{Lf_2#7t%6 zgcxw>Lha;5g9HEc0Yl9k|9w*x#jlgL`>{|h>{iNwfk6RUwc*Dk|ax`fc_?012wUl+57!r^Gl6lgEd2gAOwDwa zj}c}%kI{0I6P+7e^neH1k6h)G|5g5GRCwMCnQW*ldOGRe)A-I`=nGa!6QY)H26It6 zbzVVLW*hjgx?QfYJJGq(#T2ljiocH^78fGC?Of63EeA!rq#{$;oXIC44*&S1Pl#Ez z4f;cT;;<>MTmEHU4*?%iyxeVv633v!F8>J;P60y?I~e&Yh-^Ezcm-CE{Kp?%1l#gK zv`acN}^QRTG27WEC{_o<94D`(k>Zdk2;7v(9@_Ry!--o zlK0rMLR_j;5s1hs@xIhJ@$QwnpGvMMQ7y0k|8`l{V>u|cC7!(l zsdUsVnOSk0I~(9;7`AK}rnMLW1Ix(l;;RlW;>1Hq$gcbKT}j2tF~w@`PDG-7 zq2ue60Oti`@`Hf>a!24N^e{O2IT%;`c>Y+=ujvD?NX_gOXXX!rC*M>tQ5cRr-cWEz zdTRTy{%ci}p7-6!@Nybl*+aXA!^;M5eDPS0*mMO9LS!f~ z!-5-be3mZG8Lo_DCI{I2AEr}jZw^K#R&>R;j|vk+9E+) zLwkyWm)-M~3{o1yX2B}diw?V?^{BEt0i~YrpL-ajJwdYj3sT}rHfRC?~-oVOKM(FR*?jlU-tEGs=m{pa7+iNFY z)zkIZ$7-DVc9-HN)hhL4IhYxoC^*u>fHlGUr?Bdn$ThLkGY|I&9s>WW3 ziNB>)o{I}LMeDyT$oH*kWChS4jg8NQQ&w{vs*=lJj5F$2x6JpSR+vf~8zO1>K3Q@x zRZq&{0p$DinLbBplio!K?xvt(-C+A)hH{h|2+SSH4yXtMVkAnFi z&5v=gHi7C$!G(OD%Lb1^FH5?~)mFV4$@PjYstqp9{Rhr^{LUSPrBKjnnt#x>@ zqGps+q@`()F6Iy%f;WNvAU=+cBHB~L;q=t0-Qhltm_|D?YOxTc#%xvk#UctH1; z_Rr1Lue91Rb^-CJ)PB!>VN?H2p)(Qa>tQE4WfVV-Af0MP^-rv2i(xE7d*#SYuj*4} zFfuw)v{ffah5hL96*!!ygt1-U&Yzkg`<N>DW9n4==oEazR zxLJg6gmoEv9s1Q)gR!&K)c$hm=%!m6+2eoPt#iwijz$$^HEgbWiYObsq?+SC6YQkm z-D+T6V*$lP`oy|M=A};}31YR#-F6F5<=^(I>3PN1%stPKBNuYZFV$^AyO{;kNEx4t z4j^ht2ZrrL&XQ{g{eypPZU(|Cvo^4O{TOT54RRmS*p;JaUb_CFnlQKhcci!8|5eVz zG5oCj8#mt;c5jiNGW|Oa>o8MxMnQ{!j+4|jyW{Pvkgty=CX3btqk88>;;rhX$XDRm~_m7w0)-Et?X!F z^BkIL@?>S`a}DMbQMK)O-;d!Mr`>-MzF-MtGiCo^ zxXN!+^)q%^oM~7F_kh8P_rpx_>s9b{g;2U`cv(BW_ou&h8p3W6CQk-Wkka2OJtB zG_(4-A4eCqr%l(;KH!29-6fqrMY$Ev9aBymKOYh@n$=-IO_NrhvSjNZO|?0gSCTLa zLMG8^#xnSAtg_Xb*3O?~|I~`6Z{OikiH0L9lzWr-knP)|Z>iSoX}jUT78{!yLT}Wh ztjhWjL)a;YFDRi+Uo^$0jJj?0RD$Vu9qPN0yW`cmvUqH6bm-J>=*^5#pZtkuyS=by zX4t2AjzV-L)9z3C8t3ar7aag<{`OB^W@}(x#32mc||6I#v;ieRVCA)2i zF}eqDbJ&yz6fNCjw|9lSpC~{sT*(5lut1H@{C0Cy6`?F4P&l*m=o@B1+MC?kPpyX$uL#=&#Jg zJ-p>p7GjvWwx9w&GH@c0n`rK=R)L3v+S#P%d9-1Mu)%>YCLHg-` z_`;bJ$V*HWPiYt>^G}ME@|&t^=UXUwS05m>X|p6utzzKY6D&Jrc7>9?mnm}6iMvDM ze(#|v`L|KKRE^b+&nc0MU)6V!q;Yd`Myow)6yV{H*Z1kg9Z>C0l0lr0GMC+FGqftr zI(l-G_Pun^_Yc0blcr{33y1Y9snFw>toNyXsRmE5RfPOQ5$fp3P8sY81x+uo+`n#X zWeMRo%_Rr127$r*W4|+{k#SXuBrS0n{sm-apMd6m}uU41s3{a-GB^ZvYmbJ1j^?Y2l@2xuKsv5fof(ZP&WNo9U5*V{t zt9aL+RqNjx;In&Kf5KmmB3|vR{`Eu7nRpY?URq#dSTeexd}yQE{o)}kWdm;y5^NiP zE#LYO2i+Gm^Ivk%L*J<_hM)dM4WF%?0%pH-yie`Duao)vNrR*6(z_wNs9M4O-raHvo)v_r zMwPCl25gEdC4F@-HG)dv$b-pIY3iG63!2Rpx=%68^?8&iq%~uwDLU$!|%|CM3;#bX&nz&b?oCX1XB(h%I zf6Gevmd_DBa!+)YbVm(wirwulkA`_|4c~ zFzY@|Iu->+j?%n{3A;8B3v7Q`P!5jRi97<+3~MMowKhe$MV9^SZN(n+7Gikv;RA^8 z3ztQsbjk4Xj~N9yhQ;MxxZXX)?WvxvP>OLU@)nPHn>M;If;^If@l*ZbIdtUDrQ3sU| zyS?nNxHo+8)am))ighSwjUXdZ^D-vk_${N;lrq(&_xXy&lEu1X2HU=GG`-{^&x{La zcAwgAGC8;jeybM`Kb-XPeW z9^vw>*Jx^oRc4G4Jr_3^LhO{j z(IFQA$gx-Y4^%X39S&Umss&3gocwPZF@mzSuJ%j|i!h6L;r5UTPwoGGiVuNGN-F zXNMY0)TU=^pN@&SUhP1UblDcJJ0jX%E}q-db*?ikyj z;kpJ!4LmWeSY312%Aq5wbQob3T=8gZF+bi6TQL|^fBa<1Y!A*vgJwi~)Wpg=K18h` z!XyjX13&W~L^t~W(+pHrf;^+_pr?}Qw0rNJqH%n|XwcqAPet1NFa25e@-7X*Lk)+z z(I-?2U{>+WYrF(p51ne#hFU%w=cdHCOT=UDRK#S(Ty7k2vJQE|BA2{C?#$F8$XFPC zw^vm8W3VY#o#y(oAd_){8Rlsb5?ckP`0snWdkf|5wxR=DnmYcj&0`_RCr~Fb=y_6DyP@t`|v%Pys3{!kT8= z$?a^b@wMuspOkD#iO)aRk0l?|MrN8TOT?#X%b&75FY+R>=@(3`B*rtp2B8;U6#!bG zvev0xbxZ2=nbOJ3?ymlj5C7f&V(B5_Fm%A3Fjjx_=o~xS6yNmCf|!$V=F`eYaR2p- ztL+CE)p0w;vpeI>Witowe5X5}Z_<_+;e08+(oET%&3YPwrCSJtxz@jNs(7SIbs6V<81bpR~!TF1=c3v>`FF=J_-I=Ua_SYA94Go4NcVeLKoijmf|m35TN=9YN`{ zV{2eWE%o(A?%jhfiT@vgRA|3=`_=TaDgrwV;qgb3o#4*^wC_cJA5@)l+kGJkwD>W! zYU&&gzlC?Jf!Av%{~dr@$kWmBl0JoA%irK$A!SaKghdDIa=mQ#T{>m9#*l?2`l!ZF zK4rvOmhT;vNeGTLI=gv;*V#Q?a&edb%x!2tXMIJDwRza86gOaAah}bvQQRN+rA*cM zH09v10J(P1XOaFPNc?w(ZQC;8Ak)=4V6s28CBp zGoben`gSS&r6za(kQq`h&L}4T>4WHxz8H3(1le(N`uY>CQN7#dvRhmKMh`!#loSa# zLwsE#H#8o-54oaA1L0DchV`{8x4o!)X0uWsi^@9hC2KZ@wgltOcy!F!XjY;rD$Z0=~xAza$iXftnJtAT@Qa{sVs48roGh9ceD1E_-Xk^*mvDx{{z-g ztR1O^-Jt827g%7Ysh+f&PukQwssRxNOI~d8#ht# zN8g#!Cif_8B?xEIUiXJ!3H|dTt+X^(0|?N~$?FVCZ?pC#Z%6CFAz8lGA_VpHugl?-(dfmDHCMBXE ze~!8E(h$3sCV{$k-A@52bt43B43p0Kvwgn?YtOQH<|)FP|=ctBb72Bc+(ehsF6 zf$FZe!E0M|*mudUoslG_&VymqnklTlLkUjHji`05_kh2(k%H8#yp@Qg{w?%8BbW0P(=bQA( z$LlIy<{eR%aQ$6rfpT$81`I*h9^~X{&-UZ&i~whg)H>LZ%r}IS#7N&FD2XH8^tjmH zqkD7myTrE|v4F;t$l?dk*YG!9e1*Qr_lJeT2X=9&gPa}{sIUe$YE$-2KhWN&#FGX) z!+A}JwK3;RxH8Oe8 zIk5a1wdJLgY;>}@?5{7{iBc)Eg*evpr0K8^O5Wq}ol&|Q?@mZIq5_Rqjc5B@RbxVr zZ3CiX<|cPT|CoqWj$!>~6D4)p0&4B|bND)L_nOeB1_)^y7JA_TG9^CdR4C}SiEqy}rX}$s ze?rCD4HT1{vKzc#SZwa{b-rpD?*EJp#l4l6GCulas%)!UkIiZCBl=%n zbFl!cL zby@78pA?W^1LzgLH&seZ!i9S`R5VFW(nfa4lC_le?@dg0v1#{}%l%in-{m(A4z_=9 zx>82rU@XgD?V$Dq`#EZ^O}xE|t0WDK9SUq1#@TLS>2Gp$tmT(#*ygiS-af zfy$>Q!Rn}$qnn}PX|FF?%=N^_7@T?t*4kbwxGd&Si#87Q8I~F)%hkFs1(xUCradP8 zeC$!~D4aXkPJy-H7n<-j?6%?967!lq{N2+$Tz4VO^cm0M77z z9@}%vmN7I-C`_(CIW0j}S*mGuG0RLcU09zPrl3tPlA`Axb<{iE>RCEiWx4yx^Wg*r zdb+aHFdm;8QCukAW56p?tj$9}A~N54hNEqV01nhE=Olvq!Ov_(+|hJbs#ueSclP?j9~yH|8-9p`Uci8(zPyF)}k5$IR$p3~LGCbLwZO@2z$;2@^$7(V|$M zp?g`o_n|)eeGHyW`U3TQ8zs@fdRCOC3fE2@@pPlZAe3`8{zStXI188btQ9j&3f`+z7bH4gI@Y|XW zcT!uXb$|QC??IyM>kGhBV`h|Yx&>He9x}(e+>K1wY(bx`Cwy-ZcK397v30FL+Ca~v z(kovc^RZehgz4TEZGBtHUCe6^JaK8uGyX47nE)~}3Fnkm_(R;LPZlDyuK*QJJg7Dk z^h+;)x6HEm9RZhz+jVyQ4l2x_JKjV!fKO!^9=|4=`HXM#|c>9z@40ESy{q#g%}_uONAX zvK((~s_ok<#wzACRs6IN>|F>C$1&)ZWGFRLLjwh^gu!p4?JE(Xeko6BTp)Bw6K7nK zf!SaFZDm6B(&BH~upkny>DHHJRfmHo{`=?2&)xWYZ;?9hgJ@AkhoNIs5%1XCns6aFlH>5arppgUy&ceS*kE@% zf-$YUbP!vJDk>Vd%-=6el>j&3V^*E~@9QFeTS9HbF|23u`deS|6Y0O0d?5NnL3=!T zN!(hAE0flZG2tQPjMgeqalODyCZ`tx8uq6}o%g~k7pT7}KA&Zc`>{ToZR85s3#-Aw zb6){ia2gLzarmAj4BNfAgf^B1#`Mtrz?e!54;5GasqFBs%2vi#s|A)TSUw$+-=lB~ z7Xp&;v&fgD&>G^6$oBk&m7CQDxLUPG&+4lc_Xd}eeMY=DhsqYMcls%5d%KPyR<@Q( zib3VW`O7}c7V!j<$vyXSjVhkiNWyzwxt;>5lIUY|Z)e?V%1j&7aZ6R&qX{E6u9>Yj?YfE^;f^ z+O1%Rzl5lFwWqQ~IT~6AXMbUb#XF38 z8S6n*>fLEv9`>YbM4O?2^pvR?5PtiTyNfNP_S1q(FR1ill-DvrBJw)?9t77*Q&vx2 z-$Gvf^UmD!{5)$~El3W(ZOnX^f2|y*|KVVrqF<5I8{~t#r~Eh(K5ndoxmI0#m)m+! z_%BnBRg?5jNQUFtkD=x{6dys>k2O=7`6*LvCl_Y3%;8G}CuX)E+;GKBafcL>Qs1D8 z>iisPCR{3L6@TjdT5IWtGjk~(QcGx%zI5cMTU5CZ@BMlc*6pj2+F3|){3+(;A9GI9 zy%%;K1bIE({e2}HbDZd0p4Ay(I|+e;dlk=jJs>VFpDut`f=MdXH5lb6p1bwP?X&-| z-D?hPXN6(vM;W#E3#YNWYdq(kDe%R?ejSJgc39dK40|x%(iH&uKxI(Lt=O z2iTv!Yu2b%2sJG1u@xm-hSo^NkjP1MHrqZOK6kvf&^3Rohn?L*hJ*gsNVDW+>p|DE z-Bj_3yi!h|k^`G~R-(<;KIFyt{)-B#K*21SL;eHR^6N^RZt1tVaRv&X2~6Rgf(CAE zn&pF$vv1&i99b_0nn;*lp33J6=3x|lOZ|^Jk=u1)3ny7&HOBZXi2K7ieTTcD zTRcD6;p<OxI!anysmg2RDLe}*v$Vyttx^pp_>&b8T+Xc> z{B?B*%!l=Cni z6@Qm@Y3W*RfQ=c1ew#jlk6e6><F9i+cOEL+hlQQD8RW}O7Vr1d+& z2`wwP7?ix+P$5e105J*(-hY6JI*k@iC-dkR4>|nre6V)!ztH4z6wJo1H9e_`M)}C# z#kvWJ;ogsL2Z%erH7#MLZ3MXCn5eqAbP63Z19hQnXd zsY|J4$=DL^;JKwAdKqs&1O}+#m6=OlANaH^jds+8x@g@{yEd-=N)WHtOQJ4-^TnrP z9`b|OBE$Iu0%p?%iO>wf@h3uD5^^9oecIlKbK4GH`Dt=_V3Z!{AEsHUQC8laKarWK>NS;IF3i%C zsTwp@H+)-$nzB2P}TBs*Q*)hDjS~{t_ zJL6AE<_cZS$%eE#Ya47)6|fYvjm?s+i_Au3_mAlfg*vmk;l)Y4C(&*-^0Kn6&&loD z^7vX56S&_igZfOKff5^+qlr3Wgoimn^FJO@GQj)o-gq^2q1NDVMN;@0yYQmp^sU!y zoN~yk^WlQ5g-RHgS>E`$;v2HGJ*9_H6M;O0hbQ5JrGp+GvTz;Xq=tt3L#F2p(o@nb zREH*sL^}05W!OusEy!X+^d72N4lyuAmy#l2mzWL=qNl%xypn2sdHtcGgD|oII z#j8r6%)kvFD-|Vkau;5zkxvdKa%}#3A*AhgaL4sR%55dahl)2MmL3Iq1KXHlR~fs$ zXB6Tx!$a56Om*h;IipGFz>F<^$gloUrvxVpUnAe-bBfn6*Yl$!=bA2f#-m{qOk-(f zwWwI=4x+=y80LGw(+a#SRRF|~O2orc=5DTW$5Zf6E?X|C3q}h$oI9|~ysaxSWs7Xr z9cSx}XhIym??B0`@@aw5C)vlgb*=vaI-MKg{P~OE1cIkbWg~2{9yE^2wfL4%@fstk zbT*vb71Wn##EZa+=a-*jG#AIXoZcdi?(PQPyYvFmm|4?|oq+uib4mbL!xB`zL>S-I z^)E${lhV)Yx4HBkxuQ>nN7}jxpuB5Vx)YK+lB6y}{TJ@{_|GF~1%*mr(zC#batd zzA_^CW;aMghe!!LzpeGVOTg)z5G!LGJ@=t6CCkJ<{f~L6{D&a~R~H#W0XVhy7`?$o zX9T3#Y2BP&F=p-Z-N95EnRfy) zHZuh5!Y8OjLSOg{#yjLE=6%G&LHN(fLr@S`WnTn{`@gW2+&t5mS2LBq&JBrDW&jW8 zCE|Vct9@5!c}3|rtY$qEk8Yxvu;cI%Fqi@AKNJq{AV9~<9ty9~~i_%=?i5swZ_~6QSBPO|tMTgmzhH~TSJZ#BD^+3Cg$+sJ2 zPhBJFjl<`K4AQJr_dg8J4%RyCms?4o_H4IXKEIpQn~*}_TSQA~)lCz1{;uDbPMx>! zD&h%n@PA4(+mD&Tx`=b)=5kLc7p0l+^vs*s=Jh~)TFGlyi1o!+GH zM)K6}Jj+zLNcq%I18UW?-CEg*cAiUj0XJ1-K1y)H#ENkFO?buYsfB!4ZimJQ^$Kbu zjOmVZ0=j-z0a}{8{_f||Zj8`k1FMSz`Xezp!9`E2D676}ME8HuymvRrxs7S?-QMY0c9S_~`y>2!`EouGd1;_Fo+ucY@7SH8n7m`}Js5yEi}^dRPu>f1CJvJ16@{L56V@@np{|TP^}XXZeWl_4n#DtK>Y?*SR zVa4wNNE2k^?7gwYK+LVI4gCEmx({Hbu9+>E{ov{SsX#0hD8XkcNXeH!b#1E2r}*^j z2lztw**(g|$L0ZCrfq1*oU7NkmOu++?a4%5;+`CLq)hy}Cb>ENs0V6P7goT6{W7fx zl~Z8IV#qyigtwIpviE9bz{Lg7v=c&QIIod7d`?EMtzoer7In-;9op~7)H+@&*j}oz zK@;g#8DUN}K0y|U3?~W8`zs%=RYh29Xac9lSfa<)KD@#B-pl!I2CDHjm(*&Vdm0x+ zUDD6DpWPYlZ&141nt|b1TRD!+rsTwvt6~&p&8GQ9E%t>J7q)9?>Hk3>;CgKHWi zlnqW?u5f#!l18XZV})yMgpa(;+i{SP!sWtxUAi!O@DvW+hzScTiY62rr7Sm<3RoW{ zSE~|LQ9Y{1Rl~>8=Tzq1doN2x7VQi%;mG<&&Ai20*r`}mz&7#$44 zfJpro=nlmwRBLIz9Qmedjz{F=BgOXXwRKjn*|C~vdW6bQIlK@pt@_sT5u*y(azH~3V-VB_FgvLE3x=QkDWvElj0r3uc|ARo!{T+`8Fe&7)s9%suO z+y&&8HMjks*k1#bPoYx+Ui^@6)p;*@<0M|9qX4n>$eODZmMF&2I@{(3*C5!kTcPa`A)il_W746CM6nxftpZ$=AKP z2!JEsBcPWQ66jfpkT)49I1i)9+FvqxHxpd8(;qdwlV4?;tJZ)>&JPMsIP?>sHP&OE z@%y8(i{{DtEZ&U)?lfe1d(1>9-EUj!icmYb+Sz?c&BJ)wCpE*;Kji-F4{?<%8Y2Nx z&)nyk?O?Z;WEaUJ6MmFHUg;q0a6SdGtzhZhjQ+?))$+Wu7=LFJ-R0~z_%xc+?Y&%nC(_(u46+0M963FMRRaIs8 zKUDX3I{$nDcYq+LZPr==&%K|(r(y8j`K%8k*GeZBRd;Pg7yE|2vAw1lGe|RX%!a(@ z2SE~0`er>@pKu-6%;LHK*o@&x^K?{Z1lRMHj@Ld;cP)iCD=pZJbeBRtY}|U< ziI`EcK%hwR`~C~8Yqigwy6(<*lx22hsHCLr?z9A)eG;+-g2cQ25GH^myC9YQYM14= z{1_dWBv8Y%33}Fb;1Nu$O#t7Yc}+^*X9NTkb*;&xATz#FJENUPMHxYE|!^ajcl7qB??>(Mm2W zSqVsRIbc8A{@$Z5fo}_vNJuY^raJz2qnp*N6)_1*Qv!Q8@h2at#A2HsvM1JH)++oY z8ZFv0Ft;pe>ww3$pc)kG)lyJlWS?EnU?uX-O4hA*KEI6IUCfgy`A793S?dazU;HZA7<<88o>@*!InLgV%Sf;c```+}hyfb+$w+Z5~U#~XE^6cZQIf$z> z%Kz1w%GcIHf#{jqA~8Hy&-o&CCDPEz4}czMc8Y-BTbDyG0bCeHRLM&xt$$P{`7q%tuHuddnu_Uqrk#c zXP1OuploY=e`fmj3Zt~N;|bzz!@D{bzc(h%iy+l>l&N=@T#h(yQTe?;0A#-BR zFw|cMTpa$14`pF&z6ru>=M~B%OJa8=qCL7fd`<;+oO+BtzmCjl5t6rFMJ?|A0t=pA39aO7D8J!!pM=C!2S&xJ@A z9neW|n(B#G5HJ-)Vv9H%1){AR2)#B^_|J%e=PZt@^FMw?5WnwPp&|o8qFupE}9taMK#IydW)5kJ5L_7yIAy51KntJiT2KnXnFo zH2|u8%p>-b`dbyS40WiI5$-eEgTKdPYv#P?z(rxG|Mk?jwd^9OyE>zj_GywRzbEg( z6vL+sU%RaMiSPLmMcLyVhu=$|FFEZ z{Uw1wFr@kH1t*X1D?mO6zx>+6h|J=jv)3Lam|?YQWx^Ajygpu8oU8kveFuQ@kbJLW z!&uu_VUn&}1IJ&vn$n0Fl=)fs=_H*5(;1mW%3cP%^+2XZOGQ;x{u*z608z7BCX2+B zFS1`>43JTVzN_P5BAi-Q7o4{iWUZ`ej(*}p-AI?s?$KhS%4`Q{YC0R?R76UdhkZ&-(qwi<OrhEcWc!9qRiAl6oB&@k?6ii?AV{C-UD2UmPLSNiGmd**iLxdwNo>- zx|@C_@~(%lWLlb;G-YibgxR|~dg`@98Y7WqL;%$WQI=AhmhdC%fqCRW@adX|HvS;| zq5upchxQIP((#C`_%faB&pSwm^V-u%{V*C0G=U$M7XtU%rjgpnZ^#m?ad6;hY6%hF zw1SrrbmdGAnDA;n9zTvNd-wnY-sCS$^zw}3kVmKUIyVVGs{e?Wjfby*9Pck`fd;+B?znt>Y=diMxpeGQHj5mD6GuCr*f=*3dA9{)BXlddofb zz)D1z`(tLV1ak7!X~u@m&)Luo57&Lg>s6ARnByLmhlJ53FiX$<3 zeO(1jty}>Ug$%(Nia{&w@A{{$&Eyq*drzp>e$z;eodAEh-JtX$&CafWOI>cOUaXxi zQ|}&Ry{}mNbY*;4HY9D}zK+M)P7hr}COgoJ`hPp(PRl@Xb(tJ}mNjN{nGkLk@U@@4G09VBRH&x($Fh-B zXFX=h2LLlqN?|dMkK;)ndIZ}o>0y0h$#w1ru}Oe4bXDNVDLY{hFFLM=|?R`(o>Y|6H4^d2j#_V+KVsd_Hly{8Rc zG~h4oXkkteg~Hl2MR_w#dtXlkS`lmm(oxE78H-ZDbqzLW9}i0LA5byFNl;LvYyW)M z879Nf1(OTUL(MPGpN;Pk_xsv^(LVRXxCpb$6<^3<`3DOm_FpHu9y(bc?+wiumi8GL z2rXc0tm~_k3fiUcl*;~UA$x^VLY|m>vNU{72Ef+J_K!iA+k_sMCvO3=S`ZdAaO8SdB_-~ z29@p4cCf*EJ2~JBcK(s5MXKv1K!N=DV$|S2S!`(q3_RNO>600g5YBd)x);RIcsTOX zmF#9Ho>?#uHM9GXKY;G)^14BmaWMOL6=HL>GH&3{1_?Zd^7Y;?k#mJgw)(o)7Orx~ zd;njWv%uZ%kow3^J2%_Mlldut54;|ySam&fv3cfrmC)xzwoUFL=c*0n|ApO&L*~M~ zF|M&7@@T;HG?N=(t8s;5mMv?jPDCp>epaznyf!JCvoW>+6ESKbxXkTSU??YoDrV8j z=(wk&TtW=3n7)E?66B4o%}YKFn$Y|~Z_d5GyOd*5r3~G1)vRZUTA>7)Wm-Rbbg&pE zY~Y;3t%eN$%XqiavY)Juz3_U7y*-ja%w+y;%LH7vW4YZ%~Mo z5oJNAYj*>9yAPZw*6YW2ElEz)9364C-LQ)@(GC_H{A6=znH2XMk}8(~IXHc!E47J6 z9-nmC^l@Dc;9E{!6l$LcZ0cCUaE99i##ZE0O7%;*yOM&4*4A?Jwcf5x>ob!k<=U}` zpn^*GKfP{gQZ*az`QEr>9_hao^edEyHr76U7lziS($3!Z1+35?_h{1HeshWPV@w-< z4o!ko6)rtVcU4{6w*Z#?+n;1QU0D2Q&?E6BBoE zb8YcNiC=-(M!GuK03H+DF>SwK6P4Fwy`?LW7G}jYy^GlJ*~ zZ`Th)OK%%LRk;&yjr4<$XQ>`lj{H|%UbiH$;d@7fv%p_^wHm<6-@L?5*y|fc2fB>SZ21i6GdHO6CWN>EV5|Y4 zW>d9&g>BYKwF~}Sc+FbNgU(UeGrOm*E17ifE-lWF^YU#q3mFO?uvlo80YgU6^n^;Agt&zPiAtRg`aNH31%zi>;A~Dm8up|{v z5}I$zfAaExXwHt$mH0aW^SuCfi?4Zj6EZmEgPIQ)+2duM+zbUi0D7KL7=;A$JZhhrtrgD9B-AZ zbSyxVA71HhQ`HH8lEnQ6I;|Uku3p>U1${EDq=MS3*_BC4Q!v_gtLsB&@7b+xNo!fR z!OvvcDtib5z$lZO!PagWlmtWP*)x}@1}jry&Xe+P(LuIsVOCW`T!MSKGiWe5?!Q@9 zaY(}&6cYt3)Fto@(VKF_$vc@|s(?>!vJ%+vcU74{s;b&{IBmFfm8`ZaVufk6H=fwW!sb&w7`|+B*TKw>3Bc9CK!I}r0*m1P zGIkWVCJafdX+EEYAl+`fy#QF8H+cwt@8C*DJXj<+{^?a>gqbA`a1EQ|H|kaDW_uiv zNn2kFrByC2si~&dHj5n&5@0TS9qf@v*w$#soIBCP@uVhB&Wa7E5<7Z?$zkXzNPBPX zVf9L~l%`T-iafKMC7w<{Bul;DoBa_#lS8D`bCJ=^i|bm%B65VKtJ^Y^drm)B>k2qt@X~Tc(jEjbPKZZ zM09>AJ{)dE2pjY< z4ylrKPMN#4>(nt};gv2qer`2?s@O<}B&rXCM3(nWve8Hs_THv1SY{oINOMJG6+tJB=%NN4XE?ewn8$2S2+)robTPo#PV>px}^W z=~V*%!Y+Ag|MQ9PDs*_*1cdjhLy<7X0)NNhLvl#+U-W0XEM=jfXRcp*0nyyGKuMTm za&X++rqEkB=7T$ffq<-sP7W_7q)GNBFxhg@f#P zA`L2~D!ZBe&ct>i^WCMQI^gbk)-o}sl$m`)z382TTD4V04UC+G-r5fGyO|PINdn_h zy9jioV;`8;R=gRglJinqLB=guyxB<-J6tJ@A<7>Z_Jb$F=CQ3O?&Mer)R$*vz0&gS zm|;ahcWRdi4wuKbsNeu_tcI9@o!-JyxoA$@fThd)(sxQ4wgl2Ry;L^slah7`-|%YK z1pKd9+vIW32l{jTlO&SZ;$EsxeHlG?r{C>H4VDzQDaev=c_DU6K}At;f|AnD-Vls9 z?j%{)C#(-<=FLoyR^WCO?A_f0sOS)WK&>kHYIF2SeZW48@b|CRs*|%3 zIf2V-ZtL#9~e``n=I7O)62gGq>&VuPTO?`%Mq^@%I_Je;z)B>O`M z*-4bmf6rc;sWkQB-XkBW?o+L!g7wi)uG01z&2Sb1fwypmdNF1h1e;VW6Si!aQ=bEe z*G$te-%G!TK>k_!4HB!DL1BQFK5D37{$GGUD7?{C2EYCO>9p{wfFc4x9Im>q=-~Tk z(D&qXSkzM+!RuJ4tB$CK&2#YPvN%XyGY>tq!H}<4XgK%w{!a3Gri6q-^&hDbQ@hl% zmCo4Qxl`S?#VGC8genZ^nCvOnr)0+soW?5uf;`k;r*%)?gVNLHGywho9)`O8H+0?O zHw*>u@Z{-^X~B#;tA~ojB;Lp*7FYBOPUk0wz`9=t!x9N5hI^C#H^ULDvS6%N=YBkg z$u_h@_U0L%fSXhgu z?s#gXG==ZJ_7>H@HIL}^>Bn@$r0QD~=~ni6No=%t8d`JcPmqaOl&wFP22Am;j6bCs zb>{+#4CwQTGKT4rsEjJ-)|O!U{x@c#DA?htAZMklr<$ewFNQ4B)BV@ivpDrZtIN=Y zKt9{OvOp-+b$aU?uyC3+pZX-qqJ@Y1#(~vWmHr22Q2%CNB!s=02)Q!9Bx9`?aAtr# zJg4~{%vWT=!&sAVUmU1L+*Gq0ZOv#-dMAl0w%qv(SW{kCpej4g@}^ib9nlacw;$!z zZh#L?(l8&qnz)X-HYjqVLN-WnUVr!lo7KQ0zU3~Nu_LJj#VdO+T=bP7lnedr5cp2o z&s$s@in&+**gloI z&fcDq>I1C^wAv{f$L901!N$0Z=?MG{;PGkCR6;b&r&T^r&gXx;7<+e&0O^R0QdY1z z=sF?n{%!yobQ%~5AHQybAD!E0E`2cfaxmeA&%+;w4Q)t?StTH zud<16@mN`}D`9?H^;aUGA=^6a4>ij7-ujN|a}JQ&Kn_n{RMYrZPm(5-qW~py${*33 zYuhDW0V_a|k%aDt{ z8{in)qrb*ksfu4D$=6UG-iOhkIBoO&Ua#z#eg}hUG~J}lGgX^-#V!1fI-^7Jmts^h zDr<^z>GCVNa3&U#)_liEZ9pTCdYD6=Wy6gSYT_Y@BjS|%{X7!6-~;AfQwA}IKxNjf z`T1{b3>loS0Np!us9*-k4R2b*2(n^onxlBq6Xtjw#Y%O(ERTwL?Q7zM1;?(C&Mav2 z6@Lj}^~f&ih$uN_BSm3{RvAiG)qsW$qkP=D8njVD5$+X6{A1nYA)B(`zxKwRn1PCo z;d&maqK}K^O1JpNj2zS%Q6$}GdYzYeNK#~&TrizHzf9V6%lo~vnM5UCv3X9xLDn0d zf;8F&cRE)Q8=en1MT4BU0a1w)2+`A-tQ6mV9(w$G5L7-|dD!E?9RG*!C9ab~g4BfCE^@LbC^%qjXM z@a@#|b^O5hZ6lPma8^?+S#FZfALGtXYsbTZW-z`90@$H{<5Wbg>xaTylXLp}?luh;(^v%s#Y)_=sPviXN=n2T-i&;pMR3g0-b901vk z4(2?+FT~_vofkKt3#P)_lJIGbX+pZZiJNTy%qDxZ%M;cW0iMsNzOFJi#@e*?ph!$5 zGZc|x$xpJ>tUEW!pto5bqhN#+S5iCdReDFqk;gg{{zDF~eC$W7>)l_{4)-kwWz2Mx z-)WdTj!Ab*F7lx%L;V-kBvsj#d)ych|Ej6lZMUNme%9aptHu|wodm|1)zmI5MrP*q zcdC%@a;aG0s&qHY7gi!+@X#~j8hpiGfZARZzZm{U%>{VQ@S2Ucn~VnVp8lvW$)oOu ziR*pwwlE*d^tq`Go0Eas|3a%tO9HAuD^hd@MHh0|_;j!@yolNjocMH)?1QV_hwo79 z+~5~~Hd5i>x~_ujn$o)7F!WWo=Ga{?-v%S$ETs8+F~Bu4w!FQjlB6HwEz%6MeVWl^ ztld9Rh>R*b;rYd>TklUa+(lZfx`G%LzrGa)EAS-O1>)*1xzP(^8c}|kskGg@HhPQ> zeQdaePZ=m}-JKgi%i)Yw?B(ISIPY07?!Q+OKPRkQGqcstQ|`d{M!Dh%;~UX`J@-03 zW#|Nuvz0|_`y(#!#(?R{lz=)y^;H#UNjtsvBPX@=I~bc3)*VqLok^{_%O}qMgInT5 zG5Jkpz)FW1qk~CJK8hcAC*vRkL@JCjWz%lqTq59Yyj;jX#>9GqZs7hvZixf0mR9a0 z%7XayIm(QpFmMt6b2<)L`U=qE|Ivrr&H{~ygHF&ug;3Iw1oKT&*p~F97S17dWqByzg7hk5IL0NzGiiWV^Tpv59m6SCm zakndBLRGMui|p=JlZ*WSx|(D=;rxk|L;6F~-6nGoBMP4xOd+c}=n}^rVeEM#sWUlg z$+*AF2i((A#|&%3YL5YZ~V5|AbC&hNW+qm6&DWA!)d!sfnRZ8qjc+i#A$5zT?2R<>*i)ZfO% z>cu=BBY5 zF3Cb+5N^Tw=fJWc<@ThCWkE4-yq|aL5Tv|Nh@@2eG+b38)3Vb3YLF7TiP`2UV^)p9Fz=N0AT$b+yN<8Osd{my`SKJ zqml<`476kmWc=(sv!{YzTjPo+UGVud{jZ=8Nd}MjH8>)_{a3Sstlw*i~|<`cI+Mk?85n6hS&Qbm*=}q zOnj%SG9%U_pPF3M@qeo6qOE$bTT3P0^F6*>uwF{A`(Oqsoo!3}YWi1^=`nWH`i2x&u589?jZgM9?8U{XzI=~oWI%A|gl(G}+WIWyCgCw1gU7Cz| zXq^U$p*%N(alaY=2Ad_tH@x-#+B#wN*fK7RZGBc;? zI0H2tLP3tXJl*p}=O8VQzRp*iMtkv->FJglSFF<*fivIPai{g^;K2e` z6N`Q~>~Degb0_-jlfPgqcrkq%FK>!hDA!TR(kEY3N_*#^IjSw6Nzk0V6YGuBEI=G* zajf2TeEd%AADq`zv{6yVF6cu{mx$Kqqk3m3oKUPTMt}4pAV3232&L}&Kvsue5Z-Bf zhjSZBu&C05Xg6`xQpdy4eLd>Gq?U|D5OdT)%;6i=+tD#^x~e9#cvAjT!&QfNv~ASP z&9U=c5YtuVLKdtt5gF7lS15Akh!v4fZa0i@%&U!6?CNB;GTw16gPHn$Qx8H-^>)YK zI!Lg&RliI@7C$1fj-^O5>c_t>sAXEpG{W#rF%CdbDgS~9Y9sdd8c0VfjP^~#_^h8C zHFU3wMV;4$q zIYU?i_n1gSs?3ajQCNenB4QFGbW!^tU24fQL zSl`@M2OL$!E{V0D{g?5ltcd#KaIax z^wyIo5fn%sk)G#Nb|2reFYBb*3F*2}y?OH0{WYnrsRYpopW${&~ zV4&K@+-gfFcI>-vDrzt%3^$<{WSB@1gObTjk1DSx)}3sxx|Ay2u6h~ymXUOwAe7(! z8tBD%9WevtGhvF1 zA0#AD+ZT7H;~NWs&gZgIyfeEGW5ygL=0CTWU7q082+SxBac7$l;H+7X`aG zxH+A*d}lBE=zC{3k`k3v%IzT=8O+9*Jg0rk-0zaTKpGfyzsxS!xH5n8cN5qbZgsR@_stsOf+`20xGYx5HvH9M3kB=l@L(vCco>Oi0Q!(Ns z`$UkwW(Kfsawn>_)6L!Si!6-_-m%=JLSx4zP^tp#_~{8FuRA)6c_HPtOG?Lk%?-N^ zOgY8=lJk#2mFq%20A;w#sQ^iImH{CmooPL2abEE~V9IFw=JWvO?vW#`svgSSp@TKV zp)Q~>fw=ox4q%X?z6RrxZ@H;87|q*(K?nse&JouGZn*ZN_93kL`!D;#%4YS9=X!6E z6jz&0aPs^z@@4-T>*={6{9eK*&NC7foCtIW!V#+I_UBQ4UlS?Z29Oh$J_Hfa2;K2b z^(c1$h2i~Y?;BMd_HFw_k@{7#d#`;TNBKW@1_QdM(_m2&e@}Ic@hh8QK|YkU!ia=- z|B4lVGFhS8kT6&1ntkSLTN0`&oen!tyfJ9QzV&QJ8-Eo7TqoUyL=J485(djrb+MG) z=hFebBr&6p`!T#&6+&0S7{5ywpQpGnSp1Lv@6o7LI3}uW7I^;H$ahtp7~R74mhdZX zTE45Z7^5s8TZPcwDMHQvZu&gFWK;u;nn9PWGJ`YSaDO0ZsqnY>`VFWRDhxcD3ouy8N5vh)!<2J`+ z>e%-x6Hp8dS#U|!xMt7pX$37_=?7_aybpv(9GTHBhTVK3hO_tbPoT1Evj{OFT%fae5s z9QAsOVHPj_c#G=&pwAoHS|r4Aab@8>1B!3q>wtd>>O2=Iac}Wn@7m?-`cDetyx*b7 z#yVUVAqLwmVT?DW>Usy5%y(#@yW{-|1@J!&d%t`7{~3rcJi{)$3q+6)fYv2ekf=E(CFn2#_SgG}ubbLLtwL8pI7lVK6tCJ-PwEqgp^G<< zKB?V>rrs`m(#o*-F%(hbJD$!noqX(pYU$4In=sS8dL5i&A9w6m8^6y8Lzmu}C=^|I zKJ-8$1{MPPPRV*owa25x->bn4m%C>VevzErg(MY!f8%rwe<0k zqZn5wC~LCkt&VL|dz#R4I5p2eKG74H4G%T{`^pD3iMHDCbGM>K)50qs=zPI$CkOz1 z>_Ld7&Zw{zms@Zl3vX=fm!;jWQ)m`2gg$+|A5rZHY@%?bK+ ziH)wKcY5^l=%uJWUC`-kR<&0t)j#h6&B|0M>J&>0{FB{hN)ZP0{rK*DMoqX$H7EWQ za6kO4l=V0`>#|b~%)HE1-y)aM)dGKfBY;p-(_C)^qFL19nN~_5_-s_goAZ<@)Q0Qf z_e?6czbAm4#t$8%;X5^rh1O`(Yz`F>Utse3rUWS9&;z0y^l|q)=PGWft4K3*#SpEm z2+4|&r*jYcyjCwWz^m8e;{$;vkIt@QXvLc^w{bUsP_(=APaY&Lo3!X`4EhUxt^SM- zdxV3L{CSbT0CCG32La>*@!6K2n!2>jDM>-rp@#5eO&>r27CrvJwn1HCeh+A60z%Wb z>Tsm`pCq1efcw5{-TPage@RuHs=c4RYu7%NI`tF~ z!`^Ldd<5=sA(!R+_^8f%GH=x3LB;=EfWX9B_{#38j}en_e=v#H?OiWDeROgM^GCvF zs8`@}jE}SCz?&>?+B5b<_}u!YwR*zzyE92d0jYf|_Wb1SPalKAr;Nix!1b6ldkDM6 z6)q+1{x$!OTl@ag)Cej1`7-8=GzTo64_|^G43phjmH*>%9YOn(GkSvt5)AusUGKVL z4Zdz0pifP$?a&_a^n<|4(3ReO5f4LenMq7?ZRk_@?KS3{beUX!toka>a zC{qVJANT~kcfiEL7o)u7y|IHc#``JAzQGq}Ws7*X;xI5wv;>L~LX*ecyw+t&Zj)Td4Py)13t+R&4+>M?dl zRN|vQUh4o^!<|?KZ7`t2)R*|zH@QoejIGrxRW3Fi{d#Di=3B<2n(~!f%TJ@G*&Fta zf^&~%G|}-)KaezBZxs-kX7)j=!HCVTM>!vUvut|)s2lvAacI={hna#qxq9K?=DVS! zmKN>O+bX{K@4j3n%1X?^TTTjBeg0aPRTTT59EQLdql53n&#z_bVhdSdP-jEmf^SP9 zXMQ$O&DW>MLC-`7-2SbioA}0O%Jz;a{7v(bQeAj^-eoD+6;N%YzTupC4?c8WA>@r}!g#2|B2yM>{`?nG$13zAnyoIqG-Tr&#`rC`L4xk`R5gP-EZ*=NR+j5A`YI z_I4t84XQBSFm(b$*Gcx`ayGx;%x`sY>(EyU>s$veoMm|qBu%+E<}0V63aO9lG85J% z>u#9^pDPBGK2BK{^-!K!+;cX^#eQl#F(Tc<^c=Vqo!r6Am%kq_Nz%liCs+S+Hwo;O zb`2kE&#VUlSIj7>zong8^q>gr5Kn^q796==I%EK@4yG+Ce4SIh)4|P=xGL;6C;6+? z(_VM&@2`V)0FDjAuj$Lmhj=H6uA*c@6Gxhi)>N&hJj&2C6A8YRJ<|9`TXV05N>dN@ zVq?=AjE2@p{jHIr{hzNVnYM&JZVwKknHulznB6*yo-XIqI)b~hecg|>K+ONSI;c5S z{)rWCdj5XNT=B=cv(J*js?%iWq2^CaX;t3;+tJ|ktIa!@AZa?jPVc>yJSV?`mJ<8T z{K>_o9Tv9c#Vk?p8kEELx3xz$`3?Xsfg{%*ZK60d|5S6+;`9S;_}i|l8$`uv&fCWc z;LQ*x-tFsl?BU74yu3yZaG#{8&B3G*aR82k`;JQ*Ej}H)v&~xJe(t)7$YQ_ZTA{%2 z$ul#oePvTncF7u_%N!fV_8oVvC?PPku=D zWAc*aX%<&+bIx|PpyRL`qQN!AHfE6menk5FyiaPU-eSO6R6*sB`M++Pbo%|H1{-^8 zW(E5wSGAzo+Tay@$JgMBOy&A7=0xyaCb$wvZ~S-dbf;p&b_YAipTXO@5y(FK_V)IS1nOsj6(JHMGc8j)(@{DAv-!x3&^s3d4Mu3gli z2@)B-&&{(XF>92-@dNC7z2Ll{eF>f+M+Wpo0eZf;FA>9^r-nz|48tROl7`2}7h>aE&E838}+ z&Dbjo#8Do%U4nLAR4+RM<<0UY&NVIa9hDyiSCUpK)3+tGoBwEWuV8`B68eeARkh~5 zgs&=vZw@7;&J(Rc8)>uG9|}jn4V0%lIvm*P3p7qp3M=Nw34E}&;#g_i-x<*X3v^2V z)qQ0-*`2(> zG;kmMyv_Z$(Kk$+gDVyfl5#WhMj{tTETQUK+?Cd;FK3tCk4$TKn3gS~X27aHe8MZ3 ztT2{mdR|Z5u)r3U@L%ON-g@GH7v)j0KX1H@u9+}Y_j?3U4$eFv!}cAfv|e0QW(twR zPDea6T44Hu%C2%(SO>feUv|HyDJO42v_Pfy-{n>rc@rIrrv0z>YHuBc>WiM^^lJ9% z!|D-prT1slX%51Zi$dyuj5&PbOmLpU*mKPbdXm?Rro96QAD$T0wnH<25MJ>Yh}0l| zMeVTNA!CUJNb(zYxl0L^9pSF>FkZ$&3mhmzdZbE(~f^w57Vmf^ZRjf(xUe3 z0MErd6j=bZ%%1ZK3dqRm^Wov_%;Y&RpNlhA{h?Ip{wwA7% zyagc?J7C_hJb%$0(Pf(JjhE2ZCEOMy|A$ZvI{F_5E>yquRK|irTQ4#1KiO8B5B-^2 zg3G2EUR@-q+qB4i2GdS;jrYHvTAUw$TGCaL8HwEG)VJnl4V#7*{2g$x2}=RRnd0uh zh3^cnXDqVLO}iNFyr^8Z9op_!JaM*mk@swo5lwM-7occc{eMxk&D8xbPB-eTk_U^t zwP9z|LJUf{{n`A`iG9H`Xj61#yJ50mk&*6i7rmVqmzC*2w`NEaulg1^#G&mivxyH+ zO#v3oP3QH_X|#APiuwP`U^kKBss#wx=H25z#CCt4*Jvqy`S=)5fJ++SwE+S zD?#@vDpXkD9U6IL)8;aj^~Q>H6==!-gM2Ulwkz6w!MK3a$@Tt|HI|sbC0m)g4M*ny zJ0;u=-2cH7+k*i&-%Y_iJ%qD5;r88*bYV*E8fyIii#8CI=lav4?Dyws10{g8Zq7?G zarV-x1zKyE-|!Y7_|pU2>BSc`T1OY4oaZ-8gy)d zt=~G-b0MC`ybDiws5|l<@GVXiPrRyIBnggb5yziUsofd4q}HO* zfvX5rzwiD##d~pGm~S6-ad0`<-Do$=-;5hrgc-0y@?rAELBL{9WyfI~5`+sO=U|W- z-{>M#rVKgX7oF4Slq_n25JyrQz@JW&iT&397XhWKbyrdUwMxH5o<@ZHQqKPkkWg;W ztwwNqwE?d4g+Uk7ymv`$cx2)T?B>Bd&ReP-&ifh;CzrTNX z=aHKSm44j$%lWleckkbN#pCCLOaD<^zuNcMb@Y}MiYpB=et3I)`HeLSo42fI-01x> z+a)Tj$vGJ>6Uq6(*Ey%TXG%W}aOOn$5*~S2G{nzu-c{eR`<|Ow0UUWvhDfSR3RW3% z=&VYj&BS-;0g~{9RC*I7Og^UtnW^$ox*tpzN_+AtVRORxk^Pu}tl!eJ9ATHzEWyN& zOL=mtuX#|`Z=}5cDjYsgutdnWO~Unz{wRPI_ZY1lPLHF=bj%=$I|CF_-}qA8 z>j`*l@IjQNU72_Lyae3@N|;PY7)coP$#UTr%y(NXBaDCN$*2n7sk>jc-t-lL&beW?ZL@CC_ba(SO2`2b$Q$SW&{NDKvPT{3p;{*J}?Nc8;KgD&& zt_9CKTk!a{nL#Aww6{j9GWeGkq^hJCxz1^DGN>O9L}tZmv>~WebV89qVo=CKmX;8j-U zcK>M3_v%G&Y6=sjx9KGo3jXxmYjpuZORGURThxFJwOXN-cSb$qt2WZ@NM9Anh)Kqv z%zn|)(PgR5cl*NE(cK&`u?TJ@_-6f;o{t!&Za#exA^&?>V$%#lA0PNzfY%|SyJqLr?>sE?)5Qvi*nsS3%mB3d0Ds92~Acxsb$RWm{;_DRk{(g5u@#V(n&|xFTJD%T0+4}1t!&{`QASOHlIDm}(oKBI z{7@qdN~YnEo0|U(J=F}+<%Pn0OxRaph{uuG(cRHT4%6q6*)l?{jI4<_a&07Q^=+k? z+Ku4neN(A>h#WR0yyVd=9oc=;-E>|AJG`jtfKsvw;xW$wOB!Xhh*!+`4bNhWH@ zO3nYiw`zvidmdX~iQ^Gbpt`cjhniIx;=*~2E31Nxm<<@MK93+B-5Kq9N8aVv;btBw zF)jay+HdzoZi$TMa2hY{svDprs)~%8A5C6LjRp!s1o6Ha zA-Bk{CQ|9lr-)`2L9|ea-CQ4mH!ZieGP9csogWD&5glm>99~x4I+}!=)1Q~=QI(UC zc0-(I-(I@5ZghGD8-yo_T6D1hB?*_9lwWf*wwuwJ4+NRd;$p^odHvM8^)0*mhj$~A z8vZVs>3Tj;k!TQJgf2koHkxXK4iYlxD00{rAy??{k%GCDHKQ>fb!|T>Y|U zFlGA?46Pg`g1+JbQlj?MWzSF8^N4w4^M#xu+Zrb&i%;xk7A1=R91lm%9!AbUL8c+o zO?Bedu=d6T<$0TR_y)O|&-k%3s6vZ)=OuIg^L;qr1zisYiW9YGhCH{`zC*D2=I`!S zABx{!UWnTL(=yV$hZpeP*+vGYFN^qa4>uR#ygG0XELnnfZ+>GKTgVamcqD;O?{`YS zBV9x|)R+_^(~J2Qn4r5*t>f{DVi#Q+|Yzl9@Kxv}!7P&fj@wI}(wpYCbw{^_TE_ zsmIh&8&l}5aFp^;u2eT-)?zk$6zS{LMLF*dv_gb6=tVV#eG-nsHY_YwOL?P$_+t5M z#+6J5>0e?rb;7*)Vdv-{^U@yXQM_~lvCC?I(@;}sSvN1lHAZ}<;h@{9;9uLdH>Nx4 ziHV5k`QH`}35SbRYBZE`D@|9ayp5Z0Zq2}FstsDLDmRMM*?3!BSA2QCd7_Xh94b<( zQCFH6Xt;ijML?XbPm$MPD+*9wX0~IPUj`EySNFx5{(;|3-G%3r-yfV0R+^=l$ni!V z1uFiW-~4d;0Xnll88oFXyGRM>a5WE-7*Tygowk6Yfj3Ma>Vw*r^4i!wS6@)zUn^t? z&qNj~Bz@9uRum*4v)})gkWn;R%~#ZVA}*`cZ-v8*hf-xnT4Z4rD6FkfS7}q4S4Uwm zb(z-92KAIScU|!-M3vGP!aiYt(LgmvQEN+N5rDt^DZ?w%VSu0#{A5n#*(tu!&;@$z zTRr)UQrc@Bb(Idsc`K>bX{wrS6uA0&&kvbeSyN3hbgd?D9C=%-51b;wzH_KPtWS74 zVS9Lu=bGpOM z`r;CT2lry5=5yJG5ocx(0!QfsVYl!@QAIUHQLCXwO{Mz^qGs?ahGS+KV`ydh#>u@u zIp{csyhc>jdLZl)o&@Om`8MkFP9OuD9p)8*fqQ%NlB}P#7$eakcnQO%g}`W9Ssvkb z;HTd-5OqhESEd^>D=`~93}gf2#dD@Ph|#uPRM*D);(w$vV%p0+$Y+shne+q`HvWiL z4EgaU#*5{E#}{Zbvbr)pEv{0w-h+p^;9NUo42PG5Bk$+x!a9Y&CZq-zp>?)wELTRr z9Dy*wWA}N5ydp?*bg20U;W%3U4gp2&pyUFc({*UTM!}?AGwIp{ZC(!uYZ}%eJf4sm zHsHC3dx?x_oO@l^`XaJANzuPlC21+|^?XxR-aT86zkErb&#;*+V#EZQE@n%4nO?(i zY1HU&3B?mpr1-upWIUUf2c7w-|2Xn^9UT!t_<$TTp2f?7%SRKNe-OPyJLMv1;Cu@F zNiOCWB)ZhzXnOg3ObpVE-h;T1@w>cx(3x?m=L_zowt3+W;#qzA1N#)Jd-bm`<@e7E zmneK&bh6rA(Z4Fko`R_rBO{*jiPw^xlkA}OHh4IHo0A|x3KY83pTqB!+C1~lKpuUS85n)zld9t@Y108 zOhgCbKjrHf$}NPxrsY(YZ}VaCB-(vmhLS4fYIQ?qIc815hIYH@2c9ou2Vo=iK6%-W1hMw*OYijwn3SnOs`)P{EmQG{i|fSYw3WBg4qgp3)#j` zHXJa$A^$tVrWeZ98j8V0)c&9D%lFZG5V!ZgCL{}+ij=C=70Vq-Ivcw<$kNldl83hy z`Kd1%SubrE?;RRIyt&VZ7wyGIN-@#S$A>dneOT(o-=cvU-BjG`Cq%!a!o0W)V4XJc@^hwC?-q zf+fI*Z;IGvdu4|KvkXIu1I;hISPH_De;^SuJ3Pvx7-hq$TzM~Jw9#Lky5^qyt!X!8 z;ND*)1AUyv+gj8$QG?}Eb8$pJLi1Q`gvS}DScqLQkbDvCmI>*OBhkFf(tCpOQ>{u92u0I2k)`{s>f zB0cfn^W_iYO3D7?K0H6D^mYX?*2DRUQ5Nju!DvRy-N4@b-lk>L>;pmd;okxgMb&=C z_w~JFbVJT39Z?%6C=MaBNJmKzWhEH@@pC+HsPx4$xgBMAd)!P;(PlV7|l@ManZevvuE1j0BE*@=j1hfQLgCGO!jf^8@{vP zGLkO09zs>vbD<$rO~u+zzwk_yUqb{3HC17yLZs%OyvJwK_{i)Sc~t3`Dti{El3*a1 z@q8fvnOK21cJ6WfZ;}sP&lwdE_`u&Tq_cd45`)Z+6;(C{dCz3eFyN8!?(vN*y?GCK zD)XH<$#a7I>vZxr${UcvZ_13EH>e)BrPQ>jH}gOMJ*tBg`*Pvcf5XouWGlXOfj z*Psu0p+E#LA7x}PGVe})K=oLGN9qEJ6m65M93+ZIxZu0G?lB4&g;3v#$o#+)O?((h z51T5T)VQaz6$#p+tG=PNuaUx*N{v*^Q}PPPic3+|{6~@zJ6~>7sa~aFp#3xs)Nr2R ztStT48{LE5VB&`GL6K&aUX_so6&sv0-uu}G!MWN6|GqDUkpX!WCE4Y?;UUg<=9$cU z;t0HIK7fbyDnHM_GveCevo&BXAOZ| z>0Y2OVHy$Bwvzv+tT=8tB>bFI+{T)O?VX@X&)+A^7j6YM?#ME^qlw62@|&csFOm@t zjr`@ROXDV`em20hXDNda zK!zxw`7rLngelJg3cjDeJHVs{$(c&)gjp}>l^lrrAWB^R(IuCagEaPgCM&{R7~jt` zg@S!R{Ot7{jdmx-o2Y{b;k=J$z%#PV^d2qBuIz_;vulW8{pme~KPq|KWzRUtaDqIZ529(W zo3GDKBM)x#Q$KBvli=_xq0*uON79k0%K37!_-T4&0SHnJ^2%t84{XC=D#PLps>@9g z()>v4){Q`B>qtT@{{xm`19=om-&EdXA}dpHHe=tLp-vQN$+0C$UzR_RMHAz#U{uVk zc^bUUwj3Y1kGcX#B6w&WZIf!cZk^O}Igq=6q0cb7Th=+$Ii~<=^6qv&@12iD6MzZ5 zZy&<~^3WgjVz5xiOQRW%x6X^=GYpoi=^MTk4T#r|{90-AC;1^p399Zga?BZ0N2Uni zT5s;tV;DgmCFEF!v>h?49YgaCs?$vYI>Y}HP0BWTInaIuC@QQp`cw8G?jczbigjB`?>ch z1;}EYn<0H&zGp5PECit6guHII_k{AWYr^Y7WvI{B)M1+5yiDAVcHajh6Y-1WApT|7 z8ioc#6G}kWY%-ytf{spqMuz29fp?WZW%uJ0piQwYm8YBb(l)RU0n7JZoC{Llgg=B= zgvwv#726An;ECelXDm;TyAQrAGfTwV=Ws}{cO^XU9>fY0go@AzdN7{xbNnUPs{W~L z*&cZf2*;ACx4!#idWg;ZA z&@8Saw2Iqg5LF>+u97xKg=cTyrZ$0BQ_zqyr1t(@ahp(zbSlgqZE+dSRfn|7_YoT} zGQxr^V8we+2_#vZ9Da@fOq1YKL{s}5egL8z!#7YTG&VF&fPsQw&0hQPb-c^Nrz_R>jfLtsQOMpfLK=`eok@k7VY6XiHV|vkG)T zg@%Uihsv*@Os`#b+9CcI)^OrJ`xAR~kC7=>39^`YaMCQwhRB7@<&m^s-Bcs}KwaHt zXe-w2+ST+O;=9Nb3G5N}$2~fxMoLg@d!(*dvTOI?B^Z-4rQ*oe=C+nL8Uz(^a=-*z zkD6nK&%Cu$##blP743oZcgm35Wk^^iw^s}W7dejA(K_(UIqk{%rp zEIm2rj|wmZCw}nmxvXm^v!ZQ@@fh_b0Ic`1;v{{-%>m1mQ27befos|FabXPDlYGk) zO@GcZ&A}=_CcR?Z9ATxII^s}P(w1HBCP5~VijaS!COUdxeMCN-{{wt-4+^|OSy}oy z`*KVbpz1f7y3w8MV}^2B^@4){y^rR2@uk*`w#>GBz-7iePks}J5&4KSj6~q;F16ln zy8|ltlE<*gP~T<{m(zBw#+cGRa}t5`K4QOOH}5er!J618LKC@0 zjv*h{rw!d=ago};NCWS#c{%t8`V+vjzUg^~yWDpifP)m(!AH#>r6uqb`~X3C;15M8 z^=x4D@}6ut+paHnIYedY;taDdcQ3^mS^$JRO7`=OMX3kM>&nHe!7L5_j5f+U{F}SZ z=rUNT>cgQ!(ioq%E8cBN3Xn5zg?=EH(gCcPklGt->fOW!ZG_{KFn*^f*{oI=Ce5Pg zvbw<5=(f1FcmUc%E?Ugob&hOGtAl$qIUMRD7|h~li?Ssk6hi6lmj>j`u*Z~Ev1<-= z8*>!EqTg;i{c%^6>=e)V#;LzxzioLY_HXxZy9D-|U*zbDM|WkBchOSW0Aa1C>=*36_Y@n~ zF#S;gmCgg(Y5Sa{j|EtGK(n=No}ngwZhnDEkca9ZhUWd4DwLw-QE<}-K9d*cwg{XB z{TQSt3dwQ$%dSS5FYycvlG*XLGi|P3YJP@(R^aJWOyKK+b(SHpaT0ynvn!kgn*c_i zEQqneE5j&xtZWz+2UCxNo~(-OhHMH5 zARZ$3ubgF+ES4=?nR{GtGW0|4^XwW>wUz^W?6)}^BLAK;Se<_jJt^qUD$XWlSAg2R zPDiI_Hc$?cx6*!Rzl5OJQ=z{CqT&bHCD|lUeYVDSdT!Tp0-7d*6^U>WoCxj8%E>Ow zE(W!0z|`{9CscgDjF$VORzc-n*-6ApSl~Z1h`sBmNF6A83@I-Kq7# zazDF}UBuP}dB))93`^hFK`usiA*s>3A?n?WlfHBJ_JIL3TCXTTeF4MSEQ6E5Qahpq zHbAwVz#+6F>q>S?b~>2;Yh@pjhyR(QqupVH_XW3^wjBr+`hr|9AtS_rNGC17BPsy; z`afZ3AK>nn?}y`D6UN_~8hf4?lnd9~U}v%K9T?p&0;{ZV-3g)-X?c!1j8SPFJM%#I zehzpS0>PJN>ETmX-`l|G^!^$ zceWqA2FrYiDa8lBv(X@>R)ErQp*R68%PHGE+Y8hlmAyk7xqj6g;^>Ms1RlUeyR^D2 z_j>=uzU$Wplpop2)Tb3YUF}JNA67dpXvxK8pClmiw0thfu=KJHj&p)7ssyqac4}nV z+aKEW+IE5P9}(Cj+BhA0BMt`#yk%hXp-rQ0bEwb<1;4N`gY29($+aG8KfKyNr#PsyO`#apSACoP#Xbw2lFotvYY;UgQqBx zI2Z;D9=sx3Gh2sk*O}Ydr~m@lXc}&a{DXAvFp-oCB%APc@Nvu9_j9PV0Bl|OKTmqT z{8hoR=}`ck2gg`vzg}zqgJ_U>MZ7Z3kbX%%E@h)@^1*W!#nJccJla>bskf~G8ij5l z4p;tw#v0N30aPB`aCwkgR>dK~W%p_Y=pa$YcABn#SkTN#1Zn7(CaiZN`wAN@(H_A# zBeYZx*AxUK^-rA?F!`KSBdRw6{J-XAuavbJAdF_A%3_9^@nP|NcyC{;yhJzp-{jRpqnQ~lcmjCD zzuqV^K3_C}VsA=63 zO*Sy&IheYn%(Eb$GQQ+M%zVC_=hnChQX9TGr1eA+D)I$DwB_aibq|9J`v&YKsT}Ux-o2lq}_lV(KQP(7i=?$FDK;VV5 zYJ9k)aUV@xuWnEZ9LI z6Vk1a^ARSgJ}6wCEviH@`SpCzD)+8m?#Sy*N!kT<`;cC^jS{n<-tl>ia{Hv8BV&LA zL{R&1^WV4L!hhcy!wT;iBYcQ^D(PxJOl3 zA-cou#D%+~uW?Or0Jw=GqT7>t0#t<-GpSw&2<(3~{x)k3Ziw=5WG2cHwnnx%5h_0{ zN@+TDP?R3+INm9HEO8J4*Gov9b=8YvW}`s#=86wNY4!B6TZBV^=9?lCiC-MxePeljB@b}Owz z_kcHHH>CWK>qqY|s}XEz)N(CuaEoMuO_hq;j_8@QyEWxAR+5F)g}is8}XvWF(d8M>U<(lVNgLw#mq6Aij%OSym0 zYogmU)63Fb^%}rF5JdHsjiN=he_8B}iQP{lq3c44q%7=mYWA0yp70W`6a7VW0BC;2 zRlUIwZoa~Rv}UFt!=>@zxwIDkj%XjEW}D8Ek*sjnFW*0eUvz7DQ^<9pTaebQ0F8ac z579OD(;v|M(41|g4W+<7OwI2YT=k{`jf9?F}>l>xo7CG3C)7( z_91dK$@zpGr_js1I$gnRRWBM;n7j%RwHWOXQc)NSX+Cal-F&57DocsSC3ApoVZD zg-m!J>Oi#F5j~pxgQZTv(dlWlx+9?aWsodC{V$oqSY{9pXbBsN$>eIq78fR7g5KS_7%V5301!l1R6(}z;`Gph2xx@+O%v#c&v|(Ywm}B38(IJZx z{{2W28QT#voV=F&CCZjqyyF0t*27U>UYgq8!Dr@>;w#S@lFaMX!Z2g?Bibh5DKPiM zL+Wq;Xp|^b)SV-l&xMhqhAn8OtvajC_U6UsCFP~(WeQwe!~eu?;!D9Q1F zaV^Rj;C;1)Pfk1Oo<1HTP)^eB;!@)CqdDa0PH|`S-i|z5b91@5x`mE~5l||@I5L&2 zvqXBBSW~uBqGxXCFs+O;JUTs!Yci%ewaX#@L^nFQiJ!yj$J66T@@R+Lu`%fvOKvQ; z5v=Es$>1I0pcxI5A$1`-@kbpGXO1f2=H3nYRn1y=k}_C(1=uBiPw2iRx?4^9KtmcH z(?S1sGp#+dJ*T~(y|BHw9gd+MHZB-ijUI4hcgK*3E+WZi@^GlTEV{(<|H{yT}}=1m@w z-r4}h>N9u0%hDvqwoGyGM)uRy=mNK|%^BSX42K%J$9Z^u{kKnON5|!!5I4WoEJ{S*`4wEb=>lc9=&Q?kFs9#F7XEnWaQ56ijl4Nrmo?lrln` z%v>@%DPkHp&W?#*40n7@-TB>k)(^ycXe7bKkk%t2O#Ln0{1F^xbgqXv$cVhLKzAZz6F8x9SDWFf=S zidG?<`*SKBX9%y-*qw}`LihNBBrBabx9`%K=r4jE&hrlUf)=ZSIB;AMj)>)jh6OBr zVyr7MXOeuXFB&&lF3P?rvYT#(^Myk=RFi1SSx39+P&{dea01;ijJL1O)D!d2q<(zl zzt};1>I)HCEFjajNX`){{Swbp@)|i)PT^RTjY?{*nskbFNIEq-6uE<(Jl7x-Nup;2 zprepare("SELECT * FROM employees WHERE id = ?"); + +// Fetch employee with user info +$employee = $db->prepare(" + SELECT e.*, u.id as linked_user_id, u.email as user_email, u.last_login_at, u.welcome_email_sent_at + FROM employees e + LEFT JOIN users u ON e.user_id = u.id + WHERE e.id = ? +"); $employee->execute([$id]); $employee = $employee->fetch(); @@ -16,9 +25,77 @@ if (!$employee) { die("Employee not found."); } +// Handle Welcome Email Request +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['send_welcome'])) { + require_once __DIR__ . '/mail/MailService.php'; + + $targetUser = null; + if ($employee['linked_user_id']) { + $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); + $stmt->execute([$employee['linked_user_id']]); + $targetUser = $stmt->fetch(); + } else if ($employee['email']) { + // Create user if doesn't exist? For now let's assume we invite existing users or those with email + $stmt = $db->prepare("SELECT * FROM users WHERE email = ?"); + $stmt->execute([$employee['email']]); + $targetUser = $stmt->fetch(); + } + + if (!$targetUser && $employee['email']) { + // Create user if missing + try { + $stmt = $db->prepare("INSERT INTO users (tenant_id, name, email, role, require_password_change) VALUES (?, ?, ?, 'staff', 1)"); + $stmt->execute([$employee['tenant_id'], $employee['name'], $employee['email']]); + $newUserId = $db->lastInsertId(); + + // Link employee to user + $db->prepare("UPDATE employees SET user_id = ? WHERE id = ?")->execute([$newUserId, $id]); + + // Fetch the new user + $stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); + $stmt->execute([$newUserId]); + $targetUser = $stmt->fetch(); + } catch (\Exception $e) { + $error_msg = "Could not create user account: " . $e->getMessage(); + } + } + + if ($targetUser) { + $token = bin2hex(random_bytes(32)); + $expires = date('Y-m-d H:i:s', strtotime('+48 hours')); + + $db->prepare("INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, ?)") + ->execute([$targetUser['email'], $token, $expires]); + + $setupLink = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]/reset_password.php?token=$token"; + $subject = "Welcome to SR&ED Manager - Account Setup"; + $html = " +

Welcome to SR&ED Manager!

+

Your account has been created. Click the button below to set your password and get started.

+

Set Up Account

+

This link will expire in 48 hours.

+ "; + $text = "Welcome! Set up your account here: $setupLink"; + + if (MailService::sendMail($targetUser['email'], $subject, $html, $text)) { + $db->prepare("UPDATE users SET welcome_email_sent_at = NOW() WHERE id = ?") + ->execute([$targetUser['id']]); + $success_msg = "Welcome email sent to " . $targetUser['email']; + } + } +} + $pageTitle = "Employee Detail: " . htmlspecialchars($employee['name']); include __DIR__ . '/includes/header.php'; +// Fetch recent sessions +$sessions = []; +if ($employee['linked_user_id']) { + $sessionStmt = $db->prepare("SELECT * FROM user_sessions WHERE user_id = ? ORDER BY login_at DESC LIMIT 5"); + $sessionStmt->execute([$employee['linked_user_id']]); + $sessions = $sessionStmt->fetchAll(); +} + // Fetch recent labour entries $stmt = $db->prepare(" SELECT l.*, p.name as project_name, lt.name as labour_type @@ -97,6 +174,16 @@ function formatBytes($bytes, $precision = 2) {

• Joined

+ +
+ + +
+ +
+ Add Labour
@@ -220,7 +307,7 @@ function formatBytes($bytes, $precision = 2) { -
+
Recent Files & Attachments
@@ -233,7 +320,6 @@ function formatBytes($bytes, $precision = 2) { Filename Linked To - Project Size Date Action @@ -241,21 +327,19 @@ function formatBytes($bytes, $precision = 2) { - No files found. + No files found.
- +
- - @@ -270,6 +354,45 @@ function formatBytes($bytes, $precision = 2) {
+ + +
+
+
+
Recent Sessions
+
+
+
+ + + + + + + + + + + + + + + + + + + +
Login TimeIP / Country
No session history found.
+
+
+
+
+
+
+
+
+
+
diff --git a/employees.php b/employees.php index 4669a9e..f2f7a2a 100644 --- a/employees.php +++ b/employees.php @@ -1,8 +1,10 @@ prepare("SELECT id FROM users WHERE email = ? LIMIT 1"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user) { + $token = bin2hex(random_bytes(32)); + $expires = date('Y-m-d H:i:s', strtotime('+1 hour')); + + $stmt = db()->prepare("INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, ?)"); + $stmt->execute([$email, $token, $expires]); + + // Send Email + $resetLink = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]/reset_password.php?token=$token"; + $subject = "Password Reset Request"; + $html = " +

Password Reset Request

+

We received a request to reset your password for SR&ED Manager.

+

Click the link below to set a new password. This link will expire in 1 hour.

+

Reset Password

+

If you did not request this, please ignore this email.

+ "; + $text = "Reset your password by clicking this link: $resetLink"; + + MailService::sendMail($email, $subject, $html, $text); + } + + // Always show success to prevent email enumeration + $success = true; +} + +?> + + + + + + Forgot Password - SR&ED Manager + + + + + + + + diff --git a/includes/auth_helper.php b/includes/auth_helper.php new file mode 100644 index 0000000..539eee1 --- /dev/null +++ b/includes/auth_helper.php @@ -0,0 +1,93 @@ +prepare("INSERT INTO user_sessions (user_id, ip_address, country, user_agent) VALUES (?, ?, ?, ?)"); + $stmt->execute([$userId, $ip, $country, $userAgent]); + + // Update user + $stmt = db()->prepare("UPDATE users SET last_login_at = NOW(), last_login_ip = ? WHERE id = ?"); + $stmt->execute([$ip, $userId]); + } catch (\Throwable $e) { + // Log error but don't prevent login + error_log("Auth::login tracking error: " . $e->getMessage()); + } + } + + public static function logout(): void { + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + $_SESSION = []; + session_destroy(); + if (isset($_COOKIE[session_name()])) { + setcookie(session_name(), '', time() - 42000, '/'); + } + header('Location: login.php', true, 302); + exit; + } + + public static function getIpAddress(): string { + if (!empty($_SERVER['HTTP_CLIENT_IP'])) { + return $_SERVER['HTTP_CLIENT_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; + } else { + return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + } + } + + public static function getCountryFromIp(string $ip): ?string { + if ($ip === '127.0.0.1' || $ip === '::1') return 'Localhost'; + + try { + $ctx = stream_context_create(['http' => ['timeout' => 2]]); + $resp = @file_get_contents("http://ip-api.com/json/{$ip}?fields=country", false, $ctx); + if ($resp) { + $data = json_decode($resp, true); + return $data['country'] ?? 'Unknown'; + } + } catch (\Throwable $e) { + // Ignore errors for geolocation + } + return 'Unknown'; + } + + public static function recordResetAttempt(string $email, string $ip): void { + // We could log this to a separate table or activity_log + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([0, 'Password Reset Attempt', "Email: $email, IP: $ip"]); + } +} diff --git a/includes/header.php b/includes/header.php index 17cf347..41b3b79 100644 --- a/includes/header.php +++ b/includes/header.php @@ -62,6 +62,7 @@ $currentPage = basename($_SERVER['PHP_SELF']); @@ -89,9 +90,20 @@ $currentPage = basename($_SERVER['PHP_SELF']); -
- Tenant: Acme Research - Global Admin +
diff --git a/index.php b/index.php index 784074d..355f4c1 100644 --- a/index.php +++ b/index.php @@ -1,8 +1,10 @@ prepare(" diff --git a/labour.php b/labour.php index 25fd0f2..754529e 100644 --- a/labour.php +++ b/labour.php @@ -1,9 +1,11 @@ prepare("SELECT * FROM users WHERE email = ? LIMIT 1"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + Auth::login((int)$user['id'], (int)$user['tenant_id'], (string)$user['role']); + session_write_close(); + header('Location: index.php', true, 302); + exit; + } else { + $error = 'Invalid email or password.'; + } +} + +?> + + + + + + Login - SR&ED Manager + + + + + + + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..cfe6a75 --- /dev/null +++ b/logout.php @@ -0,0 +1,4 @@ + Monthly Calendar + diff --git a/reports_media.php b/reports_media.php index 2e5012b..8960187 100644 --- a/reports_media.php +++ b/reports_media.php @@ -1,9 +1,11 @@ prepare("SELECT * FROM password_resets WHERE token = ? AND expires_at > NOW() LIMIT 1"); +$stmt->execute([$token]); +$reset = $stmt->fetch(); + +if (!$reset) { + $error = "This password reset link is invalid or has expired."; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $reset) { + $password = $_POST['password'] ?? ''; + $confirm = $_POST['confirm_password'] ?? ''; + + if (strlen($password) < 8) { + $error = "Password must be at least 8 characters long."; + } elseif ($password !== $confirm) { + $error = "Passwords do not match."; + } else { + $hashed = password_hash($password, PASSWORD_DEFAULT); + + db()->beginTransaction(); + try { + $stmt = db()->prepare("UPDATE users SET password = ?, require_password_change = 0 WHERE email = ?"); + $stmt->execute([$hashed, $reset['email']]); + + $stmt = db()->prepare("DELETE FROM password_resets WHERE email = ?"); + $stmt->execute([$reset['email']]); + + // Log the password change activity + $ip = Auth::getIpAddress(); + $stmt = db()->prepare("INSERT INTO activity_log (tenant_id, action, details) VALUES (?, ?, ?)"); + $stmt->execute([0, 'Password Changed', "Email: {$reset['email']}, IP: $ip"]); + + db()->commit(); + $success = true; + } catch (\Exception $e) { + db()->rollBack(); + $error = "An error occurred while resetting your password."; + } + } +} + +?> + + + + + + Reset Password - SR&ED Manager + + + + + + + + diff --git a/settings.php b/settings.php index 1bbaf1f..6160bfb 100644 --- a/settings.php +++ b/settings.php @@ -4,8 +4,10 @@ */ declare(strict_types=1); require_once __DIR__ . '/db/config.php'; +require_once __DIR__ . '/includes/auth_helper.php'; +Auth::requireLogin(); -$tenant_id = 1; +$tenant_id = (int)$_SESSION['tenant_id']; // Handle Form Submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { diff --git a/sred_claim_report.php b/sred_claim_report.php new file mode 100644 index 0000000..217e508 --- /dev/null +++ b/sred_claim_report.php @@ -0,0 +1,510 @@ +prepare("SELECT * FROM company_settings WHERE id = 1"); +$stmt->execute(); +$company = $stmt->fetch(); + +// 2. Determine Fiscal Year Range +$fiscal_end_raw = $company['fiscal_year_end'] ?? '12-31'; +$fiscal_month = (int)date('m', strtotime($fiscal_end_raw)); +$fiscal_day = (int)date('d', strtotime($fiscal_end_raw)); + +// If fiscal end is 2024-03-31, and selected year is 2024: +// Start: 2023-04-01, End: 2024-03-31 +$end_date = sprintf('%d-%02d-%02d', $selected_year, $fiscal_month, $fiscal_day); +$start_date = date('Y-m-d', strtotime($end_date . ' -1 year +1 day')); + +// 3. Fetch Projects active in this period (with labour or expenses) +$projects_query = " + SELECT DISTINCT p.* + FROM projects p + LEFT JOIN labour_entries le ON p.id = le.project_id AND le.entry_date BETWEEN ? AND ? + LEFT JOIN expenses e ON p.id = e.project_id AND e.entry_date BETWEEN ? AND ? + WHERE (le.id IS NOT NULL OR e.id IS NOT NULL) + ORDER BY p.name ASC +"; +$stmt = db()->prepare($projects_query); +$stmt->execute([$start_date, $end_date, $start_date, $end_date]); +$active_projects = $stmt->fetchAll(); + +// 4. Helper for Labour Data +function getLabourForProject($projectId, $start, $end) { + $stmt = db()->prepare(" + SELECT le.*, e.name as employee_name, lt.name as labour_type + FROM labour_entries le + JOIN employees e ON le.employee_id = e.id + LEFT JOIN labour_types lt ON le.labour_type_id = lt.id + WHERE le.project_id = ? AND le.entry_date BETWEEN ? AND ? + ORDER BY le.entry_date ASC + "); + $stmt->execute([$projectId, $start, $end]); + return $stmt->fetchAll(); +} + +// 5. Helper for Expense Data +function getExpensesForProject($projectId, $start, $end) { + $stmt = db()->prepare(" + SELECT e.*, s.name as supplier_name, et.name as expense_type + FROM expenses e + JOIN suppliers s ON e.supplier_id = s.id + LEFT JOIN expense_types et ON e.expense_type_id = et.id + WHERE e.project_id = ? AND e.entry_date BETWEEN ? AND ? + ORDER BY e.entry_date ASC + "); + $stmt->execute([$projectId, $start, $end]); + return $stmt->fetchAll(); +} + +// 6. Helper for Summary Calendar (Labour Hours) +$summary_labour_query = " + SELECT p.id as project_id, p.name as project_name, DATE_FORMAT(le.entry_date, '%Y-%m') as month, SUM(le.hours) as total_hours, SUM(le.hours * IFNULL(le.hourly_rate, 0)) as total_cost + FROM labour_entries le + JOIN projects p ON le.project_id = p.id + WHERE le.entry_date BETWEEN ? AND ? + GROUP BY p.id, p.name, month +"; +$stmt = db()->prepare($summary_labour_query); +$stmt->execute([$start_date, $end_date]); +$summary_labour_data = $stmt->fetchAll(); + +// 7. Helper for Summary Calendar (Expenses) +$summary_expense_query = " + SELECT p.id as project_id, p.name as project_name, DATE_FORMAT(e.entry_date, '%Y-%m') as month, SUM(e.amount) as total_amount + FROM expenses e + JOIN projects p ON e.project_id = p.id + WHERE e.entry_date BETWEEN ? AND ? + GROUP BY p.id, p.name, month +"; +$stmt = db()->prepare($summary_expense_query); +$stmt->execute([$start_date, $end_date]); +$summary_expense_data = $stmt->fetchAll(); + +// Organize Summary Data +$months = []; +try { + $current_dt = new DateTime($start_date); + $current_dt->modify('first day of this month'); + $end_dt = new DateTime($end_date); + $end_dt->modify('first day of this month'); + + while ($current_dt <= $end_dt) { + $months[] = $current_dt->format('Y-m'); + $current_dt->modify('+1 month'); + if (count($months) > 13) break; // Safety for roughly 1 year + } +} catch (Exception $e) { + // Fallback if DateTime fails + $months = [date('Y-m', strtotime($start_date))]; +} + +$labour_matrix = []; +$project_names = []; +foreach ($summary_labour_data as $row) { + $pid = $row['project_id']; + $project_names[$pid] = $row['project_name']; + $labour_matrix[$pid][$row['month']] = ['hours' => $row['total_hours'], 'cost' => $row['total_cost']]; +} + +$expense_matrix = []; +foreach ($summary_expense_data as $row) { + $pid = $row['project_id']; + $project_names[$pid] = $row['project_name']; + $expense_matrix[$pid][$row['month']] = $row['total_amount']; +} + +$pageTitle = "SRED Claim Report - " . $selected_year; +?> + + + + + + <?= $pageTitle ?> + + + + + + + +
+
+ Back + SRED Claim Report Generator +
+ +
+ + +
+ + Logo + + +
+

+

SRED CLAIM REPORT

+

Fiscal Year:

+

Range: to

+
+ +
+

+

+

,

+

Phone: | Email:

+

Website:

+ +

Business Number:

+ +
+
+ + +
+

Table of Contents

+
+
1. Title Page1
+
2. Table of Contents2
+ +
Project:
+
Insights & Charts
+
Labour Detail Report
+
Expense Detail Report
+ +
Summarized Claim (Calendar Views)
+
Labour Summary
+
Expense Summary
+
+
+ + + $project): + $labour = getLabourForProject($project['id'], $start_date, $end_date); + $expenses = getExpensesForProject($project['id'], $start_date, $end_date); + + // Process Insights + $employee_hours = []; + $labour_type_hours = []; + $total_proj_hours = 0; + $total_proj_labour_cost = 0; + foreach ($labour as $l) { + $employee_hours[$l['employee_name']] = ($employee_hours[$l['employee_name']] ?? 0) + (float)$l['hours']; + $labour_type_hours[$l['labour_type'] ?? 'Other'] = ($labour_type_hours[$l['labour_type'] ?? 'Other'] ?? 0) + (float)$l['hours']; + $total_proj_hours += (float)$l['hours']; + $total_proj_labour_cost += (float)$l['hours'] * (float)($l['hourly_rate'] ?? 0); + } + arsort($employee_hours); + $top_employees = array_slice($employee_hours, 0, 5, true); + + $total_proj_expenses = 0; + foreach ($expenses as $e) { + $total_proj_expenses += (float)$e['amount']; + } +?> + +
+
+
+
Project Analysis
+

+
+
+ Project ID: +
+
+ +

+ +
+
+
+
+
Total Labour Hours
+

h

+
+
+
+
+
+
+
Total Estimated Cost
+

$

+
+
+
+
+ +
+
+
Top Employees by Hours
+
+ +
+
+
+
Labour by Category
+
+ +
+
+
+ + +
+ + +
+

Labour Detail Report:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateEmployeeActivity TypeHoursRateTotal Wage
No labour entries recorded.
0 ? '$'.number_format($cost, 2) : '-' ?>
Project Totals: h$
+
+ Note: Hourly rates are recorded at the time of entry to reflect historical wage adjustments accurately. +
+
+ + +
+

Expense Detail Report:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateSupplierExpense TypeNotesAllocationAmount
No expenses recorded.
%$
Total Expenses:$
+
+ + + +
+

Summarized Claim: Monthly Labour Hours

+
+ + + + + + + + + + + + $data): + $row_cost = 0; + $pname = $project_names[$pid] ?? 'Unknown Project'; + ?> + + + + + + + + + + + + + $d) $m_hours += (float)($d[$m]['hours'] ?? 0); + ?> + + + + + +
Project NameTotal $
0 ? number_format($h, 1) : '-' ?>$
Monthly Totals 0 ? number_format($m_hours, 1) : '-' ?>$
+
+

All amounts are calculated using the recorded hourly rate at the time of labour entry.

+
+ + +
+

Summarized Claim: Monthly Expenses

+
+ + + + + + + + + + + + $data): + $row_amount = 0; + $pname = $project_names[$pid] ?? 'Unknown Project'; + ?> + + + + + + + + + + + + + $d) $m_amt += (float)($d[$m] ?? 0); + ?> + + + + + +
Project NameTotal $
0 ? '$'.number_format($a, 0) : '-' ?>$
Monthly Totals 0 ? '$'.number_format($m_amt, 0) : '-' ?>$
+
+ +
+
+
Total Captured Wages and Expenses for SR&ED Claim
+

$

+

Includes all Labour Wages and Project Expenses for Fiscal Year

+
+
+
+ +
+ SR&ED Manager Claim Report Generator | © +
+ + + diff --git a/sred_claim_report_selector.php b/sred_claim_report_selector.php new file mode 100644 index 0000000..465ec5d --- /dev/null +++ b/sred_claim_report_selector.php @@ -0,0 +1,66 @@ +prepare("SELECT fiscal_year_end FROM company_settings WHERE id = 1"); +$stmt->execute(); +$settings = $stmt->fetch(); + +$fiscal_year_end = $settings['fiscal_year_end'] ?? null; + +// Determine possible years based on data +$years = []; +$res = db()->query("SELECT DISTINCT YEAR(entry_date) as y FROM labour_entries UNION SELECT DISTINCT YEAR(entry_date) as y FROM expenses ORDER BY y DESC"); +while($row = $res->fetch()) { + $years[] = (int)$row['y']; +} +if (empty($years)) $years[] = (int)date('Y'); + +$pageTitle = "SR&ED Claim Report Selector"; +include __DIR__ . '/includes/header.php'; +?> + +
+
+
+
+
+ +

SRED Claim Report

+

Generate a comprehensive Scientific Research and Experimental Development (SR&ED) claim report for a specific fiscal year.

+ +
+
+ + + +
+ Your fiscal year ends on: +
+ +
+ Fiscal year end not set in Company Preferences. +
+ +
+ + +
+
+
+
+
+
+ + diff --git a/system_preferences.php b/system_preferences.php index da18782..8c912e0 100644 --- a/system_preferences.php +++ b/system_preferences.php @@ -4,8 +4,10 @@ */ declare(strict_types=1); require_once __DIR__ . '/db/config.php'; +require_once __DIR__ . '/includes/auth_helper.php'; +Auth::requireLogin(); -$tenant_id = 1; +$tenant_id = (int)$_SESSION['tenant_id']; // Handle Form Submission if ($_SERVER['REQUEST_METHOD'] === 'POST') {