From a014b6da06f5c0d0dcd65fa9de197ee7d52bdbde Mon Sep 17 00:00:00 2001 From: Gregory SACRE Date: Fri, 17 Jan 2020 00:08:16 +0100 Subject: [PATCH] Feature request : api/stock can return detailed products #487 Added a new API: /api/stock/full This API returns the same values as "/api/stock" except that it also gives the details of the corresponding product. --- controllers/StockApiController.php | 11 ++- grocy.openapi.json | 120 ++++++++++++++++++++++++++++- routes.php | 7 +- services/StockService.php | 31 +++++--- 4 files changed, 149 insertions(+), 20 deletions(-) diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index c9cc5538..5199c5e1 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -388,6 +388,11 @@ class StockApiController extends BaseApiController return $this->ApiResponse($this->StockService->GetCurrentStock()); } + public function CurrentStockFull(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) + { + return $this->ApiResponse($this->StockService->GetCurrentStockFull()); + } + public function CurrentVolatilStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) { $nextXDays = 5; @@ -535,7 +540,7 @@ class StockApiController extends BaseApiController { $addFoundProduct = true; } - + return $this->ApiResponse($this->StockService->ExternalBarcodeLookup($args['barcode'], $addFoundProduct)); } catch (\Exception $ex) @@ -590,7 +595,7 @@ class StockApiController extends BaseApiController { throw new \Exception('Stock booking does not exist'); } - + return $this->ApiResponse($stockLogRow); } catch (\Exception $ex) @@ -609,7 +614,7 @@ class StockApiController extends BaseApiController { throw new \Exception('No transaction was found by the given transaction id'); } - + return $this->ApiResponse($transactionRows); } catch (\Exception $ex) diff --git a/grocy.openapi.json b/grocy.openapi.json index 2c981663..bf226b85 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -163,7 +163,7 @@ { "$ref": "#/components/schemas/StockEntry" } - ] + ] } } } @@ -626,7 +626,7 @@ } } } - }, + }, "/files/{group}/{fileName}": { "get": { "summary": "Serves the given file", @@ -1124,6 +1124,29 @@ } } }, + "/stock/full": { + "get": { + "summary": "Returns all products which are currently in stock incl. product details", + "tags": [ + "Stock" + ], + "responses": { + "200": { + "description": "An array of CurrentStockResponse objects with product details", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CurrentStockResponseFull" + } + } + } + } + } + } + } + }, "/stock/volatile": { "get": { "summary": "Returns all products which are expiring soon, are already expired or currently missing", @@ -4028,6 +4051,99 @@ } } }, + "CurrentStockResponseFull": { + "type": "object", + "properties": { + "product_id": { + "type": "integer" + }, + "amount": { + "type": "number" + }, + "amount_aggregated": { + "type": "number" + }, + "amount_opened": { + "type": "number" + }, + "amount_opened_aggregated": { + "type": "number" + }, + "best_before_date": { + "type": "string", + "format": "date", + "description": "The next best before date for this product" + }, + "is_aggregated_amount": { + "type": "boolean", + "description": "Indicates wheter this product has sub-products or not / if the fields `amount_aggregated` and `amount_opened_aggregated` are filled" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "location_id": { + "type": "integer" + }, + "qu_id_purchase": { + "type": "integer" + }, + "qu_id_stock": { + "type": "integer" + }, + "enable_tare_weight_handling": { + "type": "integer" + }, + "not_check_stock_fulfillment_for_recipes": { + "type": "integer" + }, + "product_group_id": { + "type": "integer" + }, + "qu_factor_purchase_to_stock": { + "type": "number", + "format": "number" + }, + "tare_weight": { + "type": "number", + "format": "number" + }, + "barcode": { + "type": "string", + "description": "Can contain multiple barcodes separated by comma" + }, + "min_stock_amount": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "default_best_before_days": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "default_best_before_days_after_open": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "picture_file_name": { + "type": "string" + }, + "allow_partial_units_in_stock": { + "type": "boolean" + }, + "row_created_timestamp": { + "type": "string", + "format": "date-time" + } + } + }, "CurrentChoreResponse": { "type": "object", "properties": { diff --git a/routes.php b/routes.php index 9f4ae346..857d831b 100644 --- a/routes.php +++ b/routes.php @@ -109,7 +109,7 @@ $app->group('', function() $this->get('/equipment', '\Grocy\Controllers\EquipmentController:Overview'); $this->get('/equipment/{equipmentId}', '\Grocy\Controllers\EquipmentController:EditForm'); } - + // Calendar routes if (GROCY_FEATURE_FLAG_CALENDAR) { @@ -129,9 +129,9 @@ $app->group('/api', function() // System $this->get('/system/info', '\Grocy\Controllers\SystemApiController:GetSystemInfo'); - $this->get('/system/db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime'); + $this->get('/system/db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime'); $this->post('/system/log-missing-localization', '\Grocy\Controllers\SystemApiController:LogMissingLocalization'); - + // Generic entity interaction $this->get('/objects/{entity}', '\Grocy\Controllers\GenericEntityApiController:GetObjects'); $this->get('/objects/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:GetObject'); @@ -162,6 +162,7 @@ $app->group('/api', function() { $this->get('/stock', '\Grocy\Controllers\StockApiController:CurrentStock'); $this->put('/stock', '\Grocy\Controllers\StockApiController:EditStock'); + $this->get('/stock/full', '\Grocy\Controllers\StockApiController:CurrentStockFull'); $this->get('/stock/volatile', '\Grocy\Controllers\StockApiController:CurrentVolatilStock'); $this->get('/stock/products/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails'); $this->get('/stock/products/{productId}/entries', '\Grocy\Controllers\StockApiController:ProductStockEntries'); diff --git a/services/StockService.php b/services/StockService.php index efde1419..8d99fe3f 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -26,7 +26,14 @@ class StockService extends BaseService $sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)'; } - + + return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); + } + + public function GetCurrentStockFull() + { + $sql = 'select * from stock_current join products on products.id = stock_current.product_id'; + return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } @@ -116,7 +123,7 @@ class StockService extends BaseService $quStock = $this->Database->quantity_units($product->qu_id_stock); $location = $this->Database->locations($product->location_id); $averageShelfLifeDays = intval($this->Database->stock_average_product_shelf_life()->where('id', $productId)->fetch()->average_shelf_life_days); - + $lastPrice = null; $lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type IN (:2, :3) AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE, self::TRANSACTION_TYPE_INVENTORY_CORRECTION)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch(); if ($lastLogRow !== null && !empty($lastLogRow)) @@ -208,20 +215,20 @@ class StockService extends BaseService { throw new \Exception('The amount cannot be lower or equal than the defined tare weight + current stock amount'); } - + $amount = $amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight); } - + //Sets the default best before date, if none is supplied if ($bestBeforeDate == null) { if (intval($productDetails->product->default_best_before_days) == -1) { - $bestBeforeDate = date('2999-12-31'); + $bestBeforeDate = date('2999-12-31'); } else if (intval($productDetails->product->default_best_before_days) > 0) { - $bestBeforeDate = date('Y-m-d', strtotime(date('Y-m-d') . ' + '.$productDetails->product->default_best_before_days.' days')); + $bestBeforeDate = date('Y-m-d', strtotime(date('Y-m-d') . ' + '.$productDetails->product->default_best_before_days.' days')); } else { @@ -235,7 +242,7 @@ class StockService extends BaseService { $transactionId = uniqid(); } - + $stockId = uniqid(); $logRow = $this->Database->stock_log()->createRow(array( @@ -294,7 +301,7 @@ class StockService extends BaseService { throw new \Exception('The amount cannot be lower than the defined tare weight'); } - + $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); } @@ -421,7 +428,7 @@ class StockService extends BaseService { throw new \Exception('The amount cannot be lower than the defined tare weight'); } - + $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); } @@ -626,7 +633,7 @@ class StockService extends BaseService { $containerWeight = floatval($productDetails->product->tare_weight); } - + if ($newAmount == floatval($productDetails->stock_amount) + $containerWeight) { throw new \Exception('The new amount cannot equal the current stock amount'); @@ -638,7 +645,7 @@ class StockService extends BaseService { $bookingAmount = $newAmount; } - + return $this->AddProduct($productId, $bookingAmount, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION, date('Y-m-d'), $price, $locationId); } else if ($newAmount < $productDetails->stock_amount + $containerWeight) @@ -819,7 +826,7 @@ class StockService extends BaseService { $productRow->update(array('amount' => $newAmount)); } - + } }