Compare commits

...

4 Commits

Author SHA1 Message Date
Bernd Bestel
bd16b8c851 Added flow to directly add articles and barcodes form purchase and inventory view 2017-04-20 22:01:14 +02:00
Bernd Bestel
c4a22c18f7 This is 1.0 2017-04-20 17:10:21 +02:00
Bernd Bestel
e38c24f9ed Going straight to 1.0... 2017-04-19 21:09:28 +02:00
Bernd Bestel
83a7534a74 Hide barcode in select dropdown but search in it 2017-04-18 23:04:26 +02:00
31 changed files with 965 additions and 191 deletions

1
.gitignore vendored
View File

@@ -198,6 +198,5 @@ FakesAssemblies/
/bower_components /bower_components
/vendor /vendor
/.release /.release
/config.php
/composer.phar /composer.phar
/composer.lock /composer.lock

View File

@@ -13,8 +13,10 @@ class GrocyDbMigrator
qu_id_purchase INTEGER NOT NULL, qu_id_purchase INTEGER NOT NULL,
qu_id_stock INTEGER NOT NULL, qu_id_stock INTEGER NOT NULL,
qu_factor_purchase_to_stock REAL NOT NULL, qu_factor_purchase_to_stock REAL NOT NULL,
barcode TEXT UNIQUE, barcode TEXT,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) 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'))
)" )"
); );
@@ -23,7 +25,7 @@ class GrocyDbMigrator
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
description TEXT, description TEXT,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)" )"
); );
@@ -32,7 +34,7 @@ class GrocyDbMigrator
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
description TEXT, description TEXT,
created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')) row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)" )"
); );
@@ -43,20 +45,23 @@ class GrocyDbMigrator
amount INTEGER NOT NULL, amount INTEGER NOT NULL,
best_before_date DATE, best_before_date DATE,
purchased_date DATE DEFAULT (datetime('now', 'localtime')), purchased_date DATE DEFAULT (datetime('now', 'localtime')),
stock_id TEXT NOT NULL stock_id TEXT NOT NULL,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)" )"
); );
self::ExecuteMigrationWhenNeeded($pdo, 5, " self::ExecuteMigrationWhenNeeded($pdo, 5, "
CREATE TABLE consumptions ( CREATE TABLE stock_log (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
product_id INTEGER NOT NULL, product_id INTEGER NOT NULL,
amount INTEGER NOT NULL, amount INTEGER NOT NULL,
best_before_date DATE, best_before_date DATE,
purchased_date DATE, purchased_date DATE,
used_date DATE DEFAULT (datetime('now', 'localtime')), used_date DATE,
spoiled INTEGER NOT NULL DEFAULT 0, spoiled INTEGER NOT NULL DEFAULT 0,
stock_id TEXT NOT NULL stock_id TEXT NOT NULL,
transaction_type TEXT NOT NULL,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
)" )"
); );

View File

@@ -6,24 +6,50 @@ class GrocyDemoDataGenerator
{ {
$sql = " $sql = "
UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1; UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1;
INSERT INTO locations (name) VALUES ('S<><53>igkeitenschrank'); INSERT INTO locations (name) VALUES ('S<><53>igkeitenschrank'); --2
INSERT INTO locations (name) VALUES ('Konvervenschrank'); INSERT INTO locations (name) VALUES ('Konservenschrank'); --3
INSERT INTO locations (name) VALUES ('K<>hlschrank'); --4
UPDATE quantity_units SET name = 'St<53>ck' WHERE id = 1; UPDATE quantity_units SET name = 'St<53>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<69>rchen', 2, 2, 2, 1); 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 ('Chips', 2, 2, 2, 1); INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gummib<EFBFBD>rchen', 2, 2, 2, 1); --3
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10); 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 stock (product_id, amount, best_before_date, stock_id) VALUES (3, 5, date('now', '+180 day'), '".uniqid()."'); 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 stock (product_id, amount, best_before_date, stock_id) VALUES (4, 5, date('now', '+180 day'), '".uniqid()."'); 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 stock (product_id, amount, best_before_date, stock_id) VALUES (5, 5, date('now', '+25 day'), '".uniqid()."'); 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) if ($pdo->exec(utf8_encode($sql)) === false)
{ {
throw new Exception($pdo->errorInfo()); throw new Exception($pdo->errorInfo());
} }
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('+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);
} }
} }

View File

@@ -2,6 +2,10 @@
class GrocyLogicStock class GrocyLogicStock
{ {
const TRANSACTION_TYPE_PURCHASE = 'purchase';
const TRANSACTION_TYPE_CONSUME = 'consume';
const TRANSACTION_TYPE_INVENTORY_CORRECTION = 'inventory-correction';
public static function GetCurrentStock() public static function GetCurrentStock()
{ {
$db = Grocy::GetDbConnectionRaw(); $db = Grocy::GetDbConnectionRaw();
@@ -15,7 +19,7 @@ class GrocyLogicStock
$product = $db->products($productId); $product = $db->products($productId);
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); $productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
$productLastPurchased = $db->stock()->where('product_id', $productId)->max('purchased_date'); $productLastPurchased = $db->stock()->where('product_id', $productId)->max('purchased_date');
$productLastUsed = $db->consumptions()->where('product_id', $productId)->max('used_date'); $productLastUsed = $db->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date');
$quPurchase = $db->quantity_units($product->qu_id_purchase); $quPurchase = $db->quantity_units($product->qu_id_purchase);
$quStock = $db->quantity_units($product->qu_id_stock); $quStock = $db->quantity_units($product->qu_id_stock);
@@ -29,59 +33,123 @@ class GrocyLogicStock
); );
} }
public static function ConsumeProduct(int $productId, int $amount, bool $spoiled) public static function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType)
{
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();
$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;
}
else
{
throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.AddProduct)");
}
}
public static function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType)
{
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
{
$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;
}
else
{
throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.ConsumeProduct)");
}
}
public static function InventoryProduct(int $productId, int $newAmount, string $bestBeforeDate)
{ {
$db = Grocy::GetDbConnection(); $db = Grocy::GetDbConnection();
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount'); $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 ($newAmount > $productStockAmount)
{ {
return false; $amountToAdd = $newAmount - $productStockAmount;
self::AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
} }
else if ($newAmount < $productStockAmount)
foreach ($potentialStockEntries as $stockEntry)
{ {
if ($amount == 0) $amountToRemove = $productStockAmount - $newAmount;
{ self::ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
break;
}
if ($amount >= $stockEntry->amount) //Take the whole stock entry
{
$consumptionRow = $db->consumptions()->createRow(array(
'product_id' => $stockEntry->product_id,
'amount' => $stockEntry->amount,
'best_before_date' => $stockEntry->best_before_date,
'purchased_date' => $stockEntry->purchased_date,
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id
));
$consumptionRow->save();
$amount -= $stockEntry->amount;
$stockEntry->delete();
}
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,
'amount' => $amount,
'best_before_date' => $stockEntry->best_before_date,
'purchased_date' => $stockEntry->purchased_date,
'spoiled' => $spoiled,
'stock_id' => $stockEntry->stock_id
));
$consumptionRow->save();
$restStockAmount = $stockEntry->amount - $amount;
$amount = 0;
$stockEntry->update(array(
'amount' => $restStockAmount
));
}
} }
return true; return true;

