This commit is contained in:
Dillan Mills 2019-09-26 23:26:17 -07:00
commit d674239d3a
31 changed files with 304 additions and 54 deletions

View File

@ -1,12 +1,26 @@
- Fixed that barcode lookups now compare the whole barcode, not parts of it (e. g. when you have two products with the barcodes "$1" and "$10" and scan "$1" maybe the product of "$10" was found till now)
### Stock improvements/fixes
- Fixed that barcode lookups now compare the whole barcode, not parts of it (e. g. when you have two products with the barcodes `$1` and `$10` and scan `$1` maybe the product of `$10` was found till now)
- Fixed that the "X products are already expired" count on the stock overview page was wrong
- It's now possible to accumulate min. stock amounts on parent product level (new option per product, means the sub product will never be "missing" then, only the parent product)
- When adding a product to the shopping list from the new context/more menu from the stock overview page and if the product is already on the shopping list, the amount of that entry will be updated acccordingly instead of adding a new (double) shopping list item
### Recipe improvements/fixes
- Fixed a problem regarding quantity unit conversion handling for recipe ingredients of products with no unit relations, but only a different purchase/stock quantity unit
- It's now possible to display a recipe directly from the meal plan (new "eye button") (thanks @kriddles)
- Improved the responsiveness of the meal plan and calendar page by automatically switching to a day calendar view on smaller screens (thanks for the idea @kriddles)
### Chores improvements
- There is now a new sub feature flag `FEATURE_FLAG_CHORES_ASSIGNMENTS` to disable chore assignments if you don't need them (defaults to `true`, so no changed behavior when not configured)
### Calendar improvements
- The calendar now also contains all planned recipes from the meal plan on the corresponding day
- When adding a product to the shopping list from the new context/more menu from the stock overview page and if the product is already on the shopping list, the amount of that entry will be updated acccordingly instead of adding a new (double) shopping list item
- Improved that dates in the iCal calendar export now include the server timezone
### General & other improvements/fixes
- Fixed that the browser barcode scanner button was not clickable on iOS Safari (thanks @DeeeeLAN)
- Fixed a problem regarding quantity unit conversion handling for recipe ingredients of products with no unit relations, but only a different purchase/stock quantity unit
- Improved that dates in the iCal calendar export now includes the server timezone
- It's now also possible to set the meal plan page as the default/entry page (`config.php` setting `ENTRY_PAGE`) (thanks @lwis)
- Some UI detail-refinements
### API improvements/fixes
- The API Endpoint `GET /files/{group}/{fileName}` now also returns a `Cache-Control` header (defaults fixed to 30 days) to further increase page load times
- Fixed that the API endpoint `/stock/shoppinglist/add-product` failed when a product should be added which was not already on the shopping list (thanks @Forceu)
- Some style/CSS detail-refinements

View File

@ -124,6 +124,7 @@ Setting('FEATURE_FLAG_STOCK_PRICE_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_LOCATION_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING', true);
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
# Feature settings

View File

@ -28,7 +28,7 @@ class StockController extends BaseController
'products' => $this->Database->products()->orderBy('name'),
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
'locations' => $this->Database->locations()->orderBy('name'),
'currentStock' => $this->StockService->GetCurrentStock(),
'currentStock' => $this->StockService->GetCurrentStock(true),
'currentStockLocations' => $this->StockService->GetCurrentStockLocations(),
'missingProducts' => $this->StockService->GetMissingProducts(),
'nextXDays' => $nextXDays,

View File

@ -1519,3 +1519,12 @@ msgstr ""
msgid "Display recipe"
msgstr ""
msgid "Accumulate sub products min. stock amount"
msgstr ""
msgid "If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be \"missing\", only this product"
msgstr ""
msgid "Are you sure to remove this conversion?"
msgstr ""

129
migrations/0092.sql Normal file
View File

