From 114dacacea833100feef1eaf2e4e00f8b550fdda Mon Sep 17 00:00:00 2001 From: Rustem Date: Wed, 20 Nov 2024 23:30:33 +0500 Subject: [PATCH 1/3] forte integration test --- android/app/build.gradle | 2 +- android/app/src/main/AndroidManifest.xml | 3 +- .../kotlin/kz/com/aman/kassa/MainActivity.kt | 46 +++- android/build.gradle | 3 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- assets/images/fortepos.png | Bin 0 -> 196711 bytes lib/core/locator.dart | 3 + .../models/{ => forte}/close_day_data.dart | 2 +- .../models/forte/forte_close_day_dao.dart | 182 ++++++++++++++++ lib/core/models/forte/forte_post_session.dart | 85 ++++++++ lib/core/models/forte/forte_response_dao.dart | 155 ++++++++++++++ lib/core/models/halyk/close_day_data.dart | 54 +++++ lib/core/models/halyk/halyk_post_session.dart | 10 + lib/core/route_names.dart | 1 + lib/core/router.dart | 8 +- lib/core/services/ApiService.dart | 13 ++ lib/core/services/BankService.dart | 27 ++- lib/core/services/ForteService.dart | 160 ++++++++++++++ lib/redux/actions/bank_actions.dart | 54 ++++- lib/redux/reducers/bank_reducer.dart | 10 +- lib/redux/state/bank_state.dart | 69 ++++-- lib/shared/app_colors.dart | 1 + lib/views/bank_setting/bank_setting_view.dart | 30 ++- .../bank_setting/forte_setting_view.dart | 138 ++++++++++++ lib/views/check/image_show_container.dart | 149 +++++++------ .../close_day_show_container.dart | 2 +- lib/views/history/history_view.dart | 2 +- lib/views/home/components/popup_menu.dart | 1 + lib/views/home/home_view_m.dart | 2 + lib/views/home/tabs/AdditionalTab.dart | 54 +++-- lib/views/home/tabs/KassaTab.dart | 80 ++++--- lib/views/login/login_view.dart | 46 ++-- lib/views/payment/forte_pos_service.dart | 158 ++++++++++++++ lib/views/payment/payment_view.dart | 199 ++++++++++-------- pubspec.lock | 24 +-- pubspec.yaml | 5 +- 36 files changed, 1476 insertions(+), 304 deletions(-) create mode 100644 assets/images/fortepos.png rename lib/core/models/{ => forte}/close_day_data.dart (97%) create mode 100644 lib/core/models/forte/forte_close_day_dao.dart create mode 100644 lib/core/models/forte/forte_post_session.dart create mode 100644 lib/core/models/forte/forte_response_dao.dart create mode 100644 lib/core/models/halyk/close_day_data.dart create mode 100644 lib/core/services/ForteService.dart create mode 100644 lib/views/bank_setting/forte_setting_view.dart create mode 100644 lib/views/payment/forte_pos_service.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 16b6b08..22c09eb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -47,7 +47,7 @@ android { defaultConfig { applicationId "kz.com.aman.kassa" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e771add..62f6a25 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,7 +30,8 @@ android:launchMode="singleTop" android:screenOrientation="portrait" android:theme="@style/LaunchTheme" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:exported="true"> apYCF<5U{1(p0nbb{G#Qb=|b;y9-{z(-FjlftJ*Om2V9jI_9Nk?7Htw`iVu|ROI=7N72eo0)Bt2xH5S@+uN|jR&4WGz! zP@5u-bFWqDb9q1$YGo@eH`gE?-h|0!LC|fL=Q?b~k*iuM2#iOCI@AW*bA5Vqx8-Ec5z|Jc zUdt1^7*PPi$fVYGJVd4{H_$5LMLurg0`N+Wd+Q}-lPFl=P3FjSUbDXtk1e)bH3g?B`qOFLR+-Si2qzw@)uf}_iVmH- zJt3CE-#nq^r`^nrLH7JtC$bQs#m@I2x6W@GPQUJsYNY=X=Ah0&c_p2=1in>MuFQv! zzp4mCnXs?+ZdUuiiE5gYvvZCUzm@#C5Q4K@Vr?Z~Zpz^~zzX{oTeoxaS_Si4p;XWH z20~OfU+slcBU@#}Q6*r-iRO+>n9CI)+wo;|KL^DvIh*q}H0H2UQ}p2{;@deGu%z`Q zDKos*yq7E0`VUV(oXM(2NnNBYDEu~pST4NC&T(>9Noj;w4qH%`hgowqX^tU?=r(s; z07B&X+A%LS1i=nd*)t{n!|omM0Fj(!$&K(h%$HO5x&Ap0zLDN2h`Ee8hFLAf`sabi z%3HbQ$?CEuJbkYFtj=?p7A%%a!BJnDX_$(NA29nLfUin=6VnmSb5zD7zADX86W3t} zeCW->XllqAuG3CMF;~b@5rpVX-I&B}&;>AAscbcK?h08>nIxVS>`B2J*0DN_;flv< zCRs>AmL0iok?2(GAd>6z1b-uz{7HZ3oYx3Jz?dVP<(_Z@=-DJ=ge?b>`v`ZGQoT_= zs-;wR8WFjHV8w1a#{_pg6pN(-F=(bKmLt)K4@=R$wK8C)aR#A!La_)R7Pc zptyF3>+qtAFQYikf>>fXznsf!IcP&DkA%Ba?NbhiX(t}7w6_4rK#OXedJ#kyf$FDBdKisc_$rGZfjp5 z$Wn#nSZ!ss4`Ml3qhvWK&TouA#FiOX(u3(8Gv2Y;h~>%@eieGjYmL*==8j=G^_&BQ z8=i_n44Z0#=sI$7?xX#KD;`uwBjhuT^)~Om4Rj~N0mY>heOsMgadu)9&k%WOHPC8%aFeUH9^9L zASc0?+i4ts+rsWaG{?VX%yP(3LD!SJq%wAwq?r>(k_(t@FD`@KCdiZ<;xV5e5}h-g zsL#W!xdMamLnh$LmJ`biynpVhB z17Qz9s1;KMN8a4vFj%W}95p?-_9W?FjO_rbrG+rx_4K8;;Y@0;YsLQAww%A87GYH; zG4ti-GD(myxBa}a-~&lc5X(uiOp=fgrHDVCzIaw<^EX61dtpAp4keRAs$jo>#iDvT zm>-ZeXMLTF<|1zeEVt~kf;WRUk)DD%4e$)T;}Mv~xD4<3woG*Kj*oTf15XI*enQwN zh|)*%LEVB0(ndqta%_0;;KQrq-C;Dhp`_o6M=-Wy;v5u$QC46DjtfBzkyc3ZT^UC$ zc4L%v=(T#{+>quDo6lt}#5|U|lqkyT42VZpbi=ld!A;=>& zyG}2I!Z61)JE;)Q_N#c#vw{IkVkN=pbnjDEoD{K~cn<0~{*Z(3S?<7~>e28SHLR=$ z&Be`J;-{0tUU5VS5FG6mYOdr*tx`U z*tL-+tE@VYs9CVaKJ0igm!hDKgV^v?N=X5Re+1WQ1da+Hc;@y|`&eo;2VGs$i$Q1X z#&a;0S`}ZPsIU@3VF+1rwTG}}&ZQU|bRxOF7qWu~NpmU65Qqw8&HZGLl`^(S4hnEm z+VJe8!nm0WX%oCyuJ5C#XN@`8Z`PuOQgK%G$a2IZ z9^iicF~h9K*~zUWKM*m2~+h`CDWC@BqjQQAnPFduwwL~ z43MZn2rGfu$XpDifa$?DrxlPX7m`+{0uflu3uXmG2-$MQnlrA0R#+~?80oRntaEZ3 zbxR-coV8)H3}Gw?K$?73A9=%NkaagKZT-2KBn2E($QZ-`OC(rrxxj7Ma%&cN(Orcw zmV26m7%Vnq2%`U7f)4HoY-%Y>4rFzT!>Z^*TLHMN!AlIY8X4i=7B1^t_@TUsf4CJD_r3Q?STImvCcaxW6y$e1I| z`9-1+HORWyacRk+&FF)s z+i>VKIPT8l467*=d7(Ije@9$30I^oufiH z&nclKK$dflH4D5}%qh_v2wu{8UF$fVL>F@f3O_(#6^jn+hpuzpH&`b*PMLG#!PGeD zsqzuwQ5CU?-NbT`K=oUl<-A$?ui~-hWt=r$fq_$~L;YHrE zpo3giU)cK1QrMM+RwOxEWGBE2A#^dfitq!8iDw}Gy;}HzQP#x(M6eu-#%R64GaaxO zgk8&V;lLwvZV}FL4yQXM33rH*oaF)s4d%#k4fI&?s04zM)&ix^L2mlMn_N5y(i@#t zO`+fc4Tju`KI;mLxK-XjI|mVWM!|pkVbIQRnUs_&&5(N^INqD z!egx1IG`zYj@#fg_v_hmka|DXn)BA0H7mG3K9IGB9$ucG=Tk{EPTv9$1V7qO4I;eW zY-t0Yp(S@TmhkThN?1O1+mn zD;Ne6LaZKRH7M2!#b9#`i}yO)T64ovRvrWctvmqM?IZlK!l6^VHP2BrJrIIG^LIw;+T?1#~sUU+(p700i~aThF`22 zedsObcrQ0f3w}OyQzP0+n3HOC`_N^-bw%73Qr2GJ`8h~#o%jD^mcz%zvIlB-oaJhe zHL*u3$=t=9v}Kn2BFB@_RpU8+9eb?_oae)5RM$AGl|Oii*MY~X*6lLp+KR8S20TZO zGl=BIqgu^lg%IJ%tL_$^_zvs_wj27bqPk)GVC@@hGvgXm0RbJ5AqT~7=9D}ag2yO> z4o5o`VdXB?oOMcl;R zMXh;Y*$_iXEBbOM6}r+% zmbuM8&v~q!=IL6V!4apVk8H#qxAX*EAraRWz6Yb?xQ?bQCl9e=nVH(?^XMh3|LOx=B%|Q zmS>aDXX8CY@CC$9D!fbKKKD8ZM^4~ zDGEtf>|}Wg%MFXcD$5mX4$8xZEVmSYK>i)V54&DHaTIg~CzV>_a<_8Dq!X0KTl#2% zHSA(eEay-?YiT;iKfGMb{UD=cgGZqVk3l2>j5!rU0HgFdX@gSMK`_Faq+0}HY|Z_mnIr)8LF@s`YV<3DCoU)s{0iRrKMe98ZI#n>@dyDW`tDmuI;@I?MHNh`_X920unDXQrWotZ!#IY{p+h zJB7D}D%5jvgx!ngXq{bqL;*iBOESa9wcRMnw<~wht!R(Vo)` zLzcEC&rzA@H*8NSso=EMiY#Ppr-1yqdOi1gHAj1{Qe2I&rXJdW+}xb=dDO97;F+bm zzaz`l_vK6ypHm1-5b|5ea?iBk3Y1AQJ0V{z=S5&VHBQ2tk|UG%t@m=}TJE>Xa`zAV z_qFCE?el#Oxp9{BlW>-kb`)w33s8&jMXK$i3AH?=(pAh#7K77x7yvHeY0uF0i3)=^U3l)ZwJ=4i{^_GZCk zI`Nx@ACMJ1F~n*hw^?nzST2{!^#iJz$nRu17RzhoD#Tk#A0YHaKTy8h&i*ZpNtex9CF7rjyh{2pqNR++lBaQC%LYXN4kjj<<7aH75njC{C#TgKy@iK@?cb72K90 zPxDt}St6Gby<|LgpOVZ59mf#A4$FBeFgXvO=Y3PWjYu%7^*@*`haQ@W zuxeDHBVAVP0)-#&$}?1KoAKpUVUT!>Ne|zZc46TeQ{3#R8%?(1C#&%4Gz+lgl#T7& zmaACsSRs!An`6%Y3=b7kg9J}tIWVYdy&jY?SJHx+rg6|;jiRqwHIIK@eNe~{i5?-@YI#dx| z-#GBZN};OOc6gU`2!l9IErdfia--YII*2*2DHVTK|mv* z<{ui~VzvhsQ#r#)9oMVI=9{+*W>55qS8|WJyh*VQpLXbjSnlIdEJx>5uSBW#GM0-C z{X|>t(cgB~W)4Wk*`})qV(Iy3j6SgTVMm&aBX=?8a1NRXm*M;LS*Z|=gkar`lII%4 zad=uRbA`#W5 z;|ug(*JB+s-to2ivhAkrU9shu7F@e%PEK{!LepTam)v7DQ;wU)pU4G3$0ho!qLkwU z5TZ7nKF2Hb+sLipxmlLuvdmtx8-(#X5}~@6o$}I2ev{IjI&m+b<~RKPR5)nzS`!&6k+DjF$ysxxdY#I zSnY*9*NQ3DO<#dO6>Bwv&dVTlJ?HSjg|qW-#$ANTaauCytWmlFS;4<9j_pAb4_Pk6 zi&(C9UTUj!L^os3Xl^R=^~G`(-l@dnn=<91h2nYyR8a9;Gss68|4UN z@l^ldItK#y)maY0QOr9?9`ab-miro1x2LfSyYdL2*oM+)T z4Wo(RLqrS9$OmGPKT69KrpC$U18Mme#r%O5Ilt3EDZ#afM=hRzsm?R9dE+m7krTl?H7j)2u zX(c)=QJ8YXa@d9Cg#OdNi^n=(x%Q(x3(G;0 zcDpzbXH9N$drS9no#p8EeaCXDg$4Kr@d#1!NlMMvFaj}`I;T$4nL z3o1aJJO`b>3|)1s#bbroZeu5=5@dCcbwpvK88=gH&ZjZ2b8l$Z}9YZ=OLndozclJFlK5tl)#?ldRQ@NvU%n zxv??F18X##Iu@Q!gEV1+)4fD-1Za&W-GakfBdw}{kQh6!cyL14;G17Q`XGFhM#GIN zC0ULterx-EjFs1NZK+zHGrWxF%BaGEz0M)am4=&8D$6nCaJ&iUv5G=+U?lV}pAHh` zSY#h3FtNf5S-}gooMiyIk|NE&msQ;Da~Spr@5)~qZ?YpzM=lW6$9Bn~X4l+YA4$JJAWtJCU*t)3|iDV7Uy zGi%VXDU-UD6sl8ct5)qxg6*>mV@ZJ!jNqhVVRwr2N{C+H!5h zv-Y&$N^(za>0%CRfI@M88AtEdY0g2KB(WX z>B=Qt0J2`rQ8?Gl4y9%mUo7z)6xeS(AvA&CtG+aodIUG;0z7U+cYHu_h3P)=ijKxy z95wnNrxj}bLBgX>(3oRs zMUAxzUiYOA!Zq4Tnp1si3A2*!oZ_h5>cJ}fW(|aAZ|ImWM}?DG3!;iY>^D5Qh);?^ zl*~D!w_?dbdi}m+^;v5a0_v|```{ppk;lxY!(o?niJmVvb1#?1+?JCcIFFUk)afRK z(L}ESSh*@Yj$Z?rN3zM*a-CbOC&`f8{{Qj#kuv%&ocpvCtf_YYIQc8+_J1O}T)1tm+j^miqvflP%|!v4Gif*y6j| zpaiJ1Zz|2*L7-Q9UmClwQZJ}>H<#zF{&(b=>;`q5AslOvJZIJWLuCJ8lVse_9*0`a zImO6?i_?tR*zwsJ1!=D4L!*lfD{RR@8wvIY--yB!( zm;U#NJ{SU*ll}z_{VL3q_>l}1X*N<_v zK2qgUY=MEt%0gD+`K=T;WVu*@hc?xoELSVG98P5bc`nxBkN(v;u9KWX4-Bmqma~Qd z!;)D|oh~f*YITiRPI^&9eJ1%;(F8%dLFAYqVXBUGI*73{UoK0i5To==k_|mp?5>dH z>dhQaYdUvtmK=_Uh2i@ugivB|9+op_EXUGTFM;3*gg)?Cp;ebvX>&5>Y`T{kF)2k@ zBv&gTaH{BQnd3xpI4Q6T%lYsd<2MB&z;eHILU7;1-{3EJ?yzh*n{Lykv0NQi2x^0P z&Pc?YV7-^};U|`N^%M?ystZM|a7kgT{fkr=;E#AA_XkK0IV3F8C(U6Q$xR2rhaUxZ z%WVDQO}X=iQoCy^=qol!j&H1IrOBlh=05mg9pF`#d#q`)1t{@2$GOKE=U`N893?$i zaFzoYeZUd0`#DN1Ud=K3K%w9RWU=O~q&3tEksKrsp<8kwIM%e*XscK*!L!66a8hM} zl?$T4IF7;!_J94_2a=ny92M$b0mkpO!YkN~l@GBMVXc;&H$`8@9-==IdrhB)4m>}D z<-jjuxj_&WP-LMoSqS8Qw3TQo%(zB!A3g-fZ5qibt}CJo$(}HWK*zvi|v*|H@RwsL;bXNH+)hz1L4$4 z%1v}PTqJe<*Po9#AKUt6HW;5yAkX);Xh+2fX}(qsI^ZWGzRDr>j1?00}}DxC}mz7TGv|Fq)R*!8QnSV48C z5n&3a@uVY`0pwE$KkX_|x6}1|_(g4@opYBG_km-8+jY{Mv_#;la#M_`7CzZb_cI2= zYYAQq2qB&VSmbH(V_yEVbjSKrt2SCW+`DnnTGalRR+VC0Q3(&Cd;Z>ya8%UO$+_SXf*^yV z@ht0K=BjjB1@(EgVSf~3+J5{dhtFgxH7V0HdcC&XRS3m6Lv|rzuYt2+NTNjNiH*Rq zwegx66v;?Uk;3tM^LbJ6-9J?^z=5~Bx_8t+i-r2lzTJOPG!G>=h&{z&1Fc8ecr#cv z6BhrZ1q{2(#sQ$m{we;h5bc)-U*hRqA*Oq4)N`Gdww<3^XuUc9P+w=q0%2uW8DGXYG%bH_5G0NHq|8umGB!;o8uFz zD`<^^^EsBjuJq=aS&?W*{^HAb`O&Ue&!@NdeM~pG#8`tHm(N;2!sPq_Q@*>5moZ0+ zL+hW*LXtjp6_j;q`;Eaqtqwk@2;ntHmNvLz8Edc?lBfN!Eg8jVtDfdm!C_bGO&&<- zkLX1H3HVuHEvu=|WiItVw`MxgLId~AeJ!>#i|!`flpkM1B%&FgfIirbnGdIn3D4O_ zy@${H)pk~@Qpy)S=>;nAdS{x86@~#TOE-OGOB3*}<7Bl_c#qL$Sy#xZZR?I^Aj^wT z9n;zx1kWJ{4{9sv=-I7SIHn7)t3g$IZu}jst5VB`AtAjTl7X*_@6^f=-tdH z=a+AW)%y)kB7}yoyZ$oQ+sn$b)$;z08-V%J0?+1tPFkW$q-XG&T-Bq(5=cpS;2aTd zqv8%uD%Z*746)M>?zwlzo14kClm5wr@1c%3-%A_p>%V?wX!2F?0QZ5dmo?nC2Di;2 zc4ZyMKvLM+D7>QJ>=Cpe{aU8~*mxe4 zzQZMcy8i_bY+nVIzqdU33DREc3^9mKpj{!V#VIcda$2W0i$)sO=&Bo|?t4g(ekkha zrE30lMqn(U2@vAFG{@EOq{MGicJ=n77m_G;#3A%qe zoA0?$fy7b=692g%c{)^veokrMg8%Ax%L|=I@cV~KZdUiL4Lsv%QrnD@?#M>m8NjQ4 zWeUT@*JQU;%?n8h4_f;|to>a-nLV4x`ZCKzg1TUG8sL373|oz-OnNT-)(PO+8YG<9 zgGUo%F zQq>+e_krAl>*ts<8tCy;MmNkfL`|_IUla}dZS}GY4P>A)@i=qmGaD>Fb~pPEt1*wu zmCQ^)p3=kk4_o_`e04goOO+}6nM^&@QuJKR-ua(O%OE-nwW%Cx;d0+}k)e&7x-zsu zE2J$O0Zji||KrZ4{6PaPm6{{FTyrepGo0Yr)Z%e?&p|tA6>j*};(cdbhhyo4c>*3J ze_-6}>NH>j*suWIp_9j_EC~aMC_X2jo+|FNO^478t)1B)+k`!n&;d8BUA^P?NvOSR zAKdbn8d|G72s5yA!%Ea=X{ZZu-Pz9kyBUS26T42u+uL9J$8-&9bPzkd24|-h#B8$= zLiBp3hU563N7p*_($Jn88j>leAtL&WH+ic!0g;Wi&G^}R*mdKe<{o`Qgc&!pMNm6w8a>VRpC>LR2$!neF9iXKE%4#uZ$UDNuf82Ukm@JL9LnMTBgTH!t*;r^3C43R~?kW zuulYJDuFTfnc6YC#(kI1aK5s(4sh-<6yGQ2>^YTCj4aDlTP?FGuh$F&%tOb!^r3nW z2T*s!YedtA#@w$o6fUx8cC4b+m6;b~dTy--KezAkJIGum<%JPMap4Pgc<2wxH~qqp zHVf59SQML><&3)(GQ(G8X&FGT61CAQi(YoQ<;Co46H@5u`p zMVv@W+7QRxf5W8deQST~dY*EWFaMh!SpR#c{0TQIndd{+8mSq(ap7K{1ixDQJD-D~ zJEYy;ZtQG|XsFi_~^_EE;kutMSAQ)xTdEk2(2 zGm1Sv%AlhuYxW{no!rK%!!t*zUVE|4Yd8{B8arYkTII6aS@metF0P2};7z{Hv@iC= zZXSx#VR)gALbT9)db6b_>xiI|_YV33YQs#xa<)#Lg5?be9g_FPD_d6pZPm_@cvcnu zkx1J$Bco(+E6rP5KhHltcjJj0mnn3!87V87qJxMTMJQvK?O#gn)>k0*{OG)f%n+Po(0b>DYzFA_VaU)F?Upn`bls;Q@D zyTf;cpLa5gxUTzWshC>ePD52$Fr=TV$h?x`Buw__h87sm7_E+?MoEQx`fr)}P1d$^Fh+-{DH!v~YY>am&F>Q& zbBZiv>W48SxCFc>O3IkNNDb-+&v9DBj3bszhgRDlS}46{LJhH!7p+gQD;Fgx3zHCJ zWn{!ZE@0O4_seGs>-p79AbKiSG6!1M(eX?E9?k)jlI16|F4w3mFhGp%@Ygj9eBxDz zm=N{%zkiWn%TVN>vgpZTtY@P>!(QB;FcnVONIW}qrMOxjFu(R7_gK=D5c%khXhd5c zM)V2(YHzl>&vbX?_zTD)i-Ui%hpBN9FbLJM8ZrS3O3C*EogJs=_7ERwWAEtI`m%%( zN$-SK!SBl_Uvah4eCBO~POeLyF?v7p+zD~L7K(L&G5Fd$ z`I1V$3(riPR&o0lwXxj!5uDyGkjtDtjn@chEEs~VCV zFh_3#Zs!;yih}fNOP!tFswHyIp>N!=c2sE`Z_*m^JVO`O+?w8SSqG%Kzhb$>K9907=LavI zd8&`jhK8?$`3)~DpiQyt#g;uKpeZUp^ViMN%U{nW{4CtliHLG8{K_+bH zSNGlHPD%#-E_(N7L%{v{I(cdoXI9Sb;FMO{@{ZG{!xLB^!|7i)WZ!>J2JHWgeidt_ zvajKxaW4494?Zsz2jni1kMy|A`P{zWY~V5q0`Ao5_E;%QY+`mkrf+(Y{>T5`yNq}k zWxBSWNn{(~C1b<3__pZPOUVxugfyXoTHONupYVMDc14Sd6;rTBHcDTNPlns&z;E6vk#>T6e zf5vaCG5n4fQIJ4Xf7dv-N!>l?zZLlbJ!F z;cVD|kNtM7;iH5owva*#PdMyCC6*}an(vL}cvDw}A}v5Oh_iJ9Qj+$yS-<&H&qMFr z`dq+*blpS%6nMwTMNyD~o)=duoqxRdr0J?I36I{hKOUX+JO`~4Jle<4rb9W*zXP;k zB@B|H>Nl98wxL{hI1lIx?IG8>UMY4>m2~aHp>Rs=<_`|jwB>u1-HqEpmn_Bi;9l$B zl0W7D+*IhfyebIW;;Q!gQB=vS`0wd`+ha$p{a`!obU=Dj1U6p20pyBN`{$UYMq@QG zxH>!X`+KD)q7rTcRA*XXk*Vjnld1;>h|E6eCDP_oY(XQ0!K?`-;@uA?&YlJzZccH4 zJuLc9`e@uek^IjegLmg`zry4y)3%^@U73f%tOJxLCv(jyJh{YHS?;@H|18EcOV5f( z8mm;t6PxMZ3ze97d)ZGgrj_5$DJfgvd*zr(=gp@=jGW?R}G z(Ro5&{S_0Jn9}1kiaqs@j&?EwF#jLc-psLM*g(?ad$2Q_?+{GzxlNvq!3YvQ;yV>( zC-BO9%C_}5z8x_)%;R^C$7@T%QE#LzuMQKJ ze_uA0@dDEISmqo&*FWJ$Hx#GN&(atALlum=+b+|+M@2;Dex1z;Elqi^#9IwTKnjN1 zk2-}Pj^u4Ng!e(f?hr_ABuC#_g+z6rt$^Y)3Y&SfpeYWeS8VEbI_kGmm^udGcy8Kr z_jN)GOQQ>;U_ZCnL{g1HEa5u#z`x2|2-UaA-Yr$XUF&qLK~`qC5CNfA(FghQrQ^`p z@_c46jAZ$ZcrfPn?w_GZPk&^+K$W zhJPS#i`zkqOpU4#jem0OsGVHX}^#GC^7M12(b70VL zpPwp5=8;ZBC5j2D=y_s8#Cqc(T%XqBVVP}``d8AwbK*crIA5g*zF|IWR`lnM%$l$= zs{uA-lZ}6(vdVl4>{X&j)wFf($H*IMtMS=3Z}Gsie7^y9tf9deB}})Q{M^1cbTg6O zXg01F#ykR(X=uDrLlEyPu}n9_G8m%l-6MWfzFqPA=bH2LEUWd1LXq$y&OI~R$voKN zl1_FlwSl-msLI=}PVNh_^tVdS3clh{v+%wzA8q2n?7KiqaMoPPy$N=A@&7SvJnO}e zPJZflKkgYoXo(rK&~Vb``ennhEz8JZOqi(EO3#2ox95+=PhHlYZJy%_^mhF(WWqmdJXYxSjgXK`1>I5|7@2(XDrN{nEIw zx5|1}zIzw`()dCal=l>Lej6gEQLw3V62uzLV9PVscI`bJXb4N%X`HRLP}b}qvEnb5 zYXEUYNuE%CgQFzk-^7wx<7Gx)EJ_bjnLNGT*h-DS3?a?{HKReiRJexV03DGpL?si{ zSG)}R1sOOhfoBinvN&7kRSCuGPY+EBf`kYBZ!^wXL*{&7BBBPd{^e3x;8_{s|%fElpX%WnBw9!p^fY084gRX1D z_><(;RZ;iX7Tp>>3sxI%C>-X54e6x0I?jKD0`xx7{h5=(4OD-#_7{=_d;R_IaUkHr zs-|w4wVNyVtLr zaO}7Sn}G4`KH_|bG!~y0bk8wrZmRN_5h33U!&!nrm2|^K{PhURK~s#dz`P;7Jke34u877GbFrx zw`p0WNPlb9_4rMPxY+H3Ujum-=e83wfH{GM0nEH$p^IqnA0S9e!w`jT0(}gp-lNS1 zCvVSA=YEq~>c~ecTy5;kByv@=o(wTCxf*bq@N|Y5l^KAU z)wcIrKmZe)!nw8y5jrXiL^n5(+J)xdffkADcj?;Tucl4h>k6`Z@%HSs*O)2RM7qP~ zPzQB_OJ{EV{l~gd{xi1fH>W0t0;{+{9`D|3{bSLW=TF#h&7zW|U5TP?=pS+yBq~0u~w% za&kSuieH^hQRz9DBFr&s@^}5i%OR|ffB4FhE<}GV<`UOZ{O{9t0Sk*>~HTgaQgA^mXoLCJSZ2KH`pKyolV8hkCRYzc@ zK3$E=WhdW0=Qe0ydV-)N`FuFFt!s6UuL}LJzW3O!HX1KXWf>d~n-Jw$EIzS*R8086 zQ|q)jdV(8#C(sUjB_3C@gS)$~;M6#wXSG2V&hZHo8hbA?l2CnQ?+UT-+bBY)Z>)~K~VH>vywOqPunUjsZFrzNyI*vuwwj?=o57?<24 zjush{*?WHfU@zx-zC$X>4o7bUH;R|%M(#o=u(i{o$6@z|1B)o`9h0G4V(QO#&NseC zvcrHTn!!Mqa`Q5fW~t&C=kp>6G*}dIpBL3M3o*$f@hNM9h7`6|x8P+eryl5d;P89} z&2*w;*cnFCZP7_Jz8OyXlhgNfCqL32u&tX_jZv@ba3)Lgv=|bnU**doBzydR zk)r$D{;T@Q@Jcj7xnWO@99_koyT0l$m|v3Gk_PtKm;(}VrLnuR)6l? zE0o=By3EisI<-h-iDsLR795mxJ%SEYO zN1xq_Mr>nOUXTBA)K+OQjQ{GZ4>N?HO*pJi%G$lsGI__Kl|JfM z%ammxnGP#%r}dt#yNP*^ZMOKo`M%)NOjXAW1YuU>U6Afn^B>hI4s8aY4LoBheL6{E zpT&w)_Q}TRUiT$wSi*rUucF*cc@KCrxLzR~vuIK0m`=iJ@sldVrnaV)DCo(?0-S(mNvxHb zkF5`4h~pKVMqV7;rj}>bmod}-NCUm-Gp^YJIX!*d+^76-e55n!!y5v9jR~3`zGWB3R3uhmB8VI-59ThQ$bBDEOLv%Y$Zm-x7iL z@^pQG16f`a zK+vR9a=Id&`t~4;K;?snp*7MJ%`R0kSJ~bakDj(k#m5QV`BkY<6MqGh-WT0#N!k-l z*%4mJEg4muKSU@>Rw8Bj=nEE-lpV8C{5>$lmzzOY@*g*0yI4Nr=pY6*_y~-;f5#AG zowKg<=1L=r9swki|$Qz7~t6>w{M@m$?45%~E2NfL zn2fx*8;cBNj}7%x3jQ~#sP;qgp(yQt^K}aEpBaJC8L`2boE>Ic~ITX-^ruNg^OHPn2~s!K!G*F7V~Io!Fkqv?P8Pn;GhkOLE>s$dVGo}I)-k+vyr)w}_7G>h z=SAoIZj9wLg-ibfBozpL5hOZB9YSs7FSDJg-kT&N&D#8wc*F+^bcRq2KaY8Rm~mX; zanNTt^m4(<&#SR%S!%z!kn%xwmsp9o!l1!U4bOQe!L$d<9M}l5gl$ewX3+uOQrNt z*rf10y`Hag94n_8m2RiR4#`8Mylwn1CyiW^Nm})i%ggJB9a5R$LleQL-(<3h*b+wS5@|_-&;JRIiZB_OFYqDyNVD1I$6A({#M&!rw*%N$O#Joy>>TSq` zOY>s6)%d%58sjJ-?ZQ>T3?iYW-mU**hAJpA7+pAM8xJHC|AOS<< z6iJ^Lhsbb|!~=QWxT8O0;CSVnQ|9}*O<&|?sfuhMY9-cl@go?l*Vw?wO?+nV?4n$7 zv0^P%6H7L;t2{A*`84kuk^Ua|XhX3S-~#CkH1=Ln{@VAnKdWI`VQ{p=5f1H4YyMOd zhu3X_u$&jr-aq)^C<9QC%!^qQ#aWGz94Vwmq>9{t?P@B(tk){lv6B(3p(Alw_ut5G zVYb@<4Yquw$tvgqW~H)+^DmfRA}r#MZT4R_Y?;hr?a@zC%L`&3|JM%_Yp-#A2;y&p z0)soU$+)TZDnyqR`(UmL2@MOmbYk!PNS|91(LdS_j06k4nLB{GWL3_DYQqU$mi;um zaDC^OWysUV1GS~&4X}cv^pp%@A%z+QjiaaNl!Nka+r?sf`(1NiY=v{`xHmy4ytFS-^MOdTfOAj2p*)-X5=qTa^=J;b=;-v_L=JZ@T!=Fk2zM@-Al|7xS836yh6 zEa8JqrPj6-O0QjyO?P;X{x{`KQ#Jw9VO*kVloo4qP4|%)hAI3aH4&9%8|`+NO|JbZ zERyigfET^35>apdap)sN&e^07+tM&d$nurVKiOAG|;LzMy{d;la)3_J2&q1m#xu^Z8ZUcbC0Xb)W}_+j|eL(IiAlcsd%&ur87 z7 zC07f#`}|p>+&&6E;9i{=cquk}`XZX_Uvg7>xKHo_L;`*+D*LO9>*cW4(C=X2UlaS0 z=UCE4t!12(OUvg$l}!Egl!oJJ`wpi5fM$^lQ&4}T)E*6wLV|G@B!F-v<#-)4SJ6~b zR^Lmlfc!pxxe+Sq$E{bxqlxs2LzA8B_?aCJn2PHrT~UqmXqgvts-)ahK@By29?p6W za^D*m5FFUp_K5t>O2&gy>&mMAzHhc9AA`YGyr z6!b-fF_QUTnYL!Jx243jG0cy)@9HkX9I@MPtZ(knSka{fNqY&9wk(9>DF=#PFyx2m z$scpkP^N09(q!K=jct;mlvAX*0y)us?s?#ivtkuYP`j}eCjV8R+xf;0f#LERctE9= zw)lgVzJ%UV;cc*>*tOU(ey^b&r<*iWuP&=uPTc&`+C@e6b8%WASnKK zZ9}XHWzRD9XG@xS%s>X)Ip^?^=mVWtEXEOhy5w`c5ofS7l})NJI;ZrBBZG`r&ifQ} zYjJ}Sdh|m9{iTirOG(Mk%L3eP?uUKcS1{DE!*JtfH zCX$G>wX`lx#-kp3r{kku(cq%DU`y-qqc9Iv{rkTBX06-OY=OKe)1#L`NA_Y=Un0;2 z`K*|dre~77Hw)aG3TYsUT18COF!C8>JrYXiJq-JQaN)hovYW=z6L9%o*hvp89#Kh2 zSU)H<+_5qTpaTPwrE#(x&{$KW?d$)%Dp|iGf4n9UlS38u*!M+XoxBac27KJC_h<5o zv%SAtPmNI?k)3b2Sy{GhL_g*ZCyHf%RyyJ+4}YojD>+4c1~O#K5r#3oJaF z{=A@~|8Zw3RKx|dQSaydiXem#f+Rvsc6f!?*5a=WK?GCxr_J{8{m;$LxdoAYeu#r2 z3t3T}tl7abxIvk@R(^?`@MWXQzT2dyX!D{MRgb6RL#3jO9bvK(tP{19(p0T*#`gO_ zo0`*e%sSJm^909OC+Ud62rO%{%8T}$;iSN~)@OW`CFbFZwIJ#qNbI%(MQWQ=c?&{u zx1|?JZPe^OtgJs$$KS}io$#}q=H=92(H==Z-r5`XIf%VY^1#I?oz8?@zgS#|x%T9b z{jn)FyN?9V0GvB(h*10nTlo{jHqPA2JndYw-sacX()a#@Dc@WJa|ZLV*ycf17)E+$ zHYHI@N93JWd#x8l2*oG^WNH00v$)(Y-O%)f)#5jgy2ArP#AlfWFa%>ZR)=V(5 zKOSiO_PhN4%BnN4TSh8Ug5)m0;>0+sW+-vRNtZ|WEvv=OQ1n)=Mut^4RkaE^p=ZD% zrOtmYBr+hmZ$Wa!g6wf^K2gdY_qF)VrtWh?h6iWqb7Ur_X5QlMES$ zx)_|=e1GBPFmB7S3ki8|!&>2``r!@YDKT#ta!d|`cp4JW$eFb$s!X2)Z+@ZbF(G&| zM6NDhd%@C6Ev_46ix3}T<$}YxJtp36cG&(CV3j#VN2jm?jNSzoo#hDCyC(^t7-!;? z^r?E)=MQ-(&*ZcyU!fTcjb5XfXC!^Y{BBmNPR6ulIAm=*kVVL>(trUlgb$gGI7>(*m-oqE?~o$JkD-){8M#+875 z%UrpqQjK3;%bm z)%y1%Q@_sUQNRV%rN>49m$*C#-tCN))D5~ef;Z72kov4jfMle$9`au1#|@^ZWPLpe zHgzYbYc$Jq7f?fFt@{^|>$LZ;PVvy=_oVPd+g3GLj#`Lt zHmM1-!*UF24=mjs z%}t>AME;HJd|q6h?!~Qghz@1&se^1diR?HDDy&_* z1>u0Ch_9+Dl*s~ivbm!|7ACBhxhx(1UambeRnTVA0ccOi2xmt2VM%IP7^Xm3?7HV4 zUUDRGktLdE{GIuN2Jp&O4&CnjApRkT4sAZkS+G6Hhp0_qeA}qg0Ld)Blb(pk4w~Ml z#tmc|`(A&+oJK1SI60TXpjcs6JMD5ShdCwgDuupkj>R7F*Pk#zC)B*)X}P1IF##_ckoldbyVkq z$bqIs-IE8W#v=Tl|0AJ4B+&WcUbS>Tl}K&hjXXKMp53*sqOA^%dMIP};;W}$>Q2u& zH8OB(jrhUY$Bwmh!xwfb+O~9T@|+x~Wj`v2!1(38x}e3qq#hJU!*VvY2Flg}3D{MV zLMxrz$#SBJVG=pEWJLdnV zQ1Mzi`G)&T7-el$)urk~+=%B~Xk?F)#T`)#ildjO*>8HdCf|C7bcS;Ld;W=vP#0Lg zXo#^qdfo`3PdBhCU+n36wJ4`vHxh_$^I8}Xrd&R&{VkJ z*@#{?EAM+D66*$bcg)=M6_>%je)+vtM=>%1yB!cIXzlavhiV_p{;`}}jcH$v2>1?> z4u@ew!lGK+jO~GXd7D{>j!ymuER9BHOK$TPI$QnGl%*ZpXL=;ZMCl-L>G%!i(ad_Y zGg)ZTmeLeeq!PpC;%);uft?=3`X9#o!qWGt(+y^)E@{!ttYg^d(xeWz1-Zj8sDRG@ zn}}}znYOlgbzOHS4rG+TlDyRq2i%=Ej&O8W4$O-zw9-;&)~NNbv+d7j^e@Ds3-QPc+iT`3-ex3g9zv$hl;W6V9dO5H93pT^mMl}c$|C2>C7?p8D zw;n}k%y7l>*A38n>z^H58KRhNp~pBv5!S~iZA#;hYf64`+d}I05sj^$b`hXs~Uds2BfxAuC9HCOJ&a3ENdT6Vy>i5hE->0(P zR2ao@2mWlLg|wYolcJ&A6M>(PY#nw>|K%NaXzEEoj;j|AuUhn6v7tc^+>;_ZgD!n!S;)vB|eHtGw5m4D$W@=FB~aT$;|L3 zqBg3RKGQI01xZr5vR%YRO6RfJR{K>H3%8ul(EN&uz^c3r#_`%W;)&0#)dJDK3riU! zJ5_W79NO5_k);D#(T=iFujb%KDUIBlo{J|!xd{ps^qBKL=p4bQ8rTJpfpeEf7rbTZ z5W>mw-~Dl~F4#uKWVCbH#pXs#B9)UK<2Ai;YMZ{I&!jE`&~ZU#0S$w4B4<8LB<{sN zd}_=3g7gwNFO~m-?==UuW-zc|be7|5u-ha7^qX2L*lBFdNs1IZgWCO;bHS~|?7e6o zkJV}bfCJDThSn67(u=T*((N2{QfHIx^xfI(!g?*gGcN4jQ=chs+1fK|^G;jEE%4GR-4`Cvi_FA?X| zvcZ;jnqLDkb#CZ;M*`465EnAG`$?2Jf~KaWSsIiz9q*ZGPAEpE-%xE(O?9S!pd&!? zhp%i2lnXmN38R~$k+cXE?;V%7{PFU?pNh*3vEi|l(6(e55l+deK}xv7b&i)?UgXie zS*DXzT?AW&=$kB`dL5r)oOd_~eJs8^= zeL9Nc&L#D66x%KoFpWtvLHtVCCS?g<33_1IDeLdnX6WZ_0Q~87dpJo&&gLv&wo&@g z@v#+k{sAlaEEm#t9bl3C1Nwp2UC7w#Gvr6IK&>dP&B<@-M_kn&r@M~X^6=j}S1?8^ z2_=ham&?B}XUNzvCE|?J3P}c3ENbHva;Z(TV?S%&*BY>XK$EvO8=pkXk=$(GAi@Oa zOu7GIwi&H~r~hF?Tk6AYMDpSBe{H^P3##*k9UXB+_42Kh`xk)oFw9lzkrTk+TCu6h zTqY~2E%|;-m1^ZFx77VWtKpdU^yuR&bLDTIoCr>xpQ!-CzKHL2h!fFDM-)jn;vE@3 zbIWu%G+B%Px#PKl=%gU{8gQJunWpOH=9Ik+htLFQcaoSK8Bn5`=ui?* zIzik|T^SBgQr2Q~CjXa^&)?$@`r~-lA_1|Z*RV#2(`y7J;0w`o45nP=ji8|0ul6D= zchnW>T;thM94p`RhG5fP(f!`dpM4;i4UKe<@HZqjwWQpu<%P{pY{yed1ct_^5Hjv< zv{n+ci#Cige4h(z+~0y*y@*YGW8Z_bVR5@`&IUCWkIYcjh$|w7JZE6mPFuI%KQq(? z#!(a670x#fmFz$rg5}>XfN#1H)`$U)3V{jfT}^{Dna&Q<7Qt?(C3z44JXByvLdapx zl>D;N4SV~Zz*y3PI74p#WTs#x5ql(K5WD3J{9gTQNE2r$=KVh=+>vmos481;HSeQ< z+*uNx0Z$1_6%rK5r?&@6;|8P{2ZyH9^et!qKOL%K?2TAQ3Tkt7~7DKpIBU?n`vpVo#ttX5)N=^v}ZRR!@egNzLEXM`h8W* z-7w%G;dE13C|jz<8$=`2O#GB|hb$XNim|B`#DuCo?U;YmysbbLqf4@??d zka2FZh1OsegeC(iEz@-VGfz4oNIait3B4qFo7;6>{D*;o7O5RYZr|Xc4~YtSI@Qv8 zLHXK3gAZN1M~eh}B4n1t5l1>dKsea+54=p2SXm_p%r)l7`-r6QFN~W$oXp5bcvt)V zX}pHjbD_KMyIiuRYUa%^>XYl=ble>Y;}BE|17__lZq1{$ks)~$$laRs-LDxUL~$}g z29dkED=M-kwx%>AfdV0YT9J8m$- zY)y^=mPen(S(TCA-!);UX?TBoxkYQ6%!J)#-x{@qvV<)kQB9vs zf{bBdTeIeV^b+iC7PHC^j$gmDO^6EwPLtNXj&durUG$%NE2~xYAZ-a_8qQ7`NFbZ8 zkw55QdzIE#wQ=J&8NJgDE4&}?o}93H&>dN$^eacPKYvN(ZO)B^s3ATf;pp?OQ9^R> zzPL^?;TSYlK^$BSWQ^oYv;#jQLu};k*~zd+U3awT#B~$lA{%~p(Ruu8e>j6=+^$fc z+hXKpddo&D-TzZM0fI_)Plb9T8kXI66{bLD9X!f8cVl9gKV&c4%9fvhE72PesMxD*TMG~5r|eC-7sP#cCtyW_xV*ujaz_HTpk=SYc}-{_(88%EQkaVXR15Y8ypY%K&U7LTunqRT9w_Aj^K(OEF8g zL+{s?^9KaWexE#l#+~wd^p884_kzeOTnhSOFDmsl?=gwu)J_T{DNNoug5zflWU5YN z=!gjsXHXqpS}K%tJ?_RS<&u!J^Q;v~tH|g)Jym~}{%B;V!|YdWa+CE6x*A+=s4ufe zeqr4Mt1kQ)GT2~J$h@?(LIcyyw3Lp@&7j#)>vyiSBA_vd0ixQ(8FIJhOov4gw%KQo~sxRnm{*>Gfr^{W%kSZL?| zDnE+a=S!SNKA|mG!2TC)47l}S{>Y%}T^xRdtzYGL8RBZwZ-+m!TGx zKgtR7dpYFjghNti%TGPIz^ES2q%pvP+xTIW#TjO?H#q>$9Xe%FL}>(1IdpHf?)9so~{wBzHBL37OJ+@Mmj`zi8y@jyXTP_Bip7oid9S1IPbMQ;N z&1>!!n00yIjf7izKjIhFgVSqKW^rR%-|`Wo#TO2*%gGS^3BP)5iB%qvpMItgV|b`C zcmOVsv4AZDBXmUK@Shzy6G3KQanP{GsscFQlkJApLHmyEnc@5Cp!w4EyjWh7#=99( zoMK&GeUR^=UQIzh(OaEtF~DQ(pkms)R$H&B@-BN8!M!e`QcPeD=xSBX7_f1F7lJH+gGKbf_AaugPmw^o*Y#GjqTb?A8?7J*_A4emJI`qBboZ2`@1=36h%L637ZAvYanZ-Omv z#dUBm-F)QCuAsXsxFU7$R|-hkQJ!$Y5f(1*!m(4uv24D&bJ8xn%4Fser|GeI7&02L z?>6cgArPA|QEzA?(X^A`su0KNV_1E@^WylWbl%zorR;OMH{gfv3Iv{+)g5}-$38Wo z&y>Yjlr5)Mf+tQFEo_iq;%lJDtH7m@nD0Td0PkZakEzrG8jU^q{va0Onko7LSjfQ6 z7eHiu5Q^HDosdc6oL3Kl-u%+}S}+PD)Oh|6OF^{06HUe+wmp}G94D45F;<6N%(a&t zluJVS!PLB|Ccz83If&mJ8=yF?8Gqoed2=}Ns00Ev{HwMDD@GxhJ(o&F+Hs;bDK*pz zIg4~z(|;w%c$QIO9W}3!z$yLW$QR%h7nF)MvBAAPUQ=*fsW> zXwHV-%+nlM&dfPuxn+pu032hCNlZ@bn675WQlFbI7c2*!o5%x^NP>B+%$I|4Vjk-v z%R%6{B+Kng1+Q@T-;kY}` z)m##R^hTx=%i&C9uenqp9bUW-mb;3UB=ug-qYvze&SVnn6xGXK`22Ok7y3T zR^893+)Kl~++~FfRI2MjErdhTWwn{qScc(b2rq@Gt>-fSa3$6H`hZn!IU)#dZL{?= z?y&uq1eYFbJnc9Wo;fU+R^e4B`8fC+{AC6ouqnxK0ls6=snzj@8#etJc%Up{amZJp z!Kxa|y#@REi@E)N7n`AnAi8aDiQY2iwjO=>WOEBEytJ9fWtca>olMB3v*GzWsT9=4~#t^C-^=B4?%IXO#t-@7R#ZrNwMYZ zFLG{syx=%$s#eb$!tdz3Rf&|zd7*B9-%Cah7qsv&r2 zCtZM9;;?%d-ElzEQ3ga?^4d7H`;QP*!GCzY7b!?ggR^D=;TH* z;JDnt%&;3iUfL?CQ@Ym3X{7;I;}ATp*sG#9VhZp`wRYF+^7?0ZsrDg`uU^Nxoy{bf z-r|mikXjmM+7+FVnAd|*5$Lr-81{3z6B}PFwT>Sz3(-{ZQU!i=8jNM%$EKX?{Iy}cE^k@S4fU5CsIR4hWd@7yX2(H{+s@r%Ina=a znR7UlOCpfoAP{(1g&@a?-}0CvI#xprC-2{w`zk2b3TrL|G-j35%sJ-GU2%UouVZ~} z7OVzyQ>?V(7P%|&nZwJ-VJ+BfyyDBD zE+xrw7J>NO4=?+|Hl8a!>o7;S;m6hcOqkOlAU8M`D2;vRumNgJCAt3D`s^a%4B_KzDhT1AbAK;}^1=zFwT=u#Dx}zHjJ*3VXT!eDq=WW|oU* z7xHYDtMFK>U{`a2QIB+-n^z@(O_{__gG1I3Oy))PLWpZraHy>QTfemI(V;x z@B=o;j@NP(;JNE%xtTiFz}FIB-I^f>f)M+eb%Y`7u9W^byN_aOp z=~@u>@hn@eOm7uLYl=r)IH|-Z6iN$G<`(+39Cc$ks?v@JGD&(z^kliIm+Hp@iv{Hm zgXn|)-AB`{-I$}+*1JHh=D3pOC@RspSvLwlgz8X!@H$osQd${*6S54X)&xVY(C4{M zEC*T2y?8H2gYHwwnFDCnk>3igYf_;wH}?TvYY?1OveH|Pyt#N{y`c7?i3N@m&-sBT zmopj!-(vYk;rTRvOY-WPBk7HgH*DeqaURPFqP6-;;ft(z7WOHSNfO2SyNN6Zn#`x?E^z zq8U%DQ|{uD+dNB{L}tH{E49G+mfZNpP6nP&;4f`~&wv*^2>4oy!630bq0R=j8uo8- zSigYf_C1;89{WDK%PGTEJxR(u)`hXu>(+;J zsYe68NOhn%%PPSUk{yJVSRz`3jAc`A>L5d`5E{N;@kybGUfeo6ms%`$O-9{}EeEud z9;;GGntWmH1M9n{H&EN4n4d6{4tg==N$heYagm=7M*nQ%E{#B zNS530(MFTD+(Y=R$O#KeZgejvTW%3-#?;>~&T@V?SHlp+b1n81517P|clHUWchUSWS^$EIA{)R4kUt75EqE!SlHC(POh&G1@TPm&USuvcZsL8Dc7dF*u5!PGu|Us{33 zAZW`G(lP)6mcvp$38A)gUGyhIt}5WikxQtsA_!_^I0B@m+&OnyHLz7_bM#(`YmPot zTTbnQJ(WarZCJ1W3?Ycp{G(P1zLDl?IF%5|NvHPwR+n}8pHL^w!H@fKBHm`p?IA>N z{9xGoi{$a?Bu~~UxFMqtcQWNlCW&mhVzoJ}!0WxE4{<2k3L&T*B2Fcv#YwSM+Gh{PIO*}-ne#aZk*<;XTFz@9 zVnysBd5&`*LM*dfd8?=-R0ex>{fy!JwLGH2Sgryvz`2776WW~8! zf6gtMWB8#hRqhWs4np-1g0ozljIqL=b2!JrlJmh{*D_mwvGxH-kM%2aNi2^<(FX-s z&qpwr6+JweE?zs4KZHsD)lppz#c_c27HxD#d1kf!Rfzy;+@=n50-mu zrW~!g>3*UqhfBFxmNSoak>D!c)nf&{**FMmHa8qfP0A}Nb9`b6cYUd4nZhZ3oxPG5)(qAv`hBSd13tq8fkz+T5Lv z+itTXIvb*k<2?FscTZdHBz$>a(3rz1mUB7Bak1JquJa^e$GTGQ>oc=H0VfiPH@<(5G?k>5>_j9 z*2Knv2jEGL<6;P6HqxpBh)-lTDAEys;F;*k^;dYgEr*=o&-7R0q`#`s55V)OW2dnO z9Y=61bUvrLRNT5ua^Ce90$AiXsnSm5I~jL1|94MVx*P!Q!Yf)=Dui&a zRXj%-Vlc?Cw<$@6YqEkKYZ`RyAWU3i=cRGJ94$FwvMFA%n?PQ$IIH6Lx-YRDAAkx~ zQJY3Z=*y9@Rx{O%+DskbZ3L%3?#;0Fa(*ut8h_#Rw!!pQr$%!@Z+Ie+L?vDg#&S0c zEVpaVQ8*!d`nF%(`2*mvDhNbf$JcXCa0*0tRV!GIMXicK;Lw=XTns(Pd0l0GFyF;; z5C->HQ}RVnMJo?SkXl<1A!*S;TpP=c=RTw<=X0mCN*@PD6;97!xrXO+Pza4AH;%*N zp$TWr=#IOWt9|60oLCZytSMwf@Bc z(kjSrPI78JjB|wjn_2E?(h}b-5Zmc2mtPfisAH>7C~n>N9pv^jrN`P9+_IS8#Z;1c zTh5~oU+jST(}E8>39uZcAX^Sguu-jXY-;B@v7E-Wa_WtNO8Bh{9;bV9Ml4aSv$)qGq}lXs@4~5QlawYZjnP00?6tvvYawWfD2IC63Z!_ z1d8&5AtmBihAOvoA?Nue`R!EG>|CN4I|88#Dc5X%YF^J{IvWG_RPHL~TNp0kP-P0=G=u1~kg z|6;ejS#YUi-2+Bob~RVsREU(zIIE*DmN~A52U>EmL(UYnLO3je&XFMqtM&?^FzaXP z#&W9jqCtlTJSVPmSSAUijJaCtg=Gj*17OvKu6&ZKn^fqwoMw2jE4okdvNoEke2~Zb zG-NU=M18l$BY+r>8s7W{4wB!wjs(l`^C>ujklY$v&Xs#3%hlogsh07D;u_t{iS+oj zfNhtu^kF~s7;EIpHG!<)DDc|KVRetS8Gqv80e(u1)h)T$dSZD}35X7A^;$g!v7rW8 zaK|Br%qNlas?<4+sE!xj%i&BNv}R$5Ie3IsZ-T5jtl1KXOeX=yiRG@UT7NvLFgT8x zBu(gI?gf6UJl0GKK01};v>%J*sFKl2(-N#VK}K4ENDR-cij(iu#yF$)RY8Z+c}YJ~ zW^k$$3yp%a^kEn40TGCX#&Jn=NF|xK<-l@`c`Twfn>!7=V=2z;I6ec96~dEAV&|$* zXHAc_Zq{kcwVhzpRl^HYQNy{Wc3%}zL%wV(^-QA@o=`?bt+Du617V0X;%;u(HTX(u zAEbZdS?}f2W!=)4)11N(9O9*>=*N)USw3z3@Fcsh4u3t`j5A_7)t1wgoJBlInpR-8 znyVUgQ&?^ge%LQ}T_Ve&_5nku+tV{inkk1Ut86*-BsqcQ-nW)j*wwC@JZ2-UC2J1k z2EMCi959~O8j#nT2e67|xh3E^D~ZriGUw{({b97V3~Ox*+$Kwo>A|7Nkozaqu_E}; zgrX0AFQ-|BlXua4dIMpJnA~K;CTPD&rP&q=w|%<|%Pre-eDogTLXn8aI*S@0wTbZf zEm}&90LEFNc$bpEfaa`6x&h2)RS;MqtVOzp->`K5A5!~}gEle( z0e`hERX%hp&dOLv6t~2Dx^ycvr=ODBr~=gKvf_~b{>Mgb$#c{UWY(J(173;?Y zqI)yT?e-AUVM~&jI?a5!O?F-~TP`rP<@P_nj@7xXbzLdN<(gWR;$v3=Xq|;MYq)(YkD0-6J$JG%{7swUh0%SHs(Tv z#IB$`EUSzpg*gs;q#pna@^thj<{nG93EBhUF@YF6)GXSM!(CdpUGb!RyB3uZfX*0EZR*L#&fG*C@xigN7u z@iKS=DfNk8<+Hz>0JU5iUD1*A_!AzMOftqE$#Q$vJ>bc-;LHk+YdNHL&DOC#RF>QS zTE?8F-PD}9;=5|*O}$|RZaKS+OWSHApo zmaCXepR1{J*w6%}&G|juW~1D>YR{43EX-;pt=vi{$y3NyOd5Q%nsr*SaSUBoaGadi zYRIL;aplu(WXqB1yub@2w+-=ct#i4g1~@eVU#yUVl>bp*>;BVeGVftO>B(q<=XeRNxn5xgA8c z>IS)U#d9Ui#V-9du`1xRlH~}clt}bbDZ*F`K?Epkob|7oQ-ZUQoQygv;8!LIj_bIW zQ%5t;~W{wwsKV9Sx>uwvT}gfj;miqOVs z!(A7<0m}|+NKl-H+f1Oj%p^JRsc`f2=Lcny8E!*k?mh42#&{|tUrBE`-rbfn_meH`N}9=~Y&lC1E}!$XHJE@n&vUzQ zSyAA{HkuPzuBXe*-pe%$@Tqo)!ubo?b4hMxv{x)Qm zYjul`?kf3>i!s&}C=TEO;P9NeJfVh;1N2t>RX*;urnA~MnC{xMf>o{mH3O|t-lG_+ zY&msAza-1KA1j3f<{!na*@Y?D?)vZLYDe_8h%$8=B5^LcnXbboJ;bhc z`ht6`=VO0-mnBdfd2XRS`q?b!F^BRqENIJ~D}Q%4f#sMK95Ghfa(Q!J^5q)O-Az+T ze&PA61X(MR*OKlk#p#g4N@FgCj5-!TV9ky(46qcUR6t-si0l-0P-{))lURmuHRzy} zBw?9!mQ`Z-B>ZDoju&%VwF*|c9H~yZa%aR?^`iY`RbK9nQsrZW=R9~m$XCZ#;MexU zmxMf4S8H2MXx4g3s)N7&zydEUWCdr4HAX70*ye+^3~5g;AT1{0*uXi#|X=9{ZsQj8_DeuV-1dDg;%tNOp^mB(TDbr*1W#Zk0LoIy7!07xR74J z|6oL?OF07_*4V&CnZXdk9gjwI$|k8a=MF2QtT~Muiz=9oLJ-r*rW=jk_jv2Tq`S(w z54FOJE^9m8Wz~2a=8-7I`W{$jO?4v?{ppS3{h|*#yoI`dg>p7XVj~P$P82wBS;5wi z-H?mtAf+EuAGUK@!E1{?6i@o?_)nz>=jjP06-I8JA6#Kht0Cx(LkvN3HsFhHtKG`E z=Sr;7{KyAuz84b?t{QA5&p{Yiv42Ii58sw*ACwmSRXyg2ms~`W9K_|BO=^WwgngJ- z98yn&H~TdeFa=wkhpRh#QI>EAra~8Ul-4F4 zQjImspw?^YqPf&xuE(yoS>g3rH*@B)g61YRCAk!yxb)`mt|bYQ-2W%@{%^RIkqm^x$pY0k*@q5RWhE({fqj69$*jDkjwSV67(wiK#c2KS!#FZ6c$|IKY@TF@;aMrdc&yfo)||5(eAXT9Id)1H!MQQlHZ&4lz{c+Ls4-S$3g=#OSjBNm#vE8~ zgIMepoTqA%{$|U(x^5qEqDSkJWbB>c@#@ve;bJSu9)+Rj_ zuY%BIYKLkdnoPyqRUg1_bN7)wCp*r}xo*9ehC&pgqvuN4nASh7^Ae&DUg3p%IZi8N z>|um8cV_rua`&`+bYAn{Fp|Rwjho-E6MY~o`ao3_an65gVOvDOW900%1i_68 zv9|q-FuAR#SBprT4L9H%YqM%5>{}10c7E}@I|Dx~_9U5J&=j5K9P+>Be(3whfU%rB z)`dUp`7HNIb*-YhWV&H-fT1R|7M4qTqZD*8hwaE&tzbDm#9e8YZY)QF!vRG#5ryBR z^;nMHDg^Dht+CwE-xi|}?TKYQm(Uaa=NRi~`Es8N4c`x8LvpM#0^%w##)BwLmP+82 z`qhoLk|0byR;sBiLpj9rG}dt%UuTvZ$9=>e=(1{3Aw5>B)}O?&z3>BHKAc3C;&Vo} z4uhr532Llaxel93o$e);bElP+tpQqeUeY=b_Tn?wJ?M@ON=72O4y!~4Au5vKD7M6M zM`>97=tBb=FTt8?&v741G$)R`R!sT?nNOj!gLg%{+ z=2r`q<)p_-%Bg9=UML}A8XM2CP=7AfPnz35h0|*DSpWVPS+1KeCn8qIr#`FopqZ^W zAg_jg<{Y-h?T6X`-G>-rC(#KzyPr#0XBdL*5;g}S&(zhPgq9pAuN@3Othd!<^4v1d zk>obzu^wIRX9e?O4kYIuE20nJI90Q1Rv~1?fnp64Pl^u2Zelj6R?{kt(7S(HhpHf| zEeFMe5>%*3FPNMOPp)gNUFZ6VBn(twN1j~sRncH><~Z`)E}lkmmM+(`<-99YE}JOU zp|o#nE-Y5tkkU zc?&#e^tRL}ouEP2VY%(}ncM$-FBd$Q)vT!Sdb1eoVf4ulwB_P);A1SP*T&BVoDg5# z1cb=1+%og<{c5&c1CKmaTv);TaH~c9{^Q&-7R)r z!lovcgE$Q`=Ncfvh+V$oIa= ztkQ2)F)P&Gj4v8<0Z4}~=dd^CvHI(A(Uxl)X!Pb|r}yfj2|F%#wdF)@2dG!ZwDeBG zG6qW~v}K$omb;2A=PY-oEjOKP$~m85^@v3*hdmAmK)ldjjpMmt&w72f3J$E;a-_NZ zf^`|o1we7ha@QC}U2}u;aC;66r+QY%|iFC5z*YxLorNx_`}#6ilr3BnE^&UzoXrY~77N*E5BowICVWlaTo76PnUhZ{Nui$2$>}1LzjfAxituL9~ z%T=z`i+HY2a>KVZLykN*+%^g!ztv4SaGd6~7SN3I;6olY0+Eg@qY$MT6HtcUK;zY@ zq7V7uPL^EtTh|5|#`02#P3OybNA$d!%g##=rM`?RRjgCt99gt>@J)2 ze?*?^V7SUqYDnuiSNbr$Koz_l6WPsp4x+>#@Gv{Lj@|d#{YPa5yQW-{gp}@N>NtD$ zoO`T)T;w*<4VP?ziqVj>W^#eb7tW%eLeJD2eSrE%Xxhl(n)7oL_!Dh}u;vm^omdlew z$3v^yb3h$QZHG4}x$`5pnlOc+LVn|kwFG&rP@0jxQI_jh>oaHW=W(orVyu0ZJKQc6 zm@HS=P&8J&Qs}kb^%R!-Q9YI6G{Laq%@?+?jM_r#yzU5oiV8W5Qf0qM4(%ByvE1y1?Stl`kX7?oJ(J`a zwGYBze;!J!Fgrie`hB=vHN*hnk59c3-7SwW#O_S?a=-N{whF$it`(f-)%!N56#|gU z3IUc|Duqz2wOS1^YaKC;5g`pX^DU(LlBYvrzkvD@V==O{^XGVDq& zG4;ZHZuhO@Rcb%R+H12(f#+f$x8GsThE8`Qtl9(qPyM@ApWa&2MibeYFmVyrcn zyc=5&M~nt(0mnIxEQQ$1FXvO?h} z(bh@G2mDfb)^hKqo{Qt@@mLRh{c9;Jl$Go1bft|v4U0crHO5r}9v)a|wT2?+)?HN&Sat+uDKD1?7!I;QOqe7Dkt1ahQb0S>5 zpHqXbOb;uC?u)5nY_#r39MSKv=ldISwwX+AyUI#Y^_3Di(3y4%W7U+L$KX zL?@X<##|H2{wl@BY&l`SV9VWOCTm{Ik>qMebS9H5-^)qfA}S#-b;1eY5R2L37iYK> zr{Bx|lwYaMOPQqL31AHRLY0`Ve`_*Y3mZK0jEv1BM!p3r>IL)Q@!TZ>0#6iKknh4wd z+-%I1)VZ(HY=&WP=nVp`e@G_Dn@?n&p_$VPv8wo?keYqC+@+nS9?SVBMIi7(>8*wn zI{9r4R{L;rt2wZoyPdnK>!Wd8v*t)}x#+G2jM@sW)3c<05zXaUSPnug2h~}Q!JH;| zHCbHN@sPOSF0N9SUMPw3CW3;BM+9e|2&I%j@a-&gGU9sfCqLen<^*?G^uyVMolG@FCt!HxF`&O3W2~{p z7GP~FNp6SWgL|xGxtAnu4>NQkB>dCp7B3cnpL|=j=b#wccgl-HucS8}Lrp9(k99fJ zij5`cimt{WLYpbBQ}#uMqb2tkVooQqDhL(uhf>^Dv77=CHPHHXt24aBa+>=9u}9k_ z=edwaT671NK71M`dr_=`EZFa;30dQ>`aOBMA4ShK<=G2F2>x!PXJ2cJ|J zB9^SVx1a0vvDII08Ef}(2d*mtG*zJzelrC5s!x+u$*w}x4p1u;ra0{m?=mZ{-&@m} zX8#;Q_Nj<57ouh0+;IE!1L&dN-Gm2+~_@$Ab^OVPj2p5C+I`nty{`uI9Lb#lbh* zVL3^}%2u<#4i2QOwtIedhSa?Y- z_>J_|l=I-@S*d~T`o^5M5q?$;1dmE|cYUFysjJmlMlPsuqJ(e7D>YI=|r+ zJHD;;pYwbI(9oLElym=!mzn{JWS4JO3ba!C%^VNGs`pFoT;FMZE-mLsb4-_0b}*5|raA~Ys|q{lZK(`G zDvMcb1Ood{n#U?z4m4-}s?y~+U;mduW%+l;&83>JkW@~{B;m+E`3%A6?*Zk_)2?j0b9xnK6Wa+)GJu#+?$m?u=f(S zIjp}5la5-=?|H!ua!xI<8c;Iv8TWFj@Jk3gS@i%3d=}}6*aQ!J;!iLPWH~%zwh9Kz zp<4f`u-sQ;Io`GzjjCo=Y=%Wck}GzH({hE=+bY;nR`A-SLS+SW=SbEuafH(+&RhPbzhs7unBQ4u`_8=U|~)KR$x!dSh(rDAws}A=XNGeKh5<{`J>?r!DtHkF`C?T5MJakn}7sPAMf$aq;oQoZ&%JZ`#tu9C2xC zFjT&;P@UXX&>OQ!3dPBcE0{Cqm`@TS9-vmYDm|E~bIhB|N3GI(1z#%Ohuj9s9sB3L zMjvKVZl62{isPulc3A5rz|w%JT^3rE;~An4{RR$}tyZHgHkYT-QOAo&@I2IfIdC62 ztez8PEti;XcQH4ln+PpBqN7(ZST5G{e%y0}7aq6e0NHZ<2Z}zqO0k)Kz=tcu$MS;l zkm0C_mo9~~8&5tSgy0I%8GPf;Yla^}R+a-e!cZwC?z(DXD>MdK?>O`Zpz~U?NlKU% zz#;r0(Fa}2J@Q_TqxIQFGmSZ!a+R$vOC_1=i_O;O;x?M|;roT=v?0lR`f@^D>#OOt z$ZO^|93(yasq@`+(XYH3LMSjA6<+Eur=inWV7Z6K+FuT}53=QCC+Y>#2L2oPRq|ua zm_%?o@`1PrgTH{+oadB3XT=bC)<~;Yw%X|Z3ib__9an0-NN}V!AMWLeb9k1iE=evI z#0Ij0g(HMsJNI%BOe5)xou17&$ii7X&$Q=^#dvgFOTvCmXVr~15t&vKr3CEbS;10# z>a=v)azHPmWReE`W)RWg;0&*cS-}v`&$BJ}M3xJ%!s|5PtRo{wA@kv1SEF?{l%orbk+x_f+l0HFI4Pd>|aE z_F@VD_H4PMzb#Ikp6Bbc#%n)fH)XtQ}3m=px}$)fe%(@%OzOtrKFP3AqtY+ zXvkr2)MNeMUbX&g&Sf5nnvx*MDkztv46lK_QQ?DU%IT#Kx}y+cJ1>FFk5qsQSWY0# ziRFF=45FH`iMC&cQF9-1Qenf+SdLSt$#TpJUQb~u9;*jl;|S%{{v>9?U5n+0rHm4D zR(r-=on+|QbF$`^p^5ug;I~q`uTv9mSO!5}V-1AcauxDel}Yl)WCereG^o%sNu1`0 z6$<-R875YaFVB_V37T}&PKl{5qWGk#esuD~t>KfrvM2hG1ecN>NtRenGrYudCqO2i zg#se~6>HtuKBtR}DI)&ee#k)p=pzcnZj*D|p}m&6;XrGaz9-N-C^b zI`)>M54}v1dzf?1a!q9LVU^_$rhhgjsf;sF!`1?*8_QjYK36eJp_fnMHcRY~O^1BB z_=6bB?TzKUi>6rag)Db*_TB|7hkYZKlR@VbTL}>yR8d{wy2P%AAb3=pPHmW~&&rc5 zchBTGEbCITV$Y@Py73CH<1wrCzbN_;^rmIC4-I>e96O>zbUMg=)NwuBwm*}I%piscDcc9SB%b!#lw zk3QV1SU)i`!g>_T9n?y*-zwOC)cc(?d&{|2)rp#PDaxFaj#qwk_=j~L*6!#gEx-b? z#3xrnKmfxuaOgD3SV43%%k9N-#g=<~9?>k=h(cBd=#d6i4OC(7hZqi$i$) zfk(rzkT)lmt3>Arc4NxiE)*a?dR7U!O}NLq6lIiPE%%%w!Qt2f%N=#NE$1E4v5^;S zJv44y@Gx0Lx-}Y{VIkdKP`U5ce|9cML=C;IlVi44r?qPa%w3>VK+72)OZPU$n`QO z1~>VST%D~ik98%sqV&1iCp}+t0pCt2-B-sstAn`W2>v>fn#a&@#q?4kk9CKY z8XIfY;Wt1MoY;*oY&LhN5JVG-JODVI>1yH}q=FFJt$oO>;4eRB1;?h-YT*Y?eo%cE z#JS;y!>Jj>wGe`<+nPy5UV8G~87wvX382sYKtms8E`$`koSqFl&5u5uFxyL-#!fRW zn6ZcFF)F;+M6-7<2ah%L95*E zR+)0|5EUf91JsMHIMEh0!M{#i{EA0fC%`JZPRF_}S4}xb;V21hNEvgtzKBbqlG1TN z73&Mu9>(0u?5=|2oaIU;NwrohbOqPTD!E`>tC1Y^B@7pNta{N{Y5)hgEu|HEg#>gl zXHKiLTq;jowC0pn62gd%#rvVkoRcjll7p;WN(lL_o9{2DE}9`SUryBzGs%hQW@GNX zM{)--9JF76sdGbTqo@NoyxyM>(RC@Vk6sxUk59}Yso#d=JH&d^UqwH37P2D7+Wxg# zzL!JvA^r7}lH_<&K)I0RAV@>Ef|api8OU#;KsC^WCN8tODDLjr+KWPk!s%!vG{|LqisTctq{REc#URjP%ed`5T z!KAz4Rt)D8z7&Tryl@wmD-)@$6rzqX41bjq3@PUAE-O9OJFuLLIBH3D*kHL5exQoA zxqG=Iq7aMG2l5Z@g}w4@$XUXwK`FsP1ysKZ*ta4KBFSX&1-(>$(=V8P#z%*(NeYj3a+AJl%MmryP#8|H*DLIj z6xU4YwlZCgElKiX4y&+SnfuW83dpNGsB>LX`T%^7hstf?B@phnnl0Ckl1tJZ2tkg6 zAV_gsvgDlJ?n03`028`^U6Smzk)-eRoaxAD-M$_4dzgB;b; zlv5gnhaa5f)=*i_6rn$%RcFO?F4;}q-YwJRrtl=$cnM>?V9NC?yp$D8k994N#JjPJ z+pzV133xU4N&LoxFErr*1|f!6&kOe4GL#++aPB^BIWKH|zyg>W?8zjQcc3_~`%Ys` zo||I1BhTaVq(T6b^;yR{wyiqW+-#%ScUZ-Hzf;c=vK+R0aXQ}-r}klT^l3`^^l2?h zo8CZi*J3$NpkAe_3vcC&=%6pA z2QNFU5K0fO4Vg-?6^J;5ksYbdYrR6?yu)!1RfAfoh3F21*l!Smm(GS?;v?5cPL{ zo--}>vv?}c(^&3xHB=GEb6PRsZsx*Hq8zLoP19JeK`b{u@4#}nm3sTUFB&LA!x2!e!I;HYvos{5SCRE zfaVkSeQF`(6(BxSlXSi2UeA}iaGaq!Xd?D7zA(i4@(hvv<(M%i^PtYw*WYR8?=jyT zNIo9mwGNrSQm4c!LpYZ-d#xN-$K6zLU~4tWE&=F`kH9di_0}v6=d7Yt%2MteJy-T6 zQRbZE;mL;>Vug??m-%v`cE&+$BVXP0>JRD9Yu6lRJM;>eW2t{Vy zN~u1NOKdri0vEPk^&y`U$GBj+A-HCYe`?rrQ}SCp8DeFxU_@9w`tT)7A7IN($R-id zjj22TPE@sPgLGATX?md#Y{lx-K-ko23R!gg5q|Ni4L0={&*|Xcn@|LGVS;AR?VN$> za~j&pmQ2N#gOawKH`WBh;p{d_|Bp>%$i))=(Y>7K%egIg)EF-ioXYiO%XK;HY>GR( z(0}66fz5Y$uaG5!L_t}tg4c^iLdcf01QS;-wdJG=)0ie4Fkq(J?MO9E0q$G#f8k8m~Sa^>MUbYeI! zZO!fUrtCQjvNC6m`=L0l*!Qa3_j0ehd?7X*eW0%qILj*Cr~wX&R=l1Mw%O?4i;0W|2w zN+GCVSrg5x##q-Yzb4UK#bs$HlkB))IZS&mNEScQBSa4+kkXYR%*$8ytjdcUYkSq@~TWRsgD4iAvE ztES(|%>uiy9Bn!;`|91yVUy#?a)8{`5`(!s{#pUpvx!QIvSuP$yrL7b!ZdH$TDpmyXuw9l@^kMIHtn3xciLA2Z;s_j4s=eH0 zXdc6u)Z#X9-O`pYRsXE`S-NnhbJdfopFZdP}`&0k>kSsU8X51kpQb=IRf#sf* zlmM3VYJGt5UcPuAP&ZBip5BuLT4gy4xf)8CwXz%*1Xl=RxZ)r~3EFiEJzzoDxSIf2 zu96+w2OC)KwPU}zno{yW%4>?Hlc-+5iBzHg5iG|pnqK=5PtYBPJ}~yc)=NinojRfy zJM1!+t41N@xiw(zSzdXF(X5N6tHk9gb8h8GJ@sDgatyJjcEDeq-PM`q!>Mpk}84c1~YHQwg<#mGmRwH{+Zp-AIWkFv*nmC_fBey zCDxZ04z==~2ErQO=}jybN{{Bg)eqRR-6U^O(m!+)=e*zxXX&hoEo&|Wu(SI8kiw(# zgX0WAg3DDVd(8@O=on}f%RwYIQ~ThVB%H@;wGVj6acYSk6%TO+4osJ;jhD;>JDgg@ zD)qV(awgNIYEWSwv&N59K4hk?W4UHa%4!|!33Rs_zZkFw1X%Gr$H3_jY)Qg8R%McC z?t?WYftjPp{*+L6MfkuQF`XP%fNJSa!lNVz?{qg8YAN>8 z&0OtI@<1Ve^Rd*L(u!k)9;;K`KVrEC=F6$`l92wYnrMRYEcj4xFs@p~b*|#3@`_b; zs8DioLrT%-E@Z=;Y|B}Z16009kPxX}?PhL(9CPCIYm-=M%4Hr2^W|)YmwE;B|2WmL zQVCF&EE0EH6^3x)ONLuXmm6XYHT-~8^MWC+jKRhjY6&kqUJPV8(wuiH+0_WdGaXi( zf&vh<1WLOIavZaQEAaKqRFTWMZ<4I^M%X}B-=emw zL)7(glu=+8(3`-~HWTPA#eRHgrd)Cyvx4L9BGHdNxb#2ndiJtt4v=Eqg`MWApE}^g z0J=L9qA=&_nHx7a?It(cS}>;S{@RJj)`zUZ}Dg7CIk?kJY? zdpSvFfSEoq_RQ{B(V*Lv8o3ezv7>dWLcm(acTN!eSIdpq5;eg^D(#w1=(Hw+OJEcF; zZ!LJp^+5d&GzSTI7^0n!8)CVmz0|o6ZE3c?^IWV9KB(^!WW{PnkzYG=zq+>eE^b>1 zKU~g*^5ZC6QpGwA6Sl+X1@-k~NQNFXjP=P`?yXFnMipzUs^yB@aWz zn+@te18dO-O6%FAqhj(VAP#*QzJuh#WgSK)%RPtGm_oDV?q$n;{Y%k@iS6YmMRaTJ z-<{#XV@_|DR$}9+ah`yZRdUTbFD^+v7{eWQZzcp;&a>x4Y}BEG+n6JK zA7@Y;HZ8i?|FI!@6Kv|VVyvaV+!)L5}8YdBwxQUarSzqO1^B%)dCZRjTFH zoe7IR@F_e;oOmtQiQcV70g^YPAkL$7XIi{uK~+I(XVJ# zLrqd4w9{ro+zKjt;2T*EyPG?Q4#LNNz$Sl9AS&jJdXvNAhO<<#aDcmMbM7_J@b8tL>Em)nKx2sTYs6sxMk|HwMT} zEN5I-OgYgTgzzkbu42h+WnwUwr_H%Jr;@MaIdYq5Zd)vO?AvnlMYEz0!l*vMaR}8> zl}<^l|L%IuojzZ`KS{n&h~-Fz9-4SJaTbi#q)NLgrVyhdtg_`oJda~k>!-VlwP%N# zK)IJA%UQx)g9vs7Q^$}~+<~TBE%jP=Sw(n*Jl5;ZrB*U97X8$e)tr0b#?x!aWO;Mg zDe6#a4ueq5mm_$MM5nyq*jDz1wOw28pPES$U{4adtSs24CCAu9qC-p+9h8y=DWW`x zt96~5PWctGoHb&C&}z`@bv@R}b)nN-xMEuISO?zAHL_BS75^^n^HMJ76bj8@{VH2- z9N)=uA)VzW<&Uh99LI^(=!1F(Q_{jaJpi$uN-|(w*fn^7)EX!SP5G)|>fE|5r;c+X zIhk}8XU(UULEPb>HHT-r9&7DMa<6+iBCn-EaKs^=#C1ajhb#Z;d7iE=-k|H%Tv^CV zS#BLpZs(4!7I|pu42m9t(|nc71l1FyS5xnI%#niF0u zljYEO$)XQ0PHwd1meW@zMOrHc{ix3Na=z1q)-ze|_LKMhUakq8UeJmI+h*{qZa-A> zSaC9cPQcuK&>VSAu+Ewf7H|c%!F^TnK8WrQsqo^+>EO6rZwx=+Vy-F4a%vWQJ?1)` zPk8QWu5~8Ca-HXd7qMC;xl~QNmd7g8K&5?<4qjQymy<4_rAX{iwGXjGa$(J>iZ%Po z{Y&?9#&XoLycIgOb2)U6hp0y1vgB||h~@C~@vY;r-Wp)GSa>?U(s`;Jf>5Cs1hIqc z)qGjroKqamC6+^~9C4rW-`it7Vl95Jwp`Xe0BOg)f!bis9a;O}L%de5-d31uwG#Gl zaAPEw9NNGC`#x6&jQaSZ+OE&f=Aqm9c8(G$$1*Uk?8fQ#W*Ytb4H>?&TPL zP>j`Cj>JYuO*ALs#j=<$)?f+25Z|TrSx$UL1@bdlPE}vtr)1U`cPKd}OC!F@d@s-& zPfwd8zd;JdbfP%(T5)Jv>xMcnN%ndLGdUPEr=ip2IRsjj7u*jmNL@iGf1oO^gK!$W z85C@wstBx9Q*LF;ODf*{3-;cBdvw1*{%!;?!;<(J`6BMfypr`%w3Te;Y& z#Cr)tY^n1ScHfq3J7cV|Vp{|Pwwx6|TofG;T^%StEA9a9>W5bqY>>(iRP^QZY`Hoi z%g_(bj%^!;Y)LW=FC)PTg-tx}qaix$Si@m;Tkf^vv5H}}jDCw%C$<~C9}_kfze0HM z;ai{EC_n99PVaa+^suXbVL315607NeB-hEnB&!8TXQ?~Q z}rmc5Ipa(VasiY z-h{D}6&&Y$D@SPN^j?W1vE^8vOjS;R{UZN^$yg_2l18C1H;oiE9oCBTQrxoL zl4!@$Rp#?)37zQ=QuW^>a5=}iqI6XxPSVL6L65e?BFOCm|BW33R& zVNcY}&U2QVO4Wl7`>qqm>G<}EpuZZjbYhYYS!%sHC9S)~N-wOegHQ+}q+o#v-O1r# z6k?k!cRaGfOVI}lJ_H^z%xXit4k~69R{H$jj^Yj%4&k3pW;qcZExK#4oN?T0rI$H| z-Qrc5ZQur0J>=K+5s4&o@k8@jUl@BxmQzx215CO9Mo*FzmQ(SAj5q}*N}v_OF!<0y zo4JqEX|8?$9cgX|XnbM4oC{I2gTZgh)*L5$;iy~(l7q~Lkkg6{sq+1Mnsgh?m;0xi z6+D;hBf#oiNf>=-+{WS4pf`2XtdnkT%W?;?Ts~KVa_;(enkUTZJQrjmF@T-=E?K`< zEU+hO2ZgMxeMpuY=VZAi+rI#NBRSe~Uln6rM7=-^P~t=F5SjzM2DK>>rAfqR5@36L z0uJQx<5e7A44-by=^+??NXc^nVmTjs1H}NpwaIp}x28>_retxe#z8!)NBU+x*2jek zuOPWbbrxSG$FA7Ds5o*HNk!G4-JWYVc{zYc3<@L{7Y*W8jjPAdE__6mkz0D?p}VrQLx z@mJsR9DeP!+zA2KsXLcihqs-pC}XKjIUj|<{KCE;Qqb2`mK$lzA(1455Bs(PI}%;v zxF6F<(qmN@&510B!_>mOOSW8%z79R^{%)Eb-W|PS_+*wF%9{^^2!ZV#grUAzLJ5_{ zs#YuZsyQV)cGs*i2&1`>8UfL;O_n?Ee`2|NCd_$KFpweEV{E3&HNgq{A$})J6jP-; zLZ75U%SUR34R9@lcUEIf8A2=ha;IJ0ImX=HxQ+IPzfNOcI!`XxLS4D{zDbXU&(ey0af{?Xq4- zaO)-iQx8`8s*AY`Sk5V~W6Pal&CO8pD%dYC3MoZz2W?s@Dd&p3-Q zheY>|HKVQvH78LiD}@NpSq|q3VOG4yE+jQvMu-E1-g?cLXv>M_yr@-FXV`U;N?^-5 z%iWRQAoB~O4q&=CH+(O5*dzRa=mYQNH2UF@_i|~?f#uj0og|0NXy?BTCV%7E|BWs* z;>&NSu+PE8u?L54#ALxCO%BegvVvpcbY_vTyIfnyBw?~#W4RzXlsgkD87mX0j11?C`Dg8Je|?|c))Nf`3gyLlklSbjaBO_XD;sL zz;gfDSnf+>IXJAKIqdyhv`^5I1CC(1;J6b^x#^Q=u5cI^F?AxaQ+@LaefxJCx@{~7 zq2hJjW5w=ztol93{=`$Ch!|pEfOTY-U>y6M?&Tgc$ogPym*B8|$ud^5+>FspIkH?V zvK$0e%OZiOtgZ%gzC9Shr_XZVc%~8(42K2f=1>3vk64Zh9w^smlx{6Ar&C5IQY6>aT*;Yh5V;9H4ML7wFt9-3d)Wq+Sh^X|YfL1G)F=EJu3N z;VC2#{`oAdOq~|faq)}&vfhf5JWM0HE{$;x*^MMpNjQZSi*8JUrW}m9yH*tx|AS;X zSaM@5zIdde_RgaKpi~0KQh(qXvgan8$!}aZgi3LO-abrU_FHE+De3VX;JLw+$#_1h zb*DHhpLj8I4&u?ARxzEVH=NrF%VA?N{BZx*U%_$_e)#+Ezk}u26n!7dOgWD~-~oPJ zh;{0yt0Qcj6Mab|pONmQ+AIjIiwEAU1W`#7>ky#?sEv^%C!ZBo+;i;KCzuq+oM6~; z>9N9;`&W=0LackYs-ri#DgXAg`8DUnT@>Bw~L^33laiRi~9Y3YYCG>6sO<=QiAs*D2~Nl^kP7A z8GL}pN|qayVN;5I%6sGf;VW1UT}j%_T(;7Tl6_zg;22}El_oax z<;Zdner^jB6|$U-_~JoEyb?rLVmTb*2byYYG&csv^Ns9hRfEWr*eM z^OaeSrVt=H~9GfM~>q&rB=`tiyYBds>Wuwd_L{Z1@5q>Rm{!**gh z@5N-zgJsYOoi4&jaL#fHxng5&i{*~?emFK`t)-IuXI{)TnhTa|$U0VVT$37D2!jsv zX8Cev$?>8N3&$D`6jEL4fVgh*@swf>bzF6A(&Q?^uNSbKXPw!6XC;Z~7nZ8L#@KuU z6Pjo8zg}v)6qa1uz#D5=BabB8p+AJfDvCF>FQPb}1^$=tUg_9eiWv_UTD4mKcsR;c z;nsDQ`=+G1PaD_j!whS7(1}7D&#~fKMtGvQrWu2F029L)O8nmC$R{E^u8%QX5sAq}j z%(j+1UwOTvNoY_-cn}@aj<27Zvgh45`H|FjJuu#iob0n0vF_;3f`9 zamtzlnT_JCncs5Pi8mGXVooF`MdW3XTqn1R3ma|rD*HXVWU0w8h}V%{_mEO$5@_tm~xo0Zx?eA@K#B63`4{sKq|9Fn8wvJzWmK7udo{B`Kifrf6s)>nF@NkH-WR9 zpuz`bkdWaZ5iAG6&3Fo;n+up_$}uH4Z{?EZGD8|H=fQ_njJk`nu27}?p+ApWvJ`)b z<$#quVW+vz#2}UviZNG-?pX?Bt`=)u=#m~!E?_q4>PQH%+(vOk`z0B3WI2}X^J32K z-%p7j$}oq@ov+o&6-q&40&!R&2(TP>sloSdJP2HN9QORi=K4ZoDo`WI|t+tuu+| zG|btG1a%LLJQ&OUC6?Rm(VIam$0mI03EyWpf!?&HQ*-fE7}QIyz0B%jaVv>ULt5AH z?UR#9z-c_NN}F^W(i$g@g6Cz3AUG`pqYqofasL#3Nyu{Hvm(w4j}?+ECmdq`GC2!;dN}QX1@YCBQCo70LnL)|Z@U1MFilQ7&2T$NE*I$_4%>N?0u=81#k*C)7H9JJfM0`e3=hKJ9*ktUK$FAV9k{R@hp@ge@{lY?hZQ8ZOO~U-nvbjV8N2?q zMiHHgajk`VxBNjy9W;X(a~6I0C_dj;wQ?_L#b?#(xUIyFCqy1ja*yp$*4(UkF3EPI z4~^w!mz5>^pgCD{50FW3$2ek-j6{gz;yBFfJvKQhRK6%p*jbL!egkJ}So{*2R{aO# zq-8fB`X?p$3*@>fK5Y`lTx#?|7A(c`LEbR#Rv$%&;hk^;_$xI~4m?Hor|=*I%T zz2iWt9CJu=2J2q+t8bX6|L`@7ySyCpFljDFv!*%c5s2BK z8%ut$f)F5(rp5+ch@`NT%B#;(pwhvmPK`P>>Be#-N7E@3?BE(p2v~)=c9`a256h;Y zIS0*tK@6bdq6<0^iXxUc_(QSWL@N$ZWf%R`>?s%fo?}@5ZvLu<>hF!^$lExmbjWus zEN2s{UGag2gSrMYz)CeR!Be$%bBeQm^j_}sIBuZS@}6@L!D?>PIm2-tHO}2qtyhz! zc%NP?HX*XxG|NR`Ey}!5<;4a}_j3TEZC15LMoBCr2p{%nj82Zj*7(9u-=SyA60^c^ zB#+p4fSL?B{gwpZ`D49U%ARyr6t(3_+s#JxBtOU2{|blfH)^%7FpwWRwwxe?u7`Ii z*i@Y2_{Hgub8NC~HMFLH>oBy7hTJX(J*c>=?T9>}xGM`ENOH+?WVKYi3)6`A5t;+m zrOGu*B6E#I3}p;8wo-*jZG4r>+9S4Ke>11sIUK3hSSuDsdl5C-3T~sDYNcLQ?*%C^ z(z>y%;C_@ zbt%w_C7lO#+F!`izw|NF^)K5O(psODZh0b@zp{85laZ&=)|ZuECb%fDEd5D zOSw_DTx^b?6!0IjA7T#-J%Hnk9m82)yvL4~-bi{;Z zO#%%_^6&lD=_f;p|TUW4ZH|Li=1393mTCYJD8FPt6hnR7vCKnErZgXIofp`aol90|*_ zLm1Mc215Wml?Cd+cX)ip_kzqaL5UtVsI1mF1d2>22XE=dh@UVvisCCJkflyvVce+d zR^Dg;jy8oR(3nHoZw-v%xUP^w(r7NvS_%~$2ZAHV1vEJg=JiZpPsTwk^=-H{$Lr{KyP!qAwY3T4@R3k zSQ9K)h1*)S{{fn@+-D)QDv>icBAbM_c`<2j%}Hz095YHJ!Ec@C7}MG2R!|&i4_y#= z=nX|KlNR5hgWrZuAFm*iUG$;IhpaFlC-4~xI4@_@K)s8^8Y3HU8XU)H!E4QpK@A4S zp_jUca;=|)CbkMMMp^tdvO2S!n!xgD2nWLlu_=ISHI#VnzLoL*E@rGLn_HYjvf(Sa zC`~dU821x0A-H|s)?^sV0hEYTTB4CfG-pNi>FK--do4!TQY-PJv)o%&T^~epXkb1J z{-A6U7Gt_W#V;{LEsLz4Sb`vKp651rH;2jzEA*#-FQ*L~_N4lJjYR^D9L z<2k?ZW`ahY7}HUccM!`R$UV?mdrv|ZDvcA%6{lgb=7E@{4;2E(k=7Xbbw)dqodFLl zp;*$y^MLyX;c_$qcr3XO6o=`$P9q1!fhNUhf@9PKMvW{LHVeZ(V3%)7k;AeBWh0JT zcaH1R)#Vjsg9TPD0Fm4{F=rST06mri=zwBlo{Zv}_{k~)d-W1&>(}h!0~+;tlJjRE zxuGezP^6@PFM~UN#g`|QXu4{0Z|~^NM{}Ev@A&%sEGL;pTYz9T2_~)UUC*4;rq-Qp z-3OlqBD>k_mCPYeY=+vEJU9ySjMq9eN0uuWfupg~sE1z-1b)+R5f14p9DTmf_K~yO zV`0RkCJcIhd$&IV;UNZz<)94m%fy}<02oRaNJ9gKt&(d+n5jav=&%9B^<-dh+{*)( zzUmxwZ5AfKB>)=Jkm7!R5%=ieN%Pe-Hd9U96#=PFV!6TqL}EE{8t~Bl0op~~nyj*X zqzq=unR8$_s_;U}%{N+tuuPK5a-g^kG*4mr^lPk=#!@k7RW=FNS({+eiVBXiX{+VT zRidksq#@6dI5B~Lj=&#SG$$US<@j`}qQ<2*x@F#4z-bAosaHR#|uX zTt62>#Yql17LvY}tB;{g;D0E?xy)S#0X=saL}57!vxMt*FS~@FEccrPb4GJy zIlPoc+bNc#){%2PRGXkBbOp<-mcFqbtHv4Y_%m92+AGJYX$X7FC(D^DI1(Pv%J0rz z&|hoJOW`Azfs}+s*$YJ>G*LI9i?(txjQf@>M`;{~8@uqgt25;)%i-cKu3}Tfa*R-+ z-+9r6Zm9@k>kRT-9&KT@ine=o@3ai(m~Jq7i>}Lv-S9=C({y!6QzhrBO;1 z$_5o;g$hlngmo0q6S3TAQn+XjLm9+c{$uveXbxmu?vmxAAc7mkaZDevYaX7D&SVzJ zqJ?sIVmV6)be4lX&vN4&(Pb+vmkx}{fL5g*#gsU_6Ed6&?1p4EbzJyJCRk4<@pV>{ zd#b{4Xu)!*isZm@(U>vUOtjyC<#zu}w=2*b7Fv-s$5(SYI(UbIc+T?Xa`(PyV+x?f zh{_$BO`^)7=m8YD(Yjcje`KqOuYjCXDaL2?S!s%hx}oO9`3Y?=XwO_YazDUu5w(%v z6Od0+x6dKmh{Ds)Z%K5Vv2wfeN-GaMZ;9)xxA(kY+*l4!Lf_(-(XVsBBeq(?0O|yQpU`G7KH?(y<&A zj9AVBI?i8hzAC~i2bJ*6oPs$tRFGV#u%G0nLZK-TA*BL=I5HJE&M)W@f1S)MVV9WAJQ&;OHP3SOLEQ%di*n(kHBnh0#&yb^JGrsdGK8s5 z?)4JQWk~l6v)okhyzE{?b39U0dmwl~dem*xo#Ogs)WjpJm;kgONhX7}{z)T!?9u0o zIaD%PScT!BzAZf16hX;R)9MijS~A50B7C4*lE{&y>mRjn9A`&duO z59E`q7kvey)7>Np2?AwYnF9S>gOP%;FR%l^KlTd@C5+=lbEm^Ole;vP=Fqs`Ya^EX^s?RJMz6myXSIZqb-(Y1l5!lYtK>NZauLe;1*{ShoNZ)w#VfiZpf?zD9vl%JJRm5734n~mZZ;apmci5HBg8CygdR+` z{z0I16HzjZ`ZUCQy^AhA0Fu7gNYCg1q~b?70)NF4=6TW zz3Re~NO-_iWw~Ir!g3=qdx*zZpQ~9q6w1}xy>u}|74@k@)7Jp6Ww{5j+~bAMpBtc> zoW8F$D&g!w>9dD1N&Nc#-z1j%!95U*I5_yP7YC&Gd=e^<+$wa$1%>G>zhs1dCche% zh%HzcM@{zcIc=-Q?mBBmKYQ{A!l(nqs=ivqJYfxlX0H_|9NqkQN9dgv;utUm8ymw$vRj37M`frhfk zlk$P1yl#w_0PIIgIZ0Y?0?mi(S78j>C`TSNYNm`7S!WohRfo_kidI*@@ZikFEpkXG zFU>u)aQi)6LllVZv8M!*koLZz66ZekaRGGLrEq?!A|a!bs6Bl=VSS@K}?t>sq`IqNg94z~B9@0=PT{?o*7_lfVq0Ep*uGgq6hGKSL|xg}_h zft+?c;Ew4dlSndGE-VU!>2Xjg@G6sGr1uLS7`~NdR_X-CaxO!#mbu1kCs_{oRZ<3$ z$N<$~pg%ywazJ$}1m~;p3eWX=Z(`F{k{wCzv$LEiPT9c=pS50Tt+iKXllDjsmwXZI zqUrq{TJ`*2>68vpX!RK_PEm#HH?X5HUm-U@8(%+ET?<16)U40HygWC4V||<4p-M*e z+}<`t%*2shs?{!E$b|;S<+?*plVdXQIPzwW6kC0$EXT#vvGCkUXO!c!RaJF+Vlae& zs}IAO8LKkt-m=E}RF2g|&Gs{maRD31JGf0|b1_}PKbYJfC;zM11sCR3-X!QZ` z-1_R1X9%MOrr>!hv@pQ7-~e4Yj$0s|%T)@qN)#F}rVEY>6E*P;;O8ttnQ(2E>nwy*ufut_(6_Up8OS{yv6=Mji#W9PZ42jGU2pmKl(J&UbCyVQJW#Ha z+;og)mq3c;s_eT~#3l=%hk7Zkn6wX^IpFfjiGLs?TxV`N($CQ$*EAbrvm4Mu&w=Au~BD&k5rgGV# zonb5u77uyvJQ)edmKm^37#-59H1(Dc@I1>woaN@_&W*SYIN!nDwcripwqE0H+}7u+ zyl@r5*=n*J)8sOe(>=GeD9&v%&{|l9&<&+tJ)6ytOHIV-s)={hcX8ULFP1}v1Mo(c zBbOsWPUJ@4@QP?Kc=Kr zND>`1P*dW$U8qE@?wsoKS@esq2>5IYYu#fY@1?L`j(z8Z&YjJ3>n^n_lYOmy|NoHO z;xewguz+cFu+ldFb|Boxva)3dk=%x9a*E>)sXSj!fm;}o-RQU5knjZQXprT~(E=IE z6`2p{9La$W3Hv^l(?UZO;qH(CKaSsQ9|W|mKct#_EjM?9up&8~H}~ttazBi{3iKX1 zyl{WkyAv0YA6wh?F5^p95gfaBf_y@- zCJ0LaD_P43%4itW#Luhif(&?Dzi^Fa5Yz_AKl8YK5GQ1^>Pl&zw6tsr`&OAG$3U)y zg1Y7bny|7QR90FOQXB(0m{I*BtTyQr1O%Y<dx!4-bMYGm4K4~r4bDwXU8Jt?NIF@Kc7%+R?_*Ug2Mw^7_4jgs);qFeZXc^oT|y5bI^_uXdV`CvK;#HwP}l24oR)@! zN6W4Y9InYC@%gJobpC!043|cWt!38wDQ=w>s$&%S3GI*|95ja`=PaiL@8@#%6UA>c z+@X?Ww!jSX5U-I`C_yUmOr!>5bg~91g38pB3LR*qCywQJaaiZpUX4E2iU)hK@9TEW z=laQH;RMl5P{a@AbBDAoQcV?{)gTFtPYFSAL2WT~183bd_^ws$<3i>LGvpADb89|G zjvTjIV797Vs|w`cXP?ttsL^`-LRM6xKhGnn`ErcqjODy~Fo&^~uuW53pM^y~;xKsOCSiu%0I#7zT^K_~+k`8`NBTaG z27v(al#B&T8rqe}bj`2W#E13yFf0&Jwgr*&_}e!Wb`#!huv{S6Rs)~`p&u*<1-b*v ztr5q$tn@Zc*GlJsnJ;#(j?avc=B z;m65|{C<|4u#(=ATQL$%jD+^`XYn&W#|)b0umJdavKydkG!#%ihC+kq*0*H4a5lFQ zsr4u82;rja?9WOfiqkD!JYcCEB=;}=;`08s0OR;@$mV9gTLc{C@c6U+UkGL4y2 zqE(2t=qi@uZZBv~iR|4RaRPOOc*iyr3CJEb7j~yGj9(9&Kwp&MnkLGucqj^l!>?hS zM;K1R=buJ5v97@IUz!XRt$)2D$Uc(n7%Ble!))>kVVa235u)V)5*eJ`v*T+GX?Aon z?%zw2mzl5ywy$!WtAYUBvMKLNgO z>tPS!I)sQY89uQm4FeYunwTqzU`0-)jVhKR7Rd$zyRh4ScR)pQ=nSO5(;m@NSl44U z=%{=^kE9KPv}eil+5hr)OT-41aW%}2ET{B1WC7>S{G6>W7jan~D2^N_Ih!8w)_&FMnJ_sPSBMd`sPsp>6;p~-Hr!g@Pr_j^HpZ{(G zWyu-8L1*8Am+73qTu?2RBUey4(Nx;<@r*yV zFs&!F(HQ%kfdKev4{d0*-Y5?0z36kA&|xba$vnXP5jL8m6g{mtdo>1?$K4|815$(u zs0SFTSA6KzmpZ{KpD}P4^4s-IP#igqdmVCfev+K+ctFLWOP$nMQ0)$g?)vmJjrZ!( z*160&A^|Yl6D?}Jzz~+*0J0)CYC^Hx*}Dv9yKNUlGs7h&tg7&;Fh5w$MKpI;Qs+J; z%S{d)Rj1Kh-wMHij!c&^osvshjb=L{LTL?_b=CWFpxBNUZAG9kE{ZXQET8)X7G(vM z4FaUl5Ku$2sGHg#3hj#H^$?3==q^N;><4Uv_hZ-e<>YmfLgBzT&wN$PP{?P#`xpz8b@Z z6S}05pwKbl*x}KSCTRj*lTP%GC5eG)!529oy%sw?m_<=txsiJA7^YBBf4dp0c6$Bj z`N84Ed#BZs@!Vfxx-0VBYRU7;_`Vy)cxMHRw|KA?fhcz4=Zq-vgc1;g)96_M_zK<9 z>M;r(vJ5GZKZyVXq^RxJ&~rTlS!%$_y!`#L6aCrg+Z5#MSL9bnRt8s5r0km2n`>=X z)uO|CvDSvJR$FU$>{}gdo+_l2k}yh_12{5K7?uoJRv(PW08t$tMtIt~ujUED68NbB zs2>A70Nu+t3|?f*0c9P)YaBc&TCm_xyjd3Jop_oFT(F6#5osxUy5cA}s5_xHS`$z7MFYZf59^fUC)&+n z5Ddu(ies7%gi1uFTzNNn1`pt{bdQjg;|TeT&(X7k=14}%(Z7mbDaLI8n4^Y*=xQ!x z$C2Z1c#zz&NawlEbR<3aBD?*Olrmw>ETLh;`nkc2J=}kcGsSuLZ#_X_yYoP zBnrBTScy^sc0viABlvMZHu~_0pC1b9Vfs<{lR~4SUth`rFmMQN(73+=9EaLka@-;p z9T3Tx_G}4d2*CJ)#w-#2nLo-18FgcKJsGbB*vlWJ0vS< z+0s{q9aGT55YS*R0({$l^W*#BKo^TFvoln+qhpYYR%&F)Edi?-aMB3BmaDF zEiMtG%@}@b>hCzX!)EF4UVS(TgTrZax8*-KH z8URpc*+$}HOB4V~xX@_O%XGz*;joWP#{sDP3?TW_`LwV%8jTM$NCfIwP+ zKz*lZ0^8a}I8L_a1=~*6VP?aZu^i>@56gK(r-|!&gnd<198DK3?hLLO++BhV!QF$q zyUU=#-Q7L7y95Sz8(agy-Q`091d_}Dav$$~snsuC-PNn=oIZP~O*%pQS}wu%dB*zXVPK<^U_qK(jx3udRkO>-c{9-i~@)ycF`8 z9DCVriWECo5r8{w{u|~+jCGt&YXINGNqM63l0E)8oV-QH2PSydjt4HOlKs2P`-emy zuS>7Pqc|DbG4sM;5y=u$4sGt?#}d>kXay@!8+DpjzJ&7Ux5mUuh-)%HBxtnDabWsV zzluz+Dl#w7;yJw%F7>sSdNro@&d2RmGG2s(RG4Wy86b3-;Kv}pZbOs&y&-UbO7Z9a|*>-BdCf@953J0jAG=Er%@9Qj;V5zzKguP?p44ElE20Dfs z0pbs9f(K#7B)k4vCu%k4)k^_UpT@wYqhR{dHfi5`DI3~jhq{q)a6rsQDJ6YN|BbV% z`T|3vy3em)gcCb1j;$5&IAe&yivRoDyBUO}E04PaVSLeA>GxUs=zbV$A&T4XNSe`+R7Xh?{t2#U08R?YEt=aCnf zG!@mG>7vEzJ~EuHCtP!R77XHeA!?h}tZQ6!U8rhjRam1{Dzg(Hhgl81>SK_zEnS3I z3J{@*Q}-?wBe8fk+>#<$v!pGxfzHq$`{9dAy1?&~-c6Qh6{QY6l`s;Bdf2?o1H0XdPNd04Df>HnhFmR zUk=2Po=z+5@tS~bQrq8Fmxl2w(=ZVG^A2%MuIvPU&xe0;VjhwQ+{(KgV`2W$;>1by6df zut`i~NUc0e*dnCn6c~!+w6{mE#ms3t1xWly99M^H<{5l+xe40un6u(&U1z~$XjGkX zFD#|*UwHikQa1S4wE5M_UJ-x1la=xs-bsn*U!8VdGOzBO<;%azf(7SaoEXym#kQY_ zy~f&GbKm|^T>s}v6mE)O7biqeO8Z}OMLDe)YOTudt3?l;;2!AHImJfpcsst(3EU9F z8Ihf~-`wrMu^QT_b;fqxVlsCZzIOW%Q43u0%G z&a26%kb{n0KuV(b!*%hu{@AtU7ANA^^#{dO$qW{_yJb$3Z?|iuEI{}T7(1!e#qJf_ z2Uc|Z6fK7r)VW*I9pRV24Pq;cI_@h=$DPbsd(o!zBSNs29U+sQH$zuW*{k!h}odcy2!HZI@hL z&V18kDIm?x0Z#g_!i(;_7d+I~Q@I}D{l>WLE|X_I-W!B)u4lWn3#DRoNVgKNcA1-YFT`O8OmEbZn@1fIB zP`2nj*nLUU)|e3O8Q($Norr1?->e`lQ2|q|9i$Qp93sADr;<#=-EyN2sVJ;F$n`m4 zG%M*W=2Y{fiV~LOP@4bj|ICx>@Z^i*8|0Y@j!3rQWR`J8;1U_uH6v3sX~(UU2?;%n z%+)m$kr83es!3Y-+(gXCZpz!=G3~6j`&6*99cm75T9(N3v~sKJM0rS-fZQjGc-!r7 z1|M8^=8oES_1~E47WKx5b(cJ~t(RKu^ADVqA6H$G%{o6lOlH6TY6xYg~1i!X#Jw{O`TmuWPPv)RS2V$r=ZbUETMeFQNpl zbv0gdRnQP(do3_SY5$$)HB3CzYESBABDqB~dR{V)%n_3U4pR2Fn86pHhO~A$BX~0p zL{A8-DO}l*MILaSH@}DE_g@y6B_4DF!m;*s#VxU({(kTRJ$JMv3Ow|GD5*agnt^n1Sh3%cVU7FAyD z?RDC^E@5sqb3RImxlj6FpwZE7L})K+D5^rvQs_NP_=$LQjRDV|pjG5@R2jg8Cj(8- z!bcZQFpd5^#r!y_oKtgZ|A_SXAT+UWFTA3*CDB7-n9Iz0ITR-bJB$< zei&%Lm%7T{{E%@J2w!pmFCe#y(U~A^>pM0J!uaX9PQfJ#2R_@Qm?JBAIPa)JzUWT! z>2NV6gOep~E7kZ}7E(qq=EtZ0Pqj!m6x*Ym@J}(&45S8>T}z90PV;hZ?+#OxQ!7!Vmmv}^tsBp0KX%DG4*r5aO+0J+*iLIp1L_l&(f&zFLXoML_hWAoT* zI2;yNNR#|<5gs$jCsuES6%S37deSDK={mDpdn-%Y4}0Nha5sy7PV0)^Y<^dO4^Zn2 z!HtlfJwFb0cRJ?3=@fL5eTLS}4FI7(*$_NQn{^7tL_)IMG-^*VAGE+-IVMX&?(lZl$PtaR z;ZF|hT4n3KUgCQ{0)p*<4X{E>!k4C**kjb!>|LNH66K7QXi+bbX;`IFE7vMR*KN%? za~L4xrV{!r*@_qx!*GCB*NurTKh!k8iJxvApf9;~Ob#{LhXrbMxL?Fz52k1@GKT)t z#fp_AVOXU&hSujwEDd5qnSy7{S~*j0?$EyZy`-Ja=mNUAkL4amt8~_rh+d8J3ERbd zBB-O(vp8+9)*NbT9)Iw#=i@!%1h~%ChsQ9f7Hp}8=uU53?x+eeQVEl3s*Qm4$zRxs zlKP8Qbv-#5TZzsIIp>Jo3!f zhb$rpqCoSt5$X}NIQj5Zux<*QnTPQorr-hMsj{(&EoufRoYySk`57T9vPNjviCx@7 z*(7n-zI6uvk8%^!-$mD{C3;%q9J}0usFj-j?Jn*B>Wo8Lfo}O$L);jA+laHjqNb3Y z8Fv=<8_P_++3F%IfU*<}9<6Wk`p?n7#hESU=FZrzfnn47;2qU6b6#VlH#OshWzXYR zac%yX5rLWp8LMV#l*|n+&0V$Sd~BVf7VKHa)0}xi^ZMOm2JIxFWv3A-!YymQ-P=(> zVxC+>v0jp7vwrYX1k8;}f@@ci77Jjj`ulO{y2Ls1;kcXm49{;xf6#-7c0w;l_a4Q0 z4{)7m3N7-GB$Th<@h|inJRJhJ6NMW?^%mvXy4?)3RPELxC3Roz1DkXj{wQt zy@V_C3@KsXgq!l~I+J^+N1&gp&=k`;BL}COJ=xCDcCQLqPIKXIeBfbYeZUt0JHCvM zI9jyPJCbS-fzHWW)Z=*9#TT9+}}D=#3inwwAffRTZv^k|{T03OTT{3Sw)~iH3}V zFRw(KB#fwqpD#&R<^q?0K$fRMNv0(08=Hl^!-J22KfQKei8+bwiSFOPH-nDPUVj=_ zg+!fZS)LI$A=vW0Xt12f}hfb zc&3HCS>WK0_qGB(=k;MQ-3yp^ERs|duKp*UM+pJ+XbigZRbGSIKSNRdcW8$hQ z|I)@fy(%%|;y4#|C|qyw_9$^tud8_ER(R4~8TxxLf{BV#1RiSa6PW*#LrEW6)FH4) zZUw^XwARG=r4hF}0wx=zwEl3{&~()%A=Sp@bt#V zaBm=nx1)4cJUTh-DAMnV-j{+dVNZ!(lw)v?z@H=Gghw`a0 z1Pf{hCss#LsZ|HiWeX&FkZrCNv_tKr<=nB#F>TD7mV!bW^kfZp(f{8s-tZZ4gD7` zIV^es)Qb^G=EYSXFm(2V`Ho|S$%A_V{Cr3s7vE%P8)#_-r^{|rs3&O58d}l>gRa<-s^ZZ5f^3&ABW3 z20h1oljd50F;AsVzw8qww{7GoYkmk_P=CcywvAOGgxUy!|Rm1l~gCA1ulh!yND(q~;r$FdtR8O^hS(}tMTfRODZw{*cdtUt2-BIbR)Nri-yFN$v zhxBtDOgM!Bs5w+o0lRQ4#&g%mt87l0@uy|8D?%RXPeCqJIjl?rR;O&&h;(O^lD;2% zzvNeATZ*4PnlPibpa{^2RZ!P?InW73%-_fwdLytNAtVE4fXlom{i_OAn0#hm8{B9P zAiT&j)C_(V>7>?8GJ-40v{bi>|0W8x%1yfA z9yHy8XSqcSw2U42@v3o>UE{h`%DYxJcbmgBp~t6wi~&?GkMzPBjSwKvko)lMCNvRn zFUH8cFqJML8}<$No!8|eJ86Eblf0T5o^dZ0gm`|IH(#7sssELPiQc|HRSic?dsJUV zRylJ}5vUwvZFd)1m*iZh73D%p#*xK!_z66LXwAu2=myu9}p#8#_CdSV#SX zSjEZAIfRT5n-q@BWai?@Lb|+mds!~HT9;~Ny>_)^t%k^N6x*&BP2}`=y6DHVn|Xrp z1Y7vBaT7si*q%P+XP>@Iy_w$Q+Qm1lb7be77&!4+=#dCkSwIK9Mw z_p1k&!I93OrrD1W=y34*qjc3qX@@tGFJaX0WdE{%)RL|AX!Wr;D|5a)WR&HlJ$a^@ ztw|x+Gh&=H5l9nmfCpu}Yf}a1!%`%*frbFwqzhcI*=&_Cis)QXEr`1H70@at6{9}6 z++RDtE;JZcnf%;U3resA+?zICMwo~q8>3#08f}bI+X6lH9RBrmmv9NE7*;iW`O8|+ zXKb5WPP*{&;+euk&?RD~tb>pjbTa97haOS*hZ`HLOy zv#w%`GC^k!sFuWQx05Tis0}`cMee41uR~#>ES8#++R|xo+^TRhe|P@f-YCiikC>Gy zg&Q0QjGHUr?BP}X5>w6U@a;0gqPMrGXNbS6oArzrd>VT4lQP0Ni*{9cPNjl{V>6Y}!ldQmpsz?j1Q5uSi`E?}UGU-nQa z_9Ee>KcrPN4Zcb)t~D}rp068baVgOpb4Cf|53xKKM_?cuaHOifO+~VHKdXZuF^ak= zQ&iz3Lx<@N@T_Ut0Z!vr7+w$v{V(dJcX~MMJc~e!aqTlY=hz9oK+S(;%MKxJEz@|@ z=Ac_8%Lv>?l>w>`TA4C6loffncuqaIHUIQ0YX?gTAoGuYV(BWn4m)r7JaX3{7{0RE zsHA3C8XJ%ukAUka#GK%BGX#Rs%U}+>8L)?4ts%vO!digZa+UP6W>;vrA||9VmDHno z+jLd@$6EPuVJC|7`ungD3iNw%se^Jq?HtKmT@rc67$qD%5JTm+qNLT)D0b^M&7@Jk zaK*e=Dd{q^>M)BG%q`YZAX>Kv18-$NTl{PoksDNj^zORc?kg{Jj2Lg4K^AV7n|~NJY=a`Uca!LcIXTlf^<6y86J0qABJCo2q2qROpUi zKBeT%B^)@vNfLb0Xiie_r58oX^q#rXgl&ZqJYj zLa(2;c;uM9^Kz)i7DmmM5Sm-|`o;TEMQLrvywm^?cYwp`Tva%Qg4|Fe3=t>ogN(q6 z-^Ia7dEc@EF{E7mJ?0d?981bz{g=`D)VIf4ZUuUJZiP4gQOW1MEE~k}`#^1j**J@Z zW((hp-tLM|zqmJhj+MlEhpc@Gc+=87b)4Kn(nR0ujA&rZ> z-?a)xThvq1_dx@UW`Q5N6L~ipm%M$@1P=E~{3*3g4uhomzpOJ$HN$aT`)*iR?$NzX znr4`8d2b!coXmwg6|Uw4(5)4;I+wueJcY;M1Y?}*%aGK>TH0AqGo&UWooS~g#}?o` zoOki?pE;aOzD|yO-T|DM*@!Ok>^{gAU;=0|4S?}@PTg$)5d(&P#l)86{Dc?Th5}4A zB^}KnTk15?^yV70W0$qoYXP-)=2U6bj&Ubq;Nu=oGxQX$A^X-QZ_;ry6ye`t#C@9s z5aUX++P$`EFs|RJvQ_PwL4lSmwv{lf7B%lq)KMY^E_L!K{M1%YUPXSO06w!;50 z-_M~r+DRVXp-15LTL2LM9+{oy!{f&y!Brx9C($Z(0zLdF6`6s}BmO2Oi-TETk8?%c zU3I7wt2zEIJfd((Ff(%gM1aja46h!M3Pp%khdns*V72ZBH_XlQ(GujMiP=s<|NHDz zlFZ%qJJX(y3krJs-;BOZ(UZ4@ev*VvRP9mns?)FYpM*^kzk*xJqr)=Cx#dUHLtS*8 z9FD9CV~Ycve6e#%(27&#C3Ze>kp=&2`UD*`FSgTeTOk1y9KW_c0E0VmEipfxme20( zdx~f88^y+@d=3wSaQsd}ICmKr#Wktu!%Cv-Sg$i!Kon>egu2niJhcV!z?kVwLc)+^ zQf!B6n^)Ptyo}^JXe~L94x{hR%im!;V4R}sqJ%n{)rMdv*vXGYS7e)kiae3K*BuD0 zdSPqBJE1rM#Lw8LiUEh&8)q9`x+`I{n>J8lWH|uc1AbgE5*vvC(QiDvfKXYY&ZHPc z{`K*G_cC?uI1bv(G_f`FJ~#c9cnTtTwD4bEVTZdS+vl@MMbC;-{3TfM6ycSyOYvQP zZk%W_^uUlGIHp;`(c);Z;n#S^+cYe z!$V9JVqAeo`GC9nkZ3JskS{0ee<(Pg!eo{rO}CtZzc>1aaeCI_i_0;px}m79f4e1&JWxpu`5Gb+ z9$ZK=J@ZHJnq5LUakGXbfPz`0e$Fg56nz}p7rghN!;L!{fr`F!{?oJ@akeGje6Zfr z!8I~f-W0?ViV`5oZu8+J@DcebhNc_|UiCqoE%srVd{ky2xQ0!>D8P&SnSh&;e+|oI z#OdE3OJ^)BR6}pA*#^>y?fdbHi1ENU5a&jdy5H++j_iqTXBiFuYY!FlEVsmbO}u@ zZ{%(j+8@3743&u7bQN6O$2-Hs!!*aGvuqyx2x3-#S8DAgU)kktc40~cmWb#SK}MWH(OlVSbq@lK4)PXefcHh7y>vf7TaagPDtzQ_L> zq87h+A0J9;BY|XySUWRTmrqCp5HUkM)i?SKC$r!cr^;A(n;$4MTVYtxCp|Q}$}{I- zkUJ7@+;`4L1S7E9?>>D5BH1_4Pz{w>sfh#L<-zzJS7IxZb~J}ag-@wR9~70)t|{|R zJTo}H|1}wWyuqqdTsL%vPn~AKpy?$vgV6PG0}g~%P+}_G^XYBmHv=0saWemuah!Z} z=fX5`-VOVH#3SwmxFmL*awZ%4qE;HaJAW`p+Jf97`3ArRb~pVur147vwQGx3%mTkt z!sIe~Qeug^IGJtSvR+S8A%k8!jnASP{-R>Ytss66E8{ASZ#n}By~CZ`b=kwNf1M5ST#_6SL&P$QJzy9~C!-^@mPhFYl1YaNhgHtGu8ZpU`W ztv%+;-xeb34BMy>q~(dsU2|Uy9wpoDb^)6srd8lrkUjH2V>VP1h7*VNR8s%bLNjFZ zOH{(Ge#g}@!Dkn5yyT3431<*wmDM(6Rm?lh2RJzOd|T7Rat?@ydV3r~SyMA>6|;WGn&n3+e|kWB7TnbHrH z4<=L6!Tq)s#9E&!s&vVtd;LRxzr0DWxKB}&uym~KE?xpDrtQ;~OIO<%-tgs!j>UcV z3s_2zgeJ{$*S`*^8zbD)#nsu15J)HJFAE03lP;s@QQleJxIIF2+oy=pBglAePdL|P z9zq^WQV&>H$|CEdQgOrRG_W|ejm5D zb@^3~q00h#X}>0-EHW}$r zSuKhZc!mzcdKgGyNC!eJab@2frR{%(wLYmX=X!qXX&QufRhiB(-sT6CEn(>-E^1G3 zaEg1Y_^3qT`@;Henx+bK2C<`YwS*#zL}kb}={IJ-?pC2wg4DiO1C3uCiB>%-u3)_+ zN9OFGtya<1+Xk)Ni>)^gN9@M(J`QEddKb-wl>Zn;W%=KTv{>K z+`IL51HL*VUG&q)vVC#ybW#K#$>lCZ+c2?J)K(~J8&KA+!|`_;wk|ZcQxn-0%2HqT zA&!Pn?{)6a#T@wzCC56NlbzGI zMF`)UNz7?yO<4@|<5>SiDh%72V1s>MVr~pOdW-_03)&k0C!B|3tzB)xVt=Zxn(&xi z%-?KW9hn#1_<`PYN`&D#l%5h0Ah4ZfO+6L#TYnfjbRQPwrJY*qCKmGVoQ??Ef8>;{ zVZ|B@)YYk5n4;zg!6;1}HzHy}mK~dOZ_YY7bp6 zFPcM|LL%6}n4Tdav)+0J-L>7EBL(9hasUvn?PGm{Ouo|)x9D~42s3ovn+Z(Wp zRV*{6wGlEuKm@=FGTO5iIu};vr{^sjU6`hR#EnbE$Blq%7SvK%px{Kf)-Pvrsuk7x zcA~!gFD`Y#6EiG58{w;CymTmW$6}6XpRM!1AnS0wctfCo%q17{k1gZSWWR`wahcsb z?lhp6_-n5D;COHHac?%8isT9)C}R#G51q}Osg6^s+Zf28L3}pyLF_ll`U^mUV!pA@ z!`JtwS0|k9d=~w=jQpz$4w-HmAI($2a9ZTmJReQ1$PPGqA*%W08>Qn>P}8H z-p!osxp&RJugeOxcPUbz_Sg!0H87XnxPs#lIg3tJD$J~21w4<(b9`fquUUda{QT+n z9XFgA$W7}@a~pGjF&rINzH|OZqq-T74Zm{>hh4{9bwu&}g_N*AB&5G=qE^|R=d+OJ z^T0-b|I5sfZcK-S|4bP~OX`G&Xx?!exmNxs)oK9bX!XCN9{v7s=XUI)y_rEb#fwwY z!((Y|h+XmV6{cC}UH&}2L4w}Cefkd~G^xu`o{yUSPijGX2deb(FqgK^Vm=6UwOWw{ ziDe-RtIA5KQ8gu*RLXX3n&6*01KIb6g>hF%OpWD1ICI8VZ{7+FT|fuGIBn{RR)4Oo z3~Bq-`p3%3(#iG!ATlqKga1}zwoWAwLDwSkPWMWJT8Jx$kjs*9Eot4yS#;AAI-BejFu@CG4?%r?5EcP}TJ<6h>ViIP;=-4!qAtxCuJhK!e(`1ZDpUc^%8l`g*8Q zn?f2xC%qRfa^NT6I)} zE>y)c#G_2t0zG@meNlkjvnv~jm^`bF`Q=y!Wtff>XIsG4U};@fb=kj|YusUF4$jZK zoR}YntZ&wEpjR+zx>wuORmUE+SnDmiE|Psk|G;)5E;bs~xwdCmd}Xp>6l~661K|vp z!n%zt&SuVx+hZk~I)L3c3$t(B?G7}b6wk~n1C=X(pxntI23Gy*yFgaj zI?EE0fZbGFx~Zd6N$HD^#H7FSRc~N|J-VeO{Aims(zbaMV`U4`5~*VEqslR;ae%n8 z-YcK6`V4OJSD#6E^uuj^fow2&vi0nQ?4URjrW6qRcp&~+v4s9RUo)+lpY{7^j zi*-OC^5C?JJ~15x2UR_7#KaCU@l0DSIbjh21nV$W&m*c_4U_`#QNz9)OO9cOO>T;I zFw0+0onPRFo;;7bt|`}k)F(UWDsH_E5${MVJxx5bO^1kzsA<~8;r2}KEW^3S*b51rji+LfIETT)VjnzU=Z^yNja793YyJ=Gi3_c3T15xzCc-wEVq62c zIV3i`OKFZuhpk;JZ^Ae)AA&ZTK_eCQnP?(EGY12G)zKOidNm|s0*F^i4f%cyO<^7q zEbSep2;QO9sFgq$u#Mj1q>$p3mWq^V$X|xxu-x2lwyg~XG*h0#DEu@znfZX&7=xP( zQ$32UF^Cfa1{BA2uNP-B;&hJFKh)$uDg%bc;Gf#hP7qg7wp9wO4N5D&Kz#A{ z{H~U@oLXD(CJMI?bCWi6@~L4;AdMS4xjjYxbo39nZ5aiv1^7MW<~(Jdfe?r}+~=`% zFY_E%ar2FnsfhSdA(c9KQDm=`d|h_>A|uNZKGry$3y!y@xVN$qlvAx7@4=lg=w|J( zw(Ir~%vQU&-=_xLOni5}Ps89kcmS+cHC6dH)<;6e3W9;O`%fng|K7Vdr(-O;4LoT#PPcr=34Q-*`eS|?4uzq9Uo$=ZEun=4EE3poU`pW8VN@y#dl%;_wL#V$7o-R(M zY!1&IrR-A>DywxAN4z7m^@BUwdD!#uxCZhnP3VA#Og$K4UM*oZK>G9=3kXIq z3i>M@jVWp%QFSUG(vUJy2lAkaj^uMkGw5aE^?WGHawPCL^sbJ(+!((?FPU|J;v~^ORq$NTqNv+H#pb*-4DDPo&s=jH=^QJL zOnLtlKYziY7rcVqU=@=oLe!(uRDihMDWjFhLn=64e!eFV^O#|Poudjp|D?5X{&kx( zHSJgwiUlw8prNJmFOs%Ds9f~G-1QV5FsHPIkrx}ZI7xQa zWK7@5(YEMQ=0)~!9H1;EPLM47*CCZ!1GFX-BZtGcR5VHxB@onIlaOafq|ofa*-6Tt zf3WBj(L9nsdNl}^AWpbE9S`SkjC30oIBo5PRmG=z!D3YAyxV?>B4{*pa~#;`t@sf? z!?`qv2s>*pb$JOfuN0Jw-Xrc4x;G_JW%nAEqs?7 zRN}0hSI3}@YZm@Mtz{Xrp8<4UPM?lJbTW1fIPZosCAHr ze@Mf*+Aw-^@d+JZM?~S<1tY<32?>jO>Kh%d#?Mh^xtJt=*Nf+ArzMjj_Jwzuf`#qE zneCrGG3lGGNg)E^&pbo0FAV9C9yko=OPLVExk(rYC)j|N`>BOM%lroYwXndcs#&!< zM-5!{$qFY=jGWA~jp%F1BG`#zR<<^Pw5DX}iq=94R`Sr{2w&27a(H5S-*og@N^b4H z@n%3q?u6yCn1q5`fq*;y zC|`r;n0+`d!nG~8y25-WM;HK<(&8^DtwO9ETEw+9FNiuWa9RTNpeo7llr;Kfk5H%> zV^(4^t81G@E_8lYR50jaIR#Z@3KMlEzoxjpp(yESe%u9GOsv+d_%Mao%D;CZ{tkd$ z-D3Hx8|vCfn%%q`z2(@9LxyumtB7M;A)H-T!d-@l#qM()ag@k&&r=rV8m19P%#1~T z6~(&@*ELw!a4H1D{iYac6k8xLr#2D-7f%xeiU_?^6*+Yat z)Y&I!*PozTlF2;lp+{~jl3n&99=@}vm}y5K2rPO#B#5n?A$!thH;BQgh~*H%e)yWh zC!-p29uBlk)$1-o+q|^A*C(sAqsXNmnF6e8RE}c1vAM|f>wvq?T{)#)eFLZJT`%V1 zk7DkhJOw`qE1($3)4_ff>!>)jjV)$8%27$h&*jeKEXc-(D^w($;P;Na6XzrljBzc? zQECfM*&|c;@6TQ$5MiM{h|*b&v5fE1aQ#R^z@-Ttw1QL4iEyui?D_HR*TmYv5u5@K zvI>*9kz-~X;xXAs6DUdK-pA$tXQ<6LBaZxk4h`T)Gvfa{ypbyns9_2I)OxqM&*xbB zxApNp@mTO%F|nHLU6m{oEv{@k<-3q6lCkkn=MZM-V{i;+W5`g)u+}W+UzhBL!^5Qr zT=$QAenmJJusVL_lW44E2W*LnaLR#$Q!xHFo583$&AzT%e=h|kkC-F8Dww_C2Uw)g za;jN}iKGH^-s6AE>OfjS%Z!PNS|u03T#%}d@~x-GIr=YyL{AiEJe77e)LTee$}adi zp4%J3O!V?;OnHx4Bt1kugmnDBCt@G_=Y#vDEHKZnlgi5R@l}bQ+^i@~_`D?U!{8yVHel?L632~Lz7%gG@T+}#%4FWnHjYO znQ4^eoxa+1@Vc?$Npa;ZSw*i-iJu2tCQ z>n{J3Mt2Ms8u2K&3|@7)U!$O!CK9VE1J?wqJ>9DsQAlb!0An&jpOR+!jj|BSN&MLDPbPkSZ0ZW0f_QNDQ6|z`Pk_Mk&3Xc)_G~XtbpCc+ ztEDf>*);?&Ds6~>bZ;zkl#YPQCZpL&r-Rlh{tSJgcK- zowW@K%%_t4i8Dr>UMI$Hzpt=2{3zcAh{GF46CiO+J?aJc-HWT#&nuyKdzPdD7ji4& z-ah5$1QfBFG{EruQ%K{wram2uk|pt(=bqU$d{a2-=OMmU7-$6<@i4W}KARINYS~~# zwAc_sY5uH7d`v`pifcw&h}T6*avi&4+141m?cV`zxJGtg1dKa|8qfEGip`Ahuflzg zeNu*B6yu0~`}~7dU`F0XQRp#a>2EF3u3lsoabSFXTEQ`|WLBKbR`hqf=G+5(L4uKsUm2PM8U*JHx=OF+RAWroG$*lP1E?>%DTG~ z`#g&y8SN3!Ycm$J6ZdPXX~;&q1uSBUaBLvn1 z2Bjg;t_!fX|9nFxxI%4vRP(?23}dF2Y?rFjwQ?RU0?j639e337U{YQF!>nbqwPx*N z8lx~2k6e7O`vPGi@dn!<@$_#jp5%yO{$!)lPh4zxQ^3%QOB9>3{CZjvm(tj(;PJ(o z|Khzyh;U7M7=P2$Z_4nT=f-`o@Zw|N#;#0i+aj8X@hay3>o!IouSl`TpNVenxuyus zxCAyCX?FP@;7|~FEO~T$#vg~z)gJgntxEYX5&zs=QK%CqA@K+XzFGmSzsi7G%^mKF zw6ZXq$X8a71#vH~q-0+P0=vv1J2s<^Ds*4wG%NL&$w&5Vcc5mHCgUz4GdzSi+;zCOyF;9YQleCu z$)A!hT?Wcv-Wu@EI#fr5~aai3K?P8T(=4wAr`z0xz8TP(c!VY>RJu@sat2 z+8sc#O8-?J)LL@?j{0W=)cu_{bfif)U1Jxo--4Eanh6ExmWrEW>Rr=ausZORyLBtO z)kpG=rV^Kx0dylfG{gNsIsJ_s{ZQtHE~J+9o8TYtNu{bJXUB0*(1~Sfz3=FyAfiVB zC6P!w=$$_ZwKDJOITpm$?)rxLvs2opH2;^gT?Vdupb97s3Mnk77tKA3ed-sPc9)xU zc4*IGpL&msYO>o9%tPAdN%3EuZTF}W6&YDAYK^Wk43p^uv40RjnSKQ#fb>+g)g>Wj zG&5+#(~$9I+Z1q6F9K~XA-~*{eg%n$sBGd5;Wr0fEC1*);A|x{2Xqy*h}%g7UKW#F z(#A=X)^Vn<+gXhR%g*%ToH;6BG9C2gQ%Sp!T~+YLgN`HkXY3=YR1S19{Aab9;CMN; zsQCS0m!j}~#ipx>pFl7->@Ko6Pd3pY$s!$ur`YsBykt>G?O$-K9|bv|z%g)65%g!N z`;~!M`A2P0ho6%9IyE%^9ZM9rHv-TrQ*=JbqY(ODZ9A-Jza*DHAwbkt%ub4WBf%E)C+Mx1Cv$I101D% zz%F<*nCTM2Hx9I_I7fVQ$8=PBS%vKmL`<$q9?{@`By1FR?D?{7Z!Q!^n?;*2wwjJ< z5$>{iuYAt1m4nm zo{ZB=+sv?zVA8>stKZ9B`9rRwpjJq$<}gaM!-U-|F#?=pHLAx)rY} z`axYd?e*~a_8*sh;y;o$^|ICvGtSc?f{ZjAR5t!MY{90k+2k*7vkvr9nSFvjhU7ce zOq3EEV+<3&J9R@o|2*&lNQiypw}&BNPF@q#~(Dx$FknsVO| zha6>dW%U74A6hi-@e=7vj8#9kcv&!Z>eC@`;6`Mr`KTPu9~~l2byu>?dk4DZsn9^h zJ%yQ}A%F2dq$S+X^#gjax@F6GF&F^(s+pfyJ^sr}|HDEF&!si!icjZcq86u0&Y=J) zp2K(!J|9ZKWSXw7H65QP)_oH`DCASh5RV3x9r}BLw){!FAo=Mso-Va4SCWukWZJ3# zXUFyHfQIv2ry$MDUvx9_K)?2{V7orhA>E$O#B@Ef7naA!0!oS1wJ8?T31#YIyPH$M zOw++oKH`5Y`_uUHV3Da8)~V_Mz-e@i^pPZFD+!HP;YAD-j7OErl*Iw9cBR^@sqi3k z7l;vxV@eek8P_>qJjBll{r`@Wec&vRwTwtr^qg@vF=NxcSE{zIP!1~L?{|I6KbEZ)2+t_Om` zDBh#Mf22Sv6t@Bi{AKxYCT*dI<=OalQ{tRi3vdkF6V}gSe^(K|!B0-M&g!pY+A5!5 zyRmiN_<}qa4MX7Q}2=^NlCotrxj7eftO&C zLeRouIQUjZM4ODd?dKC)w~eK7jwjWK>w{?3r{ zU@SGB9tL;3_cy`{qMnX-bumZ0f%V}MZ+}Jc)*Xs8 zLO_FXt7-9Im`$L<$)~z2Er}?B039#Ow8c2`K+8YkqDtwtqUo&!K`T{ZpQenqRWg^F zYkwDiqF#fa6jHiK6lz6>!9VLVmmgoTcPEm2`AWQEph!74dXU%bEgJoN*Br{T^h?gA zn4RumK9E{IUOzOmPCen#_uE&!Srw3H{Qn7>Hf6~Yo3;&F4e`O2{BKVGF@=8E(s9G< zLY#7u^+iW~Cd?fTC#flk%=EK5<}`6@DcpTav8RJF=MXg4`?!uCBB$bVhd6Z%q{ey z#i5mQ-MCFT!knNI4mnM@f4Fn*UF~u}-$>SN{3$Z^VDs;QsXN0zguJS^|eCz8ZSIZcvU3oit5?s%K0wq#L1 zRWs8zL2bJjsi2&soOJD%R@`|5v{j_Y@VGo!Z;GZ$+I4s>)UCp3bB|hCr;M7 z?ItV1hjL%B?#jb-b8wwIu|^E2m-mSN3(y^`f@tRZLrtJ z3Jc>uHEgj`uB*#IPPIQ{0V(u8f?1|qS}a8BqdFPq>Ril0#oF3S%5wWCH=)x}rHRU@o3Nb1=KR-#2UlJq%B_S% zbj}JnH#XP1P_F5$Z$rZVwri3MqcgvqDUypow{IhD^MsC?6H9 zj1w9+EidUX(0X=#J589_EN0XP=dtEf4q(l^L|3rc{vS;_@A|F11Lj_f>TLZp`nIJ4 z1X-9P@8PRP1slE0KiD_da*54)eL9bH8i&z2=RJM%o_=uSIQGQ4n)d@_KovsC2>lNF zjlcMxXdSK~lr^o)(>t1A|IOKuk+$5aMxCgX1GWIlcb=H_-2%Ph?V9pbX~=xXq3_y8 z4+A`rEH~uW3YPx%M%M$nvAY-LS}U5Aa)&OZd99AtTHUR%!y3bOjJJv;QOmj1@jyn9Ns<9rR3rR-;In;G@l1%vbqy*x0 zc)g`IcSCx4Ewj>pjVa|&-99$@ei+x9AP3c^CskYZ*2mYElRke2nPpr_8juuLtAo|` z)TN|6APdW8!AXnhwL@MtW?lJ~+60?th)ECOkUB%ifQb!zR`!cag4pR)uxJmx3*BjK)DR z8*SZA#kP6)iF5i4Q_9&`_g=D`Qcg~Qz$T*#&u7s24ATeAC#72HJXxo9 zZgWjhpBw{Wj@R<>vK+f;EV?G=46FwOnE7&-tm`V2ezMF$x&tcZmZ{GIO#6_NGW5X_ zVbjXtEh(qA0j}R&l5%Y1QX3c2=k55#k@;}?X?JR^8Jil%uf!}}9sciM{H<*pxV2B9 z6|T(=abC>o<~_=>BQB-}k#l2qerOj5VZ+fe!8p7k{?K>5x(wtmMB;@k=3{#6>*efq z%t^UlQqE|`QqlQdl)EtHtkc-=fk4F2Xdzv&fpC=5G(MC|x84@YL76sIm!Qi1LI8?} z8pf?Kf^sy)i)6H|66HFzQKflFSh@Hg(nFbL0}8I0Jmo%T2SUN%$(^vxlzQuU-8x})H&IK9*7{sNeehkJLrcpXxPVDj)X%5G( z<{eQftkB~4yH=oH0Qob#8|8q$#C+Q6b3oe@ zXlcB)%o35n&WBP?_qUyLKYo*P0EBLXh#T|Tg(xhEsefOM6*hMso6dd?%W?of-vj{P zz4PmS#^9^uL8%7)u+}Fb_~4VZURCy2Gnq+a<=G14EcyXQ+5iZ2WT)R(mIIOG$m_5~ zuXK5;WC>=wonD^9wsw&pkQK@e z?!lD%GEz>5q=#=%PAubiVEZ6@lvzhgrVhPHeYsib3L4%|Cgs-qo~vH5#MYp$Z-SyI znkyP3a;%ZW;)1DzmQgZ8N0cLJMI3*2%1OdDq2$i8oJz;yT3zH*Ov)ED`mLxp$T2U{URhAyJpf#K(qFkV&Ge1W;%ucpB{n@=Kr)#=|DmvGx zP7k~Um6#{jGR#;=`qvt(M!I=SOLE$hKGO2)Jl+ST94sk!n=y6-a}sDe5a>u`KRM;T za(%2?1cQk{JdhBvB0g3Y&FGG8Hyc;ukQ(wNKw#lRa`YaUij*1WH`jF$tgF-r$k%;#lfInWe8j~fEZ(y9y)i-STA#x)k%1xe`c zM@&bO%<(RM&qKMM7mxB?Wf*Nm=+}nCsBD<+*CB@<-8eVmgN3KUVV0$;e0@3Ea`F~T zkgG+%TZBEey+b?Y+UAk6oDN(siw-xkfhx>2!l~d-cTT!FD{F<25fCvDpQnOzPlTVj@m{^|Ud zlpF18;v743hwkE+eH!Z!)kf%wWiW>!q`sGm7n}?J%9KNi#1KuD5El&egY#^4FC@#! z&EJa3Knfe$C?^=POz_(vS*NmAnj^|3AFs6?a$59^L@rs&(Ef&mLW3zVWUXE49)v&S zO9W#XtQ)kx*E;i|SB-3`AZp6!G7evxIp5zdr;zI{kQ=m0!z$$(y^1LrRtr3rZOB^f zlI67Fc;u24?{Exrn!8M1yKnVFyfWo(w@n2i#z{RiJ_98w$9(ZyjG+3|t4l6AjoYq2 z`AvW1`$KkT^=L^|mYC@oh~VLfxwe28rW{-Dq8zQlF6j$$_|U??Pkks5#r!QnIXn;L)RoQ#pW}?vJR=e3s#m3)9AGyN{Ndc_{?9q* znQ?_Er?)8Dea_6))?HQcjB&Ods?EeU}|qMq^bodCdBPA?$54hcZwOqXt|HZ6EV&JDWWMMi#53#u%ay5kaxRcv@o1qb+0PBqrtcwqMGMT%Ur zUmx-)$B-#WISe%@rDRPAgJT`*cNMn)xdk2QHw)#w%5wH4rJ1bG+zm;}b!&f&^8*8* zSX8j27Bw{wwgdm~_rNtjV~PfARa(oq4COSP@X?gB%W_}F;$ilo#so12d?*(Z?f|mv zu?gQgj`8RvfGXYN(;L!$}ydD z>$!O+K`rKftocL#Si9lU2nHj2w8zVOA9TQ9J$DOMY1QI_@OqS+_%=*E0o$%0XplCM zikB;2aLZFzZ<=x*W8n%Nff5XnBxn@nguTlS5_R3SWMpbdsnW^+3yoF6r2RJK8WZI< z9Nr!6c)P}$IS?6b+Je{7m^NZhepNAvmZ#i_ybn4+OI~wB7LTQ3=d`=P>*YSU;k#AO zSKCgs8z-2Q%kY9q1H00DZtEfuSx)_mQckUqEa#;VkphcT{-%piMjVgnXW>(}EQh20 z)X`3loaq$hzKHc(kv-(Ppt&5^SS?YB9IZ)&8;AbS_$G>}AFSiua5p(E-|zPUCP6 z50u6beAi#9zTD5;9wv+{qpp)isfA=YLq97zS2kJBuyL~}XHJrm6wK-}k7^nK$7MNN z{U}ID$6hEGeef{l?Dm85vK%LtumCrX#5e}rM7Xf7pbJ}+a{jKeT(yUC&MM1k`RtPI zezh7x$JXT+ljS7dRaY(qYlet)JTA)tQI2A>qLkwjTX}XlwVIcuoRr#K*Bo@vZw$19 zvYfR_UL+z|O4Y?>xl8N<$i*rP%W^?A6(@w}3f=!Vj^m`BPG21%jzu_?;j9CmCHH|- z`WAc4nA~Rv9jmRB#*Ae#2LP{AE7*%?MI{6ZN|(%=1?ciA#}A!+%7Myq28pmlBnbv+ zM(W{g^ySN^^w7GPIkN6C+J2Sq?+iB#(i=4J5nCXj(Fzh-Q(|Hq=Zx zt#oz246ml!30%VZ4q;J3H+!y)|B{<{)YqEB#z4!WW>NNMZMu@#QC^CHZ>B<0{&bwhei^A^sgoHl4mcatn5%aJ?05apbt4;()Qx@S4=PLLML0K# z8}KE&PU`^eY0uJJ5z1j=Dt!rAPCAxAM5?mI%b|CR^Z~1ynwhO{ryNjL@U{(L&Z^Dn zGo=|~#bi13o~}nq8GX<tJwBvU5lM5DNd7 zr<^uHy-j3NyWGzAPgc@q@R$nPuz4R`qS~VL^gCV=UF(}?kMedorCcmyn_!_Mik2B_6$WN4KuB==2Ncx0Ap`` zmHY44b$a5h-&F)7H+)0Pa~v2kf$o?eLX8zKXP1jNP^Gh=w`2E%O(}OzAFK9WNXc@w zjdE?d5XqvHgD*MGTUXMk-mOt3WI5jJ#k>#yl5*~0z4cYnCd%1a>3WI2F~u~VU(fhW^gm2wVaqEjcuJTB{B zY|I%CQLcJ6%CR%wPPwivPdT*);GPoPtL0;@R$moKC^?z!b9lfs_sz29Yfuh_a`Dyz zZRb3uG*){#l5{VYd|A$WF2-?KW2FaLiJrveGrSb=hRlk2*-f=_Fe}Qc=BZ z--r8C&R$^BE9J_L-IVjLtq*TBbr`n&ARVf*2BP@G;l(u81m;GEc~QaCE_Vmz;Gisr zp%Qq$_d%KSjFi*fG((JX$fagl8vhd#3-~wrp{Hu(@v^3u3eF_URaJ;0d=%yM%KHSX@F&rSNQ5D?R$67vnR4T0+# zU!fF|T`os@H<{k63qY2SRYyU}l?GOv!Gzkl$Fh=VlczMhhD_S(fy zqOqca&3 zfeK|tKIN)svdd}z#nC-lW36^j4!s2JlockK_jzioHpF{{nj{)82Qkd?*6~@CBZbvM zIc8J7ET?a(Q_AJba^)yjT$a-a#JdhR5amu3Ng@@HA1ur5q@3gUG>BXt5wS=Ju_bZ8 zi7clj8#$<4cfOr+b=?wFl9b~MXWHd-q$u~SvYa3iRih|+}lu28$RVyuF0lcd07su-iIf& z%h_c)jU~}Bx_x8l$?mnK=xMO=66#+Se8S-&RQ+r9h#VBkWNg5wZzZaIHyhE zUW0P(Je2##vK&5}T~3F#?@HI^?c3YYwTaPj&@hj*U8Z_Q%3XI*ZtU9fl-n&)Znl(R z$6wfo0GW=EiUWnk<7yV>hI3O+$1ckCqkkafz%0x8!jyZmY<-}8o1`2Uj;iZx&FNkA z1AWR*@}vH8t@S_P1~X@q<)qSzF|o=ejZc|mnU0IY{F(u}=i9!Q@}v2Y!gP~~bCyYI zbp1PNtdhSldhtFyQkFX%xIslV_y{V!8xk^!E{$EuiX z25+{pbD6H9BNfkLZ+#2Zv~9d=^II_ha1AvPR9r?v#(Pjs$v`%*J5sKMkM+mDr1!xt z%c*7kT1gRJEzWyenpc1MTK*?I4LC`CImH}30j%=M*x!wEHlU!7brP$t&FJx9OGT}; zDNT_r11awGZ6ZlD%#kS966GGJvAP13tDn&OV7JSGp~}laswmvfqp_-+wQhMr=Ke(w z&A^Cd5>5x%X^2ton%uVIx7FW6iaUKT?*l7IO1TsGSkEWRq22o+^$+68SQ)CEMwZhU zc1DA_EbcuhXIK^8XSB^WWjTjll-eW9qMYlDGb8KN+i0w6?qH4e!FIW`P_BHozFn3> zM1PEOCzR!sG1M^*?Z&4E)(k*m3BYDkvcV(g&R-8uZZ5NV;p7mHmbIa?{!$t%Sj-(h zH;=U+eK}dq@^n0&t^d4~t4x#=!}=WuJdrG?gKn?2DmdPYa-C(TB5Kz4mMqtMjP#w% zh!Upd@rV{(`mMYV0Khf4iE?H0Sj*exTz@`|wLIm#>+VgtuP5@c>ey8<7+p)ERB_6w z`RhQPFqkRV9i*J<;-@uB9ue_jJXeXyBwkz*5Q6Uc{Ytz zW&VDUjbbY808n@WMxB}CeYQ_jpd9reIZ8QowsP$`j}|Z9JNa1o0o7$vjuy47UGDyr zJFWLY-+;(+??$<~y{5@5b{EP~qL9`5S5;q8%Ke}(6uu17q?$1IE|ha-%6$>#{C>(+ zcFOVjE?QqMzf+-q#@q)@lWZ)a;d}X5b$NJJJSkhdk>ZpaJ)*56Z0z_|e4k5NSph1k zPi3IoJ87&z8fgb|yzizQKiUkGD^Xt#J$t}kHILPvt?&3=!Rz~2^V3Lwh;n5ubHRk2 zrW|_}ER>V3T2N|%^a$7~cm7(zrh8Yn?d~TE#PqB3K30?tdtaxk6^u?OXN=LdK=bhV zJyo{8u8*;nrgp0;v`i434;F=$(8YIrd&~3NK&41s>XuHQtbkww$?JRS|yZ&7nsS+F$!CNjY*&6*3?K z=yL!uGQIw(b05A6Ypklz%RMR5b-KJPrxjzWuNRIA2C$^0v!rlW$~~zpC&DgomE@xH zDwRz!8*OrAthcj71-qgKM8#aqn2X0N!z=dIPc|F?0LU={^dnlm4+neeQv&v}-umS! z2Zej}L}Ro$r{3o9%mOG^R5_jm z;a&G$HA(V=u0Tzlg}e_QlQKh^a_7v}*8y~hjkfIy-idN5>~dEVS6@yCWMj@sIe=rboO7d8@;=z9 z&_8E}mr~AfJ#L#)!K+7-FYH8SeLH-c0^0@C3aWAvB{~XKyUD2E~qccP_085L{0-Y6oXd#mm?$7Pw21B=NcPTmcRESDc=32>>-TwhK*55>VFsF3`-Hda}asUByX`#vop$Y&BNw=g3S`_7EWlWeUr|n}aC^XXuMPo^Hok?Q+`A3o-4L zkLwZT3uw-TF~K6s=@NFNvjPe#t0QIqz6_3=LJq8x9xDCMFpJirt$r>#O_KGyS7 z?u)}-oyU}(*vYEyGA&Jn;CFvUAFEo(43C026}~D$If~C19i|&(@jl%l%gvfs1%(z! zDYukxSq=DTtb2bmC1oFH328sMMy zvxZW9g(%mwy(M(LmpbTH%ITk#*^~f4l#AH6>yO5|&uuMk{Zt@0O(j3~!H*JDbi&++ zJ(RN^V04`31^Z$ls82CJFV1F{)1d_;4Yj|^i0$kZN{NqN3Mqk+ax%1=7eEQjMJUH$ z_)u=w84r)RYzMq9M|WIu)W)&h#L= zHWt2k*9Xk)C?QifZQBXu&S#g?>FK_+rOhALk&~r9P_)4dq+?K*lpDGLs*B7dd6_gYm3^seikgtl5PKwk*tvaeY7Z97c9Nja3IDyB!iG zEwxD&9J$ApHOoikr2Vp-xXs~UcwbbOLl-PR)z?R3wGNO#>qC@-WZ5pnv1?eAJE2_; zagA~y|G;uZ8%rjUBh0CmLthb74TbKH)96!_gJ+lJpkioKN3VP9G*D>`XNJLng!LHhTQ#H$s#et#*OyzG&;aXy6FERzL; z^;}$6HZ5#~()P}|F+9%C*28Q0j?%4W5CwApaKD@>)sAb0oL!bvN`B9d)FH66Fhkv0%LNoqJ0OV4R9kgtIhJz_y%?jm^HjLOfUb((p zdCI}%5`U(8>N%N`^IHsHSnHu@L6i=WHMh=S;@^GIMjM-g)meAGnbB z!9R?0RcKj-n22C&Ma0S*j8o?@eJAL3vM7h4CDk!ct5^{w!)t+2t(NjyvK;(_XKJ+J zI69ijC7W{d!8B0rIkWZU2>cB*)*|`;SUB{0XA|b+aKY-GhCn%KWQyvUEhNkN$H;R0 zw%GOM6J5GTO@@PnMuK zny0bif)USDlLRs-CoE$Li;X#YK@CAg!);a>V(nl6%RF;Fg*0=gshnD4RbwMJIf(mN z516|EfDH&9<0cg`KHK=TN;&jwgE*kG>Z%rYtod`O`wV5dLXACQp3uV78 zkO}dmyn$zlj&}Nh&QF>KQ!bVOVRAzBH=F|+lAQld(H0)@#=i>bca^ZX>kWFOEJu4U>1pLTGKb2Q zU)K<{x$&$0>f4kHw@G4m;c)jKl@S@mPf9s^6QQN0@!@SUHAsJzRF&+b$QiQH8titt zwy-SMkuYbd=ty+ETm*97OI{BwC`Xt{Qbr-_deU1NbeF2SFyq)_>Jc^Cp-NK@Kx#%N zDJN|X9zi+Xd!eXi1jVx`y%QN?a*j6aQj^>fSx)y#0OwA%6|^~pM_kjY3B}J%IXWIH zF(oCIK6CTsJFnLRzgJY26G=^td8p8UF1bVH5ld|NjVTNGW)-9(|E}`ePxllg2Er@iCg!844vY5C`UP# z3;LOJc9R?{3BMh}7&N>3atg|AS+F8x`FE6)$kWZL9Km9=6y>PUbVScEKF}%#Ly~eB zu3Cz695ZSP`k*m^hZr|oPuD5O;*_&tZdxtRFECDDgX7_S5aiI=l{Sf19nFdg)!>bl*y=!ykW4ttc=<#Of+x-R`}Z41NsQGR#Lq=J_*J`tkFdAzJWVVLk6BnUw7goq zo$G~K4r39rx^jCd*3!C%_CQXf*XR*>{8WmvwAi!8;&9uXmFEVFCr2R{ZxX79JhYuK zngiAH!{VG;ksjo1l?}MQVC141^iU`?^rUg>9eHk|VY}g2L21hAK;%$J5*uZCmxXOa z@{|eVqGpZU`lq6t3eSpa_7LwE7yg?Ti0>#5xR?40nh>+j8qa9qpjFO2nw+vr$gxAr z>Y%SAWU95}z;i80k`)hXSz=ujXPj+V#Vaipnaer!(a3+^X&^g|*Ks=GyDW@sq!M%l z&Gn!9BW{Zz1bF}>kP3PrO_he~ef9shcSXyM>M(RhZD%@u*v+;5bf5eL`~P2f!;$3U z7M2SEQo4(hV27kcmgQqvmZ(KDj#a7mzsH}`aNPyvdVNgIA<8kV<{bJI4wUsa^we*qZ!51V!hUm1aDpSu*mv-lk<)4Y=3ZDeRR+#CfSvv{%=& zuUNK1u^2@&B1CCKs3KU!0nG=*LEMwekvgsLH_e6q)W8S%B23bEjD1213q=D2qy9CCJvc zLdF~aoUt5qARFu8I4Q>;m@VvinsO&YIYhpslo6$!OE4RqEogD0NvD$Pq%yDr zM%l9HK^BvFHO))|-T4ONSQ&6qHu);U1EpM0GWPpZD(tOI5<4Tx;SNg*0s!@)#Sf{T zt@m*iTn_#y7_kQ@+)PJkGAve!EovfxN4CfO?H&>Dy;?$GqbvQehXugdx@JXu6%b-c zP85S=WMSgYZS^|R6UgR*RUAS)QxUZx9@Dg6*0;D$-Zpz|pK09$rWTGhQ5&s~Nwp;D z%HM$l0^Nji$VlfcR&iap4yyNsy41Hp-((n7GR&57+fl*zv?!IS>IGcC#FFHcgm^Cfl?_rW8&Z|Q6NCM?O}88x0H%0=R`}jpRmyevU-e1StY*wsy()25zt*;_2jG;C>hNi-4Q4Xl?5xgk8#)=kVN2^*nRQPSLp?pG0wKSb&12cLz z{p$U}?O)HW zGsVXei&WTfKP-FH!*FC*M?P+Cm2(?zGZHHo-N;f^Bc9!Dl&gP2=QihdvU^T^=OsA; zfer^cb`$!^{5Q84Z!yGMxF2uo)}b=l_PiX%&*;RyB37>oohoQ@*2#bJ)} zyb}Z&qrQFy7be{@@o39H|(x9ZrixW5#+$_*zKvU3 zsh5~1bIV*9H{O?+71@~7D}Jn+%<2O@^Rj?8vU;K17Gi(3kJdT*Cz5z^h_6xFq7KFj z+E%({@f@9cx{53&VcCwOo#;Zji_YlPk_*_5Bsmv12Qz%3+(j4t@g%IVay~lB_PkJT zXOhbTx#)Bx&%+HlpS5|R+;QC@C+V~=Ur=5_AAi$ARpfl!T_|_aMHgLkq1;6mUG&$` Ye>zmI6eA=8H2?qr07*qoM6N<$g51tae*gdg literal 0 HcmV?d00001 diff --git a/lib/core/locator.dart b/lib/core/locator.dart index 682e4c7..9d495e9 100644 --- a/lib/core/locator.dart +++ b/lib/core/locator.dart @@ -1,6 +1,7 @@ import 'package:aman_kassa_flutter/core/services/BankService.dart'; +import 'package:aman_kassa_flutter/core/services/ForteService.dart'; import 'package:aman_kassa_flutter/core/services/DataService.dart'; import '../core/services/DbService.dart'; @@ -35,5 +36,7 @@ class LocatorInjector { locator.registerLazySingleton(() => DataService()); _log.d('Initializing BankService Service'); locator.registerLazySingleton(() => BankService()); + _log.d('Initializing Forte Service'); + locator.registerLazySingleton(() => ForteService()); } } \ No newline at end of file diff --git a/lib/core/models/close_day_data.dart b/lib/core/models/forte/close_day_data.dart similarity index 97% rename from lib/core/models/close_day_data.dart rename to lib/core/models/forte/close_day_data.dart index 15c91b0..1e0da16 100644 --- a/lib/core/models/close_day_data.dart +++ b/lib/core/models/forte/close_day_data.dart @@ -1,7 +1,7 @@ import 'package:aman_kassa_flutter/core/models/transaction_item.dart'; -import 'halyk/halyk_close_day_dao.dart'; +import 'forte_close_day_dao.dart'; class CloseDayData { final String title; diff --git a/lib/core/models/forte/forte_close_day_dao.dart b/lib/core/models/forte/forte_close_day_dao.dart new file mode 100644 index 0000000..31481ee --- /dev/null +++ b/lib/core/models/forte/forte_close_day_dao.dart @@ -0,0 +1,182 @@ +/// result : {"code":"0","description":"Successfully completed"} +/// transactions : {"transaction":[{"type":"PAYMENT","instrument":"CARD","amount":"6000","terminalId":"123321","operationDay":"4","transactionNumber":"69","instrumentSpecificData":{"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"}},{"type":"REFUND","instrument":"CARD","amount":"4500","terminalId":"123321","operationDay":"4","transactionNumber":"70","instrumentSpecificData":{"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"},"parentTransaction":{"terminalId":"123321","operationDay":"4","transactionNumber":"69"}}]} +/// closeDayResults : {"reconciliationResult":[{"hostResultCode":"000","hostResultDescription":"Success","terminalExternalId":"example_terminal_id"}]} + +class ForteCloseDayDao { + ResultBean result; + TransactionsBean transactions; + CloseDayResultsBean closeDayResults; + + ForteCloseDayDao({ this.result, this.closeDayResults, this.transactions}); + + static ForteCloseDayDao fromMap(Map map) { + if (map == null) return null; + ForteCloseDayDao forteCloseDayDaoBean = ForteCloseDayDao(); + forteCloseDayDaoBean.result = ResultBean.fromMap(map['result']); + forteCloseDayDaoBean.transactions = TransactionsBean.fromMap(map['transactions']); + forteCloseDayDaoBean.closeDayResults = CloseDayResultsBean.fromMap(map['closeDayResults']); + return forteCloseDayDaoBean; + } + + Map toJson() => { + "result": result, + "transactions": transactions, + "closeDayResults": closeDayResults, + }; +} + +/// reconciliationResult : [{"hostResultCode":"000","hostResultDescription":"Success","terminalExternalId":"example_terminal_id"}] + +class CloseDayResultsBean { + List reconciliationResult; + + static CloseDayResultsBean fromMap(Map map) { + if (map == null) return null; + CloseDayResultsBean closeDayResultsBean = CloseDayResultsBean(); + closeDayResultsBean.reconciliationResult = List()..addAll( + (map['reconciliationResult'] as List ?? []).map((o) => ReconciliationResultBean.fromMap(o)) + ); + return closeDayResultsBean; + } + + Map toJson() => { + "reconciliationResult": reconciliationResult, + }; +} + +/// hostResultCode : "000" +/// hostResultDescription : "Success" +/// terminalExternalId : "example_terminal_id" + +class ReconciliationResultBean { + String hostResultCode; + String hostResultDescription; + String terminalExternalId; + + static ReconciliationResultBean fromMap(Map map) { + if (map == null) return null; + ReconciliationResultBean reconciliationResultBean = ReconciliationResultBean(); + reconciliationResultBean.hostResultCode = map['hostResultCode']; + reconciliationResultBean.hostResultDescription = map['hostResultDescription']; + reconciliationResultBean.terminalExternalId = map['terminalExternalId']; + return reconciliationResultBean; + } + + Map toJson() => { + "hostResultCode": hostResultCode, + "hostResultDescription": hostResultDescription, + "terminalExternalId": terminalExternalId, + }; +} + +/// transaction : [{"type":"PAYMENT","instrument":"CARD","amount":"6000","terminalId":"123321","operationDay":"4","transactionNumber":"69","instrumentSpecificData":{"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"}},{"type":"REFUND","instrument":"CARD","amount":"4500","terminalId":"123321","operationDay":"4","transactionNumber":"70","instrumentSpecificData":{"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"},"parentTransaction":{"terminalId":"123321","operationDay":"4","transactionNumber":"69"}}] + +class TransactionsBean { + List transaction; + + static TransactionsBean fromMap(Map map) { + if (map == null) return null; + TransactionsBean transactionsBean = TransactionsBean(); + transactionsBean.transaction = List()..addAll( + (map['transaction'] as List ?? []).map((o) => TransactionBean.fromMap(o)) + ); + return transactionsBean; + } + + Map toJson() => { + "transaction": transaction, + }; +} + +/// type : "PAYMENT" +/// instrument : "CARD" +/// amount : "6000" +/// terminalId : "123321" +/// operationDay : "4" +/// transactionNumber : "69" +/// instrumentSpecificData : {"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"} + +class TransactionBean { + String type; + String instrument; + num amount; + int terminalId; + int operationDay; + int transactionNumber; + InstrumentSpecificDataBean instrumentSpecificData; + + static TransactionBean fromMap(Map map) { + if (map == null) return null; + TransactionBean transactionBean = TransactionBean(); + transactionBean.type = map['type']; + transactionBean.instrument = map['instrument']; + transactionBean.amount = map['amount']; + transactionBean.terminalId = map['terminalId']; + transactionBean.operationDay = map['operationDay']; + transactionBean.transactionNumber = map['transactionNumber']; + transactionBean.instrumentSpecificData = InstrumentSpecificDataBean.fromMap(map['instrumentSpecificData']); + return transactionBean; + } + + Map toJson() => { + "type": type, + "instrument": instrument, + "amount": amount, + "terminalId": terminalId, + "operationDay": operationDay, + "transactionNumber": transactionNumber, + "instrumentSpecificData": instrumentSpecificData, + }; +} + +/// authorizationCode : "000000" +/// rrn : "1234567890" +/// cardholderName : "IVAN IVANOV" +/// maskedPan : "123456******7890" + +class InstrumentSpecificDataBean { + String authorizationCode; + String rrn; + String cardholderName; + String maskedPan; + + static InstrumentSpecificDataBean fromMap(Map map) { + if (map == null) return null; + InstrumentSpecificDataBean instrumentSpecificDataBean = InstrumentSpecificDataBean(); + instrumentSpecificDataBean.authorizationCode = map['authorizationCode']; + instrumentSpecificDataBean.rrn = map['rrn']; + instrumentSpecificDataBean.cardholderName = map['cardholderName']; + instrumentSpecificDataBean.maskedPan = map['maskedPan']; + return instrumentSpecificDataBean; + } + + Map toJson() => { + "authorizationCode": authorizationCode, + "rrn": rrn, + "cardholderName": cardholderName, + "maskedPan": maskedPan, + }; +} + +/// code : "0" +/// description : "Successfully completed" + +class ResultBean { + int code; + String description; + + ResultBean({this.code, this.description}); + + static ResultBean fromMap(Map map) { + if (map == null) return null; + ResultBean resultBean = ResultBean(); + resultBean.code = map['code']; + resultBean.description = map['description']; + return resultBean; + } + + Map toJson() => { + "code": code, + "description": description, + }; +} \ No newline at end of file diff --git a/lib/core/models/forte/forte_post_session.dart b/lib/core/models/forte/forte_post_session.dart new file mode 100644 index 0000000..b6bbb67 --- /dev/null +++ b/lib/core/models/forte/forte_post_session.dart @@ -0,0 +1,85 @@ +import 'package:intl/intl.dart'; + +class FortePosSession { + const FortePosSession( + {this.login, + this.token, + this.serverTime, + this.tokenTimeout, + this.result}); + + final String login; + final String token; + final DateTime serverTime; + final int tokenTimeout; + final ResultBean result; + + static FortePosSession fromJson(Map data) => FortePosSession( + login: data['login'], + token: data['token'], + result: ResultBean.fromMap(data['result']), + serverTime: data['ServerTime'] != null + ? new DateFormat("dd.MM.yyyy HH:mm:ss ZZZ").parse(data['ServerTime']) + : null, + tokenTimeout: data['TokenTimeout']); + + Map toJson() => { + "login": login, + "token": token, + "ServerTime": serverTime != null + ? DateFormat("dd.MM.yyyy HH:mm:ss ZZZ").format(serverTime) + : null, + "TokenTimeout": tokenTimeout, + "result": result?.toJson(), + }; +} + +/// ServerTime : "25.06.2021 13:18:00 GMT+06:00" +/// ResultCode : "040" +/// ResultStr : "Unknown operator login. Check the correctness of the data or contact support." +/// Response : {"Code":"040","Description":"Unknown operator login. Check the correctness of the data or contact support."} + +class ResultBean { + String ServerTime; + String ResultCode; + String ResultStr; + ResponseBean Response; + + static ResultBean fromMap(Map map) { + if (map == null) return null; + ResultBean resultBean = ResultBean(); + resultBean.ServerTime = map['ServerTime']; + resultBean.ResultCode = map['ResultCode']; + resultBean.ResultStr = map['ResultStr']; + resultBean.Response = ResponseBean.fromMap(map['Response']); + return resultBean; + } + + Map toJson() => { + "ServerTime": ServerTime, + "ResultCode": ResultCode, + "ResultStr": ResultStr, + "Response": Response, + }; +} + +/// Code : "040" +/// Description : "Unknown operator login. Check the correctness of the data or contact support." + +class ResponseBean { + String Code; + String Description; + + static ResponseBean fromMap(Map map) { + if (map == null) return null; + ResponseBean responseBean = ResponseBean(); + responseBean.Code = map['Code']; + responseBean.Description = map['Description']; + return responseBean; + } + + Map toJson() => { + "Code": Code, + "Description": Description, + }; +} diff --git a/lib/core/models/forte/forte_response_dao.dart b/lib/core/models/forte/forte_response_dao.dart new file mode 100644 index 0000000..986c33a --- /dev/null +++ b/lib/core/models/forte/forte_response_dao.dart @@ -0,0 +1,155 @@ +/// result : {"code":"0","description":"Successfully completed","hostResponse":{"code":"0","description":"Successfully completed"}} +/// transaction : {"terminalId":"123321","operationDay":"4","transactionNumber":"69","instrumentSpecificData":{"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"}} + +class ForteResponse { + ResultBean result; + TransactionBean transaction; + + ForteResponse({this.result, this.transaction}); + + static ForteResponse fromMap(Map map) { + if (map == null) return null; + ForteResponse forteResponseBean = ForteResponse(); + forteResponseBean.result = ResultBean.fromMap(map['result']); + forteResponseBean.transaction = TransactionBean.fromMap(map['transaction']); + return forteResponseBean; + } + + Map toJson() => + { + "result": result, + "transaction": transaction, + }; +} + +/// terminalId : "123321" +/// operationDay : "4" +/// transactionNumber : "69" +/// instrumentSpecificData : {"authorizationCode":"000000","rrn":"1234567890","cardholderName":"IVAN IVANOV","maskedPan":"123456******7890"} + +class TransactionBean { + int terminalId; + int operationDay; + int transactionNumber; + InstrumentSpecificDataBean instrumentSpecificData; + + static TransactionBean fromMap(Map map) { + if (map == null) return null; + TransactionBean transactionBean = TransactionBean(); + transactionBean.terminalId = map['terminalId']; + transactionBean.operationDay = map['operationDay']; + transactionBean.transactionNumber = map['transactionNumber']; + transactionBean.instrumentSpecificData = InstrumentSpecificDataBean.fromMap(map['instrumentSpecificData']); + return transactionBean; + } + + Map toJson() => + { + "terminalId": terminalId, + "operationDay": operationDay, + "transactionNumber": transactionNumber, + "instrumentSpecificData": instrumentSpecificData, + }; +} + +/// authorizationCode : "000000" +/// rrn : "1234567890" +/// cardholderName : "IVAN IVANOV" +/// maskedPan : "123456******7890" + +class InstrumentSpecificDataBean { + String authorizationCode; + String rrn; + String cardholderName; + String maskedPan; + + static InstrumentSpecificDataBean fromMap(Map map) { + if (map == null) return null; + InstrumentSpecificDataBean instrumentSpecificDataBean = InstrumentSpecificDataBean(); + instrumentSpecificDataBean.authorizationCode = map['authorizationCode']; + instrumentSpecificDataBean.rrn = map['rrn']; + instrumentSpecificDataBean.cardholderName = map['cardholderName']; + instrumentSpecificDataBean.maskedPan = map['maskedPan']; + return instrumentSpecificDataBean; + } + + Map toJson() => + { + "authorizationCode": authorizationCode, + "rrn": rrn, + "cardholderName": cardholderName, + "maskedPan": maskedPan, + }; +} + +/// code : "0" +/// description : "Successfully completed" +/// hostResponse : {"code":"0","description":"Successfully completed"} + +class ResultBean { + dynamic code; + String description; + HostResponseBean hostResponse; + ErrorResponseBean errorData; + + ResultBean({this.code, this.description}); + + static ResultBean fromMap(Map map) { + if (map == null) return null; + ResultBean resultBean = ResultBean(); + resultBean.code = map['code']; + resultBean.description = map['description']; + resultBean.hostResponse = HostResponseBean.fromMap(map['hostResponse']); + resultBean.errorData = ErrorResponseBean.fromMap(map['errorData']); + return resultBean; + } + + Map toJson() => + { + "code": code, + "description": description, + "hostResponse": hostResponse, + "errorData": errorData, + }; +} + +/// code : "0" +/// description : "Successfully completed" + +class HostResponseBean { + dynamic code; + String description; + + static HostResponseBean fromMap(Map map) { + if (map == null) return null; + HostResponseBean hostResponseBean = HostResponseBean(); + hostResponseBean.code = map['code']; + hostResponseBean.description = map['description']; + return hostResponseBean; + } + + Map toJson() => + { + "code": code, + "description": description, + }; +} + +class ErrorResponseBean { + dynamic code; + String description; + + static ErrorResponseBean fromMap(Map map) { + if (map == null) return null; + ErrorResponseBean errorResponseBean = ErrorResponseBean(); + errorResponseBean.code = map['code']; + errorResponseBean.description = map['description']; + return errorResponseBean; + } + + Map toJson() => + { + "code": code, + "description": description, + }; +} \ No newline at end of file diff --git a/lib/core/models/halyk/close_day_data.dart b/lib/core/models/halyk/close_day_data.dart new file mode 100644 index 0000000..f2ee834 --- /dev/null +++ b/lib/core/models/halyk/close_day_data.dart @@ -0,0 +1,54 @@ + +import 'package:aman_kassa_flutter/core/models/transaction_item.dart'; + +import 'halyk_close_day_dao.dart'; + +class CloseDayData { + final String title; + final num totalAmount; + final int totalCount; + final num paymentAmount; + final int paymentCount; + final num refundAmount; + final int refundCount; + final num cancelAmount; + final int cancelCount; + + final List items; + CloseDayData({ + this.title, + this.items, + this.totalAmount, this.totalCount, + this.paymentAmount, this.paymentCount, + this.refundAmount, this.refundCount, + this.cancelAmount, this.cancelCount + }); + + static CloseDayData fromJson(Map json) { + return CloseDayData( + title: json['title'], + totalAmount: json['totalAmount'], + totalCount: json['totalCount'], + paymentAmount: json['paymentAmount'], + paymentCount: json['paymentCount'], + refundAmount: json['refundAmount'], + refundCount: json['refundCount'], + cancelAmount: json['cancelAmount'], + cancelCount: json['cancelCount'], + items: (json['items'] as List).map((e) => TransactionBean.fromMap(e)).toList(), + ); + } + Map toJson() => + { + 'title': title, + 'totalAmount': totalAmount, + 'totalCount': totalCount, + 'paymentAmount': paymentAmount, + 'paymentCount': paymentCount, + 'refundAmount': refundAmount, + 'refundCount': refundCount, + 'cancelAmount': cancelAmount, + 'cancelCount': cancelCount, + 'items': items.map((e) => e.toJson()).toList(), + }; +} \ No newline at end of file diff --git a/lib/core/models/halyk/halyk_post_session.dart b/lib/core/models/halyk/halyk_post_session.dart index 6ad3b2b..48a7453 100644 --- a/lib/core/models/halyk/halyk_post_session.dart +++ b/lib/core/models/halyk/halyk_post_session.dart @@ -22,6 +22,16 @@ class HalykPosSession { ? new DateFormat("dd.MM.yyyy HH:mm:ss ZZZ").parse(data['ServerTime']) : null, tokenTimeout: data['TokenTimeout']); + + Map toJson() => { + "login": login, + "token": token, + "ServerTime": serverTime != null + ? DateFormat("dd.MM.yyyy HH:mm:ss ZZZ").format(serverTime) + : null, + "TokenTimeout": tokenTimeout, + "result": result?.toJson(), + }; } /// ServerTime : "25.06.2021 13:18:00 GMT+06:00" diff --git a/lib/core/route_names.dart b/lib/core/route_names.dart index 4ea3704..68c2536 100644 --- a/lib/core/route_names.dart +++ b/lib/core/route_names.dart @@ -10,6 +10,7 @@ const String SettingsViewRoute = "SettingsViewRoute"; const String QrViewRoute = "QrViewRoute"; const String BankViewRoute = "BankViewRoute"; const String BankSettingViewRoute = "BankSettingViewRoute"; +const String ForteSettingViewRoute = "ForteSettingViewRoute"; const String SettingsPrinterRoute = "SettingsPrinterRoute"; diff --git a/lib/core/router.dart b/lib/core/router.dart index dffa6c9..831d5af 100644 --- a/lib/core/router.dart +++ b/lib/core/router.dart @@ -1,5 +1,6 @@ +import 'package:aman_kassa_flutter/views/bank_setting/forte_setting_view.dart'; import 'package:flutter/material.dart'; -import 'package:aman_kassa_flutter/core/models/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/close_day_data.dart'; import 'package:aman_kassa_flutter/views/bank_setting/bank_setting_view.dart'; import 'package:aman_kassa_flutter/views/bank_view/bank_view.dart'; import 'package:aman_kassa_flutter/views/check/image_show_container.dart'; @@ -71,6 +72,11 @@ Route generateRoute(RouteSettings settings) { routeName: settings.name, viewToShow: BankSettingView(), ); + case ForteSettingViewRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: ForteSettingView(), + ); case QrViewRoute: ImageShowModel data = settings.arguments as ImageShowModel; return _getPageRoute( diff --git a/lib/core/services/ApiService.dart b/lib/core/services/ApiService.dart index 55f734e..230202f 100644 --- a/lib/core/services/ApiService.dart +++ b/lib/core/services/ApiService.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:aman_kassa_flutter/core/base/base_service.dart'; import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart'; import 'package:aman_kassa_flutter/redux/state/user_state.dart'; import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/views/login/login_view.dart'; @@ -79,6 +80,18 @@ class ApiService extends BaseService { return HalykPosSession.fromJson(jsonDecode(response)); } + Future fortePosToken(String token, login, password) async { + String salt = '!=uF:w1N_Salh?1gVSJ#eGfJYHA(wS4D'; + String hash = md5.convert(utf8.encode('$login$salt')).toString(); + print(hash); + + Map requestBody = {'login': login, 'hash': hash}; + //var response = await requestFormData('/halykpos/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); + var response = await requestFormData('/fortepos/test/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); + print(response); + return FortePosSession.fromJson(jsonDecode(response)); + } + Future> money(String token) async { Map requestBody = {'api_token': token}; var response = await requestFormData('/money', requestBody); diff --git a/lib/core/services/BankService.dart b/lib/core/services/BankService.dart index fbfac7b..6fc8a60 100644 --- a/lib/core/services/BankService.dart +++ b/lib/core/services/BankService.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:aman_kassa_flutter/core/base/base_service.dart'; import 'package:aman_kassa_flutter/core/locator.dart'; -import 'package:aman_kassa_flutter/core/models/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/close_day_data.dart'; import 'package:aman_kassa_flutter/core/models/halyk/halyk_close_day_dao.dart' as Cd; import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart' as Ps; import 'package:aman_kassa_flutter/core/models/halyk/halyk_response_dao.dart'; @@ -15,6 +15,7 @@ import '../models/aman_dao.dart'; class BankService extends BaseService { int sdkVersion = 27; + String packageName = 'ru.m4bank.softpos.halyk'; final ApiService _api = locator(); final MethodChannel _channel = MethodChannel('channel:com.amanKassa/bank'); @@ -45,7 +46,10 @@ class BankService extends BaseService { Future closeDay({ String token}) async { try { - String response = await _channel.invokeMethod("closeDay", {'token': token }); + String response = await _channel.invokeMethod("closeDay", { + 'token': token, + 'packageName': packageName + }); log.i(response); Cd.HalykCloseDayDao dao = Cd.HalykCloseDayDao.fromMap(json.decode(response)); return dao; @@ -60,7 +64,11 @@ class BankService extends BaseService { double total = amount * 100; log.i('total: $total, ${total.toInt()}'); - String response = await _channel.invokeMethod("pay", {'amount': total.toInt(), 'token': token }); + String response = await _channel.invokeMethod("pay", { + 'amount': total.toInt(), + 'token': token, + 'packageName': packageName + }); log.i(response); HalykResponse dao = HalykResponse.fromMap(json.decode(response)); return dao; @@ -73,7 +81,12 @@ class BankService extends BaseService { Future refund({double amount, String token, int terminalId, int operDay, int transNum }) async { try { String response = await _channel.invokeMethod("refund", { - 'amount': amount.toInt(), 'token': token , 'terminalId': terminalId, 'operDay': operDay, 'transNum': transNum + 'amount': amount.toInt(), + 'token': token, + 'terminalId': terminalId, + 'operDay': operDay, + 'transNum': transNum, + 'packageName': packageName }); HalykResponse dao = HalykResponse.fromMap(json.decode(response)); return dao; @@ -86,7 +99,11 @@ class BankService extends BaseService { Future reversal({ String token, int terminalId, int operDay, int transNum }) async { try { String response = await _channel.invokeMethod("reversal", { - 'token': token , 'terminalId': terminalId, 'operDay': operDay, 'transNum': transNum + 'token': token, + 'terminalId': terminalId, + 'operDay': operDay, + 'transNum': transNum, + 'packageName': packageName }); log.i(response); HalykResponse dao = HalykResponse.fromMap(json.decode(response)); diff --git a/lib/core/services/ForteService.dart b/lib/core/services/ForteService.dart new file mode 100644 index 0000000..be1e5a1 --- /dev/null +++ b/lib/core/services/ForteService.dart @@ -0,0 +1,160 @@ +import 'dart:convert'; +import 'package:aman_kassa_flutter/core/base/base_service.dart'; +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/forte/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_close_day_dao.dart' as Cd; +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart' as Ps; +import 'package:aman_kassa_flutter/core/models/forte/forte_response_dao.dart'; +import 'package:aman_kassa_flutter/core/models/transaction_item.dart'; +import 'package:aman_kassa_flutter/core/services/ApiService.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; + +import '../models/aman_dao.dart'; + + +class ForteService extends BaseService { + int sdkVersion = 27; + String packageName = 'kz.forte.pos'; + final ApiService _api = locator(); + final MethodChannel _channel = MethodChannel('channel:com.amanKassa/bank'); + + + Future version() async { + String result; + try { + result = await _channel.invokeMethod('version'); + } catch (e, stack) { + log.e("ForteService", e, stack); + result = '0'; + } + log.i(result); + return int.parse(result) ?? 0; + } + + Future renewToken({String token, String login, String password}) async { + Ps.FortePosSession result; + try { + result = await _api.fortePosToken(token, login, password); + } catch (e, stack) { + log.e("ForteService", e, stack); + } + return result; + } + + + + Future closeDay({ String token}) async { + try { + String response = await _channel.invokeMethod("closeDay", { + 'token': token, + 'packageName': packageName + }); + log.i(response); + Cd.ForteCloseDayDao dao = Cd.ForteCloseDayDao.fromMap(json.decode(response)); + return dao; + } catch (e, stack) { + log.e("ForteService", e, stack); + return new Cd.ForteCloseDayDao(result: Cd.ResultBean(description: 'Ошибка при закрытии дня', code: -1)); + } + } + + Future pay({double amount, String token}) async { + try { + + double total = amount * 100; + log.i('total: $total, ${total.toInt()}'); + String response = await _channel.invokeMethod("pay", { + 'amount': total.toInt(), + 'token': token, + 'packageName': packageName, + }); + log.i(response); + ForteResponse dao = ForteResponse.fromMap(json.decode(response)); + return dao; + } catch (e, stack) { + log.e("ForteService", e, stack); + return new ForteResponse(result: ResultBean(description: 'Ошибка оплаты', code: -1)); + } + } + + Future refund({double amount, String token, int terminalId, int operDay, int transNum }) async { + try { + String response = await _channel.invokeMethod("refund", { + 'amount': amount.toInt(), + 'token': token , + 'terminalId': terminalId, + 'operDay': operDay, + 'transNum': transNum, + 'packageName': packageName + }); + ForteResponse dao = ForteResponse.fromMap(json.decode(response)); + return dao; + } catch (e, stack) { + log.e("ForteService", e, stack); + return new ForteResponse(result: ResultBean(description: 'Ошибка при возврате', code: -1)); + } + } + + Future reversal({ String token, int terminalId, int operDay, int transNum }) async { + try { + String response = await _channel.invokeMethod("reversal", { + 'token': token, + 'terminalId': terminalId, + 'operDay': operDay, + 'transNum': transNum, + 'packageName': packageName + }); + log.i(response); + ForteResponse dao = ForteResponse.fromMap(json.decode(response)); + return dao; + } catch (e, stack) { + log.e("ForteService", e, stack); + return new ForteResponse(result: ResultBean(description: 'Ошибка при возврате', code: -1)); + } + } + + + CloseDayData closeDayDataConvert(Cd.TransactionsBean transactions) { + final DateFormat formatter = DateFormat('dd.MM.yyyy'); + final DateTime now = DateTime.now(); + final String formatted = formatter.format(now); + List items = transactions.transaction; + num totalAmount = 0; + int totalCount = 0; + num paymentAmount = 0; + int paymentCount = 0; + num refundAmount = 0; + int refundCount = 0; + num cancelAmount = 0; + int cancelCount = 0; + + for(Cd.TransactionBean item in items) { + if(item.type == 'PAYMENT') { + paymentCount++; + paymentAmount += ( item.amount / 100 ); + totalAmount += ( item.amount / 100 ); + } else if(item.type == 'REFUND') { + refundCount++; + refundAmount += ( item.amount / 100 ); + totalAmount -= ( item.amount / 100 ); + } else if(item.type == 'REVERSAL') { + cancelCount++; + cancelAmount += ( item.amount / 100 ); + totalAmount -= ( item.amount / 100 ); + } + totalCount++; + } + + CloseDayData closeDayData = new CloseDayData( + items: items, + title: 'Отчет POS от $formatted', + totalAmount: totalAmount, totalCount: totalCount, + paymentAmount: paymentAmount, paymentCount: paymentCount, + refundAmount: refundAmount, refundCount: refundCount, + cancelAmount: cancelAmount, cancelCount: cancelCount, + ); + return closeDayData; + } + +} diff --git a/lib/redux/actions/bank_actions.dart b/lib/redux/actions/bank_actions.dart index 024c870..adc36cb 100644 --- a/lib/redux/actions/bank_actions.dart +++ b/lib/redux/actions/bank_actions.dart @@ -1,4 +1,5 @@ +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart'; import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart'; import 'package:aman_kassa_flutter/redux/state/bank_state.dart'; import 'package:meta/meta.dart'; @@ -12,15 +13,50 @@ class SetBankStateAction { final BankState bankState; SetBankStateAction(this.bankState); } - -ThunkAction saveData(String login, String password) { +ThunkAction saveData(String login, String password, {String sessionType}) { return (Store store) async { - store.dispatch(SetBankStateAction(BankState(login: login, password: password))); - }; -} + final currentState = store.state.bankState; -ThunkAction setHalykSession(HalykPosSession session) { - return (Store store) async { - store.dispatch(SetBankStateAction(BankState(session: session))); + dynamic session; + if (sessionType == 'Halyk') { + session = HalykPosSession( + login: login, + token: currentState.session != null && currentState.session is HalykPosSession + ? (currentState.session as HalykPosSession).token + : null, + serverTime: currentState.session != null && currentState.session is HalykPosSession + ? (currentState.session as HalykPosSession).serverTime + : null, + tokenTimeout: currentState.session != null && currentState.session is HalykPosSession + ? (currentState.session as HalykPosSession).tokenTimeout + : null, + result: currentState.session != null && currentState.session is HalykPosSession + ? (currentState.session as HalykPosSession).result + : null, + ); + } else if (sessionType == 'Forte') { + session = FortePosSession( + login: login, + token: currentState.session != null && currentState.session is FortePosSession + ? (currentState.session as FortePosSession).token + : null, + serverTime: currentState.session != null && currentState.session is FortePosSession + ? (currentState.session as FortePosSession).serverTime + : null, + tokenTimeout: currentState.session != null && currentState.session is FortePosSession + ? (currentState.session as FortePosSession).tokenTimeout + : null, + result: currentState.session != null && currentState.session is FortePosSession + ? (currentState.session as FortePosSession).result + : null, + ); + } + + store.dispatch(SetBankStateAction(BankState( + login: login, + password: password, + session: session, + sessionType: sessionType, + ))); }; -} +} \ No newline at end of file diff --git a/lib/redux/reducers/bank_reducer.dart b/lib/redux/reducers/bank_reducer.dart index c881809..3842645 100644 --- a/lib/redux/reducers/bank_reducer.dart +++ b/lib/redux/reducers/bank_reducer.dart @@ -1,7 +1,13 @@ import 'package:aman_kassa_flutter/redux/actions/bank_actions.dart'; import 'package:aman_kassa_flutter/redux/state/bank_state.dart'; -bankReducer(BankState prevState, SetBankStateAction action) { +BankState bankReducer(BankState prevState, SetBankStateAction action) { final payload = action.bankState; - return prevState.copyWith(login: payload.login, password: payload.password, session: payload.session); + + return prevState.copyWith( + login: payload.login ?? prevState.login, + password: payload.password ?? prevState.password, + session: payload.session ?? prevState.session, + sessionType: payload.sessionType ?? prevState.sessionType, + ); } diff --git a/lib/redux/state/bank_state.dart b/lib/redux/state/bank_state.dart index 2c4d58e..c76655f 100644 --- a/lib/redux/state/bank_state.dart +++ b/lib/redux/state/bank_state.dart @@ -1,5 +1,6 @@ import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; import 'package:meta/meta.dart'; @@ -7,42 +8,72 @@ import 'package:meta/meta.dart'; class BankState { final String login; final String password; - final HalykPosSession session; + final dynamic session; + final String sessionType; - BankState({this.login, this.password, this.session,}); + BankState({ + this.login, + this.password, + this.session, + this.sessionType, + }); - //read hive factory BankState.initial(BankState payload) { return BankState( - login: payload?.login, - password: payload?.password, - session: payload?.session + login: payload?.login, + password: payload?.password, + session: payload?.session, + sessionType: payload?.sessionType, ); } - //write hive BankState copyWith({ - @required login, - @required password, - @required session, + String login, + String password, + dynamic session, + String sessionType, }) { return BankState( login: login ?? this.login, password: password ?? this.password, - session: session ?? this.session + session: session ?? this.session, + sessionType: sessionType ?? this.sessionType, ); } - static BankState fromJson(dynamic json) { - return json != null - ? BankState( - password: json['password'], + // Создание из JSON + static BankState fromJson(Map json) { + if (json == null) return null; + + // Определяем тип сессии + dynamic session; + String sessionType = json['sessionType']; + if (sessionType == "Halyk") { + session = HalykPosSession.fromJson(json['session']); + } else if (sessionType == "Forte") { + session = FortePosSession.fromJson(json['session']); + } + + return BankState( login: json['login'], - ) - : null; + password: json['password'], + session: session, + sessionType: sessionType, + ); } - dynamic toJson() { - return {"password": password, "login": login}; + // Преобразование в JSON + Map toJson() { + return { + "login": login, + "password": password, + "sessionType": sessionType, + "session": session?.toJson(), + }; + } + + @override + String toString() { + return 'BankState(login: $login, password: $password, sessionType: $sessionType, session: $session)'; } } diff --git a/lib/shared/app_colors.dart b/lib/shared/app_colors.dart index b20dd93..8088f0a 100644 --- a/lib/shared/app_colors.dart +++ b/lib/shared/app_colors.dart @@ -5,6 +5,7 @@ const Color fillColor = Color.fromRGBO(248, 248, 248, 1); const Color primaryColor = Color.fromRGBO(51, 122, 183, 1); const Color halykColor = Color.fromRGBO(0, 118, 59, 1); +const Color forteColor = Color.fromRGBO(175, 32, 92, 1.0); const Color menuColor = Color.fromRGBO(0, 75, 120, 1); diff --git a/lib/views/bank_setting/bank_setting_view.dart b/lib/views/bank_setting/bank_setting_view.dart index 201225a..776c3bd 100644 --- a/lib/views/bank_setting/bank_setting_view.dart +++ b/lib/views/bank_setting/bank_setting_view.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/models/aman_dao.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart'; import 'package:aman_kassa_flutter/core/services/BankService.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; import 'package:aman_kassa_flutter/redux/actions/bank_actions.dart'; @@ -24,6 +26,7 @@ class _BankSettingViewState extends State { TextEditingController _passwordController; final BankService _bankService = locator(); final DialogService _dialogService = locator(); + String _sessionType; @override void initState() { @@ -32,6 +35,7 @@ class _BankSettingViewState extends State { _emailController = new TextEditingController(text: state.login); _passwordController = new TextEditingController(text: state.password); //permissions(); + _sessionType = 'Halyk'; } // Future permissions() async { @@ -52,7 +56,11 @@ class _BankSettingViewState extends State { void _saveData(BuildContext _context) async { FocusScope.of(_context).unfocus(); - await Redux.store.dispatch(saveData(_emailController.text, _passwordController.text)); + await Redux.store.dispatch(saveData( + _emailController.text, + _passwordController.text, + sessionType: _sessionType, + )); _dialogService.showDialog(description: 'Данные сохранены'); } @@ -60,6 +68,26 @@ class _BankSettingViewState extends State { @override Widget build(BuildContext context) { + final BankState state = Redux.store.state.bankState; + + // Проверяем, активна ли Forte-сессия + if (!(state.login == null || state.login.isEmpty) || + !(state.password == null || state.password.isEmpty)) { + if (state.sessionType != 'Halyk') { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text('Настройка HalykPos'), + ), + body: Center( + child: Text( + 'У вас подключен терминал Forte', + style: TextStyle(fontSize: 16.0, color: Colors.grey), + ), + ), + ); + } + } return Scaffold( appBar: AppBar( centerTitle: true, diff --git a/lib/views/bank_setting/forte_setting_view.dart b/lib/views/bank_setting/forte_setting_view.dart new file mode 100644 index 0000000..7db35e4 --- /dev/null +++ b/lib/views/bank_setting/forte_setting_view.dart @@ -0,0 +1,138 @@ +import 'dart:convert'; + +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/models/aman_dao.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_post_session.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/halyk_post_session.dart'; +import 'package:aman_kassa_flutter/core/services/BankService.dart'; +import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; +import 'package:aman_kassa_flutter/redux/actions/bank_actions.dart'; +import 'package:aman_kassa_flutter/redux/state/bank_state.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:aman_kassa_flutter/shared/app_colors.dart'; +import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ForteSettingView extends StatefulWidget { + ForteSettingView(); + + @override + _ForteSettingViewState createState() => _ForteSettingViewState(); +} + +class _ForteSettingViewState extends State { + TextEditingController _emailController; + TextEditingController _passwordController; + final DialogService _dialogService = locator(); + String _sessionType; + + @override + void initState() { + super.initState(); + BankState state = Redux.store.state.bankState; + _emailController = new TextEditingController(text: state.login); + _passwordController = new TextEditingController(text: state.password); + //permissions(); + _sessionType = 'Forte'; + } + + // Future permissions() async { + // try { + // await _bankService.permissions(); + // } on PlatformException { + // + // } + // } + + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + void _saveData(BuildContext _context) async { + FocusScope.of(_context).unfocus(); + await Redux.store.dispatch(saveData( + _emailController.text, + _passwordController.text, + sessionType: _sessionType, + )); + _dialogService.showDialog(description: 'Данные сохранены'); + } + + + + @override + Widget build(BuildContext context) { + // Получаем состояние + final BankState state = Redux.store.state.bankState; + + // Проверяем, активна ли Forte-сессия + if (!(state.login == null || state.login.isEmpty) || + !(state.password == null || state.password.isEmpty)) { + if (state.sessionType != 'Forte') { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text('Настройка FortePos'), + ), + body: Center( + child: Text( + 'У вас подключен терминал Halyk', + style: TextStyle(fontSize: 16.0, color: Colors.grey), + ), + ), + ); + } + } + + // Если сессия Forte, отображаем данные + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text('Настройка FortePos'), + ), + body: SingleChildScrollView( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 14.0), + child: Column( + children: [ + verticalSpaceTiny, + Text( + 'Необходимо указать почту и пароль для подключения к системе проведения платежей', + style: TextStyle(fontSize: 15.0), + textAlign: TextAlign.center, + ), + verticalSpaceTiny, + TextField( + controller: _emailController, + decoration: InputDecoration( + labelText: 'E-Mail', hintText: "Введите адрес почты"), + keyboardType: TextInputType.emailAddress, + ), + TextField( + controller: _passwordController, + obscureText: true, + decoration: InputDecoration( + labelText: 'Пароль', hintText: "Введите пароль"), + ), + verticalSpaceMedium, + RaisedButton( + onPressed: () => this._saveData(context), + child: Text( + 'Cохранить', + style: TextStyle(color: whiteColor, fontSize: 25.0), + ), + color: primaryColor, + padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/check/image_show_container.dart b/lib/views/check/image_show_container.dart index a60375d..38dfd96 100644 --- a/lib/views/check/image_show_container.dart +++ b/lib/views/check/image_show_container.dart @@ -16,10 +16,12 @@ import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart'; import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; +import 'package:aman_kassa_flutter/redux/state/bank_state.dart'; import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; +import 'package:aman_kassa_flutter/views/payment/forte_pos_service.dart'; import 'package:aman_kassa_flutter/views/settings/printer/PrinterTest.dart'; import 'package:aman_kassa_flutter/views/payment/halyk_pos_service.dart'; import 'package:aman_kassa_flutter/widgets/fields/busy_button_icon.dart'; @@ -191,7 +193,6 @@ class _MyFloatingActionButtonState extends State { @override Widget build(BuildContext context) { - //print(widget.data.cardData.transactionType); if (showFab) { return Column( mainAxisAlignment: MainAxisAlignment.end, @@ -205,18 +206,30 @@ class _MyFloatingActionButtonState extends State { color: whiteColor, ), onPressed: () async { - var today = new DateTime.now(); - var yesterday = today.subtract(new Duration(days: 1)); - if( Redux.store.state.userState == null - || Redux.store.state.userState.smena == null - || Redux.store.state.userState.smena.startedAt == null - || yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { - _dialog.showDialog(description: 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.'); + var today = DateTime.now(); + var yesterday = today.subtract(Duration(days: 1)); + + if (Redux.store.state.userState == null || + Redux.store.state.userState.smena == null || + Redux.store.state.userState.smena.startedAt == null || + yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { + _dialog.showDialog( + description: 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.', + ); return; } + try { await Redux.store.dispatch(changePinSkipFromSetting(true)); - AmanDao response = await reversalHalykPos(widget.data.cardData, widget.data.voucher.total); + + // Определяем метод отмены в зависимости от типа сессии + final BankState state = Redux.store.state.bankState; + final isForteSessionActive = state.sessionType == 'Forte'; + + final AmanDao response = isForteSessionActive + ? await reversalFortePos(widget.data.cardData, widget.data.voucher.total) + : await reversalHalykPos(widget.data.cardData, widget.data.voucher.total); + if (response.success) { pressRefund(); } else { @@ -225,17 +238,14 @@ class _MyFloatingActionButtonState extends State { } finally { await Redux.store.dispatch(changePinSkipFromSetting(false)); } + _navigatorService.replace(HomeViewRoute); }, heroTag: null, ) else - SizedBox( - height: 0, - ), - SizedBox( - height: 10, - ), + SizedBox(height: 0), + SizedBox(height: 10), if (widget.data.cardData != null && widget.data.cardData.transactionType == "payment") FloatingActionButton( backgroundColor: redColor, @@ -245,19 +255,29 @@ class _MyFloatingActionButtonState extends State { color: whiteColor, ), onPressed: () async { - var today = new DateTime.now(); - var yesterday = today.subtract(new Duration(days: 1)); - if( Redux.store.state.userState == null - || Redux.store.state.userState.smena == null - || Redux.store.state.userState.smena.startedAt == null - || yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { - _dialog.showDialog(description: 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.'); + var today = DateTime.now(); + var yesterday = today.subtract(Duration(days: 1)); + + if (Redux.store.state.userState == null || + Redux.store.state.userState.smena == null || + Redux.store.state.userState.smena.startedAt == null || + yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { + _dialog.showDialog( + description: 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.', + ); return; } try { await Redux.store.dispatch(changePinSkipFromSetting(true)); - AmanDao response = await refundHalykPos(widget.data.cardData, widget.data.voucher.total); + + final BankState state = Redux.store.state.bankState; + final isForteSessionActive = state.sessionType == 'Forte'; + + final AmanDao response = isForteSessionActive + ? await refundFortePos(widget.data.cardData, widget.data.voucher.total) + : await refundHalykPos(widget.data.cardData, widget.data.voucher.total); + if (response.success) { pressRefund(); } else { @@ -270,51 +290,52 @@ class _MyFloatingActionButtonState extends State { heroTag: null, ) else - SizedBox( - height: 0, - ), - SizedBox( - height: 10, - ), + SizedBox(height: 0), + SizedBox(height: 10), FloatingActionButton( child: Icon(Icons.share), onPressed: () { var bottomSheetController = showBottomSheet( - context: context, - builder: (bottomSheetContext) => Container( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(15)), - boxShadow: [BoxShadow(blurRadius: 10, color: Colors.grey[300], spreadRadius: 5)]), - height: 260, - child: Column( - children: [ - verticalSpaceSmall, - BusyButtonIcon( - title: 'WhatsApp', - onPressed: callWhatsApp, - mainColor: greenColor, - icon: MdiIcons.whatsapp, - enabled: widget.data.url != null, - ), - verticalSpaceSmall, - BusyButtonIcon( - title: 'QR-код чека', - onPressed: qrGenerate, - mainColor: primaryColor, - icon: MdiIcons.qrcode, - enabled: widget.data.url != null, - ), - verticalSpaceSmall, - BusyButtonIcon( - title: 'Поделиться', - onPressed: shareFile, - mainColor: yellowColor, - icon: Icons.share, - ), - ], - ))); + context: context, + builder: (bottomSheetContext) => Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(15)), + boxShadow: [ + BoxShadow(blurRadius: 10, color: Colors.grey[300], spreadRadius: 5) + ], + ), + height: 260, + child: Column( + children: [ + verticalSpaceSmall, + BusyButtonIcon( + title: 'WhatsApp', + onPressed: callWhatsApp, + mainColor: greenColor, + icon: MdiIcons.whatsapp, + enabled: widget.data.url != null, + ), + verticalSpaceSmall, + BusyButtonIcon( + title: 'QR-код чека', + onPressed: qrGenerate, + mainColor: primaryColor, + icon: MdiIcons.qrcode, + enabled: widget.data.url != null, + ), + verticalSpaceSmall, + BusyButtonIcon( + title: 'Поделиться', + onPressed: shareFile, + mainColor: yellowColor, + icon: Icons.share, + ), + ], + ), + ), + ); showFloatingActionButton(false); bottomSheetController.closed.then((value) { showFloatingActionButton(true); diff --git a/lib/views/close_day_view/close_day_show_container.dart b/lib/views/close_day_view/close_day_show_container.dart index fb0e3a0..585865a 100644 --- a/lib/views/close_day_view/close_day_show_container.dart +++ b/lib/views/close_day_view/close_day_show_container.dart @@ -1,4 +1,4 @@ -import 'package:aman_kassa_flutter/core/models/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/close_day_data.dart'; import 'package:aman_kassa_flutter/core/models/halyk/halyk_close_day_dao.dart'; import 'package:aman_kassa_flutter/core/models/transaction_item.dart'; import 'package:aman_kassa_flutter/shared/shared_styles.dart'; diff --git a/lib/views/history/history_view.dart b/lib/views/history/history_view.dart index 5e5ceee..aba9068 100644 --- a/lib/views/history/history_view.dart +++ b/lib/views/history/history_view.dart @@ -5,7 +5,7 @@ import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; import 'package:aman_kassa_flutter/core/models/card_data.dart'; import 'package:aman_kassa_flutter/core/models/check_data.dart'; -import 'package:aman_kassa_flutter/core/models/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/close_day_data.dart'; import 'package:aman_kassa_flutter/core/route_names.dart'; import 'package:aman_kassa_flutter/core/services/DbService.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; diff --git a/lib/views/home/components/popup_menu.dart b/lib/views/home/components/popup_menu.dart index c55bc13..5296551 100644 --- a/lib/views/home/components/popup_menu.dart +++ b/lib/views/home/components/popup_menu.dart @@ -34,6 +34,7 @@ class _PopupMenuState extends State { // const Choice(title: 'Bank', icon: Icons.text_fields, command: 'bank'), if (version >= _bankService.sdkVersion ) const Choice(title: 'Настройка HalykPos', icon: Icons.phonelink_lock_outlined, command: 'tap2phone'), + const Choice(title: 'Настройка FortePos', icon: Icons.phonelink_lock_outlined, command: 'fortepos'), const Choice(title: 'Настройки', icon: Icons.settings, command: 'settings'), const Choice(title: 'Принтер', icon: Icons.print, command: 'print'), const Choice(title: 'Выйти', icon: Icons.exit_to_app, command: 'exit') diff --git a/lib/views/home/home_view_m.dart b/lib/views/home/home_view_m.dart index c2b7baa..b5551b9 100644 --- a/lib/views/home/home_view_m.dart +++ b/lib/views/home/home_view_m.dart @@ -159,6 +159,8 @@ class _HomeViewState extends State with WidgetsBindingObserver { _navigatorService.push(BankViewRoute); } else if (choice.command == 'tap2phone') { _navigatorService.push(BankSettingViewRoute); + } else if (choice.command == 'fortepos') { + _navigatorService.push(ForteSettingViewRoute); } } diff --git a/lib/views/home/tabs/AdditionalTab.dart b/lib/views/home/tabs/AdditionalTab.dart index 78bb97d..9f41d2e 100644 --- a/lib/views/home/tabs/AdditionalTab.dart +++ b/lib/views/home/tabs/AdditionalTab.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:aman_kassa_flutter/core/entity/Voucher.dart'; import 'package:aman_kassa_flutter/core/locator.dart'; import 'package:aman_kassa_flutter/core/models/check_image_modal.dart'; -import 'package:aman_kassa_flutter/core/models/close_day_data.dart'; +import 'package:aman_kassa_flutter/core/models/halyk/close_day_data.dart'; import 'package:aman_kassa_flutter/core/models/halyk/halyk_close_day_dao.dart'; import 'package:aman_kassa_flutter/core/models/money.dart'; import 'package:aman_kassa_flutter/core/models/response.dart'; @@ -13,21 +13,24 @@ import 'package:aman_kassa_flutter/core/route_names.dart'; import 'package:aman_kassa_flutter/core/services/ApiService.dart'; import 'package:aman_kassa_flutter/core/services/BankService.dart'; import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/core/services/ForteService.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; import 'package:aman_kassa_flutter/redux/actions/user_actions.dart'; import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart'; import 'package:aman_kassa_flutter/redux/constants/setting_const.dart'; +import 'package:aman_kassa_flutter/redux/state/bank_state.dart'; import 'package:aman_kassa_flutter/redux/state/setting_state.dart'; import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; import 'package:aman_kassa_flutter/views/check/image_show_container.dart'; +import 'package:aman_kassa_flutter/views/payment/forte_pos_service.dart' as forte; import 'package:aman_kassa_flutter/widgets/fields/aman_icon_button.dart'; import 'package:aman_kassa_flutter/widgets/fields/aman_icon_button_horizontal.dart'; import 'package:aman_kassa_flutter/widgets/fields/busy_button.dart'; import 'package:aman_kassa_flutter/widgets/loader/Dialogs.dart'; -import 'package:aman_kassa_flutter/views/payment/halyk_pos_service.dart'; +import 'package:aman_kassa_flutter/views/payment/halyk_pos_service.dart' as halyk; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -45,7 +48,7 @@ class _AdditionalTabState extends State { ApiService _api = locator(); NavigatorService _navigator = locator(); DialogService _dialog = locator(); - final BankService _bankService = locator(); + dynamic _bankService; DataService _dataService = locator(); final GlobalKey _keyLoader = new GlobalKey(); @@ -222,13 +225,22 @@ class _AdditionalTabState extends State { } void _closeDay() async { - setState(() { isClosePosBusy = true; }); + BankState state = Redux.store.state.bankState; + + print(state.toJson()); + + if (state.sessionType == 'Halyk' && state.session != null) { + _bankService = locator(); + } else if (state.sessionType == 'Forte' && state.session != null) { + _bankService = locator(); + } + int version = await _bankService.version(); - if (version < _bankService.sdkVersion ) { + if (version < _bankService.sdkVersion) { setState(() { isClosePosBusy = false; }); @@ -236,10 +248,22 @@ class _AdditionalTabState extends State { return; } + final isForteSessionActive = state.sessionType == 'Forte'; + await Redux.store.dispatch(changePinSkipFromSetting(true)); - HalykCloseDayDao closeDayDao = await closeDayHalykPos(); + + dynamic closeDayDao; + if (isForteSessionActive) { + closeDayDao = await forte.closeDayFortePos(); + } else { + closeDayDao = await halyk.closeDayHalykPos(); + } + await Redux.store.dispatch(changePinSkipFromSetting(false)); - log.i(closeDayDao.toJson()); + + forte.log.i(closeDayDao.toJson()); + halyk.log.i(closeDayDao.toJson()); + if (closeDayDao.result.code != 0) { if (closeDayDao.result.description != null) { _dialog.showDialog(description: closeDayDao.result.description); @@ -250,24 +274,22 @@ class _AdditionalTabState extends State { return; } - CloseDayData closeDayData = _bankService.closeDayDataConvert(closeDayDao.transactions); User user = Redux.store.state.userState.user; _dataService.insertVoucher( - user: user, - name: closeDayData.title, - data: jsonEncode(closeDayData.toJson()), - total: closeDayData.totalAmount.toDouble(), - type: VoucherTypeCloseDayPosReport); + user: user, + name: closeDayData.title, + data: jsonEncode(closeDayData.toJson()), + total: closeDayData.totalAmount.toDouble(), + type: VoucherTypeCloseDayPosReport, + ); - // _dialog.showDialog(description: 'Закрытие дня: операция прошла успешно!'); setState(() { isClosePosBusy = false; }); - _navigator.push(CloseDayShowRoute, - arguments: closeDayData); + _navigator.push(CloseDayShowRoute, arguments: closeDayData); } @override diff --git a/lib/views/home/tabs/KassaTab.dart b/lib/views/home/tabs/KassaTab.dart index f37be9c..f19fa87 100644 --- a/lib/views/home/tabs/KassaTab.dart +++ b/lib/views/home/tabs/KassaTab.dart @@ -17,10 +17,11 @@ import 'package:aman_kassa_flutter/views/home/tabs/kassaView/CatalogBottomSheet. import 'package:aman_kassa_flutter/views/home/tabs/kassaView/ProductAddBottomSheet.dart'; import 'package:aman_kassa_flutter/views/payment/payment_view.dart'; import 'package:aman_kassa_flutter/widgets/components/ProductListItem.dart'; -import 'package:barcode_scan/gen/protos/protos.pb.dart'; -import 'package:barcode_scan/gen/protos/protos.pbenum.dart'; -import 'package:barcode_scan/model/scan_options.dart'; -import 'package:barcode_scan/platform_wrapper.dart'; +// import 'package:barcode_scan/gen/protos/protos.pb.dart'; +// import 'package:barcode_scan/gen/protos/protos.pbenum.dart'; +// import 'package:barcode_scan/model/scan_options.dart'; +// import 'package:barcode_scan/platform_wrapper.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_redux/flutter_redux.dart'; @@ -185,61 +186,58 @@ class KassaTab extends StatelessWidget { Future scan() async { try { await Redux.store.dispatch(changePinSkipFromSetting(true)); - var options = ScanOptions(strings: { - "cancel": 'Отмена', - "flash_on": 'Вкл фонарик', - "flash_off": 'Выкл фонарик', - }); + var options = ScanOptions( + strings: { + "cancel": 'Отмена', + "flash_on": 'Вкл фонарик', + "flash_off": 'Выкл фонарик', + }, + ); var result = await BarcodeScanner.scan(options: options); - // print(result.type); // The result type (barcode, cancelled, failed) - // print(result.rawContent); // The barcode content - // print(result.format); // The barcode format (as enum) - // print(result.formatNote); // If a unknown format was scanned this field contains a note - // print(result.rawContent); // content - if (result.type == ResultType.Barcode ) { + + if (result.type == ResultType.Barcode) { String barcode; String dataMatrix; - if(result.format == BarcodeFormat.ean13 || result.format == BarcodeFormat.ean8) { + + if (result.format == BarcodeFormat.ean13 || result.format == BarcodeFormat.ean8) { barcode = result.rawContent; - } else if( result.format == BarcodeFormat.dataMatrix ) { + } else if (result.format == BarcodeFormat.dataMatrix) { dataMatrix = result.rawContent; - if(dataMatrix!= null && dataMatrix.length >15) { + if (dataMatrix != null && dataMatrix.length > 15) { try { - String numberText = dataMatrix.substring(0, dataMatrix.length - 15 ).replaceAll(RegExp("[a-zA-Z]"), ''); + String numberText = dataMatrix + .substring(0, dataMatrix.length - 15) + .replaceAll(RegExp("[a-zA-Z]"), ''); barcode = int.parse(numberText).toString(); } catch (e) { - + // Обработка ошибки, если необходимо } } - // 00000046208262 nZ2qnLH ODVFWktT - // 00000046208262 tjKDqfu UzVSeFE5 } - if(barcode != null) { - List goods = - await _dataService.getGoodsByBarcode(barcode: barcode); + if (barcode != null) { + List goods = await _dataService.getGoodsByBarcode(barcode: barcode); if (goods != null && goods.isNotEmpty) { await Redux.store.dispatch(addProductToKassaItems(goods.first, dataMatrix)); } else { - _dialogService.showDialog( - description: 'Товар не найден: $barcode'); + _dialogService.showDialog(description: 'Товар не найден: $barcode'); } - } else if (result.type == ResultType.Error) { - _dialogService.showDialog(description: 'Не верный формат QR кода'); + } else { + _dialogService.showDialog(description: 'Не удалось распознать штрих-код'); } - } - } on PlatformException catch (e) { - var result = ScanResult.create(); - result.type = ResultType.Error; - result.format = BarcodeFormat.unknown; - if (e.code == BarcodeScanner.cameraAccessDenied) { - result.rawContent = 'The user did not grant the camera permission!'; - _dialogService.showDialog( - description: 'Нет доступа до камеры устройства'); - } else { - result.rawContent = 'Unknown error: $e'; - _dialogService.showDialog(description: 'Неизвестная ошибка: $e'); + } else if (result.type == ResultType.Cancelled) { + // Пользователь отменил сканирование + } else if (result.type == ResultType.Error) { + _dialogService.showDialog(description: 'Ошибка сканирования: ${result.rawContent}'); } + } on PlatformException catch (e) { + if (e.code == BarcodeScanner.cameraAccessDenied) { + _dialogService.showDialog(description: 'Нет доступа к камере устройства'); + } else { + _dialogService.showDialog(description: 'Ошибка сканирования: ${e.message}'); + } + } catch (e) { + _dialogService.showDialog(description: 'Неизвестная ошибка: $e'); } finally { await Redux.store.dispatch(changePinSkipFromSetting(false)); } diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index a196ee1..90ca62c 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -11,10 +11,11 @@ import 'package:aman_kassa_flutter/shared/app_colors.dart'; import 'package:aman_kassa_flutter/shared/ui_helpers.dart'; import 'package:aman_kassa_flutter/widgets/fields/busy_button.dart'; import 'package:aman_kassa_flutter/widgets/fields/input_field.dart'; -import 'package:barcode_scan/gen/protos/protos.pb.dart'; -import 'package:barcode_scan/gen/protos/protos.pbenum.dart'; -import 'package:barcode_scan/model/scan_options.dart'; -import 'package:barcode_scan/platform_wrapper.dart'; +// import 'package:barcode_scan/gen/protos/protos.pb.dart'; +// import 'package:barcode_scan/gen/protos/protos.pbenum.dart'; +// import 'package:barcode_scan/model/scan_options.dart'; +// import 'package:barcode_scan/platform_wrapper.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:flutter/services.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter/material.dart'; @@ -161,40 +162,35 @@ class _LoginViewState extends State { Future scan() async { try { - var options = ScanOptions(strings: { - "cancel": 'Отмена', - "flash_on": 'Вкл фонарик', - "flash_off": 'Выкл фонарик', - }); + var options = ScanOptions( + strings: { + "cancel": 'Отмена', + "flash_on": 'Вкл фонарик', + "flash_off": 'Выкл фонарик', + }, + ); var result = await BarcodeScanner.scan(options: options); - // print(result.type); // The result type (barcode, cancelled, failed) - // print(result.rawContent); // The barcode content - // print(result.format); // The barcode format (as enum) - // print(result - // .formatNote); // If a unknown format was scanned this field contains a note - if (result.type == ResultType.Barcode && - result.rawContent?.length == 60) { + + if (result.type == ResultType.Barcode && result.rawContent?.length == 60) { if (result.rawContent.toLowerCase().trim().startsWith('test')) { _apiService.test = true; } else { _apiService.test = false; } Redux.store.dispatch(authenticateToken(result.rawContent)); + } else if (result.type == ResultType.Cancelled) { + // Пользователь отменил сканирование } else if (result.type == ResultType.Error) { - _dialogService.showDialog(description: 'Не верный формат QR кода'); + _dialogService.showDialog(description: 'Ошибка сканирования: ${result.rawContent}'); } } on PlatformException catch (e) { - var result = ScanResult.create(); - result.type = ResultType.Error; - result.format = BarcodeFormat.unknown; if (e.code == BarcodeScanner.cameraAccessDenied) { - result.rawContent = 'The user did not grant the camera permission!'; - _dialogService.showDialog( - description: 'Нет доступа до камеры устройства'); + _dialogService.showDialog(description: 'Нет доступа к камере устройства'); } else { - result.rawContent = 'Unknown error: $e'; - _dialogService.showDialog(description: 'Неизвестная ошибка: $e'); + _dialogService.showDialog(description: 'Ошибка сканирования: ${e.message}'); } + } catch (e) { + _dialogService.showDialog(description: 'Неизвестная ошибка: $e'); } } } diff --git a/lib/views/payment/forte_pos_service.dart b/lib/views/payment/forte_pos_service.dart new file mode 100644 index 0000000..9d55396 --- /dev/null +++ b/lib/views/payment/forte_pos_service.dart @@ -0,0 +1,158 @@ +import 'package:aman_kassa_flutter/core/locator.dart'; +import 'package:aman_kassa_flutter/core/logger.dart'; +import 'package:aman_kassa_flutter/core/models/forte/forte_close_day_dao.dart' + as Cd; +import 'package:aman_kassa_flutter/core/models/forte/forte_response_dao.dart'; +import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; +import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; +import 'package:aman_kassa_flutter/redux/store.dart'; +import 'package:logger/logger.dart'; +import '../../core/models/aman_dao.dart'; +import '../../core/models/card_data.dart'; +import '../../core/models/forte/forte_post_session.dart'; +import '../../core/services/ForteService.dart'; +import '../../redux/state/bank_state.dart'; + +ForteService _bankService = locator(); +DialogService _dialogService = locator(); +final DataService _dataService = locator(); +final NavigatorService _navigatorService = locator(); +Logger log = getLogger('PaymentNfcView'); + +Future> paymentFortePos(double total) async { + //Авторизация + String token = Redux.store.state.userState.user.token; + BankState bankState = Redux.store.state.bankState; + //права доступа + FortePosSession session = await _bankService.renewToken( + token: token, login: bankState.login, password: bankState.password); + if (session.token == null) { + return sessionDeclineDao(session); + } + + //Инициализация + ForteResponse response = + await _bankService.pay(token: session.token, amount: total); + if (response.result.code == 0) { + CardData cardData = new CardData( + authorizationCode: + response.transaction.instrumentSpecificData.authorizationCode, + cardholderName: + response.transaction.instrumentSpecificData.cardholderName, + cardNumber: response.transaction.instrumentSpecificData.maskedPan, + operationDay: response.transaction.operationDay, + transactionNumber: response.transaction.transactionNumber, + terminalId: response.transaction.terminalId, + transactionType: 'payment'); + return AmanDao( + msg: response.result.description, success: true, data: cardData); + } + return AmanDao( + msg: response.result.errorData != null + ? response.result.errorData.description + : response.result.description, + success: false); +} + +AmanDao sessionDeclineDao(FortePosSession session) { + String msg = 'Отказано в доступе к API банка'; + if(session.result?.Response?.Description != null) { + msg = '${session.result.Response.Description} (${session.result.Response.Code}) '; + } + return AmanDao(success: false, msg: msg); +} + +Future> refundFortePos( + CardData refundData, double total) async { + //Авторизация + String token = Redux.store.state.userState.user.token; + BankState bankState = Redux.store.state.bankState; + //права доступа + FortePosSession session = await _bankService.renewToken( + token: token, login: bankState.login, password: bankState.password); + if (session.token == null) { + return sessionDeclineDao(session); + } + ForteResponse response = await _bankService.refund( + token: session.token, + amount: total, + operDay: refundData.operationDay, + terminalId: refundData.terminalId, + transNum: refundData.transactionNumber); + if (response.result.code == 0) { + CardData cardData = new CardData( + authorizationCode: + response.transaction.instrumentSpecificData.authorizationCode, + cardholderName: + response.transaction.instrumentSpecificData.cardholderName, + cardNumber: response.transaction.instrumentSpecificData.maskedPan, + operationDay: response.transaction.operationDay, + transactionNumber: response.transaction.transactionNumber, + terminalId: response.transaction.terminalId, + transactionType: 'refund'); + return AmanDao( + msg: response.result.description, success: true, data: cardData); + } + return AmanDao( + msg: response.result.errorData != null + ? response.result.errorData.description + : response.result.description, + success: false); +} + +Future> reversalFortePos( + CardData refundData, double total) async { + //Авторизация + String token = Redux.store.state.userState.user.token; + BankState bankState = Redux.store.state.bankState; + //права доступа + FortePosSession session = await _bankService.renewToken( + token: token, login: bankState.login, password: bankState.password); + if (session.token == null) { + return sessionDeclineDao(session); + } + log.i(refundData.toJson()); + ForteResponse response = await _bankService.reversal( + token: session.token, + operDay: refundData.operationDay, + terminalId: refundData.terminalId, + transNum: refundData.transactionNumber); + if (response.result.code == 0) { + CardData cardData = new CardData( + authorizationCode: + response.transaction.instrumentSpecificData.authorizationCode, + cardholderName: + response.transaction.instrumentSpecificData.cardholderName, + cardNumber: response.transaction.instrumentSpecificData.maskedPan, + operationDay: response.transaction.operationDay, + transactionNumber: response.transaction.transactionNumber, + terminalId: response.transaction.terminalId, + transactionType: 'reversal'); + return AmanDao( + msg: response.result.description, success: true, data: cardData); + } + return AmanDao( + msg: response.result.errorData != null + ? response.result.errorData.description + : response.result.description, + success: false); +} + +Future closeDayFortePos() async { + //Авторизация + String token = Redux.store.state.userState.user.token; + BankState bankState = Redux.store.state.bankState; + //права доступа + FortePosSession session = await _bankService.renewToken( + token: token, login: bankState.login, password: bankState.password); + if (session.token == null) { + return new Cd.ForteCloseDayDao( + result: Cd.ResultBean( + description: 'Отказано в доступе к API банка', code: -1)); + } + //Инициализация + Cd.ForteCloseDayDao response = + await _bankService.closeDay(token: session.token); + return response; +} diff --git a/lib/views/payment/payment_view.dart b/lib/views/payment/payment_view.dart index 3cdeee1..2fd6ebd 100644 --- a/lib/views/payment/payment_view.dart +++ b/lib/views/payment/payment_view.dart @@ -10,6 +10,7 @@ import 'package:aman_kassa_flutter/core/models/response.dart'; import 'package:aman_kassa_flutter/core/route_names.dart'; import 'package:aman_kassa_flutter/core/services/BankService.dart'; import 'package:aman_kassa_flutter/core/services/DataService.dart'; +import 'package:aman_kassa_flutter/core/services/ForteService.dart'; import 'package:aman_kassa_flutter/core/services/dialog_service.dart'; import 'package:aman_kassa_flutter/core/services/navigator_service.dart'; import 'package:aman_kassa_flutter/redux/actions/calc_actions.dart'; @@ -36,6 +37,7 @@ import 'package:aman_kassa_flutter/views/payment/halyk_pos_service.dart'; import '../../core/models/aman_dao.dart'; import '../../core/models/card_data.dart'; import '../../core/models/card_data.dart'; +import 'forte_pos_service.dart'; class PaymentView extends StatefulWidget { final PaymentModel model; @@ -50,7 +52,7 @@ class _PaymentViewState extends State { final GlobalKey _keyLoader = new GlobalKey(); final DataService _dataService = locator(); final DialogService _dialogService = locator(); - BankService _bankService = locator(); + dynamic _bankService; final NavigatorService _navigatorService = locator(); bool isBusy; bool isBankApiAccess; @@ -64,12 +66,25 @@ class _PaymentViewState extends State { } _bankInit() async { - int version = await _bankService.version(); - if (version >= _bankService.sdkVersion) { + BankState state = Redux.store.state.bankState; + + print(state.toJson()); + + if (state.sessionType == 'Halyk' && state.session != null) { + _bankService = locator(); + } else if (state.sessionType == 'Forte' && state.session != null) { + _bankService = locator(); + } else { setState(() { - isBankApiAccess = true; + isBankApiAccess = false; }); + return; } + + int version = await _bankService.version(); + setState(() { + isBankApiAccess = version >= _bankService.sdkVersion; + }); } @override @@ -200,93 +215,97 @@ class _PaymentViewState extends State { return Container(); } return StoreConnector( - converter: (store) => store.state, - builder: (_, _state) { - BankState state = _state.bankState; - double _total; - if (widget.model.mode == SettingModeCalc) { - String value = totalCalc(_state.calcState.calcItems); - _total = double.parse(value); - } else { - String value = totalKassa(_state.kassaState.kassaItems); - _total = double.parse(value); - } - - if (state.password == null || state.login == null || state.password.length < 1 || state.login.length < 1) { - return Container(); - } - return InkWell( - onTap: () async { - - var today = new DateTime.now(); - var yesterday = today.subtract(new Duration(days: 1)); - if( Redux.store.state.userState == null - || Redux.store.state.userState.smena == null - || Redux.store.state.userState.smena.startedAt == null - || yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { - _dialogService.showDialog(description: 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.'); - return; - } - - try { - await Redux.store.dispatch(changePinSkipFromSetting(true)); - AmanDao data = await paymentHalykPos(_total); - if (data.success) { - pressPayment(widget.model.operationType, data.data); - } else { - _dialogService.showDialog(description: data.msg); - } - } finally { - await Redux.store.dispatch(changePinSkipFromSetting(false)); - } - }, - splashColor: halykColor.withOpacity(0.4), - borderRadius: BorderRadius.circular(10.0), - highlightColor: halykColor.withOpacity(0.1), - child: Container( - width: ScreenUtil().setSp(100.0), - padding: const EdgeInsets.symmetric(vertical: 8.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0) - ), - child: Column( - children: [ - Container( - width: ScreenUtil().setSp(80.0), - height: ScreenUtil().setSp(80.0), - margin: const EdgeInsets.only(bottom: 8.0), - decoration: BoxDecoration( - border: Border.all(color: Colors.white), - borderRadius: BorderRadius.circular(10.0), - image: DecorationImage( - image: AssetImage('assets/images/halykpos.png'), fit: BoxFit.fitWidth - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: Offset(0, 3), // changes position of shadow - ), - ], - ), - ), - // Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Icon( - // MdiIcons.nfc, - // color: halykColor, - // size: ScreenUtil().setSp(20.0), - // ), - // Text('Tap2Phone',style: TextStyle(fontSize: ScreenUtil().setSp(10.0), color: halykColor, fontWeight: FontWeight.bold ),), - // ], - // ), - ], - ), - ), - ); + converter: (store) => store.state, + builder: (_, _state) { + BankState state = _state.bankState; + double _total; + if (widget.model.mode == SettingModeCalc) { + String value = totalCalc(_state.calcState.calcItems); + _total = double.parse(value); + } else { + String value = totalKassa(_state.kassaState.kassaItems); + _total = double.parse(value); } + + if ((state.password == null || state.password.isEmpty) || + (state.login == null || state.login.isEmpty)) { + return Container(); + } + + final bool isForteSessionActive = state.sessionType == 'Forte'; + + final String imageAsset = isForteSessionActive + ? 'assets/images/fortepos.png' + : 'assets/images/halykpos.png'; + final Color sessionColor = isForteSessionActive ? forteColor : halykColor; + final Future> Function(double) paymentMethod = + isForteSessionActive ? paymentFortePos : paymentHalykPos; + + return InkWell( + onTap: () async { + var today = DateTime.now(); + var yesterday = today.subtract(Duration(days: 1)); + if (Redux.store.state.userState == null || + Redux.store.state.userState.smena == null || + Redux.store.state.userState.smena.startedAt == null || + yesterday.isAfter(Redux.store.state.userState.smena.startedAt)) { + _dialogService.showDialog( + description: + 'Текущая смена открыта более 24 ч. Необходимо закрыть смену и открыть ее заново.', + ); + return; + } + + try { + await Redux.store.dispatch(changePinSkipFromSetting(true)); + AmanDao data = await paymentMethod(_total); + + if (data.success) { + pressPayment(widget.model.operationType, data.data); + } else { + _dialogService.showDialog(description: data.msg); + } + } finally { + await Redux.store.dispatch(changePinSkipFromSetting(false)); + } + }, + splashColor: sessionColor.withOpacity(0.4), + borderRadius: BorderRadius.circular(10.0), + highlightColor: sessionColor.withOpacity(0.1), + child: Container( + width: ScreenUtil().setSp(100.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + ), + child: Column( + children: [ + Container( + width: ScreenUtil().setSp(80.0), + height: ScreenUtil().setSp(80.0), + margin: const EdgeInsets.only(bottom: 8.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: BorderRadius.circular(10.0), + image: DecorationImage( + image: AssetImage(imageAsset), + fit: BoxFit.fitWidth, + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // Смещение тени + ), + ], + ), + ), + ], + ), + ), + ); + }, ); } diff --git a/pubspec.lock b/pubspec.lock index 1188dda..4b93d8c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.5.0" auto_size_text: dependency: "direct main" description: @@ -29,13 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - barcode_scan: + barcode_scan2: dependency: "direct main" description: - name: barcode_scan + name: barcode_scan2 url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.2.4" boolean_selector: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.2.0" charset_converter: dependency: "direct main" description: @@ -175,7 +175,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -344,7 +344,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.3.0" nested: dependency: transitive description: @@ -449,7 +449,7 @@ packages: name: protobuf url: "https://pub.dartlang.org" source: hosted - version: "1.1.4" + version: "2.0.1" provider: dependency: "direct main" description: @@ -566,7 +566,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" sqflite: dependency: "direct main" description: @@ -622,7 +622,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.2.19" typed_data: dependency: transitive description: @@ -685,7 +685,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.2.4" + version: "2.0.5" xdg_directories: dependency: transitive description: @@ -701,5 +701,5 @@ packages: source: hosted version: "4.5.1" sdks: - dart: ">=2.13.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" flutter: ">=1.22.2" diff --git a/pubspec.yaml b/pubspec.yaml index 294bdc8..8617f45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: aman_kassa_flutter description: A new Flutter project. version: 1.2.3+36 environment: - sdk: '>=2.3.0 <3.0.0' + sdk: '>=2.3.0 <4.0.0' dependencies: flutter: sdk: flutter @@ -23,7 +23,8 @@ dependencies: google_fonts: ^1.1.1 material_design_icons_flutter: ^4.0.5855 intl: ^0.16.1 - barcode_scan: ^3.0.1 +# barcode_scan: ^3.0.1 + barcode_scan2: ^4.0.0 device_info: ^1.0.0 esys_flutter_share: ^1.0.2 auto_size_text: ^2.1.0 -- 2.34.1 From a0741ce84d2527f643c2f97ea930f05ad39073e3 Mon Sep 17 00:00:00 2001 From: Rustem Date: Thu, 21 Nov 2024 21:29:24 +0500 Subject: [PATCH 2/3] forte integration test --- android/app/src/main/kotlin/kz/com/aman/kassa/MainActivity.kt | 3 ++- .../main/kotlin/kz/com/aman/kassa/bank/JsonForExternalCall.kt | 2 +- lib/core/services/ForteService.dart | 3 ++- lib/views/payment/payment_view.dart | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/kotlin/kz/com/aman/kassa/MainActivity.kt b/android/app/src/main/kotlin/kz/com/aman/kassa/MainActivity.kt index 2a85c59..27697a3 100644 --- a/android/app/src/main/kotlin/kz/com/aman/kassa/MainActivity.kt +++ b/android/app/src/main/kotlin/kz/com/aman/kassa/MainActivity.kt @@ -86,10 +86,11 @@ class MainActivity : FlutterActivity() { val amount = call.argument("amount").toString() val packageName = call.argument("packageName").toString() val operationParameters = createOperationParameters(token) + val body = JsonForExternalCall.getRefundCardJson(operationParameters.authToken, terminalId, operDay, transNum, amount) startOperation( OperationType.REFUND, - JsonForExternalCall.getRefundCardJson(operationParameters.authToken, terminalId, operDay, transNum, amount), + body, packageName ) } diff --git a/android/app/src/main/kotlin/kz/com/aman/kassa/bank/JsonForExternalCall.kt b/android/app/src/main/kotlin/kz/com/aman/kassa/bank/JsonForExternalCall.kt index 6360943..136d46b 100644 --- a/android/app/src/main/kotlin/kz/com/aman/kassa/bank/JsonForExternalCall.kt +++ b/android/app/src/main/kotlin/kz/com/aman/kassa/bank/JsonForExternalCall.kt @@ -37,7 +37,7 @@ object JsonForExternalCall { "instrument": "CARD", "amountData" : { "currencyCode": "348", - "amount": "6000", + "amount": "$amount", "amountExponent": "2" }, "parentTransaction" : { diff --git a/lib/core/services/ForteService.dart b/lib/core/services/ForteService.dart index be1e5a1..235eddf 100644 --- a/lib/core/services/ForteService.dart +++ b/lib/core/services/ForteService.dart @@ -80,8 +80,9 @@ class ForteService extends BaseService { Future refund({double amount, String token, int terminalId, int operDay, int transNum }) async { try { + double total = amount * 100; String response = await _channel.invokeMethod("refund", { - 'amount': amount.toInt(), + 'amount': total, 'token': token , 'terminalId': terminalId, 'operDay': operDay, diff --git a/lib/views/payment/payment_view.dart b/lib/views/payment/payment_view.dart index 2fd6ebd..f347145 100644 --- a/lib/views/payment/payment_view.dart +++ b/lib/views/payment/payment_view.dart @@ -261,7 +261,7 @@ class _PaymentViewState extends State { AmanDao data = await paymentMethod(_total); if (data.success) { - pressPayment(widget.model.operationType, data.data); + pressPayment('card', data.data); } else { _dialogService.showDialog(description: data.msg); } -- 2.34.1 From b25781909f5baba59dd04bc61ac7b56d41977e93 Mon Sep 17 00:00:00 2001 From: Rustem Date: Sat, 23 Nov 2024 12:08:22 +0500 Subject: [PATCH 3/3] forte integration prod --- lib/core/services/ApiService.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/services/ApiService.dart b/lib/core/services/ApiService.dart index 230202f..ca6cbdf 100644 --- a/lib/core/services/ApiService.dart +++ b/lib/core/services/ApiService.dart @@ -86,8 +86,8 @@ class ApiService extends BaseService { print(hash); Map requestBody = {'login': login, 'hash': hash}; - //var response = await requestFormData('/halykpos/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); - var response = await requestFormData('/fortepos/test/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); + //var response = await requestFormData('/fortepos/test/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); + var response = await requestFormData('/fortepos/prod/gettoken', requestBody, bodyEntry: true, posEndPoint: true, statusCheck: false); print(response); return FortePosSession.fromJson(jsonDecode(response)); } -- 2.34.1