From c2f0023cd88a5143ffd7012fb47cd1ed9768e923 Mon Sep 17 00:00:00 2001 From: Ivan Chaplygin Date: Fri, 6 Sep 2024 16:29:28 +0300 Subject: [PATCH] Adding a price update scheduler Refinement of price update Adding translations --- doc/1.Setup/Cron tasks.md | 3 +- resources/pot/retailcrm-es_ES.pot | 6 ++++ resources/pot/retailcrm-ru_RU.pot | 6 ++++ src/assets/js/retailcrm-cron-info.js | 6 +++- src/include/class-wc-retailcrm-base.php | 27 ++++++++++++++++++ src/include/class-wc-retailcrm-plugin.php | 4 +++ ...ass-wc-retailcrm-upload-discount-price.php | 7 +++++ src/languages/retailcrm-es_ES.l10n.php | 2 ++ src/languages/retailcrm-es_ES.mo | Bin 18403 -> 18566 bytes src/languages/retailcrm-ru_RU.l10n.php | 2 ++ src/languages/retailcrm-ru_RU.mo | Bin 22678 -> 22886 bytes 11 files changed, 60 insertions(+), 3 deletions(-) diff --git a/doc/1.Setup/Cron tasks.md b/doc/1.Setup/Cron tasks.md index c468c05..5fc51f2 100644 --- a/doc/1.Setup/Cron tasks.md +++ b/doc/1.Setup/Cron tasks.md @@ -14,6 +14,7 @@ 'icml' => 'three_hours', 'history' => 'five_minutes', 'inventories' => 'fiveteen_minutes', + 'loyalty_upload_price' => 'four_hours' ] ``` > Важно! При использовании фильтра **retailcrm_cron_schedules**, можно использовать ключи: 'icml', 'history', 'inventories'. @@ -49,5 +50,3 @@ function change_cron_tasks($cronTasks) return $cronTasks; } ``` - - diff --git a/resources/pot/retailcrm-es_ES.pot b/resources/pot/retailcrm-es_ES.pot index a09c4d5..e1bdfd9 100644 --- a/resources/pot/retailcrm-es_ES.pot +++ b/resources/pot/retailcrm-es_ES.pot @@ -591,3 +591,9 @@ msgstr "Utiliza el cupón:" msgid "Points will be awarded upon completion of the order:" msgstr "Los puntos se concederán al finalizar el pedido:" + +msgid "Upload of current prices of offers" +msgstr "Carga de los precios actuales de las ofertas" + +msgid "Every 4 hours" +msgstr "Cada 4 horas" diff --git a/resources/pot/retailcrm-ru_RU.pot b/resources/pot/retailcrm-ru_RU.pot index d6cf7c0..191bc1b 100644 --- a/resources/pot/retailcrm-ru_RU.pot +++ b/resources/pot/retailcrm-ru_RU.pot @@ -600,3 +600,9 @@ msgstr "Используйте купон:" msgid "Points will be awarded upon completion of the order:" msgstr "По завершению заказа будет начислено баллов:" + +msgid "Upload of current prices of offers" +msgstr "Выгрузка текущих цен торговых предложений" + +msgid "Every 4 hours" +msgstr "Каждые 4 часа" diff --git a/src/assets/js/retailcrm-cron-info.js b/src/assets/js/retailcrm-cron-info.js index 25083e3..14f4a9d 100644 --- a/src/assets/js/retailcrm-cron-info.js +++ b/src/assets/js/retailcrm-cron-info.js @@ -16,6 +16,7 @@ jQuery(function () { this.history = 0; this.inventories = 0; this.messageSuccessful = ''; + this.loyalty_upload_price = 0; this.adminUrl = AdminUrl.url; @@ -33,12 +34,14 @@ jQuery(function () { _this.icml = response.icml; _this.inventories = response.inventories; _this.messageSuccessful = response.translate.tr_successful; + _this.loyalty_upload_price = response.loyalty_upload_price _this.displayInfoAboutCron( response.translate.tr_td_cron, response.translate.tr_td_icml, response.translate.tr_td_history, response.translate.tr_td_inventories, + response.translate.tr_td_loyalty_upload_price ); }) @@ -47,7 +50,7 @@ jQuery(function () { jQuery(this.submitButton).click(this.clearCronTasks); } - RetailcrmCronInfo.prototype.displayInfoAboutCron = function (cron, icml, history, inventories) { + RetailcrmCronInfo.prototype.displayInfoAboutCron = function (cron, icml, history, inventories, loyalty_upload_price) { this.table = jQuery(this.title).next(); this.table.append(''); this.infoTable = jQuery('tbody[class="retail-debug-info"]').get(0); @@ -56,6 +59,7 @@ jQuery(function () { jQuery(this.infoTable).append("" + icml + " " + this.icml + ""); jQuery(this.infoTable).append("" + history + " " + this.history + ""); jQuery(this.infoTable).append("" + inventories + " " + this.inventories + ""); + jQuery(this.infoTable).append("" + loyalty_upload_price + "" + this.loyalty_upload_price + ""); } RetailcrmCronInfo.prototype.clearCronTasks = function () { diff --git a/src/include/class-wc-retailcrm-base.php b/src/include/class-wc-retailcrm-base.php index c2404d5..8e69a9e 100644 --- a/src/include/class-wc-retailcrm-base.php +++ b/src/include/class-wc-retailcrm-base.php @@ -127,6 +127,7 @@ if (!class_exists('WC_Retailcrm_Base')) { add_action('woocommerce_applied_coupon', [$this, 'apply_coupon'], 11, 1); add_action('woocommerce_review_order_before_payment', [$this, 'reviewCreditBonus'], 11, 1); add_action('wp_trash_post', [$this, 'trash_order_action'], 10, 1); + add_action('retailcrm_loyalty_upload_price', [$this, 'upload_loyalty_price']); if ( !$this->get_option('deactivate_update_order') @@ -190,12 +191,23 @@ if (!class_exists('WC_Retailcrm_Base')) { */ public function api_sanitized($settings) { + $isLoyaltyUploadPrice = false; + + if ( + isset($settings['icml'], $settings['loyalty']) + && $settings['icml'] === static::YES + && $settings['loyalty'] === static::YES + ) { + $isLoyaltyUploadPrice = true; + } + $timeInterval = apply_filters( 'retailcrm_cron_schedules', [ 'icml' => 'three_hours', 'history' => 'five_minutes', 'inventories' => 'fiveteen_minutes', + 'loyalty_upload_price' => 'four_hours' ] ); @@ -223,6 +235,12 @@ if (!class_exists('WC_Retailcrm_Base')) { wp_clear_scheduled_hook('retailcrm_icml'); } + if ($isLoyaltyUploadPrice && !wp_next_scheduled('retailcrm_loyalty_upload_price')) { + wp_schedule_event(time(), $timeInterval['loyalty_upload_price'], 'retailcrm_loyalty_upload_price'); + } elseif (!$isLoyaltyUploadPrice) { + wp_clear_scheduled_hook('retailcrm_loyalty_upload_price'); + } + if (!$this->get_errors() && !get_option('retailcrm_active_in_crm')) { $this->activate_integration($settings); } @@ -268,6 +286,7 @@ if (!class_exists('WC_Retailcrm_Base')) { wp_clear_scheduled_hook('retailcrm_icml'); wp_clear_scheduled_hook('retailcrm_history'); wp_clear_scheduled_hook('retailcrm_inventories'); + wp_clear_scheduled_hook('retailcrm_loyalty_upload_price'); //Add new cron tasks $this->api_sanitized($this->settings); @@ -930,12 +949,15 @@ if (!class_exists('WC_Retailcrm_Base')) { $icml = $defaultValue; $history = $defaultValue; $inventories = $defaultValue; + $loyaltyUploadPrice = $defaultValue; + $translate = [ 'tr_td_cron' => __('Cron launches', 'retailcrm'), 'tr_td_icml' => __('Generation ICML', 'retailcrm'), 'tr_td_history' => __('Syncing history', 'retailcrm'), 'tr_successful' => __('Cron tasks cleared', 'retailcrm'), 'tr_td_inventories' => __('Syncing inventories', 'retailcrm'), + 'tr_td_loyalty_upload_price' => __('Upload of current prices of offers', 'retailcrm') ]; if (isset($this->settings['history']) && $this->settings['history'] == static::YES) { @@ -944,6 +966,10 @@ if (!class_exists('WC_Retailcrm_Base')) { if (isset($this->settings['icml']) && $this->settings['icml'] == static::YES) { $icml = date('H:i:s d-m-Y', wp_next_scheduled('retailcrm_icml')); + + if (isset($this->settings['loyalty']) && $this->settings['loyalty'] === static::YES) { + $loyaltyUploadPrice = date('H:i:s d-m-Y', wp_next_scheduled('retailcrm_loyalty_upload_price')); + } } if (isset($this->settings['sync']) && $this->settings['sync'] == static::YES) { @@ -956,6 +982,7 @@ if (!class_exists('WC_Retailcrm_Base')) { 'icml' => $icml, 'inventories' => $inventories, 'translate' => $translate, + 'loyalty_upload_price' => $loyaltyUploadPrice ] ); diff --git a/src/include/class-wc-retailcrm-plugin.php b/src/include/class-wc-retailcrm-plugin.php index 1e9bdac..0d36453 100644 --- a/src/include/class-wc-retailcrm-plugin.php +++ b/src/include/class-wc-retailcrm-plugin.php @@ -58,6 +58,10 @@ class WC_Retailcrm_Plugin 'fiveteen_minutes' => [ 'interval' => 900, // seconds 'display' => __('Every 15 minutes') + ], + 'four_hours' => [ + 'interval' => 14400, //seconds + 'display' => __('Every 4 hours') ] ], apply_filters('retailcrm_add_cron_interval', $schedules) diff --git a/src/include/class-wc-retailcrm-upload-discount-price.php b/src/include/class-wc-retailcrm-upload-discount-price.php index e80cac7..dca3213 100644 --- a/src/include/class-wc-retailcrm-upload-discount-price.php +++ b/src/include/class-wc-retailcrm-upload-discount-price.php @@ -27,6 +27,10 @@ if (!class_exists('WC_Retailcrm_Upload_Discount_Price')): public function upload() { + if (!$this->activeLoyalty) { + return; + } + $error = $this->uploadSettings(); if ($error !== '') { @@ -85,7 +89,10 @@ if (!class_exists('WC_Retailcrm_Upload_Discount_Price')): foreach ($chunks as $chunk) { $this->apiClient->storePricesUpload($chunk, $this->site); + time_nanosleep(0, 200000000); } + + unset($chunks); } catch (\Throwable $exception) { writeBaseLogs($exception->getMessage() . PHP_EOL . $exception->getTraceAsString()); diff --git a/src/languages/retailcrm-es_ES.l10n.php b/src/languages/retailcrm-es_ES.l10n.php index 5d58e8e..b33d9a0 100644 --- a/src/languages/retailcrm-es_ES.l10n.php +++ b/src/languages/retailcrm-es_ES.l10n.php @@ -248,6 +248,8 @@ return [ "bonuses" => "bonificaciones", "Use coupon:" => "Utiliza el cupón:", "Points will be awarded upon completion of the order:" => "Los puntos se concederán al finalizar el pedido:", + "Upload of current prices of offers" => "Upload of current prices of offers", + "Every 4 hours" => "Cada 4 horas" ], "language" => "es", "x-generator" => "GlotPress/2.4.0-alpha", diff --git a/src/languages/retailcrm-es_ES.mo b/src/languages/retailcrm-es_ES.mo index 051c9cfcec855e0095b33ddae0e6a9503a1de7f9..c6b63f806342f1594d2a3047378229442521aff3 100644 GIT binary patch delta 4338 zcmYk<3v`Zk0LStFv>RqK*D;sZHk-`-HkYwxGietYhH_i;ZdS7~BT?RzON){sj7gMR z!YmZ#UgVspbh<39qE085O5&VU^!>fh-#MP+_kRBW=Y8Ji`Cp&a#ic$Am-%>V8u+d- z9LGru(!<}F03T!0f^^oH$@Ps1#(WIGS=bhz#X7hNb!i3G#X}f^r;&fAnvZ698TGwi zs4*Qe6vK@1m;?$DoEVLLa1Ms!9_Iq{8MdZ=3H87_4UDOWjjV0348vut$&NrJm36CK@SY%Zgk#6p&k&6d}aC||4bSm>e)op_fxSs&c&v<8f#+( zszV1*4XHtm!0)K*Yd12c2}WTk&o})jsAuV@p&pHGFi#g?DXM{MFbVgeM(Q8zjXmjR z2Q0*VT!XRb$5S+AT~Xin!GV~IYS32nw4!jFLIz$y^`t8weQ}6&J|<8PC8jlmSmM_soIgYc;R{tR*pQxnGc>jyVEk&gZ}t38guiRi&Z`~h{t2*!_5 zH2J8JdIoj<5{$-u7>_mB3&R;UU7v}aFc;N<6{smX7S8y0qwq5)_-De{0%}kXR11@_ zC5}bfU}mFwyd3-C2K4p>)qtC*9=GI2x~>!UzX{a?d3z zW+^74FMfe^#(aZ;coWs)JIMMnjaZ>tob6GoJ|1;l3aUXvZ9NCofdcG|voQ*{AtUWE zXDR3h-y_SwTu0WS31Wg)VkFWja{xPHH8MCRknYA{H0pO5s1eD-EL@9PGuKg5)R(8y zSCffzu^4-5|6ik^8^-WjtA&G5Z>k4TLpB0=rg5Ws_yTG!m!TTE%6`Ao`Z?-BH&8>} zn1!J!XotFQHfm(2U@*@&FH+FrTaB9AJ*XT1ggN*R4#Np_S3_Tc`|%{|hR?NgrtoFd zP_IFq---da2eqiH&<{UBP1!m0sD@u&tRpzf27YRFjBS}KTT{B@&woX{L@M7;?QVlO<2`u?`nMJoqV zABgJN0_#i2ESePANMK-{m&q6)Gjq1Q0)bGntBV2_n5A%_Sf)?2oR73tl^`u#6 zXIBhBH7L{8-M0P|GU{dt>i2t*&Y6>#g@55gm`dx1ec%9bQ9Q6o{#(kKKHKm%h|XH z*W-iOF3CAR4Qo+fiCU~>*aEj8?k6c9T$3H67JZ z&m0PVx)8OPmY{yH(Yg)QpbAtEPN1gh8phx))B__JCQVsy)Lf^dUO1!B2X)LR45;^b zi_gj0|5^5gdY?*kQw>odnMsxq9ojl;$rO@HG<@o@)<-3Iis;xzc9YGjaH#QWpxyyv z$TOrlQ6u$yljkisy?)x3r{O-5NSH$Jp@pjBJ)#kNl+Zcvp>0)W%PXw!pw@_{XSzKn zT!f9}{f`?x-}`z5@50}7;B{RqZqV=FdTd{=nB-)0P$aeA=d7tQD=b0*R z$@$Q>3nQB9<;0`y_7c&NPuh{Ks&KqU)_F^;4}8*=*W(+u+|8P4jkfAlTTaqQjy)%4 zkpNqt>K%V7_mKkfEV=j4meTQrgE@+e$WpSHJV12xa`667${Nc3$WYRT%p2Z`_CZ76y?6+Tty|x^TN65RjewP*$29d+$b$hZq7LsIAYR^r?)nu5h zzm0E_Ew;V`i?r!Jps=g5WBn@uK0PXf!gA_`yNYsMIWvlj-GwEtsm1v@?&&&Hl$+}= zuFPvYFPQ7jcmF&jHYvL}FWWWA?J6jm&V}xre9X=%nUP(jizJo=*<>Y~L{?Ra;CUj8M1+QfNTR4}Q=!t}mZTv~N<~5l zRkW56UOla)wM1w1pGMI^%v7hA>Cn<@sWOy)zW3hDbov|L`}v)7&pqe2o!^r+*EPI( zZA0ac-5WO*WxE_G13Q$`&``?oE;(Dup?j6m#L?`?DICNWcIE}lr7O4x*RU%$(0}C) zAHCSde7}Pu_$qsrQYjs~m2!|1{Wyt7^8j8(7nGaWmk%)ye35(dHTK|Nnd`pbboT6C zd+<{3=lc5j4fW$YIoR{dy()R&^VIqBF7tp7=)cn7DS6P|KGL(H%=Z(yKbyH9 z7jbv4U^=vlI$7>wM&J?V`WLt_-(@$?FT3qs>)AfcQ1@ehj-pPL*-Qf$^JrF>k@__! z@;@BT@w$617jqbQFjMvk^L>M-PUT3ZLCaX_qq0tACbu#@`GkkFn}ax=lX(?$qu($+ z`;Zxl_QpRW(}T?%zW|EWpn+wwWsl);l$HU9Kt5eI+K%l6mMp3_y-!T zGQjv{geEZ8H*+wT@d$3_c)rA3-^H+vWIv|ivzV#5lw)|?0mfgwyyzo6_#4y0&)J*% zdL=x&jAeQ{lV9TLtoDRyKr7SZSD5SG=2(8j4jkrYSxaMRw#u>W$N3eNT)2`MiB{&q z&zT!_Ge23qeVLvNVy>Ud^l&W)@K#RXcAm&jIFZx()*7~ox$j0E&%2qasr*CbT9wb) z$ZPu5_JQl!$#E;w;>TzmmFJj6`4+R`OR7$&cDj^>_E+q z18KI(aHavXnFlRo*2o3S1K0B`-p+KeQ7bd_`?CYbk1+ljk}ua!oXA5RpTf-P)%ElD zFg<#ZnW`t5#r0DC{Cms;K4V6z`^ehw$Fh^-xwH(*0%mQkVj8lsqLQApGOy1|OoQI3 zAAeRq-fL7TM!gJRZZLy7RTgp{*Kr!(;7J^z7k*l<;4-#yG!OET5ueM9c;yV0St>U$ zJ^nLISLrau3g>91N2@u5cX2X5G{l7yNYvoE7QXFc_oL8t2JON)8LPpMbzS=Ja`d}ep$v*yp2cmcbvc;KDzM) zI#m{MV&4CaD(UeK&g94J&f~nqY0!MyL&|by3O3W5Se{{e^cNn-{7ugFE!5_67JKkU zW~%Pvsocpd;?alO;5@%fQt=0?oXXtzLgtUm7S87@+?QjV%-kQ(ywCHgtz`+#TDgV2 z_#D%rw|OLc9HFh)53?CFFs{E_P2tv zS_kq#j-kD%%wlJ*WP7e*f3D|2e1Pe|3(NyQrDamOYIR1cg#&n6MdbjMmTzqEQ(d-zrAHn3{>n$P29yDXPB`L1NxGc~))`6^lESqI;ipNUzhmUHSZ$@|~x z;D^#ISIAE#8_bpRbNQY$N?++GQ{_7O|MCMT7D=WlXgdeKI{|K zGP~}b+(WLdAKM1XQ*xqYV_7J-%5L(o+$`;6kbFf_X2=9NRxXj0A#$nwM2?ZCGyf?! z$*-hE9+&S(N)~2H7H0PJY|q(3x5{B+CoSKQ^W+KHUmlWdUbjigGMQGb)!wkj)Nhnc z)pMSIxk_HaUFFPrC96H<-Wt`db*1`!vY~!1Tv)H4z_ImuI9N`Wyml$SkQFjs?w6G7 zYyGeNYiEuVk5*6HNO**NU$Qf%G}Wl?3yak+laA6^#z{9x`K64O$#RBtl^;pUwi?wx z-4CkI*yy8^ERZdd@`$vTBXgj1s8RjL;~M`9LL8ce diff --git a/src/languages/retailcrm-ru_RU.l10n.php b/src/languages/retailcrm-ru_RU.l10n.php index 5afac2b..0c250ca 100644 --- a/src/languages/retailcrm-ru_RU.l10n.php +++ b/src/languages/retailcrm-ru_RU.l10n.php @@ -247,6 +247,8 @@ return [ "bonuses" => "бонусов", "Use coupon:" => "Используйте купон:", "Points will be awarded upon completion of the order:" => "По завершению заказа будет начислено баллов:", + "Upload of current prices of offers" => "Выгрузка текущих цен торговых предложений", + "Every 4 hours" => "Каждые 4 часа" ], "language" => "ru", "x-generator" => "GlotPress/2.4.0-alpha", diff --git a/src/languages/retailcrm-ru_RU.mo b/src/languages/retailcrm-ru_RU.mo index 5cb7b6941debe4458e6e9b8dbd8bccc9de072cf6..d499cfe44113d0b61a790831888a4bfe07dbcf7b 100644 GIT binary patch delta 4429 zcmYk<3s9A19>?)NEFvTjLS8_Pb5OxR@fv}b6_ph+&=7UiGIa?IycM8nWT%U0dCkmR zQxc`yrb0rtilOP6qsuUJXm@hkbf>0SW;Y!tZL)0|m;L_E`?AwB{m$okp7V0v=k~tU z>&JpR8iRcQhzZ$klnbOk$qO}$3^IErLcL}mM4Lt7G3<_?;9$Inci|1xr9WX04DD%l zH^yKbCZhVsBmdYmKKkMU9A@UT3b*4RCh|oyX5v-shkat4KNyV(wDV9mUXGFY6vkpT z>WBAY9yTIlvQF%U(OlFGlTiIb18tw#1S&~%OhMhS3|R!*h`PZx|@{iT?p&6b- zeg7H8<5$=Rf5vc(y~ml*0Mre$P%Bb^x_%k<=Ki*k3X`=O)XW-COWurLyySj=4K=_! zI0fUFmR4#ZX5bziiheA`JD7?~dpldV3-$dzoQxOIrxEpF*!SXKoPpV>ne5_YEFO04 zz=^cIth0V-32J8BQ7du4^(bnEPT^E+MO_!e%_49h>iZFK?7wc5MTho&p8G`==F{GS zDfk60#t_zR5*DL=_+4bR?FMSaf>|D2-xIZ^<8eF|VLH~LuD^_FcnubISi^fiDS z9ED}bHCBTf=}FW;FW~_E8X1#yqGldP=QvD8T~~w}Kox4{hfvow;TSxNp?KRzMThGS zvaJ@)K6r32>cXk0m8e2pcM0`FS5T+CAv}*6*vsP# z%r}FIes~e)VmWG$KE@q*2}5u`Cy~k8qZo!&r~z+4&YA5+9pVPm=|79Qt`)W9e|6jc zLQUW%4%hSFNo5cn!&!eV^=#B1JcgVbTZgGmK=T zwU@n7108^zM;n8>Zl-HFvii0gwc^JygZtY>D!M@s!`Bi=qE2%P>JVn4_IM8J1{<&l z_hA8cqPC!52=5GBiu%38`<$&zLoN9@RDUjZ$2sWJk}ssv4Od_|R-i_>9`$s*f|_Xy z>P8n(1H6LzyK5MNH<8`6+gKUIHt;NJE4HON19=B$({4fyAZ#e>uNg&i7cFfv>IX8A z->?O!71-eR*W!b;&*3bL;s9jf5*&?(P%HB}>i5R7Z1O=IiI1QT>2}oPTbsuEvs%_n zhX=pG3Jl?Ba&Bxb(q;QmKh%oa(<>N-otT4BBg}q{b5XDG-KZB)EqbsO8M9qSoq;$; zqX}mEs7$0%gcGn1^}}D|So{v7F?EzPpz){yEk>=>4%81FL(S+*)D3>XLhQw|c(4>F z<7V89ZMY15^LR#HpmH7`#vC4jH?a<<;N)~?02`4m+k-2x4VjFMrvmILyfq@^#|l07Qz8Z#eBRE*SXdq=hNCSQP<^}wPP}J4D2T? z!SsCApM!5T`OfqC8EUCJaV3tJ!Sdq)d>&J0IxBP(7tn6S%b4^K`;Gs_6fEIH2IEfb zg|DFo@;++dZKy-s+sC@cP)S1#WD*X=HOM`!4mILi$hOWBZ0x-Nnv zKN52>8LN=FSRJO}P1Jxrob)R6{fCN1SUH#fKnUXHfOpaU^0&?*>u~)Z)vuEk1wC*u z#$zaEx#nOD?HQxka6D$kOEq?_*ldn!+nT+%>PJ|;8C z10;fcKt_eW6%Q^i*vmZiEjsP5 zlKaW0Zr@_$d3Ss&KoeA`Z-cYqR1IChAbg3k$&WH(vu7#g`}$-b9SSv>A1^nmf-W` z6}PaG*X~;)9UEZu%IzbEBhXd?C)7s;wf5PUS7Pi((^=lX;E>7dRCQ` z6qi>tg(bDba$$XDb>9i?FZhr9o7-P(f7yScz1q{hk!FjB-qY>R`J4Qw{f+Hg==YzY YdEEb@{}f#x=^FnD|H&rrtkt3a1B870y#N3J delta 4278 zcmYk;2~bs49LMoLib#S;U=sN z63jFcB`sPkQm2_#(`MSWWpuK!nx@UMnWpdW-s4PX_`lCN_uhB!IsbFceVjcIw0VC} z;8axbCZilB;)&c4v(7KU>0wMz|Jr{z<$NcVSOF zf|ub}r~#cpbtJUApOIM9^+T}-&$nVK49+T1DP4+6c^&q~Mt8my)xl#p4!=WXsxQME zj%ApJTd)d`VJZ&l;ZI!&>U;%`#q}7_9G#@n3xCF93}+Y`NeLf0SnYfXN7Fusy3s)L zt&z<@W#V>JW)@>Uu6E}?z;N2%xbr_Dx3G&b%)e3;MKAQlEG)wDcolBInRpoUFp<3J z#k_y~9mqmurWAGEJRF8AFa%prYwDE%6{gYN!z4V7x={0> zOk|-htVi8wGint#p+>SBb^RBp5k@4M^}%G!#(bQNwKyCz2jq1=NuKgTp0DEu{wqc6)|2Zm3 zbp-REFZ4pzhh-w`(XPYCu^btsUBq;ZWtb$(CSZT8M15}^DkD2_27ZNFGnqV1Q?v|8 zf;D0-&$s&aXFQL}^dHy>g9nm-%~=;J>S1@(w&;(ccmwJ|x1u^&jrw982ICrJ_G}$4 z3NqV=nu3&6zayjY2HFLv4s1gW=y_B|52uoU-QZ&m@Ll^2m4P^Vq2t+j9o~#nu^GqU z1ssYaSP06@Jk$+8aej|OX#b8{oP9~Bwrw^tc$QQT;1vS9qI2teFD9j1aP2IQ#bFcv; z@f}o$PNF*WH!36jGW{D(M2%=6>H!bqG<*@0Q0W;9y>Tth#<}<_)?o=-qyz(}s60a@ zhb=G;zd&^$;c9tsm5S#01Zw;BA`L8P%SBx{8#QP5;uLH~W#(^ez&PH&dcY3+0WaVh{5qGc zVkLRj{Z66oAHuv%#}u5X{lA>bO&mCaH)A}*(_AgViMSm#=jV_=8#0E~jtejm&!Qd> zlIN#91L=b;aPGz4w9nxj3}aqcm$neA0#vqB(TKW`f2BABHCJUAj_+eCevZmS@U{M$ zNJn+N6qV|O7>#M;{arK$wYEx;KbyT#uW z5o!wRQ5pCE2jT^1G7E?GXoZ-p>+loYiA=xM6#6MYh^%vqFY>qL6jY{bi^%_cD*HI_ z5cVoIyB~L>Qj~T*|2$v;eum9>JFcAUulh6Cfp#P-Q*X*>R7VD)I$nrc#G6nV*^BDP z+o<=$p8+cBY4TKm`<0ipD{ufy zT^(z}$wB;HU>Dk9H~MQU(8E>wpiT_N2poxhu>eEx9_LbwqFsv`K_lw=!^n=b4^dMT z%F5L7Obo?5WLB&I>4#TVYyY#Tz0%BQ6~Xtt!t22MLO5z|=q5{vb%aV5@iZ}?(3D(7 zFlg_8gqKkZS!EB=O6*dD5>50VGKgEW|7)oz<(JAGuA)(|boGV!DlwK|m)TB2*-|-3 zbRx8aCK4*C#8$7yZ-(AyqCxo)U^&&1`)*syU6?b z)t-NVh$mE@AQlta5t^b(VlPoav=J(!iNjva{|F>vEYZ^)%*5fYE;N_BU3(*LbM*$S zarI|$yQ>d%vL3ynMXI9h_6$)-%qCn3*fMIJU2B1J6wV{G75`tf;8gDNv3GC{v7UIC z$Rkv;eQXRqMQDeyQ@x#3hmC}`dq3rW6BVu8Frt9CR3=cl)ivsoo$9^)I${dZorom3 zg||~yp~{^;)`IsE+OGdE9cYvghjdV1t|kVkK{?`MWq2iVfS5rf5l4v!i6-Jc;w?gZ zoOhDPs|}y(up^Txx@}aWhSwK2o&)#ow$v7l(?LDow!t9ag}b^PP}yK zgfjv2i5G~+-LYYKH!+@A>yFLBCyA-9EuPa(e8e5thxZV#5&K*BMuvp8w#RJj+FH}^ RP;_g4&it6xj?*ea{sRY