diff --git a/changelog/60_3.0.0_2020-12-22.md b/changelog/60_3.0.0_2020-12-22.md index c7442c8c..774b41fa 100644 --- a/changelog/60_3.0.0_2020-12-22.md +++ b/changelog/60_3.0.0_2020-12-22.md @@ -194,6 +194,19 @@ - The field `expiring_products` was renamed to `due_products` - The field `expired_products` now only contains expired products (so them with `Due date type = Expiration date`) - The new field `overdue_products` contains only overdue products (so them with `Due date type = Best before date`) + - The following endpoints now return all bookings of the transaction (so the response is now an array, was before a single stock booking - and a random one if the transaction affected multiple stock entries) + - PUT `/stock/entry/{entryId}` + - POST `/stock/products/{productId}/add` + - POST `/stock/products/{productId}/consume` + - POST `/stock/products/{productId}/transfer` + - POST `/stock/products/{productId}/inventory` + - POST `/stock/products/{productId}/open` + - POST `/stock/products/by-barcode/{barcode}/add` + - POST `/stock/products/by-barcode/{barcode}/consume` + - POST `/stock/products/by-barcode/{barcode}/transfer` + - POST `/stock/products/by-barcode/{barcode}/inventory` + - POST `/stock/products/by-barcode/{barcode}/open` + - (The response is the same as if you would fetch the stock transaction via `/stock/transactions/{transactionId}`) - For better integration (apps), it's now possible to show a QR-Code for API keys (thanks @fipwmaqzufheoxq92ebc) - New QR-Code button on the "Manage API keys"-page (top right corner settings menu), the QR-Codes contains `|` - And on the calendar page when using the button "Share/Integrate calendar (iCal)", there the QR-Codes contains the Share-URL (which is displayed in the textbox above) diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 0475dee1..410cbf5a 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -139,8 +139,9 @@ class StockApiController extends BaseApiController $transactionType = $requestBody['transactiontype']; } - $bookingId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { @@ -293,8 +294,9 @@ class StockApiController extends BaseApiController } $transactionId = null; - $bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId, $transactionId, $allowSubproductSubstitution, $consumeExact); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId, $transactionId, $allowSubproductSubstitution, $consumeExact); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { @@ -387,8 +389,9 @@ class StockApiController extends BaseApiController $shoppingLocationId = $requestBody['shopping_location_id']; } - $bookingId = $this->getStockService()->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $shoppingLocationId, $price, $requestBody['open'], $requestBody['purchased_date']); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->EditStockEntry($args['entryId'], $requestBody['amount'], $bestBeforeDate, $locationId, $shoppingLocationId, $price, $requestBody['open'], $requestBody['purchased_date']); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { @@ -465,8 +468,9 @@ class StockApiController extends BaseApiController $shoppingLocationId = $requestBody['shopping_location_id']; } - $bookingId = $this->getStockService()->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate, $locationId, $price, $shoppingLocationId, $purchasedDate); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate, $locationId, $price, $shoppingLocationId, $purchasedDate); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { @@ -518,9 +522,9 @@ class StockApiController extends BaseApiController } $transactionId = null; - - $bookingId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId, $transactionId, $allowSubproductSubstitution); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId, $transactionId, $allowSubproductSubstitution); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { @@ -670,7 +674,6 @@ class StockApiController extends BaseApiController try { $transactionRows = $this->getDatabase()->stock_log()->where('transaction_id = :1', $args['transactionId'])->fetchAll(); - if (count($transactionRows) === 0) { throw new \Exception('No transaction was found by the given transaction id'); @@ -719,8 +722,9 @@ class StockApiController extends BaseApiController $specificStockEntryId = $requestBody['stock_entry_id']; } - $bookingId = $this->getStockService()->TransferProduct($args['productId'], $requestBody['amount'], $requestBody['location_id_from'], $requestBody['location_id_to'], $specificStockEntryId); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + $transactionId = $this->getStockService()->TransferProduct($args['productId'], $requestBody['amount'], $requestBody['location_id_from'], $requestBody['location_id_to'], $specificStockEntryId); + $args['transactionId'] = $transactionId; + return $this->StockTransactions($request, $response, $args); } catch (\Exception $ex) { diff --git a/grocy.openapi.json b/grocy.openapi.json index 4025fc0b..95307a28 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1473,7 +1473,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -1830,7 +1833,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -1921,7 +1927,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -1997,7 +2006,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2074,7 +2086,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2142,7 +2157,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2265,7 +2283,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2356,7 +2377,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2432,7 +2456,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2504,7 +2531,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -2572,7 +2602,10 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockLogEntry" + "type": "array", + "items": { + "$ref": "#/components/schemas/StockLogEntry" + } } } } @@ -4770,6 +4803,9 @@ "stock_id": { "type": "string" }, + "transaction_id": { + "type": "string" + }, "transaction_type": { "$ref": "#/components/internalSchemas/StockTransactionType" }, diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js index fa2dbcb6..f87ec466 100644 --- a/public/viewjs/consume.js +++ b/public/viewjs/consume.js @@ -72,11 +72,11 @@ if (productDetails.product.enable_tare_weight_handling == 1 && !jsonData.exact_amount) { - var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount - (parseFloat(productDetails.product.tare_weight) + parseFloat(productDetails.stock_amount))) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; + var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount - (parseFloat(productDetails.product.tare_weight) + parseFloat(productDetails.stock_amount))) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; } else { - var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; + var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; } if (GetUriParam("embedded") !== undefined) @@ -166,7 +166,7 @@ $('#save-mark-as-open-button').on('click', function(e) } Grocy.FrontendHelpers.EndUiBusy("consume-form"); - toastr.success(__t('Marked %1$s of %2$s as opened', jsonForm.amount + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''); + toastr.success(__t('Marked %1$s of %2$s as opened', jsonForm.amount + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''); if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount)) { diff --git a/public/viewjs/inventory.js b/public/viewjs/inventory.js index 895dab30..f9c22c06 100644 --- a/public/viewjs/inventory.js +++ b/public/viewjs/inventory.js @@ -65,7 +65,7 @@ Grocy.Api.Get('stock/products/' + jsonForm.product_id, function(result) { - var successMessage = __t('Stock amount of %1$s is now %2$s', result.product.name, result.stock_amount + " " + __n(result.stock_amount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural)) + '
' + __t("Undo") + ''; + var successMessage = __t('Stock amount of %1$s is now %2$s', result.product.name, result.stock_amount + " " + __n(result.stock_amount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural)) + '
' + __t("Undo") + ''; if (GetUriParam("embedded") !== undefined) { diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index c2395f1a..2eaf57a8 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -635,7 +635,7 @@ $(document).on('click', '.product-consume-button', function(e) Grocy.Api.Get('stock/products/' + productId, function(result) { - var toastMessage = __t('Removed %1$s of %2$s from stock', consumeAmount.toString() + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; + var toastMessage = __t('Removed %1$s of %2$s from stock', consumeAmount.toString() + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; Grocy.FrontendHelpers.EndUiBusy(); toastr.success(toastMessage); diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js index 08654027..66c2c020 100644 --- a/public/viewjs/purchase.js +++ b/public/viewjs/purchase.js @@ -99,7 +99,7 @@ $('#save-purchase-button').on('click', function(e) ); } - var successMessage = __t('Added %1$s of %2$s to stock', result.amount + " " + __n(result.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; + var successMessage = __t('Added %1$s of %2$s to stock', result.amount + " " + __n(result.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + __t("Undo") + ''; if (GetUriParam("embedded") !== undefined) { diff --git a/public/viewjs/stockoverview.js b/public/viewjs/stockoverview.js index 6ad12360..e070af97 100755 --- a/public/viewjs/stockoverview.js +++ b/public/viewjs/stockoverview.js @@ -124,11 +124,11 @@ $(document).on('click', '.product-consume-button', function(e) { if (result.product.enable_tare_weight_handling == 1) { - var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(originalTotalStockAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; + var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(originalTotalStockAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; } else { - var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(consumeAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; + var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(consumeAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural), result.product.name) + '
' + __t("Undo") + ''; } if (wasSpoiled) @@ -184,7 +184,7 @@ $(document).on('click', '.product-open-button', function(e) } Grocy.FrontendHelpers.EndUiBusy(); - toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + productQuName, productName) + '
' + __t("Undo") + ''); + toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + productQuName, productName) + '
' + __t("Undo") + ''); RefreshStatistics(); RefreshProductRow(productId); }, diff --git a/public/viewjs/transfer.js b/public/viewjs/transfer.js index a4a44f22..9ca74b0b 100644 --- a/public/viewjs/transfer.js +++ b/public/viewjs/transfer.js @@ -51,11 +51,11 @@ if (productDetails.product.enable_tare_weight_handling == 1) { - var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount - parseFloat(productDetails.product.tare_weight)) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '
' + __t("Undo") + ''; + var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount - parseFloat(productDetails.product.tare_weight)) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '
' + __t("Undo") + ''; } else { - var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '
' + __t("Undo") + ''; + var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '
' + __t("Undo") + ''; } if (GetUriParam("embedded") !== undefined) diff --git a/services/StockService.php b/services/StockService.php index 6dbda4c4..d6f20372 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -165,8 +165,6 @@ class StockService extends BaseService ]); $logRow->save(); - $returnValue = $this->getDatabase()->lastInsertId(); - $stockRow = $this->getDatabase()->stock()->createRow([ 'product_id' => $productId, 'amount' => $amount, @@ -179,7 +177,7 @@ class StockService extends BaseService ]); $stockRow->save(); - return $returnValue; + return $transactionId; } else { @@ -363,7 +361,7 @@ class StockService extends BaseService } } - return $this->getDatabase()->lastInsertId(); + return $transactionId; } else { @@ -440,7 +438,7 @@ class StockService extends BaseService ]); $logNewRowForStockUpdate->save(); - return $this->getDatabase()->lastInsertId(); + return $transactionId; } public function ExternalBarcodeLookup($barcode, $addFoundProduct) @@ -742,7 +740,6 @@ class StockService extends BaseService } // Tare weight handling - // The given amount is the new total amount including the container weight (gross) // So assume that the amount in stock is the amount also including the container weight $containerWeight = 0; @@ -909,7 +906,7 @@ class StockService extends BaseService } } - return $this->getDatabase()->lastInsertId(); + return $transactionId; } public function RemoveProductFromShoppingList($productId, $amount = 1, $listId = 1) @@ -955,7 +952,6 @@ class StockService extends BaseService } // Tare weight handling - // The given amount is the new total amount including the container weight (gross) // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight $productDetails = (object) $this->GetProductDetails($productId); @@ -1126,7 +1122,7 @@ class StockService extends BaseService } } - return $this->getDatabase()->lastInsertId(); + return $transactionId; } public function UndoBooking($bookingId, $skipCorrelatedBookings = false)