mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Show more info in product card (closes #173)
This commit is contained in:
parent
162adeb359
commit
d72fe69a17
@ -2206,6 +2206,14 @@
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/Location"
|
||||
},
|
||||
"average_shelf_life_days": {
|
||||
"type": "number",
|
||||
"format": "integer"
|
||||
},
|
||||
"spoil_rate_percent": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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
26
migrations/0064.sql
Normal 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;
|
@ -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 */
|
||||
|
@ -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"));
|
||||
})
|
||||
|
@ -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')));
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user