This commit is contained in:
@@ -383,19 +383,19 @@ export function categoryLabel(cat) {
|
||||
* Pantry v2 — hybrydowy format.
|
||||
*
|
||||
* Wartość dla składnika może być:
|
||||
* number — składnik generyczny (bez zdefiniowanych produktów)
|
||||
* { _total, items: [{productId, qty}], generic } — składnik z produktami
|
||||
* number — składnik bez produktów (generyczny)
|
||||
* { _total, items: [{productId, qty}] } — składnik z produktami
|
||||
*
|
||||
* _total = generic + sum(items[].qty) (cache, zawsze przeliczany przy zapisie)
|
||||
* Brak pojęcia "generic" — jeśli składnik ma produkty, każda ilość
|
||||
* musi być przypisana do konkretnego produktu.
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/** @typedef {{ productId: string, qty: number }} PantryProductItem */
|
||||
/** @typedef {{ _total: number, items: PantryProductItem[], generic: number }} PantryProductEntry */
|
||||
/** @typedef {{ _total: number, items: PantryProductItem[] }} PantryProductEntry */
|
||||
/** @typedef {Record<string, number | PantryProductEntry>} PantryV2 */
|
||||
|
||||
function recalcTotal(entry) {
|
||||
const itemSum = entry.items.reduce((s, i) => s + i.qty, 0);
|
||||
entry._total = Math.round((entry.generic + itemSum) * 1000) / 1000;
|
||||
entry._total = Math.round(entry.items.reduce((s, i) => s + i.qty, 0) * 1000) / 1000;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -406,13 +406,30 @@ function normalizePantryEntry(ingredientId, val) {
|
||||
.filter(i => i && typeof i.productId === 'string' && PRODUCTS[i.productId] && Number.isFinite(Number(i.qty)) && Number(i.qty) > 0)
|
||||
.map(i => ({ productId: i.productId, qty: Math.round(Number(i.qty) * 1000) / 1000 }))
|
||||
: [];
|
||||
// Migrate generic stock → first product
|
||||
const generic = Math.max(0, Number(val.generic) || 0);
|
||||
return recalcTotal({ items, generic, _total: 0 });
|
||||
if (generic > 0 && ingredientHasProducts(ingredientId)) {
|
||||
const products = getProductsForIngredient(ingredientId);
|
||||
if (products.length > 0) {
|
||||
const firstPid = products[0].id;
|
||||
const existing = items.find(i => i.productId === firstPid);
|
||||
if (existing) existing.qty = Math.round((existing.qty + generic) * 1000) / 1000;
|
||||
else items.push({ productId: firstPid, qty: Math.round(generic * 1000) / 1000 });
|
||||
}
|
||||
}
|
||||
return recalcTotal({ items, _total: 0 });
|
||||
}
|
||||
const n = Number(val);
|
||||
if (!Number.isFinite(n) || n < 0) return null;
|
||||
if (ingredientHasProducts(ingredientId)) {
|
||||
return recalcTotal({ items: [], generic: n, _total: 0 });
|
||||
// Migrate number → first product
|
||||
if (n > 0) {
|
||||
const products = getProductsForIngredient(ingredientId);
|
||||
if (products.length > 0) {
|
||||
return recalcTotal({ items: [{ productId: products[0].id, qty: n }], _total: 0 });
|
||||
}
|
||||
}
|
||||
return recalcTotal({ items: [], _total: 0 });
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -480,30 +497,18 @@ export function getPantryProducts(ingredientId, pantry) {
|
||||
return val.items || [];
|
||||
}
|
||||
|
||||
/** Ilość generyczna (nieprzypisana do produktu). */
|
||||
/** @deprecated No generic stock for ingredients with products. Returns 0 for those. */
|
||||
export function getPantryGeneric(ingredientId, pantry) {
|
||||
const val = pantry[ingredientId];
|
||||
if (typeof val === 'number') return val;
|
||||
if (!val) return 0;
|
||||
return val.generic || 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Ustaw ilość generyczną składnika (bez produktu). */
|
||||
/** Ustaw ilość składnika BEZ produktów. Dla składników z produktami użyj setPantryProductQty. */
|
||||
export function setPantryQty(ingredientId, qty) {
|
||||
const pantry = loadPantry();
|
||||
const val = pantry[ingredientId];
|
||||
if (val && typeof val === 'object') {
|
||||
val.generic = Math.max(0, Math.round(qty * 1000) / 1000);
|
||||
recalcTotal(val);
|
||||
if (val._total <= 0) delete pantry[ingredientId];
|
||||
} else {
|
||||
if (qty <= 0 || !Number.isFinite(qty)) delete pantry[ingredientId];
|
||||
else if (ingredientHasProducts(ingredientId)) {
|
||||
pantry[ingredientId] = recalcTotal({ items: [], generic: Math.round(qty * 1000) / 1000, _total: 0 });
|
||||
} else {
|
||||
pantry[ingredientId] = Math.round(qty * 1000) / 1000;
|
||||
}
|
||||
}
|
||||
if (qty <= 0 || !Number.isFinite(qty)) delete pantry[ingredientId];
|
||||
else pantry[ingredientId] = Math.round(qty * 1000) / 1000;
|
||||
savePantry(pantry);
|
||||
return pantry;
|
||||
}
|
||||
@@ -513,8 +518,7 @@ export function setPantryProductQty(ingredientId, productId, qty) {
|
||||
const pantry = loadPantry();
|
||||
let val = pantry[ingredientId];
|
||||
if (!val || typeof val === 'number') {
|
||||
const generic = typeof val === 'number' ? val : 0;
|
||||
val = { items: [], generic, _total: 0 };
|
||||
val = { items: [], _total: 0 };
|
||||
pantry[ingredientId] = val;
|
||||
}
|
||||
const idx = val.items.findIndex(i => i.productId === productId);
|
||||
@@ -545,27 +549,21 @@ export function applyCheckedKitchenListToPantry() {
|
||||
const def = INGREDIENTS[it.ingredientId];
|
||||
if (!def) continue;
|
||||
if (normalizeUnitForPantry(it.unit, def.pantryUnit) === null) continue;
|
||||
const val = pantry[it.ingredientId];
|
||||
if (val && typeof val === 'object') {
|
||||
if (it.productId && PRODUCTS[it.productId]) {
|
||||
const idx = val.items.findIndex(i => i.productId === it.productId);
|
||||
if (idx >= 0) val.items[idx].qty = Math.round((val.items[idx].qty + it.amount) * 1000) / 1000;
|
||||
else val.items.push({ productId: it.productId, qty: Math.round(it.amount * 1000) / 1000 });
|
||||
} else {
|
||||
val.generic = Math.round(((val.generic || 0) + it.amount) * 1000) / 1000;
|
||||
|
||||
if (it.productId && PRODUCTS[it.productId]) {
|
||||
// Add to specific product
|
||||
let val = pantry[it.ingredientId];
|
||||
if (!val || typeof val === 'number') {
|
||||
val = { items: [], _total: 0 };
|
||||
pantry[it.ingredientId] = val;
|
||||
}
|
||||
const idx = val.items.findIndex(i => i.productId === it.productId);
|
||||
if (idx >= 0) val.items[idx].qty = Math.round((val.items[idx].qty + it.amount) * 1000) / 1000;
|
||||
else val.items.push({ productId: it.productId, qty: Math.round(it.amount * 1000) / 1000 });
|
||||
recalcTotal(val);
|
||||
} else if (ingredientHasProducts(it.ingredientId)) {
|
||||
const cur = typeof val === 'number' ? val : 0;
|
||||
const entry = { items: [], generic: cur, _total: 0 };
|
||||
if (it.productId && PRODUCTS[it.productId]) {
|
||||
entry.items.push({ productId: it.productId, qty: Math.round(it.amount * 1000) / 1000 });
|
||||
} else {
|
||||
entry.generic = Math.round((entry.generic + it.amount) * 1000) / 1000;
|
||||
}
|
||||
pantry[it.ingredientId] = recalcTotal(entry);
|
||||
} else {
|
||||
const cur = typeof val === 'number' ? val : 0;
|
||||
// Generic ingredient (no products)
|
||||
const cur = typeof pantry[it.ingredientId] === 'number' ? pantry[it.ingredientId] : 0;
|
||||
pantry[it.ingredientId] = Math.round((cur + it.amount) * 1000) / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user