Stock detail updates (#493)

* Fix spelling

* stockdetail refresh with location name

* Stock updates

* change stock_row_id to id

* fix stockdetail refresh rows after clicking undo

* fix stockdetail consume spoiled
This commit is contained in:
kriddles 2020-01-17 10:54:34 -06:00 committed by Bernd Bestel
parent d4bec3bd10
commit 2a608c41e9
7 changed files with 96 additions and 32 deletions

View File

@ -124,7 +124,7 @@ class StockApiController extends BaseApiController
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
} }
if (!array_key_exists('stock_row_id', $requestBody)) if (!array_key_exists('id', $requestBody))
{ {
throw new \Exception('A stock row id is required'); throw new \Exception('A stock row id is required');
} }
@ -152,7 +152,7 @@ class StockApiController extends BaseApiController
$locationId = $requestBody['location_id']; $locationId = $requestBody['location_id'];
} }
$bookingId = $this->StockService->EditStock($requestBody['stock_row_id'], $requestBody['amount'], $bestBeforeDate, $locationId, $price); $bookingId = $this->StockService->EditStock($requestBody['id'], $requestBody['amount'], $bestBeforeDate, $locationId, $price);
return $this->ApiResponse($this->Database->stock_log($bookingId)); return $this->ApiResponse($this->Database->stock_log($bookingId));
} }
catch (\Exception $ex) catch (\Exception $ex)
@ -388,7 +388,7 @@ class StockApiController extends BaseApiController
return $this->ApiResponse($this->StockService->GetCurrentStock()); return $this->ApiResponse($this->StockService->GetCurrentStock());
} }
public function CurrentVolatilStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function CurrentVolatileStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
$nextXDays = 5; $nextXDays = 5;
if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days'])) if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days']))
@ -580,6 +580,11 @@ class StockApiController extends BaseApiController
return $this->ApiResponse($this->StockService->GetProductStockLocations($args['productId'])); return $this->ApiResponse($this->StockService->GetProductStockLocations($args['productId']));
} }
public function StockEntry(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
return $this->ApiResponse($this->StockService->GetStockEntry($args['entryId']));
}
public function StockBooking(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function StockBooking(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
try try

View File

@ -1063,10 +1063,10 @@
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"stock_row_id": { "id": {
"type": "number", "type": "number",
"format": "number", "format": "number",
"description": "The Stock Row Id" "description": "The stock table id"
}, },
"amount": { "amount": {
"type": "number", "type": "number",
@ -1090,7 +1090,7 @@
} }
}, },
"example": { "example": {
"stock_row_id": 2, "id": 2,
"amount": 1, "amount": 1,
"best_before_date": "2019-01-19", "best_before_date": "2019-01-19",
"location_id": 2, "location_id": 2,
@ -1124,6 +1124,47 @@
} }
} }
}, },
"/stock/{entryId}/entry": {
"get": {
"summary": "Returns details of the given stock",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "entryId",
"required": true,
"description": "A valid stock row id",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "A StockEntry Response object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StockEntry"
}
}
}
},
"400": {
"description": "The operation was not successful (possible errors are: Not existing product)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GenericErrorResponse"
}
}
}
}
}
}
},
"/stock/volatile": { "/stock/volatile": {
"get": { "get": {
"summary": "Returns all products which are expiring soon, are already expired or currently missing", "summary": "Returns all products which are expiring soon, are already expired or currently missing",
@ -3203,7 +3244,6 @@
"quantity_unit_conversions", "quantity_unit_conversions",
"shopping_list", "shopping_list",
"shopping_lists", "shopping_lists",
"stock",
"recipes", "recipes",
"recipes_pos", "recipes_pos",
"recipes_nestings", "recipes_nestings",

View File

@ -68,7 +68,7 @@ $(document).on('click', '.stock-consume-button', function(e)
var stockRowId = $(e.currentTarget).attr('data-stockrow-id'); var stockRowId = $(e.currentTarget).attr('data-stockrow-id');
var consumeAmount = $(e.currentTarget).attr('data-consume-amount'); var consumeAmount = $(e.currentTarget).attr('data-consume-amount');
var wasSpoiled = $(e.currentTarget).hasClass("product-consume-button-spoiled"); var wasSpoiled = $(e.currentTarget).hasClass("stock-consume-button-spoiled");
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'location_id': locationId, 'stock_entry_id': specificStockEntryId}, Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'location_id': locationId, 'stock_entry_id': specificStockEntryId},
function(bookingResponse) function(bookingResponse)
@ -76,15 +76,15 @@ $(document).on('click', '.stock-consume-button', function(e)
Grocy.Api.Get('stock/products/' + productId, Grocy.Api.Get('stock/products/' + productId,
function(result) 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) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(' + bookingResponse.id + ')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>'; 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) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse.id + ',' + stockRowId + ')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (wasSpoiled) if (wasSpoiled)
{ {
toastMessage += " (" + __t("Spoiled") + ")"; toastMessage += " (" + __t("Spoiled") + ")";
} }
Grocy.FrontendHelpers.EndUiBusy(); Grocy.FrontendHelpers.EndUiBusy();
toastr.success(toastMessage);
RefreshStockDetailRow(stockRowId); RefreshStockDetailRow(stockRowId);
toastr.success(toastMessage);
}, },
function(xhr) function(xhr)
{ {
@ -215,7 +215,7 @@ $(document).on("click", ".product-add-to-shopping-list-button", function(e)
function RefreshStockDetailRow(stockRowId) function RefreshStockDetailRow(stockRowId)
{ {
Grocy.Api.Get("objects/stock/" + stockRowId, Grocy.Api.Get("stock/" + stockRowId + "/entry",
function(result) function(result)
{ {
var stockRow = $('#stock-' + stockRowId + '-row'); var stockRow = $('#stock-' + stockRowId + '-row');
@ -249,11 +249,20 @@ function RefreshStockDetailRow(stockRowId)
$(this).text(result.best_before_date).fadeIn(500); $(this).text(result.best_before_date).fadeIn(500);
}); });
var locationName = "";
Grocy.Api.Get("objects/locations/" + result.location_id,
function(locationResult)
{
locationName = locationResult.name;
},
function(xhr)
{
console.error(xhr);
});
$('#stock-' + stockRowId + '-location').parent().effect('highlight', { }, 500); $('#stock-' + stockRowId + '-location').parent().effect('highlight', { }, 500);
$('#stock-' + stockRowId + '-location').fadeOut(500, function() $('#stock-' + stockRowId + '-location').fadeOut(500, function()
{ {
//TODO grab location name instead of id $(this).text(locationName).fadeIn(500);
$(this).text(result.location_id).fadeIn(500);
}); });
$('#stock-' + stockRowId + '-price').parent().effect('highlight', { }, 500); $('#stock-' + stockRowId + '-price').parent().effect('highlight', { }, 500);
@ -292,3 +301,18 @@ $(window).on("message", function(e)
RefreshStockDetailRow(data.Payload); RefreshStockDetailRow(data.Payload);
} }
}); });
function UndoStockBookingEntry(bookingId, stockRowId)
{
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', { },
function(result)
{
window.postMessage(WindowMessageBag("StockDetailChanged", stockRowId), Grocy.BaseUrl);
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -1,6 +1,6 @@
$(document).ready(function() { $(document).ready(function() {
var stockRowId = GetUriParam('stockRowId'); var stockRowId = GetUriParam('stockRowId');
Grocy.Api.Get("objects/stock/" + stockRowId, Grocy.Api.Get("stock/" + stockRowId + "/entry",
function(stockEntry) function(stockEntry)
{ {
Grocy.Components.LocationPicker.SetId(stockEntry.location_id); Grocy.Components.LocationPicker.SetId(stockEntry.location_id);
@ -79,12 +79,12 @@ $('#save-stockedit-button').on('click', function(e)
var bookingResponse = null; var bookingResponse = null;
var stockRowId = GetUriParam('stockRowId'); var stockRowId = GetUriParam('stockRowId');
jsonData.stock_row_id = stockRowId; jsonData.id = stockRowId;
Grocy.Api.Put("stock", jsonData, Grocy.Api.Put("stock", jsonData,
function(result) function(result)
{ {
var successMessage = __t('Stock entry successfully updated') + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBooking(\'' + result.id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>'; var successMessage = __t('Stock entry successfully updated') + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(\'' + result.id + '\',\'' + stockRowId + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
window.parent.postMessage(WindowMessageBag("StockDetailChanged", stockRowId), Grocy.BaseUrl); window.parent.postMessage(WindowMessageBag("StockDetailChanged", stockRowId), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl); window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl);
@ -135,17 +135,3 @@ if (Grocy.Components.DateTimePicker)
Grocy.FrontendHelpers.ValidateForm('stockedit-form'); Grocy.FrontendHelpers.ValidateForm('stockedit-form');
}); });
} }
function UndoStockBooking(bookingId)
{
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', { },
function(result)
{
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
console.error(xhr);
}
);
};

View File

@ -161,8 +161,9 @@ $app->group('/api', function()
if (GROCY_FEATURE_FLAG_STOCK) if (GROCY_FEATURE_FLAG_STOCK)
{ {
$this->get('/stock', '\Grocy\Controllers\StockApiController:CurrentStock'); $this->get('/stock', '\Grocy\Controllers\StockApiController:CurrentStock');
$this->get('/stock/{entryId}/entry', '\Grocy\Controllers\StockApiController:StockEntry');
$this->put('/stock', '\Grocy\Controllers\StockApiController:EditStock'); $this->put('/stock', '\Grocy\Controllers\StockApiController:EditStock');
$this->get('/stock/volatile', '\Grocy\Controllers\StockApiController:CurrentVolatilStock'); $this->get('/stock/volatile', '\Grocy\Controllers\StockApiController:CurrentVolatileStock');
$this->get('/stock/products/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails'); $this->get('/stock/products/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails');
$this->get('/stock/products/{productId}/entries', '\Grocy\Controllers\StockApiController:ProductStockEntries'); $this->get('/stock/products/{productId}/entries', '\Grocy\Controllers\StockApiController:ProductStockEntries');
$this->get('/stock/products/{productId}/locations', '\Grocy\Controllers\StockApiController:ProductStockLocations'); $this->get('/stock/products/{productId}/locations', '\Grocy\Controllers\StockApiController:ProductStockLocations');

View File

@ -170,6 +170,11 @@ class StockService extends BaseService
return $returnData; return $returnData;
} }
public function GetStockEntry($entryId)
{
return $this->Database->stock()->where('id', $entryId)->fetch();
}
public function GetProductStockEntries($productId, $excludeOpened = false) public function GetProductStockEntries($productId, $excludeOpened = false)
{ {
// In order of next use: // In order of next use:

View File

@ -127,10 +127,13 @@
<i class="fas fa-edit"></i> {{ $__t('Edit product') }} <i class="fas fa-edit"></i> {{ $__t('Edit product') }}
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item product-consume-button product-consume-button-spoiled @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#" <a class="dropdown-item stock-consume-button stock-consume-button-spoiled @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
data-product-id="{{ $currentStockEntry->product_id }}" data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}" data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}" data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
data-stock-id="{{ $currentStockEntry->stock_id }}"
data-stockrow-id="{{ $currentStockEntry->id }}"
data-location-id="{{ $currentStockEntry->location_id }}"
data-consume-amount="1"> data-consume-amount="1">
<i class="fas fa-utensils"></i> {{ $__t('Consume %1$s of %2$s as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }} <i class="fas fa-utensils"></i> {{ $__t('Consume %1$s of %2$s as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}
</a> </a>