diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 5db2038b..489b8072 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -419,6 +419,59 @@ class StockApiController extends BaseApiController )); } + public function CurrentShoppingLists(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $lists = $this->getStockService()->GetCurrentShoppingLists(); + + return $this->ApiResponse($response, $lists); + } + + public function ShoppingListDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, $this->getStockService()->GetShoppingListDetails($args['listId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + + public function ShoppingListEntries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, $this->getStockService()->GetShoppingListEntries($args['listId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + + public function ShoppingListSetDone(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $entryId = $args['entryId']; + + try { + $requestBody = $request->getParsedBody(); + + $done = 1; + if (array_key_exists('done', $requestBody) && is_numeric($requestBody['done'])) + { + $done = intval($requestBody['done']); + } + + $this->getStockService()->SetShoppingListEntryDone($entryId, $done); + + return $this->EmptyApiResponse($response); + } + catch (\Exception $ex) { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } + public function AddMissingProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try @@ -548,7 +601,7 @@ class StockApiController extends BaseApiController { $addFoundProduct = true; } - + return $this->ApiResponse($response, $this->getStockService()->ExternalBarcodeLookup($args['barcode'], $addFoundProduct)); } catch (\Exception $ex) @@ -614,7 +667,7 @@ class StockApiController extends BaseApiController { throw new \Exception('Stock booking does not exist'); } - + return $this->ApiResponse($response, $stockLogRow); } catch (\Exception $ex) @@ -633,7 +686,7 @@ class StockApiController extends BaseApiController { throw new \Exception('No transaction was found by the given transaction id'); } - + return $this->ApiResponse($response, $transactionRows); } catch (\Exception $ex) diff --git a/grocy.openapi.json b/grocy.openapi.json index 1f4baac7..b7273604 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -2242,6 +2242,164 @@ } } }, + "/stock/shoppinglist": { + "get": { + "summary": "Returns array of shopping lists", + "tags": [ + "Stock" + ], + "responses": { + "200": { + "description": "An array of shopping lists", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CurrentShoppingListResponse" + } + } + } + } + } + } + } + }, + "/stock/shoppinglist/{listId}": { + "get": { + "summary": "Returns the given shopping list content", + "tags": [ + "Stock" + ], + "parameters": [ + { + "in": "path", + "name": "listId", + "required": true, + "description": "A valid shopping list id", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "A ShoppingList object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CurrentShoppingListResponse" + } + } + } + }, + "400": { + "description": "A shopping list with the provided id is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponse" + } + } + } + } + } + } + }, + "/stock/shoppinglist/{listId}/entries": { + "get": { + "summary": "Returns product in shopping list", + "tags": [ + "Stock" + ], + "parameters": [ + { + "in": "path", + "name": "listId", + "required": true, + "description": "A valid shopping list id", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "An array of shopping list entries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShoppingListEntryResponse" + } + } + } + }, + "400": { + "description": "A shopping list with the provided id is not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponse" + } + } + } + } + } + } + }, + "/stock/shoppinglist/set-done/{entryId}": { + "post": { + "summary": "Updates done state for shopping list entry", + "tags": [ + "Stock" + ], + "parameters": [ + { + "in": "path", + "name": "entryId", + "required": true, + "description": "A valid shopping list entry id", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "done": { + "type": "integer", + "description": "1 if entry is done, 0 if not, when omitted 1 is used" + } + }, + "example": { + "done": 1 + } + } + } + } + }, + "responses": { + "204": { + "description": "The operation was successful" + }, + "400": { + "description": "The operation was not successful (possible errors are: Not existing shopping list)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericErrorResponse" + } + } + } + } + } + } + }, "/stock/shoppinglist/add-missing-products": { "post": { "summary": "Adds currently missing products (below defined min. stock amount) to the given shopping list", @@ -4203,6 +4361,55 @@ } } }, + "CurrentShoppingListResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "row_created_timestamp": { + "type": "string", + "format": "date-time" + } + } + }, + "ShoppingListEntryResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "amount": { + "type": "integer" + }, + "row_created_timestamp": { + "type": "string", + "format": "date-time" + }, + "shopping_list_id": { + "type": "integer" + }, + "done": { + "type": "integer", + "description": "0 or 1" + }, + "product": { + "$ref": "#/components/schemas/Product" + } + } + }, "CurrentChoreResponse": { "type": "object", "properties": { diff --git a/routes.php b/routes.php index 8df89867..b328ab2d 100644 --- a/routes.php +++ b/routes.php @@ -141,9 +141,9 @@ $app->group('/api', function(RouteCollectorProxy $group) // System $group->get('/system/info', '\Grocy\Controllers\SystemApiController:GetSystemInfo'); - $group->get('/system/db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime'); + $group->get('/system/db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime'); $group->post('/system/log-missing-localization', '\Grocy\Controllers\SystemApiController:LogMissingLocalization'); - + // Generic entity interaction $group->get('/objects/{entity}', '\Grocy\Controllers\GenericEntityApiController:GetObjects'); $group->get('/objects/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:GetObject'); @@ -201,6 +201,10 @@ $app->group('/api', function(RouteCollectorProxy $group) // Shopping list if (GROCY_FEATURE_FLAG_SHOPPINGLIST) { + $group->get('/stock/shoppinglist', '\Grocy\Controllers\StockApiController:CurrentShoppingLists'); + $group->get('/stock/shoppinglist/{listId}', '\Grocy\Controllers\StockApiController:ShoppingListDetails'); + $group->get('/stock/shoppinglist/{listId}/entries', '\Grocy\Controllers\StockApiController:ShoppingListEntries'); + $group->post('/stock/shoppinglist/set-done/{entryId}', '\Grocy\Controllers\StockApiController:ShoppingListSetDone'); $group->post('/stock/shoppinglist/add-missing-products', '\Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList'); $group->post('/stock/shoppinglist/clear', '\Grocy\Controllers\StockApiController:ClearShoppingList'); $group->post('/stock/shoppinglist/add-product', '\Grocy\Controllers\StockApiController:AddProductToShoppingList'); diff --git a/services/StockService.php b/services/StockService.php index 02c1e0a1..8671ccd0 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -28,7 +28,7 @@ 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)'; } $currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP|\PDO::FETCH_OBJ); - + $relevantProducts = $this->getDatabase()->products()->where('id IN (SELECT product_id FROM (' . $sql . ') x)'); foreach ($relevantProducts as $product) { @@ -818,6 +818,53 @@ class StockService extends BaseService return $this->getDatabase()->lastInsertId(); } + public function GetCurrentShoppingLists() + { + return $this->getDatabase()->shopping_lists()->fetchAll(\PDO::FETCH_OBJ); + } + + public function GetShoppingListDetails($listId) + { + $shoppingListRow = $this->getDatabase()->shopping_lists()->where('id = :1', $listId)->fetch(); + + if ($shoppingListRow === null) + { + throw new \Exception('Shopping list does not exist'); + } + + return $shoppingListRow; + } + + public function GetShoppingListEntries($listId) + { + if (!$this->ShoppingListExists($listId)) + { + throw new \Exception('Shopping list does not exist'); + } + + $entries = $this->getDatabase()->shopping_list()->where('shopping_list_id', $listId)->fetchAll(); + + foreach ($entries as $entry) { + $entry->product = $this->getDatabase()->products()->where('id', $entry->product_id)->fetch(); + } + + return $entries; + } + + public function SetShoppingListEntryDone($entryId, $done = 1) + { + if (!$this->ShoppingListEntryExists($entryId)) + { + throw new \Exception('Entry does not exist'); + } + + $entry = $this->getDatabase()->shopping_list()->where('id', $entryId)->fetch(); + + $entry->update(array( + 'done' => $done + )); + } + public function AddMissingProductsToShoppingList($listId = 1) { if (!$this->ShoppingListExists($listId)) @@ -929,18 +976,25 @@ class StockService extends BaseService return $productRow !== null; } + + private function ShoppingListExists($listId) + { + $shoppingListRow = $this->getDatabase()->shopping_lists()->where('id = :1', $listId)->fetch(); + return $shoppingListRow !== null; + } + + private function ShoppingListEntryExists($listId) + { + $shoppingListEntryRow = $this->getDatabase()->shopping_list()->where('id = :1', $listId)->fetch(); + return $shoppingListEntryRow !== null; + } + private function LocationExists($locationId) { $locationRow = $this->getDatabase()->locations()->where('id = :1', $locationId)->fetch(); return $locationRow !== null; } - private function ShoppingListExists($listId) - { - $shoppingListRow = $this->getDatabase()->shopping_lists()->where('id = :1', $listId)->fetch(); - return $shoppingListRow !== null; - } - private function LoadBarcodeLookupPlugin() { $pluginName = defined('GROCY_STOCK_BARCODE_LOOKUP_PLUGIN') ? GROCY_STOCK_BARCODE_LOOKUP_PLUGIN : '';