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) - 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) - 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 - 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 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) - 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 - 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) - 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_LOCATION_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING', true); Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING', true); Setting('FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING', true);
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
# Feature settings # Feature settings

View File

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

View File

@ -1519,3 +1519,12 @@ msgstr ""
msgid "Display recipe" msgid "Display recipe"
msgstr "" 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,
.night-mode .table-info > td, .night-mode .table-info > td,
.night-mode .table-info > th { .night-mode .table-info > th,
.night-mode .alert-info {
background-color: #07373f; background-color: #07373f;
color: #6c757d; color: #6c757d;
} }
@ -54,14 +55,17 @@
border-color: #0d3a18; 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; color: #c1c1c1;
background-color: #292b2a; background-color: #292b2a;
border-color: #292b2a; border-color: #292b2a;
} }
.night-mode .form-control { .night-mode .form-control {
color: #495057; color: #ced4da;
background-color: #333131; background-color: #333131;
border: 1px solid #ced4da; border: 1px solid #ced4da;
} }
@ -100,7 +104,7 @@
} }
.night-mode .form-control:focus { .night-mode .form-control:focus {
color: #495057; color: #ced4da;
background-color: #333131; background-color: #333131;
border-color: #80bdff; border-color: #80bdff;
} }

View File

@ -298,8 +298,21 @@ RefreshContextualTimeago = function()
$("time.timeago").each(function() $("time.timeago").each(function()
{ {
var element = $(this); var element = $(this);
if (!element.hasAttr("datetime"))
{
element.text("")
return
}
var timestamp = element.attr("datetime"); var timestamp = element.attr("datetime");
if (timestamp.isEmpty())
{
element.text("")
return
}
var isNever = timestamp && timestamp.substring(0, 10) == "2999-12-31"; var isNever = timestamp && timestamp.substring(0, 10) == "2999-12-31";
var isToday = timestamp && timestamp.substring(0, 10) == moment().format("YYYY-MM-DD"); var isToday = timestamp && timestamp.substring(0, 10) == moment().format("YYYY-MM-DD");
var isDateWithoutTime = element.hasClass("timeago-date-only"); var isDateWithoutTime = element.hasClass("timeago-date-only");
@ -515,18 +528,33 @@ $("#about-dialog-link").on("click", function()
function RefreshLocaleNumberDisplay() 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 })); $(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 })); $(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 })); $(this).text(parseFloat($(this).text()).toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 }));
}); });
} }

View File

@ -3,7 +3,11 @@
e.preventDefault(); e.preventDefault();
var jsonData = $('#chore-form').serializeJSON({ checkboxUncheckedValue: "0" }); 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"); Grocy.FrontendHelpers.BeginUiBusy("chore-form");
if (Grocy.EditMode === 'create') if (Grocy.EditMode === 'create')

View File

