Banner: suppress low-qty alert when sibling product entries exist elsewhere

A partially-used fridge entry (e.g. 191 ml of milk) triggered a
'suspiciously low quantity' banner even when sealed packages of the
same product were present in another location (e.g. pantry).

Fix: before pushing a low-qty review alert, group all inventory rows
by product key (barcode, or name+brand fallback). If any sibling entry
for the same product has qty > 0 in a different row, skip the alert.
High-qty and suspicious package-size alerts are unaffected.
This commit is contained in:
dadaloop82
2026-04-30 05:28:43 +00:00
parent 8359b14931
commit 4e583127dd
3 changed files with 34 additions and 9 deletions
+30 -9
View File
@@ -2726,18 +2726,39 @@ async function loadBannerAlerts() {
});
// 2. Suspicious quantities ("expiring soon" shown only in dashboard sections, not in banner)
// Group items by product identity to detect sibling entries in other locations.
// A "low quantity" alert is suppressed when other stock of the same product exists
// (e.g. 191 ml of milk in the fridge is fine if there are 11 sealed packages in the pantry).
const _productKey = item => item.barcode || `${item.name}||${item.brand || ''}`;
const _productGroups = {};
items.forEach(item => {
const k = _productKey(item);
if (!_productGroups[k]) _productGroups[k] = [];
_productGroups[k].push(item);
});
items.forEach(item => {
if (confirmed[item.id]) return;
if (isSuspiciousQty(item.quantity, item.unit) || isSuspiciousDefaultQty(item.default_quantity, item.unit, item.package_unit)) {
const t_ = QTY_THRESHOLDS[item.unit] || QTY_THRESHOLDS['pz'];
const suspQty = isSuspiciousQty(item.quantity, item.unit);
const suspDq = isSuspiciousDefaultQty(item.default_quantity, item.unit, item.package_unit);
let warning;
if (suspDq && !suspQty) warning = '📦 Conf. sospetta';
else if (parseFloat(item.quantity) < t_.min) warning = '⬇️ Troppo poco';
else warning = '⬆️ Troppo';
_bannerQueue.push({ type: 'review', data: { ...item, warning } });
const t_ = QTY_THRESHOLDS[item.unit] || QTY_THRESHOLDS['pz'];
const qty = parseFloat(item.quantity);
const isLow = !isNaN(qty) && qty > 0 && qty < t_.min;
const isHigh = !isNaN(qty) && qty > t_.max;
const suspDq = isSuspiciousDefaultQty(item.default_quantity, item.unit, item.package_unit);
if (!isLow && !isHigh && !suspDq) return;
// Suppress low-qty warning when sibling entries for the same product exist
// in other locations — the user is simply tracking a partial/opened unit.
if (isLow && !isHigh && !suspDq) {
const siblings = (_productGroups[_productKey(item)] || []).filter(s => s.id !== item.id && parseFloat(s.quantity) > 0);
if (siblings.length > 0) return;
}
let warning;
if (suspDq && !isLow && !isHigh) warning = '📦 Conf. sospetta';
else if (isLow) warning = '⬇️ Troppo poco';
else warning = '⬆️ Troppo';
_bannerQueue.push({ type: 'review', data: { ...item, warning } });
});
// 4. Consumption predictions that don't match actual quantity