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:
dadaloop82
2026-04-07 15:20:33 +00:00
parent 0bca79b8a2
commit dcc7e9de42
2 changed files with 59 additions and 38 deletions
+28
View File
@@ -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';