diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index beecdafe..49d94c55 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -170,7 +170,7 @@ class StockApiController extends BaseApiController { try { - $this->ApiResponse($this->StockService->UndoBooking($args['stockLogId'])); + $this->ApiResponse($this->StockService->UndoBooking($args['bookingId'])); return $this->ApiResponse(array('success' => true)); } catch (\Exception $ex) diff --git a/grocy.openapi.json b/grocy.openapi.json index 61097667..cce2e53a 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1288,7 +1288,7 @@ } } }, - "/stock/undo-booking/{stockLogId}": { + "/stock/undo-booking/{bookingId}": { "get": { "description": "Undoes a booking", "tags": [ @@ -1297,9 +1297,9 @@ "parameters": [ { "in": "path", - "name": "stockLogId", + "name": "bookingId", "required": true, - "description": "A valid stock log id", + "description": "A valid stock booking id", "schema": { "type": "integer" } diff --git a/migrations/0044.sql b/migrations/0044.sql index 6f2aac90..0657fb4f 100644 --- a/migrations/0044.sql +++ b/migrations/0044.sql @@ -1,5 +1,8 @@ ALTER TABLE stock_log ADD undone TINYINT NOT NULL DEFAULT 0 CHECK(undone IN (0, 1)); +UPDATE stock_log +SET undone = 0; + ALTER TABLE stock_log ADD undone_timestamp DATETIME; diff --git a/routes.php b/routes.php index 02964c67..d11a497b 100644 --- a/routes.php +++ b/routes.php @@ -114,7 +114,7 @@ $app->group('/api', function() $this->get('/stock/add-missing-products-to-shoppinglist', '\Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList'); $this->get('/stock/clear-shopping-list', '\Grocy\Controllers\StockApiController:ClearShoppingList'); $this->get('/stock/external-barcode-lookup/{barcode}', '\Grocy\Controllers\StockApiController:ExternalBarcodeLookup'); - $this->get('/stock/undo-booking/{stockLogId}', '\Grocy\Controllers\StockApiController:UndoBooking'); + $this->get('/stock/undo-booking/{bookingId}', '\Grocy\Controllers\StockApiController:UndoBooking'); // Recipes $this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList'); diff --git a/services/StockService.php b/services/StockService.php index 1f421100..b2a39da2 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -47,14 +47,14 @@ class StockService extends BaseService $product = $this->Database->products($productId); $productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount'); - $productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date'); - $productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date'); + $productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date')->where('undone', 0); + $productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date')->where('undone', 0); $nextBestBeforeDate = $this->Database->stock()->where('product_id', $productId)->min('best_before_date'); $quPurchase = $this->Database->quantity_units($product->qu_id_purchase); $quStock = $this->Database->quantity_units($product->qu_id_stock); $lastPrice = null; - $lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2', $productId, self::TRANSACTION_TYPE_PURCHASE)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch(); + $lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2 AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch(); if ($lastLogRow !== null && !empty($lastLogRow)) { $lastPrice = $lastLogRow->price; @@ -80,7 +80,7 @@ class StockService extends BaseService } $returnData = array(); - $rows = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2', $productId, self::TRANSACTION_TYPE_PURCHASE)->whereNOT('price', null)->orderBy('purchased_date', 'DESC'); + $rows = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2 AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE)->whereNOT('price', null)->orderBy('purchased_date', 'DESC'); foreach ($rows as $row) { $returnData[] = array( @@ -306,18 +306,21 @@ class StockService extends BaseService return $pluginOutput; } - public function UndoBooking($stockLogId) + public function UndoBooking($bookingId) { - //TODO: This is not possible when the given booking has subsequent bookings - - $logRow = $this->Database->stock_log()->where('id = :1', $stockLogId)->fetch(); - + $logRow = $this->Database->stock_log()->where('id = :1 AND undone = 0', $bookingId)->fetch(); if ($logRow == null) { - throw new \Exception('Booking does not exist'); + throw new \Exception('Booking does not exist or was already undone'); } - if ($logRow->transaction_type === self::TRANSACTION_TYPE_PURCHASE) + $hasSubsequentBookings = $this->Database->stock_log()->where('stock_id = :1 AND id != :2 AND id > :2', $logRow->stock_id, $logRow->id)->count() > 0; + if ($hasSubsequentBookings) + { + throw new \Exception('Booking has subsequent dependent bookings, undo not possible'); + } + + if ($logRow->transaction_type === self::TRANSACTION_TYPE_PURCHASE || ($logRow->transaction_type === self::TRANSACTION_TYPE_INVENTORY_CORRECTION && $logRow->amount > 0)) { // Remove corresponding stock entry $stockRows = $this->Database->stock()->where('stock_id', $logRow->stock_id); @@ -329,7 +332,7 @@ class StockService extends BaseService 'undone_timestamp' => date('Y-m-d H:i:s') )); } - elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_CONSUME || $logRow->transaction_type === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) + elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_CONSUME || ($logRow->transaction_type === self::TRANSACTION_TYPE_INVENTORY_CORRECTION && $logRow->amount < 0)) { // Add corresponding amount back to stock $stockRow = $this->Database->stock()->createRow(array(