View File

@@ -14,4 +14,36 @@ class GrocyPhpHelper
return null; 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;
}
} }

View File

@@ -11,10 +11,7 @@ For now my main focus is on stock management, ERP your fridge!
Public demo of the latest version &rarr; [https://grocy.projectdemos.berrnd.org](https://grocy.projectdemos.berrnd.org) Public demo of the latest version &rarr; [https://grocy.projectdemos.berrnd.org](https://grocy.projectdemos.berrnd.org)
## How to install ## 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. 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.
## Todo
A lot...
## License ## License
The MIT License (MIT) The MIT License (MIT)

View File

@@ -8,4 +8,4 @@ for /f "tokens=*" %%a in ('type version.txt') do set version=%%a
del "%releasePath%\grocy_%version%.zip" 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" 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

View File

@@ -75,3 +75,23 @@ Grocy.EmptyElementWhenMatches = function(selector, text)
$(selector).text(''); $(selector).text('');
} }
}; };
String.prototype.contains = function(search)
{
return this.toLowerCase().indexOf(search.toLowerCase()) !== -1;
};
Grocy.GetUriParam = function(key)
{
var currentUri = decodeURIComponent(window.location.search.substring(1));
var vars = currentUri.split('&');
for (i = 0; i < vars.length; i++) {
var currentParam = vars[i].split('=');
if (currentParam[0] === key)
{
return currentParam[1] === undefined ? true : currentParam[1];
}
}
};

View File

@@ -24,6 +24,7 @@
<Compile Include="GrocyDbMigrator.php" /> <Compile Include="GrocyDbMigrator.php" />
<Compile Include="index.php" /> <Compile Include="index.php" />
<Compile Include="views\consumption.php" /> <Compile Include="views\consumption.php" />
<Compile Include="views\inventory.php" />
<Compile Include="views\purchase.php" /> <Compile Include="views\purchase.php" />
<Compile Include="views\quantityunitform.php" /> <Compile Include="views\quantityunitform.php" />
<Compile Include="views\locationform.php" /> <Compile Include="views\locationform.php" />
@@ -49,6 +50,7 @@
<Content Include="version.txt" /> <Content Include="version.txt" />
<Content Include="views\consumption.js" /> <Content Include="views\consumption.js" />
<Content Include="views\dashboard.js" /> <Content Include="views\dashboard.js" />
<Content Include="views\inventory.js" />
<Content Include="views\purchase.js" /> <Content Include="views\purchase.js" />
<Content Include="views\quantityunitform.js" /> <Content Include="views\quantityunitform.js" />
<Content Include="views\locationform.js" /> <Content Include="views\locationform.js" />

141
index.php
View File

@@ -4,13 +4,13 @@ use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response; use \Psr\Http\Message\ResponseInterface as Response;
use Slim\Views\PhpRenderer; use Slim\Views\PhpRenderer;
require_once 'vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
require_once 'config.php'; require_once __DIR__ . '/data/config.php';
require_once 'Grocy.php'; require_once __DIR__ . '/Grocy.php';
require_once 'GrocyDbMigrator.php'; require_once __DIR__ . '/GrocyDbMigrator.php';
require_once 'GrocyDemoDataGenerator.php'; require_once __DIR__ . '/GrocyDemoDataGenerator.php';
require_once 'GrocyLogicStock.php'; require_once __DIR__ . '/GrocyLogicStock.php';
require_once 'GrocyPhpHelper.php'; require_once __DIR__ . '/GrocyPhpHelper.php';
$app = new \Slim\App(new \Slim\Container([ $app = new \Slim\App(new \Slim\Container([
'settings' => [ 'settings' => [
@@ -32,10 +32,10 @@ if (!Grocy::IsDemoInstallation())
])); ]));
} }
$app->get('/', function(Request $request, Response $response) $db = Grocy::GetDbConnection();
{
$db = Grocy::GetDbConnection();
$app->get('/', function(Request $request, Response $response) use($db)
{
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Dashboard', 'title' => 'Dashboard',
'contentPage' => 'dashboard.php', 'contentPage' => 'dashboard.php',
@@ -44,10 +44,8 @@ $app->get('/', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/purchase', function(Request $request, Response $response) $app->get('/purchase', function(Request $request, Response $response) use($db)
{ {
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Purchase', 'title' => 'Purchase',
'contentPage' => 'purchase.php', 'contentPage' => 'purchase.php',
@@ -55,10 +53,8 @@ $app->get('/purchase', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/consumption', function(Request $request, Response $response) $app->get('/consumption', function(Request $request, Response $response) use($db)
{ {
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Consumption', 'title' => 'Consumption',
'contentPage' => 'consumption.php', 'contentPage' => 'consumption.php',
@@ -66,10 +62,17 @@ $app->get('/consumption', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/products', function(Request $request, Response $response) $app->get('/inventory', function(Request $request, Response $response) use($db)
{ {
$db = Grocy::GetDbConnection(); 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', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Products', 'title' => 'Products',
'contentPage' => 'products.php', 'contentPage' => 'products.php',
@@ -79,10 +82,8 @@ $app->get('/products', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/locations', function(Request $request, Response $response) $app->get('/locations', function(Request $request, Response $response) use($db)
{ {
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Locations', 'title' => 'Locations',
'contentPage' => 'locations.php', 'contentPage' => 'locations.php',
@@ -90,10 +91,8 @@ $app->get('/locations', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/quantityunits', function(Request $request, Response $response) $app->get('/quantityunits', function(Request $request, Response $response) use($db)
{ {
$db = Grocy::GetDbConnection();
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
'title' => 'Quantity units', 'title' => 'Quantity units',
'contentPage' => 'quantityunits.php', 'contentPage' => 'quantityunits.php',
@@ -101,10 +100,8 @@ $app->get('/quantityunits', function(Request $request, Response $response)
]); ]);
}); });
$app->get('/product/{productId}', function(Request $request, Response $response, $args) $app->get('/product/{productId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
if ($args['productId'] == 'new') if ($args['productId'] == 'new')
{ {
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
@@ -128,10 +125,8 @@ $app->get('/product/{productId}', function(Request $request, Response $response,
} }
}); });
$app->get('/location/{locationId}', function(Request $request, Response $response, $args) $app->get('/location/{locationId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
if ($args['locationId'] == 'new') if ($args['locationId'] == 'new')
{ {
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
@@ -151,10 +146,8 @@ $app->get('/location/{locationId}', function(Request $request, Response $respons
} }
}); });
$app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args) $app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
if ($args['quantityunitId'] == 'new') if ($args['quantityunitId'] == 'new')
{ {
return $this->renderer->render($response, '/layout.php', [ return $this->renderer->render($response, '/layout.php', [
@@ -174,67 +167,57 @@ $app->get('/quantityunit/{quantityunitId}', function(Request $request, Response
} }
}); });
$app->group('/api', function() $app->group('/api', function() use($db, $app)
{ {
$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) $this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
echo json_encode($db->{$args['entity']}()); echo json_encode($db->{$args['entity']}());
return $response->withHeader('Content-Type', 'application/json');
}); });
$this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args) $this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
echo json_encode($db->{$args['entity']}($args['objectId'])); echo json_encode($db->{$args['entity']}($args['objectId']));
return $response->withHeader('Content-Type', 'application/json');
}); });
$this->post('/add-object/{entity}', function(Request $request, Response $response, $args) $this->post('/add-object/{entity}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
$newRow = $db->{$args['entity']}()->createRow($request->getParsedBody()); $newRow = $db->{$args['entity']}()->createRow($request->getParsedBody());
$newRow->save(); $newRow->save();
$success = $newRow->isClean(); $success = $newRow->isClean();
echo json_encode(array('success' => $success)); echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
}); });
$this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args) $this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
$row = $db->{$args['entity']}($args['objectId']); $row = $db->{$args['entity']}($args['objectId']);
$row->update($request->getParsedBody()); $row->update($request->getParsedBody());
$success = $row->isClean(); $success = $row->isClean();
echo json_encode(array('success' => $success)); echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
}); });
$this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args) $this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
{ {
$db = Grocy::GetDbConnection();
$row = $db->{$args['entity']}($args['objectId']); $row = $db->{$args['entity']}($args['objectId']);
$row->delete(); $row->delete();
$success = $row->isClean(); $success = $row->isClean();
echo json_encode(array('success' => $success)); echo json_encode(array('success' => $success));
return $response->withHeader('Content-Type', 'application/json');
}); });
$this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args) $this->get('/stock/add-product/{productId}/{amount}', function(Request $request, Response $response, $args)
{ {
echo json_encode(GrocyLogicStock::GetProductDetails($args['productId'])); $bestBeforeDate = date('Y-m-d');
return $response->withHeader('Content-Type', 'application/json'); if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
}); {
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
}
$this->get('/stock/get-current-stock', function(Request $request, Response $response) $transactionType = GrocyLogicStock::TRANSACTION_TYPE_PURCHASE;
{ if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
echo json_encode(GrocyLogicStock::GetCurrentStock()); {
return $response->withHeader('Content-Type', 'application/json'); $transactionType = $request->getQueryParams()['transactiontype'];
}
echo json_encode(array('success' => GrocyLogicStock::AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType)));
}); });
$this->get('/stock/consume-product/{productId}/{amount}', function(Request $request, Response $response, $args) $this->get('/stock/consume-product/{productId}/{amount}', function(Request $request, Response $response, $args)
@@ -245,15 +228,39 @@ $app->group('/api', function()
$spoiled = true; $spoiled = true;
} }
echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled))); $transactionType = GrocyLogicStock::TRANSACTION_TYPE_CONSUME;
return $response->withHeader('Content-Type', 'application/json'); if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
{
$transactionType = $request->getQueryParams()['transactiontype'];
}
echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType)));
}); });
$this->get('/helper/uniqid', function(Request $request, Response $response) $this->get('/stock/inventory-product/{productId}/{newAmount}', function(Request $request, Response $response, $args)
{ {
echo json_encode(array('uniqid' => uniqid())); $bestBeforeDate = date('Y-m-d');
return $response->withHeader('Content-Type', 'application/json'); 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']));
});
$this->get('/stock/get-current-stock', function(Request $request, Response $response)
{
echo json_encode(GrocyLogicStock::GetCurrentStock());
});
})->add(function($request, $response, $next)
{
$response = $next($request, $response);
return $response->withHeader('Content-Type', 'application/json');
}); });
$app->run(); $app->run();

