From bcddba46d45a620ec828c59f6c81514ef418a84d Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Mon, 30 Mar 2026 14:13:11 +0000 Subject: [PATCH] =?UTF-8?q?Remove=20kg/l=20units=20everywhere=20=E2=80=94?= =?UTF-8?q?=20only=20g=20(grammi)=20and=20ml=20(millilitri)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HTML: removed kg/l options from all unit selector dropdowns - JS detectUnitAndQuantity(): auto-converts kg→g (*1000) and l→ml (*1000) - JS unit labels: removed all kg/l entries from unitLabels maps - JS category defaults: frutta 1000g, verdura 500g, bevande 1000ml - JS step/min logic: simplified for g/ml only (no more 0.01 steps) - JS getSubUnitStep(): removed kg/l cases - JS isLowStock(): removed kg/l threshold - JS spec parser: labels now show g/ml instead of kg/L - PHP recipe parser: converts kg→g and l→ml immediately on parse - PHP AI prompt: updated to specify only g/ml/pz/conf units - PHP migration endpoint available at ?action=migrate_units (no-op if DB already clean) --- api/index.php | 74 +++++++++++++++++++++++++++++++++++++++------- assets/js/app.js | 75 +++++++++++++++++++---------------------------- data/dispensa.db | Bin 266240 -> 266240 bytes index.html | 10 +------ 4 files changed, 94 insertions(+), 65 deletions(-) diff --git a/api/index.php b/api/index.php index e66016c..a94af66 100644 --- a/api/index.php +++ b/api/index.php @@ -135,6 +135,10 @@ try { getClientLog(); break; + case 'migrate_units': + migrateUnitsToBase($db); + break; + // ===== SPESA ONLINE ===== case 'dupliclick_login': dupliclickLogin(); @@ -1480,7 +1484,7 @@ REGOLE IMPORTANTI: 5. Adatta le quantità per $persons persona/e 6. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile 7. La ricetta deve essere adatta al pasto: $mealLabel -8. IMPORTANTE - QUANTITÀ NUMERICHE: per ogni ingrediente dalla dispensa, il campo "qty_number" DEVE contenere il valore NUMERICO da scalare dall'inventario, espresso nella STESSA unità di misura della dispensa. Esempio: se in dispensa c'è "Farina: 1000 g" e la ricetta richiede 200g, qty_number = 200. Se "Riso: 2 kg" e servono 300g, qty_number = 0.3. Per ingredienti non dalla dispensa, qty_number = 0. +8. IMPORTANTE - QUANTITÀ NUMERICHE: per ogni ingrediente dalla dispensa, il campo "qty_number" DEVE contenere il valore NUMERICO da scalare dall'inventario, espresso nella STESSA unità di misura della dispensa. Le unità ammesse sono SOLO: g (grammi), ml (millilitri), pz (pezzi), conf (confezioni). NON usare mai kg o litri. Esempio: se in dispensa c'è "Farina: 1000 g" e la ricetta richiede 200g, qty_number = 200. Se "Riso: 2000 g" e servono 300g, qty_number = 300. Per ingredienti non dalla dispensa, qty_number = 0. 9. GESTIONE SMART QUANTITÀ: NON lasciare rimasugli poco usabili in dispensa. Se un ingrediente ha una quantità piccola (es. 50g di formaggio, 1 uovo, 100ml di latte), preferisci usarlo TUTTO piuttosto che lasciarne una quantità inutilizzabile. Se invece la quantità è abbondante, usa solo il necessario lasciando abbastanza per un altro pasto. Pensa sempre: "quello che resta sarà sufficiente per un altro utilizzo?" INGREDIENTI DISPONIBILI IN DISPENSA: @@ -1622,27 +1626,27 @@ PROMPT; $recipeVal = (float)str_replace(',', '.', $qm[1]); $ru = strtolower($qm[2]); if (strpos($ru, 'g') === 0) $recipeUnit = 'g'; - elseif ($ru === 'kg') $recipeUnit = 'kg'; + elseif ($ru === 'kg') { $recipeUnit = 'g'; $recipeVal *= 1000; } elseif ($ru === 'ml') $recipeUnit = 'ml'; - elseif ($ru === 'cl') $recipeUnit = 'ml'; // cl→ml - elseif ($ru === 'l' || strpos($ru, 'litr') === 0) $recipeUnit = 'l'; + elseif ($ru === 'cl') { $recipeUnit = 'ml'; $recipeVal *= 10; } + elseif ($ru === 'l' || strpos($ru, 'litr') === 0) { $recipeUnit = 'ml'; $recipeVal *= 1000; } elseif (strpos($ru, 'pz') === 0 || strpos($ru, 'pezz') === 0) $recipeUnit = 'pz'; elseif (strpos($ru, 'conf') === 0) $recipeUnit = 'conf'; } // Convert qty_number to inventory unit if mismatch detected if ($recipeUnit && $recipeUnit !== $invUnit) { - // Weight conversions + // Weight conversions (both should be 'g' now, but handle legacy 'kg') if ($recipeUnit === 'g' && $invUnit === 'kg') { $qtyNum = $recipeVal / 1000; - } elseif ($recipeUnit === 'kg' && $invUnit === 'g') { - $qtyNum = $recipeVal * 1000; - // Volume conversions + } elseif ($recipeUnit === 'g' && $invUnit === 'g') { + $qtyNum = $recipeVal; + // Volume conversions (both should be 'ml' now, but handle legacy 'l') } elseif ($recipeUnit === 'ml' && $invUnit === 'l') { $qtyNum = $recipeVal / 1000; - } elseif ($recipeUnit === 'l' && $invUnit === 'ml') { - $qtyNum = $recipeVal * 1000; - // g/kg/ml/l → pz (approximate to nearest piece) + } elseif ($recipeUnit === 'ml' && $invUnit === 'ml') { + $qtyNum = $recipeVal; + // g/ml → pz (approximate to nearest piece) } elseif ($invUnit === 'pz' || $invUnit === 'conf') { $defQty = (float)($bestMatch['default_quantity'] ?? 0); if ($defQty > 0) { @@ -3123,3 +3127,51 @@ function chatClear(PDO $db): void { $db->exec("DELETE FROM chat_messages"); echo json_encode(['success' => true]); } + +/** + * One-time migration: convert all kg→g and l→ml in products table, + * and scale inventory quantities accordingly. + */ +function migrateUnitsToBase(PDO $db): void { + $changes = 0; + + // Get products with kg or l units + $stmt = $db->query("SELECT id, unit, default_quantity, package_unit FROM products WHERE unit IN ('kg','l') OR package_unit IN ('kg','l')"); + $products = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($products as $p) { + $newUnit = $p['unit']; + $newDefQty = (float)$p['default_quantity']; + $newPkgUnit = $p['package_unit']; + $scaleInventory = false; + + if ($p['unit'] === 'kg') { + $newUnit = 'g'; + $newDefQty = $newDefQty * 1000; + $scaleInventory = true; + } elseif ($p['unit'] === 'l') { + $newUnit = 'ml'; + $newDefQty = $newDefQty * 1000; + $scaleInventory = true; + } + + if ($p['package_unit'] === 'kg') { + $newPkgUnit = 'g'; + if ($p['unit'] === 'conf') $newDefQty = $newDefQty * 1000; + } elseif ($p['package_unit'] === 'l') { + $newPkgUnit = 'ml'; + if ($p['unit'] === 'conf') $newDefQty = $newDefQty * 1000; + } + + $upd = $db->prepare("UPDATE products SET unit = ?, default_quantity = ?, package_unit = ? WHERE id = ?"); + $upd->execute([$newUnit, $newDefQty, $newPkgUnit, $p['id']]); + $changes++; + + // Scale inventory quantities (kg→g means multiply by 1000) + if ($scaleInventory) { + $db->prepare("UPDATE inventory SET quantity = quantity * 1000 WHERE product_id = ?")->execute([$p['id']]); + } + } + + echo json_encode(['success' => true, 'changes' => $changes]); +} diff --git a/assets/js/app.js b/assets/js/app.js index c71c4ee..adc2011 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -267,6 +267,8 @@ function detectUnitAndQuantity(quantityInfo) { let perUnitVal = parseFloat(multiMatch[2].replace(',', '.')); let perUnitUnit = multiMatch[3].toLowerCase(); if (perUnitUnit === 'cl') { perUnitUnit = 'ml'; perUnitVal *= 10; } + if (perUnitUnit === 'kg') { perUnitUnit = 'g'; perUnitVal *= 1000; } + if (perUnitUnit === 'l') { perUnitUnit = 'ml'; perUnitVal *= 1000; } return { unit: 'conf', quantity: perUnitVal, packageUnit: perUnitUnit, confCount: count, weightInfo: quantityInfo }; } // Match single package patterns like "500 g", "1 l", "750 ml", "1.5 kg" @@ -275,6 +277,8 @@ function detectUnitAndQuantity(quantityInfo) { let unit = match[2].toLowerCase(); let val = parseFloat(match[1].replace(',', '.')); if (unit === 'cl') { unit = 'ml'; val *= 10; } + if (unit === 'kg') { unit = 'g'; val *= 1000; } + if (unit === 'l') { unit = 'ml'; val *= 1000; } return { unit, quantity: val, weightInfo: quantityInfo }; } return { unit: 'pz', quantity: 1, weightInfo: quantityInfo }; @@ -990,7 +994,7 @@ async function loadDashboard() { const locInfo = LOCATIONS[item.location] || { icon: '📦', label: item.location }; const qty = parseFloat(item.quantity); const pkgSize = parseFloat(item.default_quantity); - const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; + const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' }; let qtyText = ''; if (item.unit === 'conf') { @@ -1061,9 +1065,7 @@ const QTY_THRESHOLDS = { 'pz': { min: 0.3, max: 50 }, 'conf': { min: 0.3, max: 50 }, 'g': { min: 3, max: 10000 }, - 'kg': { min: 0.005, max: 50 }, 'ml': { min: 3, max: 10000 }, - 'l': { min: 0.005, max: 50 }, }; function isSuspiciousQty(qty, unit) { @@ -1252,9 +1254,7 @@ function showAlertItemDetail(inventoryId, productId) { } function formatSubRemainder(amt, pkgUnit) { - const uL = { 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml' }; - if (pkgUnit === 'l' && amt < 1) return `${Math.round(amt * 1000)}ml`; - if (pkgUnit === 'kg' && amt < 1) return `${Math.round(amt * 1000)}g`; + const uL = { 'g': 'g', 'ml': 'ml' }; if (pkgUnit === 'ml' || pkgUnit === 'g') return `${Math.round(amt)}${uL[pkgUnit] || pkgUnit}`; return `${Math.round(amt * 10) / 10}${uL[pkgUnit] || pkgUnit}`; } @@ -1262,7 +1262,7 @@ function formatSubRemainder(amt, pkgUnit) { function formatQuantity(qty, unit, defaultQty, packageUnit) { if (!qty && qty !== 0) return ''; const n = parseFloat(qty); - const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; + const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' }; const label = unitLabels[unit] || unit || 'pz'; // Special handling for conf with partial packages @@ -1292,7 +1292,7 @@ function formatQuantity(qty, unit, defaultQty, packageUnit) { // Returns { mainQty: '10', unitLabel: 'conf', packageDetail: 'da 36g', fraction: '¼' } function formatQuantityParts(qty, unit, defaultQty, packageUnit) { const n = parseFloat(qty) || 0; - const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; + const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' }; const label = unitLabels[unit] || unit || 'pz'; // Special handling for conf with partial packages @@ -1601,7 +1601,7 @@ function editInventoryItem(id) {
@@ -1609,7 +1609,7 @@ function editInventoryItem(id) {
@@ -2423,12 +2423,12 @@ function onCategoryChange(fromAutoDetect = false) { 'latticini': { unit: 'pz', qty: 1 }, 'carne': { unit: 'g', qty: 500 }, 'pesce': { unit: 'g', qty: 300 }, - 'frutta': { unit: 'kg', qty: 1 }, - 'verdura': { unit: 'kg', qty: 0.5 }, + 'frutta': { unit: 'g', qty: 1000 }, + 'verdura': { unit: 'g', qty: 500 }, 'pasta': { unit: 'g', qty: 500 }, 'pane': { unit: 'pz', qty: 1 }, 'surgelati': { unit: 'g', qty: 450 }, - 'bevande': { unit: 'l', qty: 1 }, + 'bevande': { unit: 'ml', qty: 1000 }, 'condimenti': { unit: 'pz', qty: 1 }, 'snack': { unit: 'g', qty: 250 }, 'conserve': { unit: 'g', qty: 400 }, @@ -2824,7 +2824,7 @@ function editActionInventoryItem(inventoryId) {
@@ -2832,7 +2832,7 @@ function editActionInventoryItem(inventoryId) {
@@ -3270,9 +3270,7 @@ function onAddUnitChange() { // Convert between related units if logical if (unit === 'g' && currentQty <= 10) qtyInput.value = currentProduct.weight_info ? parseFloat(currentProduct.weight_info) || 250 : 250; - if (unit === 'kg' && currentQty > 100) qtyInput.value = (currentQty / 1000).toFixed(1); if (unit === 'ml' && currentQty <= 10) qtyInput.value = 500; - if (unit === 'l' && currentQty > 100) qtyInput.value = (currentQty / 1000).toFixed(1); if (unit === 'pz' && currentQty > 100) qtyInput.value = 1; if (unit === 'conf' && currentQty > 10) qtyInput.value = 1; } @@ -3283,8 +3281,6 @@ function updateAddQtyStep() { qtyInput.step = 'any'; if (unit === 'g' || unit === 'ml') { qtyInput.min = '1'; - } else if (unit === 'kg' || unit === 'l') { - qtyInput.min = '0.1'; } else { qtyInput.min = '1'; } @@ -3300,9 +3296,7 @@ function adjustAddQty(delta) { const unit = document.getElementById('add-unit').value; let val = parseFloat(qtyInput.value) || 0; let step; - if (unit === 'kg' || unit === 'l') { - step = val < 1 ? 0.1 : 0.5; - } else if (unit === 'g' || unit === 'ml') { + if (unit === 'g' || unit === 'ml') { step = val < 50 ? 1 : (val < 500 ? 10 : 50); } else { step = 1; @@ -3431,7 +3425,7 @@ async function submitAdd(e) { let qtyInfo = ''; if (result.total_qty) { const u = result.unit || 'pz'; - const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; + const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' }; const uLabel = unitLabels[u] || u; if (u === 'conf' && result.package_unit && result.default_quantity > 0) { const pkgLabel = unitLabels[result.package_unit] || result.package_unit; @@ -3536,7 +3530,7 @@ async function loadUseInventoryInfo() { // --- CONF MODE: show sub-unit controls --- const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0); const totalSub = totalConf * pkgSize; - const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; + const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' }; const subLabel = unitLabels[pkgUnit] || pkgUnit; _useConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel }; @@ -3568,9 +3562,9 @@ async function loadUseInventoryInfo() { }).join(' · '); const qtyInput = document.getElementById('use-quantity'); - qtyInput.value = (unit === 'kg' || unit === 'l') ? 0.1 : 1; + qtyInput.value = 1; qtyInput.step = 'any'; - qtyInput.min = (unit === 'kg' || unit === 'l') ? '0.01' : (unit === 'g' || unit === 'ml') ? '1' : '1'; + qtyInput.min = (unit === 'g' || unit === 'ml') ? '1' : '1'; document.getElementById('use-partial-hint').textContent = 'Oppure specifica la quantità usata:'; } } catch(e) { @@ -3607,9 +3601,7 @@ function switchUseUnit(mode) { function getSubUnitStep(pkgUnit) { switch (pkgUnit) { case 'ml': return 50; - case 'l': return 0.1; case 'g': return 10; - case 'kg': return 0.05; default: return 1; } } @@ -3625,16 +3617,13 @@ function adjustUseQty(direction) { } else { // Unit-aware step for normal mode const u = _useNormalUnit || 'pz'; - if (u === 'kg' || u === 'l') { - step = val <= 0.1 ? 0.01 : (val < 1 ? 0.1 : 0.5); - } else if (u === 'g' || u === 'ml') { + if (u === 'g' || u === 'ml') { step = val < 50 ? 1 : (val < 500 ? 10 : 50); } else { step = 1; } } - const minVal = ((_useNormalUnit === 'kg' || _useNormalUnit === 'l') && !_useConfMode) ? 0.01 : step; - val = Math.max(minVal, val + direction * step); + val = Math.max(step, val + direction * step); input.value = Math.round(val * 1000) / 1000; } @@ -3653,7 +3642,6 @@ function isLowStock(totalRemaining, unit, defaultQty) { if (defaultQty > 0) return totalRemaining <= defaultQty * 0.25; // Fallback fixed thresholds if (unit === 'g' || unit === 'ml') return totalRemaining <= 100; - if (unit === 'kg' || unit === 'l') return totalRemaining <= 0.15; return false; } @@ -3700,7 +3688,7 @@ function showLowStockBringPrompt(result, afterCallback) { const subTotal = Math.round(totalRemaining * defaultQty); remainLabel = `${subTotal}${result.product_package_unit}`; } else { - const unitLabels = { pz: 'pz', g: 'g', kg: 'kg', ml: 'ml', l: 'L', conf: 'conf' }; + const unitLabels = { pz: 'pz', g: 'g', ml: 'ml', conf: 'conf' }; remainLabel = `${Number.isInteger(totalRemaining) ? totalRemaining : totalRemaining.toFixed(1)} ${unitLabels[unit] || unit}`; } @@ -4481,10 +4469,10 @@ function parseQtyFromSpec(spec) { let val = parseFloat(m[1].replace(',', '.')); const unit = m[2].toLowerCase(); if (unit === 'g' || unit === 'gr') return { kg: val / 1000, label: val + 'g', type: 'weight' }; - if (unit === 'kg') return { kg: val, label: val + 'kg', type: 'weight' }; + if (unit === 'kg') return { kg: val, label: (val * 1000) + 'g', type: 'weight' }; if (unit === 'ml') return { kg: val / 1000, label: val + 'ml', type: 'weight' }; if (unit === 'cl') return { kg: val / 100, label: val * 10 + 'ml', type: 'weight' }; - if (unit === 'l' || unit === 'lt') return { kg: val, label: val + 'L', type: 'weight' }; + if (unit === 'l' || unit === 'lt') return { kg: val, label: (val * 1000) + 'ml', type: 'weight' }; } // Match unit count: 2 pz, 3 pezzi, 5, 2x, ~5 pz const pzMatch = s.match(/~?(\d+)\s*(pz|pezzi|x|$)/i); @@ -5792,7 +5780,7 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) { if (isConf) { const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0); const totalSub = totalConf * pkgSize; - const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; + const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' }; const subLabel = unitLabels[pkgUnit] || pkgUnit; _recipeUseConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel, _activeUnit: 'sub' }; @@ -5813,9 +5801,9 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) { `; } else { _recipeUseNormalUnit = unit; - const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml' }; + const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml' }; const unitLabel = unitLabels[unit] || unit; - const inputMin = (unit === 'kg' || unit === 'l') ? '0.01' : '0.1'; + const inputMin = '0.1'; qtySection = `

Quantità da usare (${unitLabel}):

@@ -5907,16 +5895,13 @@ function adjustRecipeUseQty(direction) { step = 0.5; } else { const u = _recipeUseNormalUnit || 'pz'; - if (u === 'kg' || u === 'l') { - step = val <= 0.1 ? 0.01 : (val < 1 ? 0.1 : 0.5); - } else if (u === 'g' || u === 'ml') { + if (u === 'g' || u === 'ml') { step = val < 50 ? 1 : (val < 500 ? 10 : 50); } else { step = 1; } } - const minVal = ((_recipeUseNormalUnit === 'kg' || _recipeUseNormalUnit === 'l') && !_recipeUseConfMode) ? 0.01 : step; - val = Math.max(minVal, val + direction * step); + val = Math.max(step, val + direction * step); input.value = Math.round(val * 1000) / 1000; } diff --git a/data/dispensa.db b/data/dispensa.db index 4d1f06ed58561555406c2e41445e16db7aa91125..3c6da906285027a06399ef8e79ae6eeb194ae995 100644 GIT binary patch delta 907 zcmX|o9LMMU&dj|tbMMUD@uuFJSjuWwX-mz=r59yS2(cu*5E0w9xZ>?fQSDZi zb9;UWXW(z%tO2St5pB@wxA?%rn?cekLp6~Z_e&?4y z8%m!I^`M?_1VPY+SK#|yx(vC-Z{lg;mG%%X2n?uCG^9L`iNAvqDdJ+BfRB7S+cK=;Lf*43yUs%j zyGYK%i!L~w9GvR*Nc7O7Rd&fpEw=xC@F%uM9wp)c^{gm_o{MrjsIJRmIgCY6mlxFWI>rnGE37e!raARtG)EFS<0J?8)ZP|FPFoaOR}}Ii16>gGyV`y2 z$KcDrSfELrp&)cYuJ&*CU6QKtKhVp|+ELiT1aa(=%zP5h1d?4|ZnCvaa8Sh)U$&<# zMLYU(XzopesY#d%%_--=CU8XOk49j~S)POfUP#TcK2Sf_vdGat`bRi-hG#!Kl>U tYiEL;fqr#VjmVRBe(AWZS&;@FIzOB58Sm8;e9Y@@?^3YSrHGq+{{d`m9{T_Q delta 927 zcmX|0$O6SZ{~t1zU#6s^RhSYJ@- zwC#y@g^99q+zw97bTCmTQhTcx$Iz%XR_04vbrZi>@r#N&1rhgcCvon4JDlJ1|NqbP zJUxfap2Ow<9{5n!k&ZgSwMpKAI*p_7)|1u#!u!2f)cYDHx803O3!`MI9U0sw$q1qK z;+C>cpQ(`jnJPs`v9Ps_CJdl+;&JwN;E0rleJp#r+a5nRODTVa^xx1cSfJ&7!`>I_ zV>06Db$_WWam|%Gq&6XS!#;AgTQX^LSVOUyA&pl1NsCZ=liLcZwXEafLVM~TIyULn zkN~EKn`pvMRtVtM#r*|f&v1xU4!PLMb1K&jNo9vFm7DQ0k!$XZSb?QY0=|CfKml$U zo6H-xfW_>~HD*mvKrAYsK5VD|_AxoUg}a{1%Z+ z_S0p;a^r+e49ol_k-Y5Fe7S91ACvr)B7XFjU|AJGDj)!?}HF(*LYZ12O zYJ;UoJCKPiWW;novb1SFtbR?7c}Cqwl|k2^@>%IA9LE>%D$)4DvGZdKi&JP9txMXI z?*1;kVo~AsRj{cTwp4hX96j|eY|*G_h>ZG=iy&oom__*YFY zew}x1fMfrSmrV@1xYZ8o|IUQ3+z3hMpY8B1){;~e%?@ay&LDhm2kgdav7|wMVLn!v zH*c`#cT~Q314Num7wn`Z1Rpz - -
@@ -440,9 +436,7 @@ @@ -458,9 +452,7 @@ @@ -997,6 +989,6 @@ - +