From c63faf56e4c046301b0fdbc93ef422ad073b9b94 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Mon, 30 Mar 2026 13:36:51 +0000 Subject: [PATCH] Conservative Bring! cleanup + operations log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cleanupObsoleteBringItems() now much more conservative: * Only removes items matching a known DB product (preserves manual additions) * Only removes if the product has current_qty > 0 (has stock) * AND item is no longer flagged by smart shopping - Added logOperation() — stores all Bring! operations in localStorage '_opLog' (bring_auto_add, bring_cleanup, bring_found, bring_manual_remove) Capped at 200 entries, each with timestamp + action + details - All Bring! add/remove paths now log their operations --- assets/js/app.js | 64 ++++++++++++++++++++++++++++++++++++----------- data/dispensa.db | Bin 266240 -> 266240 bytes index.html | 2 +- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index b84b127..787c855 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -4310,6 +4310,7 @@ async function confirmShoppingItemFound() { const idx = shoppingItems.findIndex(i => i.name.toLowerCase() === name.toLowerCase()); if (idx >= 0) shoppingItems.splice(idx, 1); showToast(`✅ ${name} rimosso dalla lista!`, 'success'); + logOperation('bring_found', { name }); loadShoppingCount(); } } catch (e) { console.error('confirmShoppingItemFound', e); } @@ -4327,42 +4328,61 @@ async function autoAddCriticalItems() { const result = await api('bring_add', {}, 'POST', { items: itemsToAdd, listUUID: shoppingListUUID }); if (result.success && result.added > 0) { showToast(`🔴 ${result.added} prodott${result.added === 1 ? 'o urgente aggiunto' : 'i urgenti aggiunti'} automaticamente a Bring!`, 'success'); + logOperation('bring_auto_add', { added: itemsToAdd.map(i => i.name) }); loadShoppingList(); } } catch (e) { /* ignore */ } } /** - * One-time cleanup: remove items from Bring! that the new smart algorithm no longer considers relevant. - * Compares current shoppingItems vs smartShoppingItems — if a Bring! item has NO match in - * smart predictions (or matches a product with urgency 'none' that was filtered out), remove it. + * One-time cleanup: remove items from Bring! that were auto-added but the algorithm no + * longer considers relevant. CONSERVATIVE: only removes items that match a known product + * in our inventory with current_qty > 0 AND that no longer appear in smart predictions. + * Items not matching any DB product are left untouched (likely manually added by user). */ async function cleanupObsoleteBringItems() { if (sessionStorage.getItem('_bringCleanupDone')) return; sessionStorage.setItem('_bringCleanupDone', '1'); if (!shoppingItems.length || !smartShoppingItems.length) return; - // Build a set of names that the smart algorithm still considers relevant + // Build set of smart-flagged names (these should stay) const smartNames = new Set(smartShoppingItems.map(i => i.name.toLowerCase())); - // Find Bring! items that have NO match in smart predictions AND are not manually added - // (We consider items "auto-added" if their name matches a known product in our DB) + // Load all products from our DB to cross-reference + let allProducts = []; + try { + const res = await api('products'); + allProducts = res.products || res.data || []; + } catch (e) { return; } + if (!allProducts.length) return; + + // Index our products by lowercase name + const dbByName = {}; + for (const p of allProducts) { + dbByName[p.name.toLowerCase()] = p; + } + const toRemove = []; for (const item of shoppingItems) { const nameLower = item.name.toLowerCase(); - // If smart shopping still flags this item, keep it + // If smart shopping still flags it, keep it if (smartNames.has(nameLower)) continue; - // Check with token matching too - const smartMatch = _findSimilarItem(item.name, smartShoppingItems); - if (smartMatch) continue; - // This item is no longer predicted by smart shopping — candidate for removal + if (_findSimilarItem(item.name, smartShoppingItems)) continue; + + // Must match a known DB product — otherwise it's likely a manual addition + const dbProduct = dbByName[nameLower] || _findSimilarItem(item.name, allProducts); + if (!dbProduct) continue; + + // Only remove if we have stock (qty > 0) — if qty == 0 user may still need it + if ((dbProduct.quantity || 0) <= 0) continue; + toRemove.push(item); } if (toRemove.length === 0) return; - // Remove them from Bring! let removed = 0; + const removedNames = []; for (const item of toRemove) { try { const r = await api('bring_remove', {}, 'POST', { @@ -4370,16 +4390,31 @@ async function cleanupObsoleteBringItems() { rawName: item.rawName || '', listUUID: shoppingListUUID }); - if (r.success) removed++; + if (r.success) { removed++; removedNames.push(item.name); } } catch (e) { /* ignore individual failures */ } } if (removed > 0) { - showToast(`🧹 ${removed} prodott${removed === 1 ? 'o non più necessario rimosso' : 'i non più necessari rimossi'} dalla lista`, 'info'); + showToast(`🧹 ${removed} prodott${removed === 1 ? 'o con scorte sufficienti rimosso' : 'i con scorte sufficienti rimossi'} dalla lista`, 'info'); + logOperation('bring_cleanup', { removed: removedNames }); loadShoppingList(); } } +/** + * Log an app operation (not a food transaction) for auditing/debugging. + * Stored in localStorage under '_opLog', capped at 200 entries. + */ +function logOperation(action, details) { + try { + const log = JSON.parse(localStorage.getItem('_opLog') || '[]'); + log.push({ ts: new Date().toISOString(), action, details }); + // Keep last 200 entries + if (log.length > 200) log.splice(0, log.length - 200); + localStorage.setItem('_opLog', JSON.stringify(log)); + } catch (e) { /* ignore */ } +} + const DEFAULT_SPESA_AI_PROMPT = `Sei un assistente per la spesa online. Ti viene dato il nome di un prodotto che l'utente vuole comprare e una lista di prodotti trovati nel catalogo del supermercato. Regole di selezione: @@ -5092,6 +5127,7 @@ async function removeBringItem(idx) { shoppingItems.splice(idx, 1); renderShoppingItems(); showToast('Rimosso dalla lista', 'success'); + logOperation('bring_manual_remove', { name: item.name }); // Update dashboard shopping count loadShoppingCount(); } diff --git a/data/dispensa.db b/data/dispensa.db index 09223b11bead085e6d0f68d149ec52519b698d7c..26a6b3b0e4189702f78cf8c4081eed6e214daa35 100644 GIT binary patch delta 3209 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 HJJSCTG0uAp delta 1876 zcma)7e{2&~9KUzh?t0f=ySZqm$SBp}26Vk2{jpsXEZf|=k&XRw#8g<o*sO5ZC$h3fGq(jAtK5`db8~h#0=Ny z&5B~nP%*hxNGeil9MQ5rt*t4?*5OeV-|&Gtw_%g|PcDTSSG!j{U_MSGLM?WJw$K>)mZ6f!Vk=$0~iRBM>&vcka`nejT92A=ctY(sYc zm2@>=4;Vo$PLo5#86tjw1-$xB&nLM$0L}sf^%Am9ogvm#2AW6&*uip+Ga)Vc3^1(; z12ty0(ZP6mFU_;QTp9x(8^M!W9RX^l3J#B-rx})JeJ+}FF{}>F_<6?D*H((hwddp2 z6j!lqam3ILhT~};@AVf=g{Uly(oi%peh1^>IhyBu1FbOX(mc!DIRf~%njW-iy1tE? zWg4u^Ry-ZGX@~ZK5vjY!w-Wy0NM^}_|d#=(5me|2`sr|C&6HVtTx;> z5Pjw`{AZ0^03OuzVXk=r;JCrG3u%G!1hm<|z`#KN7Ck3a*NB@aRQY0&BzIQ?s6P~+ zg>;Kf*5_auC?I_9F)=hulIIw0%}o%*G^7q9)vO53*hvJn<9QThCJ(dr$2awMsIb}N zqFJr+26$9kcLP+C`T9UOFRfW`foEC+Qc-yV*_PklS=@IQUOyy|B*W7snOUz+5-ROs z%nVWs$vmF>{1zC*DsJ72&be4xtLR57SC&%AGvHXB*j4m1u>^Y5@+znQ0Y4q(PruOztiuq4Z3 zGPYSxi4h^a-3jAGMjc|(p`^meh$uOEXcNMz$gmJ~@=7Wrtai#NDVm8Wx@&P5gg!6r z^U=Q5&SW?tIC*F9hL)xdM_*5%vnkl+Tl+s*%`mlic>F)M{zP#)CWx_Nr9UY`7S4`VJwiA#8jcCgLOPOyHXRYNmZCZr zmiBS*N)#IOh*Jr;v_BPxzF|d?(|pJsa*vIT)kiaOT#UeHv|dy~Zh2Tzq>vld!5ebZ z4Iww4{0Os^}vfcN27)XtP7pja0hlI8(6*y&CNS8?gX|G gAJ*`1vD5Gro+{HuLZ~IDT*UU)!ud5D{C8FT3%BJG7XSbN diff --git a/index.html b/index.html index f3d41e4..83f92e9 100644 --- a/index.html +++ b/index.html @@ -997,6 +997,6 @@ - +