Remove kg/l units everywhere — only g (grammi) and ml (millilitri)

- HTML: removed kg/l options from all unit selector dropdowns
- JS detectUnitAndQuantity(): auto-converts kg→g (*1000) and l→ml (*1000)
- JS unit labels: removed all kg/l entries from unitLabels maps
- JS category defaults: frutta 1000g, verdura 500g, bevande 1000ml
- JS step/min logic: simplified for g/ml only (no more 0.01 steps)
- JS getSubUnitStep(): removed kg/l cases
- JS isLowStock(): removed kg/l threshold
- JS spec parser: labels now show g/ml instead of kg/L
- PHP recipe parser: converts kg→g and l→ml immediately on parse
- PHP AI prompt: updated to specify only g/ml/pz/conf units
- PHP migration endpoint available at ?action=migrate_units (no-op if DB already clean)
This commit is contained in:
dadaloop82
2026-03-30 14:13:11 +00:00
parent c4938457ac
commit bcddba46d4
4 changed files with 94 additions and 65 deletions
+63 -11
View File
@@ -135,6 +135,10 @@ try {
getClientLog(); getClientLog();
break; break;
case 'migrate_units':
migrateUnitsToBase($db);
break;
// ===== SPESA ONLINE ===== // ===== SPESA ONLINE =====
case 'dupliclick_login': case 'dupliclick_login':
dupliclickLogin(); dupliclickLogin();
@@ -1480,7 +1484,7 @@ REGOLE IMPORTANTI:
5. Adatta le quantità per $persons persona/e 5. Adatta le quantità per $persons persona/e
6. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile 6. Se non ci sono abbastanza ingredienti per una ricetta completa, suggerisci la migliore combinazione possibile
7. La ricetta deve essere adatta al pasto: $mealLabel 7. La ricetta deve essere adatta al pasto: $mealLabel
8. IMPORTANTE - QUANTITÀ NUMERICHE: per ogni ingrediente dalla dispensa, il campo "qty_number" DEVE contenere il valore NUMERICO da scalare dall'inventario, espresso nella STESSA unità di misura della dispensa. Esempio: se in dispensa c'è "Farina: 1000 g" e la ricetta richiede 200g, qty_number = 200. Se "Riso: 2 kg" e servono 300g, qty_number = 0.3. Per ingredienti non dalla dispensa, qty_number = 0. 8. IMPORTANTE - QUANTITÀ NUMERICHE: per ogni ingrediente dalla dispensa, il campo "qty_number" DEVE contenere il valore NUMERICO da scalare dall'inventario, espresso nella STESSA unità di misura della dispensa. Le unità ammesse sono SOLO: g (grammi), ml (millilitri), pz (pezzi), conf (confezioni). NON usare mai kg o litri. Esempio: se in dispensa c'è "Farina: 1000 g" e la ricetta richiede 200g, qty_number = 200. Se "Riso: 2000 g" e servono 300g, qty_number = 300. Per ingredienti non dalla dispensa, qty_number = 0.
9. GESTIONE SMART QUANTITÀ: NON lasciare rimasugli poco usabili in dispensa. Se un ingrediente ha una quantità piccola (es. 50g di formaggio, 1 uovo, 100ml di latte), preferisci usarlo TUTTO piuttosto che lasciarne una quantità inutilizzabile. Se invece la quantità è abbondante, usa solo il necessario lasciando abbastanza per un altro pasto. Pensa sempre: "quello che resta sarà sufficiente per un altro utilizzo?" 9. GESTIONE SMART QUANTITÀ: NON lasciare rimasugli poco usabili in dispensa. Se un ingrediente ha una quantità piccola (es. 50g di formaggio, 1 uovo, 100ml di latte), preferisci usarlo TUTTO piuttosto che lasciarne una quantità inutilizzabile. Se invece la quantità è abbondante, usa solo il necessario lasciando abbastanza per un altro pasto. Pensa sempre: "quello che resta sarà sufficiente per un altro utilizzo?"
INGREDIENTI DISPONIBILI IN DISPENSA: INGREDIENTI DISPONIBILI IN DISPENSA:
@@ -1622,27 +1626,27 @@ PROMPT;
$recipeVal = (float)str_replace(',', '.', $qm[1]); $recipeVal = (float)str_replace(',', '.', $qm[1]);
$ru = strtolower($qm[2]); $ru = strtolower($qm[2]);
if (strpos($ru, 'g') === 0) $recipeUnit = 'g'; if (strpos($ru, 'g') === 0) $recipeUnit = 'g';
elseif ($ru === 'kg') $recipeUnit = 'kg'; elseif ($ru === 'kg') { $recipeUnit = 'g'; $recipeVal *= 1000; }
elseif ($ru === 'ml') $recipeUnit = 'ml'; elseif ($ru === 'ml') $recipeUnit = 'ml';
elseif ($ru === 'cl') $recipeUnit = 'ml'; // cl→ml elseif ($ru === 'cl') { $recipeUnit = 'ml'; $recipeVal *= 10; }
elseif ($ru === 'l' || strpos($ru, 'litr') === 0) $recipeUnit = 'l'; elseif ($ru === 'l' || strpos($ru, 'litr') === 0) { $recipeUnit = 'ml'; $recipeVal *= 1000; }
elseif (strpos($ru, 'pz') === 0 || strpos($ru, 'pezz') === 0) $recipeUnit = 'pz'; elseif (strpos($ru, 'pz') === 0 || strpos($ru, 'pezz') === 0) $recipeUnit = 'pz';
elseif (strpos($ru, 'conf') === 0) $recipeUnit = 'conf'; elseif (strpos($ru, 'conf') === 0) $recipeUnit = 'conf';
} }
// Convert qty_number to inventory unit if mismatch detected // Convert qty_number to inventory unit if mismatch detected
if ($recipeUnit && $recipeUnit !== $invUnit) { if ($recipeUnit && $recipeUnit !== $invUnit) {
// Weight conversions // Weight conversions (both should be 'g' now, but handle legacy 'kg')
if ($recipeUnit === 'g' && $invUnit === 'kg') { if ($recipeUnit === 'g' && $invUnit === 'kg') {
$qtyNum = $recipeVal / 1000; $qtyNum = $recipeVal / 1000;
} elseif ($recipeUnit === 'kg' && $invUnit === 'g') { } elseif ($recipeUnit === 'g' && $invUnit === 'g') {
$qtyNum = $recipeVal * 1000; $qtyNum = $recipeVal;
// Volume conversions // Volume conversions (both should be 'ml' now, but handle legacy 'l')
} elseif ($recipeUnit === 'ml' && $invUnit === 'l') { } elseif ($recipeUnit === 'ml' && $invUnit === 'l') {
$qtyNum = $recipeVal / 1000; $qtyNum = $recipeVal / 1000;
} elseif ($recipeUnit === 'l' && $invUnit === 'ml') { } elseif ($recipeUnit === 'ml' && $invUnit === 'ml') {
$qtyNum = $recipeVal * 1000; $qtyNum = $recipeVal;
// g/kg/ml/l → pz (approximate to nearest piece) // g/ml → pz (approximate to nearest piece)
} elseif ($invUnit === 'pz' || $invUnit === 'conf') { } elseif ($invUnit === 'pz' || $invUnit === 'conf') {
$defQty = (float)($bestMatch['default_quantity'] ?? 0); $defQty = (float)($bestMatch['default_quantity'] ?? 0);
if ($defQty > 0) { if ($defQty > 0) {
@@ -3123,3 +3127,51 @@ function chatClear(PDO $db): void {
$db->exec("DELETE FROM chat_messages"); $db->exec("DELETE FROM chat_messages");
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
} }
/**
* One-time migration: convert all kg→g and l→ml in products table,
* and scale inventory quantities accordingly.
*/
function migrateUnitsToBase(PDO $db): void {
$changes = 0;
// Get products with kg or l units
$stmt = $db->query("SELECT id, unit, default_quantity, package_unit FROM products WHERE unit IN ('kg','l') OR package_unit IN ('kg','l')");
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($products as $p) {
$newUnit = $p['unit'];
$newDefQty = (float)$p['default_quantity'];
$newPkgUnit = $p['package_unit'];
$scaleInventory = false;
if ($p['unit'] === 'kg') {
$newUnit = 'g';
$newDefQty = $newDefQty * 1000;
$scaleInventory = true;
} elseif ($p['unit'] === 'l') {
$newUnit = 'ml';
$newDefQty = $newDefQty * 1000;
$scaleInventory = true;
}
if ($p['package_unit'] === 'kg') {
$newPkgUnit = 'g';
if ($p['unit'] === 'conf') $newDefQty = $newDefQty * 1000;
} elseif ($p['package_unit'] === 'l') {
$newPkgUnit = 'ml';
if ($p['unit'] === 'conf') $newDefQty = $newDefQty * 1000;
}
$upd = $db->prepare("UPDATE products SET unit = ?, default_quantity = ?, package_unit = ? WHERE id = ?");
$upd->execute([$newUnit, $newDefQty, $newPkgUnit, $p['id']]);
$changes++;
// Scale inventory quantities (kg→g means multiply by 1000)
if ($scaleInventory) {
$db->prepare("UPDATE inventory SET quantity = quantity * 1000 WHERE product_id = ?")->execute([$p['id']]);
}
}
echo json_encode(['success' => true, 'changes' => $changes]);
}
+30 -45
View File
@@ -267,6 +267,8 @@ function detectUnitAndQuantity(quantityInfo) {
let perUnitVal = parseFloat(multiMatch[2].replace(',', '.')); let perUnitVal = parseFloat(multiMatch[2].replace(',', '.'));
let perUnitUnit = multiMatch[3].toLowerCase(); let perUnitUnit = multiMatch[3].toLowerCase();
if (perUnitUnit === 'cl') { perUnitUnit = 'ml'; perUnitVal *= 10; } if (perUnitUnit === 'cl') { perUnitUnit = 'ml'; perUnitVal *= 10; }
if (perUnitUnit === 'kg') { perUnitUnit = 'g'; perUnitVal *= 1000; }
if (perUnitUnit === 'l') { perUnitUnit = 'ml'; perUnitVal *= 1000; }
return { unit: 'conf', quantity: perUnitVal, packageUnit: perUnitUnit, confCount: count, weightInfo: quantityInfo }; return { unit: 'conf', quantity: perUnitVal, packageUnit: perUnitUnit, confCount: count, weightInfo: quantityInfo };
} }
// Match single package patterns like "500 g", "1 l", "750 ml", "1.5 kg" // Match single package patterns like "500 g", "1 l", "750 ml", "1.5 kg"
@@ -275,6 +277,8 @@ function detectUnitAndQuantity(quantityInfo) {
let unit = match[2].toLowerCase(); let unit = match[2].toLowerCase();
let val = parseFloat(match[1].replace(',', '.')); let val = parseFloat(match[1].replace(',', '.'));
if (unit === 'cl') { unit = 'ml'; val *= 10; } if (unit === 'cl') { unit = 'ml'; val *= 10; }
if (unit === 'kg') { unit = 'g'; val *= 1000; }
if (unit === 'l') { unit = 'ml'; val *= 1000; }
return { unit, quantity: val, weightInfo: quantityInfo }; return { unit, quantity: val, weightInfo: quantityInfo };
} }
return { unit: 'pz', quantity: 1, weightInfo: quantityInfo }; return { unit: 'pz', quantity: 1, weightInfo: quantityInfo };
@@ -990,7 +994,7 @@ async function loadDashboard() {
const locInfo = LOCATIONS[item.location] || { icon: '📦', label: item.location }; const locInfo = LOCATIONS[item.location] || { icon: '📦', label: item.location };
const qty = parseFloat(item.quantity); const qty = parseFloat(item.quantity);
const pkgSize = parseFloat(item.default_quantity); const pkgSize = parseFloat(item.default_quantity);
const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' };
let qtyText = ''; let qtyText = '';
if (item.unit === 'conf') { if (item.unit === 'conf') {
@@ -1061,9 +1065,7 @@ const QTY_THRESHOLDS = {
'pz': { min: 0.3, max: 50 }, 'pz': { min: 0.3, max: 50 },
'conf': { min: 0.3, max: 50 }, 'conf': { min: 0.3, max: 50 },
'g': { min: 3, max: 10000 }, 'g': { min: 3, max: 10000 },
'kg': { min: 0.005, max: 50 },
'ml': { min: 3, max: 10000 }, 'ml': { min: 3, max: 10000 },
'l': { min: 0.005, max: 50 },
}; };
function isSuspiciousQty(qty, unit) { function isSuspiciousQty(qty, unit) {
@@ -1252,9 +1254,7 @@ function showAlertItemDetail(inventoryId, productId) {
} }
function formatSubRemainder(amt, pkgUnit) { function formatSubRemainder(amt, pkgUnit) {
const uL = { 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml' }; const uL = { 'g': 'g', 'ml': 'ml' };
if (pkgUnit === 'l' && amt < 1) return `${Math.round(amt * 1000)}ml`;
if (pkgUnit === 'kg' && amt < 1) return `${Math.round(amt * 1000)}g`;
if (pkgUnit === 'ml' || pkgUnit === 'g') return `${Math.round(amt)}${uL[pkgUnit] || pkgUnit}`; if (pkgUnit === 'ml' || pkgUnit === 'g') return `${Math.round(amt)}${uL[pkgUnit] || pkgUnit}`;
return `${Math.round(amt * 10) / 10}${uL[pkgUnit] || pkgUnit}`; return `${Math.round(amt * 10) / 10}${uL[pkgUnit] || pkgUnit}`;
} }
@@ -1262,7 +1262,7 @@ function formatSubRemainder(amt, pkgUnit) {
function formatQuantity(qty, unit, defaultQty, packageUnit) { function formatQuantity(qty, unit, defaultQty, packageUnit) {
if (!qty && qty !== 0) return ''; if (!qty && qty !== 0) return '';
const n = parseFloat(qty); const n = parseFloat(qty);
const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' };
const label = unitLabels[unit] || unit || 'pz'; const label = unitLabels[unit] || unit || 'pz';
// Special handling for conf with partial packages // Special handling for conf with partial packages
@@ -1292,7 +1292,7 @@ function formatQuantity(qty, unit, defaultQty, packageUnit) {
// Returns { mainQty: '10', unitLabel: 'conf', packageDetail: 'da 36g', fraction: '¼' } // Returns { mainQty: '10', unitLabel: 'conf', packageDetail: 'da 36g', fraction: '¼' }
function formatQuantityParts(qty, unit, defaultQty, packageUnit) { function formatQuantityParts(qty, unit, defaultQty, packageUnit) {
const n = parseFloat(qty) || 0; const n = parseFloat(qty) || 0;
const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' };
const label = unitLabels[unit] || unit || 'pz'; const label = unitLabels[unit] || unit || 'pz';
// Special handling for conf with partial packages // Special handling for conf with partial packages
@@ -1601,7 +1601,7 @@ function editInventoryItem(id) {
<div class="form-group"> <div class="form-group">
<label>📏 Unità di misura</label> <label>📏 Unità di misura</label>
<select id="edit-unit" class="form-input" onchange="onEditUnitChange()"> <select id="edit-unit" class="form-input" onchange="onEditUnitChange()">
${['pz','g','kg','ml','l','conf'].map(u => `<option value="${u}" ${(item.unit||'pz') === u ? 'selected' : ''}>${u === 'pz' ? 'pz (pezzi)' : u === 'g' ? 'g (grammi)' : u === 'kg' ? 'kg (chilogrammi)' : u === 'ml' ? 'ml (millilitri)' : u === 'l' ? 'L (litri)' : u === 'conf' ? 'conf (confezioni)' : u}</option>`).join('')} ${['pz','g','ml','conf'].map(u => `<option value="${u}" ${(item.unit||'pz') === u ? 'selected' : ''}>${u === 'pz' ? 'pz (pezzi)' : u === 'g' ? 'g (grammi)' : u === 'ml' ? 'ml (millilitri)' : u === 'conf' ? 'conf (confezioni)' : u}</option>`).join('')}
</select> </select>
</div> </div>
<div class="form-group" id="edit-conf-size-group" style="display:${isConf ? 'block' : 'none'}"> <div class="form-group" id="edit-conf-size-group" style="display:${isConf ? 'block' : 'none'}">
@@ -1609,7 +1609,7 @@ function editInventoryItem(id) {
<div class="conf-size-inputs"> <div class="conf-size-inputs">
<input type="number" id="edit-conf-size" class="form-input conf-size-input" min="1" step="any" value="${confSizeVal}" placeholder="es. 300"> <input type="number" id="edit-conf-size" class="form-input conf-size-input" min="1" step="any" value="${confSizeVal}" placeholder="es. 300">
<select id="edit-conf-unit" class="form-input conf-size-unit"> <select id="edit-conf-unit" class="form-input conf-size-unit">
${['g','kg','ml','l'].map(u => `<option value="${u}" ${confUnitVal === u ? 'selected' : ''}>${u === 'l' ? 'L' : u}</option>`).join('')} ${['g','ml'].map(u => `<option value="${u}" ${confUnitVal === u ? 'selected' : ''}>${u}</option>`).join('')}
</select> </select>
</div> </div>
</div> </div>
@@ -2423,12 +2423,12 @@ function onCategoryChange(fromAutoDetect = false) {
'latticini': { unit: 'pz', qty: 1 }, 'latticini': { unit: 'pz', qty: 1 },
'carne': { unit: 'g', qty: 500 }, 'carne': { unit: 'g', qty: 500 },
'pesce': { unit: 'g', qty: 300 }, 'pesce': { unit: 'g', qty: 300 },
'frutta': { unit: 'kg', qty: 1 }, 'frutta': { unit: 'g', qty: 1000 },
'verdura': { unit: 'kg', qty: 0.5 }, 'verdura': { unit: 'g', qty: 500 },
'pasta': { unit: 'g', qty: 500 }, 'pasta': { unit: 'g', qty: 500 },
'pane': { unit: 'pz', qty: 1 }, 'pane': { unit: 'pz', qty: 1 },
'surgelati': { unit: 'g', qty: 450 }, 'surgelati': { unit: 'g', qty: 450 },
'bevande': { unit: 'l', qty: 1 }, 'bevande': { unit: 'ml', qty: 1000 },
'condimenti': { unit: 'pz', qty: 1 }, 'condimenti': { unit: 'pz', qty: 1 },
'snack': { unit: 'g', qty: 250 }, 'snack': { unit: 'g', qty: 250 },
'conserve': { unit: 'g', qty: 400 }, 'conserve': { unit: 'g', qty: 400 },
@@ -2824,7 +2824,7 @@ function editActionInventoryItem(inventoryId) {
<div class="form-group"> <div class="form-group">
<label>📏 Unità di misura</label> <label>📏 Unità di misura</label>
<select id="action-edit-unit" class="form-input" onchange="onActionEditUnitChange()"> <select id="action-edit-unit" class="form-input" onchange="onActionEditUnitChange()">
${['pz','g','kg','ml','l','conf'].map(u => `<option value="${u}" ${(item.unit||'pz') === u ? 'selected' : ''}>${u === 'pz' ? 'pz (pezzi)' : u === 'g' ? 'g (grammi)' : u === 'kg' ? 'kg (chilogrammi)' : u === 'ml' ? 'ml (millilitri)' : u === 'l' ? 'L (litri)' : u === 'conf' ? 'conf (confezioni)' : u}</option>`).join('')} ${['pz','g','ml','conf'].map(u => `<option value="${u}" ${(item.unit||'pz') === u ? 'selected' : ''}>${u === 'pz' ? 'pz (pezzi)' : u === 'g' ? 'g (grammi)' : u === 'ml' ? 'ml (millilitri)' : u === 'conf' ? 'conf (confezioni)' : u}</option>`).join('')}
</select> </select>
</div> </div>
<div class="form-group" id="action-edit-conf-group" style="display:${isConf ? 'block' : 'none'}"> <div class="form-group" id="action-edit-conf-group" style="display:${isConf ? 'block' : 'none'}">
@@ -2832,7 +2832,7 @@ function editActionInventoryItem(inventoryId) {
<div class="conf-size-inputs"> <div class="conf-size-inputs">
<input type="number" id="action-edit-conf-size" class="form-input conf-size-input" min="1" step="any" value="${confSizeVal}" placeholder="es. 300"> <input type="number" id="action-edit-conf-size" class="form-input conf-size-input" min="1" step="any" value="${confSizeVal}" placeholder="es. 300">
<select id="action-edit-conf-unit" class="form-input conf-size-unit"> <select id="action-edit-conf-unit" class="form-input conf-size-unit">
${['g','kg','ml','l'].map(u => `<option value="${u}" ${confUnitVal === u ? 'selected' : ''}>${u === 'l' ? 'L' : u}</option>`).join('')} ${['g','ml'].map(u => `<option value="${u}" ${confUnitVal === u ? 'selected' : ''}>${u}</option>`).join('')}
</select> </select>
</div> </div>
</div> </div>
@@ -3270,9 +3270,7 @@ function onAddUnitChange() {
// Convert between related units if logical // Convert between related units if logical
if (unit === 'g' && currentQty <= 10) qtyInput.value = currentProduct.weight_info ? parseFloat(currentProduct.weight_info) || 250 : 250; if (unit === 'g' && currentQty <= 10) qtyInput.value = currentProduct.weight_info ? parseFloat(currentProduct.weight_info) || 250 : 250;
if (unit === 'kg' && currentQty > 100) qtyInput.value = (currentQty / 1000).toFixed(1);
if (unit === 'ml' && currentQty <= 10) qtyInput.value = 500; if (unit === 'ml' && currentQty <= 10) qtyInput.value = 500;
if (unit === 'l' && currentQty > 100) qtyInput.value = (currentQty / 1000).toFixed(1);
if (unit === 'pz' && currentQty > 100) qtyInput.value = 1; if (unit === 'pz' && currentQty > 100) qtyInput.value = 1;
if (unit === 'conf' && currentQty > 10) qtyInput.value = 1; if (unit === 'conf' && currentQty > 10) qtyInput.value = 1;
} }
@@ -3283,8 +3281,6 @@ function updateAddQtyStep() {
qtyInput.step = 'any'; qtyInput.step = 'any';
if (unit === 'g' || unit === 'ml') { if (unit === 'g' || unit === 'ml') {
qtyInput.min = '1'; qtyInput.min = '1';
} else if (unit === 'kg' || unit === 'l') {
qtyInput.min = '0.1';
} else { } else {
qtyInput.min = '1'; qtyInput.min = '1';
} }
@@ -3300,9 +3296,7 @@ function adjustAddQty(delta) {
const unit = document.getElementById('add-unit').value; const unit = document.getElementById('add-unit').value;
let val = parseFloat(qtyInput.value) || 0; let val = parseFloat(qtyInput.value) || 0;
let step; let step;
if (unit === 'kg' || unit === 'l') { if (unit === 'g' || unit === 'ml') {
step = val < 1 ? 0.1 : 0.5;
} else if (unit === 'g' || unit === 'ml') {
step = val < 50 ? 1 : (val < 500 ? 10 : 50); step = val < 50 ? 1 : (val < 500 ? 10 : 50);
} else { } else {
step = 1; step = 1;
@@ -3431,7 +3425,7 @@ async function submitAdd(e) {
let qtyInfo = ''; let qtyInfo = '';
if (result.total_qty) { if (result.total_qty) {
const u = result.unit || 'pz'; const u = result.unit || 'pz';
const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml', 'conf': 'conf' }; const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml', 'conf': 'conf' };
const uLabel = unitLabels[u] || u; const uLabel = unitLabels[u] || u;
if (u === 'conf' && result.package_unit && result.default_quantity > 0) { if (u === 'conf' && result.package_unit && result.default_quantity > 0) {
const pkgLabel = unitLabels[result.package_unit] || result.package_unit; const pkgLabel = unitLabels[result.package_unit] || result.package_unit;
@@ -3536,7 +3530,7 @@ async function loadUseInventoryInfo() {
// --- CONF MODE: show sub-unit controls --- // --- CONF MODE: show sub-unit controls ---
const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0); const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0);
const totalSub = totalConf * pkgSize; const totalSub = totalConf * pkgSize;
const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' };
const subLabel = unitLabels[pkgUnit] || pkgUnit; const subLabel = unitLabels[pkgUnit] || pkgUnit;
_useConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel }; _useConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel };
@@ -3568,9 +3562,9 @@ async function loadUseInventoryInfo() {
}).join(' · '); }).join(' · ');
const qtyInput = document.getElementById('use-quantity'); const qtyInput = document.getElementById('use-quantity');
qtyInput.value = (unit === 'kg' || unit === 'l') ? 0.1 : 1; qtyInput.value = 1;
qtyInput.step = 'any'; qtyInput.step = 'any';
qtyInput.min = (unit === 'kg' || unit === 'l') ? '0.01' : (unit === 'g' || unit === 'ml') ? '1' : '1'; qtyInput.min = (unit === 'g' || unit === 'ml') ? '1' : '1';
document.getElementById('use-partial-hint').textContent = 'Oppure specifica la quantità usata:'; document.getElementById('use-partial-hint').textContent = 'Oppure specifica la quantità usata:';
} }
} catch(e) { } catch(e) {
@@ -3607,9 +3601,7 @@ function switchUseUnit(mode) {
function getSubUnitStep(pkgUnit) { function getSubUnitStep(pkgUnit) {
switch (pkgUnit) { switch (pkgUnit) {
case 'ml': return 50; case 'ml': return 50;
case 'l': return 0.1;
case 'g': return 10; case 'g': return 10;
case 'kg': return 0.05;
default: return 1; default: return 1;
} }
} }
@@ -3625,16 +3617,13 @@ function adjustUseQty(direction) {
} else { } else {
// Unit-aware step for normal mode // Unit-aware step for normal mode
const u = _useNormalUnit || 'pz'; const u = _useNormalUnit || 'pz';
if (u === 'kg' || u === 'l') { if (u === 'g' || u === 'ml') {
step = val <= 0.1 ? 0.01 : (val < 1 ? 0.1 : 0.5);
} else if (u === 'g' || u === 'ml') {
step = val < 50 ? 1 : (val < 500 ? 10 : 50); step = val < 50 ? 1 : (val < 500 ? 10 : 50);
} else { } else {
step = 1; step = 1;
} }
} }
const minVal = ((_useNormalUnit === 'kg' || _useNormalUnit === 'l') && !_useConfMode) ? 0.01 : step; val = Math.max(step, val + direction * step);
val = Math.max(minVal, val + direction * step);
input.value = Math.round(val * 1000) / 1000; input.value = Math.round(val * 1000) / 1000;
} }
@@ -3653,7 +3642,6 @@ function isLowStock(totalRemaining, unit, defaultQty) {
if (defaultQty > 0) return totalRemaining <= defaultQty * 0.25; if (defaultQty > 0) return totalRemaining <= defaultQty * 0.25;
// Fallback fixed thresholds // Fallback fixed thresholds
if (unit === 'g' || unit === 'ml') return totalRemaining <= 100; if (unit === 'g' || unit === 'ml') return totalRemaining <= 100;
if (unit === 'kg' || unit === 'l') return totalRemaining <= 0.15;
return false; return false;
} }
@@ -3700,7 +3688,7 @@ function showLowStockBringPrompt(result, afterCallback) {
const subTotal = Math.round(totalRemaining * defaultQty); const subTotal = Math.round(totalRemaining * defaultQty);
remainLabel = `${subTotal}${result.product_package_unit}`; remainLabel = `${subTotal}${result.product_package_unit}`;
} else { } else {
const unitLabels = { pz: 'pz', g: 'g', kg: 'kg', ml: 'ml', l: 'L', conf: 'conf' }; const unitLabels = { pz: 'pz', g: 'g', ml: 'ml', conf: 'conf' };
remainLabel = `${Number.isInteger(totalRemaining) ? totalRemaining : totalRemaining.toFixed(1)} ${unitLabels[unit] || unit}`; remainLabel = `${Number.isInteger(totalRemaining) ? totalRemaining : totalRemaining.toFixed(1)} ${unitLabels[unit] || unit}`;
} }
@@ -4481,10 +4469,10 @@ function parseQtyFromSpec(spec) {
let val = parseFloat(m[1].replace(',', '.')); let val = parseFloat(m[1].replace(',', '.'));
const unit = m[2].toLowerCase(); const unit = m[2].toLowerCase();
if (unit === 'g' || unit === 'gr') return { kg: val / 1000, label: val + 'g', type: 'weight' }; if (unit === 'g' || unit === 'gr') return { kg: val / 1000, label: val + 'g', type: 'weight' };
if (unit === 'kg') return { kg: val, label: val + 'kg', type: 'weight' }; if (unit === 'kg') return { kg: val, label: (val * 1000) + 'g', type: 'weight' };
if (unit === 'ml') return { kg: val / 1000, label: val + 'ml', type: 'weight' }; if (unit === 'ml') return { kg: val / 1000, label: val + 'ml', type: 'weight' };
if (unit === 'cl') return { kg: val / 100, label: val * 10 + 'ml', type: 'weight' }; if (unit === 'cl') return { kg: val / 100, label: val * 10 + 'ml', type: 'weight' };
if (unit === 'l' || unit === 'lt') return { kg: val, label: val + 'L', type: 'weight' }; if (unit === 'l' || unit === 'lt') return { kg: val, label: (val * 1000) + 'ml', type: 'weight' };
} }
// Match unit count: 2 pz, 3 pezzi, 5, 2x, ~5 pz // Match unit count: 2 pz, 3 pezzi, 5, 2x, ~5 pz
const pzMatch = s.match(/~?(\d+)\s*(pz|pezzi|x|$)/i); const pzMatch = s.match(/~?(\d+)\s*(pz|pezzi|x|$)/i);
@@ -5792,7 +5780,7 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
if (isConf) { if (isConf) {
const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0); const totalConf = items.reduce((s, i) => s + parseFloat(i.quantity), 0);
const totalSub = totalConf * pkgSize; const totalSub = totalConf * pkgSize;
const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' }; const unitLabels = { 'ml': 'ml', 'g': 'g', 'pz': 'pz' };
const subLabel = unitLabels[pkgUnit] || pkgUnit; const subLabel = unitLabels[pkgUnit] || pkgUnit;
_recipeUseConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel, _activeUnit: 'sub' }; _recipeUseConfMode = { packageSize: pkgSize, packageUnit: pkgUnit, totalSub, totalConf, subLabel, _activeUnit: 'sub' };
@@ -5813,9 +5801,9 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
</div>`; </div>`;
} else { } else {
_recipeUseNormalUnit = unit; _recipeUseNormalUnit = unit;
const unitLabels = { 'pz': 'pz', 'kg': 'kg', 'g': 'g', 'l': 'L', 'ml': 'ml' }; const unitLabels = { 'pz': 'pz', 'g': 'g', 'ml': 'ml' };
const unitLabel = unitLabels[unit] || unit; const unitLabel = unitLabels[unit] || unit;
const inputMin = (unit === 'kg' || unit === 'l') ? '0.01' : '0.1'; const inputMin = '0.1';
qtySection = ` qtySection = `
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:8px">Quantità da usare (${unitLabel}):</p> <p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:8px">Quantità da usare (${unitLabel}):</p>
<div class="qty-control"> <div class="qty-control">
@@ -5907,16 +5895,13 @@ function adjustRecipeUseQty(direction) {
step = 0.5; step = 0.5;
} else { } else {
const u = _recipeUseNormalUnit || 'pz'; const u = _recipeUseNormalUnit || 'pz';
if (u === 'kg' || u === 'l') { if (u === 'g' || u === 'ml') {
step = val <= 0.1 ? 0.01 : (val < 1 ? 0.1 : 0.5);
} else if (u === 'g' || u === 'ml') {
step = val < 50 ? 1 : (val < 500 ? 10 : 50); step = val < 50 ? 1 : (val < 500 ? 10 : 50);
} else { } else {
step = 1; step = 1;
} }
} }
const minVal = ((_recipeUseNormalUnit === 'kg' || _recipeUseNormalUnit === 'l') && !_recipeUseConfMode) ? 0.01 : step; val = Math.max(step, val + direction * step);
val = Math.max(minVal, val + direction * step);
input.value = Math.round(val * 1000) / 1000; input.value = Math.round(val * 1000) / 1000;
} }
BIN
View File
Binary file not shown.
+1 -9
View File
@@ -218,9 +218,7 @@
<option value="pz">pz</option> <option value="pz">pz</option>
<option value="conf">conf</option> <option value="conf">conf</option>
<option value="g">g</option> <option value="g">g</option>
<option value="kg">kg</option>
<option value="ml">ml</option> <option value="ml">ml</option>
<option value="l">L</option>
</select> </select>
</div> </div>
<div id="add-conf-size-row" class="conf-size-row" style="display:none"> <div id="add-conf-size-row" class="conf-size-row" style="display:none">
@@ -229,9 +227,7 @@
<input type="number" id="add-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300"> <input type="number" id="add-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300">
<select id="add-conf-unit" class="form-input conf-size-unit"> <select id="add-conf-unit" class="form-input conf-size-unit">
<option value="g">g</option> <option value="g">g</option>
<option value="kg">kg</option>
<option value="ml">ml</option> <option value="ml">ml</option>
<option value="l">L</option>
</select> </select>
</div> </div>
</div> </div>
@@ -440,9 +436,7 @@
<label>📏 Unità di misura</label> <label>📏 Unità di misura</label>
<select id="pf-unit" class="form-input" onchange="onPfUnitChange()"> <select id="pf-unit" class="form-input" onchange="onPfUnitChange()">
<option value="pz">Pezzi</option> <option value="pz">Pezzi</option>
<option value="kg">Kg</option>
<option value="g">Grammi</option> <option value="g">Grammi</option>
<option value="l">Litri</option>
<option value="ml">ml</option> <option value="ml">ml</option>
<option value="conf">Confezione</option> <option value="conf">Confezione</option>
</select> </select>
@@ -458,9 +452,7 @@
<input type="number" id="pf-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300"> <input type="number" id="pf-conf-size" class="form-input conf-size-input" min="1" step="any" placeholder="es. 300">
<select id="pf-conf-unit" class="form-input conf-size-unit"> <select id="pf-conf-unit" class="form-input conf-size-unit">
<option value="g">g</option> <option value="g">g</option>
<option value="kg">kg</option>
<option value="ml">ml</option> <option value="ml">ml</option>
<option value="l">L</option>
</select> </select>
</div> </div>
</div> </div>
@@ -997,6 +989,6 @@
</div> </div>
</div> </div>
<script src="assets/js/app.js?v=20260330b"></script> <script src="assets/js/app.js?v=20260330c"></script>
</body> </body>
</html> </html>