diff --git a/.gitignore b/.gitignore index 3c60286a..3b1a7ff1 100644 --- a/.gitignore +++ b/.gitignore @@ -198,6 +198,5 @@ FakesAssemblies/ /bower_components /vendor /.release -/config.php /composer.phar /composer.lock diff --git a/grocy.php b/Grocy.php similarity index 100% rename from grocy.php rename to Grocy.php diff --git a/GrocyDbMigrator.php b/GrocyDbMigrator.php index f49645f9..d8e67f65 100644 --- a/GrocyDbMigrator.php +++ b/GrocyDbMigrator.php @@ -14,6 +14,8 @@ class GrocyDbMigrator qu_id_stock INTEGER NOT NULL, qu_factor_purchase_to_stock REAL NOT NULL, barcode TEXT, + min_stock_amount INTEGER NOT NULL DEFAULT 0, + default_best_before_days INTEGER NOT NULL DEFAULT 0, row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) )" ); diff --git a/GrocyDemoDataGenerator.php b/GrocyDemoDataGenerator.php index 1831fd32..246e19d6 100644 --- a/GrocyDemoDataGenerator.php +++ b/GrocyDemoDataGenerator.php @@ -6,15 +6,31 @@ class GrocyDemoDataGenerator { $sql = " UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1; - INSERT INTO locations (name) VALUES ('Süßigkeitenschrank'); - INSERT INTO locations (name) VALUES ('Konvervenschrank'); + INSERT INTO locations (name) VALUES ('Süßigkeitenschrank'); --2 + INSERT INTO locations (name) VALUES ('Konservenschrank'); --3 + INSERT INTO locations (name) VALUES ('Kühlschrank'); --4 UPDATE quantity_units SET name = 'Stück' WHERE id = 1; - INSERT INTO quantity_units (name) VALUES ('Packung'); + INSERT INTO quantity_units (name) VALUES ('Packung'); --2 + INSERT INTO quantity_units (name) VALUES ('Glas'); --3 + INSERT INTO quantity_units (name) VALUES ('Dose'); --4 + INSERT INTO quantity_units (name) VALUES ('Becher'); --5 + INSERT INTO quantity_units (name) VALUES ('Bund'); --6 - INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gummibärchen', 2, 2, 2, 1); - INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Chips', 2, 2, 2, 1); - INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10); + DELETE FROM products WHERE id IN (1, 2); + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gummibärchen', 2, 2, 2, 1); --3 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Chips', 2, 2, 2, 1); --4 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10); --5 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Nudeln', 1, 2, 2, 1); --6 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Essiggurken', 3, 3, 3, 1); --7 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gulaschsuppe', 3, 4, 4, 1); --8 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Joghurt', 4, 5, 5, 1); --9 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Käse', 4, 2, 2, 1); --10 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Aufschnitt', 4, 2, 2, 1); --11 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Paprika', 4, 1, 1, 1); --12 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gurke', 4, 1, 1, 1); --13 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Radieschen', 4, 6, 6, 1); --14 + INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Tomate', 4, 1, 1, 1); --15 "; if ($pdo->exec(utf8_encode($sql)) === false) @@ -24,6 +40,16 @@ class GrocyDemoDataGenerator GrocyLogicStock::AddProduct(3, 5, date('Y-m-d', strtotime('+180 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); GrocyLogicStock::AddProduct(4, 5, date('Y-m-d', strtotime('+180 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); - GrocyLogicStock::AddProduct(5, 5, date('Y-m-d', strtotime('+25 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(5, 5, date('Y-m-d', strtotime('+20 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(6, 5, date('Y-m-d', strtotime('+600 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(7, 5, date('Y-m-d', strtotime('+800 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(8, 5, date('Y-m-d', strtotime('+900 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(9, 5, date('Y-m-d', strtotime('+14 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(10, 5, date('Y-m-d', strtotime('+21 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(11, 5, date('Y-m-d', strtotime('+10 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(12, 5, date('Y-m-d', strtotime('+2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(13, 5, date('Y-m-d', strtotime('-2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(14, 5, date('Y-m-d', strtotime('+2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); + GrocyLogicStock::AddProduct(15, 5, date('Y-m-d', strtotime('-2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE); } } diff --git a/GrocyLogicStock.php b/GrocyLogicStock.php index 69179ed5..c4fb8fe1 100644 --- a/GrocyLogicStock.php +++ b/GrocyLogicStock.php @@ -35,88 +35,121 @@ class GrocyLogicStock public static function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType) { - $db = Grocy::GetDbConnection(); - $stockId = uniqid(); + if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) + { + $db = Grocy::GetDbConnection(); + $stockId = uniqid(); - $logRow = $db->stock_log()->createRow(array( - 'product_id' => $productId, - 'amount' => $amount, - 'best_before_date' => $bestBeforeDate, - 'purchased_date' => date('Y-m-d'), - 'stock_id' => $stockId, - 'transaction_type' => $transactionType - )); - $logRow->save(); + $logRow = $db->stock_log()->createRow(array( + 'product_id' => $productId, + 'amount' => $amount, + 'best_before_date' => $bestBeforeDate, + 'purchased_date' => date('Y-m-d'), + 'stock_id' => $stockId, + 'transaction_type' => $transactionType + )); + $logRow->save(); - $stockRow = $db->stock()->createRow(array( - 'product_id' => $productId, - 'amount' => $amount, - 'best_before_date' => $bestBeforeDate, - 'purchased_date' => date('Y-m-d'), - 'stock_id' => $stockId, - )); - $stockRow->save(); + $stockRow = $db->stock()->createRow(array( + 'product_id' => $productId, + 'amount' => $amount, + 'best_before_date' => $bestBeforeDate, + 'purchased_date' => date('Y-m-d'), + 'stock_id' => $stockId, + )); + $stockRow->save(); - return true; + return true; + } + else + { + throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.AddProduct)"); + } } public static function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType) { - $db = Grocy::GetDbConnection(); - - $productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); - $potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('purchased_date', 'ASC')->fetchAll(); //FIFO - - if ($amount > $productStockAmount) + if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) { - return false; + $db = Grocy::GetDbConnection(); + + $productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); + $potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('purchased_date', 'ASC')->fetchAll(); //FIFO + + if ($amount > $productStockAmount) + { + return false; + } + + foreach ($potentialStockEntries as $stockEntry) + { + if ($amount == 0) + { + break; + } + + if ($amount >= $stockEntry->amount) //Take the whole stock entry + { + $logRow = $db->stock_log()->createRow(array( + 'product_id' => $stockEntry->product_id, + 'amount' => $stockEntry->amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'used_date' => date('Y-m-d'), + 'spoiled' => $spoiled, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => $transactionType + )); + $logRow->save(); + + $amount -= $stockEntry->amount; + $stockEntry->delete(); + } + else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount + { + $logRow = $db->stock_log()->createRow(array( + 'product_id' => $stockEntry->product_id, + 'amount' => $amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'used_date' => date('Y-m-d'), + 'spoiled' => $spoiled, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => $transactionType + )); + $logRow->save(); + + $restStockAmount = $stockEntry->amount - $amount; + $amount = 0; + + $stockEntry->update(array( + 'amount' => $restStockAmount + )); + } + } + + return true; } - - foreach ($potentialStockEntries as $stockEntry) + else { - if ($amount == 0) - { - break; - } + throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.ConsumeProduct)"); + } + } - if ($amount >= $stockEntry->amount) //Take the whole stock entry - { - $logRow = $db->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $stockEntry->amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'used_date' => date('Y-m-d'), - 'spoiled' => $spoiled, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => $transactionType - )); - $logRow->save(); + public static function InventoryProduct(int $productId, int $newAmount, string $bestBeforeDate) + { + $db = Grocy::GetDbConnection(); + $productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); - $amount -= $stockEntry->amount; - $stockEntry->delete(); - } - else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount - { - $logRow = $db->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'used_date' => date('Y-m-d H:i:s'), - 'spoiled' => $spoiled, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => $transactionType - )); - $logRow->save(); - - $restStockAmount = $stockEntry->amount - $amount; - $amount = 0; - - $stockEntry->update(array( - 'amount' => $restStockAmount - )); - } + if ($newAmount > $productStockAmount) + { + $amountToAdd = $newAmount - $productStockAmount; + self::AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION); + } + else if ($newAmount < $productStockAmount) + { + $amountToRemove = $productStockAmount - $newAmount; + self::ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION); } return true; diff --git a/GrocyPhpHelper.php b/GrocyPhpHelper.php index 9fb0dda9..bb6b6e45 100644 --- a/GrocyPhpHelper.php +++ b/GrocyPhpHelper.php @@ -14,4 +14,36 @@ class GrocyPhpHelper return null; } + + public static function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==') + { + $returnArray = array(); + + foreach($array as $object) + { + switch($operator) + { + case '==': + if($object->{$propertyName} == $propertyValue) + { + $returnArray[] = $object; + } + break; + case '>': + if($object->{$propertyName} > $propertyValue) + { + $returnArray[] = $object; + } + break; + case '<': + if($object->{$propertyName} < $propertyValue) + { + $returnArray[] = $object; + } + break; + } + } + + return $returnArray; + } } diff --git a/README.md b/README.md index 66f54e5d..e296212e 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,7 @@ For now my main focus is on stock management, ERP your fridge! Public demo of the latest version → [https://grocy.projectdemos.berrnd.org](https://grocy.projectdemos.berrnd.org) ## How to install -Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP enabled webserver, copy `config-dist.php` to `config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually. - -## Todo -A lot... +Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP enabled webserver, copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually. ## License The MIT License (MIT) diff --git a/build.bat b/build.bat index 97dc1a10..5835331b 100644 --- a/build.bat +++ b/build.bat @@ -8,4 +8,4 @@ for /f "tokens=*" %%a in ('type version.txt') do set version=%%a del "%releasePath%\grocy_%version%.zip" "build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!composer.phar -xr!grocy.phpproj -xr!grocy.phpproj.user -xr!grocy.sln -"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\add_before_end_body.html data\demo.txt data\grocy.db data\.gitignore config.php bower.json +"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\add_before_end_body.html data\demo.txt data\config.php data\grocy.db data\.gitignore bower.json diff --git a/grocy.phpproj b/grocy.phpproj index 36db974b..89063966 100644 --- a/grocy.phpproj +++ b/grocy.phpproj @@ -24,6 +24,7 @@ + @@ -49,6 +50,7 @@ + diff --git a/index.php b/index.php index d6e24fe8..0bbcebdf 100644 --- a/index.php +++ b/index.php @@ -4,13 +4,13 @@ use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; use Slim\Views\PhpRenderer; -require_once 'vendor/autoload.php'; -require_once 'config.php'; -require_once 'Grocy.php'; -require_once 'GrocyDbMigrator.php'; -require_once 'GrocyDemoDataGenerator.php'; -require_once 'GrocyLogicStock.php'; -require_once 'GrocyPhpHelper.php'; +require_once __DIR__ . '/vendor/autoload.php'; +require_once __DIR__ . '/data/config.php'; +require_once __DIR__ . '/Grocy.php'; +require_once __DIR__ . '/GrocyDbMigrator.php'; +require_once __DIR__ . '/GrocyDemoDataGenerator.php'; +require_once __DIR__ . '/GrocyLogicStock.php'; +require_once __DIR__ . '/GrocyPhpHelper.php'; $app = new \Slim\App(new \Slim\Container([ 'settings' => [ @@ -62,6 +62,15 @@ $app->get('/consumption', function(Request $request, Response $response) use($db ]); }); +$app->get('/inventory', function(Request $request, Response $response) use($db) +{ + return $this->renderer->render($response, '/layout.php', [ + 'title' => 'Inventory', + 'contentPage' => 'inventory.php', + 'products' => $db->products() + ]); +}); + $app->get('/products', function(Request $request, Response $response) use($db) { return $this->renderer->render($response, '/layout.php', [ @@ -228,6 +237,17 @@ $app->group('/api', function() use($db, $app) echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType))); }); + $this->get('/stock/inventory-product/{productId}/{newAmount}', function(Request $request, Response $response, $args) + { + $bestBeforeDate = date('Y-m-d'); + if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate'])) + { + $bestBeforeDate = $request->getQueryParams()['bestbeforedate']; + } + + echo json_encode(array('success' => GrocyLogicStock::InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate))); + }); + $this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args) { echo json_encode(GrocyLogicStock::GetProductDetails($args['productId'])); diff --git a/style.css b/style.css index eb1a3752..779a7236 100644 --- a/style.css +++ b/style.css @@ -106,6 +106,15 @@ font-size: 0.8em; } -.disabled { +.disabled, +.no-real-button { pointer-events: none; } + +.warning-bg { + background-color: #fcf8e3 !important; +} + +.error-bg { + background-color: #f2dede !important; +} diff --git a/version.txt b/version.txt index 60a2d3e9..afaf360d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.0 \ No newline at end of file +1.0.0 \ No newline at end of file diff --git a/views/consumption.js b/views/consumption.js index 1e8e89be..077d78d7 100644 --- a/views/consumption.js +++ b/views/consumption.js @@ -45,22 +45,22 @@ $('#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-stock-qu-name2').text(productStatistics.quantity_unit_stock.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 || '')); - $('#amount').attr('max', productStatistics.stock_amount); + $('#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-stock-qu-name2').text(productDetails.quantity_unit_stock.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 || '')); + $('#amount').attr('max', productDetails.stock_amount); 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) + if ((productDetails.stock_amount || 0) === 0) { $('#product_id').val(''); $('#product_id_text_input').val(''); @@ -69,6 +69,7 @@ $('#product_id').on('change', function(e) $('#product_id_text_input').closest('.form-group').addClass('has-error'); $('#product-error').text('This product is not in stock.'); $('#product-error').show(); + $('#product_id_text_input').focus(); } else { @@ -89,25 +90,19 @@ $('#product_id').on('change', function(e) $(function() { $('.combobox').combobox({ - appendId: '_text_input', - matcher: function(text) + appendId: '_text_input' + }); + + $('#product_id_text_input').on('change', function(e) + { + var input = $('#product_id_text_input').val().toString(); + var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first(); + + if (possibleOptionElement.length > 0) { - var input = $('#product_id_text_input').val(); - var optionElement = $("#product_id option:contains('" + text + "')").first(); - var additionalSearchdata = optionElement.data('additional-searchdata'); - - if (text.contains(input)) - { - return true; - } - else if (additionalSearchdata !== null && additionalSearchdata.length > 0) - { - return additionalSearchdata.contains(input); - } - else - { - return false; - } + $('#product_id').val(possibleOptionElement.val()); + $('#product_id').data('combobox').refresh(); + $('#product_id').trigger('change'); } }); @@ -120,6 +115,14 @@ $(function() $('#consumption-form').validator(); $('#consumption-form').validator('validate'); + $('#amount').on('focus', function(e) + { + if ($('#product_id_text_input').val().length === 0) + { + $('#product_id_text_input').focus(); + } + }); + $('#consumption-form input').keydown(function(event) { if (event.keyCode === 13) //Enter diff --git a/views/dashboard.php b/views/dashboard.php index f2d35b6f..dd27c387 100644 --- a/views/dashboard.php +++ b/views/dashboard.php @@ -1,7 +1,14 @@
+

Dashboard

-

Current stock

+

Stock overview

+ +
+

products expiring within the next 5 days

+

products are already expired

+
+
@@ -13,7 +20,7 @@ - + @@ -21,11 +28,13 @@ amount; ?>
product_id)->name; ?> - best_before_date; ?> + best_before_date; ?> +
+
diff --git a/views/inventory.js b/views/inventory.js new file mode 100644 index 00000000..f492e81d --- /dev/null +++ b/views/inventory.js @@ -0,0 +1,237 @@ +$('#save-inventory-button').on('click', function(e) +{ + e.preventDefault(); + + var jsonForm = $('#inventory-form').serializeJSON(); + + Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id, + function (productDetails) + { + Grocy.FetchJson('/api/stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(), + function (result) { + toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name); + + $('#new_amount').val(''); + $('#best_before_date').val(''); + $('#product_id').val(''); + $('#product_id_text_input').focus(); + $('#product_id_text_input').val(''); + $('#product_id_text_input').trigger('change'); + $('#inventory-form').validator('validate'); + }, + function (xhr) { + console.error(xhr); + } + ); + }, + function(xhr) + { + console.error(xhr); + } + ); +}); + +$('#product_id').on('change', function(e) +{ + var productId = $(e.target).val(); + + if (productId) + { + Grocy.FetchJson('/api/stock/get-product-details/' + productId, + function(productDetails) + { + $('#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 || '')); + $('#new_amount').attr('not-equal', productDetails.stock_amount); + $('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name); + + Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago'); + Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago'); + }, + function(xhr) + { + console.error(xhr); + } + ); + } +}); + +$(function() +{ + $('.datepicker').datepicker( + { + format: 'yyyy-mm-dd', + startDate: '+0d', + todayHighlight: true, + autoclose: true, + calendarWeeks: true, + orientation: 'bottom auto', + weekStart: 1, + showOnFocus: false + }); + $('.datepicker').trigger('change'); + + $('.combobox').combobox({ + appendId: '_text_input' + }); + + $('#product_id_text_input').on('change', function(e) + { + var input = $('#product_id_text_input').val().toString(); + var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first(); + + if (possibleOptionElement.length > 0) + { + $('#product_id').val(possibleOptionElement.val()); + $('#product_id').data('combobox').refresh(); + $('#product_id').trigger('change'); + } + }); + + $('#new_amount').val(''); + $('#best_before_date').val(''); + $('#product_id').val(''); + $('#product_id_text_input').focus(); + $('#product_id_text_input').val(''); + $('#product_id_text_input').trigger('change'); + + $('#inventory-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'; + } + }, + 'notequal': function($el) + { + if ($el.val().length !== 0 && $el.val().toString() === $el.attr('not-equal').toString()) + { + return 'This value cannot be equal to ' + $el.attr('not-equal').toString(); + } + } + } + }); + $('#inventory-form').validator('validate'); + + $('#new_amount').on('focus', function(e) + { + if ($('#product_id_text_input').val().length === 0) + { + $('#product_id_text_input').focus(); + } + }); + + $('#inventory-form input').keydown(function(event) + { + if (event.keyCode === 13) //Enter + { + if ($('#inventory-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); + $('#inventory-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')); + } + } + + $('#inventory-form').validator('validate'); +}); + +$('#new_amount').on('change', function(e) +{ + if ($('#product_id').parent().hasClass('has-error')) + { + $('#inventory-change-info').hide(); + return; + } + + var productId = $('#product_id').val(); + var newAmount = $('#new_amount').val(); + + if (productId) + { + Grocy.FetchJson('/api/stock/get-product-details/' + productId, + function(productDetails) + { + var productStockAmount = productDetails.stock_amount || '0'; + + if (newAmount > productStockAmount) + { + var amountToAdd = newAmount - productDetails.stock_amount; + $('#inventory-change-info').text('This means ' + amountToAdd.toString() + ' ' + productDetails.quantity_unit_stock.name + ' will be added to stock'); + $('#inventory-change-infoo').show(); + } + else if (newAmount < productStockAmount) + { + var amountToRemove = productStockAmount - newAmount; + $('#inventory-change-info').text('This means ' + amountToRemove.toString() + ' ' + productDetails.quantity_unit_stock.name + ' will be removed from stock'); + $('#inventory-change-info').show(); + } + else + { + $('#inventory-change-info').hide(); + } + }, + function(xhr) + { + console.error(xhr); + } + ); + } +}); diff --git a/views/inventory.php b/views/inventory.php new file mode 100644 index 00000000..b8a23d31 --- /dev/null +++ b/views/inventory.php @@ -0,0 +1,49 @@ +
+

Inventory

+ +
+ +
+ + +
+
+ +
+ + +
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + + +
+
+ +
+

Product overview

+

Purchase quantity:

+ +

+ Stock amount:
+ Last purchased:
+ Last used: +

+
diff --git a/views/layout.php b/views/layout.php index b68ad902..fb482c7e 100644 --- a/views/layout.php +++ b/views/layout.php @@ -49,6 +49,9 @@
  •  Record consumption
  • +
  • +  Inventory +