Added an API endpoint to add/remove products to stock by its barcode (closes #331)

This commit is contained in:
Bernd Bestel 2019-09-19 18:36:46 +02:00
parent a4d479d047
commit 58a69d650f
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
4 changed files with 308 additions and 42 deletions

View File

@ -61,6 +61,8 @@
- New endpoint `/stock/shoppinglist/add-product` to add a product to a shopping list (thanks @Forceu)
- New endpoint `/stock/shoppinglist/remove-product` to remove a product from a shopping list (thanks @Forceu)
- New endpoint `/chores/executions/calculate-next-assignments` to (re)calculate next user assignments for a single or all chores
- New endpoint `/stock/products/by-barcode/{barcode}/add` to add a product to stock by its barcode
- New endpoint `/stock/products/by-barcode/{barcode}/consume` to remove a product to stock by its barcode
- New endpoint `/objects/{entity}/search/{searchString}` search for objects by name (contains search)
- Endpoint `GET /files/{group}/{fileName}` can now also downscale pictures (see API documentation on [/api](https://demo-en.grocy.info/api))
- When adding a product (through `stock/product/{productId}/add` or `stock/product/{productId}/inventory`) with omitted best before date and if the given product has "Default best before days" set, the best before date is calculated based on that (so far always today was used which is still the case when no date is supplied and also the product has no "Default best before days set) (thanks @Forceu)

View File

@ -100,6 +100,57 @@ class StockApiController extends BaseApiController
}
}
public function AddProductByBarcode(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('amount', $requestBody))
{
throw new \Exception('An amount is required');
}
$bestBeforeDate = null;
if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date']))
{
$bestBeforeDate = $requestBody['best_before_date'];
}
$price = null;
if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price']))
{
$price = $requestBody['price'];
}
$locationId = null;
if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id']))
{
$locationId = $requestBody['location_id'];
}
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
{
$transactionType = $requestBody['transactiontype'];
}
$productId = $this->StockService->GetProductIdFromBarcode($args['barcode']);
$bookingId = $this->StockService->AddProduct($productId, $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $locationId);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
@ -149,6 +200,57 @@ class StockApiController extends BaseApiController
}
}
public function ConsumeProductByBarcode(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();
try
{
if ($requestBody === null)
{
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
if (!array_key_exists('amount', $requestBody))
{
throw new \Exception('An amount is required');
}
$spoiled = false;
if (array_key_exists('spoiled', $requestBody))
{
$spoiled = $requestBody['spoiled'];
}
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype']))
{
$transactionType = $requestBody['transactiontype'];
}
$specificStockEntryId = 'default';
if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id']))
{
$specificStockEntryId = $requestBody['stock_entry_id'];
}
$recipeId = null;
if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id']))
{
$recipeId = $requestBody['recipe_id'];
}
$productId = $this->StockService->GetProductIdFromBarcode($args['barcode']);
$bookingId = $this->StockService->ConsumeProduct($productId, $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId);
return $this->ApiResponse($this->Database->stock_log($bookingId));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$requestBody = $request->getParsedBody();

View File

@ -1131,47 +1131,6 @@
}
}
},
"/stock/products/by-barcode/{barcode}": {
"get": {
"summary": "Returns details of the given product by its barcode",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "barcode",
"required": true,
"description": "Barcode",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "A ProductDetailsResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProductDetailsResponse"
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Unknown barcode)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponse"
}
}
}
}
}
}
},
"/stock/products/{productId}/entries": {
"get": {
"summary": "Returns all stock entries of the given product in order of next use (first expiring first, then first in first out)",
@ -1556,6 +1515,207 @@
}
}
},
"/stock/products/by-barcode/{barcode}": {
"get": {
"summary": "Returns details of the given product by its barcode",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "barcode",
"required": true,
"description": "Barcode",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "A ProductDetailsResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProductDetailsResponse"
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Unknown barcode)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponse"
}
}
}
}
}
}
},
"/stock/products/by-barcode/{barcode}/add": {
"post": {
"summary": "Adds the given amount of the given product by its barcode to stock",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "barcode",
"required": true,
"description": "Barcode",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"format": "number",
"description": "The amount to add - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
},
"best_before_date": {
"type": "string",
"format": "date",
"description": "The best before date of the product to add, when omitted, the current date is used"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
},
"price": {
"type": "number",
"format": "number",
"description": "The price per purchase quantity unit in configured currency"
},
"location_id": {
"type": "number",
"format": "integer",
"description": "If omitted, the default location of the product is used"
}
},
"example": {
"amount": 1,
"best_before_date": "2019-01-19",
"transaction_type": "purchase",
"price": "1.99"
}
}
}
}
},
"responses": {
"200": {
"description": "The operation was successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StockLogEntry"
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Not existing product, invalid transaction type)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponse"
}
}
}
}
}
}
},
"/stock/products/by-barcode/{barcode}/consume": {
"post": {
"summary": "Removes the given amount of the given product by its barcode from stock",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "barcode",
"required": true,
"description": "Barcode",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight"
},
"transaction_type": {
"$ref": "#/components/internalSchemas/StockTransactionType"
},
"spoiled": {
"type": "boolean",
"description": "True when the given product was spoiled, defaults to false"
},
"stock_entry_id": {
"type": "string",
"description": "A specific stock entry id to consume, if used, the amount has to be 1"
},
"recipe_id": {
"type": "number",
"format": "integer",
"description": "A valid recipe id for which this product was used (for statistical purposes only)"
}
},
"example": {
"amount": 1,
"transaction_type": "consume",
"spoiled": false
}
}
}
}
},
"responses": {
"200": {
"description": "The operation was successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StockLogEntry"
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Not existing product, invalid transaction type, given amount > current stock amount)",
"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",

View File

@ -159,13 +159,15 @@ $app->group('/api', function()
$this->get('/stock', '\Grocy\Controllers\StockApiController:CurrentStock');
$this->get('/stock/volatile', '\Grocy\Controllers\StockApiController:CurrentVolatilStock');
$this->get('/stock/products/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails');
$this->get('/stock/products/by-barcode/{barcode}', '\Grocy\Controllers\StockApiController:ProductDetailsByBarcode');
$this->get('/stock/products/{productId}/entries', '\Grocy\Controllers\StockApiController:ProductStockEntries');
$this->get('/stock/products/{productId}/price-history', '\Grocy\Controllers\StockApiController:ProductPriceHistory');
$this->post('/stock/products/{productId}/add', '\Grocy\Controllers\StockApiController:AddProduct');
$this->post('/stock/products/{productId}/consume', '\Grocy\Controllers\StockApiController:ConsumeProduct');
$this->post('/stock/products/{productId}/inventory', '\Grocy\Controllers\StockApiController:InventoryProduct');
$this->post('/stock/products/{productId}/open', '\Grocy\Controllers\StockApiController:OpenProduct');
$this->get('/stock/products/by-barcode/{barcode}', '\Grocy\Controllers\StockApiController:ProductDetailsByBarcode');
$this->post('/stock/products/by-barcode/{barcode}/add', '\Grocy\Controllers\StockApiController:AddProductByBarcode');
$this->post('/stock/products/by-barcode/{barcode}/consume', '\Grocy\Controllers\StockApiController:ConsumeProductByBarcode');
$this->post('/stock/bookings/{bookingId}/undo', '\Grocy\Controllers\StockApiController:UndoBooking');
$this->get('/stock/barcodes/external-lookup', '\Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
}