diff --git a/changelog/60_UNRELEASED_2020-xx-xx.md b/changelog/60_UNRELEASED_2020-xx-xx.md index ffef84ce..1daaaabd 100644 --- a/changelog/60_UNRELEASED_2020-xx-xx.md +++ b/changelog/60_UNRELEASED_2020-xx-xx.md @@ -43,6 +43,8 @@ - When clicking the product name on the shopping list, the product card will now be displayed (like on the stock overview page) (thanks @kriddles) - On the product card there is now also a button to jump directly to the stock entries of the corresponding product (thanks @kriddles) - The product picker workflows can now also be started by `ENTER` (additionally to `TAB`) +- Added a grouped/summarized stock journal (new button "Journal summary" at the top of the stock journal page) (thanks @fipwmaqzufheoxq92ebc) + - Provides an overview of summarized transactions per product, transaction type and user + summarized amount - Fixed that changing the products "Factor purchase to stock quantity unit" not longer messes up historical prices (which results for example in wrong recipe costs) (thanks @kriddles) - Fixed that when adding products through a product picker workflow and when the created products contains special characters, the product was not preselected on the previous page (thanks @Forceu) - Fixed that when editing a product the default store was not visible / always empty regardless if the product had one set (thanks @kriddles) diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 3c935d66..7bbd0c51 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -414,35 +414,30 @@ class StockApiController extends BaseApiController } $bestBeforeDate = null; - if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date'])) { $bestBeforeDate = $requestBody['best_before_date']; } $purchasedDate = null; - if (array_key_exists('purchased_date', $requestBody) && IsIsoDate($requestBody['purchased_date'])) { - $bestBeforeDate = $requestBody['purchased_date']; + $purchasedDate = $requestBody['purchased_date']; } $locationId = null; - if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id'])) { $locationId = $requestBody['location_id']; } $price = null; - if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price'])) { $price = $requestBody['price']; } $shoppingLocationId = null; - if (array_key_exists('shopping_location_id', $requestBody) && is_numeric($requestBody['shopping_location_id'])) { $shoppingLocationId = $requestBody['shopping_location_id']; diff --git a/grocy.openapi.json b/grocy.openapi.json index af6608b0..737876ce 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1233,7 +1233,7 @@ "price": { "type": "number", "format": "number", - "description": "The price per purchase quantity unit in configured currency" + "description": "The price per stock quantity unit in configured currency" }, "open": { "type": "boolean", @@ -1549,7 +1549,7 @@ "price": { "type": "number", "format": "number", - "description": "The price per purchase quantity unit in configured currency" + "description": "The price per stock quantity unit in configured currency" }, "location_id": { "type": "number", @@ -1977,7 +1977,7 @@ "price": { "type": "number", "format": "number", - "description": "The price per purchase quantity unit in configured currency" + "description": "The price per stock quantity unit in configured currency" }, "location_id": { "type": "number", diff --git a/localization/strings.pot b/localization/strings.pot index a8383021..7260462f 100644 --- a/localization/strings.pot +++ b/localization/strings.pot @@ -394,15 +394,6 @@ msgstr "" msgid "All" msgstr "" -msgid "Track charge cycle of battery %s" -msgstr "" - -msgid "Track execution of chore %s" -msgstr "" - -msgid "Filter by location" -msgstr "" - msgid "Search" msgstr "" @@ -561,9 +552,6 @@ msgstr "" msgid "Unknown" msgstr "" -msgid "Filter by chore" -msgstr "" - msgid "Chores journal" msgstr "" @@ -585,9 +573,6 @@ msgstr "" msgid "Price" msgstr "" -msgid "in %s per purchase quantity unit" -msgstr "" - msgid "The price cannot be lower than %s" msgstr "" @@ -824,9 +809,6 @@ msgstr "" msgid "No picture available" msgstr "" -msgid "Filter by product group" -msgstr "" - msgid "Presets for new products" msgstr "" @@ -854,27 +836,12 @@ msgstr "" msgid "Stock journal" msgstr "" -msgid "Filter by product" -msgstr "" - -msgid "Booking time" -msgstr "" - -msgid "Booking type" -msgstr "" - -msgid "Undo booking" -msgstr "" - msgid "Undone on" msgstr "" msgid "Batteries journal" msgstr "" -msgid "Filter by battery" -msgstr "" - msgid "Undo charge cycle" msgstr "" @@ -1034,21 +1001,6 @@ msgstr "" msgid "The current picture will be deleted when you save the recipe" msgstr "" -msgid "Show product details" -msgstr "" - -msgid "Stock journal for this product" -msgstr "" - -msgid "Show chore details" -msgstr "" - -msgid "Journal for this chore" -msgstr "" - -msgid "Show battery details" -msgstr "" - msgid "Journal for this battery" msgstr "" @@ -1145,9 +1097,6 @@ msgstr "" msgid "Userfields" msgstr "" -msgid "Filter by entity" -msgstr "" - msgid "Entity" msgstr "" @@ -1586,9 +1535,6 @@ msgstr "" msgid "Transfered %1$s of %2$s from %3$s to %4$s" msgstr "" -msgid "Show stock entries" -msgstr "" - msgid "Stock entries" msgstr "" @@ -1945,3 +1891,48 @@ msgstr "" msgid "Hide/view columns" msgstr "" + +msgid "This product is currently on a shopping list" +msgstr "" + +msgid "Undo transaction" +msgstr "" + +msgid "Transaction type" +msgstr "" + +msgid "Transaction time" +msgstr "" + +msgid "Edit this timee" +msgstr "" + +msgid "Delete this tiem" +msgstr "" + +msgid "Chore journal" +msgstr "" + +msgid "Track chore execution" +msgstr "" + +msgid "Mark task as completed" +msgstr "" + +msgid "Track charge cycle" +msgstr "" + +msgid "Battery journal" +msgstr "" + +msgid "This product has a picture" +msgstr "" + +msgid "Consume this stock entry as spoiled" +msgstr "" + +msgid "Configure user permissions" +msgstr "" + +msgid "Show a QR-Code for this API key" +msgstr "" diff --git a/public/js/grocy.js b/public/js/grocy.js index 2be5b6e5..ed57ac90 100644 --- a/public/js/grocy.js +++ b/public/js/grocy.js @@ -764,6 +764,8 @@ $(window).on("message", function(e) $(".change-table-columns-visibility-button").on("click", function(e) { + e.preventDefault(); + var dataTableSelector = $(e.currentTarget).attr("data-table-selector"); var dataTable = $(dataTableSelector).DataTable(); diff --git a/public/viewjs/choreform.js b/public/viewjs/choreform.js index bbad9f8d..ec054827 100644 --- a/public/viewjs/choreform.js +++ b/public/viewjs/choreform.js @@ -122,12 +122,12 @@ $('.input-group-chore-period-type').on('change', function(e) $(".period-type-input").addClass("d-none"); $(".period-type-" + periodType).removeClass("d-none"); - $('#chore-period-type-info').attr("title", ""); + $('#chore-period-type-info').attr("data-original-title", ""); $("#period_config").val(""); if (periodType === 'manually') { - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is not scheduled')); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is not scheduled')); } else if (periodType === 'dynamic-regular') { @@ -135,32 +135,32 @@ $('.input-group-chore-period-type').on('change', function(e) $("#period_days").attr("min", "0"); $("#period_days").attr("max", "9999"); $("#period_days").parent().find(".invalid-feedback").text(__t('This cannot be negative')); - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is scheduled %s days after the last execution', periodDays.toString())); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled %s days after the last execution', periodDays.toString())); } else if (periodType === 'daily') { - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is scheduled 1 day after the last execution')); - $('#chore-period-interval-info').attr("title", __t('This means the next execution of this chore should only be scheduled every %s days', periodInterval.toString())); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled 1 day after the last execution')); + $('#chore-period-interval-info').attr("data-original-title", __t('This means the next execution of this chore should only be scheduled every %s days', periodInterval.toString())); } else if (periodType === 'weekly') { - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is scheduled 1 day after the last execution, but only for the weekdays selected below')); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled 1 day after the last execution, but only for the weekdays selected below')); $("#period_config").val($(".period-type-weekly input:checkbox:checked").map(function() { return this.value; }).get().join(",")); - $('#chore-period-interval-info').attr("title", __t('This means the next execution of this chore should only be scheduled every %s weeks', periodInterval.toString())); + $('#chore-period-interval-info').attr("data-original-title", __t('This means the next execution of this chore should only be scheduled every %s weeks', periodInterval.toString())); } else if (periodType === 'monthly') { - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is scheduled on the below selected day of each month')); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled on the below selected day of each month')); $("label[for='period_days']").text(__t("Day of month")); $("#period_days").attr("min", "1"); $("#period_days").attr("max", "31"); $("#period_days").parent().find(".invalid-feedback").text(__t('The amount must be between %1$s and %2$s', "1", "31")); - $('#chore-period-interval-info').attr("title", __t('This means the next execution of this chore should only be scheduled every %s months', periodInterval.toString())); + $('#chore-period-interval-info').attr("data-original-title", __t('This means the next execution of this chore should only be scheduled every %s months', periodInterval.toString())); } else if (periodType === 'yearly') { - $('#chore-period-type-info').attr("title", __t('This means the next execution of this chore is scheduled 1 year after the last execution')); - $('#chore-period-interval-info').attr("title", __t('This means the next execution of this chore should only be scheduled every %s years', periodInterval.toString())); + $('#chore-period-type-info').attr("data-original-title", __t('This means the next execution of this chore is scheduled 1 year after the last execution')); + $('#chore-period-interval-info').attr("data-original-title", __t('This means the next execution of this chore should only be scheduled every %s years', periodInterval.toString())); } Grocy.FrontendHelpers.ValidateForm('chore-form'); @@ -176,23 +176,23 @@ $('.input-group-chore-assignment-type').on('change', function(e) if (assignmentType === 'no-assignment') { - $('#chore-assignment-type-info').attr("title", __t('This means the next execution of this chore will not be assigned to anyone')); + $('#chore-assignment-type-info').attr("data-original-title", __t('This means the next execution of this chore will not be assigned to anyone')); } else if (assignmentType === 'who-least-did-first') { - $('#chore-assignment-type-info').attr("title", __t('This means the next execution of this chore will be assigned to the one who executed it least')); + $('#chore-assignment-type-info').attr("data-original-title", __t('This means the next execution of this chore will be assigned to the one who executed it least')); $("#assignment_config").attr("required", ""); $("#assignment_config").removeAttr("disabled"); } else if (assignmentType === 'random') { - $('#chore-assignment-type-info').attr("title", __t('This means the next execution of this chore will be assigned randomly')); + $('#chore-assignment-type-info').attr("data-original-title", __t('This means the next execution of this chore will be assigned randomly')); $("#assignment_config").attr("required", ""); $("#assignment_config").removeAttr("disabled"); } else if (assignmentType === 'in-alphabetical-order') { - $('#chore-assignment-type-info').attr("title", __t('This means the next execution of this chore will be assigned to the next one in alphabetical order')); + $('#chore-assignment-type-info').attr("data-original-title", __t('This means the next execution of this chore will be assigned to the next one in alphabetical order')); $("#assignment_config").attr("required", ""); $("#assignment_config").removeAttr("disabled"); } diff --git a/public/viewjs/components/numberpicker.js b/public/viewjs/components/numberpicker.js index d66f870b..bd7afd33 100644 --- a/public/viewjs/components/numberpicker.js +++ b/public/viewjs/components/numberpicker.js @@ -16,7 +16,7 @@ $(".numberpicker-up-button").unbind('click').on("click", function() $(".numberpicker").on("keyup", function() { - if ($(this).data("not-equal") && !$(this).data("not-equal").toString().isEmpty() && $(this).data("not-equal") == $(this).val()) + if ($(this).attr("data-not-equal") && !$(this).attr("data-not-equal").toString().isEmpty() && $(this).attr("data-not-equal") == $(this).val()) { $(this)[0].setCustomValidity("error"); } diff --git a/public/viewjs/components/productamountpicker.js b/public/viewjs/components/productamountpicker.js index 0967443f..9ab43ac0 100644 --- a/public/viewjs/components/productamountpicker.js +++ b/public/viewjs/components/productamountpicker.js @@ -94,7 +94,7 @@ $(".input-group-productamountpicker").on("change", function() var destinationAmount = amount / quFactor; var destinationQuName = __n(destinationAmount, $("#qu_id").attr("data-destination-qu-name"), $("#qu_id").attr("data-destination-qu-name-plural")) - if (destinationQuName == selectedQuName || Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled || amount.toString().isEmpty() || selectedQuName.toString().isEmpty()) + if ($("#qu_id").attr("data-destination-qu-name") == selectedQuName || Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled || amount.toString().isEmpty() || selectedQuName.toString().isEmpty()) { $("#qu-conversion-info").addClass("d-none"); } diff --git a/public/viewjs/inventory.js b/public/viewjs/inventory.js index d41b3379..ca1b3774 100644 --- a/public/viewjs/inventory.js +++ b/public/viewjs/inventory.js @@ -134,7 +134,8 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id); Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id); - $('#display_amount').attr('data-not-equal', productDetails.stock_amount); + $('#display_amount').attr("data-stock-amount", productDetails.stock_amount) + $('#display_amount').attr('data-not-equal', productDetails.stock_amount * $("#qu_id option:selected").attr("data-qu-factor")); if (productDetails.product.allow_partial_units_in_stock == 1) { @@ -228,9 +229,12 @@ $('#inventory-form input').keydown(function(event) } }); -$('#display_amount').on('keypress', function(e) + +$('#qu_id').on('change', function(e) { - $('#display_amount').trigger('change'); + $('#display_amount').attr('data-not-equal', parseFloat($('#display_amount').attr('data-stock-amount')) * parseFloat($("#qu_id option:selected").attr("data-qu-factor"))); + $("#display_amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %1$s or equal %2$s', "0." + "0".repeat(parseInt(Grocy.UserSettings.stock_decimal_places_amounts) - 1) + "1", parseFloat($('#display_amount').attr('data-not-equal')).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }))); + Grocy.FrontendHelpers.ValidateForm('inventory-form'); }); Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e) @@ -246,7 +250,7 @@ Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) $('#display_amount').on('keyup', function(e) { var productId = Grocy.Components.ProductPicker.GetValue(); - var newAmount = parseInt($('#display_amount').val()); + var newAmount = parseInt($('#amount').val()); if (productId) { diff --git a/public/viewjs/mealplan.js b/public/viewjs/mealplan.js index 9f9dda22..c9265a6c 100644 --- a/public/viewjs/mealplan.js +++ b/public/viewjs/mealplan.js @@ -133,8 +133,8 @@ var calendar = $("#calendar").fullCalendar({