View File

@@ -106,6 +106,15 @@
font-size: 0.8em; font-size: 0.8em;
} }
.disabled { .disabled,
.no-real-button {
pointer-events: none; pointer-events: none;
} }
.warning-bg {
background-color: #fcf8e3 !important;
}
.error-bg {
background-color: #f2dede !important;
}

View File

@@ -1 +1 @@
0.4.0 1.0.1

View File

@@ -45,22 +45,23 @@ $('#product_id').on('change', function(e)
if (productId) if (productId)
{ {
Grocy.FetchJson('/api/stock/get-product-details/' + productId, Grocy.FetchJson('/api/stock/get-product-details/' + productId,
function(productStatistics) function (productDetails)
{ {
$('#selected-product-name').text(productStatistics.product.name); $('#selected-product-name').text(productDetails.product.name);
$('#selected-product-stock-amount').text(productStatistics.stock_amount || '0'); $('#selected-product-stock-amount').text(productDetails.stock_amount || '0');
$('#selected-product-stock-qu-name').text(productStatistics.quantity_unit_stock.name); $('#selected-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
$('#selected-product-stock-qu-name2').text(productStatistics.quantity_unit_stock.name); $('#selected-product-stock-qu-name2').text(productDetails.quantity_unit_stock.name);
$('#selected-product-last-purchased').text((productStatistics.last_purchased || 'never').substring(0, 10)); $('#selected-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
$('#selected-product-last-purchased-timeago').text($.timeago(productStatistics.last_purchased || '')); $('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
$('#selected-product-last-used').text((productStatistics.last_used || 'never').substring(0, 10)); $('#selected-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
$('#selected-product-last-used-timeago').text($.timeago(productStatistics.last_used || '')); $('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
$('#amount').attr('max', productStatistics.stock_amount); $('#amount').attr('max', productDetails.stock_amount);
$('#consumption-form').validator('update');
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago'); Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
Grocy.EmptyElementWhenMatches('#selected-product-last-used-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').val('');
$('#product_id_text_input').val(''); $('#product_id_text_input').val('');
@@ -69,6 +70,7 @@ $('#product_id').on('change', function(e)
$('#product_id_text_input').closest('.form-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').text('This product is not in stock.');
$('#product-error').show(); $('#product-error').show();
$('#product_id_text_input').focus();
} }
else else
{ {
@@ -88,7 +90,22 @@ $('#product_id').on('change', function(e)
$(function() $(function()
{ {
$('.combobox').combobox({ appendId: '_text_input' }); $('.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');
}
});
$('#amount').val(1); $('#amount').val(1);
$('#product_id').val(''); $('#product_id').val('');
@@ -99,6 +116,14 @@ $(function()
$('#consumption-form').validator(); $('#consumption-form').validator();
$('#consumption-form').validator('validate'); $('#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) $('#consumption-form input').keydown(function(event)
{ {
if (event.keyCode === 13) //Enter if (event.keyCode === 13) //Enter

View File

@@ -2,27 +2,32 @@
<h1 class="page-header">Consumption</h1> <h1 class="page-header">Consumption</h1>
<form id="consumption-form"> <form id="consumption-form">
<div class="form-group"> <div class="form-group">
<label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label> <label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<select data-instockproduct="instockproduct" class="form-control combobox" id="product_id" name="product_id" required> <select data-instockproduct="instockproduct" class="form-control combobox" id="product_id" name="product_id" required>
<option value=""></option> <option value=""></option>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<option value="<?php echo $product->id; ?>"><?php echo $product->name; ?><?php if (!empty($product->barcode)) echo ' [' . $product->barcode . ']'; ?></option> <option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div id="product-error" class="help-block with-errors"></div> <div id="product-error" class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="amount">Amount</label> <label for="amount">Amount</label>
<input type="number" class="form-control" id="amount" name="amount" value="1" min="1" required> <input type="number" class="form-control" id="amount" name="amount" value="1" min="1" required>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="checkbox"> <div class="checkbox">
<label for="spoiled"> <label for="spoiled">
<input type="checkbox" id="spoiled" name="spoiled"> Spoiled <input type="checkbox" id="spoiled" name="spoiled"> Spoiled
</label> </label>
</div> </div>
<button id="save-consumption-button" type="submit" class="btn btn-default">OK</button> <button id="save-consumption-button" type="submit" class="btn btn-default">OK</button>
</form> </form>
</div> </div>

View File

@@ -1,7 +1,14 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Dashboard</h1> <h1 class="page-header">Dashboard</h1>
<h3>Current stock</h3> <h3>Stock overview</h3>
<div>
<p class="btn btn-lg btn-warning no-real-button"><strong><?php echo count(GrocyPhpHelper::FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('+5 days')), '<')); ?></strong> products expiring within the next 5 days</p>
<p class="btn btn-lg btn-danger no-real-button"><strong><?php echo count(GrocyPhpHelper::FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('-1 days')), '<')); ?></strong> products are already expired</p>
</div>
<div class="table-responsive"> <div class="table-responsive">
<table id="current-stock-table" class="table table-striped"> <table id="current-stock-table" class="table table-striped">
<thead> <thead>
@@ -13,7 +20,7 @@
</thead> </thead>
<tbody> <tbody>
<?php foreach ($currentStock as $currentStockEntry) : ?> <?php foreach ($currentStock as $currentStockEntry) : ?>
<tr> <tr class="<?php if ($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days'))) echo 'error-bg'; else if ($currentStockEntry->best_before_date < date('Y-m-d', strtotime('+5 days'))) echo 'warning-bg'; ?>">
<td> <td>
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name; ?> <?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name; ?>
</td> </td>
@@ -21,11 +28,13 @@
<?php echo $currentStockEntry->amount; ?> <?php echo $currentStockEntry->amount; ?>
</td> </td>
<td> <td>
<?php echo $currentStockEntry->best_before_date; ?> <time class="timeago timeago-contextual" datetime="<?php echo $currentStockEntry->best_before_date; ?>"></time> <?php echo $currentStockEntry->best_before_date; ?>
<time class="timeago timeago-contextual" datetime="<?php echo $currentStockEntry->best_before_date; ?>"></time>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

334
views/inventory.js Normal file
View File

@@ -0,0 +1,334 @@
$('#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)
{
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
var existingBarcodes = productDetails.product.barcode || '';
if (existingBarcodes.length === 0)
{
productDetails.product.barcode = addBarcode;
}
else
{
productDetails.product.barcode += ',' + addBarcode;
}
Grocy.PostJson('/api/edit-object/products/' + productDetails.product.id, productDetails.product,
function (result) { },
function(xhr)
{
console.error(xhr);
}
);
}
toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name);
if (addBarcode !== undefined)
{
window.location.href = '/inventory';
}
else
{
$('#inventory-change-info').hide();
$('#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 (Grocy.GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
}
else
{
var optionElement = $("#product_id option:contains('" + input + "')").first();
if (input.length > 0 && optionElement.length === 0 && Grocy.GetUriParam('addbarcodetoselection') === undefined )
{
bootbox.dialog({
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
title: 'Create or assign product',
onEscape: function() { },
buttons: {
cancel: {
label: 'Cancel',
className: 'btn-default',
callback: function() { }
},
addnewproduct: {
label: 'Add as new product',
className: 'btn-success',
callback: function()
{
window.location.href = '/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
}
},
addbarcode: {
label: 'Add as barcode to existing product',
className: 'btn-info',
callback: function()
{
window.location.href = '/inventory?addbarcodetoselection=' + encodeURIComponent(input);
}
}
}
});
}
}
});
$('#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;
}
}
});
var prefillProduct = Grocy.GetUriParam('createdproduct');
if (prefillProduct !== undefined)
{
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = $("#product_id option:contains('" + prefillProduct + "')").first();
}
if (possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
$('#new_amount').focus();
}
}
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
$('#addbarcodetoselection').text(addBarcode);
$('#flow-info-addbarcodetoselection').removeClass('hide');
}
});
$('#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-info').show();
$('#best_before_date').attr('required', 'required');
}
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();
$('#best_before_date').removeAttr('required');
}
else
{
$('#inventory-change-info').hide();
}
$('#inventory-form').validator('update');
$('#inventory-form').validator('validate');
},
function(xhr)
{
console.error(xhr);
}
);
}
});