@ -8,7 +8,7 @@
Grocy.Api.Get('chores/' + jsonForm.chore_id, Grocy.Api.Get('chores/' + jsonForm.chore_id,
function (choreDetails) 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) function(result)
{ {
Grocy.FrontendHelpers.EndUiBusy("choretracking-form"); Grocy.FrontendHelpers.EndUiBusy("choretracking-form");

View File

@ -131,4 +131,5 @@ $("#selectedEquipmentDescriptionToggleFullscreenButton").on('click', function(e)
$("#selectedEquipmentDescriptionCard").toggleClass("fullscreen"); $("#selectedEquipmentDescriptionCard").toggleClass("fullscreen");
$("#selectedEquipmentDescriptionCard .card-header").toggleClass("fixed-top"); $("#selectedEquipmentDescriptionCard .card-header").toggleClass("fixed-top");
$("#selectedEquipmentDescriptionCard .card-body").toggleClass("mt-5"); $("#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>' 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>' 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) "eventRender": function(event, element)
{ {
@ -98,7 +98,7 @@ var calendar = $("#calendar").fullCalendar({
<h5 class="text-truncate">' + recipe.name + '<h5> \ <h5 class="text-truncate">' + recipe.name + '<h5> \
<h5 class="small text-truncate">' + __n(mealPlanEntry.servings, "%s serving", "%s servings") + '</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 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> \ <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-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> \ <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(); RefreshLocaleNumberDisplay();
LoadImagesLazy(); LoadImagesLazy();
$('[data-toggle="tooltip"]').tooltip();
if (GetUriParam("week") !== undefined) if (GetUriParam("week") !== undefined)
{ {
@ -213,6 +214,10 @@ $(document).on("keyodwn", "#servings", function(e)
$(document).on('click', '.recipe-order-missing-button', 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 objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id'); var objectId = $(e.currentTarget).attr('data-recipe-id');
var button = $(this); var button = $(this);
@ -262,6 +267,10 @@ $(document).on('click', '.recipe-order-missing-button', function(e)
$(document).on('click', '.recipe-consume-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 objectName = $(e.currentTarget).attr('data-recipe-name');
var objectId = $(e.currentTarget).attr('data-recipe-id'); 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) $(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'); var objectId = $(e.currentTarget).attr('data-recipe-id');
bootbox.dialog({ bootbox.dialog({

View File

@ -222,6 +222,7 @@ $('.input-group-qu').on('change', function(e)
$('#product-form input').keyup(function(event) $('#product-form input').keyup(function(event)
{ {
Grocy.FrontendHelpers.ValidateForm('product-form'); Grocy.FrontendHelpers.ValidateForm('product-form');
$(".input-group-qu").trigger("change");
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error 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-warning");
productRow.removeClass("table-danger"); productRow.removeClass("table-danger");
productRow.removeClass("table-info");
productRow.removeClass("d-none");
productRow.removeAttr("style");
if (now.isAfter(nextBestBeforeDate)) if (now.isAfter(nextBestBeforeDate))
{ {
productRow.addClass("table-danger"); productRow.addClass("table-danger");
@ -328,12 +331,12 @@ function RefreshProductRow(productId)
productRow.addClass("table-warning"); 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() $('#product-' + productId + '-row').fadeOut(500, function()
{ {
$(this).tooltip("hide"); $(this).tooltip("hide");
$(this).remove(); $(this).addClass("d-none");
}); });
} }
else else
@ -366,6 +369,11 @@ function RefreshProductRow(productId)
$(this).text("").fadeIn(500); $(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); $('#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) public function GetCurrentStock($includeNotInStockButMissingProducts = false)
{ {
$missingProductsView = 'stock_missing_products_including_opened'; $sql = 'SELECT * FROM stock_current';
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)';
if ($includeNotInStockButMissingProducts) 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)'; $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) 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")), '<'); $currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("+$days days")), '<');
if ($excludeExpired) if ($excludeExpired)

View File

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

View File

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

View File

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

View File

@ -18,7 +18,9 @@
</h1> </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-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> <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> <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>
</div> </div>
@ -35,15 +37,17 @@
<option class="bg-danger" value="overdue">{{ $__t('Overdue') }}</option> <option class="bg-danger" value="overdue">{{ $__t('Overdue') }}</option>
</select> </select>
</div> </div>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div class="col-xs-12 col-md-6 col-xl-3"> <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> <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"> <select class="form-control input-group-filter" id="user-filter">
<option></option> <option></option>
@foreach($users as $user) @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 @endforeach
</select> </select>
</div> </div>
@endif
</div> </div>
<div class="row"> <div class="row">
@ -55,9 +59,13 @@
<th>{{ $__t('Chore') }}</th> <th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Next estimated tracking') }}</th> <th>{{ $__t('Next estimated tracking') }}</th>
<th>{{ $__t('Last tracked') }}</th> <th>{{ $__t('Last tracked') }}</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th>{{ $__t('Assigned to') }}</th> <th>{{ $__t('Assigned to') }}</th>
@endif
<th class="d-none">Hidden status</th> <th class="d-none">Hidden status</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th class="d-none">Hidden assigned to user id</th> <th class="d-none">Hidden assigned to user id</th>
@endif
@include('components.userfields_thead', array( @include('components.userfields_thead', array(
'userfields' => $userfields 'userfields' => $userfields
@ -106,6 +114,7 @@
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span> <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> <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> </td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td> <td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user"> <span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id)) @if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
@ -115,14 +124,17 @@
@endif @endif
</span> </span>
</td> </td>
@endif
<td id="chore-{{ $curentChoreEntry->chore_id }}-due-filter-column" class="d-none"> <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 @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> </td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td class="d-none"> <td class="d-none">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id)) @if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
xx{{ $curentChoreEntry->next_execution_assigned_to_user_id }}xx xx{{ $curentChoreEntry->next_execution_assigned_to_user_id }}xx
@endif @endif
</td> </td>
@endif
@include('components.userfields_tbody', array( @include('components.userfields_tbody', array(
'userfields' => $userfields, 'userfields' => $userfields,

View File

@ -32,12 +32,16 @@
'invalidFeedback' => $__t('This can only be before now') 'invalidFeedback' => $__t('This can only be before now')
)) ))
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
@include('components.userpicker', array( @include('components.userpicker', array(
'label' => 'Done by', 'label' => 'Done by',
'users' => $users, 'users' => $users,
'nextInputSelector' => '#user_id', 'nextInputSelector' => '#user_id',
'prefillByUserId' => GROCY_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> <button id="save-choretracking-button" class="btn btn-success">{{ $__t('OK') }}</button>

View File

@ -12,7 +12,7 @@
<div class="card-body"> <div class="card-body">
<h3><span id="batterycard-battery-name"></span></h3> <h3><span id="batterycard-battery-name"></span></h3>
<strong>{{ $__t('Used in') }}:</strong> <span id="batterycard-battery-used_in"></span><br> <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> <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>
</div> </div>

View File

@ -11,8 +11,10 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<h3><span id="chorecard-chore-name"></span></h3> <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> <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> <strong>{{ $__t('Last done by') }}:</strong> <span id="chorecard-chore-last-done-by"></span>
@endif
</div> </div>
</div> </div>

View File

@ -18,8 +18,8 @@
<a class="collapsed" data-toggle="collapse" href="#productcard-product-description">{{ $__t('Show more') }}</a> <a class="collapsed" data-toggle="collapse" href="#productcard-product-description">{{ $__t('Show more') }}</a>
</div> </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> <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"></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> <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 @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 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> <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 id="selectedEquipmentInstructionManualCard" class="card">
<div class="card-header"> <div class="card-header">
<i class="fas fa-toolbox"></i> <span class="selected-equipment-name"></span>&nbsp;&nbsp; <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> <i class="fas fa-edit"></i>
</a> </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> <i class="fas fa-trash"></i>
</a> </a>
<a id="selectedEquipmentInstructionManualToggleFullscreenButton" class="btn btn-sm btn-outline-secondary py-0 float-right" href="#" data-toggle="tooltip" title="{{ $__t('Expand to fullscreen') }}"> <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> </a>
</div> </div>
<div class="card-body py-0 px-0"> <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"> <embed id="selected-equipment-instruction-manual" class="embed-responsive embed-responsive-4by3" src="" type="application/pdf">
</div> </div>
</div> </div>
@ -81,10 +81,10 @@
<div id="selectedEquipmentDescriptionCard" class="card"> <div id="selectedEquipmentDescriptionCard" class="card">
<div class="card-header"> <div class="card-header">
<i class="fas fa-toolbox"></i> <span class="selected-equipment-name"></span>&nbsp;&nbsp; <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> <i class="fas fa-edit"></i>
</a> </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> <i class="fas fa-trash"></i>
</a> </a>
<a id="selectedEquipmentDescriptionToggleFullscreenButton" class="btn btn-sm btn-outline-secondary py-0 float-right" href="#" data-toggle="tooltip" title="{{ $__t('Expand to fullscreen') }}"> <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 }} {{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td> </td>
<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> <span class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
</td> </td>
<td class=""></td> <td class=""></td>

View File

@ -94,9 +94,20 @@
'label' => 'Minimum stock amount', 'label' => 'Minimum stock amount',
'min' => 0, 'min' => 0,
'value' => $value, '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 @php if($mode == 'edit') { $value = $product->default_best_before_days; } else { $value = 0; } @endphp
@include('components.numberpicker', array( @include('components.numberpicker', array(
'id' => 'default_best_before_days', 'id' => 'default_best_before_days',
@ -168,7 +179,7 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group mb-1">
<div class="form-check"> <div class="form-check">
<input type="hidden" name="enable_tare_weight_handling" value="0"> <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"> <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> </a>
</td> </td>
<td> <td>
{{ $quConversion->factor }} <span class="locale-number locale-number-quantity-amount">{{ $quConversion->factor }}</span>
</td> </td>
<td> <td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name }}

View File

@ -77,7 +77,7 @@
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }} {{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
</td> </td>
<td> <td>
{{ $product->min_stock_amount }} <span class="locale-number locale-number-quantity-amount">{{ $product->min_stock_amount }}</span>
</td> </td>
<td> <td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }}
@ -86,7 +86,7 @@
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }}
</td> </td>
<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>
<td> <td>
@if(!empty($product->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', $product->product_group_id)->name }} @endif @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)) @if(!empty($recipePosition->variable_amount))
{{ $recipePosition->variable_amount }} {{ $recipePosition->variable_amount }}
@else @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 @endif
{{ $__n($recipePosition->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural) }} {{ $__n($recipePosition->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural) }}
</td> </td>

View File

@ -164,7 +164,7 @@
<div class="col-2"> <div class="col-2">
<label>{{ $__t('Energy (kcal)') }}</label> <label>{{ $__t('Energy (kcal)') }}</label>
<p class="mb-0"> <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> </p>
</div> </div>
@endif @endif
@ -174,7 +174,7 @@
<span class="small text-muted">{{ $__t('Based on the prices of the last purchase per product') }}</span> <span class="small text-muted">{{ $__t('Based on the prices of the last purchase per product') }}</span>
</label> </label>
<p class="mb-0"> <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> </p>
</div> </div>
@endif @endif
@ -216,7 +216,7 @@
@if(!empty($selectedRecipePosition->recipe_variable_amount)) @if(!empty($selectedRecipePosition->recipe_variable_amount))
{{ $selectedRecipePosition->recipe_variable_amount }} {{ $selectedRecipePosition->recipe_variable_amount }}
@else @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 @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 }} {{ $__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 @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)) @if(!empty($selectedRecipePosition->recipe_variable_amount))
{{ $selectedRecipePosition->recipe_variable_amount }} {{ $selectedRecipePosition->recipe_variable_amount }}
@else @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 @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 }} {{ $__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 @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> @if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
</td> </td>
<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>
<td class="d-none"> <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 @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 @endif
</td> </td>
<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>
<td> <td>
{{ $stockLogEntry->row_created_timestamp }} {{ $stockLogEntry->row_created_timestamp }}

View File

@ -174,11 +174,11 @@
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }} {{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td> </td>
<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> <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) @if($currentStockEntry->is_aggregated_amount == 1)
<span class="pl-1 text-secondary"> <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 @if($currentStockEntry->amount_opened_aggregated > 0)<span class="small font-italic">{{ $__t('%s opened', $currentStockEntry->amount_opened_aggregated) }}</span>@endif
</span> </span>
@endif @endif