@ -0,0 +1,129 @@
ALTER TABLE products
ADD cumulate_min_stock_amount_of_sub_products TINYINT DEFAULT 0;
CREATE VIEW products_view
AS
SELECT *, CASE WHEN (SELECT 1 FROM products WHERE parent_product_id = p.id) NOTNULL THEN 1 ELSE 0 END AS has_sub_products
FROM products p;
DROP VIEW stock_missing_products;
CREATE VIEW stock_missing_products
AS
-- Products WITHOUT sub products where the amount of the sub products SHOULD NOT be cumulated
SELECT
p.id,
MAX(p.name) AS name,
p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products_view p
LEFT JOIN stock_current s
ON p.id = s.product_id
WHERE p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 0
AND p.has_sub_products = 0
AND p.parent_product_id IS NULL
GROUP BY p.id
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount
UNION
-- Parent products WITH sub products where the amount of the sub products SHOULD be cumulated
SELECT
p.id,
MAX(p.name) AS name,
SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount_aggregated), 0) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products_view p
JOIN products_resolved pr
ON p.id = pr.parent_product_id
JOIN products sub_p
ON pr.sub_product_id = sub_p.id
LEFT JOIN stock_current s
ON pr.sub_product_id = s.product_id
WHERE sub_p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 1
GROUP BY p.id
HAVING IFNULL(SUM(s.amount_aggregated), 0) < SUM(sub_p.min_stock_amount)
UNION
-- Sub products where the amount SHOULD NOT be cumulated into the parent product
SELECT
sub_p.id,
MAX(sub_p.name) AS name,
SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount), 0) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products p
JOIN products_resolved pr
ON p.id = pr.parent_product_id
JOIN products sub_p
ON pr.sub_product_id = sub_p.id
LEFT JOIN stock_current s
ON pr.sub_product_id = s.product_id
WHERE sub_p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 0
GROUP BY sub_p.id
HAVING IFNULL(SUM(s.amount), 0) < sub_p.min_stock_amount;
DROP VIEW stock_missing_products_including_opened;
CREATE VIEW stock_missing_products_including_opened
AS
/* This is basically the same view as stock_missing_products, but the column "amount_missing" includes opened amounts */
-- Products WITHOUT sub products where the amount of the sub products SHOULD NOT be cumulated
SELECT
p.id,
MAX(p.name) AS name,
p.min_stock_amount - (IFNULL(SUM(s.amount), 0) - IFNULL(SUM(s.amount_opened), 0)) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products_view p
LEFT JOIN stock_current s
ON p.id = s.product_id
WHERE p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 0
AND p.has_sub_products = 0
AND p.parent_product_id IS NULL
GROUP BY p.id
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount
UNION
-- Parent products WITH sub products where the amount of the sub products SHOULD be cumulated
SELECT
p.id,
MAX(p.name) AS name,
SUM(sub_p.min_stock_amount) - (IFNULL(SUM(s.amount_aggregated), 0) - IFNULL(SUM(s.amount_opened), 0)) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products_view p
JOIN products_resolved pr
ON p.id = pr.parent_product_id
JOIN products sub_p
ON pr.sub_product_id = sub_p.id
LEFT JOIN stock_current s
ON pr.sub_product_id = s.product_id
WHERE sub_p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 1
GROUP BY p.id
HAVING IFNULL(SUM(s.amount_aggregated), 0) < SUM(sub_p.min_stock_amount)
UNION
-- Sub products where the amount SHOULD NOT be cumulated into the parent product
SELECT
sub_p.id,
MAX(sub_p.name) AS name,
SUM(sub_p.min_stock_amount) - (IFNULL(SUM(s.amount), 0) - IFNULL(SUM(s.amount_opened), 0)) AS amount_missing,
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
FROM products p
JOIN products_resolved pr
ON p.id = pr.parent_product_id
JOIN products sub_p
ON pr.sub_product_id = sub_p.id
LEFT JOIN stock_current s
ON pr.sub_product_id = s.product_id
WHERE sub_p.min_stock_amount != 0
AND p.cumulate_min_stock_amount_of_sub_products = 0
GROUP BY sub_p.id
HAVING IFNULL(SUM(s.amount), 0) < sub_p.min_stock_amount;

View File

@ -4,7 +4,8 @@
.night-mode .table-info,
.night-mode .table-info > td,
.night-mode .table-info > th {
.night-mode .table-info > th,
.night-mode .alert-info {
background-color: #07373f;
color: #6c757d;
}
@ -54,14 +55,17 @@
border-color: #0d3a18;
}
.night-mode .btn-light {
.night-mode .btn-light,
.night-mode .input-group-text,
.night-mode .note-editor.note-frame .note-statusbar,
.night-mode .img-thumbnail {
color: #c1c1c1;
background-color: #292b2a;
border-color: #292b2a;
}
.night-mode .form-control {
color: #495057;
color: #ced4da;
background-color: #333131;
border: 1px solid #ced4da;
}
@ -100,7 +104,7 @@
}
.night-mode .form-control:focus {
color: #495057;
color: #ced4da;
background-color: #333131;
border-color: #80bdff;
}

View File