50
views/inventory.php Normal file
View File

@@ -0,0 +1,50 @@
<div class="col-sm-4 col-sm-offset-3 col-md-3 col-md-offset-2 main">
<h1 class="page-header">Inventory</h1>
<form id="inventory-form">
<div class="form-group">
<label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<select class="form-control combobox" id="product_id" name="product_id" required>
<option value=""></option>
<?php foreach ($products as $product) : ?>
<option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>
<?php endforeach; ?>
</select>
<div class="help-block with-errors"></div>
<div id="flow-info-addbarcodetoselection" class="text-muted small hide"><strong><span id="addbarcodetoselection"></span></strong> will be added to the list of barcodes for the selected product on submit.</div>
</div>
<div class="form-group">
<label for="new_amount">New amount&nbsp;&nbsp;<span id="new_amount_qu_unit" class="small text-muted"></span></label>
<input type="number" data-notequal="notequal" class="form-control" id="new_amount" name="new_amount" min="0" not-equal="-1" required>
<div class="help-block with-errors"></div>
<div id="inventory-change-info" class="help-block text-muted"></div>
</div>
<div class="form-group">
<label for="best_before_date">Best before&nbsp;&nbsp;<span class="small text-muted">This will apply to added products</span></label>
<div class="input-group date">
<input type="text" data-isodate="isodate" class="form-control datepicker" id="best_before_date" name="best_before_date" autocomplete="off">
<div id="best_before_date-datepicker-button" class="input-group-addon">
<i class="fa fa-calendar"></i>
</div>
</div>
<div class="help-block with-errors"></div>
</div>
<button id="save-inventory-button" type="submit" class="btn btn-default">OK</button>
</form>
</div>
<div class="col-sm-6 col-md-5 col-lg-3 main well">
<h3>Product overview <strong><span id="selected-product-name"></span></strong></h3>
<h4><strong>Purchase quantity:</strong> <span id="selected-product-purchase-qu-name"></span></h4>
<p>
<strong>Stock amount:</strong> <span id="selected-product-stock-amount"></span> <span id="selected-product-stock-qu-name"></span><br />
<strong>Last purchased:</strong> <span id="selected-product-last-purchased"></span> <time id="selected-product-last-purchased-timeago" class="timeago timeago-contextual"></time><br />
<strong>Last used:</strong> <span id="selected-product-last-used"></span> <time id="selected-product-last-used-timeago" class="timeago timeago-contextual"></time>
</p>
</div>

View File

