Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6478b20e1 | |||
| 223457bbdf | |||
| 12c6a8977a | |||
| c7a69d8379 |
@@ -11,6 +11,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap.
|
||||
|
||||
## [1.7.31] - 2026-05-29
|
||||
|
||||
### Fixed
|
||||
- **New pack merges into opened pack on add** — `addToInventory` was looking for ANY existing row for the same product+location and adding the new quantity to it. This caused a newly purchased sealed pack to be silently merged with an already-opened pack, collapsing two physically distinct containers into one row and corrupting the `opened_at` timestamp. The fix now searches only for a **sealed** (unopened) row (`opened_at IS NULL`) to merge into. If only opened rows exist, a new sealed row is created instead — keeping the two packs separate and allowing the anomaly model and shelf-life tracker to work correctly.
|
||||
|
||||
|
||||
## [1.7.30] - 2026-05-29
|
||||
|
||||
### Fixed
|
||||
- **False consumption anomaly with multi-row stock** — The anomaly detection banner was evaluating each inventory row in isolation. Products split across multiple rows (e.g. one opened pack with 1 pz + one sealed pack with 6 pz) incorrectly triggered a "consumed faster than expected" warning because only the opened row (1 pz) was compared against the model. The check now aggregates the total quantity across all rows for the same product before deciding to flag an anomaly. If the combined total ≥ expected remaining, the anomaly is suppressed.
|
||||
|
||||
|
||||
## [1.7.29] - 2026-05-29
|
||||
|
||||
### Added
|
||||
|
||||
+30
-5
@@ -2634,19 +2634,26 @@ function addToInventory(PDO $db): void {
|
||||
|
||||
$vacuumSealed = (int)($input['vacuum_sealed'] ?? 0);
|
||||
|
||||
// Check if product already exists in this location
|
||||
$stmt = $db->prepare("SELECT id, quantity FROM inventory WHERE product_id = ? AND location = ?");
|
||||
// Check if a SEALED (not yet opened) row exists for this product+location.
|
||||
// We merge new stock into a sealed row only — never into an already-opened
|
||||
// pack, because that would conflate two physically distinct containers and
|
||||
// corrupt the opened_at timestamp tracking.
|
||||
$stmt = $db->prepare("
|
||||
SELECT id, quantity FROM inventory
|
||||
WHERE product_id = ? AND location = ? AND opened_at IS NULL
|
||||
ORDER BY added_at ASC LIMIT 1
|
||||
");
|
||||
$stmt->execute([$productId, $location]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
|
||||
if ($existing) {
|
||||
// Update quantity
|
||||
// Merge into the existing sealed row
|
||||
$newQty = $existing['quantity'] + $quantity;
|
||||
$stmt = $db->prepare("UPDATE inventory SET quantity = ?, expiry_date = COALESCE(?, expiry_date), vacuum_sealed = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
$stmt->execute([$newQty, $expiry, $vacuumSealed, $existing['id']]);
|
||||
} else {
|
||||
$newQty = $quantity;
|
||||
// Insert new inventory entry
|
||||
// All existing rows (if any) are opened packs — insert a new sealed row
|
||||
$stmt = $db->prepare("INSERT INTO inventory (product_id, location, quantity, expiry_date, vacuum_sealed) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$productId, $location, $quantity, $expiry, $vacuumSealed]);
|
||||
}
|
||||
@@ -4155,6 +4162,24 @@ function getConsumptionPredictions(PDO $db): void {
|
||||
$expectedQty = max(0, $baselineQty - ($dailyRate * $daysSinceRestock));
|
||||
$actualQty = floatval($item['quantity']);
|
||||
|
||||
// Aggregate total stock for this product across ALL inventory rows.
|
||||
// A product may be split into multiple rows (e.g. one opened pack + one
|
||||
// sealed pack at a different location). The opened row alone may look
|
||||
// depleted while the total is healthy — do not flag in that case.
|
||||
$totalQtyStmt = $db->prepare("
|
||||
SELECT COALESCE(SUM(quantity), 0)
|
||||
FROM inventory
|
||||
WHERE product_id = ? AND quantity > 0
|
||||
");
|
||||
$totalQtyStmt->execute([$pid]);
|
||||
$totalQtyAllRows = floatval($totalQtyStmt->fetchColumn() ?: 0);
|
||||
// If the aggregate total is above the expected remaining, the "depletion"
|
||||
// is just stock spread across rows — suppress the anomaly.
|
||||
if ($totalQtyAllRows >= $expectedQty) continue;
|
||||
// Use the aggregate total as the visible actual qty so the banner shows
|
||||
// the real combined stock, not just the single opened row.
|
||||
$actualQty = $totalQtyAllRows;
|
||||
|
||||
// Need at least some post-restock usage observations before warning.
|
||||
if ($txSinceRestock < 2) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user