Add something for product price tracking (references #22)

This commit is contained in:
Bernd Bestel 2018-07-26 20:27:38 +02:00
parent f4eb5196f7
commit c64eb27ca1
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
15 changed files with 335 additions and 32 deletions

View File

@ -7,6 +7,11 @@ Setting('MODE', 'production');
# one of the other available localization files in the "/localization" directory
Setting('CULTURE', 'en');
# To keep it simpel, grocy does not handle any currency conversions,
# this here is used to format all money values,
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
Setting('CURRENCY', '$');
# The base url of your installation,
# should be just "/" when running directly under the root of a (sub)domain
# or for example "https:/example.com/grocy" when using a subdirectory

View File

@ -26,6 +26,18 @@ class StockApiController extends BaseApiController
}
}
public function ProductPriceHistory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
return $this->ApiResponse($this->StockService->GetProductPriceHistory($args['productId']));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
$bestBeforeDate = date('Y-m-d');
@ -34,6 +46,12 @@ class StockApiController extends BaseApiController
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
$price = null;
if (isset($request->getQueryParams()['price']) && !empty($request->getQueryParams()['price']) && is_numeric($request->getQueryParams()['price']))
{
$price = $request->getQueryParams()['price'];
}
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
@ -42,7 +60,7 @@ class StockApiController extends BaseApiController
try
{
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType);
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
return $this->VoidApiActionResponse($response);
}
catch (\Exception $ex)

View File