@@ -38,6 +38,7 @@
</div> </div>
<div id="navbar-mobile" class="navbar-collapse collapse"> <div id="navbar-mobile" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li data-nav-for-page="dashboard.php"> <li data-nav-for-page="dashboard.php">
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a> <a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a>
@@ -48,7 +49,11 @@
<li data-nav-for-page="consumption.php"> <li data-nav-for-page="consumption.php">
<a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i>&nbsp;Record consumption</a> <a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i>&nbsp;Record consumption</a>
</li> </li>
<li data-nav-for-page="inventory.php">
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a>
</li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li data-nav-for-page="products.php"> <li data-nav-for-page="products.php">
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i>&nbsp;Manage products</a> <a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i>&nbsp;Manage products</a>
@@ -60,13 +65,16 @@
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Manage quantity units</a> <a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Manage quantity units</a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-sm-3 col-md-2 sidebar"> <div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">
<li data-nav-for-page="dashboard.php"> <li data-nav-for-page="dashboard.php">
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a> <a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i>&nbsp;Dashboard</a>
@@ -77,7 +85,11 @@
<li data-nav-for-page="consumption.php"> <li data-nav-for-page="consumption.php">
<a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i>&nbsp;Record consumption</a> <a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i>&nbsp;Record consumption</a>
</li> </li>
<li data-nav-for-page="inventory.php">
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i>&nbsp;Inventory</a>
</li>
</ul> </ul>
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">
<li data-nav-for-page="products.php"> <li data-nav-for-page="products.php">
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i>&nbsp;Manage products</a> <a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i>&nbsp;Manage products</a>
@@ -89,6 +101,7 @@
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Manage quantity units</a> <a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i>&nbsp;Manage quantity units</a>
</li> </li>
</ul> </ul>
<div class="nav-copyright nav nav-sidebar"> <div class="nav-copyright nav nav-sidebar">
grocy is a project by grocy is a project by
<a class="discrete-link" href="https://berrnd.de" target="_blank">Bernd Bestel</a> <a class="discrete-link" href="https://berrnd.de" target="_blank">Bernd Bestel</a>
@@ -103,10 +116,12 @@
<i class="fa fa-github"></i> <i class="fa fa-github"></i>
</a> </a>
</div> </div>
</div> </div>
<script>Grocy.ContentPage = '<?php echo $contentPage; ?>';</script> <script>Grocy.ContentPage = '<?php echo $contentPage; ?>';</script>
<?php include $contentPage; ?> <?php include $contentPage; ?>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main"> <div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main">
<h1 class="page-header"><?php echo $title; ?></h1> <h1 class="page-header"><?php echo $title; ?></h1>
<script>Grocy.EditMode = '<?php echo $mode; ?>';</script> <script>Grocy.EditMode = '<?php echo $mode; ?>';</script>
@@ -8,15 +9,20 @@
<?php endif; ?> <?php endif; ?>
<form id="location-form"> <form id="location-form">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $location->name; ?>" /> <input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $location->name; ?>" />
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="description">Description</label>
<textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $location->description; ?></textarea> <textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $location->description; ?></textarea>
</div> </div>
<button id="save-location-button" type="submit" class="btn btn-default">Save</button> <button id="save-location-button" type="submit" class="btn btn-default">Save</button>
</form> </form>
</div> </div>

View File

@@ -14,7 +14,7 @@
}, },
callback: function(result) callback: function(result)
{ {
if (result == true) if (result === true)
{ {
Grocy.FetchJson('/api/delete-object/locations/' + $(e.target).attr('data-location-id'), Grocy.FetchJson('/api/delete-object/locations/' + $(e.target).attr('data-location-id'),
function(result) function(result)
@@ -41,4 +41,3 @@ $(function()
] ]
}); });
}); });

View File

