Fix smart shopping: skip depleted products with equivalent in-stock substitutes
PHP smartShopping(): - Add nameTokens() helper (mirrors JS _nameTokens) - Build stockByFirstToken map before product loop - Skip depleted (qty=0) products whose first token has stock elsewhere → 'Aglio rosso' depleted but 'Aglio' qty=3 → skip → 'Latte Parzialmente Scremato' depleted but 'Latte di Montagna' 4.8 conf → skip → 'Muesli Frutta Secca' depleted but 'Muesli multifrutta' 930g → skip - Result: 13→9 items, no more false critical flagging for covered products JS cleanupObsoleteBringItems(): - Rewrite with stockByFirstToken approach (aggregate by first token, not product_id) - urgentMatch logic: if smart item is completely depleted (qty=0) but equivalent stock exists via first token → still remove from Bring (need is covered) - Only keep Bring item if: smart flags it with current_qty>0 (genuinely running low) Also: removed Milch/Knoblauch/Fruechte/Passata from Bring directly (immediate fix)
This commit is contained in:
@@ -2568,6 +2568,14 @@ function smartShopping(PDO $db): void {
|
||||
$now = time();
|
||||
$today = date('Y-m-d');
|
||||
|
||||
// Helper: extract significant tokens from a product name (mirrors JS _nameTokens)
|
||||
$nameTokens = function(string $name): array {
|
||||
$stop = ['di','del','della','dei','degli','delle','da','in','con','per','su',
|
||||
'a','e','il','lo','la','i','gli','le','un','uno','una','al','alle','agli','allo'];
|
||||
$tokens = preg_split('/\s+/', strtolower(preg_replace('/[^\p{L}\s]/u', ' ', $name)));
|
||||
return array_values(array_filter($tokens, fn($t) => strlen($t) > 2 && !in_array($t, $stop)));
|
||||
};
|
||||
|
||||
// 1. Get all products with their inventory and transaction history
|
||||
$products = $db->query("
|
||||
SELECT p.id, p.name, p.brand, p.category, p.unit, p.default_quantity, p.package_unit
|
||||
@@ -2623,6 +2631,20 @@ function smartShopping(PDO $db): void {
|
||||
}
|
||||
} catch (Exception $e) { /* ignore */ }
|
||||
|
||||
// 4b. Build stockByFirstToken: first-significant-token → total in-stock qty.
|
||||
// Used to skip depleted products that have an equivalent product in stock
|
||||
// (e.g. "Aglio rosso" depleted but "Aglio" has 3 pz → don't re-add Aglio rosso to list)
|
||||
$stockByFirstToken = [];
|
||||
foreach ($products as $pStock) {
|
||||
$qty = isset($inventory[$pStock['id']]) ? (float)$inventory[$pStock['id']]['total_qty'] : 0;
|
||||
if ($qty <= 0) continue;
|
||||
$toks = $nameTokens($pStock['name']);
|
||||
if (!empty($toks)) {
|
||||
$tok = $toks[0];
|
||||
$stockByFirstToken[$tok] = ($stockByFirstToken[$tok] ?? 0) + $qty;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Analyze each product
|
||||
$items = [];
|
||||
foreach ($products as $p) {
|
||||
@@ -2699,6 +2721,12 @@ function smartShopping(PDO $db): void {
|
||||
|
||||
// Out of stock
|
||||
if ($qty <= 0) {
|
||||
// If another product with the same first-token has stock, this depletion is covered
|
||||
// (e.g. "Aglio rosso" depleted but "Aglio" has 3 pz → skip "Aglio rosso")
|
||||
$pToks = $nameTokens($p['name']);
|
||||
$pFirst = $pToks[0] ?? null;
|
||||
if ($pFirst && ($stockByFirstToken[$pFirst] ?? 0) > 0) continue;
|
||||
|
||||
if ($isFrequent && $isRecent && $buyCount >= 2) {
|
||||
// Frequently used, recently active, AND bought multiple times → critical
|
||||
$urgency = 'critical';
|
||||
|
||||
Reference in New Issue
Block a user