@ -571,6 +571,16 @@
"type": "date"
}
},
{
"in": "query",
"name": "price",
"required": false,
"description": "The price per purchase quantity unit in configured currency",
"schema": {
"type": "number",
"format": "double"
}
},
{
"in": "query",
"name": "transactiontype",
@ -774,6 +784,50 @@
}
}
},
"/stock/get-product-price-history/{productId}": {
"get": {
"description": "Returns the price history of the given product",
"tags": [
"Stock"
],
"parameters": [
{
"in": "path",
"name": "productId",
"required": true,
"description": "A valid product id",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "An array of ProductPriceHistory objects",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProductPriceHistory"
}
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object (possible errors are: Not existing product)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
}
}
}
}
}
}
},
"/stock/get-current-stock": {
"get": {
"description": "Returns all products which are currently in stock incl. the next expiring date per product",
@ -1272,6 +1326,19 @@
}
}
},
"ProductPriceHistory": {
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date-time"
},
"price": {
"type": "number",
"format": "double"
}
}
},
"ExternalBarcodeLookupResponse": {
"type": "object",
"properties": {

View File

@ -188,6 +188,12 @@ return array(
'Habits analysis' => 'Gewohnheiten Analyse',
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
'Last price' => 'Letzter Preis',
'Price history' => 'Preisentwicklung',
'No price history available' => 'Keine Preisdaten verfügbar',
'Price' => 'Preis',
'in #1 per purchase quantity unit' => 'in #1 pro Einkaufsmengeneinheit',
'The price cannot be lower than #1' => 'Der Preis darf nicht niedriger als #1 sein',
//Constants
'manually' => 'Manuell',

5
migrations/0029.sql Normal file
View File

@ -0,0 +1,5 @@
ALTER TABLE stock
ADD price DECIMAL(15, 2);
ALTER TABLE stock_log
ADD price DECIMAL(15, 2);

View File

@ -25,6 +25,7 @@
"tagmanager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
"tempusdominus-bootstrap-4": "^5.0.1",
"timeago": "^1.6.3",
"toastr": "^2.1.4"
"toastr": "^2.1.4",
"chart.js": "^2.7.2"
}
}

View File

@ -14,6 +14,15 @@ Grocy.Components.ProductCard.Refresh = function(productId)
$('#productcard-product-last-used').text((productDetails.last_used || L('never')).substring(0, 10));
$('#productcard-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
if (productDetails.last_price !== null)
{
$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency);
}
else
{
$('#productcard-product-last-price').text(L('Unknown'));
}
EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', L('timeago_nan'));
EmptyElementWhenMatches('#productcard-product-last-used-timeago', L('timeago_nan'));
},
@ -22,4 +31,90 @@ Grocy.Components.ProductCard.Refresh = function(productId)
console.error(xhr);
}
);
Grocy.Api.Get('stock/get-product-price-history/' + productId,
function(priceHistoryDataPoints)
{
if (priceHistoryDataPoints.length > 0)
{
$("#productcard-product-price-history-chart").removeClass("d-none");
$("#productcard-no-price-data-hint").addClass("d-none");
Grocy.Components.ProductCard.ReInitPriceHistoryChart();
priceHistoryDataPoints.forEach((dataPoint) =>
{
Grocy.Components.ProductCard.PriceHistoryChart.data.labels.push(moment(dataPoint.date).toDate());
var dataset = Grocy.Components.ProductCard.PriceHistoryChart.data.datasets[0];
dataset.data.push(dataPoint.price);
});
Grocy.Components.ProductCard.PriceHistoryChart.update();
}
else
{
$("#productcard-product-price-history-chart").addClass("d-none");
$("#productcard-no-price-data-hint").removeClass("d-none");
}
},
function(xhr)
{
console.error(xhr);
}
);
};
Grocy.Components.ProductCard.ReInitPriceHistoryChart = function()
{
if (typeof Grocy.Components.ProductCard.PriceHistoryChart !== "undefined")
{
Grocy.Components.ProductCard.PriceHistoryChart.destroy();
}
var format = 'YYYY-MM-DD';
Grocy.Components.ProductCard.PriceHistoryChart = new Chart(document.getElementById("productcard-product-price-history-chart"), {
type: "line",
data: {
labels: [ //Date objects
new Date()
// Will be populated in Grocy.Components.ProductCard.Refresh
],
datasets: [{
data: [
0
// Will be populated in Grocy.Components.ProductCard.Refresh
],
fill: false,
borderColor: '#17a2b8'
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
parser: format,
round: 'day',
tooltipFormat: format,
unit: 'day',
unitStepSize: 10,
displayFormats: {
'day': format
}
},
ticks: {
autoSkip: true,
maxRotation: 0
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: {
display: false
}
}
});
}

View File

@ -9,7 +9,13 @@
{
var amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock;
Grocy.Api.Get('stock/add-product/' + jsonForm.product_id + '/' + amount + '?bestbeforedate=' + Grocy.Components.DateTimePicker.GetValue(),
var price = "";
if (!jsonForm.price.toString().isEmpty())
{
price = parseFloat(jsonForm.price).toFixed(2);
}
Grocy.Api.Get('stock/add-product/' + jsonForm.product_id + '/' + amount + '?bestbeforedate=' + Grocy.Components.DateTimePicker.GetValue() + '&price=' + price,
function(result)
{
var addBarcode = GetUriParam('addbarcodetoselection');
@ -43,6 +49,7 @@
else
{
$('#amount').val(0);
$('#price').val('');
Grocy.Components.DateTimePicker.SetValue('');
Grocy.Components.ProductPicker.SetValue('');
Grocy.Components.ProductPicker.GetInputElement().focus();
@ -74,6 +81,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
function(productDetails)
{
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
$('#price').val(productDetails.last_price);
if (productDetails.product.default_best_before_days.toString() !== '0')
{

View File

@ -82,6 +82,7 @@ $app->group('/api', function()
$this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');
$this->get('/stock/inventory-product/{productId}/{newAmount}', '\Grocy\Controllers\StockApiController:InventoryProduct');
$this->get('/stock/get-product-details/{productId}', '\Grocy\Controllers\StockApiController:ProductDetails');
$this->get('/stock/get-product-price-history/{productId}', '\Grocy\Controllers\StockApiController:ProductPriceHistory');
$this->get('/stock/get-current-stock', '\Grocy\Controllers\StockApiController:CurrentStock');
$this->get('/stock/add-missing-products-to-shoppinglist', '\Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
$this->get('/stock/clear-shopping-list', '\Grocy\Controllers\StockApiController:ClearShoppingList');

View File

@ -89,19 +89,71 @@ class DemoDataGeneratorService extends BaseService
$this->DatabaseService->ExecuteDbStatement($sql);
$stockService = new StockService();
$stockService->AddProduct(3, 5, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(4, 5, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(5, 5, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(6, 5, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(7, 5, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(8, 5, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(9, 5, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(10, 5, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(11, 5, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(12, 5, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(13, 5, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(14, 5, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(15, 5, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE);
$stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(3, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(4, 1, date('Y-m-d', strtotime('+180 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(5, 1, date('Y-m-d', strtotime('+20 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(6, 1, date('Y-m-d', strtotime('+600 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(7, 1, date('Y-m-d', strtotime('+800 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(8, 1, date('Y-m-d', strtotime('+900 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(9, 1, date('Y-m-d', strtotime('+14 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(10, 1, date('Y-m-d', strtotime('+21 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(11, 1, date('Y-m-d', strtotime('+10 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(12, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(13, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(14, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice());
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice());
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice());
$stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice());
$stockService->AddMissingProductsToShoppingList();
$habitsService = new HabitsService();
@ -126,9 +178,8 @@ class DemoDataGeneratorService extends BaseService
}
}
public function RecreateDemo()
private function RandomPrice()
{
unlink(GROCY_DATAPATH . '/grocy.db');
$this->PopulateDemoData();
return mt_rand(2 * 100, 25 * 100) / 100;
}
}

View File

@ -33,6 +33,7 @@ class StockService extends BaseService
$productLastUsed = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date');
$quPurchase = $this->Database->quantity_units($product->qu_id_purchase);
$quStock = $this->Database->quantity_units($product->qu_id_stock);
$lastPrice = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2', $productId, self::TRANSACTION_TYPE_PURCHASE)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch()->price;
return array(
'product' => $product,
@ -40,11 +41,31 @@ class StockService extends BaseService
'last_used' => $productLastUsed,
'stock_amount' => $productStockAmount,
'quantity_unit_purchase' => $quPurchase,
'quantity_unit_stock' => $quStock
'quantity_unit_stock' => $quStock,
'last_price' => $lastPrice
);
}
public function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType)
public function GetProductPriceHistory(int $productId)
{
if (!$this->ProductExists($productId))
{
throw new \Exception('Product does not exist');
}
$returnData = array();
$rows = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2', $productId, self::TRANSACTION_TYPE_PURCHASE)->whereNOT('price', null)->orderBy('purchased_date', 'DESC');
foreach ($rows as $row)
{
$returnData[] = array(
'date' => $row->purchased_date,
'price' => $row->price
);
}
return $returnData;
}
public function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType, $purchasedDate, $price)
{
if (!$this->ProductExists($productId))
{
@ -59,9 +80,10 @@ class StockService extends BaseService
'product_id' => $productId,
'amount' => $amount,
'best_before_date' => $bestBeforeDate,
'purchased_date' => date('Y-m-d'),
'purchased_date' => $purchasedDate,
'stock_id' => $stockId,
'transaction_type' => $transactionType
'transaction_type' => $transactionType,
'price' => $price
));
$logRow->save();
@ -69,8 +91,9 @@ class StockService extends BaseService
'product_id' => $productId,
'amount' => $amount,
'best_before_date' => $bestBeforeDate,
'purchased_date' => date('Y-m-d'),
'purchased_date' => $purchasedDate,
'stock_id' => $stockId,
'price' => $price
));
$stockRow->save();
@ -116,7 +139,8 @@ class StockService extends BaseService
'used_date' => date('Y-m-d'),
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id,
'transaction_type' => $transactionType
'transaction_type' => $transactionType,
'price' => $stockEntry->price
));
$logRow->save();
@ -133,7 +157,8 @@ class StockService extends BaseService
'used_date' => date('Y-m-d'),
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id,
'transaction_type' => $transactionType
'transaction_type' => $transactionType,
'price' => $stockEntry->price
));
$logRow->save();
@ -165,8 +190,9 @@ class StockService extends BaseService
if ($newAmount > $productStockAmount)
{
$productDetails = $this->GetProductDetails($productId);
$amountToAdd = $newAmount - $productStockAmount;
$this->AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
$this->AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION, date('Y-m-d'), $productDetails['last_price']);
}
else if ($newAmount < $productStockAmount)
{

View File

@ -1,4 +1,5 @@
@push('componentScripts')
<script src="{{ $U('/node_modules/chart.js//dist/Chart.min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/viewjs/components/productcard.js', true) }}?v={{ $version }}"></script>
@endpush
@ -11,6 +12,11 @@
<strong>{{ $L('Stock quantity unit') }}:</strong> <span id="productcard-product-stock-qu-name"></span><br>
<strong>{{ $L('Stock amount') }}:</strong> <span id="productcard-product-stock-amount"></span> <span id="productcard-product-stock-qu-name2"></span><br>
<strong>{{ $L('Last purchased') }}:</strong> <span id="productcard-product-last-purchased"></span> <time id="productcard-product-last-purchased-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $L('Last used') }}:</strong> <span id="productcard-product-last-used"></span> <time id="productcard-product-last-used-timeago" class="timeago timeago-contextual"></time>
<strong>{{ $L('Last used') }}:</strong> <span id="productcard-product-last-used"></span> <time id="productcard-product-last-used-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $L('Last price') }}:</strong> <span id="productcard-product-last-price"></span>
<h5 class="mt-3">{{ $L('Price history') }}</h5>
<canvas id="productcard-product-price-history-chart" class="w-100 d-none"></canvas>
<span id="productcard-no-price-data-hint" class="font-italic d-none">{{ $L('No price history available') }}</span>
</div>
</div>

View File

@ -39,6 +39,7 @@
Grocy.LocalizationStrings = {!! json_encode($localizationStrings) !!};
Grocy.ActiveNav = '@yield('activeNav', '')';
Grocy.Culture = '{{ GROCY_CULTURE }}';
Grocy.Currency = '{{ GROCY_CURRENCY }}';
</script>
</head>

View File

@ -30,10 +30,16 @@
<div class="form-group">
<label for="amount">{{ $L('Amount') }}&nbsp;&nbsp;<span id="amount_qu_unit" class="small text-muted"></span></label>
<input type="number" class="form-control" id="amount" name="amount" value="1" min="1" required>
<input type="number" class="form-control" id="amount" name="amount" value="1" min="1" step="1.00" required>
<div class="invalid-feedback">{{ $L('The amount cannot be lower than #1', '1') }}</div>
</div>
<div class="form-group">
<label for="price">{{ $L('Price') }}&nbsp;&nbsp;<span id="amount_qu_unit" class="small text-muted">{{ $L('in #1 per purchase quantity unit', GROCY_CURRENCY) }}</span></label>
<input type="number" class="form-control" id="price" name="price" min="0" step="0.01">
<div class="invalid-feedback">{{ $L('The price cannot be lower than #1', '0') }}</div>
</div>
<button id="save-purchase-button" type="submit" class="btn btn-success">{{ $L('OK') }}</button>
</form>

View File

@ -10,7 +10,7 @@
version "5.2.0"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.2.0.tgz#50cd9856774351c56c0b1b0db4efe122d7913e58"
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2":
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2", "tagmanager@https://github.com/max-favilli/tagmanager.git#3.0.2":
version "3.0.1"
resolved "https://github.com/max-favilli/tagmanager.git#df9eb9935c8585a392dfc00602f890caf233fa94"
dependencies:
@ -39,13 +39,20 @@ chart.js@2.7.1:
chartjs-color "~2.2.0"
moment "~2.18.0"
chart.js@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.7.2.tgz#3c9fde4dc5b95608211bdefeda7e5d33dffa5714"
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz#8d3752d8581d86687c35bfe2cb80ac5213ceb8c1"
dependencies:
color-name "^1.0.0"
chartjs-color@~2.2.0:
chartjs-color@^2.1.0, chartjs-color@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.2.0.tgz#84a2fb755787ed85c39dd6dd8c7b1d88429baeae"
dependencies:
@ -161,7 +168,7 @@ moment-timezone@^0.5.11:
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.22.2:
"moment@>= 2.9.0", moment@^2.10.2, moment@^2.22.2:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"