diff --git a/README.md b/README.md index 0b60b81c..f8728d26 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ Please don't send me private messages regarding grocy help. I check the issue tr See the website for a list of community contributed Add-ons / Tools: [https://grocy.info/addons](https://grocy.info/addons) ## Motivation -A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge! +A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# Windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge! ## How to install -> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal ("indows) desktop application. +> Checkout [grocy-desktop](https://github.com/grocy/grocy-desktop), if you want to run grocy without having to manage a webserver just like a normal (Windows) desktop application. > > Directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next". diff --git a/changelog/62_UNRELEASED_xxxx-xx-xx.md b/changelog/62_UNRELEASED_xxxx-xx-xx.md new file mode 100644 index 00000000..3e8ad015 --- /dev/null +++ b/changelog/62_UNRELEASED_xxxx-xx-xx.md @@ -0,0 +1,29 @@ +### Stock improvements/fixes +- Product barcodes are now enforced to be unique across products +- Fixed that editing stock entries was not possible +- Fixed that consuming with Scan Mode was not possible +- Fixed that the current stock total value (header of the stock overview page) didn't include decimal amounts (thanks @Ape) +- Fixed that the transfer page was not fully populated when opening it from the stock entries page + +### Shopping list improvements/fixes +- The amount now defaults to `1` for adding items quicker +- Fixed that shopping list prints had a grey background (thanks @Forceu) +- Fixed the form validation on the shopping list item page (thanks @Forceu) + +### Recipe improvements/fixes +- Recipe printing improvements (thanks @Ape) +- Calories are now always displayed per single serving (on the recipe and meal plan page) +- Fixed that "Only check if any amount is in stock" (recipe ingredient option) didn't work for stock amounts < 1 + +### Chores fixes +- Fixed that tracking chores with "Done by" a different user was not possible + +### Userfield fixes +- Fixed that numeric Userfields were initialised with `1.0` + +### General & other improvements/fixes +- Some night mode style improvements (thanks @BlizzWave and @KTibow) +- Fixed that the number picker up/down buttons did not work when the input field was empty or contained an invalid number + +### API fixes +- Fixed that due soon products with `due_type` = "Expiration date" were missing in `due_products` of the `/stock/volatile` endpoint diff --git a/composer.json b/composer.json index 8f3f6abd..5313a982 100644 --- a/composer.json +++ b/composer.json @@ -25,5 +25,8 @@ "files": [ "helpers/extensions.php" ] + }, + "config": { + "platform-check": false } } diff --git a/config-dist.php b/config-dist.php index 37411082..9c28d1e8 100644 --- a/config-dist.php +++ b/config-dist.php @@ -124,7 +124,7 @@ DefaultUserSetting('stock_default_consume_amount_use_quick_consume_amount', fals DefaultUserSetting('scan_mode_consume_enabled', false); DefaultUserSetting('scan_mode_purchase_enabled', false); DefaultUserSetting('show_icon_on_stock_overview_page_when_product_is_on_shopping_list', true); -DefaultUserSetting('show_purchased_date_on_purchase', false); // Wheter the purchased date should be editable on purchase (defaults to today otherwise) +DefaultUserSetting('show_purchased_date_on_purchase', false); // Whether the purchased date should be editable on purchase (defaults to today otherwise) DefaultUserSetting('show_warning_on_purchase_when_due_date_is_earlier_than_next', true); // Show a warning on purchase when the due date of the purchased product is earlier than the next due date in stock // Shopping list settings @@ -201,4 +201,4 @@ Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true); // Feature settings Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true, opened items will be counted as missing for calculating if a product is below its minimum stock amount -Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automaticaly (if the device has one) +Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automatically (if the device has one) diff --git a/controllers/BaseController.php b/controllers/BaseController.php index f51f6109..f50cff07 100644 --- a/controllers/BaseController.php +++ b/controllers/BaseController.php @@ -157,18 +157,18 @@ class BaseController if (GROCY_AUTHENTICATED) { $this->View->set('permissions', User::PermissionList()); - } - $decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts')); - if ($decimalPlacesAmounts <= 0) - { - $defaultMinAmount = 1; + $decimalPlacesAmounts = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts')); + if ($decimalPlacesAmounts <= 0) + { + $defaultMinAmount = 1; + } + else + { + $defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1'; + } + $this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount); } - else - { - $defaultMinAmount = '0.' . str_repeat('0', $decimalPlacesAmounts - 1) . '1'; - } - $this->View->set('DEFAULT_MIN_AMOUNT', $defaultMinAmount); return $this->View->render($response, $page, $data); } diff --git a/controllers/ChoresApiController.php b/controllers/ChoresApiController.php index fb8d51fe..d9396467 100644 --- a/controllers/ChoresApiController.php +++ b/controllers/ChoresApiController.php @@ -81,7 +81,7 @@ class ChoresApiController extends BaseApiController if ($doneBy != GROCY_USER_ID) { - User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION); + User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION); } $choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy); diff --git a/helpers/extensions.php b/helpers/extensions.php index 73296a3a..c17f6ab7 100644 --- a/helpers/extensions.php +++ b/helpers/extensions.php @@ -156,6 +156,11 @@ function BoolToString(bool $bool) return $bool ? 'true' : 'false'; } +function BoolToInt(bool $bool) +{ + return $bool ? 1 : 0; +} + function ExternalSettingValue(string $value) { $tvalue = rtrim($value, "\r\n"); diff --git a/middleware/AuthMiddleware.php b/middleware/AuthMiddleware.php index e0d1f2f1..3b3d75f0 100644 --- a/middleware/AuthMiddleware.php +++ b/middleware/AuthMiddleware.php @@ -87,7 +87,7 @@ abstract class AuthMiddleware extends BaseMiddleware /** * @param array $postParams * @return bool True/False if the provided credentials were valid - * @throws \Exception Throws an \Exception if an error happended during credentials processing or if this AuthMiddleware doesn't provide credentials processing (e. g. handles this externally) + * @throws \Exception Throws an \Exception if an error happened during credentials processing or if this AuthMiddleware doesn't provide credentials processing (e. g. handles this externally) */ abstract public static function ProcessLogin(array $postParams); diff --git a/migrations/0128.sql b/migrations/0128.sql new file mode 100644 index 00000000..9acb2bbd --- /dev/null +++ b/migrations/0128.sql @@ -0,0 +1,13 @@ +-- Duplicate product barcodes were most probably not created on purpose, +-- so just keep the newer one for any duplicates +DELETE FROM product_barcodes +WHERE id IN ( + SELECT MIN(id) + FROM product_barcodes + GROUP BY barcode + HAVING COUNT(*) > 1 +); + +CREATE UNIQUE INDEX ix_product_barcodes ON product_barcodes ( + barcode +); diff --git a/migrations/0129.sql b/migrations/0129.sql new file mode 100644 index 00000000..6e0a0864 --- /dev/null +++ b/migrations/0129.sql @@ -0,0 +1,99 @@ +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 r.base_servings * 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 0.00000001 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 0.00000001 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 r.base_servings * rnrr.base_servings*1.0 ELSE 1 END) END)) ELSE 0 END AS missing_amount, + IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock 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 * p.qu_factor_purchase_to_stock) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 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.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 r.base_servings * 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) * p.qu_factor_purchase_to_stock 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.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/css/grocy_night_mode.css b/public/css/grocy_night_mode.css index 18f76e4f..819efd8a 100644 --- a/public/css/grocy_night_mode.css +++ b/public/css/grocy_night_mode.css @@ -1,8 +1,12 @@ -body.night-mode { +body.night-mode { color: #c1c1c1; background-color: #333131; } +.night-mode .navbar-brand img { + filter: invert(0.9) hue-rotate(176deg); +} + .night-mode .table-info, .night-mode .table-info > td, .night-mode .table-info > th, @@ -12,7 +16,7 @@ } .night-mode .table { - color: #6c757d; + color: #c1c1c1; } .night-mode table.dataTable tr.dtrg-group td, @@ -24,6 +28,7 @@ .night-mode .btn, .night-mode .nav-link, .night-mode #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link::after, +.night-mode #mainNav.navbar-light .navbar-collapse .navbar-sidenav .nav-link-collapse::after, .night-mode .dropdown-item { color: #c1c1c1 !important; } @@ -32,6 +37,11 @@ border-color: #c1c1c1; } +.night-mode .btn-outline-info { + color: #1ed1ee !important; + border-color: #1ed1ee !important; +} + .night-mode .btn-info { color: #c1c1c1; background-color: #07373f; @@ -46,14 +56,14 @@ .night-mode .btn-danger { color: #c1c1c1; - background-color: #471116; - border-color: #471116; + background-color: #6f1b23; + border-color: #6f1b23; } .night-mode .btn-success { color: #c1c1c1; - background-color: #0d3a18; - border-color: #0d3a18; + background-color: #17642a; + border-color: #17642a; } .night-mode .btn-light, @@ -71,6 +81,10 @@ border: 1px solid #ced4da; } +.night-mode ::placeholder { + color: #b1bac4; +} + .night-mode .content-wrapper { background: #333131; } @@ -110,6 +124,11 @@ border-color: #80bdff; } +.night-mode select { + color: #ced4da; + background-color: #333131; +} + .night-mode .dropdown-item:focus, .night-mode .dropdown-item:hover { color: #16181b; @@ -139,13 +158,13 @@ } .night-mode a.discrete-link:hover { - color: #16354f !important; + color: #83c6ff !important; text-decoration: none !important; background-color: #333131; } .night-mode a.discrete-link:focus { - color: #3a0b0f !important; + color: #ffa8af !important; background-color: #333131; } @@ -204,7 +223,7 @@ } .night-mode .active-page { - box-shadow: inset 5px 0 0 #350a0f !important; + box-shadow: inset 5px 0 0 #ff7585 !important; background-color: #383838 !important; } @@ -241,3 +260,20 @@ .night-mode .dropdown-menu { background-color: #333131; } + +.night-mode .table-secondary td, +.night-mode .table-secondary th { + background-color: #4c4e50; +} + +.night-mode .secondary-message { + border-top-color: #4c4e50; +} + +.night-mode .normal-message { + border-top-color: #07373f; +} + +.night-mode .text-muted { + color: #8f9ba5 !important; +} diff --git a/public/viewjs/components/barcodescanner.js b/public/viewjs/components/barcodescanner.js index 6cdf9324..14678439 100644 --- a/public/viewjs/components/barcodescanner.js +++ b/public/viewjs/components/barcodescanner.js @@ -36,7 +36,7 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function() Grocy.Components.BarcodeScanner.TorchOn(track); } - // Reduce the height of the video, if it's heigher than then the viewport + // Reduce the height of the video, if it's higher than then the viewport if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted) { var bc = document.getElementById('barcodescanner-container'); diff --git a/public/viewjs/components/numberpicker.js b/public/viewjs/components/numberpicker.js index 803c71c9..249e2b85 100644 --- a/public/viewjs/components/numberpicker.js +++ b/public/viewjs/components/numberpicker.js @@ -1,7 +1,7 @@ $(".numberpicker-down-button").unbind('click').on("click", function() { var inputElement = $(this).parent().parent().find('input[type="number"]'); - inputElement.val(parseFloat(inputElement.val()) - 1); + inputElement.val(parseFloat(inputElement.val() || 1) - 1); inputElement.trigger('keyup'); inputElement.trigger('change'); }); @@ -9,7 +9,7 @@ $(".numberpicker-down-button").unbind('click').on("click", function() $(".numberpicker-up-button").unbind('click').on("click", function() { var inputElement = $(this).parent().parent().find('input[type="number"]'); - inputElement.val(parseFloat(inputElement.val()) + 1); + inputElement.val(parseFloat(inputElement.val() || 0) + 1); inputElement.trigger('keyup'); inputElement.trigger('change'); }); diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js index a94c55e1..fbce4707 100644 --- a/public/viewjs/consume.js +++ b/public/viewjs/consume.js @@ -278,6 +278,11 @@ $("#location_id").on('change', function(e) console.error(xhr); } ); + + if (document.getElementById("product_id").getAttribute("barcode") == "null") + { + ScanModeSubmit(); + } }, function(xhr) { @@ -382,10 +387,9 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) $(".input-group-productamountpicker").trigger("change"); Grocy.FrontendHelpers.ValidateForm('consume-form'); RefreshLocaleNumberInput(); + ScanModeSubmit(false); } } - - ScanModeSubmit(false); }, function(xhr) { @@ -393,10 +397,6 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) } ); } - else - { - ScanModeSubmit(); - } }, function(xhr) { @@ -625,6 +625,11 @@ var current_productDetails; function RefreshForm() { var productDetails = current_productDetails; + if (!productDetails) + { + return; + } + if (productDetails.product.enable_tare_weight_handling == 1) { $("#consume-exact-amount-group").removeClass("d-none"); @@ -663,12 +668,10 @@ function ScanModeSubmit(singleUnit = true) if (singleUnit) { $("#display_amount").val(1); + $(".input-group-productamountpicker").trigger("change"); } - RefreshLocaleNumberInput(); - $(".input-group-productamountpicker").trigger("change"); Grocy.FrontendHelpers.ValidateForm("consume-form"); - if (document.getElementById("consume-form").checkValidity() === true) { $('#save-consume-button').click(); diff --git a/public/viewjs/shoppinglist.js b/public/viewjs/shoppinglist.js index e0a2c17b..cbc2d9c2 100644 --- a/public/viewjs/shoppinglist.js +++ b/public/viewjs/shoppinglist.js @@ -449,6 +449,7 @@ $(document).on("click", "#print-shopping-list-button", function(e) callback: function() { bootbox.hideAll(); + $('.modal-backdrop').remove(); $(".print-timestamp").text(moment().format("l LT")); @@ -470,6 +471,7 @@ $(document).on("click", "#print-shopping-list-button", function(e) shoppingListPrintShadowTable.draw(); } + $(".print-layout-container").addClass("d-none"); $("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none"); window.print(); diff --git a/public/viewjs/shoppinglistitemform.js b/public/viewjs/shoppinglistitemform.js index fc8dacac..5a8fdc3f 100644 --- a/public/viewjs/shoppinglistitemform.js +++ b/public/viewjs/shoppinglistitemform.js @@ -162,6 +162,12 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id); } + if ($("#display_amount").val().toString().isEmpty()) + { + $("#display_amount").val(1); + $("#display_amount").trigger("change"); + } + $('#display_amount').focus(); Grocy.FrontendHelpers.ValidateForm('shoppinglist-form'); Grocy.ShoppingListItemFormInitialLoadDone = true; @@ -244,17 +250,14 @@ if (GetUriParam("embedded") !== undefined) } var eitherRequiredFields = $("#product_id,#product_id_text_input,#note"); -eitherRequiredFields.on("input", function() +eitherRequiredFields.prop('required', ""); +eitherRequiredFields.on('input', function() { - eitherRequiredFields.attr("required", ""); - if (!$(this).val().isEmpty()) - { - eitherRequiredFields.not(this).removeAttr("required"); - } - + eitherRequiredFields.not(this).prop('required', !$(this).val().length); Grocy.FrontendHelpers.ValidateForm('shoppinglist-form'); }); + if (GetUriParam("product-name") != null) { Grocy.Components.ProductPicker.GetPicker().trigger('change'); diff --git a/public/viewjs/stockoverview.js b/public/viewjs/stockoverview.js index 284d6a8a..8316d182 100755 --- a/public/viewjs/stockoverview.js +++ b/public/viewjs/stockoverview.js @@ -229,12 +229,6 @@ function RefreshStatistics() Grocy.Api.Get('stock', function(result) { - var amountSum = 0; - result.forEach(element => - { - amountSum += parseInt(element.amount); - }); - if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) { $("#info-current-stock").text(__n(result.length, '%s Product', '%s Products')); @@ -244,7 +238,7 @@ function RefreshStatistics() var valueSum = 0; result.forEach(element => { - valueSum += parseInt(element.value); + valueSum += parseFloat(element.value); }); $("#info-current-stock").text(__n(result.length, '%s Product', '%s Products') + ", " + __t('%s total value', valueSum.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency }))); } diff --git a/public/viewjs/transfer.js b/public/viewjs/transfer.js index b679c7c6..2651ce37 100644 --- a/public/viewjs/transfer.js +++ b/public/viewjs/transfer.js @@ -126,12 +126,15 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) { $("#specific_stock_entry").find("option").remove().end().append(""); - if ($("#use_specific_stock_entry").is(":checked")) + if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null) { $("#use_specific_stock_entry").click(); } $("#location_id_to").val(""); - $("#location_id_from").val(""); + if (GetUriParam("stockId") == null) + { + $("#location_id_from").val(""); + } var productId = $(e.target).val(); @@ -185,6 +188,12 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) $("#location_id_from").trigger('change'); } }); + + if (GetUriParam("locationId") != null) + { + $("#location_id_from").val(GetUriParam("locationId")); + $("#location_id_from").trigger("change"); + } }, function(xhr) { @@ -284,7 +293,7 @@ $("#location_id_from").on('change', function(e) } $("#specific_stock_entry").find("option").remove().end().append(""); - if ($("#use_specific_stock_entry").is(":checked")) + if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null) { $("#use_specific_stock_entry").click(); } @@ -475,6 +484,7 @@ if (GetUriParam("embedded") !== undefined) $("#location_id_from").trigger('change'); $("#use_specific_stock_entry").click(); $("#use_specific_stock_entry").trigger('change'); + Grocy.Components.ProductPicker.GetPicker().trigger('change'); } } diff --git a/services/StockService.php b/services/StockService.php index c60d424c..2bb41f6f 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -431,11 +431,11 @@ class StockService extends BaseService $openedDate = $stockRow->opened_date; - if ($open && $openedDate == null) + if (boolval($open) && $openedDate == null) { $openedDate = date('Y-m-d'); } - elseif (!$open) + elseif (!boolval($open)) { $openedDate = null; } @@ -447,7 +447,7 @@ class StockService extends BaseService 'location_id' => $locationId, 'shopping_location_id' => $shoppingLocationId, 'opened_date' => $openedDate, - 'open' => $open, + 'open' => BoolToInt($open), 'purchased_date' => $purchasedDate ]); @@ -549,7 +549,6 @@ class StockService extends BaseService { $currentStock = $this->GetCurrentStock(false); $currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d 23:59:59', strtotime("+$days days")), '<'); - $currentStock = FindAllObjectsInArrayByPropertyValue($currentStock, 'due_type', 1); if ($excludeOverdue) { @@ -995,8 +994,8 @@ class StockService extends BaseService if ($productDetails->product->enable_tare_weight_handling == 1) { - // Hard fail for now, as we not yet support transfering tare weight enabled products - throw new \Exception('Transfering tare weight enabled products is not yet possible'); + // Hard fail for now, as we not yet support transferring tare weight enabled products + throw new \Exception('Transferring tare weight enabled products is not yet possible'); if ($amount < floatval($productDetails->product->tare_weight)) { throw new \Exception('The amount cannot be lower than the defined tare weight'); @@ -1010,7 +1009,7 @@ class StockService extends BaseService if ($amount > $productStockAmountAtFromLocation) { - throw new \Exception('Amount to be transfered cannot be > current stock amount at the source location'); + throw new \Exception('Amount to be transferred cannot be > current stock amount at the source location'); } if ($specificStockEntryId !== 'default') @@ -1141,7 +1140,7 @@ class StockService extends BaseService 'amount' => $restStockAmount ]); - // The transfered amount gets into a new stock entry + // The transferred amount gets into a new stock entry $stockEntryNew = $this->getDatabase()->stock()->createRow([ 'product_id' => $stockEntry->product_id, 'amount' => $amount, diff --git a/views/components/userfieldsform.blade.php b/views/components/userfieldsform.blade.php index bc250c87..73809fbe 100644 --- a/views/components/userfieldsform.blade.php +++ b/views/components/userfieldsform.blade.php @@ -34,7 +34,8 @@ 'min' => 0, 'isRequired' => false, 'additionalCssClasses' => 'userfield-input', - 'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"' + 'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"', + 'value' => '' )) @elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_DECIMAL_NUMBER) @include('components.numberpicker', array( @@ -45,7 +46,8 @@ 'decimals' => 4, 'isRequired' => false, 'additionalCssClasses' => 'userfield-input', - 'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"' + 'additionalAttributes' => 'data-userfield-name="' . $userfield->name . '"', + 'value' => '' )) @elseif($userfield->type == \Grocy\Services\UserfieldsService::USERFIELD_TYPE_DATE) @include('components.datetimepicker', array( diff --git a/views/inventory.blade.php b/views/inventory.blade.php index 2a94fd3d..5c33f19b 100644 --- a/views/inventory.blade.php +++ b/views/inventory.blade.php @@ -40,7 +40,7 @@ 'id' => 'purchased_date', 'label' => 'Purchased date', 'format' => 'YYYY-MM-DD', - 'hint' => 'This will apply to added products', + 'hint' => $__t('This will apply to added products'), 'initWithNow' => true, 'limitEndToNow' => false, 'limitStartToNow' => false, @@ -61,7 +61,7 @@ @include('components.datetimepicker', array( 'id' => 'best_before_date', 'label' => 'Due date', - 'hint' => 'This will apply to added products', + 'hint' => $__t('This will apply to added products'), 'format' => 'YYYY-MM-DD', 'initWithNow' => false, 'limitEndToNow' => false, diff --git a/views/productform.blade.php b/views/productform.blade.php index 76699f7c..cf4309d8 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -688,7 +688,7 @@ @endif - {!! $__t('This means 1 %1$s is the same as %2$s %3$s', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->from_qu_id)->name, '' . $quConversion->factor . '', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name) !!} + {!! $__t('This means 1 %1$s is the same as %2$s %3$s', FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->from_qu_id)->name, '' . $quConversion->factor . '', $__n($quConversion->factor, FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $quConversion->to_qu_id)->name_plural)) !!} @endif diff --git a/views/recipes.blade.php b/views/recipes.blade.php index 48f04547..3b46c0f0 100644 --- a/views/recipes.blade.php +++ b/views/recipes.blade.php @@ -311,16 +311,20 @@
@if(!empty($calories) && intval($calories) > 0)
- +   +