@@ -1,4 +1,5 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header"> <h1 class="page-header">
Locations Locations
<a class="btn btn-default" href="/location/new" role="button"> <a class="btn btn-default" href="/location/new" role="button">
@@ -37,4 +38,5 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@@ -2,12 +2,19 @@
{ {
e.preventDefault(); e.preventDefault();
var redirectDestination = '/products';
var returnTo = Grocy.GetUriParam('returnto');
if (returnTo !== undefined)
{
redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val());
}
if (Grocy.EditMode === 'create') if (Grocy.EditMode === 'create')
{ {
Grocy.PostJson('/api/add-object/products', $('#product-form').serializeJSON(), Grocy.PostJson('/api/add-object/products', $('#product-form').serializeJSON(),
function(result) function(result)
{ {
window.location.href = '/products'; window.location.href = redirectDestination;
}, },
function(xhr) function(xhr)
{ {
@@ -20,7 +27,7 @@
Grocy.PostJson('/api/edit-object/products/' + Grocy.EditObjectId, $('#product-form').serializeJSON(), Grocy.PostJson('/api/edit-object/products/' + Grocy.EditObjectId, $('#product-form').serializeJSON(),
function(result) function(result)
{ {
window.location.href = '/products'; window.location.href = redirectDestination;
}, },
function(xhr) function(xhr)
{ {
@@ -42,7 +49,7 @@ $(function()
Grocy.FetchJson('/api/get-object/products/' + Grocy.EditObjectId, Grocy.FetchJson('/api/get-object/products/' + Grocy.EditObjectId,
function (product) function (product)
{ {
if (product.barcode.length > 0) if (product.barcode !== null && product.barcode.length > 0)
{ {
product.barcode.split(',').forEach(function(item) product.barcode.split(',').forEach(function(item)
{ {
@@ -61,6 +68,13 @@ $(function()
$('#name').focus(); $('#name').focus();
$('#product-form').validator(); $('#product-form').validator();
$('#product-form').validator('validate'); $('#product-form').validator('validate');
var prefillName = Grocy.GetUriParam('prefillname');
if (prefillName !== undefined)
{
$('#name').val(prefillName);
$('#name').focus();
}
}); });
$('.input-group-qu').on('change', function(e) $('.input-group-qu').on('change', function(e)

View File

@@ -1,4 +1,5 @@
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main"> <div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main">
<h1 class="page-header"><?php echo $title; ?></h1> <h1 class="page-header"><?php echo $title; ?></h1>
<script>Grocy.EditMode = '<?php echo $mode; ?>';</script> <script>Grocy.EditMode = '<?php echo $mode; ?>';</script>
@@ -8,20 +9,24 @@
<?php endif; ?> <?php endif; ?>
<form id="product-form"> <form id="product-form">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $product->name; ?>"> <input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $product->name; ?>">
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="description">Description</label>
<textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $product->description; ?></textarea> <textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $product->description; ?></textarea>
</div> </div>
<div class="form-group tm-group"> <div class="form-group tm-group">
<label for="barcode-taginput">Barcode(s)</label> <label for="barcode-taginput">Barcode(s)&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<input type="text" class="form-control tm-input" id="barcode-taginput" placeholder="Add (scan) a barcode here to add one..."> <input type="text" class="form-control tm-input" id="barcode-taginput">
<div id="barcode-taginput-container"></div> <div id="barcode-taginput-container"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="location_id">Location</label> <label for="location_id">Location</label>
<select required class="form-control" id="location_id" name="location_id"> <select required class="form-control" id="location_id" name="location_id">
@@ -31,6 +36,19 @@
</select> </select>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group">
<label for="min_stock_amount">Minimum stock amount</label>
<input required min="0" type="number" class="form-control" id="min_stock_amount" name="min_stock_amount" value="<?php if ($mode == 'edit') echo $product->min_stock_amount; else echo '0'; ?>">
<div class="help-block with-errors"></div>
</div>
<div class="form-group">
<label for="default_best_before_days">Default best before days<br /><span class="small text-muted">For purchases this amount of days will be added to today for the best before date suggestion</span></label>
<input required min="0" type="number" class="form-control" id="default_best_before_days" name="default_best_before_days" value="<?php if ($mode == 'edit') echo $product->default_best_before_days; else echo '0'; ?>">
<div class="help-block with-errors"></div>
</div>
<div class="form-group"> <div class="form-group">
<label for="qu_id_purchase">Quantity unit purchase</label> <label for="qu_id_purchase">Quantity unit purchase</label>
<select required class="form-control input-group-qu" id="qu_id_purchase" name="qu_id_purchase"> <select required class="form-control input-group-qu" id="qu_id_purchase" name="qu_id_purchase">
@@ -40,6 +58,7 @@
</select> </select>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="qu_id_stock">Quantity unit stock</label> <label for="qu_id_stock">Quantity unit stock</label>
<select required class="form-control input-group-qu" id="qu_id_stock" name="qu_id_stock"> <select required class="form-control input-group-qu" id="qu_id_stock" name="qu_id_stock">
@@ -49,12 +68,16 @@
</select> </select>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="qu_factor_purchase_to_stock">Factor purchase to stock quantity unit</label> <label for="qu_factor_purchase_to_stock">Factor purchase to stock quantity unit</label>
<input required min="1" type="number" class="form-control input-group-qu" id="qu_factor_purchase_to_stock" name="qu_factor_purchase_to_stock" value="<?php if ($mode == 'edit') echo $product->qu_factor_purchase_to_stock; else echo '1'; ?>"> <input required min="1" type="number" class="form-control input-group-qu" id="qu_factor_purchase_to_stock" name="qu_factor_purchase_to_stock" value="<?php if ($mode == 'edit') echo $product->qu_factor_purchase_to_stock; else echo '1'; ?>">
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<p id="qu-conversion-info" class="help-block text-muted"></p> <p id="qu-conversion-info" class="help-block text-muted"></p>
<button id="save-product-button" type="submit" class="btn btn-default">Save</button> <button id="save-product-button" type="submit" class="btn btn-default">Save</button>
</form> </form>
</div> </div>

View File

@@ -14,7 +14,7 @@
}, },
callback: function(result) callback: function(result)
{ {
if (result == true) if (result === true)
{ {
Grocy.FetchJson('/api/delete-object/products/' + $(e.target).attr('data-product-id'), Grocy.FetchJson('/api/delete-object/products/' + $(e.target).attr('data-product-id'),
function(result) function(result)

View File

@@ -1,4 +1,5 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header"> <h1 class="page-header">
Products Products
<a class="btn btn-default" href="/product/new" role="button"> <a class="btn btn-default" href="/product/new" role="button">
@@ -13,6 +14,7 @@
<th>#</th> <th>#</th>
<th>Name</th> <th>Name</th>
<th>Location</th> <th>Location</th>
<th>Min. stock amount</th>
<th>QU purchase</th> <th>QU purchase</th>
<th>QU stock</th> <th>QU stock</th>
<th>QU factor</th> <th>QU factor</th>
@@ -36,6 +38,9 @@
<td> <td>
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name; ?> <?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name; ?>
</td> </td>
<td>
<?php echo $product->min_stock_amount; ?>
</td>
<td> <td>
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name; ?> <?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name; ?>
</td> </td>
@@ -53,4 +58,5 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@@ -7,31 +7,49 @@
Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id, Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id,
function (productDetails) function (productDetails)
{ {
jsonForm.amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock; var amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock;
Grocy.FetchJson('/api/helper/uniqid', Grocy.FetchJson('/api/stock/add-product/' + jsonForm.product_id + '/' + amount + '?bestbeforedate=' + $('#best_before_date').val(),
function(uniqidResponse) function(result)
{ {
jsonForm.stock_id = uniqidResponse.uniqid; var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
Grocy.PostJson('/api/add-object/stock', jsonForm, {
function(result) var existingBarcodes = productDetails.product.barcode || '';
if (existingBarcodes.length === 0)
{ {
toastr.success('Added ' + jsonForm.amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' to stock'); productDetails.product.barcode = addBarcode;
$('#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('validate');
},
function(xhr)
{
console.error(xhr);
} }
); else
{
productDetails.product.barcode += ',' + addBarcode;
}
Grocy.PostJson('/api/edit-object/products/' + productDetails.product.id, productDetails.product,
function (result) { },
function(xhr)
{
console.error(xhr);
}
);
}
toastr.success('Added ' + amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' to stock');
if (addBarcode !== undefined)
{
window.location.href = '/purchase';
}
else
{
$('#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('validate');
}
}, },
function(xhr) function(xhr)
{ {
@@ -63,6 +81,12 @@ $('#product_id').on('change', function(e)
$('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || '')); $('#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').text((productDetails.last_used || 'never').substring(0, 10));
$('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || '')); $('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
$('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name);
if (productDetails.product.default_best_before_days.toString() !== '0')
{
$('#best_before_date').val(moment().add(productDetails.product.default_best_before_days, 'days').format('YYYY-MM-DD'));
}
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago'); Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago'); Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago');
@@ -90,7 +114,57 @@ $(function()
}); });
$('.datepicker').trigger('change'); $('.datepicker').trigger('change');
$('.combobox').combobox({ appendId: '_text_input' }); $('.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 (Grocy.GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
}
else
{
var optionElement = $("#product_id option:contains('" + input + "')").first();
if (input.length > 0 && optionElement.length === 0 && Grocy.GetUriParam('addbarcodetoselection') === undefined )
{
bootbox.dialog({
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
title: 'Create or assign product',
onEscape: function() { },
buttons: {
cancel: {
label: 'Cancel',
className: 'btn-default',
callback: function() { }
},
addnewproduct: {
label: 'Add as new product',
className: 'btn-success',
callback: function()
{
window.location.href = '/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
}
},
addbarcode: {
label: 'Add as barcode to existing product',
className: 'btn-info',
callback: function()
{
window.location.href = '/purchase?addbarcodetoselection=' + encodeURIComponent(input);
}
}
}
});
}
}
});
$('#amount').val(1); $('#amount').val(1);
$('#best_before_date').val(''); $('#best_before_date').val('');
@@ -112,6 +186,14 @@ $(function()
}); });
$('#purchase-form').validator('validate'); $('#purchase-form').validator('validate');
$('#best_before_date').on('focus', function(e)
{
if ($('#product_id_text_input').val().length === 0)
{
$('#product_id_text_input').focus();
}
});
$('#purchase-form input').keydown(function(event) $('#purchase-form input').keydown(function(event)
{ {
if (event.keyCode === 13) //Enter if (event.keyCode === 13) //Enter
@@ -123,6 +205,31 @@ $(function()
} }
} }
}); });
var prefillProduct = Grocy.GetUriParam('createdproduct');
if (prefillProduct !== undefined)
{
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = $("#product_id option:contains('" + prefillProduct + "')").first();
}
if (possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
$('#best_before_date').focus();
}
}
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
if (addBarcode !== undefined)
{
$('#addbarcodetoselection').text(addBarcode);
$('#flow-info-addbarcodetoselection').removeClass('hide');
}
}); });
$('#best_before_date-datepicker-button').on('click', function(e) $('#best_before_date-datepicker-button').on('click', function(e)

View File

@@ -2,16 +2,19 @@
<h1 class="page-header">Purchase</h1> <h1 class="page-header">Purchase</h1>
<form id="purchase-form"> <form id="purchase-form">
<div class="form-group"> <div class="form-group">
<label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label> <label for="product_id">Product&nbsp;&nbsp;<i class="fa fa-barcode"></i></label>
<select class="form-control combobox" id="product_id" name="product_id" required> <select class="form-control combobox" id="product_id" name="product_id" required>
<option value=""></option> <option value=""></option>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<option value="<?php echo $product->id; ?>"><?php echo $product->name; ?><?php if (!empty($product->barcode)) echo ' [' . $product->barcode . ']'; ?></option> <option data-additional-searchdata="<?php echo $product->barcode; ?>" value="<?php echo $product->id; ?>"><?php echo $product->name; ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
<div id="flow-info-addbarcodetoselection" class="text-muted small hide"><strong><span id="addbarcodetoselection"></span></strong> will be added to the list of barcodes for the selected product on submit.</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="best_before_date">Best before</label> <label for="best_before_date">Best before</label>
<div class="input-group date"> <div class="input-group date">
@@ -22,12 +25,15 @@
</div> </div>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="amount">Amount</label> <label for="amount">Amount&nbsp;&nbsp;<span id="new_amount_qu_unit" class="small text-muted"></span></label>
<input type="number" class="form-control" id="amount" name="amount" value="1" min="1" required> <input type="number" class="form-control" id="amount" name="amount" value="1" min="1" required>
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<button id="save-purchase-button" type="submit" class="btn btn-default">OK</button> <button id="save-purchase-button" type="submit" class="btn btn-default">OK</button>
</form> </form>
</div> </div>

View File

@@ -1,4 +1,5 @@
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main"> <div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2 main">
<h1 class="page-header"><?php echo $title; ?></h1> <h1 class="page-header"><?php echo $title; ?></h1>
<script>Grocy.EditMode = '<?php echo $mode; ?>';</script> <script>Grocy.EditMode = '<?php echo $mode; ?>';</script>
@@ -8,15 +9,20 @@
<?php endif; ?> <?php endif; ?>
<form id="quantityunit-form"> <form id="quantityunit-form">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $quantityunit->name; ?>" /> <input type="text" class="form-control" required id="name" name="name" value="<?php if ($mode == 'edit') echo $quantityunit->name; ?>" />
<div class="help-block with-errors"></div> <div class="help-block with-errors"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description</label> <label for="description">Description</label>
<textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $quantityunit->description; ?></textarea> <textarea class="form-control" rows="2" id="description" name="description"><?php if ($mode == 'edit') echo $quantityunit->description; ?></textarea>
</div> </div>
<button id="save-quantityunit-button" type="submit" class="btn btn-default">Save</button> <button id="save-quantityunit-button" type="submit" class="btn btn-default">Save</button>
</form> </form>
</div> </div>

View File

@@ -14,7 +14,7 @@
}, },
callback: function(result) callback: function(result)
{ {
if (result == true) if (result === true)
{ {
Grocy.FetchJson('/api/delete-object/quantity_units/' + $(e.target).attr('data-quantityunit-id'), Grocy.FetchJson('/api/delete-object/quantity_units/' + $(e.target).attr('data-quantityunit-id'),
function(result) function(result)

View File

@@ -1,4 +1,5 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header"> <h1 class="page-header">
Quantity units Quantity units
<a class="btn btn-default" href="/quantityunit/new" role="button"> <a class="btn btn-default" href="/quantityunit/new" role="button">
@@ -37,4 +38,5 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>