mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Also relate the shopping list amount to QU stock
This commit is contained in:
parent
ab68a51ba7
commit
b0b3322266
@ -3,6 +3,7 @@
|
||||
### New feature: Use any product related quantity unit anywhere
|
||||
- Finally it's possible to use any product related quantity unit on any page
|
||||
- Products still have one quantity unit stock and one (default) quantity unit purchase, but any QU, which has a direct or indirect conversion for that product, can be used to pick an amount
|
||||
_- (Because the stock quantity unit is now the base for everything, it cannot be changed after the product was once added to stock (for now, maybe there will be a possibilty to change it in a future release))_
|
||||
|
||||
### New feature: Prefill purchase data by barcodes
|
||||
- Imagine you buy for example eggs in different pack sizes and they have different barcodes
|
||||
@ -127,8 +128,9 @@
|
||||
- Korean (demo available at https://ko.demo.grocy.info)
|
||||
|
||||
### API improvements/fixes
|
||||
- Breaking changes:
|
||||
- All prices are now related to the products **stock** quantity unit (instead of the purchase QU)
|
||||
- **Breaking changes**:
|
||||
- All prices are now related to the products stock quantity unit (instead of the products purchase QU)
|
||||
- All (product) amounts are now related to the products stock quantity unit (was related to the products purchase QU for the shopping list before)
|
||||
- The product object no longer has a field `barcodes` with a comma separated barcode list, instead barcodes are now stored in a separate table/entity `product_barcodes` (use the existing "Generic entity interactions" endpoints to access them)
|
||||
- For better integration (apps), it's now possible to show a QR-Code for API keys (thanks @fipwmaqzufheoxq92ebc)
|
||||
- New QR-Code button on the "Manage API keys"-page (top right corner settings menu), the QR-Codes contains `<API-Url>|<API-Key>`
|
||||
|
@ -322,6 +322,7 @@ class StockController extends BaseController
|
||||
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'),
|
||||
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'),
|
||||
'selectedShoppingListId' => $listId,
|
||||
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
|
||||
'userfields' => $this->getUserfieldsService()->GetFields('products'),
|
||||
'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products')
|
||||
]);
|
||||
|
@ -3811,7 +3811,7 @@
|
||||
"product_barcodes": {
|
||||
"$ref": "#/components/schemas/ProductBarcodeDetailsResponse"
|
||||
},
|
||||
"quantity_unit_purchase": {
|
||||
"default_quantity_unit_purchase": {
|
||||
"$ref": "#/components/schemas/QuantityUnit"
|
||||
},
|
||||
"quantity_unit_stock": {
|
||||
@ -3898,7 +3898,7 @@
|
||||
"last_used": null,
|
||||
"stock_amount": "2",
|
||||
"stock_amount_opened": null,
|
||||
"quantity_unit_purchase": {
|
||||
"default_quantity_unit_purchase": {
|
||||
"id": "3",
|
||||
"name": "Pack",
|
||||
"description": null,
|
||||
|
@ -181,15 +181,6 @@ msgstr ""
|
||||
msgid "Min. stock amount"
|
||||
msgstr ""
|
||||
|
||||
msgid "QU purchase"
|
||||
msgstr ""
|
||||
|
||||
msgid "QU stock"
|
||||
msgstr ""
|
||||
|
||||
msgid "QU factor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
@ -205,7 +196,7 @@ msgstr ""
|
||||
msgid "Default best before days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quantity unit purchase"
|
||||
msgid "Default quantity unit purchase"
|
||||
msgstr ""
|
||||
|
||||
msgid "Quantity unit stock"
|
||||
@ -1930,3 +1921,6 @@ msgstr ""
|
||||
|
||||
msgid "Show a QR-Code for this API key"
|
||||
msgstr ""
|
||||
|
||||
msgid "This is the default quantity unit used when adding a product to the shopping list"
|
||||
msgstr ""
|
||||
|
118
migrations/0117.sql
Normal file
118
migrations/0117.sql
Normal file
@ -0,0 +1,118 @@
|
||||
ALTER TABLE shopping_list
|
||||
ADD qu_id INTEGER;
|
||||
|
||||
UPDATE shopping_list
|
||||
SET qu_id = (SELECT qu_id_purchase FROM products WHERE id = product_id)
|
||||
WHERE product_id IS NOT NULL;
|
||||
|
||||
UPDATE shopping_list
|
||||
SET amount = IFNULL(ROUND(amount * (SELECT CASE WHEN IFNULL(qu_factor_purchase_to_stock, 0) = 0 THEN 1 ELSE qu_factor_purchase_to_stock END FROM products WHERE id = product_id), 2), amount)
|
||||
WHERE product_id IS NOT NULL;
|
||||
|
||||
CREATE TRIGGER shopping_list_qu_id_default AFTER INSERT ON shopping_list
|
||||
BEGIN
|
||||
UPDATE shopping_list
|
||||
SET qu_id = (SELECT qu_id_stock FROM products where id = product_id)
|
||||
WHERE qu_id IS NULL
|
||||
AND id = NEW.id;
|
||||
END;
|
||||
|
||||
DROP VIEW recipes_pos_resolved;
|
||||
CREATE VIEW recipes_pos_resolved
|
||||
AS
|
||||
|
||||
-- Multiplication by 1.0 to force conversion to float (REAL)
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END < 0 THEN ABS(IFNULL(sc.amount_aggregated, 0) - (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END)) ELSE 0 END AS missing_amount,
|
||||
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * pop.price * rp.price_factor AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
|
||||
p.active AS product_active
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN products_oldest_stock_unit_price pop
|
||||
ON rp.product_id = pop.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- Just add all recipe positions which should not be checked against stock with fulfilled need
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
1 AS need_fulfilled,
|
||||
0 AS missing_amount,
|
||||
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
|
||||
1 AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) END) * rp.amount * IFNULL(pop.price, 0) * rp.price_factor AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount * (r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p.calories, 0) AS calories,
|
||||
p.active AS product_active
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN products_oldest_stock_unit_price pop
|
||||
ON rp.product_id = pop.product_id
|
||||
WHERE rp.not_check_stock_fulfillment = 1;
|
@ -219,9 +219,9 @@ var calendar = $("#calendar").fullCalendar({
|
||||
<h5 class="small timeago-contextual text-truncate">' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '</h5> \
|
||||
' + costsAndCaloriesPerServing + ' \
|
||||
<h5> \
|
||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-product-button" href="#"><i class="fas fa-trash"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#"><i class="fas fa-edit"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-success btn-xs product-consume-button ' + productConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume %1$s of %2$s", parseFloat(mealPlanEntry.product_amount).toLocaleString() + ' ' + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural), productDetails.product.name) + '" data-product-id="' + productDetails.product.id.toString() + '" data-product-name="' + productDetails.product.name + '" data-product-amount="' + mealPlanEntry.product_amount + '"><i class="fas fa-utensils"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-product-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-trash"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#" data-toggle="tooltip" title="' + __t("Edit this item") + '"><i class="fas fa-edit"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-success btn-xs product-consume-button ' + productConsumeButtonDisabledClasses + '" href="#" data-toggle="tooltip" title="' + __t("Consume %1$s of %2$s", parseFloat(mealPlanEntry.product_amount).toLocaleString() + ' ' + __n(mealPlanEntry.product_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '" data-product-id="' + productDetails.product.id.toString() + '" data-product-name="' + productDetails.product.name + '" data-product-amount="' + mealPlanEntry.product_amount + '"><i class="fas fa-utensils"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-primary btn-xs show-as-dialog-link ' + productOrderMissingButtonDisabledClasses + '" href="' + U("/shoppinglistitem/new?embedded&updateexistingproduct&product=") + mealPlanEntry.product_id + '&amount=' + mealPlanEntry.product_amount + '" data-toggle="tooltip" title="' + __t("Add to shopping list") + '" data-product-id="' + productDetails.product.id.toString() + '" data-product-name="' + productDetails.product.name + '" data-product-amount="' + mealPlanEntry.product_amount + '"><i class="fas fa-cart-plus"></i></a> \
|
||||
</h5> \
|
||||
</div>');
|
||||
@ -258,8 +258,8 @@ var calendar = $("#calendar").fullCalendar({
|
||||
<div> \
|
||||
<h5 class="text-wrap text-break">' + mealPlanEntry.note + '<h5> \
|
||||
<h5> \
|
||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-note-button" href="#"><i class="fas fa-trash"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#"><i class="fas fa-edit"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-danger btn-xs remove-note-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-trash"></i></a> \
|
||||
<a class="ml-1 btn btn-outline-info btn-xs edit-meal-plan-entry-button" href="#" data-toggle="tooltip" title="' + __t("Delete this item") + '"><i class="fas fa-edit"></i></a> \
|
||||
</h5> \
|
||||
</div>');
|
||||
}
|
||||
|
@ -163,13 +163,14 @@ if (Grocy.Components.ProductPicker !== undefined)
|
||||
function(productDetails)
|
||||
{
|
||||
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_purchase.id);
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id);
|
||||
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }));
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
|
||||
if (GetUriParam("flow") === "shoppinglistitemtostock")
|
||||
{
|
||||
$('#display_amount').val(parseFloat(GetUriParam("amount")).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }));
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(GetUriParam("quId"));
|
||||
$('#display_amount').val(parseFloat(GetUriParam("amount") * $("#qu_id option:selected").attr("data-qu-factor")).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }));
|
||||
}
|
||||
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
@ -282,7 +283,6 @@ if (Grocy.Components.ProductPicker !== undefined)
|
||||
if (barcode.amount != null)
|
||||
{
|
||||
$("#display_amount").val(barcode.amount);
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
$("#display_amount").select();
|
||||
}
|
||||
|
||||
@ -296,6 +296,7 @@ if (Grocy.Components.ProductPicker !== undefined)
|
||||
Grocy.Components.ShoppingLocationPicker.SetId(barcode.shopping_location_id);
|
||||
}
|
||||
|
||||
$(".input-group-productamountpicker").trigger("change");
|
||||
Grocy.FrontendHelpers.ValidateForm('purchase-form');
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
$('#save-shoppinglist-button').on('click', function(e)
|
||||
Grocy.ShoppingListItemFormInitialLoadDone = false;
|
||||
|
||||
$('#save-shoppinglist-button').on('click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var jsonData = $('#shoppinglist-form').serializeJSON();
|
||||
if (!jsonData.product_id)
|
||||
{
|
||||
jsonData.amount = jsonData.display_amount;
|
||||
}
|
||||
delete jsonData.display_amount;
|
||||
delete jsonData.qu_id;
|
||||
|
||||
Grocy.FrontendHelpers.BeginUiBusy("shoppinglist-form");
|
||||
|
||||
if (GetUriParam("updateexistingproduct") !== undefined)
|
||||
@ -20,7 +26,7 @@
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.product_amount + " " + __n(jsonData.product_amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.product_amount + " " + __n(jsonData.product_amount, productDetails.default_quantity_unit_purchase.name, productDetails.default_.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
},
|
||||
@ -50,18 +56,26 @@
|
||||
{
|
||||
if (GetUriParam("embedded") !== undefined)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.amount + " " + __n(jsonData.amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
if (jsonData.product_id)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.amount + " " + __n(jsonData.amount, productDetails.default_quantity_unit_purchase.name, productDetails.default_quantity_unit_purchase.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -82,18 +96,26 @@
|
||||
{
|
||||
if (GetUriParam("embedded") !== undefined)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.amount + " " + __n(jsonData.amount, productDetails.quantity_unit_purchase.name, productDetails.quantity_unit_purchase.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
if (jsonData.product_id)
|
||||
{
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", jsonData.amount + " " + __n(jsonData.amount, productDetails.default_quantity_unit_purchase.name, productDetails.default_quantity_unit_purchase.name_plural), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
|
||||
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -120,11 +142,19 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
function(productDetails)
|
||||
{
|
||||
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_purchase.id);
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_purchase.id);
|
||||
if (!Grocy.ShoppingListItemFormInitialLoadDone)
|
||||
{
|
||||
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);
|
||||
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id);
|
||||
}
|
||||
|
||||
$('#display_amount').focus();
|
||||
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
|
||||
Grocy.ShoppingListItemFormInitialLoadDone = true;
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@ -136,13 +166,17 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
|
||||
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
|
||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger('change');
|
||||
|
||||
if (Grocy.EditMode === "edit")
|
||||
{
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger('change');
|
||||
}
|
||||
|
||||
if (Grocy.EditMode == "create")
|
||||
{
|
||||
Grocy.ShoppingListItemFormInitialLoadDone = true;
|
||||
}
|
||||
|
||||
$('#display_amount').on('focus', function(e)
|
||||
{
|
||||
$(this).select();
|
||||
|
@ -24,11 +24,11 @@ class RecipesService extends BaseService
|
||||
{
|
||||
$product = $this->getDataBase()->products($recipePosition->product_id);
|
||||
|
||||
$toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list) / $product->qu_factor_purchase_to_stock, 2);
|
||||
$toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list), 2);
|
||||
|
||||
if ($recipe->not_check_shoppinglist == 1)
|
||||
{
|
||||
$toOrderAmount = round($recipePosition->missing_amount / $product->qu_factor_purchase_to_stock, 2);
|
||||
$toOrderAmount = round($recipePosition->missing_amount, 2);
|
||||
}
|
||||
|
||||
if ($toOrderAmount > 0)
|
||||
@ -63,7 +63,6 @@ class RecipesService extends BaseService
|
||||
}
|
||||
|
||||
$recipeRow = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch();
|
||||
|
||||
if (!empty($recipeRow->product_id))
|
||||
{
|
||||
$recipeResolvedRow = $this->getDatabase()->recipes_resolved()->where('recipe_id = :1', $recipeId)->fetch();
|
||||
|
@ -26,7 +26,7 @@ class StockService extends BaseService
|
||||
foreach ($missingProducts as $missingProduct)
|
||||
{
|
||||
$product = $this->getDatabase()->products()->where('id', $missingProduct->id)->fetch();
|
||||
$amountToAdd = round($missingProduct->amount_missing / $product->qu_factor_purchase_to_stock, 2);
|
||||
$amountToAdd = round($missingProduct->amount_missing, 2);
|
||||
|
||||
$alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch();
|
||||
|
||||
@ -577,7 +577,7 @@ class StockService extends BaseService
|
||||
'stock_amount_opened' => $stockCurrentRow->amount_opened,
|
||||
'stock_amount_aggregated' => $stockCurrentRow->amount_aggregated,
|
||||
'stock_amount_opened_aggregated' => $stockCurrentRow->amount_opened_aggregated,
|
||||
'quantity_unit_purchase' => $quPurchase,
|
||||
'default_quantity_unit_purchase' => $quPurchase,
|
||||
'quantity_unit_stock' => $quStock,
|
||||
'last_price' => $lastPrice,
|
||||
'avg_price' => $avgPrice,
|
||||
|
@ -201,22 +201,6 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="qu_id_purchase">{{ $__t('Quantity unit purchase') }}</label>
|
||||
<select required
|
||||
class="form-control input-group-qu"
|
||||
id="qu_id_purchase"
|
||||
name="qu_id_purchase">
|
||||
<option></option>
|
||||
@foreach($quantityunits as $quantityunit)
|
||||
<option @if($mode=='edit'
|
||||
&&
|
||||
$quantityunit->id == $product->qu_id_purchase) selected="selected" @endif value="{{ $quantityunit->id }}">{{ $quantityunit->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="qu_id_stock">{{ $__t('Quantity unit stock') }}</label>
|
||||
<i class="fas fa-question-circle"
|
||||
@ -240,6 +224,25 @@
|
||||
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="qu_id_purchase">{{ $__t('Default quantity unit purchase') }}</label>
|
||||
<i class="fas fa-question-circle"
|
||||
data-toggle="tooltip"
|
||||
title="{{ $__t('This is the default quantity unit used when adding a product to the shopping list') }}"></i>
|
||||
<select required
|
||||
class="form-control input-group-qu"
|
||||
id="qu_id_purchase"
|
||||
name="qu_id_purchase">
|
||||
<option></option>
|
||||
@foreach($quantityunits as $quantityunit)
|
||||
<option @if($mode=='edit'
|
||||
&&
|
||||
$quantityunit->id == $product->qu_id_purchase) selected="selected" @endif value="{{ $quantityunit->id }}">{{ $quantityunit->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
|
||||
</div>
|
||||
|
||||
@php if($mode == 'edit') { $value = $product->qu_factor_purchase_to_stock; } else { $value = 1; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'qu_factor_purchase_to_stock',
|
||||
|
@ -98,8 +98,8 @@
|
||||
<th>{{ $__t('Name') }}</th>
|
||||
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">{{ $__t('Location') }}</th>
|
||||
<th>{{ $__t('Min. stock amount') }}</th>
|
||||
<th>{{ $__t('QU purchase') }}</th>
|
||||
<th>{{ $__t('QU stock') }}</th>
|
||||
<th>{{ $__t('Default quantity unit purchase') }}</th>
|
||||
<th>{{ $__t('Quantity unit stock') }}</th>
|
||||
<th>{{ $__t('Product group') }}</th>
|
||||
|
||||
@include('components.userfields_thead', array(
|
||||
|
@ -223,8 +223,8 @@
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-primary @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif @if(empty($listItem->product_id)) disabled @else shopping-list-stock-add-workflow-list-item-button @endif"
|
||||
href="{{ $U('/purchase?embedded&flow=shoppinglistitemtostock&product=') }}{{ $listItem->product_id }}&amount={{ $listItem->amount }}&listitemid={{ $listItem->id }}"
|
||||
@if(!empty($listItem->product_id)) data-toggle="tooltip" title="{{ $__t('Add %1$s of %2$s to stock', $listItem->amount . ' ' . $__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), FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name, $listItem->amount) }}" @endif>
|
||||
href="{{ $U('/purchase?embedded&flow=shoppinglistitemtostock&product=') }}{{ $listItem->product_id }}&amount={{ $listItem->amount }}&listitemid={{ $listItem->id }}&quId={{ $listItem->qu_id }}"
|
||||
@if(!empty($listItem->product_id)) data-toggle="tooltip" title="{{ $__t('Add %1$s of %2$s to stock', $listItem->amount . ' ' . $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name_plural), FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name, $listItem->amount) }}" @endif>
|
||||
<i class="fas fa-box"></i>
|
||||
</a>
|
||||
</td>
|
||||
@ -233,7 +233,20 @@
|
||||
@if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
|
||||
</td>
|
||||
<td>
|
||||
<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
|
||||
@if(!empty($listItem->product_id))
|
||||
@php
|
||||
$listItem->amount_origin_qu = $listItem->amount;
|
||||
$product = FindObjectInArrayByPropertyValue($products, 'id', $listItem->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', $listItem->qu_id);
|
||||
if ($productQuConversion)
|
||||
{
|
||||
$listItem->amount = $listItem->amount * $productQuConversion->factor;
|
||||
}
|
||||
@endphp
|
||||
@endif
|
||||
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name_plural) }}@endif
|
||||
</td>
|
||||
<td class="d-none">
|
||||
@if(!empty(FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->product_group_id)->name }} @else <span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span> @endif
|
||||
@ -339,7 +352,7 @@
|
||||
@if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
|
||||
</td>
|
||||
<td>
|
||||
{{ $listItem->amount }} @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->qu_id_purchase)->name_plural) }}@endif
|
||||
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name_plural) }}@endif
|
||||
</td>
|
||||
|
||||
@include('components.userfields_tbody', array(
|
||||
|
@ -66,11 +66,15 @@
|
||||
'prefillById' => $productId
|
||||
))
|
||||
</div>
|
||||
|
||||
@php if($mode == 'edit') { $value = $listItem->amount; } else { $value = 1; } @endphp
|
||||
@php if($mode == 'edit') { $initialQuId = $listItem->qu_id; } else { $initialQuId = ''; } @endphp
|
||||
@include('components.productamountpicker', array(
|
||||
'value' => $value,
|
||||
'initialQuId' => $initialQuId,
|
||||
'min' => '0.' . str_repeat('0', $userSettings['stock_decimal_places_amounts'] - 1) . '1',
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0.' . str_repeat('0', $userSettings['stock_decimal_places_amounts'] - 1) . '1')
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '0.' . str_repeat('0', $userSettings['stock_decimal_places_amounts'] - 1) . '1'),
|
||||
'isRequired' => false
|
||||
))
|
||||
|
||||
<div class="form-group">
|
||||
|
Loading…
x
Reference in New Issue
Block a user