{{ $calories }}

@endif @if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
- - +

{{ $costs }}

@endif @@ -410,8 +414,10 @@ @endif {{ $__n($selectedRecipePosition->recipe_amount, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityUnits, 'id', $selectedRecipePosition->qu_id)->name_plural) }} {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name }} @if(GROCY_FEATURE_FLAG_STOCK) - @if($selectedRecipePosition->need_fulfilled == 1)@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)@else@endif - @if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list', round($selectedRecipePosition->missing_amount, 2), round($selectedRecipePosition->amount_on_shopping_list, 2)) }} @endif + + @if($selectedRecipePosition->need_fulfilled == 1)@elseif($selectedRecipePosition->need_fulfilled_with_shopping_list == 1)@else@endif + @if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock (not included in costs), %1$s missing, %2$s already on shopping list', round($selectedRecipePosition->missing_amount, 2), round($selectedRecipePosition->amount_on_shopping_list, 2)) }} @endif + @endif @if($selectedRecipePosition->need_fulfilled == 1 && GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {{ $selectedRecipePosition->costs }} @endif {{ $selectedRecipePosition->calories }} {{ $__t('Calories') }} diff --git a/views/shoppinglist.blade.php b/views/shoppinglist.blade.php index d6fe522c..22d1123c 100644 --- a/views/shoppinglist.blade.php +++ b/views/shoppinglist.blade.php @@ -339,7 +339,7 @@
-