From b0b3322266404d6168b0000a21be221d8d1eb3b1 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Fri, 13 Nov 2020 17:30:57 +0100 Subject: [PATCH] Also relate the shopping list amount to QU stock --- changelog/60_UNRELEASED_2020-xx-xx.md | 6 +- controllers/StockController.php | 1 + grocy.openapi.json | 4 +- localization/strings.pot | 14 +-- migrations/0117.sql | 118 ++++++++++++++++++++++++++ public/viewjs/mealplan.js | 10 +-- public/viewjs/purchase.js | 7 +- public/viewjs/shoppinglistitemform.js | 94 +++++++++++++------- services/RecipesService.php | 5 +- services/StockService.php | 4 +- views/productform.blade.php | 35 ++++---- views/products.blade.php | 4 +- views/shoppinglist.blade.php | 21 ++++- views/shoppinglistitemform.blade.php | 6 +- 14 files changed, 249 insertions(+), 80 deletions(-) create mode 100644 migrations/0117.sql diff --git a/changelog/60_UNRELEASED_2020-xx-xx.md b/changelog/60_UNRELEASED_2020-xx-xx.md index 1daaaabd..ca8d6e4a 100644 --- a/changelog/60_UNRELEASED_2020-xx-xx.md +++ b/changelog/60_UNRELEASED_2020-xx-xx.md @@ -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 `|` diff --git a/controllers/StockController.php b/controllers/StockController.php index 18edba1f..89ed2a76 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -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') ]); diff --git a/grocy.openapi.json b/grocy.openapi.json index 737876ce..0745ca2d 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -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, diff --git a/localization/strings.pot b/localization/strings.pot index 91d7a39c..b039ae5f 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -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 "" diff --git a/migrations/0117.sql b/migrations/0117.sql new file mode 100644 index 00000000..3604dc9b --- /dev/null +++ b/migrations/0117.sql @@ -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; diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index 36525fff..b12fd026 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -219,9 +219,9 @@ var calendar = $("#calendar").fullCalendar({
' + fulfillmentIconHtml + " " + fulfillmentInfoHtml + '
\ ' + costsAndCaloriesPerServing + ' \
\ - \ - \ - \ + \ + \ + \ \
\ '); @@ -258,8 +258,8 @@ var calendar = $("#calendar").fullCalendar({
\
' + mealPlanEntry.note + '
\
\ - \ - \ + \ + \
\
'); } diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js index b362b72a..521df441 100644 --- a/public/viewjs/purchase.js +++ b/public/viewjs/purchase.js @@ -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'); } } diff --git a/public/viewjs/shoppinglistitemform.js b/public/viewjs/shoppinglistitemform.js index 2ebceeb1..e2d78275 100644 --- a/public/viewjs/shoppinglistitemform.js +++ b/public/viewjs/shoppinglistitemform.js @@ -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(); diff --git a/services/RecipesService.php b/services/RecipesService.php index 5e5e53bb..856009d5 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -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(); diff --git a/services/StockService.php b/services/StockService.php index 1a54779b..df045332 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -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, diff --git a/views/productform.blade.php b/views/productform.blade.php index 0cd10ae2..7f0a59a7 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -201,22 +201,6 @@ -
- - -
{{ $__t('A quantity unit is required') }}
-
-
{{ $__t('A quantity unit is required') }}
+
+ + + +
{{ $__t('A quantity unit is required') }}
+
+ @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', diff --git a/views/products.blade.php b/views/products.blade.php index 2877e568..d903580d 100644 --- a/views/products.blade.php +++ b/views/products.blade.php @@ -98,8 +98,8 @@ {{ $__t('Name') }} {{ $__t('Location') }} {{ $__t('Min. stock amount') }} - {{ $__t('QU purchase') }} - {{ $__t('QU stock') }} + {{ $__t('Default quantity unit purchase') }} + {{ $__t('Quantity unit stock') }} {{ $__t('Product group') }} @include('components.userfields_thead', array( diff --git a/views/shoppinglist.blade.php b/views/shoppinglist.blade.php index 16697cd8..ddfd381a 100644 --- a/views/shoppinglist.blade.php +++ b/views/shoppinglist.blade.php @@ -223,8 +223,8 @@ 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> @@ -233,7 +233,20 @@ @if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}
@endif{!! nl2br($listItem->note) !!} - {{ $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 + @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 + {{ $listItem->amount }} @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name_plural) }}@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 {{ $__t('Ungrouped') }} @endif @@ -339,7 +352,7 @@ @if(!empty($listItem->product_id)) {{ FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id)->name }}
@endif{!! nl2br($listItem->note) !!} - {{ $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 + {{ $listItem->amount }} @if(!empty($listItem->product_id)){{ $__n($listItem->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $listItem->qu_id)->name_plural) }}@endif @include('components.userfields_tbody', array( diff --git a/views/shoppinglistitemform.blade.php b/views/shoppinglistitemform.blade.php index 9d8d95ad..1d37aca1 100644 --- a/views/shoppinglistitemform.blade.php +++ b/views/shoppinglistitemform.blade.php @@ -66,11 +66,15 @@ 'prefillById' => $productId )) + @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 ))