diff --git a/controllers/BatteriesApiController.php b/controllers/BatteriesApiController.php index 0029f04e..0a681473 100644 --- a/controllers/BatteriesApiController.php +++ b/controllers/BatteriesApiController.php @@ -27,7 +27,7 @@ class BatteriesApiController extends BaseApiController } $chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime); - return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId)); + return $this->ApiResponse($this->Database->battery_charge_cycles($chargeCycleId)); } catch (\Exception $ex) { diff --git a/controllers/ChoresApiController.php b/controllers/ChoresApiController.php index 16fd58b1..9805a107 100644 --- a/controllers/ChoresApiController.php +++ b/controllers/ChoresApiController.php @@ -33,7 +33,7 @@ class ChoresApiController extends BaseApiController } $choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy); - return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId)); + return $this->ApiResponse($this->Database->chores_log($choreExecutionId)); } catch (\Exception $ex) { diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 5f8ff54d..2469b06a 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -79,7 +79,7 @@ class StockApiController extends BaseApiController } $bookingId = $this->StockService->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price, $locationId); - return $this->ApiResponse(array('booking_id' => $bookingId)); + return $this->ApiResponse($this->Database->stock_log($bookingId)); } catch (\Exception $ex) { @@ -128,7 +128,7 @@ class StockApiController extends BaseApiController } $bookingId = $this->StockService->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId); - return $this->ApiResponse(array('booking_id' => $bookingId)); + return $this->ApiResponse($this->Database->stock_log($bookingId)); } catch (\Exception $ex) { @@ -159,7 +159,7 @@ class StockApiController extends BaseApiController } $bookingId = $this->StockService->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate); - return $this->ApiResponse(array('booking_id' => $bookingId)); + return $this->ApiResponse($this->Database->stock_log($bookingId)); } catch (\Exception $ex) { @@ -190,7 +190,7 @@ class StockApiController extends BaseApiController } $bookingId = $this->StockService->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId); - return $this->ApiResponse(array('booking_id' => $bookingId)); + return $this->ApiResponse($this->Database->stock_log($bookingId)); } catch (\Exception $ex) { diff --git a/grocy.openapi.json b/grocy.openapi.json index a9aac3d5..ae358859 100644 --- a/grocy.openapi.json +++ b/grocy.openapi.json @@ -1022,7 +1022,8 @@ "properties": { "amount": { "type": "number", - "format": "double" + "format": "double", + "description": "The amount to add - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight" }, "best_before_date": { "type": "string", @@ -1059,7 +1060,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockBookingResponse" + "$ref": "#/components/schemas/StockLogEntry" } } } @@ -1103,7 +1104,7 @@ "properties": { "amount": { "type": "double", - "description": "The amount to remove" + "description": "The amount to remove - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight" }, "transaction_type": { "$ref": "#/components/internalSchemas/StockTransactionType" @@ -1137,7 +1138,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockBookingResponse" + "$ref": "#/components/schemas/StockLogEntry" } } } @@ -1181,7 +1182,7 @@ "properties": { "new_amount": { "type": "integer", - "description": "The new current amount for the given product" + "description": "The new current amount for the given product - please note that when tare weight handling for the product is enabled, this needs to be the amount including the container weight (gross), the amount to be posted will be automatically calculated based on what is in stock and the defined tare weight" }, "best_before_date": { "type": "string", @@ -1199,7 +1200,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockBookingResponse" + "$ref": "#/components/schemas/StockLogEntry" } } } @@ -1263,7 +1264,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/StockBookingResponse" + "$ref": "#/components/schemas/StockLogEntry" } } } @@ -1568,12 +1569,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "chore_execution_id": { - "type": "integer" - } - } + "$ref": "#/components/schemas/ChoreLogEntry" } } } @@ -1729,12 +1725,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "charge_cycle_id": { - "type": "integer" - } - } + "$ref": "#/components/schemas/BatteryChargeCycleEntry" } } } @@ -2345,7 +2336,7 @@ } } }, - "BatteryChargeCycle": { + "BatteryChargeCycleEntry": { "type": "object", "properties": { "id": { @@ -2462,14 +2453,6 @@ "error_message": "The error message..." } }, - "StockBookingResponse": { - "type": "object", - "properties": { - "booking_id": { - "type": "integer" - } - } - }, "CurrentStockResponse": { "type": "object", "properties": { diff --git a/localization/en/strings.php b/localization/en/strings.php index f2e4c377..79aee3d6 100644 --- a/localization/en/strings.php +++ b/localization/en/strings.php @@ -335,5 +335,11 @@ return array( 'This is for statistical purposes only' => 'This is for statistical purposes only', 'You have to select a recipe' => 'You have to select a recipe', 'Key type' => 'Key type', - 'Export as iCal' => 'Export as iCal' + 'Export as iCal' => 'Export as iCal', + 'Allow partial units in stock' => 'Allow partial units in stock', + 'Enable tare weight handling' => 'Enable tare weight handling', + 'This is useful e.g. for flour in jars - on purchase/consume/inventory you always weigh the whole jar, the amount to be posted is then automatically calculated based on what is in stock and the tare weight defined below' => 'This is useful e.g. for flour in jars - on purchase/consume/inventory you always weigh the whole jar, the amount to be posted is then automatically calculated based on what is in stock and the tare weight defined below', + 'Tare weight' => 'Tare weight', + 'Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated' => 'Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated', + 'You have to select a location' => 'You have to select a location' ); diff --git a/migrations/0057.sql b/migrations/0057.sql new file mode 100644 index 00000000..491d3275 --- /dev/null +++ b/migrations/0057.sql @@ -0,0 +1,5 @@ +ALTER TABLE products +ADD enable_tare_weight_handling TINYINT NOT NULL DEFAULT 0; + +ALTER TABLE products +ADD tare_weight REAL NOT NULL DEFAULT 0; diff --git a/public/js/grocy.js b/public/js/grocy.js index 0a27099f..9f60a332 100644 --- a/public/js/grocy.js +++ b/public/js/grocy.js @@ -502,7 +502,7 @@ $("#about-dialog-link").on("click", function() }); }); -$(".local-number-format[data-format='currency']").each(function () +$(".locale-number-format[data-format='currency']").each(function () { - $(this).text(parseFloat($(this).text()).toLocaleString(undefined, { minimumFractionDigits: 2 })); + $(this).text(parseFloat($(this).text()).toLocaleString(undefined, { style: "currency", currency: Grocy.Currency })); }); diff --git a/public/viewjs/batterytracking.js b/public/viewjs/batterytracking.js index 6fe88df1..4f95bb1a 100644 --- a/public/viewjs/batterytracking.js +++ b/public/viewjs/batterytracking.js @@ -12,7 +12,7 @@ function(result) { Grocy.FrontendHelpers.EndUiBusy("batterytracking-form"); - toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()) + '
' + L("Undo") + ''); + toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()) + '
' + L("Undo") + ''); $('#battery_id').val(''); $('#battery_id_text_input').focus(); diff --git a/public/viewjs/choretracking.js b/public/viewjs/choretracking.js index 9776dd58..87378082 100644 --- a/public/viewjs/choretracking.js +++ b/public/viewjs/choretracking.js @@ -12,7 +12,7 @@ function(result) { Grocy.FrontendHelpers.EndUiBusy("choretracking-form"); - toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()) + '
' + L("Undo") + ''); + toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()) + '
' + L("Undo") + ''); $('#chore_id').val(''); $('#chore_id_text_input').focus(); diff --git a/public/viewjs/consume.js b/public/viewjs/consume.js index ab0c06f6..01661008 100644 --- a/public/viewjs/consume.js +++ b/public/viewjs/consume.js @@ -39,9 +39,14 @@ } Grocy.FrontendHelpers.EndUiBusy("consume-form"); - toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''); + toastr.success(L('Removed #1 #2 of #3 from stock', Math.abs(result.amount), Pluralize(Math.abs(result.amount), productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''); + $("#amount").attr("min", "1"); + $("#amount").attr("max", "999999"); + $("#amount").attr("step", "1"); + $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1')); $('#amount').val(1); + $("#tare-weight-handling-info").addClass("d-none"); Grocy.Components.ProductPicker.Clear(); if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_RECIPES) { @@ -100,7 +105,7 @@ $('#save-mark-as-open-button').on('click', function(e) } Grocy.FrontendHelpers.EndUiBusy("consume-form"); - toastr.success(L('Marked #1 #2 of #3 as opened', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''); + toastr.success(L('Marked #1 #2 of #3 as opened', jsonForm.amount, Pluralize(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''); $('#amount').val(1); Grocy.Components.ProductPicker.Clear(); @@ -146,13 +151,25 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) { $("#amount").attr("min", "0.01"); $("#amount").attr("step", "0.01"); - $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', 0.01.toLocaleString())); + $("#amount").parent().find(".invalid-feedback").text(L('The amount must be between #1 and #2', 0.01.toLocaleString(), parseFloat(productDetails.stock_amount).toLocaleString())); } else { $("#amount").attr("min", "1"); $("#amount").attr("step", "1"); - $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1')); + $("#amount").parent().find(".invalid-feedback").text(L('The amount must be between #1 and #2', "1", parseFloat(productDetails.stock_amount).toLocaleString())); + } + + if (productDetails.product.enable_tare_weight_handling == 1) + { + $("#amount").attr("min", productDetails.product.tare_weight); + $('#amount').attr('max', parseFloat(productDetails.stock_amount) + parseFloat(productDetails.product.tare_weight)); + $("#amount").parent().find(".invalid-feedback").text(L('The amount must be between #1 and #2', parseFloat(productDetails.product.tare_weight).toLocaleString(), (parseFloat(productDetails.stock_amount) + parseFloat(productDetails.product.tare_weight)).toLocaleString())); + $("#tare-weight-handling-info").removeClass("d-none"); + } + else + { + $("#tare-weight-handling-info").addClass("d-none"); } if ((productDetails.stock_amount || 0) === 0) diff --git a/public/viewjs/inventory.js b/public/viewjs/inventory.js index 75c42c65..2498d3fb 100644 --- a/public/viewjs/inventory.js +++ b/public/viewjs/inventory.js @@ -6,7 +6,7 @@ Grocy.FrontendHelpers.BeginUiBusy("inventory-form"); Grocy.Api.Get('stock/products/' + jsonForm.product_id, - function (productDetails) + function(productDetails) { var jsonData = { }; jsonData.new_amount = jsonForm.new_amount; @@ -38,7 +38,7 @@ } Grocy.FrontendHelpers.EndUiBusy("inventory-form"); - toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, Pluralize(jsonForm.new_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + '
' + L("Undo") + ''); + toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, productDetails.stock_amount, Pluralize(productDetails.stock_amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + '
' + L("Undo") + ''); if (addBarcode !== undefined) { @@ -47,6 +47,10 @@ else { $('#inventory-change-info').addClass('d-none'); + $("#tare-weight-handling-info").addClass("d-none"); + $("#new_amount").attr("min", "0"); + $("#new_amount").attr("step", "1"); + $("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '0')); $('#new_amount').val(''); Grocy.Components.DateTimePicker.Clear(); Grocy.Components.ProductPicker.SetValue(''); @@ -91,9 +95,20 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) } else { - $("#new_amount").attr("min", "1"); + $("#new_amount").attr("min", "0"); $("#new_amount").attr("step", "1"); - $("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1')); + $("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '0')); + } + + if (productDetails.product.enable_tare_weight_handling == 1) + { + $("#new_amount").attr("min", productDetails.product.tare_weight); + $("#new_amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', parseFloat(productDetails.product.tare_weight).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }))); + $("#tare-weight-handling-info").removeClass("d-none"); + } + else + { + $("#tare-weight-handling-info").addClass("d-none"); } $('#new_amount').focus(); @@ -177,26 +192,31 @@ $('#new_amount').on('keyup', function(e) Grocy.Api.Get('stock/products/' + productId, function(productDetails) { - var productStockAmount = parseInt(productDetails.stock_amount || '0'); + var productStockAmount = parseFloat(productDetails.stock_amount || parseFloat('0')); + + var containerWeight = parseFloat("0"); + if (productDetails.product.enable_tare_weight_handling == 1) + { + containerWeight = parseFloat(productDetails.product.tare_weight); + } - if (newAmount > productStockAmount) - { - var amountToAdd = newAmount - productDetails.stock_amount; - $('#inventory-change-info').text(L('This means #1 will be added to stock', amountToAdd.toString() + ' ' + productDetails.quantity_unit_stock.name)); - $('#inventory-change-info').removeClass('d-none'); - Grocy.Components.DateTimePicker.GetInputElement().attr('required', ''); - } - else if (newAmount < productStockAmount) - { - var amountToRemove = productStockAmount - newAmount; - $('#inventory-change-info').text(L('This means #1 will be removed from stock', amountToRemove.toString() + ' ' + productDetails.quantity_unit_stock.name)); - $('#inventory-change-info').removeClass('d-none'); - Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required'); - } - else + var estimatedBookingAmount = Math.abs(newAmount - productStockAmount - containerWeight); + $('#inventory-change-info').removeClass('d-none'); + + if (productDetails.product.enable_tare_weight_handling == 1 && newAmount < containerWeight) { $('#inventory-change-info').addClass('d-none'); } + else if (newAmount > productStockAmount + containerWeight) + { + $('#inventory-change-info').text(L('This means #1 will be added to stock', estimatedBookingAmount.toLocaleString() + ' ' + Pluralize(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural))); + Grocy.Components.DateTimePicker.GetInputElement().attr('required', ''); + } + else if (newAmount < productStockAmount + containerWeight) + { + $('#inventory-change-info').text(L('This means #1 will be removed from stock', estimatedBookingAmount.toLocaleString() + ' ' + Pluralize(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural))); + Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required'); + } Grocy.FrontendHelpers.ValidateForm('inventory-form'); }, diff --git a/public/viewjs/productform.js b/public/viewjs/productform.js index dc2d45a3..8a9b9711 100644 --- a/public/viewjs/productform.js +++ b/public/viewjs/productform.js @@ -169,6 +169,8 @@ $('.input-group-qu').on('change', function(e) $('#qu-conversion-info').addClass('d-none'); } + $("#tare_weight_qu_info").text($("#qu_id_stock option:selected").text()); + Grocy.FrontendHelpers.ValidateForm('product-form'); }); @@ -199,6 +201,20 @@ $('#product-form input').keydown(function(event) } }); +$("#enable_tare_weight_handling").on("click", function() +{ + if (this.checked) + { + $("#tare_weight").removeAttr("disabled"); + } + else + { + $("#tare_weight").attr("disabled", ""); + } + + Grocy.FrontendHelpers.ValidateForm("product-form"); +}); + Grocy.DeleteProductPictureOnSave = false; $('#delete-current-product-picture-button').on('click', function (e) { diff --git a/public/viewjs/purchase.js b/public/viewjs/purchase.js index 017bd7ef..b7b6ff8a 100644 --- a/public/viewjs/purchase.js +++ b/public/viewjs/purchase.js @@ -48,7 +48,7 @@ ); } - var successMessage = L('Added #1 #2 of #3 to stock', amount, Pluralize(amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''; + var successMessage = L('Added #1 #2 of #3 to stock', result.amount, Pluralize(result.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '
' + L("Undo") + ''; if (addBarcode !== undefined) { @@ -64,8 +64,13 @@ { Grocy.FrontendHelpers.EndUiBusy("purchase-form"); toastr.success(successMessage); + + $("#amount").attr("min", "1"); + $("#amount").attr("step", "1"); + $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1')); $('#amount').val(0); $('#price').val(''); + $("#tare-weight-handling-info").addClass("d-none"); Grocy.Components.LocationPicker.Clear(); Grocy.Components.DateTimePicker.Clear(); Grocy.Components.ProductPicker.SetValue(''); @@ -97,7 +102,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) Grocy.Components.ProductCard.Refresh(productId); Grocy.Api.Get('stock/products/' + productId, - function(productDetails) + function (productDetails) { $('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name); $('#price').val(productDetails.last_price); @@ -116,6 +121,18 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e) $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', '1')); } + if (productDetails.product.enable_tare_weight_handling == 1) + { + var minAmount = parseFloat(productDetails.product.tare_weight) + parseFloat(productDetails.stock_amount) + 1; + $("#amount").attr("min", minAmount); + $("#amount").parent().find(".invalid-feedback").text(L('The amount cannot be lower than #1', minAmount.toLocaleString())); + $("#tare-weight-handling-info").removeClass("d-none"); + } + else + { + $("#tare-weight-handling-info").addClass("d-none"); + } + if (productDetails.product.default_best_before_days.toString() !== '0') { if (productDetails.product.default_best_before_days == -1) diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index c8000785..52907578 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -63,7 +63,7 @@ class DemoDataGeneratorService extends BaseService INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Salami')}', 2, 3, 3, 1, 6); --18 INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Toast')}', 4, 5, 5, 1, 2); --19 INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Minced meat')}', 2, 3, 3, 1, 4); --20 - INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Flour')}', 2, 3, 3, 1, 3); --21 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id, enable_tare_weight_handling, tare_weight) VALUES ('{$localizationService->LocalizeForSqlString('Flour')}', 3, 8, 8, 1, 3, 1, 500); --21 INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->LocalizeForSqlString('Sugar')}', 3, 3, 3, 1, 3); --22 INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->LocalizeForSqlString('Milk')}', 2, 10, 10, 1); --23 @@ -191,15 +191,15 @@ class DemoDataGeneratorService extends BaseService $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-30 days')), $this->RandomPrice()); $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice()); $stockService->AddProduct(15, 1, date('Y-m-d', strtotime('-2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice()); - $stockService->AddProduct(21, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice()); - $stockService->AddProduct(21, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice()); + $stockService->AddProduct(21, 1500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice()); + $stockService->AddProduct(21, 2500, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice()); $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-10 days')), $this->RandomPrice()); $stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice()); $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-40 days')), $this->RandomPrice()); $stockService->AddProduct(23, 1, date('Y-m-d', strtotime('+2 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-50 days')), $this->RandomPrice()); $stockService->AddMissingProductsToShoppingList(); + $stockService->OpenProduct(3, 1); $stockService->OpenProduct(6, 1); - $stockService->OpenProduct(21, 1); $stockService->OpenProduct(22, 1); $choresService = new ChoresService(); diff --git a/services/StockService.php b/services/StockService.php index 843fedc5..8a4600f8 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -130,6 +130,20 @@ class StockService extends BaseService throw new \Exception('Product does not exist'); } + // Tare weight handling + // The given amount is the new total amount including the container weight (gross) + // The amount to be posted needs to be the given amount - stock amount - tare weight + $productDetails = (object)$this->GetProductDetails($productId); + if ($productDetails->product->enable_tare_weight_handling == 1) + { + if ($amount <= $productDetails->product->tare_weight + $productDetails->stock_amount) + { + throw new \Exception('The amount cannot be lower or equal than the defined tare weight + current stock amount'); + } + + $amount = $amount - $productDetails->stock_amount - $productDetails->product->tare_weight; + } + if ($transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) { $stockId = uniqid(); @@ -174,6 +188,20 @@ class StockService extends BaseService throw new \Exception('Product does not exist'); } + // Tare weight handling + // The given amount is the new total amount including the container weight (gross) + // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight + $productDetails = (object)$this->GetProductDetails($productId); + if ($productDetails->product->enable_tare_weight_handling == 1) + { + if ($amount < $productDetails->product->tare_weight) + { + throw new \Exception('The amount cannot be lower than the defined tare weight'); + } + + $amount = abs($amount - $productDetails->stock_amount - $productDetails->product->tare_weight); + } + if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) { $productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount'); @@ -258,19 +286,37 @@ class StockService extends BaseService { throw new \Exception('Product does not exist'); } - - $productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount'); - if ($newAmount > $productStockAmount) + $productDetails = (object)$this->GetProductDetails($productId); + + // Tare weight handling + // The given amount is the new total amount including the container weight (gross) + // So assume that the amount in stock is the amount also including the container weight + $containerWeight = 0; + if ($productDetails->product->enable_tare_weight_handling == 1) { - $productDetails = $this->GetProductDetails($productId); - $amountToAdd = $newAmount - $productStockAmount; - $this->AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION, date('Y-m-d'), $productDetails['last_price']); + $containerWeight = $productDetails->product->tare_weight; } - else if ($newAmount < $productStockAmount) + + if ($newAmount > $productDetails->stock_amount + $containerWeight) { - $amountToRemove = $productStockAmount - $newAmount; - $this->ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION); + $bookingAmount = $newAmount - $productDetails->stock_amount; + if ($productDetails->product->enable_tare_weight_handling == 1) + { + $bookingAmount = $newAmount; + } + + $this->AddProduct($productId, $bookingAmount, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION, date('Y-m-d'), $productDetails->last_price); + } + else if ($newAmount < $productDetails->stock_amount + $containerWeight) + { + $bookingAmount = $productDetails->stock_amount - $newAmount; + if ($productDetails->product->enable_tare_weight_handling == 1) + { + $bookingAmount = $newAmount; + } + + $this->ConsumeProduct($productId, $bookingAmount, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION); } return $this->Database->lastInsertId(); diff --git a/views/components/numberpicker.blade.php b/views/components/numberpicker.blade.php index 97ff57a9..ffdb131b 100644 --- a/views/components/numberpicker.blade.php +++ b/views/components/numberpicker.blade.php @@ -12,10 +12,11 @@ @php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp @php if(empty($additionalAttributes)) { $additionalAttributes = ''; } @endphp @php if(empty($additionalHtmlElements)) { $additionalHtmlElements = ''; } @endphp +@php if(empty($additionalHtmlContextHelp)) { $additionalHtmlContextHelp = ''; } @endphp @php if(!isset($isRequired)) { $isRequired = true; } @endphp
- +
diff --git a/views/consume.blade.php b/views/consume.blade.php index 421ba150..6de34d22 100644 --- a/views/consume.blade.php +++ b/views/consume.blade.php @@ -23,7 +23,8 @@ 'hintId' => 'amount_qu_unit', 'min' => 1, 'value' => 1, - 'invalidFeedback' => $L('The amount cannot be lower than #1', '1') + 'invalidFeedback' => $L('The amount cannot be lower than #1', '1'), + 'additionalHtmlContextHelp' => '
' . $L('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '
' ))
diff --git a/views/inventory.blade.php b/views/inventory.blade.php index 6f4d9bdc..7259e9d2 100644 --- a/views/inventory.blade.php +++ b/views/inventory.blade.php @@ -22,9 +22,10 @@ 'hintId' => 'new_amount_qu_unit', 'min' => 0, 'value' => 1, - 'invalidFeedback' => $L('The amount cannot be lower than #1', '1'), + 'invalidFeedback' => $L('The amount cannot be lower than #1', '0'), 'additionalAttributes' => 'data-notequal="notequal" not-equal="-1"', - 'additionalHtmlElements' => '
' + 'additionalHtmlElements' => '
', + 'additionalHtmlContextHelp' => '
' . $L('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '
' )) @include('components.datetimepicker', array( diff --git a/views/productform.blade.php b/views/productform.blade.php index 9a33e785..de1e97e5 100644 --- a/views/productform.blade.php +++ b/views/productform.blade.php @@ -134,6 +134,29 @@
+
+
+ + enable_tare_weight_handling == 1) checked @endif class="form-check-input" type="checkbox" id="enable_tare_weight_handling" name="enable_tare_weight_handling" value="1"> + +
+
+ + @php if($mode == 'edit') { $value = $product->tare_weight; } else { $value = 0; } @endphp + @php if(($mode == 'edit' && $product->enable_tare_weight_handling == 0) || $mode == 'create') { $additionalAttributes = 'disabled'; } else { $additionalAttributes = ''; } @endphp + @include('components.numberpicker', array( + 'id' => 'tare_weight', + 'label' => 'Tare weight', + 'min' => 0, + 'step' => 0.01, + 'value' => $value, + 'invalidFeedback' => $L('This cannot be lower than #1', '0'), + 'additionalAttributes' => $additionalAttributes, + 'hintId' => 'tare_weight_qu_info' + )) +

- {{ $totalRecipeCosts }} {{ GROCY_CURRENCY }} + {{ $totalRecipeCosts }} {{ GROCY_CURRENCY }}