From 2b8c672279349f313f22f289c83878651489a559 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Mon, 17 Apr 2017 16:51:49 +0200 Subject: [PATCH] Form validation and barcode input handling improvements --- GrocyDbMigrator.php | 6 +- GrocyDemoDataGenerator.php | 9 ++- GrocyLogicStock.php | 4 +- bower.json | 3 +- grocy.php | 14 ++++- style.css | 6 +- version.txt | 2 +- views/consumption.js | 71 ++++++++++++++++------ views/consumption.php | 18 ++---- views/dashboard.js | 5 +- views/layout.php | 48 ++++++++------- views/productform.js | 9 +++ views/purchase.js | 120 ++++++++++++++++++++++++++++++------- views/purchase.php | 30 ++++------ 14 files changed, 242 insertions(+), 103 deletions(-) diff --git a/GrocyDbMigrator.php b/GrocyDbMigrator.php index e99975e3..e0293963 100644 --- a/GrocyDbMigrator.php +++ b/GrocyDbMigrator.php @@ -72,7 +72,11 @@ class GrocyDbMigrator { if ($pdo->query("SELECT COUNT(*) FROM migrations WHERE migration = $migrationId")->fetchColumn() == 0) { - $pdo->exec(utf8_encode($sql)); + if ($pdo->exec(utf8_encode($sql)) === false) + { + throw new Exception($pdo->errorInfo()); + } + $pdo->exec('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')'); } } diff --git a/GrocyDemoDataGenerator.php b/GrocyDemoDataGenerator.php index 1ccea703..c6b801c0 100644 --- a/GrocyDemoDataGenerator.php +++ b/GrocyDemoDataGenerator.php @@ -4,7 +4,7 @@ class GrocyDemoDataGenerator { public static function PopulateDemoData(PDO $pdo) { - $pdo->exec(utf8_encode(" + $sql = " UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1; INSERT INTO locations (name) VALUES ('Süßigkeitenschrank'); INSERT INTO locations (name) VALUES ('Konvervenschrank'); @@ -19,6 +19,11 @@ class GrocyDemoDataGenerator INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (3, 5, date('now', '+180 day'), '".uniqid()."'); INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (4, 5, date('now', '+180 day'), '".uniqid()."'); INSERT INTO stock (product_id, amount, best_before_date, stock_id) VALUES (5, 5, date('now', '+25 day'), '".uniqid()."'); - ")); + "; + + if ($pdo->exec(utf8_encode($sql)) === false) + { + throw new Exception($pdo->errorInfo()); + } } } diff --git a/GrocyLogicStock.php b/GrocyLogicStock.php index cd729c30..97f38650 100644 --- a/GrocyLogicStock.php +++ b/GrocyLogicStock.php @@ -5,7 +5,7 @@ class GrocyLogicStock public static function GetCurrentStock() { $db = Grocy::GetDbConnectionRaw(); - return $db->query('SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date from stock GROUP BY product_id ORDER BY MIN(best_before_date) DESC')->fetchAll(PDO::FETCH_OBJ); + return $db->query('SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date from stock GROUP BY product_id ORDER BY MIN(best_before_date) ASC')->fetchAll(PDO::FETCH_OBJ); } public static function GetProductDetails(int $productId) @@ -63,7 +63,7 @@ class GrocyLogicStock $amount -= $stockEntry->amount; $stockEntry->delete(); } - else //Stock entry amount is > than need amount -> split the stock entry resp. update the amount + else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount { $consumptionRow = $db->consumptions()->createRow(array( 'product_id' => $stockEntry->product_id, diff --git a/bower.json b/bower.json index 8d2433e7..8eeeb40a 100644 --- a/bower.json +++ b/bower.json @@ -14,6 +14,7 @@ "datatables.net-bs": "2.1.1", "datatables.net-responsive": "2.1.1", "datatables.net-responsive-bs": "2.1.1", - "jquery-timeago": "1.5.4" + "jquery-timeago": "1.5.4", + "toastr": "2.1.3" } } diff --git a/grocy.php b/grocy.php index 4ffeaeae..ea363ee7 100644 --- a/grocy.php +++ b/grocy.php @@ -2,9 +2,7 @@ class Grocy { - private static $DbConnection; private static $DbConnectionRaw; - /** * @return PDO */ @@ -33,6 +31,7 @@ class Grocy return self::$DbConnectionRaw; } + private static $DbConnection; /** * @return LessQL\Database */ @@ -50,4 +49,15 @@ class Grocy { return file_exists(__DIR__ . '/data/demo.txt'); } + + private static $InstalledVersion; + public static function GetInstalledVersion() + { + if (self::$InstalledVersion == null) + { + self::$InstalledVersion = file_get_contents(__DIR__ . '/version.txt'); + } + + return self::$InstalledVersion; + } } diff --git a/style.css b/style.css index d2a0e0ca..eb1a3752 100644 --- a/style.css +++ b/style.css @@ -104,4 +104,8 @@ .timeago-contextual { font-style: italic; font-size: 0.8em; -} \ No newline at end of file +} + +.disabled { + pointer-events: none; +} diff --git a/version.txt b/version.txt index 341cf11f..9325c3cc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.2.0 \ No newline at end of file +0.3.0 \ No newline at end of file diff --git a/views/consumption.js b/views/consumption.js index 20cbde67..f0ea5660 100644 --- a/views/consumption.js +++ b/views/consumption.js @@ -10,14 +10,26 @@ spoiled = 1; } - Grocy.FetchJson('/api/stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled, - function(result) + Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id, + function (productDetails) { - $('#product_id_text_input').focus(); - $('#product_id_text_input').val(''); - $('#product_id_text_input').trigger('change'); - $('#amount').val(1); - $('#consumption-form').validator('validate'); + Grocy.FetchJson('/api/stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled, + function(result) + { + toastr.success('Removed ' + jsonForm.amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' from stock'); + + $('#amount').val(1); + $('#product_id').val(''); + $('#product_id_text_input').focus(); + $('#product_id_text_input').val(''); + $('#product_id_text_input').trigger('change'); + $('#consumption-form').validator('validate'); + }, + function(xhr) + { + console.error(xhr); + } + ); }, function(xhr) { @@ -47,6 +59,24 @@ $('#product_id').on('change', function(e) Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago'); Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago'); + + if ((productStatistics.stock_amount || 0) === 0) + { + $('#product_id').val(''); + $('#product_id_text_input').val(''); + $('#product_id_text_input').addClass('has-error'); + $('#product_id_text_input').parent('.input-group').addClass('has-error'); + $('#product_id_text_input').closest('.form-group').addClass('has-error'); + $('#product-error').text('This product is not in stock.'); + $('#product-error').show(); + } + else + { + $('#product_id_text_input').removeClass('has-error'); + $('#product_id_text_input').parent('.input-group').removeClass('has-error'); + $('#product_id_text_input').closest('.form-group').removeClass('has-error'); + $('#product-error').hide(); + } }, function(xhr) { @@ -58,23 +88,26 @@ $('#product_id').on('change', function(e) $(function() { - $('.datepicker').datepicker( - { - format: 'yyyy-mm-dd', - startDate: '-3d', - todayHighlight: true, - autoclose: true, - calendarWeeks: true, - orientation: 'bottom auto' - }); - $('.datepicker').val(moment().format('YYYY-MM-DD')); - $('.datepicker').trigger('change'); - $('.combobox').combobox({ appendId: '_text_input' }); + + $('#amount').val(1); + $('#product_id').val(''); $('#product_id_text_input').focus(); $('#product_id_text_input').val(''); $('#product_id_text_input').trigger('change'); $('#consumption-form').validator(); $('#consumption-form').validator('validate'); + + $('#consumption-form input').keydown(function(event) + { + if (event.keyCode === 13) //Enter + { + if ($('#consumption-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error + { + event.preventDefault(); + return false; + } + } + }); }); diff --git a/views/consumption.php b/views/consumption.php index 57b06296..6d237bec 100644 --- a/views/consumption.php +++ b/views/consumption.php @@ -1,22 +1,16 @@
-

Record consumption

+

Consumption

- - - + -
- -
- -
-
-
+
@@ -32,7 +26,7 @@
-
+

Product overview

Stock quantity unit:

diff --git a/views/dashboard.js b/views/dashboard.js index ff920ae3..ea4ea8cb 100644 --- a/views/dashboard.js +++ b/views/dashboard.js @@ -1,4 +1,7 @@ $(function() { - $('#current-stock-table').DataTable({ 'paging': false }); + $('#current-stock-table').DataTable({ + 'paging': false, + 'order': [[2, 'asc']] + }); }); diff --git a/views/layout.php b/views/layout.php index 6e70c025..6dc5f675 100644 --- a/views/layout.php +++ b/views/layout.php @@ -11,16 +11,17 @@ <?php echo $title; ?> | grocy - - - - - - - + + + + + + + + - - + + @@ -93,7 +94,7 @@
Created with passion since 2017
- Version + Version
Life runs on code
@@ -108,21 +109,22 @@
- - - - - - - - - - - - + + + + + + + + + + + + + - + diff --git a/views/productform.js b/views/productform.js index d423ec11..a0a9c6a7 100644 --- a/views/productform.js +++ b/views/productform.js @@ -36,3 +36,12 @@ $(function() $('#product-form').validator(); $('#product-form').validator('validate'); }); + +$('#barcode').keydown(function(event) +{ + if (event.keyCode === 13) //Enter + { + event.preventDefault(); + return false; + } +}); diff --git a/views/purchase.js b/views/purchase.js index 773ccf47..9b252bf1 100644 --- a/views/purchase.js +++ b/views/purchase.js @@ -3,26 +3,28 @@ e.preventDefault(); var jsonForm = $('#purchase-form').serializeJSON(); - delete jsonForm.barcode; - Grocy.FetchJson('/api/get-object/products/' + jsonForm.product_id, - function(product) + Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id, + function (productDetails) { - jsonForm.amount = jsonForm.amount * product.qu_factor_purchase_to_stock; + jsonForm.amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock; Grocy.FetchJson('/api/helper/uniqid', - function (uniqidResponse) + function(uniqidResponse) { - jsonForm.amount = jsonForm.amount * product.qu_factor_purchase_to_stock; jsonForm.stock_id = uniqidResponse.uniqid; Grocy.PostJson('/api/add-object/stock', jsonForm, function(result) { + toastr.success('Added ' + jsonForm.amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' to stock'); + + $('#amount').val(1); + $('#best_before_date').val(''); + $('#product_id').val(''); $('#product_id_text_input').focus(); $('#product_id_text_input').val(''); $('#product_id_text_input').trigger('change'); - $('#amount').val(1); $('#purchase-form').validator('validate'); }, function(xhr) @@ -51,16 +53,16 @@ $('#product_id').on('change', function(e) if (productId) { Grocy.FetchJson('/api/stock/get-product-details/' + productId, - function(productStatistics) + function(productDetails) { - $('#selected-product-name').text(productStatistics.product.name); - $('#selected-product-stock-amount').text(productStatistics.stock_amount || '0'); - $('#selected-product-stock-qu-name').text(productStatistics.quantity_unit_stock.name); - $('#selected-product-purchase-qu-name').text(productStatistics.quantity_unit_purchase.name); - $('#selected-product-last-purchased').text((productStatistics.last_purchased || 'never').substring(0, 10)); - $('#selected-product-last-purchased-timeago').text($.timeago(productStatistics.last_purchased || '')); - $('#selected-product-last-used').text((productStatistics.last_used || 'never').substring(0, 10)); - $('#selected-product-last-used-timeago').text($.timeago(productStatistics.last_used || '')); + $('#selected-product-name').text(productDetails.product.name); + $('#selected-product-stock-amount').text(productDetails.stock_amount || '0'); + $('#selected-product-stock-qu-name').text(productDetails.quantity_unit_stock.name); + $('#selected-product-purchase-qu-name').text(productDetails.quantity_unit_purchase.name); + $('#selected-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10)); + $('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || '')); + $('#selected-product-last-used').text((productDetails.last_used || 'never').substring(0, 10)); + $('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || '')); Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago'); Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago'); @@ -78,20 +80,98 @@ $(function() $('.datepicker').datepicker( { format: 'yyyy-mm-dd', - startDate: '+7d', + startDate: '+0d', todayHighlight: true, autoclose: true, calendarWeeks: true, - orientation: 'bottom auto' + orientation: 'bottom auto', + weekStart: 1, + showOnFocus: false }); - $('.datepicker').val(moment().format('YYYY-MM-DD')); $('.datepicker').trigger('change'); $('.combobox').combobox({ appendId: '_text_input' }); + + $('#amount').val(1); + $('#best_before_date').val(''); + $('#product_id').val(''); $('#product_id_text_input').focus(); $('#product_id_text_input').val(''); $('#product_id_text_input').trigger('change'); - $('#purchase-form').validator(); + $('#purchase-form').validator({ + custom: { + 'isodate': function($el) + { + if ($el.val().length !== 0 && !moment($el.val(), 'YYYY-MM-DD', true).isValid()) + { + return 'Wrong date format, needs to be YYYY-MM-DD'; + } + } + } + }); + $('#purchase-form').validator('validate'); + + $('#purchase-form input').keydown(function(event) + { + if (event.keyCode === 13) //Enter + { + if ($('#purchase-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error + { + event.preventDefault(); + return false; + } + } + }); +}); + +$('#best_before_date-datepicker-button').on('click', function(e) +{ + $('.datepicker').datepicker('show'); +}); + +$('#best_before_date').on('change', function(e) +{ + var value = $('#best_before_date').val(); + if (value.length === 8 && $.isNumeric(value)) + { + value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3'); + $('#best_before_date').val(value); + $('#purchase-form').validator('validate'); + } +}); + +$('#best_before_date').on('keypress', function(e) +{ + var element = $(e.target); + var value = element.val(); + var dateObj = moment(element.val(), 'YYYY-MM-DD', true); + + $('.datepicker').datepicker('hide'); + + if (value.length === 0) + { + element.val(moment().format('YYYY-MM-DD')); + } + else if (dateObj.isValid()) + { + if (e.keyCode === 38) //Up + { + element.val(dateObj.add(-1, 'days').format('YYYY-MM-DD')); + } + else if (e.keyCode === 40) //Down + { + element.val(dateObj.add(1, 'days').format('YYYY-MM-DD')); + } + else if (e.keyCode === 37) //Left + { + element.val(dateObj.add(-1, 'weeks').format('YYYY-MM-DD')); + } + else if (e.keyCode === 39) //Right + { + element.val(dateObj.add(1, 'weeks').format('YYYY-MM-DD')); + } + } + $('#purchase-form').validator('validate'); }); diff --git a/views/purchase.php b/views/purchase.php index d7ea0dee..0123f137 100644 --- a/views/purchase.php +++ b/views/purchase.php @@ -1,19 +1,23 @@ -
-

Record purchase

+
+

Purchase

- + +
+
+
+
- -
- + +
+
@@ -23,21 +27,11 @@
-
- -
- -
- -
-
-
-
-
+

Product overview

Purchase quantity: