From 223457bbdfe496f8ce028e967f7555365c0e4486 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Fri, 29 May 2026 06:46:37 +0000 Subject: [PATCH] fix: addToInventory creates new row when all existing rows are opened When adding a new pack of a product that already has an opened row in inventory (opened_at IS NOT NULL), the previous code merged the new stock into the opened row, corrupting opened_at tracking and hiding the second pack from the anomaly model. Now: search only for sealed rows (opened_at IS NULL) to merge into. If only opened rows exist, INSERT a new sealed row instead. --- CHANGELOG.md | 6 ++++++ api/index.php | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4458a..42b96d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ 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 diff --git a/api/index.php b/api/index.php index 187d042..0b1c43c 100644 --- a/api/index.php +++ b/api/index.php @@ -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]); }