mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 17:45:39 +00:00
Finished qu unit conversion handling (closes #177)
This commit is contained in:
parent
c532a67884
commit
a799f2b43f
@ -1,12 +1,16 @@
|
||||
- Stock improvements
|
||||
- Products can now have variations
|
||||
- Products can now have variations (nested products)
|
||||
- Define the parent product for a product on the product edit page (only one level is possible, means a product which is used as a parent product in another product, cannot have a parent product itself)
|
||||
- Parent and sub products can have stock (both are regular products, no difference from that side)
|
||||
- On the stock overview page the aggregated amount is displayed next to the amount (sigma sign)
|
||||
- When a recipe needs a parent product, the need is also fulfilled when enough sub product(s) are in stock
|
||||
- API change (no breaking change): `/stock/products/{productId}` returns additional fields for the aggregated amount(s): `stock_amount_aggregated` and `stock_amount_opened_aggregated` - contains the same for "normal" products, `is_aggregated_amount` indicates if aggregation has happened
|
||||
- Quantity units can now be linked (related measurements / unit conversion)
|
||||
- On the quantity unit edit page default conversion can be defined for each unit
|
||||
- Products "inherit" the default conversion and additionally can have their own / override the default ones
|
||||
- It's now possible to print a "Location Content Sheet" with the current stock per location - new button at the top of the stock overview page
|
||||
- The product description now can have formattings (HTML/WYSIWYG editor like for recipes)
|
||||
- Recipe improvements
|
||||
- Based on the new linked quantity units, recipe ingredients can now use any product related unit, the amount is calculated according to the cnoversion factor of the unit relation
|
||||
- Chores improvements
|
||||
- New option "Due date rollover" per chore which means the chore can never be overdue, the due date will shift forward each day when due
|
||||
- Equipment improvements/fixes
|
||||
@ -15,10 +19,11 @@
|
||||
- Improved the handling which entry page to use with disabled feature flags (thanks @nielstholenaar)
|
||||
- Fixed that the Userfield type "Preset list" had always the caption "Product group" instead of the configured one (thanks @oncleben31)
|
||||
- Userfields of type "checkbox" are rendered as a checkmark in tables when checked (instead of "1" as till now)
|
||||
- API improvements
|
||||
- New endpoint `/stock/shoppinglist/add-product` to add a product to a shopping list (thanks @Forceu)
|
||||
- API improvements & non-breaking changes
|
||||
- New endpoint `/stock/shoppinglist/add-product` to add a product to a shopping list (thanks @Forceu)
|
||||
- New endpoint `/stock/shoppinglist/remove-product` to remove a product from a shopping list (thanks @Forceu)
|
||||
- When adding a product (through `stock/product/{productId}/add` or `stock/product/{productId}/inventory`) with omitted best before date and if the given product has "Default best before days" set, the best before date is calculated based on that (so far always today was used which is still the case when no date is supplied and also the product has no "Default best before days set) (thanks @Forceu)
|
||||
- Field `stock_amount` of endpoint `/stock/products/{productId}´ now returns `0` instead of `null` when the given product is not in stock (thanks @Forceu)
|
||||
- New endpoint `/objects/{entity}/search/{searchString}` search for objects by name (contains search)
|
||||
- It's now also possible to provide the API key via a query parameter (same name as the header, so `GROCY-API-KEY`)
|
||||
- `/stock/products/{productId}` returns additional fields for the aggregated amount(s): `stock_amount_aggregated` and `stock_amount_opened_aggregated` - contains the same for "normal" products, `is_aggregated_amount` indicates if aggregation has happened
|
||||
|
@ -69,7 +69,8 @@ class RecipesController extends BaseController
|
||||
'includedRecipeIdsAbsolute' => $includedRecipeIdsAbsolute,
|
||||
'selectedRecipeTotalCosts' => FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->costs,
|
||||
'userfields' => $this->UserfieldsService->GetFields('recipes'),
|
||||
'userfieldValues' => $this->UserfieldsService->GetAllValues('recipes')
|
||||
'userfieldValues' => $this->UserfieldsService->GetAllValues('recipes'),
|
||||
'quantityUnitConversionsResolved' => $this->Database->quantity_unit_conversions_resolved()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -96,7 +97,8 @@ class RecipesController extends BaseController
|
||||
'recipesResolved' => $this->RecipesService->GetRecipesResolved(),
|
||||
'recipes' => $this->Database->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'),
|
||||
'recipeNestings' => $this->Database->recipes_nestings()->where('recipe_id', $recipeId),
|
||||
'userfields' => $this->UserfieldsService->GetFields('recipes')
|
||||
'userfields' => $this->UserfieldsService->GetFields('recipes'),
|
||||
'quantityUnitConversionsResolved' => $this->Database->quantity_unit_conversions_resolved()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -108,7 +110,8 @@ class RecipesController extends BaseController
|
||||
'mode' => 'create',
|
||||
'recipe' => $this->Database->recipes($args['recipeId']),
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
|
||||
'quantityUnits' => $this->Database->quantity_units()->orderBy('name'),
|
||||
'quantityUnitConversionsResolved' => $this->Database->quantity_unit_conversions_resolved()
|
||||
]);
|
||||
}
|
||||
else
|
||||
@ -118,7 +121,8 @@ class RecipesController extends BaseController
|
||||
'recipe' => $this->Database->recipes($args['recipeId']),
|
||||
'recipePos' => $this->Database->recipes_pos($args['recipePosId']),
|
||||
'products' => $this->Database->products()->orderBy('name'),
|
||||
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
|
||||
'quantityUnits' => $this->Database->quantity_units()->orderBy('name'),
|
||||
'quantityUnitConversionsResolved' => $this->Database->quantity_unit_conversions_resolved()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -283,3 +283,8 @@ msgstr ""
|
||||
|
||||
msgid "Dark Chocolate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Slice"
|
||||
msgid_plural "Slices"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
@ -1323,19 +1323,28 @@ msgstr ""
|
||||
msgid "Not possible because this product is already used as a parent product in another product"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default conversions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Factor"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 %s is the same as..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Create QU conversion"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default for QU unit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quantity unit from"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quantity unit to"
|
||||
msgstr ""
|
||||
|
||||
msgid "The amount cannot be lower than %s and must be a valid number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Factor"
|
||||
msgid "This cannot be lower than %1$s and must be a valid number with max. %2$s decimal places"
|
||||
msgstr ""
|
||||
|
||||
msgid "This cannot be equal to %s"
|
||||
@ -1344,29 +1353,14 @@ msgstr ""
|
||||
msgid "This means 1 %1$s is the same as %2$s %3$s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit QU conversion"
|
||||
msgstr ""
|
||||
|
||||
msgid "For product"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default for QU unit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Override for product"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default conversions"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 %s is the same as..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure to remove this conversion?"
|
||||
msgstr ""
|
||||
|
||||
msgid "QU conversions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Product overrides"
|
||||
msgstr ""
|
||||
|
||||
msgid "Override for product"
|
||||
msgstr ""
|
||||
|
||||
msgid "This equals %1$s %2$s in stock"
|
||||
msgstr ""
|
||||
|
@ -50,34 +50,55 @@ CREATE VIEW quantity_unit_conversions_resolved
|
||||
AS
|
||||
-- First: Product "purchase to stock" conversion factor
|
||||
SELECT
|
||||
p.id AS id, -- Dummy, LessQL needs an id column
|
||||
p.id AS product_id,
|
||||
p.qu_id_purchase AS from_qu_id,
|
||||
qu_from.name AS from_qu_name,
|
||||
p.qu_id_stock AS to_qu_id,
|
||||
qu_to.name AS to_qu_name,
|
||||
p.qu_factor_purchase_to_stock AS factor
|
||||
FROM products p
|
||||
JOIN quantity_units qu_from
|
||||
ON p.qu_id_purchase = qu_from.id
|
||||
JOIN quantity_units qu_to
|
||||
ON p.qu_id_stock = qu_to.id
|
||||
|
||||
UNION
|
||||
|
||||
-- Second: Product specific overrides
|
||||
SELECT
|
||||
p.id AS id, -- Dummy, LessQL needs an id column
|
||||
p.id AS product_id,
|
||||
p.qu_id_stock AS from_qu_id,
|
||||
quc.from_qu_id AS from_qu_id,
|
||||
qu_from.name AS from_qu_name,
|
||||
quc.to_qu_id AS to_qu_id,
|
||||
qu_to.name AS to_qu_name,
|
||||
quc.factor AS factor
|
||||
FROM products p
|
||||
JOIN quantity_unit_conversions quc
|
||||
ON p.qu_id_stock = quc.from_qu_id
|
||||
AND p.id = quc.product_id
|
||||
JOIN quantity_units qu_from
|
||||
ON quc.from_qu_id = qu_from.id
|
||||
JOIN quantity_units qu_to
|
||||
ON quc.to_qu_id = qu_to.id
|
||||
|
||||
UNION
|
||||
|
||||
-- Third: Default quantity unit conversion factors
|
||||
SELECT
|
||||
p.id AS id, -- Dummy, LessQL needs an id column
|
||||
p.id AS product_id,
|
||||
p.qu_id_stock AS from_qu_id,
|
||||
qu_from.name AS from_qu_name,
|
||||
quc.to_qu_id AS to_qu_id,
|
||||
qu_to.name AS to_qu_name,
|
||||
quc.factor AS factor
|
||||
FROM products p
|
||||
JOIN quantity_unit_conversions quc
|
||||
ON p.qu_id_stock = quc.from_qu_id
|
||||
AND quc.product_id IS NULL;
|
||||
AND quc.product_id IS NULL
|
||||
JOIN quantity_units qu_from
|
||||
ON quc.from_qu_id = qu_from.id
|
||||
JOIN quantity_units qu_to
|
||||
ON quc.to_qu_id = qu_to.id;
|
||||
|
@ -97,3 +97,18 @@ FindObjectInArrayByPropertyValue = function(array, propertyName, propertyValue)
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
FindAllObjectsInArrayByPropertyValue = function(array, propertyName, propertyValue)
|
||||
{
|
||||
var returnArray = [];
|
||||
|
||||
for (var i = 0; i < array.length; i++)
|
||||
{
|
||||
if (array[i][propertyName] == propertyValue)
|
||||
{
|
||||
returnArray.push(array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
|
75
public/viewjs/components/productamountpicker.js
Normal file
75
public/viewjs/components/productamountpicker.js
Normal file
@ -0,0 +1,75 @@
|
||||
Grocy.Components.ProductAmountPicker = {};
|
||||
Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = false;
|
||||
|
||||
Grocy.Components.ProductAmountPicker.Reload = function(productId, destinationQuId, forceInitialDisplayQu = false)
|
||||
{
|
||||
if (!Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled)
|
||||
{
|
||||
var conversionsForProduct = FindAllObjectsInArrayByPropertyValue(Grocy.QuantityUnitConversionsResolved, 'product_id', productId);
|
||||
$("#qu_id").find("option").remove().end();
|
||||
$("#qu_id").attr("data-destination-qu-name", FindObjectInArrayByPropertyValue(Grocy.QuantityUnits, 'id', destinationQuId).name);
|
||||
conversionsForProduct.forEach(conversion =>
|
||||
{
|
||||
$("#qu_id").append('<option value="' + conversion.to_qu_id + '" data-qu-factor="' + conversion.factor + '">' + conversion.to_qu_name + '</option>');
|
||||
});
|
||||
}
|
||||
|
||||
if (!Grocy.Components.ProductAmountPicker.InitalValueSet || forceInitialDisplayQu)
|
||||
{
|
||||
$("#qu_id").val($("#qu_id").attr("data-inital-qu-id"));
|
||||
}
|
||||
|
||||
if (!Grocy.Components.ProductAmountPicker.InitalValueSet)
|
||||
{
|
||||
var convertedAmount = $("#display_amount").val() * $("#qu_id option:selected").attr("data-qu-factor");
|
||||
$("#display_amount").val(convertedAmount);
|
||||
|
||||
Grocy.Components.ProductAmountPicker.InitalValueSet = true;
|
||||
}
|
||||
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
}
|
||||
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit = function()
|
||||
{
|
||||
$("#qu_id").val(quId);
|
||||
}
|
||||
|
||||
Grocy.Components.ProductAmountPicker.AllowAnyQu = function(quId)
|
||||
{
|
||||
Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = true;
|
||||
|
||||
$("#qu_id").find("option").remove().end();
|
||||
Grocy.QuantityUnits.forEach(qu =>
|
||||
{
|
||||
$("#qu_id").append('<option value="' + qu.id + '" data-qu-factor="1">' + qu.name + '</option>');
|
||||
});
|
||||
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
}
|
||||
|
||||
$(".input-group-productamountpicker").on("change", function()
|
||||
{
|
||||
var destinationQuName = $("#qu_id").attr("data-destination-qu-name");
|
||||
var selectedQuName = $("#qu_id option:selected").text();
|
||||
var quFactor = $("#qu_id option:selected").attr("data-qu-factor");
|
||||
var amount = $("#display_amount").val();
|
||||
var destinationAmount = amount / quFactor;
|
||||
|
||||
if (destinationQuName == selectedQuName || Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled)
|
||||
{
|
||||
$("#qu-conversion-info").addClass("d-none");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu-conversion-info").removeClass("d-none");
|
||||
$("#qu-conversion-info").text(__t("This equals %1$s %2$s in stock", destinationAmount.toLocaleString(), destinationQuName));
|
||||
}
|
||||
|
||||
$("#amount").val(destinationAmount);
|
||||
});
|
||||
|
||||
$("#display_amount").on("keyup", function()
|
||||
{
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
});
|
@ -55,7 +55,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination;
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);;
|
||||
}
|
||||
},
|
||||
function (xhr)
|
||||
@ -73,7 +73,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination;
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -118,7 +118,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination;
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);;
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
@ -136,7 +136,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination;
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -233,6 +233,29 @@ $('.input-group-qu').on('change', function(e)
|
||||
$('#product-form input').keyup(function(event)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('product-form');
|
||||
|
||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#qu-conversion-add-button").addClass("disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu-conversion-add-button").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$('#product-form select').change(function(event)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('product-form');
|
||||
|
||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#qu-conversion-add-button").addClass("disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu-conversion-add-button").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$('#location_id').change(function(event)
|
||||
@ -343,6 +366,7 @@ $('#qu-conversions-table tbody').removeClass("d-none");
|
||||
quConversionsTable.columns.adjust().draw();
|
||||
|
||||
Grocy.Components.UserfieldsForm.Load();
|
||||
$("#name").trigger("keyup");
|
||||
$('#name').focus();
|
||||
$('.input-group-qu').trigger('change');
|
||||
Grocy.FrontendHelpers.ValidateForm('product-form');
|
||||
@ -390,12 +414,12 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
|
||||
$(document).on('click', '.qu-conversion-edit-button', function (e)
|
||||
{
|
||||
var id = $(e.currentTarget).attr('data-qu-conversion-id');
|
||||
Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/" + id.toString() + "?product=" + Grocy.EditObjectId.toString());
|
||||
Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/" + id.toString() + "?product=editobjectid");
|
||||
$('#save-product-button').click();
|
||||
});
|
||||
|
||||
$("#qu-conversion-add-button").on("click", function(e)
|
||||
{
|
||||
Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/new?product=" + Grocy.EditObjectId.toString());
|
||||
Grocy.ProductEditFormRedirectUri = U("/quantityunitconversion/new?product=editobjectid");
|
||||
$('#save-product-button').click();
|
||||
});
|
||||
|
@ -5,6 +5,15 @@
|
||||
var jsonData = $('#quantityunit-form').serializeJSON();
|
||||
Grocy.FrontendHelpers.BeginUiBusy("quantityunit-form");
|
||||
|
||||
if (Grocy.QuantityUnitEditFormRedirectUri !== undefined)
|
||||
{
|
||||
redirectDestination = Grocy.QuantityUnitEditFormRedirectUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
redirectDestination = U('/quantityunits');
|
||||
}
|
||||
|
||||
if (Grocy.EditMode === 'create')
|
||||
{
|
||||
Grocy.Api.Post('objects/quantity_units', jsonData,
|
||||
@ -13,7 +22,14 @@
|
||||
Grocy.EditObjectId = result.created_object_id;
|
||||
Grocy.Components.UserfieldsForm.Save(function()
|
||||
{
|
||||
window.location.href = U('/quantityunits');
|
||||
if (redirectDestination == "reload")
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(xhr)
|
||||
@ -30,7 +46,14 @@
|
||||
{
|
||||
Grocy.Components.UserfieldsForm.Save(function()
|
||||
{
|
||||
window.location.href = U('/quantityunits');
|
||||
if (redirectDestination == "reload")
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
window.location.href = redirectDestination.replace("editobjectid", Grocy.EditObjectId);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(xhr)
|
||||
@ -44,6 +67,24 @@
|
||||
|
||||
$('#quantityunit-form input').keyup(function(event)
|
||||
{
|
||||
if (!$("#name").val().isEmpty())
|
||||
{
|
||||
$("#qu-conversion-headline-info").text(__t('1 %s is the same as...', $("#name").val()));
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu-conversion-headline-info").text("");
|
||||
}
|
||||
|
||||
if (document.getElementById('quantityunit-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#qu-conversion-add-button").addClass("disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu-conversion-add-button").removeClass("disabled");
|
||||
}
|
||||
|
||||
Grocy.FrontendHelpers.ValidateForm('quantityunit-form');
|
||||
});
|
||||
|
||||
@ -88,6 +129,7 @@ $('#qu-conversions-table tbody').removeClass("d-none");
|
||||
quConversionsTable.columns.adjust().draw();
|
||||
|
||||
Grocy.Components.UserfieldsForm.Load();
|
||||
$("#name").trigger("keyup");
|
||||
$('#name').focus();
|
||||
Grocy.FrontendHelpers.ValidateForm('quantityunit-form');
|
||||
|
||||
@ -114,7 +156,8 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
|
||||
Grocy.Api.Delete('objects/quantity_unit_conversions/' + objectId, { },
|
||||
function(result)
|
||||
{
|
||||
window.location.reload();
|
||||
Grocy.QuantityUnitEditFormRedirectUri = "reload";
|
||||
$('#save-quantityunit-button').click();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -129,29 +172,12 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
|
||||
$(document).on('click', '.qu-conversion-edit-button', function (e)
|
||||
{
|
||||
var id = $(e.currentTarget).attr('data-qu-conversion-id');
|
||||
|
||||
Grocy.Api.Put('objects/quantity_units/' + Grocy.EditObjectId, $('#quantityunit-form').serializeJSON(),
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U("/quantityunitconversion/" + id.toString() + "?qu-unit=" + Grocy.EditObjectId.toString());
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
Grocy.QuantityUnitEditFormRedirectUri = U("/quantityunitconversion/" + id.toString() + "?qu-unit=editobjectid");
|
||||
$('#save-quantityunit-button').click();
|
||||
});
|
||||
|
||||
$("#qu-conversion-add-button").on("click", function(e)
|
||||
{
|
||||
Grocy.Api.Put('objects/quantity_units/' + Grocy.EditObjectId, $('#quantityunit-form').serializeJSON(),
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U("/quantityunitconversion/new?qu-unit=" + Grocy.EditObjectId.toString());
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
Grocy.QuantityUnitEditFormRedirectUri = U("/quantityunitconversion/new?qu-unit=editobjectid");
|
||||
$('#save-quantityunit-button').click();
|
||||
});
|
||||
|
@ -1,9 +1,12 @@
|
||||
$('#save-recipe-pos-button').on('click', function(e)
|
||||
Grocy.RecipePosFormProductChangeCount = 0;
|
||||
|
||||
$('#save-recipe-pos-button').on('click', function (e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var jsonData = $('#recipe-pos-form').serializeJSON({ checkboxUncheckedValue: "0" });
|
||||
jsonData.recipe_id = Grocy.EditObjectParentId;
|
||||
delete jsonData.display_amount;
|
||||
|
||||
Grocy.FrontendHelpers.BeginUiBusy("recipe-pos-form");
|
||||
|
||||
@ -44,31 +47,37 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
if (productId)
|
||||
{
|
||||
Grocy.Components.ProductCard.Refresh(productId);
|
||||
|
||||
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
function(productDetails)
|
||||
{
|
||||
if (!$("#only_check_single_unit_in_stock").is(":checked"))
|
||||
Grocy.RecipePosFormProductChangeCount++;
|
||||
console.log(Grocy.RecipePosFormProductChangeCount);
|
||||
if (Grocy.RecipePosFormProductChangeCount < 3) // This triggers twice on inital page load, however
|
||||
{
|
||||
$("#qu_id").val(productDetails.quantity_unit_stock.id);
|
||||
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
|
||||
}
|
||||
|
||||
if (productDetails.product.allow_partial_units_in_stock == 1)
|
||||
{
|
||||
$("#amount").attr("min", "0.01");
|
||||
$("#amount").attr("step", "0.01");
|
||||
$("#amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', 0.01.toLocaleString()));
|
||||
$("#display_amount").attr("min", "0.01");
|
||||
$("#display_amount").attr("step", "0.01");
|
||||
$("#display_amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', 0.01.toLocaleString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#amount").attr("min", "1");
|
||||
$("#amount").attr("step", "1");
|
||||
$("#amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', '1'));
|
||||
$("#display_amount").attr("min", "1");
|
||||
$("#display_amount").attr("step", "1");
|
||||
$("#display_amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', '1'));
|
||||
}
|
||||
|
||||
$("#not_check_stock_fulfillment").prop("checked", productDetails.product.not_check_stock_fulfillment_for_recipes == 1);
|
||||
|
||||
$('#amount').focus();
|
||||
$('#display_amount').focus();
|
||||
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
|
||||
},
|
||||
function(xhr)
|
||||
@ -87,7 +96,7 @@ if (Grocy.Components.ProductPicker.InProductAddWorkflow() === false)
|
||||
}
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger('change');
|
||||
|
||||
$('#amount').on('focus', function(e)
|
||||
$('#display_amount').on('focus', function(e)
|
||||
{
|
||||
if (Grocy.Components.ProductPicker.GetValue().length === 0)
|
||||
{
|
||||
@ -125,19 +134,19 @@ $("#only_check_single_unit_in_stock").on("click", function()
|
||||
{
|
||||
if (this.checked)
|
||||
{
|
||||
$("#qu_id").removeAttr("disabled");
|
||||
$("#amount").attr("min", "0.01");
|
||||
$("#amount").attr("step", "0.01");
|
||||
$("#amount").parent().find(".invalid-feedback").text(__t("This cannot be negative"));
|
||||
$("#display_amount").attr("min", "0.01");
|
||||
$("#display_amount").attr("step", "0.01");
|
||||
$("#display_amount").parent().find(".invalid-feedback").text(__t("This cannot be negative"));
|
||||
Grocy.Components.ProductAmountPicker.AllowAnyQu();
|
||||
Grocy.FrontendHelpers.ValidateForm("recipe-pos-form");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#qu_id").attr("disabled", "");
|
||||
$("#amount").attr("min", "0");
|
||||
$("#amount").attr("step", "1");
|
||||
$("#display_amount").attr("min", "0");
|
||||
$("#display_amount").attr("step", "1");
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger("change"); // Selects the default quantity unit of the selected product
|
||||
$("#amount").parent().find(".invalid-feedback").text(__t("This cannot be negative and must be an integral number"));
|
||||
$("#display_amount").parent().find(".invalid-feedback").text(__t("This cannot be negative and must be an integral number"));
|
||||
Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = false;
|
||||
Grocy.FrontendHelpers.ValidateForm("recipe-pos-form");
|
||||
}
|
||||
});
|
||||
|
@ -48,6 +48,7 @@ class DemoDataGeneratorService extends BaseService
|
||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$this->__n_sql(1, 'Liter', 'Liters')}', '{$this->__n_sql(2, 'Liter', 'Liters')}'); --9
|
||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$this->__n_sql(1, 'Bottle', 'Bottles')}', '{$this->__n_sql(2, 'Bottle', 'Bottles')}'); --10
|
||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$this->__n_sql(1, 'Milliliter', 'Milliliters')}', '{$this->__n_sql(2, 'Milliliter', 'Milliliters')}'); --11
|
||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$this->__n_sql(1, 'Slice', 'Slices')}', '{$this->__n_sql(2, 'Slice', 'Slices')}'); --12
|
||||
|
||||
INSERT INTO product_groups(name) VALUES ('01 {$this->__t_sql('Sweets')}'); --1
|
||||
INSERT INTO product_groups(name) VALUES ('02 {$this->__t_sql('Bakery products')}'); --2
|
||||
@ -83,6 +84,8 @@ class DemoDataGeneratorService extends BaseService
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Milk Chocolate')}', 4, 3, 3, 1, 1, 2); --24
|
||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, parent_product_id) VALUES ('{$this->__t_sql('Dark Chocolate')}', 4, 3, 3, 1, 1, 2); --25
|
||||
|
||||
INSERT INTO quantity_unit_conversions (from_qu_id, to_qu_id, factor, product_id) VALUES (3, 12, 10, 10);
|
||||
|
||||
INSERT INTO shopping_list (note, amount) VALUES ('{$this->__t_sql('Some good snacks')}', 1);
|
||||
INSERT INTO shopping_list (product_id, amount) VALUES (20, 1);
|
||||
INSERT INTO shopping_list (product_id, amount) VALUES (17, 1);
|
||||
@ -102,7 +105,7 @@ class DemoDataGeneratorService extends BaseService
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount) VALUES (2, 10, 1);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount, note) VALUES (2, 17, 1, '{$this->__t_sql('This is the note content of the recipe ingredient')}');
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount) VALUES (2, 20, 1);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount) VALUES (3, 10, 1);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id) VALUES (3, 10, 0.2, 12);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount) VALUES (3, 11, 1);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount) VALUES (4, 5, 4);
|
||||
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 21, 200, 8, 1);
|
||||
|
34
views/components/productamountpicker.blade.php
Normal file
34
views/components/productamountpicker.blade.php
Normal file
@ -0,0 +1,34 @@
|
||||
@push('componentScripts')
|
||||
<script src="{{ $U('/viewjs/components/productamountpicker.js', true) }}?v={{ $version }}"></script>
|
||||
@endpush
|
||||
|
||||
@php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp
|
||||
|
||||
<div class="form-group row {{ $additionalGroupCssClasses }}">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'display_amount',
|
||||
'label' => 'Amount',
|
||||
'min' => 0,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('This cannot be negative and must be an integral number'),
|
||||
'additionalGroupCssClasses' => 'col-4 mb-1',
|
||||
'additionalCssClasses' => 'input-group-productamountpicker'
|
||||
))
|
||||
|
||||
<div class="form-group col-8 mb-1">
|
||||
<label for="qu_id">{{ $__t('Quantity unit') }}</label>
|
||||
<select required class="form-control input-group-productamountpicker" id="qu_id" name="qu_id" data-inital-qu-id="{{ $initialQuId }}">
|
||||
<option></option>
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
|
||||
</div>
|
||||
|
||||
<div id="qu-conversion-info" class="col form-text text-info d-none"></div>
|
||||
<input type="hidden" id="amount" name="amount" value="">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -242,10 +242,10 @@
|
||||
@foreach($quConversions as $quConversion)
|
||||
<tr>
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-sm btn-info qu-conversion-edit-button" href="#" data-qu-conversion-id="{{ $quConversion->id }}">
|
||||
<a class="btn btn-sm btn-info qu-conversion-edit-button @if($quConversion->product_id == null) disabled @endif" href="#" data-qu-conversion-id="{{ $quConversion->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-danger qu-conversion-delete-button" href="#" data-qu-conversion-id="{{ $quConversion->id }}">
|
||||
<a class="btn btn-sm btn-danger qu-conversion-delete-button @if($quConversion->product_id == null) disabled @endif" href="#" data-qu-conversion-id="{{ $quConversion->id }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -60,7 +60,7 @@
|
||||
'min' => 0,
|
||||
'step' => 0.001,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s and must be a valid number', '0'),
|
||||
'invalidFeedback' => $__t('This cannot be lower than %1$s and must be a valid number with max. %2$s decimal places', '0', '3'),
|
||||
'additionalHtmlElements' => '<p id="qu-conversion-info" class="form-text text-info d-none"></p>',
|
||||
'additionalCssClasses' => 'input-group-qu'
|
||||
))
|
||||
|
@ -68,7 +68,7 @@
|
||||
<i class="fas fa-plus"></i> {{ $__t('Add') }}
|
||||
</a>
|
||||
</h2>
|
||||
<h5 class="text-muted font-italic">{{ $__t('1 %s is the same as...', $quantityUnit->name) }}</h5>
|
||||
<h5 id="qu-conversion-headline-info" class="text-muted font-italic"></h5>
|
||||
<table id="qu-conversions-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -22,7 +22,11 @@
|
||||
<div class="col">
|
||||
<h1>@yield('title')</h1>
|
||||
|
||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||
<script>
|
||||
Grocy.EditMode = '{{ $mode }}';
|
||||
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
|
||||
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
|
||||
</script>
|
||||
|
||||
@if($mode == 'edit')
|
||||
<script>Grocy.EditObjectId = {{ $recipe->id }};</script>
|
||||
@ -125,6 +129,16 @@
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
|
||||
</td>
|
||||
<td>
|
||||
@php
|
||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
|
||||
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id);
|
||||
if ($productQuConversion)
|
||||
{
|
||||
$recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
|
||||
}
|
||||
@endphp
|
||||
@if(!empty($recipePosition->variable_amount))
|
||||
{{ $recipePosition->variable_amount }}
|
||||
@else
|
||||
|
@ -17,6 +17,9 @@
|
||||
<script>
|
||||
Grocy.EditMode = '{{ $mode }}';
|
||||
Grocy.EditObjectParentId = {{ $recipe->id }};
|
||||
Grocy.EditObject = {!! json_encode($recipePos) !!};
|
||||
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
|
||||
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
|
||||
</script>
|
||||
|
||||
@if($mode == 'edit')
|
||||
@ -32,39 +35,20 @@
|
||||
'prefillByName' => $prefillByName
|
||||
))
|
||||
|
||||
<div class="form-group row">
|
||||
@php if($mode == 'edit') { $value = $recipePos->amount; } else { $value = 1; } @endphp
|
||||
@php if($mode == 'edit') { $initialQuId = $recipePos->qu_id; } else { $initialQuId = ''; } @endphp
|
||||
@include('components.productamountpicker', array(
|
||||
'value' => $value,
|
||||
'initialQuId' => $initialQuId,
|
||||
'additionalGroupCssClasses' => 'mb-0'
|
||||
))
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
|
||||
@php if($mode == 'edit') { $value = $recipePos->amount; } else { $value = 1; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'amount',
|
||||
'label' => 'Amount',
|
||||
'min' => 0,
|
||||
'value' => $value,
|
||||
'invalidFeedback' => $__t('This cannot be negative and must be an integral number'),
|
||||
'additionalGroupCssClasses' => 'col-4'
|
||||
))
|
||||
|
||||
<div class="form-group col-8">
|
||||
<label for="qu_id">{{ $__t('Quantity unit') }}</label>
|
||||
<select required @if($mode == 'create' || ($mode == 'edit' && $recipePos->only_check_single_unit_in_stock != 1)) disabled @endif class="form-control" id="qu_id" name="qu_id">
|
||||
@foreach($quantityUnits as $quantityunit)
|
||||
<option @if($mode == 'edit' && $quantityunit->id == $recipePos->qu_id) selected @endif value="{{ $quantityunit->id }}">{{ $quantityunit->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-check">
|
||||
<input type="hidden" name="only_check_single_unit_in_stock" value="0">
|
||||
<input @if($mode == 'edit' && $recipePos->only_check_single_unit_in_stock == 1) checked @endif class="form-check-input" type="checkbox" id="only_check_single_unit_in_stock" name="only_check_single_unit_in_stock" value="1">
|
||||
<label class="form-check-label" for="only_check_single_unit_in_stock">{{ $__t('Only check if a single unit is in stock (a different quantity can then be used above)') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-group">
|
||||
<input type="hidden" name="only_check_single_unit_in_stock" value="0">
|
||||
<input @if($mode == 'edit' && $recipePos->only_check_single_unit_in_stock == 1) checked @endif class="form-check-input" type="checkbox" id="only_check_single_unit_in_stock" name="only_check_single_unit_in_stock" value="1">
|
||||
<label class="form-check-label" for="only_check_single_unit_in_stock">{{ $__t('Only check if a single unit is in stock (a different quantity can then be used above)') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,6 +5,11 @@
|
||||
@section('viewJsName', 'recipes')
|
||||
|
||||
@section('content')
|
||||
<script>
|
||||
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
|
||||
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-12 col-md-6 pb-3">
|
||||
@ -183,6 +188,16 @@
|
||||
<h5 class="mb-2 mt-2 ml-4"><strong>{{ $selectedRecipePosition->ingredient_group }}</strong></h5>
|
||||
@endif
|
||||
<li class="list-group-item">
|
||||
@php
|
||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
|
||||
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $selectedRecipePosition->qu_id);
|
||||
if ($productQuConversion)
|
||||
{
|
||||
$selectedRecipePosition->recipe_amount = $selectedRecipePosition->recipe_amount * $productQuConversion->factor;
|
||||
}
|
||||
@endphp
|
||||
@if(!empty($selectedRecipePosition->recipe_variable_amount))
|
||||
{{ $selectedRecipePosition->recipe_variable_amount }}
|
||||
@else
|
||||
@ -224,6 +239,16 @@
|
||||
<h5 class="mb-2 mt-2 ml-4"><strong>{{ $selectedRecipePosition->ingredient_group }}</strong></h5>
|
||||
@endif
|
||||
<li class="list-group-item">
|
||||
@php
|
||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
|
||||
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
|
||||
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $selectedRecipePosition->qu_id);
|
||||
if ($productQuConversion)
|
||||
{
|
||||
$selectedRecipePosition->recipe_amount = $selectedRecipePosition->recipe_amount * $productQuConversion->factor;
|
||||
}
|
||||
@endphp
|
||||
@if(!empty($selectedRecipePosition->recipe_variable_amount))
|
||||
{{ $selectedRecipePosition->recipe_variable_amount }}
|
||||
@else
|
||||
|
Loading…
x
Reference in New Issue
Block a user