@ -298,8 +298,21 @@ RefreshContextualTimeago = function()
$("time.timeago").each(function()
{
var element = $(this);
if (!element.hasAttr("datetime"))
{
element.text("")
return
}
var timestamp = element.attr("datetime");
if (timestamp.isEmpty())
{
element.text("")
return
}
var isNever = timestamp && timestamp.substring(0, 10) == "2999-12-31";
var isToday = timestamp && timestamp.substring(0, 10) == moment().format("YYYY-MM-DD");
var isDateWithoutTime = element.hasClass("timeago-date-only");
@ -515,18 +528,33 @@ $("#about-dialog-link").on("click", function()
function RefreshLocaleNumberDisplay()
{
$(".locale-number-format[data-format='currency']").each(function()
$(".locale-number.locale-number-currency").each(function()
{
if (isNaN(parseFloat($(this).text())))
{
return;
}
$(this).text(parseFloat($(this).text()).toLocaleString(undefined, { style: "currency", currency: Grocy.Currency }));
});
$(".locale-number-format[data-format='quantity-amount']").each(function()
$(".locale-number.locale-number-quantity-amount").each(function()
{
if (isNaN(parseFloat($(this).text())))
{
return;
}
$(this).text(parseFloat($(this).text()).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 3 }));
});
$(".locale-number-format[data-format='generic']").each(function ()
$(".locale-number.locale-number-generic").each(function ()
{
if (isNaN(parseFloat($(this).text())))
{
return;
}
$(this).text(parseFloat($(this).text()).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 }));
});
}

View File

@ -3,7 +3,11 @@
e.preventDefault();
var jsonData = $('#chore-form').serializeJSON({ checkboxUncheckedValue: "0" });
jsonData.assignment_config = $("#assignment_config").val().join(",");
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
{
jsonData.assignment_config = $("#assignment_config").val().join(",");
}
Grocy.FrontendHelpers.BeginUiBusy("chore-form");
if (Grocy.EditMode === 'create')

View File

@ -8,7 +8,7 @@
Grocy.Api.Get('chores/' + jsonForm.chore_id,
function (choreDetails)
{
Grocy.Api.Post('chores/' + jsonForm.chore_id + '/execute', { 'tracked_time': Grocy.Components.DateTimePicker.GetValue(), 'done_by': Grocy.Components.UserPicker.GetValue() },
Grocy.Api.Post('chores/' + jsonForm.chore_id + '/execute', { 'tracked_time': Grocy.Components.DateTimePicker.GetValue(), 'done_by': $("#user_id").val() },
function(result)
{
Grocy.FrontendHelpers.EndUiBusy("choretracking-form");

View File

@ -131,4 +131,5 @@ $("#selectedEquipmentDescriptionToggleFullscreenButton").on('click', function(e)
$("#selectedEquipmentDescriptionCard").toggleClass("fullscreen");
$("#selectedEquipmentDescriptionCard .card-header").toggleClass("fixed-top");
$("#selectedEquipmentDescriptionCard .card-body").toggleClass("mt-5");
$("body").toggleClass("fullscreen-card");
});

View File

@ -54,7 +54,7 @@ var calendar = $("#calendar").fullCalendar({
weekRecipeOrderMissingButtonHtml = '<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + weekRecipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-cart-plus"></i></a>'
weekRecipeConsumeButtonHtml = '<a class="ml-1 btn btn-outline-success btn-xs recipe-consume-button ' + weekRecipeConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume all ingredients needed by this recipe") + '" data-recipe-id="' + weekRecipe.id.toString() + '" data-recipe-name="' + weekRecipe.name + '" data-recipe-type="' + weekRecipe.type + '"><i class="fas fa-utensils"></i></a>'
}
$(".fc-header-toolbar .fc-center").html("<h4>" + __t("Week costs") + ': <span class="locale-number-format" data-format="currency">' + weekCosts.toString() + "</span> " + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
$(".fc-header-toolbar .fc-center").html("<h4>" + __t("Week costs") + ': <span class="locale-number locale-number-currency">' + weekCosts.toString() + "</span> " + weekRecipeOrderMissingButtonHtml + weekRecipeConsumeButtonHtml + "</h4>");
},
"eventRender": function(event, element)
{
@ -98,7 +98,7 @@ var calendar = $("#calendar").fullCalendar({
<h5 class="text-truncate">' + recipe.name + '<h5> \
<h5 class="small text-truncate">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</h5> \
<h5 class="small timeago-contextual text-truncate">' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '</h5> \
<h5 class="small text-truncate"><span class="locale-number-format" data-format="currency">' + resolvedRecipe.costs + '</span> ' + __t('per serving') + '<h5> \
<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + resolvedRecipe.costs + '</span> ' + __t('per serving') + '<h5> \
<h5> \
<a class="ml-1 btn btn-outline-danger btn-xs remove-recipe-button" href="#"><i class="fas fa-trash"></i></a> \
<a class="ml-1 btn btn-outline-primary btn-xs recipe-order-missing-button ' + recipeOrderMissingButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Put missing products on shopping list") + '" data-recipe-id="' + recipe.id.toString() + '" data-recipe-name="' + recipe.name + '" data-recipe-type="' + recipe.type + '"><i class="fas fa-cart-plus"></i></a> \
@ -116,6 +116,7 @@ var calendar = $("#calendar").fullCalendar({
{
RefreshLocaleNumberDisplay();
LoadImagesLazy();
$('[data-toggle="tooltip"]').tooltip();
if (GetUriParam("week") !== undefined)
{
@ -213,6 +214,10 @@ $(document).on("keyodwn", "#servings", function(e)
$(document).on('click', '.recipe-order-missing-button', function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
var button = $(this);
@ -262,6 +267,10 @@ $(document).on('click', '.recipe-order-missing-button', function(e)
$(document).on('click', '.recipe-consume-button', function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id');
@ -304,6 +313,10 @@ $(document).on('click', '.recipe-consume-button', function(e)
$(document).on("click", ".recipe-popup-button", function(e)
{
// Remove the focus from the current button
// to prevent that the tooltip stays until clicked anywhere else
document.activeElement.blur();
var objectId = $(e.currentTarget).attr('data-recipe-id');
bootbox.dialog({

View File

@ -222,6 +222,7 @@ $('.input-group-qu').on('change', function(e)
$('#product-form input').keyup(function(event)
{
Grocy.FrontendHelpers.ValidateForm('product-form');
$(".input-group-qu").trigger("change");
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
{

View File

@ -319,6 +319,9 @@ function RefreshProductRow(productId)
productRow.removeClass("table-warning");
productRow.removeClass("table-danger");
productRow.removeClass("table-info");
productRow.removeClass("d-none");
productRow.removeAttr("style");
if (now.isAfter(nextBestBeforeDate))
{
productRow.addClass("table-danger");
@ -328,12 +331,12 @@ function RefreshProductRow(productId)
productRow.addClass("table-warning");
}
if (result.stock_amount <= 0)
if (result.stock_amount == 0 && result.product.min_stock_amount == 0)
{
$('#product-' + productId + '-row').fadeOut(500, function()
{
$(this).tooltip("hide");
$(this).remove();
$(this).addClass("d-none");
});
}
else
@ -366,6 +369,11 @@ function RefreshProductRow(productId)
$(this).text("").fadeIn(500);
}
});
if (result.stock_amount == 0 && result.product.min_stock_amount > 0)
{
productRow.addClass("table-info");
}
}
$('#product-' + productId + '-next-best-before-date').parent().effect('highlight', {}, 500);

View File

@ -11,15 +11,15 @@ class StockService extends BaseService
public function GetCurrentStock($includeNotInStockButMissingProducts = false)
{
$missingProductsView = 'stock_missing_products_including_opened';
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
{
$missingProductsView = 'stock_missing_products';
}
$sql = 'SELECT * FROM stock_current UNION SELECT id, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)';
$sql = 'SELECT * FROM stock_current';
if ($includeNotInStockButMissingProducts)
{
$missingProductsView = 'stock_missing_products_including_opened';
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
{
$missingProductsView = 'stock_missing_products';
}
$sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)';
}
@ -69,7 +69,7 @@ class StockService extends BaseService
public function GetExpiringProducts(int $days = 5, bool $excludeExpired = false)
{
$currentStock = $this->GetCurrentStock(true);
$currentStock = $this->GetCurrentStock(false);
$currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("+$days days")), '<');
if ($excludeExpired)

View File

@ -30,8 +30,8 @@
<div class="form-group">
<label for="scanned_codes">{{ $__t('Scanned barcodes') }}</label>
<div class="float-right font-weight-bold">
<span class="text-success">{{ $__t('Hit') }}: <span id="hit-count" class="locale-number-format" data-format="generic">0</span></span> //
<span class="text-danger">{{ $__t('Miss') }}: <span id="miss-count" class="locale-number-format" data-format="generic">0</span></span>
<span class="text-success">{{ $__t('Hit') }}: <span id="hit-count" class="locale-number locale-number-generic">0</span></span> //
<span class="text-danger">{{ $__t('Miss') }}: <span id="miss-count" class="locale-number locale-number-generic">0</span></span>
</div>
<select class="form-control" id="scanned_codes" name="scanned_codes" multiple size="30"></select>
</div>

View File

@ -86,6 +86,7 @@
<input type="hidden" id="period_config" name="period_config" value="@if($mode == 'edit'){{ $chore->period_config }}@endif">
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div class="form-group">
<label for="assignment_type">{{ $__t('Assignment type') }} <span id="chore-assignment-type-info" class="small text-muted"></span></label>
<select required class="form-control input-group-chore-assignment-type" id="assignment_type" name="assignment_type">
@ -105,6 +106,10 @@
</select>
<div class="invalid-feedback">{{ $__t('This assignment type requires that at least one is assigned') }}</div>
</div>
@else
<input type="hidden" id="assignment_type" name="assignment_type" value="{{ \Grocy\Services\ChoresService::CHORE_ASSIGNMENT_TYPE_NO_ASSIGNMENT }}">
<input type="hidden" id="assignment_config" name="assignment_config" value="">
@endif
<div class="form-group">
<div class="form-check">

View File

@ -35,7 +35,9 @@
<th class="border-right"></th>
<th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Tracked time') }}</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th>{{ $__t('Done by') }}</th>
@endif
</tr>
</thead>
<tbody class="d-none">
@ -58,6 +60,7 @@
<span>{{ $choreLogEntry->tracked_time }}</span>
<time class="timeago timeago-contextual @if(FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->track_date_only == 1) timeago-date-only @endif" datetime="{{ $choreLogEntry->tracked_time }}"></time>
</td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td>
@if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $choreLogEntry->done_by_user_id)) }}
@ -65,6 +68,7 @@
{{ $__t('Unknown') }}
@endif
</td>
@endif
</tr>
@endforeach
</tbody>

View File

@ -18,7 +18,9 @@
</h1>
<p id="info-due-chores" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-overdue-chores" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button mr-2"></p>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<p id="info-assigned-to-me-chores" data-user-filter="xx{{ GROCY_USER_ID }}xx" class="btn btn-lg btn-secondary user-filter-button responsive-button"></p>
@endif
</div>
</div>
@ -35,15 +37,17 @@
<option class="bg-danger" value="overdue">{{ $__t('Overdue') }}</option>
</select>
</div>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="user-filter">{{ $__t('Filter by assignment') }}</label> <i class="fas fa-filter"></i>
<select class="form-control input-group-filter" id="user-filter">
<option></option>
@foreach($users as $user)
<option class="@if($user->id == GROCY_USER_ID) bg-secondary text-white @endif" data-user-id="{{ $user->id }}" value="xx{{ $user->id }}xx">{{ $user->display_name }}</option>
<option class="@if($user->id == GROCY_USER_ID) bg-secondary text-white @endif" data-user-id="{{ $user->id }}" value="xx{{ $user->id }}xx">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
@endif
</div>
<div class="row">
@ -55,9 +59,13 @@
<th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Next estimated tracking') }}</th>
<th>{{ $__t('Last tracked') }}</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th>{{ $__t('Assigned to') }}</th>
@endif
<th class="d-none">Hidden status</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th class="d-none">Hidden assigned to user id</th>
@endif
@include('components.userfields_thead', array(
'userfields' => $userfields
@ -106,6 +114,7 @@
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago" class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif" datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
</td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
@ -115,14 +124,17 @@
@endif
</span>
</td>
@endif
<td id="chore-{{ $curentChoreEntry->chore_id }}-due-filter-column" class="d-none">
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
</td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td class="d-none">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
xx{{ $curentChoreEntry->next_execution_assigned_to_user_id }}xx
@endif
</td>
@endif
@include('components.userfields_tbody', array(
'userfields' => $userfields,

View File

@ -32,12 +32,16 @@
'invalidFeedback' => $__t('This can only be before now')
))
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
@include('components.userpicker', array(
'label' => 'Done by',
'users' => $users,
'nextInputSelector' => '#user_id',
'prefillByUserId' => GROCY_USER_ID
))
@else
<input type="hidden" id="user_id" name="user_id" value="{{ GROCY_USER_ID }}">
@endif
<button id="save-choretracking-button" class="btn btn-success">{{ $__t('OK') }}</button>

View File

@ -12,7 +12,7 @@
<div class="card-body">
<h3><span id="batterycard-battery-name"></span></h3>
<strong>{{ $__t('Used in') }}:</strong> <span id="batterycard-battery-used_in"></span><br>
<strong>{{ $__t('Charge cycles count') }}:</strong> <span id="batterycard-battery-charge-cycles-count"></span><br>
<strong>{{ $__t('Charge cycles count') }}:</strong> <span id="batterycard-battery-charge-cycles-count" class="locale-number locale-number-generic"></span><br>
<strong>{{ $__t('Last charged') }}:</strong> <span id="batterycard-battery-last-charged"></span> <time id="batterycard-battery-last-charged-timeago" class="timeago timeago-contextual"></time><br>
</div>
</div>

View File

@ -11,8 +11,10 @@
</div>
<div class="card-body">
<h3><span id="chorecard-chore-name"></span></h3>
<strong>{{ $__t('Tracked count') }}:</strong> <span id="chorecard-chore-tracked-count"></span><br>
<strong>{{ $__t('Tracked count') }}:</strong> <span id="chorecard-chore-tracked-count" class="locale-number locale-number-generic"></span><br>
<strong>{{ $__t('Last tracked') }}:</strong> <span id="chorecard-chore-last-tracked"></span> <time id="chorecard-chore-last-tracked-timeago" class="timeago timeago-contextual"></time><br>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<strong>{{ $__t('Last done by') }}:</strong> <span id="chorecard-chore-last-done-by"></span>
@endif
</div>
</div>

View File

@ -18,8 +18,8 @@
<a class="collapsed" data-toggle="collapse" href="#productcard-product-description">{{ $__t('Show more') }}</a>
</div>
<strong>{{ $__t('Stock amount') . ' / ' . $__t('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>
<span id="productcard-aggregated-amounts" class="pl-2 text-secondary d-none"><i class="fas fa-custom-sigma-sign"></i> <span id="productcard-product-stock-amount-aggregated"></span> <span id="productcard-product-stock-qu-name-aggregated"></span> <span id="productcard-product-stock-opened-amount-aggregated" class="small font-italic"></span></span><br>
<strong>{{ $__t('Stock amount') . ' / ' . $__t('Quantity unit') }}:</strong> <span id="productcard-product-stock-amount" class="locale-number locale-number-quantity-amount"></span> <span id="productcard-product-stock-qu-name"></span> <span id="productcard-product-stock-opened-amount" class="small font-italic locale-number locale-number-quantity-amount"></span>
<span id="productcard-aggregated-amounts" class="pl-2 text-secondary d-none"><i class="fas fa-custom-sigma-sign"></i> <span id="productcard-product-stock-amount-aggregated" class="locale-number locale-number-quantity-amount"></span> <span id="productcard-product-stock-qu-name-aggregated"></span> <span id="productcard-product-stock-opened-amount-aggregated locale-number locale-number-quantity-amount" class="small font-italic"></span></span><br>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<strong>{{ $__t('Location') }}:</strong> <span id="productcard-product-location"></span><br>@endif
<strong>{{ $__t('Last purchased') }}:</strong> <span id="productcard-product-last-purchased"></span> <time id="productcard-product-last-purchased-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $__t('Last used') }}:</strong> <span id="productcard-product-last-used"></span> <time id="productcard-product-last-used-timeago" class="timeago timeago-contextual"></time><br>

View File

@ -61,10 +61,10 @@
<div id="selectedEquipmentInstructionManualCard" class="card">
<div class="card-header">
<i class="fas fa-toolbox"></i> <span class="selected-equipment-name"></span>&nbsp;&nbsp;
<a class="btn btn-sm btn-outline-info py-0 equipment-edit-button" href="#">
<a class="btn btn-sm btn-outline-info py-0 equipment-edit-button hide-on-fullscreen-card" href="#">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-outline-danger py-0 equipment-delete-button" href="#" data-equipment-id="{{ $equipmentItem->id }}" data-equipment-name="{{ $equipmentItem->name }}">
<a class="btn btn-sm btn-outline-danger py-0 equipment-delete-button hide-on-fullscreen-card" href="#" data-equipment-id="{{ $equipmentItem->id }}" data-equipment-name="{{ $equipmentItem->name }}">
<i class="fas fa-trash"></i>
</a>
<a id="selectedEquipmentInstructionManualToggleFullscreenButton" class="btn btn-sm btn-outline-secondary py-0 float-right" href="#" data-toggle="tooltip" title="{{ $__t('Expand to fullscreen') }}">
@ -72,7 +72,7 @@
</a>
</div>
<div class="card-body py-0 px-0">
<p id="selected-equipment-has-no-instruction-manual-hint" class="text-muted font-italic d-none">{{ $__t('The selected equipment has no instruction manual') }}</p>
<p id="selected-equipment-has-no-instruction-manual-hint" class="text-muted font-italic d-none pt-3 pl-3">{{ $__t('The selected equipment has no instruction manual') }}</p>
<embed id="selected-equipment-instruction-manual" class="embed-responsive embed-responsive-4by3" src="" type="application/pdf">
</div>
</div>
@ -81,10 +81,10 @@
<div id="selectedEquipmentDescriptionCard" class="card">
<div class="card-header">
<i class="fas fa-toolbox"></i> <span class="selected-equipment-name"></span>&nbsp;&nbsp;
<a class="btn btn-sm btn-outline-info py-0 equipment-edit-button" href="#">
<a class="btn btn-sm btn-outline-info py-0 equipment-edit-button hide-on-fullscreen-card" href="#">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-outline-danger py-0 equipment-delete-button" href="#" data-equipment-id="{{ $equipmentItem->id }}" data-equipment-name="{{ $equipmentItem->name }}">
<a class="btn btn-sm btn-outline-danger py-0 equipment-delete-button hide-on-fullscreen-card" href="#" data-equipment-id="{{ $equipmentItem->id }}" data-equipment-name="{{ $equipmentItem->name }}">
<i class="fas fa-trash"></i>
</a>
<a id="selectedEquipmentDescriptionToggleFullscreenButton" class="btn btn-sm btn-outline-secondary py-0 float-right" href="#" data-toggle="tooltip" title="{{ $__t('Expand to fullscreen') }}">

View File

@ -69,7 +69,7 @@
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td>
<td>
<span>{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
<span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
<span class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
</td>
<td class=""></td>

View File

@ -94,9 +94,20 @@
'label' => 'Minimum stock amount',
'min' => 0,
'value' => $value,
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0')
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0'),
'additionalGroupCssClasses' => 'mb-1'
))
<div class="form-group">
<div class="form-check">
<input type="hidden" name="cumulate_min_stock_amount_of_sub_products" value="0">
<input @if($mode == 'edit' && $product->cumulate_min_stock_amount_of_sub_products == 1) checked @endif class="form-check-input" type="checkbox" id="cumulate_min_stock_amount_of_sub_products" name="cumulate_min_stock_amount_of_sub_products" value="1">
<label class="form-check-label" for="cumulate_min_stock_amount_of_sub_products">{{ $__t('Accumulate sub products min. stock amount') }}
<span class="text-muted small">{{ $__t('If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be "missing", only this product') }}</span>
</label>
</div>
</div>
@php if($mode == 'edit') { $value = $product->default_best_before_days; } else { $value = 0; } @endphp
@include('components.numberpicker', array(
'id' => 'default_best_before_days',
@ -168,7 +179,7 @@
</div>
</div>
<div class="form-group">
<div class="form-group mb-1">
<div class="form-check">
<input type="hidden" name="enable_tare_weight_handling" value="0">
<input @if($mode == 'edit' && $product->enable_tare_weight_handling == 1) checked @endif class="form-check-input" type="checkbox" id="enable_tare_weight_handling" name="enable_tare_weight_handling" value="1">
@ -267,7 +278,7 @@
</a>
</td>
<td>
{{ $quConversion->factor }}
<span class="locale-number locale-number-quantity-amount">{{ $quConversion->factor }}</span>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name }}

View File

@ -77,7 +77,7 @@
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
</td>
<td>
{{ $product->min_stock_amount }}
<span class="locale-number locale-number-quantity-amount">{{ $product->min_stock_amount }}</span>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }}
@ -86,7 +86,7 @@
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }}
</td>
<td>
{{ $product->qu_factor_purchase_to_stock }}
<span class="locale-number locale-number-quantity-amount">{{ $product->qu_factor_purchase_to_stock }}</span>
</td>
<td>
@if(!empty($product->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', $product->product_group_id)->name }} @endif

View File

@ -142,7 +142,7 @@
@if(!empty($recipePosition->variable_amount))
{{ $recipePosition->variable_amount }}
@else
@if($recipePosition->amount == round($recipePosition->amount)){{ round($recipePosition->amount) }}@else{{ $recipePosition->amount }}@endif
<span class="locale-number locale-number-quantity-amount">@if($recipePosition->amount == round($recipePosition->amount)){{ round($recipePosition->amount) }}@else{{ $recipePosition->amount }}@endif</span>
@endif
{{ $__n($recipePosition->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural) }}
</td>

View File

@ -164,7 +164,7 @@
<div class="col-2">
<label>{{ $__t('Energy (kcal)') }}</label>
<p class="mb-0">
<h3 class="locale-number-format pt-0" data-format="generic">{{ $selectedRecipeTotalCalories }}</h3>
<h3 class="locale-number locale-number-generic pt-0">{{ $selectedRecipeTotalCalories }}</h3>
</p>
</div>
@endif
@ -174,7 +174,7 @@
<span class="small text-muted">{{ $__t('Based on the prices of the last purchase per product') }}</span>
</label>
<p class="mb-0">
<h3 class="locale-number-format pt-0" data-format="currency">{{ $selectedRecipeTotalCosts }}</h3>
<h3 class="locale-number locale-number-currency pt-0">{{ $selectedRecipeTotalCosts }}</h3>
</p>
</div>
@endif
@ -216,7 +216,7 @@
@if(!empty($selectedRecipePosition->recipe_variable_amount))
{{ $selectedRecipePosition->recipe_variable_amount }}
@else
<span class="locale-number-format" data-format="quantity-amount">@if($selectedRecipePosition->recipe_amount == round($selectedRecipePosition->recipe_amount, 2)){{ round($selectedRecipePosition->recipe_amount, 2) }}@else{{ $selectedRecipePosition->recipe_amount }}@endif</span>
<span class="llocale-number locale-number-quantity-amount">@if($selectedRecipePosition->recipe_amount == round($selectedRecipePosition->recipe_amount, 2)){{ round($selectedRecipePosition->recipe_amount, 2) }}@else{{ $selectedRecipePosition->recipe_amount }}@endif</span>
@endif
{{ $__n($selectedRecipePosition->recipe_amount, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name_plural) }} {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name }}
@if($selectedRecipePosition->need_fulfilled == 1)<i class="fas fa-check text-success"></i>@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)<i class="fas fa-exclamation text-warning"></i>@else<i class="fas fa-times text-danger"></i>@endif
@ -267,7 +267,7 @@
@if(!empty($selectedRecipePosition->recipe_variable_amount))
{{ $selectedRecipePosition->recipe_variable_amount }}
@else
<span class="locale-number-format" data-format="quantity-amount">@if($selectedRecipePosition->recipe_amount == round($selectedRecipePosition->recipe_amount, 2)){{ round($selectedRecipePosition->recipe_amount, 2) }}@else{{ $selectedRecipePosition->recipe_amount }}@endif</span>
<span class="locale-number locale-number-quantity-amount">@if($selectedRecipePosition->recipe_amount == round($selectedRecipePosition->recipe_amount, 2)){{ round($selectedRecipePosition->recipe_amount, 2) }}@else{{ $selectedRecipePosition->recipe_amount }}@endif</span>
@endif
{{ $__n($selectedRecipePosition->recipe_amount, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name_plural) }} {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name }}
@if($selectedRecipePosition->need_fulfilled == 1)<i class="fas fa-check text-success"></i>@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)<i class="fas fa-exclamation text-warning"></i>@else<i class="fas fa-times text-danger"></i>@endif

View File

@ -120,7 +120,7 @@
@if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
</td>
<td>
{{ $listItem->amount }} @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name_plural) }}@endif
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name_plural) }}@endif
</td>
<td class="d-none">
@if(!empty(FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->product_group_id)->name }} @else <span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span> @endif

View File

@ -56,7 +56,7 @@
@endif
</td>
<td>
{{ $stockLogEntry->amount }} {{ $__n($stockLogEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name_plural) }}
<span class="locale-number locale-number-quantity-amount">{{ $stockLogEntry->amount }}</span> {{ $__n($stockLogEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockLogEntry->product_id)->qu_id_stock)->name_plural) }}
</td>
<td>
{{ $stockLogEntry->row_created_timestamp }}

View File

@ -174,11 +174,11 @@
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td>
<td>
<span id="product-{{ $currentStockEntry->product_id }}-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
<span id="product-{{ $currentStockEntry->product_id }}-amount" class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}</span>
<span id="product-{{ $currentStockEntry->product_id }}-opened-amount" class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
@if($currentStockEntry->is_aggregated_amount == 1)
<span class="pl-1 text-secondary">
<i class="fas fa-custom-sigma-sign"></i> {{ $currentStockEntry->amount_aggregated }} {{ $__n($currentStockEntry->amount_aggregated, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}
<i class="fas fa-custom-sigma-sign"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount_aggregated }}</span> {{ $__n($currentStockEntry->amount_aggregated, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural) }}
@if($currentStockEntry->amount_opened_aggregated > 0)<span class="small font-italic">{{ $__t('%s opened', $currentStockEntry->amount_opened_aggregated) }}</span>@endif
</span>
@endif