Estendi 'Prodotti Aperti' a tutti i tipi (g, ml, l, pz, non solo conf)
- Query SQL ora rileva prodotti aperti per tutte le unità dove la quantità non è multiplo della dimensione confezione (soglia 2% per evitare errori arrot.) - Frontend: rendering differente per conf (X conf + rimanente) e altri (Xg / Yg) - Versione: 20260315i
This commit is contained in:
+11
-3
@@ -808,13 +808,21 @@ function getStats(PDO $db): void {
|
|||||||
ORDER BY i.expiry_date ASC
|
ORDER BY i.expiry_date ASC
|
||||||
")->fetchAll();
|
")->fetchAll();
|
||||||
|
|
||||||
// Opened (partially used conf items with known package capacity)
|
// Opened (partially used items with known package capacity)
|
||||||
$opened = $db->query("
|
$opened = $db->query("
|
||||||
SELECT i.*, p.name, p.brand, p.category, p.unit, p.default_quantity, p.package_unit, p.image_url,
|
SELECT i.*, p.name, p.brand, p.category, p.unit, p.default_quantity, p.package_unit, p.image_url,
|
||||||
COALESCE(i.vacuum_sealed, 0) as vacuum_sealed
|
COALESCE(i.vacuum_sealed, 0) as vacuum_sealed
|
||||||
FROM inventory i JOIN products p ON i.product_id = p.id
|
FROM inventory i JOIN products p ON i.product_id = p.id
|
||||||
WHERE p.unit = 'conf' AND p.default_quantity > 0 AND p.package_unit IS NOT NULL
|
WHERE i.quantity > 0 AND p.default_quantity > 0
|
||||||
AND i.quantity > 0 AND CAST(i.quantity AS REAL) != CAST(CAST(i.quantity AS INTEGER) AS REAL)
|
AND (
|
||||||
|
-- conf products with fractional quantity
|
||||||
|
(p.unit = 'conf' AND p.package_unit IS NOT NULL
|
||||||
|
AND CAST(i.quantity AS REAL) != CAST(CAST(i.quantity AS INTEGER) AS REAL))
|
||||||
|
OR
|
||||||
|
-- non-conf products where quantity is not a clean multiple of package size (>2% tolerance)
|
||||||
|
(p.unit != 'conf'
|
||||||
|
AND ABS(i.quantity - ROUND(CAST(i.quantity AS REAL) / p.default_quantity) * p.default_quantity) > (p.default_quantity * 0.02))
|
||||||
|
)
|
||||||
ORDER BY i.updated_at DESC
|
ORDER BY i.updated_at DESC
|
||||||
")->fetchAll();
|
")->fetchAll();
|
||||||
|
|
||||||
|
|||||||
+25
-11
@@ -832,7 +832,7 @@ async function loadDashboard() {
|
|||||||
// Review suspicious quantities
|
// Review suspicious quantities
|
||||||
loadReviewItems();
|
loadReviewItems();
|
||||||
|
|
||||||
// Opened (partially used conf products with known package capacity)
|
// Opened (partially used products with known package capacity)
|
||||||
const openedSection = document.getElementById('alert-opened');
|
const openedSection = document.getElementById('alert-opened');
|
||||||
const openedList = document.getElementById('opened-list');
|
const openedList = document.getElementById('opened-list');
|
||||||
if (statsData.opened && statsData.opened.length > 0) {
|
if (statsData.opened && statsData.opened.length > 0) {
|
||||||
@@ -841,18 +841,32 @@ 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 pkgUnit = item.package_unit;
|
const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg', 'pz': 'pz' };
|
||||||
const unitLabels = { 'ml': 'ml', 'l': 'L', 'g': 'g', 'kg': 'kg' };
|
|
||||||
const pkgLabel = unitLabels[pkgUnit] || pkgUnit;
|
|
||||||
const wholeConf = Math.floor(qty + 0.001);
|
|
||||||
const frac = Math.round((qty - wholeConf) * 1000) / 1000;
|
|
||||||
const remainderAmt = frac * pkgSize;
|
|
||||||
const remainderText = formatSubRemainder(remainderAmt, pkgUnit);
|
|
||||||
let qtyText = '';
|
let qtyText = '';
|
||||||
if (wholeConf > 0) {
|
|
||||||
qtyText = `${wholeConf} conf (da ${pkgSize}${pkgLabel}) + ${remainderText}`;
|
if (item.unit === 'conf') {
|
||||||
|
const pkgUnit = item.package_unit;
|
||||||
|
const pkgLabel = unitLabels[pkgUnit] || pkgUnit;
|
||||||
|
const wholeConf = Math.floor(qty + 0.001);
|
||||||
|
const frac = Math.round((qty - wholeConf) * 1000) / 1000;
|
||||||
|
const remainderAmt = frac * pkgSize;
|
||||||
|
const remainderText = formatSubRemainder(remainderAmt, pkgUnit);
|
||||||
|
if (wholeConf > 0) {
|
||||||
|
qtyText = `${wholeConf} conf (da ${pkgSize}${pkgLabel}) + ${remainderText}`;
|
||||||
|
} else {
|
||||||
|
qtyText = remainderText;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
qtyText = remainderText;
|
const unitLabel = unitLabels[item.unit] || item.unit || '';
|
||||||
|
const wholePackages = Math.floor(qty / pkgSize + 0.001);
|
||||||
|
const remainder = Math.round((qty - wholePackages * pkgSize) * 100) / 100;
|
||||||
|
if (wholePackages > 0 && remainder > 0.01) {
|
||||||
|
qtyText = `${wholePackages} × ${pkgSize}${unitLabel} + ${Math.round(remainder)}${unitLabel} rimasti`;
|
||||||
|
} else if (remainder > 0.01) {
|
||||||
|
qtyText = `${Math.round(remainder)}${unitLabel} / ${pkgSize}${unitLabel}`;
|
||||||
|
} else {
|
||||||
|
qtyText = `${qty}${unitLabel}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return `
|
return `
|
||||||
<div class="alert-item alert-item-clickable" onclick="showAlertItemDetail(${item.id}, ${item.product_id})">
|
<div class="alert-item alert-item-clickable" onclick="showAlertItemDetail(${item.id}, ${item.product_id})">
|
||||||
|
|||||||
Binary file not shown.
+1
-1
@@ -904,6 +904,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="assets/js/app.js?v=20260315h"></script>
|
<script src="assets/js/app.js?v=20260315i"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user