Show more info in product card (closes #173)

This commit is contained in:
Bernd Bestel 2019-04-22 10:11:58 +02:00
parent 162adeb359
commit d72fe69a17
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
8 changed files with 119 additions and 7 deletions

View File

@ -2206,6 +2206,14 @@
},
"location": {
"$ref": "#/components/schemas/Location"
},
"average_shelf_life_days": {
"type": "number",
"format": "integer"
},
"spoil_rate_percent": {
"type": "number",
"format": "double"
}
}
},

View File

@ -368,4 +368,8 @@ return array(
'Tasks settings' => 'Tasks settings',
'Create shopping list' => 'Create shopping list',
'Are you sure to delete shopping list "#1"?' => 'Are you sure to delete shopping list "#1"?',
'Average shelf life' => 'Average shelf life',
'Spoil rate' => 'Spoil rate',
'Show more' => 'Show more',
'Show less' => 'Show less'
);

26
migrations/0064.sql Normal file
View File

@ -0,0 +1,26 @@
CREATE VIEW stock_average_product_shelf_life
AS
SELECT
p.id,
CASE WHEN x.product_id IS NULL THEN -1 ELSE AVG(x.shelf_life_days) END AS average_shelf_life_days
FROM products p
LEFT JOIN (
SELECT
sl_p.product_id,
JULIANDAY(MIN(sl_p.best_before_date)) - JULIANDAY(MIN(sl_c.used_date)) AS shelf_life_days
FROM stock_log sl_p
JOIN (
SELECT
product_id,
stock_id,
MAX(used_date) AS used_date
FROM stock_log
WHERE transaction_type = 'consume'
GROUP BY product_id, stock_id
) sl_c
ON sl_p.stock_id = sl_c.stock_id
WHERE sl_p.transaction_type = 'purchase'
GROUP BY sl_p.product_id, sl_p.stock_id
) x
ON p.id = x.product_id
GROUP BY p.id;

View File

@ -168,6 +168,30 @@ input::-webkit-inner-spin-button {
cursor: wait;
}
.expandable-text .collapse, .module .collapsing {
height: 2.4rem;
}
.expandable-text .collapse {
position: relative;
display: block;
overflow: hidden;
}
.expandable-text .collapse:before {
position: absolute;
right: 0;
bottom: 0;
}
.expandable-text .collapse.show {
height: auto;
}
.expandable-text .collapse.show:before {
display: none;
}
/* Third party component customizations - Bootstrap */
/* Hide the form validation feedback icons introduced in Bootstrap 4.2.0 - a colored border is enough */

View File

@ -8,13 +8,33 @@ Grocy.Components.ProductCard.Refresh = function(productId)
var stockAmount = productDetails.stock_amount || '0';
var stockAmountOpened = productDetails.stock_amount_opened || '0';
$('#productcard-product-name').text(productDetails.product.name);
$('#productcard-product-description').text(productDetails.product.description);
$('#productcard-product-stock-amount').text(stockAmount);
$('#productcard-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
$('#productcard-product-stock-qu-name2').text(Pluralize(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
$('#productcard-product-stock-qu-name').text(Pluralize(stockAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural));
$('#productcard-product-last-purchased').text((productDetails.last_purchased || L('never')).substring(0, 10));
$('#productcard-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
$('#productcard-product-last-used').text((productDetails.last_used || L('never')).substring(0, 10));
$('#productcard-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
$('#productcard-product-location').text(productDetails.location.name);
$('#productcard-product-spoil-rate').text(parseFloat(productDetails.spoil_rate_percent).toLocaleString(undefined, { style: "percent" }));
if (productDetails.product.description != null && !productDetails.product.description.isEmpty())
{
$("#productcard-product-description-wrapper").removeClass("d-none");
}
else
{
$("#productcard-product-description-wrapper").addClass("d-none");
}
if (productDetails.average_shelf_life_days == -1)
{
$('#productcard-product-average-shelf-life').text(L("Unknown"));
}
else
{
$('#productcard-product-average-shelf-life').text(moment.duration(productDetails.average_shelf_life_days, "days").humanize());
}
if (stockAmountOpened > 0)
{
@ -142,3 +162,13 @@ Grocy.Components.ProductCard.ReInitPriceHistoryChart = function()
}
});
}
$("#productcard-product-description").on("shown.bs.collapse", function()
{
$(".expandable-text").find("a[data-toggle='collapse']").text(L("Show less"));
})
$("#productcard-product-description").on("hidden.bs.collapse", function()
{
$(".expandable-text").find("a[data-toggle='collapse']").text(L("Show more"));
})

View File

@ -53,7 +53,7 @@ class DemoDataGeneratorService extends BaseService
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Gulash soup')}', 5, 5, 5, 1, 3); --8
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Yogurt')}', 2, 6, 6, 1, 6); --9
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Cheese')}', 2, 3, 3, 1, 6); --10
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Cold cuts')}', 2, 3, 3, 1, 6); --11
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, description) VALUES ('{$localizationService->LocalizeForSqlString('Cold cuts')}', 2, 3, 3, 1, 6, '{$loremIpsum}'); --11
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, picture_file_name, default_best_before_days) VALUES ('{$localizationService->LocalizeForSqlString('Paprika')}', 2, 2, 2, 1, 5, 'paprika.jpg', 7); --12
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, picture_file_name, default_best_before_days) VALUES ('{$localizationService->LocalizeForSqlString('Cucumber')}', 2, 2, 2, 1, 5, 'cucumber.jpg', 7); --13
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, default_best_before_days) VALUES ('{$localizationService->LocalizeForSqlString('Radish')}', 2, 7, 7, 1, 5, 7); --14
@ -201,6 +201,7 @@ class DemoDataGeneratorService extends BaseService
$stockService->OpenProduct(3, 1);
$stockService->OpenProduct(6, 1);
$stockService->OpenProduct(22, 1);
$stockService->ConsumeProduct(11, 1, true, StockService::TRANSACTION_TYPE_CONSUME);
$choresService = new ChoresService();
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-5 days')));

