mirror of
https://github.com/grocy/grocy.git
synced 2025-09-18 18:46:51 +00:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
35f2f33ae3 | ||
|
f0f84b304b | ||
|
23146417e6 | ||
|
bd3155d39b | ||
|
b5fe0a642b | ||
|
b4b29878db | ||
|
9e68d38df8 | ||
|
574d363d7c | ||
|
69a011bc86 | ||
|
fe969c57c4 | ||
|
88d8b72c57 | ||
|
5639797c8d | ||
|
049a9cee06 | ||
|
14faf57a9e | ||
|
e19b548eff | ||
|
e3d84c40f7 | ||
|
50d49219a5 | ||
|
96209c852c |
4
.htaccess
Normal file
4
.htaccess
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [QSA,L]
|
44
Grocy.php
44
Grocy.php
@@ -101,4 +101,48 @@ class Grocy
|
|||||||
|
|
||||||
return self::$InstalledVersion;
|
return self::$InstalledVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function IsValidSession($sessionKey)
|
||||||
|
{
|
||||||
|
if ($sessionKey === null || empty($sessionKey))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return file_exists(__DIR__ . "/data/sessions/$sessionKey.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function CreateSession()
|
||||||
|
{
|
||||||
|
if (!file_exists(__DIR__ . '/data/sessions'))
|
||||||
|
{
|
||||||
|
mkdir(__DIR__ . '/data/sessions');
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
foreach (new FilesystemIterator(__DIR__ . '/data/sessions') as $file)
|
||||||
|
{
|
||||||
|
if ($now - $file->getCTime() >= 2678400) //31 days
|
||||||
|
{
|
||||||
|
unlink(__DIR__ . '/data/sessions/' . $file->getFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newSessionKey = uniqid() . uniqid() . uniqid();
|
||||||
|
file_put_contents(__DIR__ . "/data/sessions/$newSessionKey.txt", '');
|
||||||
|
return $newSessionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function RemoveSession($sessionKey)
|
||||||
|
{
|
||||||
|
unlink(__DIR__ . "/data/sessions/$sessionKey.txt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,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_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date');
|
||||||
$productLastUsed = $db->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->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);
|
||||||
@@ -80,7 +80,7 @@ class GrocyLogicStock
|
|||||||
$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
|
$potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('best_before_date', 'ASC')->orderBy('purchased_date', 'ASC')->fetchAll(); //First expiring first, then first in first out
|
||||||
|
|
||||||
if ($amount > $productStockAmount)
|
if ($amount > $productStockAmount)
|
||||||
{
|
{
|
||||||
|
15
README.md
15
README.md
@@ -13,5 +13,20 @@ Public demo of the latest version → [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 `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.
|
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.
|
||||||
|
|
||||||
|
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
||||||
|
|
||||||
|
## Notes about barcode readers
|
||||||
|
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter this is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
#### Dashboard
|
||||||
|

|
||||||
|
|
||||||
|
#### Purchase - with barcode scan
|
||||||
|

|
||||||
|
|
||||||
|
#### Consume - with manual search
|
||||||
|

|
||||||
|
|
||||||
## License
|
## License
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
@@ -7,5 +7,6 @@ mkdir "%releasePath%"
|
|||||||
for /f "tokens=*" %%a in ('type version.txt') do set version=%%a
|
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 -xr!bower.json
|
"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 -xr!bower.json -xr!publication_assets
|
||||||
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.*
|
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\.htaccess"
|
||||||
|
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"slim/slim": "^3.8",
|
"slim/slim": "^3.8",
|
||||||
"slim/php-view": "^2.2",
|
"slim/php-view": "^2.2",
|
||||||
"morris/lessql": "^0.3.4",
|
"morris/lessql": "^0.3.4"
|
||||||
"tuupola/slim-basic-auth": "^2.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,12 @@
|
|||||||
</RootNamespace>
|
</RootNamespace>
|
||||||
<ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids>
|
<ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids>
|
||||||
<AssemblyName>grocy</AssemblyName>
|
<AssemblyName>grocy</AssemblyName>
|
||||||
|
<SaveServerSettingsInUserFile>false</SaveServerSettingsInUserFile>
|
||||||
|
<Runtime>PHP</Runtime>
|
||||||
|
<RuntimeVersion>7.1</RuntimeVersion>
|
||||||
|
<EnvName>PHPDev</EnvName>
|
||||||
|
<PHPDevHostName>localhost</PHPDevHostName>
|
||||||
|
<PHPDevAutoPort>true</PHPDevAutoPort>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<IncludeDebugInformation>true</IncludeDebugInformation>
|
<IncludeDebugInformation>true</IncludeDebugInformation>
|
||||||
@@ -23,7 +29,8 @@
|
|||||||
<Compile Include="GrocyPhpHelper.php" />
|
<Compile Include="GrocyPhpHelper.php" />
|
||||||
<Compile Include="GrocyDbMigrator.php" />
|
<Compile Include="GrocyDbMigrator.php" />
|
||||||
<Compile Include="index.php" />
|
<Compile Include="index.php" />
|
||||||
<Compile Include="views\consumption.php" />
|
<Compile Include="views\consume.php" />
|
||||||
|
<Compile Include="views\login.php" />
|
||||||
<Compile Include="views\inventory.php" />
|
<Compile Include="views\inventory.php" />
|
||||||
<Compile Include="views\shoppinglistform.php" />
|
<Compile Include="views\shoppinglistform.php" />
|
||||||
<Compile Include="views\shoppinglist.php" />
|
<Compile Include="views\shoppinglist.php" />
|
||||||
@@ -41,6 +48,7 @@
|
|||||||
<Content Include="bower.json" />
|
<Content Include="bower.json" />
|
||||||
<None Include="build.bat" />
|
<None Include="build.bat" />
|
||||||
<Content Include="composer.json" />
|
<Content Include="composer.json" />
|
||||||
|
<Content Include="grocy.png" />
|
||||||
<Content Include="grocy.js" />
|
<Content Include="grocy.js" />
|
||||||
<None Include="README.md" />
|
<None Include="README.md" />
|
||||||
<Content Include="README.html">
|
<Content Include="README.html">
|
||||||
@@ -50,9 +58,10 @@
|
|||||||
<Content Include="robots.txt" />
|
<Content Include="robots.txt" />
|
||||||
<Content Include="style.css" />
|
<Content Include="style.css" />
|
||||||
<Content Include="version.txt" />
|
<Content Include="version.txt" />
|
||||||
<Content Include="views\consumption.js" />
|
<Content Include="views\consume.js" />
|
||||||
<Content Include="views\dashboard.js" />
|
<Content Include="views\dashboard.js" />
|
||||||
<Content Include="views\inventory.js" />
|
<Content Include="views\inventory.js" />
|
||||||
|
<Content Include="views\login.js" />
|
||||||
<Content Include="views\shoppinglistform.js" />
|
<Content Include="views\shoppinglistform.js" />
|
||||||
<Content Include="views\shoppinglist.js" />
|
<Content Include="views\shoppinglist.js" />
|
||||||
<Content Include="views\purchase.js" />
|
<Content Include="views\purchase.js" />
|
||||||
|
73
index.php
73
index.php
@@ -15,6 +15,7 @@ require_once __DIR__ . '/GrocyPhpHelper.php';
|
|||||||
$app = new \Slim\App(new \Slim\Container([
|
$app = new \Slim\App(new \Slim\Container([
|
||||||
'settings' => [
|
'settings' => [
|
||||||
'displayErrorDetails' => true,
|
'displayErrorDetails' => true,
|
||||||
|
'determineRouteBeforeAppMiddleware' => true
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
$container = $app->getContainer();
|
$container = $app->getContainer();
|
||||||
@@ -22,18 +23,65 @@ $container['renderer'] = new PhpRenderer('./views');
|
|||||||
|
|
||||||
if (!Grocy::IsDemoInstallation())
|
if (!Grocy::IsDemoInstallation())
|
||||||
{
|
{
|
||||||
$isHttpsReverseProxied = !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https';
|
$sessionMiddleware = function(Request $request, Response $response, callable $next)
|
||||||
$app->add(new \Slim\Middleware\HttpBasicAuthentication([
|
{
|
||||||
'realm' => 'grocy',
|
$route = $request->getAttribute('route');
|
||||||
'secure' => !$isHttpsReverseProxied,
|
$routeName = $route->getName();
|
||||||
'users' => [
|
|
||||||
HTTP_USER => HTTP_PASSWORD
|
if (!Grocy::IsValidSession($_COOKIE['grocy_session']) && $routeName !== 'login')
|
||||||
]
|
{
|
||||||
]));
|
$response = $response->withRedirect('/login');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
};
|
||||||
|
|
||||||
|
$app->add($sessionMiddleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
$db = Grocy::GetDbConnection();
|
$db = Grocy::GetDbConnection();
|
||||||
|
|
||||||
|
$app->get('/login', function(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
return $this->renderer->render($response, '/layout.php', [
|
||||||
|
'title' => 'Login',
|
||||||
|
'contentPage' => 'login.php'
|
||||||
|
]);
|
||||||
|
})->setName('login');
|
||||||
|
|
||||||
|
$app->post('/login', function(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
$postParams = $request->getParsedBody();
|
||||||
|
if (isset($postParams['username']) && isset($postParams['password']))
|
||||||
|
{
|
||||||
|
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
||||||
|
{
|
||||||
|
$sessionKey = Grocy::CreateSession();
|
||||||
|
setcookie('grocy_session', $sessionKey, time()+2592000); //30 days
|
||||||
|
|
||||||
|
return $response->withRedirect('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect('/login?invalid=true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect('/login?invalid=true');
|
||||||
|
}
|
||||||
|
})->setName('login');
|
||||||
|
|
||||||
|
$app->get('/logout', function(Request $request, Response $response)
|
||||||
|
{
|
||||||
|
Grocy::RemoveSession($_COOKIE['grocy_session']);
|
||||||
|
return $response->withRedirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
$app->get('/', function(Request $request, Response $response) use($db)
|
$app->get('/', function(Request $request, Response $response) use($db)
|
||||||
{
|
{
|
||||||
$db = Grocy::GetDbConnection(true); //For database schema migration
|
$db = Grocy::GetDbConnection(true); //For database schema migration
|
||||||
@@ -42,6 +90,7 @@ $app->get('/', function(Request $request, Response $response) use($db)
|
|||||||
'title' => 'Dashboard',
|
'title' => 'Dashboard',
|
||||||
'contentPage' => 'dashboard.php',
|
'contentPage' => 'dashboard.php',
|
||||||
'products' => $db->products(),
|
'products' => $db->products(),
|
||||||
|
'quantityunits' => $db->quantity_units(),
|
||||||
'currentStock' => GrocyLogicStock::GetCurrentStock(),
|
'currentStock' => GrocyLogicStock::GetCurrentStock(),
|
||||||
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
||||||
]);
|
]);
|
||||||
@@ -56,11 +105,11 @@ $app->get('/purchase', function(Request $request, Response $response) use($db)
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->get('/consumption', function(Request $request, Response $response) use($db)
|
$app->get('/consume', function(Request $request, Response $response) use($db)
|
||||||
{
|
{
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
return $this->renderer->render($response, '/layout.php', [
|
||||||
'title' => 'Consumption',
|
'title' => 'Consume',
|
||||||
'contentPage' => 'consumption.php',
|
'contentPage' => 'consume.php',
|
||||||
'products' => $db->products()
|
'products' => $db->products()
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -182,7 +231,7 @@ $app->get('/quantityunit/{quantityunitId}', function(Request $request, Response
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->get('/shoppinglist/{itemId}', function(Request $request, Response $response, $args) use($db)
|
$app->get('/shoppinglistitem/{itemId}', function(Request $request, Response $response, $args) use($db)
|
||||||
{
|
{
|
||||||
if ($args['itemId'] == 'new')
|
if ($args['itemId'] == 'new')
|
||||||
{
|
{
|
||||||
|
BIN
publication_assets/consume.gif
Normal file
BIN
publication_assets/consume.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
publication_assets/dashboard.png
Normal file
BIN
publication_assets/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
BIN
publication_assets/purchase-with-barcode.gif
Normal file
BIN
publication_assets/purchase-with-barcode.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
@@ -1 +1 @@
|
|||||||
1.1.0
|
1.4.0
|
@@ -1,8 +1,8 @@
|
|||||||
$('#save-consumption-button').on('click', function(e)
|
$('#save-consume-button').on('click', function(e)
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var jsonForm = $('#consumption-form').serializeJSON();
|
var jsonForm = $('#consume-form').serializeJSON();
|
||||||
|
|
||||||
var spoiled = 0;
|
var spoiled = 0;
|
||||||
if ($('#spoiled').is(':checked'))
|
if ($('#spoiled').is(':checked'))
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
$('#product_id_text_input').val('');
|
$('#product_id_text_input').val('');
|
||||||
$('#product_id_text_input').trigger('change');
|
$('#product_id_text_input').trigger('change');
|
||||||
$('#consumption-form').validator('validate');
|
$('#consume-form').validator('validate');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ $('#product_id').on('change', function(e)
|
|||||||
$('#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 || ''));
|
||||||
$('#amount').attr('max', productDetails.stock_amount);
|
$('#amount').attr('max', productDetails.stock_amount);
|
||||||
$('#consumption-form').validator('update');
|
$('#consume-form').validator('update');
|
||||||
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
||||||
|
|
||||||
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
||||||
@@ -114,8 +114,8 @@ $(function()
|
|||||||
$('#product_id_text_input').val('');
|
$('#product_id_text_input').val('');
|
||||||
$('#product_id_text_input').trigger('change');
|
$('#product_id_text_input').trigger('change');
|
||||||
|
|
||||||
$('#consumption-form').validator();
|
$('#consume-form').validator();
|
||||||
$('#consumption-form').validator('validate');
|
$('#consume-form').validator('validate');
|
||||||
|
|
||||||
$('#amount').on('focus', function(e)
|
$('#amount').on('focus', function(e)
|
||||||
{
|
{
|
||||||
@@ -125,11 +125,11 @@ $(function()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#consumption-form input').keydown(function(event)
|
$('#consume-form input').keydown(function(event)
|
||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
if ($('#consumption-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
|
if ($('#consume-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return false;
|
return false;
|
@@ -1,8 +1,8 @@
|
|||||||
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2 main">
|
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2 main">
|
||||||
|
|
||||||
<h1 class="page-header">Consumption</h1>
|
<h1 class="page-header">Consume</h1>
|
||||||
|
|
||||||
<form id="consumption-form">
|
<form id="consume-form">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="product_id">Product <i class="fa fa-barcode"></i></label>
|
<label for="product_id">Product <i class="fa fa-barcode"></i></label>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="save-consumption-button" type="submit" class="btn btn-default">OK</button>
|
<button id="save-consume-button" type="submit" class="btn btn-default">OK</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
@@ -30,7 +30,7 @@
|
|||||||
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name; ?>
|
<?php echo GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php echo $currentStockEntry->amount; ?>
|
<?php echo $currentStockEntry->amount . ' ' . GrocyPhpHelper::FindObjectInArrayByPropertyValue($quantityunits, 'id', GrocyPhpHelper::FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name; ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php echo $currentStockEntry->best_before_date; ?>
|
<?php echo $currentStockEntry->best_before_date; ?>
|
||||||
|
@@ -134,6 +134,8 @@ $(function()
|
|||||||
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
||||||
title: 'Create or assign product',
|
title: 'Create or assign product',
|
||||||
onEscape: function() { },
|
onEscape: function() { },
|
||||||
|
size: 'large',
|
||||||
|
backdrop: true,
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
@@ -155,6 +157,14 @@ $(function()
|
|||||||
{
|
{
|
||||||
window.location.href = '/inventory?addbarcodetoselection=' + encodeURIComponent(input);
|
window.location.href = '/inventory?addbarcodetoselection=' + encodeURIComponent(input);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
addnewproductwithbarcode: {
|
||||||
|
label: '<u><strong>A</strong></u>dd as new product + prefill barcode',
|
||||||
|
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
||||||
|
callback: function()
|
||||||
|
{
|
||||||
|
window.location.href = '/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).on('keypress', function(e)
|
}).on('keypress', function(e)
|
||||||
@@ -167,6 +177,10 @@ $(function()
|
|||||||
{
|
{
|
||||||
$('.add-new-product-dialog-button').click();
|
$('.add-new-product-dialog-button').click();
|
||||||
}
|
}
|
||||||
|
if (e.key === 'a' || e.key === 'A')
|
||||||
|
{
|
||||||
|
$('.add-new-product-with-barcode-dialog-button').click();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,6 +275,20 @@ $('#best_before_date-datepicker-button').on('click', function(e)
|
|||||||
$('#best_before_date').on('change', function(e)
|
$('#best_before_date').on('change', function(e)
|
||||||
{
|
{
|
||||||
var value = $('#best_before_date').val();
|
var value = $('#best_before_date').val();
|
||||||
|
var now = new Date();
|
||||||
|
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
|
||||||
|
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
|
||||||
|
|
||||||
|
if (value === 'x' || value === 'X')
|
||||||
|
{
|
||||||
|
value = '29991231';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
|
||||||
|
{
|
||||||
|
value = (new Date()).getFullYear().toString() + value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.length === 8 && $.isNumeric(value))
|
if (value.length === 8 && $.isNumeric(value))
|
||||||
{
|
{
|
||||||
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
<meta name="robots" content="noindex,nofollow" />
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
|
||||||
<meta name="author" content="Bernd Bestel (bernd@berrnd.de)" />
|
<meta name="author" content="Bernd Bestel (bernd@berrnd.de)" />
|
||||||
|
<link rel="icon" href="/grocy.png" />
|
||||||
|
|
||||||
<title><?php echo $title; ?> | grocy</title>
|
<title><?php echo $title; ?> | grocy</title>
|
||||||
|
|
||||||
@@ -37,6 +38,14 @@
|
|||||||
<a class="navbar-brand" href="/">grocy</a>
|
<a class="navbar-brand" href="/">grocy</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li>
|
||||||
|
<a class="discrete-link logout-button" href="/logout"><i class="fa fa-sign-out fa-fw"></i> Logout</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</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">
|
||||||
@@ -44,10 +53,10 @@
|
|||||||
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i> Dashboard</a>
|
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="purchase.php">
|
<li data-nav-for-page="purchase.php">
|
||||||
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> Record purchase</a>
|
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> Purchase</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="consumption.php">
|
<li data-nav-for-page="consume.php">
|
||||||
<a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i> Record consumption</a>
|
<a class="discrete-link" href="/consume"><i class="fa fa-cutlery fa-fw"></i> Consume</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="inventory.php">
|
<li data-nav-for-page="inventory.php">
|
||||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
||||||
@@ -58,14 +67,21 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li class="disabled"><a href="#"><strong>Manage master data</strong></a></li>
|
||||||
<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> Manage products</a>
|
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i> Products</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="locations.php">
|
<li data-nav-for-page="locations.php">
|
||||||
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> Manage locations</a>
|
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> Locations</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="quantityunits.php">
|
<li data-nav-for-page="quantityunits.php">
|
||||||
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> Manage quantity units</a>
|
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> Quantity units</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li>
|
||||||
|
<a class="discrete-link logout-button" href="/logout"><i class="fa fa-sign-out fa-fw"></i> Logout</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -83,10 +99,10 @@
|
|||||||
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i> Dashboard</a>
|
<a class="discrete-link" href="/"><i class="fa fa-tachometer fa-fw"></i> Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="purchase.php">
|
<li data-nav-for-page="purchase.php">
|
||||||
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> Record purchase</a>
|
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> Purchase</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="consumption.php">
|
<li data-nav-for-page="consume.php">
|
||||||
<a class="discrete-link" href="/consumption"><i class="fa fa-cutlery fa-fw"></i> Record consumption</a>
|
<a class="discrete-link" href="/consume"><i class="fa fa-cutlery fa-fw"></i> Consume</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="inventory.php">
|
<li data-nav-for-page="inventory.php">
|
||||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> Inventory</a>
|
||||||
@@ -97,14 +113,15 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="nav nav-sidebar">
|
<ul class="nav nav-sidebar">
|
||||||
|
<li class="disabled"><a href="#"><strong>Manage master data</strong></a></li>
|
||||||
<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> Manage products</a>
|
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i> Products</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="locations.php">
|
<li data-nav-for-page="locations.php">
|
||||||
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> Manage locations</a>
|
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> Locations</a>
|
||||||
</li>
|
</li>
|
||||||
<li data-nav-for-page="quantityunits.php">
|
<li data-nav-for-page="quantityunits.php">
|
||||||
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> Manage quantity units</a>
|
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> Quantity units</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
12
views/login.js
Normal file
12
views/login.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
$(function()
|
||||||
|
{
|
||||||
|
$('.logout-button').hide();
|
||||||
|
|
||||||
|
$('#username').focus();
|
||||||
|
|
||||||
|
if (Grocy.GetUriParam('invalid') === 'true')
|
||||||
|
{
|
||||||
|
$('#login-error').text('Invalid credentials, please try again.');
|
||||||
|
$('#login-error').show();
|
||||||
|
}
|
||||||
|
});
|
23
views/login.php
Normal file
23
views/login.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="col-md-4 col-md-offset-5 main">
|
||||||
|
|
||||||
|
<h1 class="page-header text-center">Login</h1>
|
||||||
|
|
||||||
|
<form method="post" action="/login" id="login-form">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Username</label>
|
||||||
|
<input type="text" class="form-control" required id="username" name="username" />
|
||||||
|
<div class="help-block with-errors"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Password</label>
|
||||||
|
<input type="password" class="form-control" required id="password" name="password" />
|
||||||
|
<div id="login-error" class="help-block with-errors"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="login-button" type="submit" class="btn btn-default">Login</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
@@ -75,6 +75,13 @@ $(function()
|
|||||||
$('#name').val(prefillName);
|
$('#name').val(prefillName);
|
||||||
$('#name').focus();
|
$('#name').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prefillBarcode = Grocy.GetUriParam('prefillbarcode');
|
||||||
|
if (prefillBarcode !== undefined)
|
||||||
|
{
|
||||||
|
$('#barcode-taginput').tagsManager('pushTag', prefillBarcode);
|
||||||
|
$('#name').focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.input-group-qu').on('change', function(e)
|
$('.input-group-qu').on('change', function(e)
|
||||||
|
@@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$('#amount').val(1);
|
$('#amount').val(0);
|
||||||
$('#best_before_date').val('');
|
$('#best_before_date').val('');
|
||||||
$('#product_id').val('');
|
$('#product_id').val('');
|
||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
@@ -87,6 +87,7 @@ $('#product_id').on('change', function(e)
|
|||||||
if (productDetails.product.default_best_before_days.toString() !== '0')
|
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'));
|
$('#best_before_date').val(moment().add(productDetails.product.default_best_before_days, 'days').format('YYYY-MM-DD'));
|
||||||
|
$('#best_before_date').trigger('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
||||||
@@ -139,6 +140,8 @@ $(function()
|
|||||||
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
||||||
title: 'Create or assign product',
|
title: 'Create or assign product',
|
||||||
onEscape: function() { },
|
onEscape: function() { },
|
||||||
|
size: 'large',
|
||||||
|
backdrop: true,
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
@@ -160,6 +163,14 @@ $(function()
|
|||||||
{
|
{
|
||||||
window.location.href = '/purchase?addbarcodetoselection=' + encodeURIComponent(input);
|
window.location.href = '/purchase?addbarcodetoselection=' + encodeURIComponent(input);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
addnewproductwithbarcode: {
|
||||||
|
label: '<u><strong>A</strong></u>dd as new product + prefill barcode',
|
||||||
|
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
||||||
|
callback: function()
|
||||||
|
{
|
||||||
|
window.location.href = '/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).on('keypress', function(e)
|
}).on('keypress', function(e)
|
||||||
@@ -172,12 +183,16 @@ $(function()
|
|||||||
{
|
{
|
||||||
$('.add-new-product-dialog-button').click();
|
$('.add-new-product-dialog-button').click();
|
||||||
}
|
}
|
||||||
|
if (e.key === 'a' || e.key === 'A')
|
||||||
|
{
|
||||||
|
$('.add-new-product-with-barcode-dialog-button').click();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#amount').val(1);
|
$('#amount').val(0);
|
||||||
$('#best_before_date').val('');
|
$('#best_before_date').val('');
|
||||||
$('#product_id').val('');
|
$('#product_id').val('');
|
||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
@@ -259,6 +274,19 @@ $('#best_before_date-datepicker-button').on('click', function(e)
|
|||||||
$('#best_before_date').on('change', function(e)
|
$('#best_before_date').on('change', function(e)
|
||||||
{
|
{
|
||||||
var value = $('#best_before_date').val();
|
var value = $('#best_before_date').val();
|
||||||
|
var now = new Date();
|
||||||
|
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
|
||||||
|
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
|
||||||
|
|
||||||
|
if (value === 'x' || value === 'X') {
|
||||||
|
value = '29991231';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
|
||||||
|
{
|
||||||
|
value = (new Date()).getFullYear().toString() + value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.length === 8 && $.isNumeric(value))
|
if (value.length === 8 && $.isNumeric(value))
|
||||||
{
|
{
|
||||||
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<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">
|
||||||
Shopping List
|
Shopping list
|
||||||
<a class="btn btn-default" href="/shoppinglist/new" role="button">
|
<a class="btn btn-default" href="/shoppinglistitem/new" role="button">
|
||||||
<i class="fa fa-plus"></i> Add
|
<i class="fa fa-plus"></i> Add
|
||||||
</a>
|
</a>
|
||||||
<a id="add-products-below-min-stock-amount" class="btn btn-info" href="#" role="button">
|
<a id="add-products-below-min-stock-amount" class="btn btn-info" href="#" role="button">
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<?php foreach ($listItems as $listItem) : ?>
|
<?php foreach ($listItems as $listItem) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fit-content">
|
<td class="fit-content">
|
||||||
<a class="btn btn-info" href="/shoppinglist/<?php echo $listItem->id; ?>" role="button">
|
<a class="btn btn-info" href="/shoppinglistitem/<?php echo $listItem->id; ?>" role="button">
|
||||||
<i class="fa fa-pencil"></i>
|
<i class="fa fa-pencil"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-danger shoppinglist-delete-button" href="#" role="button" data-shoppinglist-id="<?php echo $listItem->id; ?>">
|
<a class="btn btn-danger shoppinglist-delete-button" href="#" role="button" data-shoppinglist-id="<?php echo $listItem->id; ?>">
|
||||||
|
Reference in New Issue
Block a user