fix: compound shopping names + auto-Bring on depletion + panna da cucina
1. shopping_name compound-phrase map (computeShoppingName) Add phraseMap checked against the full product name BEFORE the single-token keyword loop. Prevents 'pane grattugiato' → 'Pane', 'panna da cucina' → 'Panna', etc. Key new phrases: - pane/pan grattugiato → Pangrattato - panna da cucina / panna cucina / panna chef → Panna da cucina - fette biscottate → Fette biscottate - aceto balsamico / glassa balsamico → Aceto balsamico - latte condensato/evaporato/vegetale/di soia/mandorla/avena/riso/cocco → specific - prosciutto cotto → Prosciutto cotto - farina di riso/mais/integrale → specific - pasta fresca, zucchero di canna, acqua minerale/frizzante/gassata, brodo, … Also added single-token safety-net entries: 'grattugiato'/'grattato'/'pangrattato' → 'Pangrattato', 'biscottate' → 'Fette biscottate'. 2. DB migration (sqlite3 UPDATE) Re-classified 10 products that had wrong shopping_name: Pane grattugiato → Pangrattato Panna da cucina (×4) → Panna da cucina Fette biscottate (×2) → Fette biscottate Aceto balsamico (×3) → Aceto balsamico Cleared 2 stale Gemini cache entries. 3. showLowStockBringPrompt (app.js) When totalRemaining <= 0 (product fully depleted), skip the modal entirely. The backend already auto-adds to Bring! on depletion; the JS only asks as a fallback if that failed (fire-and-forget async, never blocks the UI). The afterCallback (e.g. move-remainder modal, navigate to dashboard) is called immediately without user interaction.
This commit is contained in:
@@ -3665,6 +3665,70 @@ function computeShoppingName(string $name, string $category = '', string $brand
|
||||
fn($w) => mb_strlen($w) > 2 && !in_array($w, $stop)
|
||||
));
|
||||
|
||||
// 0. Compound-phrase map — checked against the FULL lowercase name (stop words included)
|
||||
// so multi-word product types are classified BEFORE single-token lookup.
|
||||
// This prevents "Pane grattugiato" → "Pane", "Panna da cucina" → "Panna", etc.
|
||||
$phraseMap = [
|
||||
// Breadcrumbs (MUST come before generic "pane")
|
||||
'pangrattato' => 'Pangrattato',
|
||||
'pan grattato' => 'Pangrattato',
|
||||
'pane grattato' => 'Pangrattato',
|
||||
'pane grattugiato' => 'Pangrattato',
|
||||
'pan grattugiato' => 'Pangrattato',
|
||||
// Cooking cream (MUST come before generic "panna")
|
||||
'panna da cucina' => 'Panna da cucina',
|
||||
'panna cucina' => 'Panna da cucina',
|
||||
'panna chef' => 'Panna da cucina',
|
||||
'panna acida' => 'Panna acida',
|
||||
// Plant-based milks (MUST come before generic "latte")
|
||||
'latte condensato' => 'Latte condensato',
|
||||
'latte evaporato' => 'Latte condensato',
|
||||
'latte di soia' => 'Latte di soia',
|
||||
'latte soia' => 'Latte di soia',
|
||||
'latte vegetale' => 'Latte vegetale',
|
||||
'latte di mandorla' => 'Latte di mandorla',
|
||||
'latte mandorla' => 'Latte di mandorla',
|
||||
'latte di avena' => 'Latte di avena',
|
||||
'latte avena' => 'Latte di avena',
|
||||
'latte di riso' => 'Latte di riso',
|
||||
'latte riso' => 'Latte di riso',
|
||||
'latte di cocco' => 'Latte di cocco',
|
||||
'latte cocco' => 'Latte di cocco',
|
||||
// Baked bakery — different from bread
|
||||
'fette biscottate' => 'Fette biscottate',
|
||||
'pan di spagna' => 'Pan di Spagna',
|
||||
// Specific vinegars
|
||||
'aceto balsamico' => 'Aceto balsamico',
|
||||
'glassa balsamico' => 'Aceto balsamico',
|
||||
'glassa balsamic' => 'Aceto balsamico',
|
||||
// Cold cuts — specific cuts
|
||||
'prosciutto cotto' => 'Prosciutto cotto',
|
||||
// Flour subtypes
|
||||
'farina di riso' => 'Farina di riso',
|
||||
'farina riso' => 'Farina di riso',
|
||||
'farina di mais' => 'Farina di mais',
|
||||
'farina mais' => 'Farina di mais',
|
||||
'farina integrale' => 'Farina integrale',
|
||||
// Fresh pasta
|
||||
'pasta fresca' => 'Pasta fresca',
|
||||
// Broth / stock
|
||||
'brodo vegetale' => 'Brodo',
|
||||
'brodo pollo' => 'Brodo',
|
||||
'brodo manzo' => 'Brodo',
|
||||
// Sugar subtypes
|
||||
'zucchero di canna' => 'Zucchero di canna',
|
||||
'zucchero canna' => 'Zucchero di canna',
|
||||
// Water
|
||||
'acqua frizzante' => 'Acqua',
|
||||
'acqua gassata' => 'Acqua',
|
||||
'acqua minerale' => 'Acqua',
|
||||
];
|
||||
foreach ($phraseMap as $phrase => $canonical) {
|
||||
if (mb_strpos($lower, $phrase) !== false) {
|
||||
return $canonical;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Curated keyword → canonical group name.
|
||||
// Extended list covers the most common Italian pantry items and avoids Gemini calls.
|
||||
$keywordMap = [
|
||||
@@ -3702,6 +3766,11 @@ function computeShoppingName(string $name, string $category = '', string $brand
|
||||
'piadelle' => 'Piadina',
|
||||
'biscotto' => 'Biscotti',
|
||||
'biscotti' => 'Biscotti',
|
||||
// Breadcrumbs single-token safety net (phrase map has priority, but just in case)
|
||||
'grattugiato' => 'Pangrattato',
|
||||
'grattato' => 'Pangrattato',
|
||||
'pangrattato' => 'Pangrattato',
|
||||
'biscottate' => 'Fette biscottate',
|
||||
// Dairy
|
||||
'latte' => 'Latte',
|
||||
'yogurt' => 'Yogurt',
|
||||
|
||||
@@ -5922,6 +5922,29 @@ function showLowStockBringPrompt(result, afterCallback) {
|
||||
const defaultQty = result.product_default_qty || parseFloat(currentProduct?.default_quantity) || 0;
|
||||
const totalRemaining = result.total_remaining;
|
||||
|
||||
// ── Fully depleted: no need to ask — backend already added to Bring! ──
|
||||
// Skip the modal entirely and proceed to the next step (e.g. move modal).
|
||||
if (totalRemaining <= 0) {
|
||||
// Backend auto-adds to Bring! when fully depleted. If it failed (Bring not
|
||||
// configured, or product already on list), silently attempt it from JS.
|
||||
if (!result.added_to_bring && name) {
|
||||
// Fire-and-forget — don't block the callback
|
||||
(async () => {
|
||||
try {
|
||||
const spec = name;
|
||||
const payload = { items: [{ name, specification: spec }] };
|
||||
if (shoppingListUUID) payload.listUUID = shoppingListUUID;
|
||||
const data = await api('bring_add', {}, 'POST', payload);
|
||||
if (data.success && data.added > 0) {
|
||||
showToast('🛒 Prodotto finito → aggiunto a Bring!', 'info');
|
||||
}
|
||||
} catch(_e) { /* silent */ }
|
||||
})();
|
||||
}
|
||||
if (afterCallback) afterCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLowStock(totalRemaining, unit, defaultQty)) {
|
||||
if (afterCallback) afterCallback();
|
||||
return;
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
"405ea6ec33d54042d046599650f422ea": "Succo",
|
||||
"f624c420f14d8eff122c0bb395eb63da": "Snack Dolci",
|
||||
"92751fbb97923590c402bc7810778b36": "Biscotti",
|
||||
"0e342f4f977e814b2108e8e0475a57d5": "Aceto",
|
||||
"edd038513b2641005bd36884f90789c1": "Pane",
|
||||
"8727f7abcb66764b5eb3d1f036bc18b8": "Tè",
|
||||
"0eb53fe1a5d4d106eac47c8a81d1afe7": "Farina",
|
||||
"0ebada5597d1d166d0ed8f49500bfeba": "Verdure",
|
||||
|
||||
Reference in New Issue
Block a user