PHP getStats() opened section:
- Primary detection: opened_at IS NOT NULL (reliable, set by useFromInventory)
Fallback: fractional-qty pattern (legacy items)
- Per-item compute opened_expiry = min(opened_at + estimateOpenedExpiryDaysPHP, original_expiry)
→ vacuum_sealed items get 1.5× multiplier
→ always take sooner of 'opened shelf life' vs 'original sealed expiry'
- Add days_to_expiry, opened_expiry, is_edible, has_opened_at to each item
- Filter legacy items (no opened_at) with expiry > 14 days (too much noise)
- Sort by days_to_expiry ASC (soonest/spoiled first) instead of updated_at DESC
JS dashboard opened render:
- Expiry badge: ⛔ Scaduto / ⚠️ Scade oggi / ⏰ Xgg (urgent≤2, soon≤5, ok>5)
- 🔒 icon added when vacuum_sealed=1
- Spoiled items shown with strikethrough name + muted styling (.alert-item-spoiled)
- Cap display at 10 items; 'e altri N prodotti aperti...' note if more
- Sort comes from server (removed JS openedFraction sort)
CSS:
- .opened-expiry-{ok,soon,urgent,today,spoiled} badge classes
- .alert-item-spoiled strikethrough styling
- .alert-more-note
Root cause: autoAddCriticalItems used stale in-memory cache (old critical items)
and re-added items to Bring right after manual removal, because on_bring was
now false but urgency was still 'critical' in the old cache.
PHP smartShopping():
- Rename stockByFirstToken → stockByAnyToken (indexes ALL significant tokens)
- 'Passata di pomodoro' depleted + 'Polpa di pomodoro' in stock → share token
'pomodoro' → passata no longer flagged as critical (COVERS: passata/polpa/pelato
and any future tomato product variant)
- Same logic: 'aglio'/'aglio rosso', 'latte'/'latte di montagna', etc.
JS loadSmartShopping():
- When critical item set changes (items added OR removed), immediately reset
_autoAddedCriticalTs and _bringCleanupTs so next shopping load uses fresh data
instead of debounced old data
JS cleanupObsoleteBringItems():
- Use any-token matching (like PHP) for both stockByAnyToken and urgentSmartByToken
→ 'Passata di pomodoro' in Bring, 'polpa' in stock → share 'pomodoro' → removed
BRING! REMOVAL FIX (latte/aglio not removed after shopping):
- PHP addToInventory: replace exact strcasecmp with token-based fuzzy
matching (same logic as _productOnBring) so custom Bring item names
and translated catalog keys both match correctly
- JS submitAdd: add client-side fallback — if PHP removal missed the item,
use _findSimilarItem against the loaded shoppingItems and call bring_remove
MULTI-EXPIRY BATCHES (when buying conf with different expiry dates):
- Add form (unit=conf): shows '+ Lotto con scadenza diversa' button
- Each extra batch has its own qty + expiry date input with +/- controls
- On submitAdd, extra batches are submitted as additional inventory_add calls
(separate DB rows, separate expiry dates)
- Multi-batch section hidden in 'Ce l'avevo già' mode and for non-conf units
- Re-shown/hidden when switching unit via onAddUnitChange
RECIPE COOKING STEPS - FIFO ingredient display:
- renderCookingStep: each ingredient row now shows brand chip, location chip,
and expiry date chip (color-coded: red ≤3d, yellow ≤7d)
- PHP already selected earliest-expiry inventory entry (ORDER BY days_left ASC
with > not >= ensures first/earliest match wins)
- CSS: .cooking-ing-meta, .cooking-ing-chip, .exp-soon, .exp-close,
.multi-batch-row, .multi-batch-qty, .multi-batch-date, .btn-icon-sm
- PHP smart_shopping: add absolute stock fallback that flags conf/pz items
with <=2 units (medium) or <=1 unit (high) and g/ml at <=20% of default,
regardless of usage frequency. Fixes products like Panna da cucina that
are rarely used but running low and were invisible to the frequency-based
urgency logic (pctLeft was 66% since last purchase was 3 at once).
- JS isLowStock(): return true (not false) when totalRemaining <= 0.
A fully depleted item is definitely low-stock; the Bring! add prompt
should fire when you use the very last unit.
- PHP: new 'expiry_history' action computes avg shelf life (expiry_date - added_at)
from inventory table for the same product_id (last 730 days, valid entries only)
- JS: _fetchExpiryHistoryAndUpdate() fires async after showAddForm() renders
and replaces the rule-based estimate with the historical average if available
- Labeled with '📊 storico' badge on the estimate line (tooltip shows sample count)
- recalculateAddExpiry() and selectPurchaseType('new') both honour window._historyExpiryDays
- Vacuum-sealed multiplier still applied on top of historical base
- Falls back silently to rule-based estimateExpiryDays when no history exists
- 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)
- Products bought only once (buy_count=1) and out of stock are skipped entirely
(not enough history to predict repurchase need, e.g. piadelle)
- usesPerMonth for items tracked 7-30 days now normalized instead of using raw count
- usesPerMonth for items tracked <7 days halved to avoid inflation
- Critical urgency now requires buy_count >= 2
L'algoritmo ora calcola:
- usesPerMonth: utilizzi al mese (non piu solo useCount totale)
- daysSinceLastUse: giorni dall'ultimo utilizzo
- isFrequent: >= 1.5 utilizzi/mese
- isRegular: >= 0.5 utilizzi/mese (almeno 1 ogni 2 mesi)
- isRecent: usato/comprato negli ultimi 60 giorni
Nuova logica urgenze:
- CRITICAL (esaurito): solo se frequente E recente
- HIGH (esaurito): regolare E recente E storico consistente
- Quasi finito/scorta bassa: solo per prodotti regolari/recenti
- Previsione esaurimento: solo per frequenti E recenti
- Prodotti usati raramente o non di recente vengono esclusi
- Counter nei tab aggiornati dinamicamente
- Auto-aggiunta prodotti CRITICI a Bring! al caricamento (1x per sessione)
- Badge urgenza e frequenza sugli item in lista (cross-ref smart shopping)
- Tag locali per item (Urgente/Priorità/Verificare) con menu dropdown
- Ordinamento automatico per frequenza utilizzo (item più usati in cima)
- Tap su un item → scanner barcode, con banner 'Trovato! Rimuovi dalla lista'
- Fix pctLeft: usa max(1, qty) come fallback refQty per evitare falsi alert
- Fix daysLeft capped a 365gg per pulire stringhe di previsione
- Back button on action page → torna a shopping se aperto da lista
- API returns total_remaining, product_name, unit, default_qty after use
- isLowStock() detects when inventory is running low (pz<=2, conf<=1, weight/vol<=25% of default)
- After using a product, if low stock detected, shows prompt asking to add to Bring shopping list
- Works in main USE form, USE ALL, and recipe ingredient USE
- Prompt chains properly: low-stock then move-after-use (if applicable)
- Skips prompt when product is fully depleted (already auto-added to Bring)
- Fixed isSuspiciousDefaultQty: for conf products, checks package_unit thresholds
(375g is fine for g-max=10000, not checked against conf-max=50)
- Auto-split on use: when using from a conf product with whole+fractional qty,
automatically separates whole confs from opened part
e.g. Panna 2.6conf → use 5g → 2conf (sealed) + 0.56conf (opened)
- Move modal now moves only the opened row (via opened_id)
- Use query prefers fractional rows (opened packages) first
- Non-conf products still get standard move-after-use behavior
- Added 'Ricetta veloce' button between stat cards and expired section
(navigates to chat and auto-asks Gemini for a recipe with expiring products)
- Added waste vs consumption mini chart between expiring and opened sections
(horizontal bar showing used/wasted ratio from last 30 days)
- API: getStats() now returns used_30d and wasted_30d counts
- Cache busters updated to 20260316c
- Dopo aver usato un prodotto, se rimane quantità mostra modal con opzione
di spostarlo in un'altra posizione (es. dispensa→frigo dopo apertura)
- La scadenza viene ricalcolata per la nuova posizione
- Fix marmellata: default_quantity era 0 → non appariva tra prodotti aperti
- Auto-set default_quantity al primo add per prodotti g/ml/kg/l senza pkg size
- Versione: 20260316b
- Query SQL ora rileva prodotti aperti per tutte le unità dove la quantità
non è multiplo della dimensione confezione (soglia 2% per evitare errori arrot.)
- Frontend: rendering differente per conf (X conf + rimanente) e altri (Xg / Yg)
- Versione: 20260315i
- Scanner: usa BarcodeDetector API nativa (Chrome Android) come primario, Quagga come fallback
- Settings: aggiunta tab Fotocamera per scegliere posteriore/anteriore/specifica
- Scanner feedback: barra verde (scansione attiva), gialla (barcode rilevato)
- Ricette: invio titoli ricette del giorno per evitare duplicati nello stesso giorno
- Debug: sistema di logging remoto (client_debug.log) per diagnostica da dispositivi chioscati
- Fix: permessi .env per scrittura da Apache
- Nuovo campo package_unit in DB (migrazione automatica)
- Form aggiungi/modifica: quando si seleziona 'conf', appare campo per
specificare il contenuto della singola confezione (es. 300g, 2L)
- Visualizzazione: '3 conf (da 300g)' in inventario, dettaglio, butta
- formatQuantity aggiornato con supporto package_unit
- API: salva/restituisce package_unit in tutti gli endpoint
- Ricette e chat: contesto arricchito con info confezione
- CSS: stili per il nuovo campo conf-size
- Gemini star icon button next to camera in header
- Full chat page with message bubbles, typing indicator
- Conversation history persisted in localStorage (last 50 messages)
- System context includes: full inventory with expiry dates, appliances, dietary restrictions
- Multi-turn conversation with Gemini 2.0 Flash
- Pre-built suggestion chips: snack, juice/smoothie, light meal, use expiring items
- Clear chat button for fresh conversations
- Indigo/purple themed UI matching Gemini branding
- PHP gemini_chat API endpoint with inventory context injection
- Fix qty_number nelle ricette: validazione e conversione automatica
delle unità di misura (g↔kg, ml↔L, g→pz con default_quantity)
- Sanity check: cap qty_number al disponibile, correzione valori
assurdamente piccoli dovuti a errori di Gemini
- Aggiunto indicatore confezione (¼, ½, ¾, pieno) vicino alla
quantità nell'inventario e nella barra stato dopo scansione
- Aggiunto default_quantity nella query inventory_list
- Nuova funzione formatPackageFraction() per calcolo frazioni
- Icona ingranaggio nella navbar, salvataggio su localStorage e .env
- Preview prodotto più grande dopo scansione barcode
- Controllo inventario dopo scan: mostra quantità disponibile in grande
- 3 pulsanti contestuali (AGGIUNGI/USA/BUTTA) se prodotto già presente
- Funzionalità BUTTA con modale per quantità parziale o totale
- Quantità prominenti nella lista inventario
- Quantità visibili negli alert scadenza/scaduti in dashboard
- Unità di misura modificabile nella modale di modifica inventario
- Opzioni ricetta: Pasto Veloce, Poca Fame, Priorità Scadenze, ecc.
- Gestione smart quantità ricette (evita rimasugli inutilizzabili)
- Elettrodomestici configurabili per suggerimenti ricette
- Restrizioni alimentari nel prompt ricette
- Endpoint API: save_settings, get_settings
1. Bug scan→usa: ora auto-seleziona la posizione corretta (frigo/dispensa/freezer)
dove il prodotto è effettivamente presente, non più sempre 'dispensa'
2. Icona telecamera header: più grande (1.8rem/52px) e più centrata nel cerchio
3. Log: icone/colori differenziati - ➕ verde aggiunte, ➖ rosso uscite,
🛒 blu Bring! - sfondo tintato per ogni tipo
4. Operazioni Bring! loggate come transazioni tipo 'bring' nel diario
- Nuova sezione 'Log' nella bottom nav con icona 📒
- Mostra tutte le transazioni (entrate/uscite) raggruppate per data
- Ogni voce: icona 📥/📤, nome prodotto, marca, quantità, posizione, orario
- Bordo verde per aggiunte, rosso per uscite
- Paginazione con 'Carica altri...' (50 per pagina)
- Backend: aggiunto supporto offset a listTransactions
- useFromInventory controlla se il prodotto è esaurito ovunque (qty=0 in tutte le location)
- Se finito, lo aggiunge automaticamente alla lista Bring! con nome e marca
- Toast notifica '🛒 Prodotto finito → aggiunto a Bring!' in tutte le UI (ricetta, uso diretto, usa tutto)
- Prosciutto crudo light e Philadelphia Light aggiunti manualmente a Bring!
- Backend: aggiunge brand e expiry_date all'arricchimento ingredienti
- Frontend: sotto ogni ingrediente dalla dispensa mostra:
- Marca in corsivo (se presente)
- Icona posizione (🧊 Frigo/Freezer, 🗄️ Dispensa)
- Scadenza con colore urgenza (⛔ scaduto, 🔴 <3gg, 🟡 <7gg, 📅 data)
- Nuovo stile CSS per riga dettaglio ingrediente
- Ricetta cachata in localStorage: riaprendo mostra la stessa ricetta
- Si rigenera solo se cambia tipo pasto (colazione/pranzo/cena) o bottone 'Generane un'altra'
- Prompt Gemini aggiornato: richiede qty_number nella stessa unità di misura dell'inventario
- Bottone 'Usa' ora scala la quantità effettiva della ricetta (non più sempre 1)
- Estratta renderRecipe() per riutilizzo con cache
- Aggiunto bottone '🔄 Generane un'altra' nel risultato ricetta