View File

@ -79,6 +79,7 @@ class StockService extends BaseService
$quPurchase = $this->Database->quantity_units($product->qu_id_purchase);
$quStock = $this->Database->quantity_units($product->qu_id_stock);
$location = $this->Database->locations($product->location_id);
$averageShelfLifeDays = intval($this->Database->stock_average_product_shelf_life()->where('id', $productId)->fetch()->average_shelf_life_days);
$lastPrice = null;
$lastLogRow = $this->Database->stock_log()->where('product_id = :1 AND transaction_type = :2 AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch();
@ -87,6 +88,14 @@ class StockService extends BaseService
$lastPrice = $lastLogRow->price;
}
$consumeCount = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone', 0)->sum('amount') * -1;
$consumeCountSpoiled = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone = 0 AND spoiled = 1')->sum('amount') * -1;
if ($consumeCount == 0)
{
$consumeCount = 1;
}
$spoilRate = ($consumeCountSpoiled * 100) / $consumeCount;
return array(
'product' => $product,
'last_purchased' => $productLastPurchased,
@ -97,7 +106,9 @@ class StockService extends BaseService
'quantity_unit_stock' => $quStock,
'last_price' => $lastPrice,
'next_best_before_date' => $nextBestBeforeDate,
'location' => $location
'location' => $location,
'average_shelf_life_days' => $averageShelfLifeDays,
'spoil_rate_percent' => $spoilRate
);
}

View File

@ -12,11 +12,19 @@
</div>
<div class="card-body">
<h3><span id="productcard-product-name"></span></h3>
<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> <span id="productcard-product-stock-opened-amount" class="small font-italic"></span><br>
<div id="productcard-product-description-wrapper" class="expandable-text mb-2 d-none">
<p id="productcard-product-description" class="text-muted collapse mb-0"></p>
<a class="collapsed" data-toggle="collapse" href="#productcard-product-description">{{ $L('Show more') }}</a>
</div>
<strong>{{ $L('Stock amount') . ' / ' . $L('Quantity unit') }}:</strong> <span id="productcard-product-stock-amount"></span> <span id="productcard-product-stock-qu-name"></span> <span id="productcard-product-stock-opened-amount" class="small font-italic"></span><br>
<strong>{{ $L('Location') }}:</strong> <span id="productcard-product-location"></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><br>
<strong>{{ $L('Last price') }}:</strong> <span id="productcard-product-last-price"></span>
<strong>{{ $L('Last price') }}:</strong> <span id="productcard-product-last-price"></span><br>
<strong>{{ $L('Average shelf life') }}:</strong> <span id="productcard-product-average-shelf-life"></span><br>
<strong>{{ $L('Spoil rate') }}:</strong> <span id="productcard-product-spoil-rate"></span>
<h5 class="mt-3">{{ $L('Product picture') }}</h5>
<p class="w-75 mx-auto"><img id="productcard-product-picture" src="" class="img-fluid img-thumbnail d-none"></p>