Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
580bd5ac0c | ||
|
2bf3448d18 | ||
|
e9bc51ca3d | ||
|
5ddae116e0 | ||
|
13566bc6fd | ||
|
642f95a3f8 | ||
|
5a1d21ef31 | ||
|
7dcd39f82f | ||
|
655aa89bd6 | ||
|
feb88ab685 | ||
|
79b4bad014 | ||
|
bcd5092427 | ||
|
554a83fa01 | ||
|
57acb62520 | ||
|
52ed5f2285 | ||
|
dd1d253ea5 | ||
|
e40979a874 | ||
|
2ddbc2656b | ||
|
7351fce395 |
201
.gitignore
vendored
@@ -1,202 +1,3 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
/public/bower_components
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
build/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
|
|
||||||
# Visual Studo 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding addin-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/packages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
|
|
||||||
# Windows Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Windows Store app package directory
|
|
||||||
AppPackages/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
*.[Cc]ache
|
|
||||||
ClientBin/
|
|
||||||
[Ss]tyle[Cc]op.*
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
node_modules/
|
|
||||||
bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
/bower_components
|
|
||||||
/vendor
|
/vendor
|
||||||
/.release
|
/.release
|
||||||
/composer.phar
|
|
||||||
/composer.lock
|
|
||||||
|
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"phpserver.relativePath": "public"
|
||||||
|
}
|
148
Grocy.php
@@ -1,148 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Grocy
|
|
||||||
{
|
|
||||||
private static $DbConnectionRaw;
|
|
||||||
/**
|
|
||||||
* @return PDO
|
|
||||||
*/
|
|
||||||
public static function GetDbConnectionRaw($doMigrations = false)
|
|
||||||
{
|
|
||||||
if ($doMigrations === true)
|
|
||||||
{
|
|
||||||
self::$DbConnectionRaw = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$DbConnectionRaw == null)
|
|
||||||
{
|
|
||||||
$pdo = new PDO('sqlite:' . __DIR__ . '/data/grocy.db');
|
|
||||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
if ($doMigrations === true)
|
|
||||||
{
|
|
||||||
Grocy::ExecuteDbStatement($pdo, "CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))");
|
|
||||||
GrocyDbMigrator::MigrateDb($pdo);
|
|
||||||
|
|
||||||
if (self::IsDemoInstallation())
|
|
||||||
{
|
|
||||||
GrocyDemoDataGenerator::PopulateDemoData($pdo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$DbConnectionRaw = $pdo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$DbConnectionRaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static $DbConnection;
|
|
||||||
/**
|
|
||||||
* @return LessQL\Database
|
|
||||||
*/
|
|
||||||
public static function GetDbConnection($doMigrations = false)
|
|
||||||
{
|
|
||||||
if ($doMigrations === true)
|
|
||||||
{
|
|
||||||
self::$DbConnection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self::$DbConnection == null)
|
|
||||||
{
|
|
||||||
self::$DbConnection = new LessQL\Database(self::GetDbConnectionRaw($doMigrations));
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$DbConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public static function ExecuteDbStatement(PDO $pdo, string $sql)
|
|
||||||
{
|
|
||||||
if ($pdo->exec($sql) === false)
|
|
||||||
{
|
|
||||||
throw new Exception($pdo->errorInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean|PDOStatement
|
|
||||||
*/
|
|
||||||
public static function ExecuteDbQuery(PDO $pdo, string $sql)
|
|
||||||
{
|
|
||||||
if (self::ExecuteDbStatement($pdo, $sql) === true)
|
|
||||||
{
|
|
||||||
return $pdo->query($sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public static function IsDemoInstallation()
|
|
||||||
{
|
|
||||||
return file_exists(__DIR__ . '/data/demo.txt');
|
|
||||||
}
|
|
||||||
|
|
||||||
private static $InstalledVersion;
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function GetInstalledVersion()
|
|
||||||
{
|
|
||||||
if (self::$InstalledVersion == null)
|
|
||||||
{
|
|
||||||
self::$InstalledVersion = preg_replace("/\r|\n/", '', file_get_contents(__DIR__ . '/version.txt'));
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,174 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyDbMigrator
|
|
||||||
{
|
|
||||||
public static function MigrateDb(PDO $pdo)
|
|
||||||
{
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 1, "
|
|
||||||
CREATE TABLE products (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
location_id INTEGER NOT NULL,
|
|
||||||
qu_id_purchase INTEGER NOT NULL,
|
|
||||||
qu_id_stock INTEGER NOT NULL,
|
|
||||||
qu_factor_purchase_to_stock REAL NOT NULL,
|
|
||||||
barcode TEXT,
|
|
||||||
min_stock_amount INTEGER NOT NULL DEFAULT 0,
|
|
||||||
default_best_before_days INTEGER NOT NULL DEFAULT 0,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 2, "
|
|
||||||
CREATE TABLE locations (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 3, "
|
|
||||||
CREATE TABLE quantity_units (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 4, "
|
|
||||||
CREATE TABLE stock (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
product_id INTEGER NOT NULL,
|
|
||||||
amount INTEGER NOT NULL,
|
|
||||||
best_before_date DATE,
|
|
||||||
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
|
|
||||||
stock_id TEXT NOT NULL,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 5, "
|
|
||||||
CREATE TABLE stock_log (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
product_id INTEGER NOT NULL,
|
|
||||||
amount INTEGER NOT NULL,
|
|
||||||
best_before_date DATE,
|
|
||||||
purchased_date DATE,
|
|
||||||
used_date DATE,
|
|
||||||
spoiled INTEGER NOT NULL DEFAULT 0,
|
|
||||||
stock_id TEXT NOT NULL,
|
|
||||||
transaction_type TEXT NOT NULL,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 6, "
|
|
||||||
INSERT INTO locations (name, description) VALUES ('DefaultLocation', 'This is the first default location, edit or delete it');
|
|
||||||
INSERT INTO quantity_units (name, description) VALUES ('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
|
|
||||||
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
|
|
||||||
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 7, "
|
|
||||||
CREATE VIEW stock_missing_products
|
|
||||||
AS
|
|
||||||
SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing
|
|
||||||
FROM products p
|
|
||||||
LEFT JOIN stock s
|
|
||||||
ON p.id = s.product_id
|
|
||||||
WHERE p.min_stock_amount != 0
|
|
||||||
GROUP BY p.id
|
|
||||||
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount;"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 8, "
|
|
||||||
CREATE VIEW stock_current
|
|
||||||
AS
|
|
||||||
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
|
||||||
FROM stock
|
|
||||||
GROUP BY product_id
|
|
||||||
ORDER BY MIN(best_before_date) ASC;"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 9, "
|
|
||||||
CREATE TABLE shopping_list (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
product_id INTEGER NOT NULL UNIQUE,
|
|
||||||
amount INTEGER NOT NULL DEFAULT 0,
|
|
||||||
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 10, "
|
|
||||||
CREATE TABLE habits (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
period_type TEXT NOT NULL,
|
|
||||||
period_days INTEGER,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 11, "
|
|
||||||
CREATE TABLE habits_log (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
habit_id INTEGER NOT NULL,
|
|
||||||
tracked_time DATETIME,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 12, "
|
|
||||||
CREATE VIEW habits_current
|
|
||||||
AS
|
|
||||||
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
|
||||||
FROM habits_log
|
|
||||||
GROUP BY habit_id
|
|
||||||
ORDER BY MAX(tracked_time) DESC;"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 13, "
|
|
||||||
CREATE TABLE batteries (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
name TEXT NOT NULL UNIQUE,
|
|
||||||
description TEXT,
|
|
||||||
used_in TEXT,
|
|
||||||
charge_interval_days INTEGER NOT NULL DEFAULT 0,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 14, "
|
|
||||||
CREATE TABLE battery_charge_cycles (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
|
||||||
battery_id TEXT NOT NULL,
|
|
||||||
tracked_time DATETIME,
|
|
||||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
|
||||||
)"
|
|
||||||
);
|
|
||||||
|
|
||||||
self::ExecuteMigrationWhenNeeded($pdo, 15, "
|
|
||||||
CREATE VIEW batteries_current
|
|
||||||
AS
|
|
||||||
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
|
||||||
FROM battery_charge_cycles
|
|
||||||
GROUP BY battery_id
|
|
||||||
ORDER BY MAX(tracked_time) DESC;"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql)
|
|
||||||
{
|
|
||||||
$rowCount = Grocy::ExecuteDbQuery($pdo, 'SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn();
|
|
||||||
if (intval($rowCount) === 0)
|
|
||||||
{
|
|
||||||
Grocy::ExecuteDbStatement($pdo, $sql);
|
|
||||||
Grocy::ExecuteDbStatement($pdo, 'INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyLogicBatteries
|
|
||||||
{
|
|
||||||
public static function GetCurrent()
|
|
||||||
{
|
|
||||||
$sql = 'SELECT * from batteries_current';
|
|
||||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetNextChargeTime(int $batteryId)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$battery = $db->batteries($batteryId);
|
|
||||||
$batteryLastLogRow = Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), "SELECT * from batteries_current WHERE battery_id = $batteryId LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
|
||||||
|
|
||||||
if ($battery->charge_interval_days > 0)
|
|
||||||
{
|
|
||||||
return date('Y-m-d H:i:s', strtotime('+' . $battery->charge_interval_days . ' day', strtotime($batteryLastLogRow->last_tracked_time)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return date('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetBatteryDetails(int $batteryId)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$battery = $db->batteries($batteryId);
|
|
||||||
$batteryChargeCylcesCount = $db->battery_charge_cycles()->where('battery_id', $batteryId)->count();
|
|
||||||
$batteryLastChargedTime = $db->battery_charge_cycles()->where('battery_id', $batteryId)->max('tracked_time');
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'battery' => $battery,
|
|
||||||
'last_charged' => $batteryLastChargedTime,
|
|
||||||
'charge_cycles_count' => $batteryChargeCylcesCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function TrackChargeCycle(int $batteryId, string $trackedTime)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$logRow = $db->battery_charge_cycles()->createRow(array(
|
|
||||||
'battery_id' => $batteryId,
|
|
||||||
'tracked_time' => $trackedTime
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyLogicHabits
|
|
||||||
{
|
|
||||||
const HABIT_TYPE_MANUALLY = 'manually';
|
|
||||||
const HABIT_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
|
||||||
|
|
||||||
public static function GetCurrentHabits()
|
|
||||||
{
|
|
||||||
$sql = 'SELECT * from habits_current';
|
|
||||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetNextHabitTime(int $habitId)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$habit = $db->habits($habitId);
|
|
||||||
$habitLastLogRow = Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), "SELECT * from habits_current WHERE habit_id = $habitId LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
|
||||||
|
|
||||||
switch ($habit->period_type)
|
|
||||||
{
|
|
||||||
case self::HABIT_TYPE_MANUALLY:
|
|
||||||
return date('Y-m-d H:i:s');
|
|
||||||
case self::HABIT_TYPE_DYNAMIC_REGULAR:
|
|
||||||
return date('Y-m-d H:i:s', strtotime('+' . $habit->period_days . ' day', strtotime($habitLastLogRow->last_tracked_time)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetHabitDetails(int $habitId)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$habit = $db->habits($habitId);
|
|
||||||
$habitTrackedCount = $db->habits_log()->where('habit_id', $habitId)->count();
|
|
||||||
$habitLastTrackedTime = $db->habits_log()->where('habit_id', $habitId)->max('tracked_time');
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'habit' => $habit,
|
|
||||||
'last_tracked' => $habitLastTrackedTime,
|
|
||||||
'tracked_count' => $habitTrackedCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function TrackHabit(int $habitId, string $trackedTime)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$logRow = $db->habits_log()->createRow(array(
|
|
||||||
'habit_id' => $habitId,
|
|
||||||
'tracked_time' => $trackedTime
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyPhpHelper
|
|
||||||
{
|
|
||||||
public static function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
|
|
||||||
{
|
|
||||||
foreach($array as $object)
|
|
||||||
{
|
|
||||||
if($object->{$propertyName} == $propertyValue)
|
|
||||||
{
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function SumArrayValue($array, $propertyName)
|
|
||||||
{
|
|
||||||
$sum = 0;
|
|
||||||
foreach($array as $object)
|
|
||||||
{
|
|
||||||
$sum += $object->{$propertyName};
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetClassConstants($className)
|
|
||||||
{
|
|
||||||
$r = new ReflectionClass($className);
|
|
||||||
return $r->getConstants();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,7 +11,7 @@ For now my main focus is on stock management, ERP your fridge!
|
|||||||
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
||||||
|
|
||||||
## 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 (7.0 or later required) enabled webserver (root is the `/public` directory), 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.
|
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
||||||
|
|
||||||
|
47
app.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use \Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use \Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
|
||||||
|
use \Grocy\Middleware\SessionAuthMiddleware;
|
||||||
|
use \Grocy\Middleware\JsonMiddleware;
|
||||||
|
use \Grocy\Middleware\CliMiddleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/data/config.php';
|
||||||
|
require_once __DIR__ . '/extensions.php';
|
||||||
|
|
||||||
|
// Setup base application
|
||||||
|
if (PHP_SAPI !== 'cli')
|
||||||
|
{
|
||||||
|
$appContainer = new \Slim\Container([
|
||||||
|
'settings' => [
|
||||||
|
'displayErrorDetails' => true,
|
||||||
|
'determineRouteBeforeAppMiddleware' => true
|
||||||
|
],
|
||||||
|
'view' => function($container)
|
||||||
|
{
|
||||||
|
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$app = new \Slim\App($appContainer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$app = new \Slim\App();
|
||||||
|
$app->add(\pavlakis\cli\CliRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add session handling if this is not a demo installation
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
if (!$applicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
$app->add(SessionAuthMiddleware::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/routes.php';
|
||||||
|
|
||||||
|
$app->run();
|
@@ -8,5 +8,6 @@ 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!bower.json -xr!publication_assets
|
"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!bower.json -xr!publication_assets
|
||||||
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\.htaccess"
|
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
|
||||||
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions
|
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
|
||||||
|
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions data\viewcache\*
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"slim/slim": "^3.8",
|
"slim/slim": "^3.8",
|
||||||
"slim/php-view": "^2.2",
|
|
||||||
"morris/lessql": "^0.3.4",
|
"morris/lessql": "^0.3.4",
|
||||||
"pavlakis/slim-cli": "^1.0"
|
"pavlakis/slim-cli": "^1.0",
|
||||||
|
"rubellum/slim-blade-view": "^0.1.1"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Grocy\\Services\\": "services/",
|
||||||
|
"Grocy\\Controllers\\": "controllers/",
|
||||||
|
"Grocy\\Middleware\\": "middleware/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1240
composer.lock
generated
Normal file
11
controllers/BaseApiController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
class BaseApiController extends BaseController
|
||||||
|
{
|
||||||
|
protected function ApiResponse($response)
|
||||||
|
{
|
||||||
|
return json_encode($response);
|
||||||
|
}
|
||||||
|
}
|
22
controllers/BaseController.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\DatabaseService;
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
|
||||||
|
class BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container) {
|
||||||
|
$this->AppContainer = $container;
|
||||||
|
|
||||||
|
$databaseService = new DatabaseService();
|
||||||
|
$this->Database = $databaseService->GetDbConnection();
|
||||||
|
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
$container->view->set('version', $applicationService->GetInstalledVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $AppContainer;
|
||||||
|
protected $Database;
|
||||||
|
}
|
32
controllers/BatteriesApiController.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\BatteriesService;
|
||||||
|
|
||||||
|
class BatteriesApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->BatteriesService = new BatteriesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $BatteriesService;
|
||||||
|
|
||||||
|
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$trackedTime = date('Y-m-d H:i:s');
|
||||||
|
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||||
|
{
|
||||||
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
|
||||||
|
}
|
||||||
|
}
|
62
controllers/BatteriesController.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\BatteriesService;
|
||||||
|
|
||||||
|
class BatteriesController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->BatteriesService = new BatteriesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $BatteriesService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$nextChargeTimes = array();
|
||||||
|
foreach($this->Database->batteries() as $battery)
|
||||||
|
{
|
||||||
|
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
||||||
|
'batteries' => $this->Database->batteries(),
|
||||||
|
'current' => $this->BatteriesService->GetCurrent(),
|
||||||
|
'nextChargeTimes' => $nextChargeTimes
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'batterytracking', [
|
||||||
|
'batteries' => $this->Database->batteries()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function BatteriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'batteries', [
|
||||||
|
'batteries' => $this->Database->batteries()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function BatteryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['batteryId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'batteryform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'batteryform', [
|
||||||
|
'battery' => $this->Database->batteries($args['batteryId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
controllers/CliController.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
use \Grocy\Services\DatabaseMigrationService;
|
||||||
|
|
||||||
|
class CliController extends BaseController
|
||||||
|
{
|
||||||
|
public function RecreateDemo(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
if ($applicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
$databaseMigrationService = new DatabaseMigrationService();
|
||||||
|
$databaseMigrationService->RecreateDemo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
controllers/GenericEntityApiController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
class GenericEntityApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
|
||||||
|
$newRow->save();
|
||||||
|
$success = $newRow->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
|
$row->update($request->getParsedBody());
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
|
$row->delete();
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
}
|
32
controllers/HabitsApiController.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\HabitsService;
|
||||||
|
|
||||||
|
class HabitsApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->HabitsService = new HabitsService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $HabitsService;
|
||||||
|
|
||||||
|
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$trackedTime = date('Y-m-d H:i:s');
|
||||||
|
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||||
|
{
|
||||||
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => $this->HabitsService->TrackHabit($args['habitId'], $trackedTime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
||||||
|
}
|
||||||
|
}
|
64
controllers/HabitsController.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\HabitsService;
|
||||||
|
|
||||||
|
class HabitsController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->HabitsService = new HabitsService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $HabitsService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$nextHabitTimes = array();
|
||||||
|
foreach($this->Database->habits() as $habit)
|
||||||
|
{
|
||||||
|
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
||||||
|
'habits' => $this->Database->habits(),
|
||||||
|
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
||||||
|
'nextHabitTimes' => $nextHabitTimes
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'habittracking', [
|
||||||
|
'habits' => $this->Database->habits()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function HabitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'habits', [
|
||||||
|
'habits' => $this->Database->habits()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function HabitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['habitId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'habitform', [
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'habitform', [
|
||||||
|
'habit' => $this->Database->habits($args['habitId']),
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
controllers/LoginController.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\SessionService;
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
use \Grocy\Services\DatabaseMigrationService;
|
||||||
|
use \Grocy\Services\DemoDataGeneratorService;
|
||||||
|
|
||||||
|
class LoginController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionService = new SessionService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionService;
|
||||||
|
|
||||||
|
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$postParams = $request->getParsedBody();
|
||||||
|
if (isset($postParams['username']) && isset($postParams['password']))
|
||||||
|
{
|
||||||
|
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
||||||
|
{
|
||||||
|
$sessionKey = $this->SessionService->CreateSession();
|
||||||
|
setcookie('grocy_session', $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
||||||
|
|
||||||
|
return $response->withRedirect('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect('/login?invalid=true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect('/login?invalid=true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function LoginPage(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$this->SessionService->RemoveSession($_COOKIE['grocy_session']);
|
||||||
|
return $response->withRedirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
// Schema migration is done here
|
||||||
|
$databaseMigrationService = new DatabaseMigrationService();
|
||||||
|
$databaseMigrationService->MigrateDatabase();
|
||||||
|
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
if ($applicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
$demoDataGeneratorService = new DemoDataGeneratorService();
|
||||||
|
$demoDataGeneratorService->PopulateDemoData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->withRedirect('/stockoverview');
|
||||||
|
}
|
||||||
|
}
|
77
controllers/StockApiController.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\StockService;
|
||||||
|
|
||||||
|
class StockApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->StockService = new StockService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $StockService;
|
||||||
|
|
||||||
|
public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$bestBeforeDate = date('Y-m-d');
|
||||||
|
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||||
|
{
|
||||||
|
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
||||||
|
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||||
|
{
|
||||||
|
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$spoiled = false;
|
||||||
|
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
|
||||||
|
{
|
||||||
|
$spoiled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
|
||||||
|
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||||
|
{
|
||||||
|
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$bestBeforeDate = date('Y-m-d');
|
||||||
|
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||||
|
{
|
||||||
|
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AddmissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$this->StockService->AddMissingProductsToShoppingList();
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
}
|
155
controllers/StockController.php
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\StockService;
|
||||||
|
|
||||||
|
class StockController extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->StockService = new StockService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $StockService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'stockoverview', [
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'currentStock' => $this->StockService->GetCurrentStock(),
|
||||||
|
'missingProducts' => $this->StockService->GetMissingProducts()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'purchase', [
|
||||||
|
'products' => $this->Database->products()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Consume(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'consume', [
|
||||||
|
'products' => $this->Database->products()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Inventory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'inventory', [
|
||||||
|
'products' => $this->Database->products()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'shoppinglist', [
|
||||||
|
'listItems' => $this->Database->shopping_list(),
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'missingProducts' => $this->StockService->GetMissingProducts()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ProductsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'products', [
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'locations' => $this->Database->locations(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function LocationsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'locations', [
|
||||||
|
'locations' => $this->Database->locations()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'quantityunits', [
|
||||||
|
'quantityunits' => $this->Database->quantity_units()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ProductEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['productId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productform', [
|
||||||
|
'locations' => $this->Database->locations(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productform', [
|
||||||
|
'product' => $this->Database->products($args['productId']),
|
||||||
|
'locations' => $this->Database->locations(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function LocationEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['locationId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'locationform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'locationform', [
|
||||||
|
'location' => $this->Database->locations($args['locationId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['quantityunitId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'quantityunitform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'quantityunitform', [
|
||||||
|
'quantityunit' => $this->Database->quantity_units($args['quantityunitId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ShoppingListItemEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['itemId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'shoppinglistform', [
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'shoppinglistform', [
|
||||||
|
'listItem' => $this->Database->shopping_list($args['itemId']),
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
data/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!viewcache
|
||||||
|
2
data/viewcache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
63
extensions.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
|
||||||
|
{
|
||||||
|
foreach($array as $object)
|
||||||
|
{
|
||||||
|
if($object->{$propertyName} == $propertyValue)
|
||||||
|
{
|
||||||
|
return $object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SumArrayValue($array, $propertyName)
|
||||||
|
{
|
||||||
|
$sum = 0;
|
||||||
|
foreach($array as $object)
|
||||||
|
{
|
||||||
|
$sum += $object->{$propertyName};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetClassConstants($className)
|
||||||
|
{
|
||||||
|
$r = new ReflectionClass($className);
|
||||||
|
return $r->getConstants();
|
||||||
|
}
|
531
index.php
@@ -1,531 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use \Psr\Http\Message\ServerRequestInterface as Request;
|
|
||||||
use \Psr\Http\Message\ResponseInterface as Response;
|
|
||||||
use Slim\Views\PhpRenderer;
|
|
||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
|
||||||
require_once __DIR__ . '/data/config.php';
|
|
||||||
require_once __DIR__ . '/Grocy.php';
|
|
||||||
require_once __DIR__ . '/GrocyDbMigrator.php';
|
|
||||||
require_once __DIR__ . '/GrocyDemoDataGenerator.php';
|
|
||||||
require_once __DIR__ . '/GrocyLogicStock.php';
|
|
||||||
require_once __DIR__ . '/GrocyLogicHabits.php';
|
|
||||||
require_once __DIR__ . '/GrocyLogicBatteries.php';
|
|
||||||
require_once __DIR__ . '/GrocyPhpHelper.php';
|
|
||||||
|
|
||||||
$app = new \Slim\App;
|
|
||||||
|
|
||||||
if (PHP_SAPI !== 'cli')
|
|
||||||
{
|
|
||||||
$app = new \Slim\App(new \Slim\Container([
|
|
||||||
'settings' => [
|
|
||||||
'displayErrorDetails' => true,
|
|
||||||
'determineRouteBeforeAppMiddleware' => true
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
$container = $app->getContainer();
|
|
||||||
$container['renderer'] = new PhpRenderer('./views');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli')
|
|
||||||
{
|
|
||||||
$app->add(new \pavlakis\cli\CliRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Grocy::IsDemoInstallation())
|
|
||||||
{
|
|
||||||
$sessionMiddleware = function(Request $request, Response $response, callable $next)
|
|
||||||
{
|
|
||||||
$route = $request->getAttribute('route');
|
|
||||||
$routeName = $route->getName();
|
|
||||||
|
|
||||||
if ((!isset($_COOKIE['grocy_session']) || !Grocy::IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login')
|
|
||||||
{
|
|
||||||
$response = $response->withRedirect('/login');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$response = $next($request, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
};
|
|
||||||
|
|
||||||
$app->add($sessionMiddleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
$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)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection(true); //For database schema migration
|
|
||||||
|
|
||||||
return $response->withRedirect('/stockoverview');
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/stockoverview', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Stock overview',
|
|
||||||
'contentPage' => 'stockoverview.php',
|
|
||||||
'products' => $db->products(),
|
|
||||||
'quantityunits' => $db->quantity_units(),
|
|
||||||
'currentStock' => GrocyLogicStock::GetCurrentStock(),
|
|
||||||
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/habitsoverview', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Habits overview',
|
|
||||||
'contentPage' => 'habitsoverview.php',
|
|
||||||
'habits' => $db->habits(),
|
|
||||||
'currentHabits' => GrocyLogicHabits::GetCurrentHabits(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/batteriesoverview', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Batteries overview',
|
|
||||||
'contentPage' => 'batteriesoverview.php',
|
|
||||||
'batteries' => $db->batteries(),
|
|
||||||
'current' => GrocyLogicBatteries::GetCurrent(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/purchase', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Purchase',
|
|
||||||
'contentPage' => 'purchase.php',
|
|
||||||
'products' => $db->products()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/consume', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Consume',
|
|
||||||
'contentPage' => 'consume.php',
|
|
||||||
'products' => $db->products()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/inventory', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Inventory',
|
|
||||||
'contentPage' => 'inventory.php',
|
|
||||||
'products' => $db->products()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/shoppinglist', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Shopping list',
|
|
||||||
'contentPage' => 'shoppinglist.php',
|
|
||||||
'listItems' => $db->shopping_list(),
|
|
||||||
'products' => $db->products(),
|
|
||||||
'quantityunits' => $db->quantity_units(),
|
|
||||||
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/habittracking', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Habit tracking',
|
|
||||||
'contentPage' => 'habittracking.php',
|
|
||||||
'habits' => $db->habits()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/batterytracking', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Battery tracking',
|
|
||||||
'contentPage' => 'batterytracking.php',
|
|
||||||
'batteries' => $db->batteries()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/products', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Products',
|
|
||||||
'contentPage' => 'products.php',
|
|
||||||
'products' => $db->products(),
|
|
||||||
'locations' => $db->locations(),
|
|
||||||
'quantityunits' => $db->quantity_units()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/locations', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Locations',
|
|
||||||
'contentPage' => 'locations.php',
|
|
||||||
'locations' => $db->locations()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/quantityunits', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Quantity units',
|
|
||||||
'contentPage' => 'quantityunits.php',
|
|
||||||
'quantityunits' => $db->quantity_units()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/habits', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Habits',
|
|
||||||
'contentPage' => 'habits.php',
|
|
||||||
'habits' => $db->habits()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/batteries', function(Request $request, Response $response) use($db)
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Batteries',
|
|
||||||
'contentPage' => 'batteries.php',
|
|
||||||
'batteries' => $db->batteries()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$app->get('/product/{productId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['productId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Create product',
|
|
||||||
'contentPage' => 'productform.php',
|
|
||||||
'locations' => $db->locations(),
|
|
||||||
'quantityunits' => $db->quantity_units(),
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit product',
|
|
||||||
'contentPage' => 'productform.php',
|
|
||||||
'product' => $db->products($args['productId']),
|
|
||||||
'locations' => $db->locations(),
|
|
||||||
'quantityunits' => $db->quantity_units(),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/location/{locationId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['locationId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Create location',
|
|
||||||
'contentPage' => 'locationform.php',
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit location',
|
|
||||||
'contentPage' => 'locationform.php',
|
|
||||||
'location' => $db->locations($args['locationId']),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['quantityunitId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Create quantity unit',
|
|
||||||
'contentPage' => 'quantityunitform.php',
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit quantity unit',
|
|
||||||
'contentPage' => 'quantityunitform.php',
|
|
||||||
'quantityunit' => $db->quantity_units($args['quantityunitId']),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/habit/{habitId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['habitId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Create habit',
|
|
||||||
'contentPage' => 'habitform.php',
|
|
||||||
'periodTypes' => GrocyPhpHelper::GetClassConstants('GrocyLogicHabits'),
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit habit',
|
|
||||||
'contentPage' => 'habitform.php',
|
|
||||||
'habit' => $db->habits($args['habitId']),
|
|
||||||
'periodTypes' => GrocyPhpHelper::GetClassConstants('GrocyLogicHabits'),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/battery/{batteryId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['batteryId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Create battery',
|
|
||||||
'contentPage' => 'batteryform.php',
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit battery',
|
|
||||||
'contentPage' => 'batteryform.php',
|
|
||||||
'battery' => $db->batteries($args['batteryId']),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->get('/shoppinglistitem/{itemId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
if ($args['itemId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Add shopping list item',
|
|
||||||
'contentPage' => 'shoppinglistform.php',
|
|
||||||
'products' => $db->products(),
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->renderer->render($response, '/layout.php', [
|
|
||||||
'title' => 'Edit shopping list item',
|
|
||||||
'contentPage' => 'shoppinglistform.php',
|
|
||||||
'listItem' => $db->shopping_list($args['itemId']),
|
|
||||||
'products' => $db->products(),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->group('/api', function() use($db)
|
|
||||||
{
|
|
||||||
$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
echo json_encode($db->{$args['entity']}());
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
echo json_encode($db->{$args['entity']}($args['objectId']));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->post('/add-object/{entity}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
$newRow = $db->{$args['entity']}()->createRow($request->getParsedBody());
|
|
||||||
$newRow->save();
|
|
||||||
$success = $newRow->isClean();
|
|
||||||
echo json_encode(array('success' => $success));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
$row = $db->{$args['entity']}($args['objectId']);
|
|
||||||
$row->update($request->getParsedBody());
|
|
||||||
$success = $row->isClean();
|
|
||||||
echo json_encode(array('success' => $success));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
|
||||||
{
|
|
||||||
$row = $db->{$args['entity']}($args['objectId']);
|
|
||||||
$row->delete();
|
|
||||||
$success = $row->isClean();
|
|
||||||
echo json_encode(array('success' => $success));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/stock/add-product/{productId}/{amount}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
$bestBeforeDate = date('Y-m-d');
|
|
||||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
|
||||||
{
|
|
||||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$transactionType = GrocyLogicStock::TRANSACTION_TYPE_PURCHASE;
|
|
||||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
|
||||||
{
|
|
||||||
$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)
|
|
||||||
{
|
|
||||||
$spoiled = false;
|
|
||||||
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
|
|
||||||
{
|
|
||||||
$spoiled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$transactionType = GrocyLogicStock::TRANSACTION_TYPE_CONSUME;
|
|
||||||
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('/stock/inventory-product/{productId}/{newAmount}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
$bestBeforeDate = date('Y-m-d');
|
|
||||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
|
||||||
{
|
|
||||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(array('success' => GrocyLogicStock::InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate)));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
echo json_encode(GrocyLogicStock::GetProductDetails($args['productId']));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/stock/get-current-stock', function(Request $request, Response $response)
|
|
||||||
{
|
|
||||||
echo json_encode(GrocyLogicStock::GetCurrentStock());
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/stock/add-missing-products-to-shoppinglist', function(Request $request, Response $response)
|
|
||||||
{
|
|
||||||
GrocyLogicStock::AddMissingProductsToShoppingList();
|
|
||||||
echo json_encode(array('success' => true));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/habits/track-habit/{habitId}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
$trackedTime = date('Y-m-d H:i:s');
|
|
||||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
|
||||||
{
|
|
||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(array('success' => GrocyLogicHabits::TrackHabit($args['habitId'], $trackedTime)));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/habits/get-habit-details/{habitId}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
echo json_encode(GrocyLogicHabits::GetHabitDetails($args['habitId']));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/batteries/track-charge-cycle/{batteryId}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
$trackedTime = date('Y-m-d H:i:s');
|
|
||||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
|
||||||
{
|
|
||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(array('success' => GrocyLogicBatteries::TrackChargeCycle($args['batteryId'], $trackedTime)));
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->get('/batteries/get-battery-details/{batteryId}', function(Request $request, Response $response, $args)
|
|
||||||
{
|
|
||||||
echo json_encode(GrocyLogicBatteries::GetBatteryDetails($args['batteryId']));
|
|
||||||
});
|
|
||||||
})->add(function($request, $response, $next)
|
|
||||||
{
|
|
||||||
$response = $next($request, $response);
|
|
||||||
return $response->withHeader('Content-Type', 'application/json');
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->group('/cli', function()
|
|
||||||
{
|
|
||||||
$this->get('/recreatedemo', function(Request $request, Response $response)
|
|
||||||
{
|
|
||||||
if (Grocy::IsDemoInstallation())
|
|
||||||
{
|
|
||||||
GrocyDemoDataGenerator::RecreateDemo();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})->add(function($request, $response, $next)
|
|
||||||
{
|
|
||||||
$response = $next($request, $response);
|
|
||||||
|
|
||||||
if (PHP_SAPI !== 'cli')
|
|
||||||
{
|
|
||||||
echo 'Please call this only from CLI';
|
|
||||||
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response->withHeader('Content-Type', 'text/plain');
|
|
||||||
});
|
|
||||||
|
|
||||||
$app->run();
|
|
12
middleware/BaseMiddleware.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
class BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container) {
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $container;
|
||||||
|
}
|
20
middleware/CliMiddleware.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
class CliMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
if (PHP_SAPI !== 'cli')
|
||||||
|
{
|
||||||
|
$response->write('Please call this only from CLI');
|
||||||
|
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response = $next($request, $response, $next);
|
||||||
|
return $response->withHeader('Content-Type', 'text/plain');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
middleware/JsonMiddleware.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
class JsonMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
$response = $next($request, $response, $next);
|
||||||
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
}
|
33
middleware/SessionAuthMiddleware.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\SessionService;
|
||||||
|
|
||||||
|
class SessionAuthMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
$route = $request->getAttribute('route');
|
||||||
|
$routeName = $route->getName();
|
||||||
|
|
||||||
|
if ($routeName === 'root')
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sessionService = new SessionService();
|
||||||
|
if ((!isset($_COOKIE['grocy_session']) || !$sessionService->IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login')
|
||||||
|
{
|
||||||
|
$response = $response->withRedirect('/login');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
13
migrations/0001.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE products (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
location_id INTEGER NOT NULL,
|
||||||
|
qu_id_purchase INTEGER NOT NULL,
|
||||||
|
qu_id_stock INTEGER NOT NULL,
|
||||||
|
qu_factor_purchase_to_stock REAL NOT NULL,
|
||||||
|
barcode TEXT,
|
||||||
|
min_stock_amount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
default_best_before_days INTEGER NOT NULL DEFAULT 0,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0002.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE locations (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0003.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE quantity_units (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
9
migrations/0004.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE stock (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
amount INTEGER NOT NULL,
|
||||||
|
best_before_date DATE,
|
||||||
|
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
|
||||||
|
stock_id TEXT NOT NULL,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
12
migrations/0005.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE stock_log (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
amount INTEGER NOT NULL,
|
||||||
|
best_before_date DATE,
|
||||||
|
purchased_date DATE,
|
||||||
|
used_date DATE,
|
||||||
|
spoiled INTEGER NOT NULL DEFAULT 0,
|
||||||
|
stock_id TEXT NOT NULL,
|
||||||
|
transaction_type TEXT NOT NULL,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
19
migrations/0006.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
INSERT INTO locations
|
||||||
|
(name, description)
|
||||||
|
VALUES
|
||||||
|
('DefaultLocation', 'This is the first default location, edit or delete it');
|
||||||
|
|
||||||
|
INSERT INTO quantity_units
|
||||||
|
(name, description)
|
||||||
|
VALUES
|
||||||
|
('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
|
||||||
|
|
||||||
|
INSERT INTO products
|
||||||
|
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
|
||||||
|
VALUES
|
||||||
|
('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
|
||||||
|
|
||||||
|
INSERT INTO products
|
||||||
|
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
|
||||||
|
VALUES
|
||||||
|
('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);
|
9
migrations/0007.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE VIEW stock_missing_products
|
||||||
|
AS
|
||||||
|
SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing
|
||||||
|
FROM products p
|
||||||
|
LEFT JOIN stock s
|
||||||
|
ON p.id = s.product_id
|
||||||
|
WHERE p.min_stock_amount != 0
|
||||||
|
GROUP BY p.id
|
||||||
|
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount
|
6
migrations/0008.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE VIEW stock_current
|
||||||
|
AS
|
||||||
|
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||||
|
FROM stock
|
||||||
|
GROUP BY product_id
|
||||||
|
ORDER BY MIN(best_before_date) ASC
|
7
migrations/0009.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE shopping_list (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
product_id INTEGER NOT NULL UNIQUE,
|
||||||
|
amount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
8
migrations/0010.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE habits (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
period_type TEXT NOT NULL,
|
||||||
|
period_days INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0011.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE habits_log (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
habit_id INTEGER NOT NULL,
|
||||||
|
tracked_time DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0012.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE VIEW habits_current
|
||||||
|
AS
|
||||||
|
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
||||||
|
FROM habits_log
|
||||||
|
GROUP BY habit_id
|
||||||
|
ORDER BY MAX(tracked_time) DESC
|
8
migrations/0013.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE batteries (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
used_in TEXT,
|
||||||
|
charge_interval_days INTEGER NOT NULL DEFAULT 0,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0014.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE battery_charge_cycles (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
battery_id TEXT NOT NULL,
|
||||||
|
tracked_time DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
6
migrations/0015.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE VIEW batteries_current
|
||||||
|
AS
|
||||||
|
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
||||||
|
FROM battery_charge_cycles
|
||||||
|
GROUP BY battery_id
|
||||||
|
ORDER BY MAX(tracked_time) DESC
|
1
migrations/0016.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE shopping_list RENAME TO shopping_list_old
|
8
migrations/0017.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE shopping_list (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
product_id INTEGER,
|
||||||
|
note TEXT,
|
||||||
|
amount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
4
migrations/0018.sql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO shopping_list
|
||||||
|
(product_id, amount, amount_autoadded, row_created_timestamp)
|
||||||
|
SELECT product_id, amount, amount_autoadded, row_created_timestamp
|
||||||
|
FROM shopping_list_old
|
1
migrations/0019.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE shopping_list_old
|
6
migrations/0020.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE sessions (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
session_key TEXT NOT NULL UNIQUE,
|
||||||
|
expires DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -22,8 +22,8 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #f5f5f5;
|
background-color: #e5e5e5;
|
||||||
border-right: 1px solid #5e5e5e;
|
border-right: 2px solid #d6d6d6;
|
||||||
min-width: 220px;
|
min-width: 220px;
|
||||||
max-width: 260px;
|
max-width: 260px;
|
||||||
}
|
}
|
||||||
@@ -42,13 +42,29 @@
|
|||||||
.nav-sidebar > li > a {
|
.nav-sidebar > li > a {
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar > li > a:hover {
|
||||||
|
box-shadow: inset 4px 0 0 #337ab7;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-sidebar > li > a:focus {
|
||||||
|
box-shadow: inset 4px 0 0 #ab2230;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar > .active > a,
|
.nav-sidebar > .active > a,
|
||||||
.nav-sidebar > .active > a:hover,
|
.nav-sidebar > .active > a:hover,
|
||||||
.nav-sidebar > .active > a:focus {
|
.nav-sidebar > .active > a:focus {
|
||||||
color: #fff;
|
background-color: #d6d6d6;
|
||||||
background-color: #5e5e5e;
|
box-shadow: inset 4px 0 0 #ab2230;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-default {
|
||||||
|
background-color: #e5e5e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
@@ -67,36 +83,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-copyright {
|
.nav-copyright {
|
||||||
color: #b3b3b1;
|
color: #a7a7a7;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: 'Arial', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.discrete-link {
|
.discrete-link {
|
||||||
color: inherit;
|
color: inherit !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.discrete-link:hover {
|
a.discrete-link:hover {
|
||||||
color: #5cb85c;
|
color: #337ab7 !important;
|
||||||
text-decoration: none;
|
text-decoration: none !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.discrete-link:focus {
|
a.discrete-link:focus {
|
||||||
color: #337ab7;
|
color: #ab2230 !important;
|
||||||
text-decoration: none;
|
text-decoration: none !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-fixed-top {
|
.navbar-fixed-top {
|
||||||
border-bottom: solid;
|
border-bottom: 2px solid;
|
||||||
border-color: #5e5e5e;
|
border-color: #d6d6d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
letter-spacing: -2px;
|
letter-spacing: -5px;
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-family: 'Arial', sans-serif;
|
color: inherit !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table td.fit-content,
|
.table td.fit-content,
|
||||||
@@ -144,3 +163,7 @@ a.discrete-link:focus {
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.well {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
3
public/index.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../app.php';
|
33
public/js/extensions.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
EmptyElementWhenMatches = function(selector, text)
|
||||||
|
{
|
||||||
|
if ($(selector).text() === text)
|
||||||
|
{
|
||||||
|
$(selector).text('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
String.prototype.contains = function(search)
|
||||||
|
{
|
||||||
|
return this.toLowerCase().indexOf(search.toLowerCase()) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
String.prototype.isEmpty = function()
|
||||||
|
{
|
||||||
|
return (this.length === 0 || !this.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -1,9 +1,13 @@
|
|||||||
var Grocy = { };
|
var Grocy = { };
|
||||||
|
Grocy.Components = { };
|
||||||
|
|
||||||
$(function()
|
$(function()
|
||||||
{
|
{
|
||||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ContentPage + "']");
|
if (!Grocy.ActiveNav.isEmpty())
|
||||||
menuItem.addClass('active');
|
{
|
||||||
|
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||||
|
menuItem.addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
$.timeago.settings.allowFuture = true;
|
$.timeago.settings.allowFuture = true;
|
||||||
$('time.timeago').timeago();
|
$('time.timeago').timeago();
|
||||||
@@ -67,37 +71,3 @@ Grocy.PostJson = function(url, jsonData, success, error)
|
|||||||
xhr.setRequestHeader('Content-type', 'application/json');
|
xhr.setRequestHeader('Content-type', 'application/json');
|
||||||
xhr.send(JSON.stringify(jsonData));
|
xhr.send(JSON.stringify(jsonData));
|
||||||
};
|
};
|
||||||
|
|
||||||
Grocy.EmptyElementWhenMatches = function(selector, text)
|
|
||||||
{
|
|
||||||
if ($(selector).text() === 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Grocy.Wait = function(ms)
|
|
||||||
{
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
@@ -39,23 +39,8 @@ $('#battery_id').on('change', function(e)
|
|||||||
|
|
||||||
if (batteryId)
|
if (batteryId)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/batteries/get-battery-details/' + batteryId,
|
Grocy.Components.BatteryCard.Refresh(batteryId);
|
||||||
function(batteryDetails)
|
$('#tracked_time').focus();
|
||||||
{
|
|
||||||
$('#selected-battery-name').text(batteryDetails.battery.name);
|
|
||||||
$('#selected-battery-last-charged').text((batteryDetails.last_charged || 'never'));
|
|
||||||
$('#selected-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
|
||||||
$('#selected-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
|
||||||
|
|
||||||
$('#tracked_time').focus();
|
|
||||||
|
|
||||||
Grocy.EmptyElementWhenMatches('#selected-battery-last-charged-timeago', 'NaN years ago');
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
20
public/viewjs/components/batterycard.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Grocy.Components.BatteryCard = { };
|
||||||
|
|
||||||
|
Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
||||||
|
{
|
||||||
|
Grocy.FetchJson('/api/batteries/get-battery-details/' + batteryId,
|
||||||
|
function(batteryDetails)
|
||||||
|
{
|
||||||
|
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
||||||
|
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || 'never'));
|
||||||
|
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
||||||
|
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', 'NaN years ago');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
91
public/viewjs/components/datepicker.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
$(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');
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#datepicker-timeago', 'NaN years ago');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.datepicker').on('keydown', function(e)
|
||||||
|
{
|
||||||
|
if (e.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
$('.datepicker').trigger('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.datepicker').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 input is empty and any arrow key is pressed, set date to today
|
||||||
|
if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
|
||||||
|
{
|
||||||
|
dateObj = moment(new Date(), 'YYYY-MM-DD', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.datepicker').on('change', function(e)
|
||||||
|
{
|
||||||
|
var value = $('.datepicker').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))
|
||||||
|
{
|
||||||
|
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||||
|
$('.datepicker').val(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#datepicker-timeago').text($.timeago($('.datepicker').val()));
|
||||||
|
EmptyElementWhenMatches('#datepicker-timeago', 'NaN years ago');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#datepicker-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
$('.datepicker').datepicker('show');
|
||||||
|
});
|
10
public/viewjs/components/datetimepicker.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
$(function()
|
||||||
|
{
|
||||||
|
$('.datetimepicker').datetimepicker(
|
||||||
|
{
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
showTodayButton: true,
|
||||||
|
calendarWeeks: true,
|
||||||
|
maxDate: moment()
|
||||||
|
});
|
||||||
|
});
|
20
public/viewjs/components/habitcard.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Grocy.Components.HabitCard = { };
|
||||||
|
|
||||||
|
Grocy.Components.HabitCard.Refresh = function (habitId)
|
||||||
|
{
|
||||||
|
Grocy.FetchJson('/api/habits/get-habit-details/' + habitId,
|
||||||
|
function(habitDetails)
|
||||||
|
{
|
||||||
|
$('#habitcard-habit-name').text(habitDetails.habit.name);
|
||||||
|
$('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || 'never'));
|
||||||
|
$('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
|
||||||
|
$('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0'));
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', 'NaN years ago');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
25
public/viewjs/components/productcard.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Grocy.Components.ProductCard = { };
|
||||||
|
|
||||||
|
Grocy.Components.ProductCard.Refresh = function(productId)
|
||||||
|
{
|
||||||
|
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||||
|
function(productDetails)
|
||||||
|
{
|
||||||
|
$('#productcard-product-name').text(productDetails.product.name);
|
||||||
|
$('#productcard-product-stock-amount').text(productDetails.stock_amount || '0');
|
||||||
|
$('#productcard-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
|
||||||
|
$('#productcard-product-stock-qu-name2').text(productDetails.quantity_unit_stock.name);
|
||||||
|
$('#productcard-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
|
||||||
|
$('#productcard-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
||||||
|
$('#productcard-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
|
||||||
|
$('#productcard-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', 'NaN years ago');
|
||||||
|
EmptyElementWhenMatches('#productcard-product-last-used-timeago', 'NaN years ago');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
@@ -44,24 +44,15 @@ $('#product_id').on('change', function(e)
|
|||||||
|
|
||||||
if (productId)
|
if (productId)
|
||||||
{
|
{
|
||||||
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||||
function (productDetails)
|
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-stock-qu-name2').text(productDetails.quantity_unit_stock.name);
|
|
||||||
$('#selected-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
|
|
||||||
$('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
|
||||||
$('#selected-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
|
|
||||||
$('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
|
||||||
$('#amount').attr('max', productDetails.stock_amount);
|
$('#amount').attr('max', productDetails.stock_amount);
|
||||||
$('#consume-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-used-timeago', 'NaN years ago');
|
|
||||||
|
|
||||||
if ((productDetails.stock_amount || 0) === 0)
|
if ((productDetails.stock_amount || 0) === 0)
|
||||||
{
|
{
|
||||||
$('#product_id').val('');
|
$('#product_id').val('');
|
||||||
@@ -124,6 +115,10 @@ $(function()
|
|||||||
{
|
{
|
||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$(this).select();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#consume-form input').keydown(function(event)
|
$('#consume-form input').keydown(function(event)
|
88
public/viewjs/habittracking.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
$('#save-habittracking-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonForm = $('#habittracking-form').serializeJSON();
|
||||||
|
|
||||||
|
Grocy.FetchJson('/api/habits/get-habit-details/' + jsonForm.habit_id,
|
||||||
|
function (habitDetails)
|
||||||
|
{
|
||||||
|
Grocy.FetchJson('/api/habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + $('#tracked_time').val(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
toastr.success('Tracked execution of habit ' + habitDetails.habit.name + ' on ' + $('#tracked_time').val());
|
||||||
|
|
||||||
|
$('#habit_id').val('');
|
||||||
|
$('#habit_id_text_input').focus();
|
||||||
|
$('#habit_id_text_input').val('');
|
||||||
|
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
$('#tracked_time').trigger('change');
|
||||||
|
$('#habit_id_text_input').trigger('change');
|
||||||
|
$('#habittracking-form').validator('validate');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#habit_id').on('change', function(e)
|
||||||
|
{
|
||||||
|
var habitId = $(e.target).val();
|
||||||
|
|
||||||
|
if (habitId)
|
||||||
|
{
|
||||||
|
Grocy.Components.HabitCard.Refresh(habitId);
|
||||||
|
$('#tracked_time').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(function()
|
||||||
|
{
|
||||||
|
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
$('#tracked_time').trigger('change');
|
||||||
|
|
||||||
|
$('#tracked_time').on('focus', function(e)
|
||||||
|
{
|
||||||
|
if ($('#habit_id_text_input').val().length === 0)
|
||||||
|
{
|
||||||
|
$('#habit_id_text_input').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.combobox').combobox({
|
||||||
|
appendId: '_text_input'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#habit_id').val('');
|
||||||
|
$('#habit_id_text_input').focus();
|
||||||
|
$('#habit_id_text_input').val('');
|
||||||
|
$('#habit_id_text_input').trigger('change');
|
||||||
|
|
||||||
|
$('#habittracking-form').validator();
|
||||||
|
$('#habittracking-form').validator('validate');
|
||||||
|
|
||||||
|
$('#habittracking-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
if ($('#habittracking-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#tracked_time').on('keypress', function(e)
|
||||||
|
{
|
||||||
|
$('#habittracking-form').validator('validate');
|
||||||
|
});
|
@@ -10,7 +10,7 @@
|
|||||||
Grocy.FetchJson('/api/stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
Grocy.FetchJson('/api/stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
|
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||||
if (addBarcode !== undefined)
|
if (addBarcode !== undefined)
|
||||||
{
|
{
|
||||||
var existingBarcodes = productDetails.product.barcode || '';
|
var existingBarcodes = productDetails.product.barcode || '';
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name);
|
toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name);
|
||||||
Grocy.Wait(1000);
|
|
||||||
|
|
||||||
if (addBarcode !== undefined)
|
if (addBarcode !== undefined)
|
||||||
{
|
{
|
||||||
@@ -70,24 +69,15 @@ $('#product_id').on('change', function(e)
|
|||||||
|
|
||||||
if (productId)
|
if (productId)
|
||||||
{
|
{
|
||||||
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||||
function(productDetails)
|
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').attr('not-equal', productDetails.stock_amount);
|
||||||
$('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
$('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
||||||
|
|
||||||
$('#new_amount').focus();
|
$('#new_amount').focus();
|
||||||
|
|
||||||
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
|
||||||
Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago');
|
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -99,19 +89,6 @@ $('#product_id').on('change', function(e)
|
|||||||
|
|
||||||
$(function()
|
$(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({
|
$('.combobox').combobox({
|
||||||
appendId: '_text_input'
|
appendId: '_text_input'
|
||||||
});
|
});
|
||||||
@@ -121,7 +98,7 @@ $(function()
|
|||||||
var input = $('#product_id_text_input').val().toString();
|
var input = $('#product_id_text_input').val().toString();
|
||||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
|
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
|
||||||
|
|
||||||
if (Grocy.GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
|
if (GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
|
||||||
{
|
{
|
||||||
$('#product_id').val(possibleOptionElement.val());
|
$('#product_id').val(possibleOptionElement.val());
|
||||||
$('#product_id').data('combobox').refresh();
|
$('#product_id').data('combobox').refresh();
|
||||||
@@ -130,7 +107,7 @@ $(function()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var optionElement = $("#product_id option:contains('" + input + "')").first();
|
var optionElement = $("#product_id option:contains('" + input + "')").first();
|
||||||
if (input.length > 0 && optionElement.length === 0 && Grocy.GetUriParam('addbarcodetoselection') === undefined )
|
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
||||||
{
|
{
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
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?',
|
||||||
@@ -228,6 +205,10 @@ $(function()
|
|||||||
{
|
{
|
||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$(this).select();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#inventory-form input').keydown(function(event)
|
$('#inventory-form input').keydown(function(event)
|
||||||
@@ -242,7 +223,7 @@ $(function()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var prefillProduct = Grocy.GetUriParam('createdproduct');
|
var prefillProduct = GetUriParam('createdproduct');
|
||||||
if (prefillProduct !== undefined)
|
if (prefillProduct !== undefined)
|
||||||
{
|
{
|
||||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();
|
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();
|
||||||
@@ -260,7 +241,7 @@ $(function()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
|
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||||
if (addBarcode !== undefined)
|
if (addBarcode !== undefined)
|
||||||
{
|
{
|
||||||
$('#addbarcodetoselection').text(addBarcode);
|
$('#addbarcodetoselection').text(addBarcode);
|
||||||
@@ -269,73 +250,29 @@ $(function()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#best_before_date-datepicker-button').on('click', function(e)
|
$('#new_amount').on('keypress', function(e)
|
||||||
{
|
{
|
||||||
$('.datepicker').datepicker('show');
|
$('#new_amount').trigger('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#best_before_date').on('change', function(e)
|
$('#best_before_date').on('change', function(e)
|
||||||
{
|
{
|
||||||
var value = $('#best_before_date').val();
|
$('#inventory-form').validator('validate');
|
||||||
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))
|
|
||||||
{
|
|
||||||
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)
|
$('#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 input is empty and any arrow key is pressed, set date to today
|
|
||||||
if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
|
|
||||||
{
|
|
||||||
dateObj = moment(new Date(), 'YYYY-MM-DD', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
$('#inventory-form').validator('validate');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#best_before_date').on('keydown', function(e)
|
||||||
|
{
|
||||||
|
if (e.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
$('#best_before_date').trigger('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#new_amount').on('change', function(e)
|
$('#new_amount').on('change', function(e)
|
||||||
{
|
{
|
||||||
if ($('#product_id').parent().hasClass('has-error'))
|
if ($('#product_id').parent().hasClass('has-error'))
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
$('#username').focus();
|
$('#username').focus();
|
||||||
|
|
||||||
if (Grocy.GetUriParam('invalid') === 'true')
|
if (GetUriParam('invalid') === 'true')
|
||||||
{
|
{
|
||||||
$('#login-error').text('Invalid credentials, please try again.');
|
$('#login-error').text('Invalid credentials, please try again.');
|
||||||
$('#login-error').show();
|
$('#login-error').show();
|
@@ -3,7 +3,7 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var redirectDestination = '/products';
|
var redirectDestination = '/products';
|
||||||
var returnTo = Grocy.GetUriParam('returnto');
|
var returnTo = GetUriParam('returnto');
|
||||||
if (returnTo !== undefined)
|
if (returnTo !== undefined)
|
||||||
{
|
{
|
||||||
redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val());
|
redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val());
|
||||||
@@ -69,14 +69,14 @@ $(function()
|
|||||||
$('#product-form').validator();
|
$('#product-form').validator();
|
||||||
$('#product-form').validator('validate');
|
$('#product-form').validator('validate');
|
||||||
|
|
||||||
var prefillName = Grocy.GetUriParam('prefillname');
|
var prefillName = GetUriParam('prefillname');
|
||||||
if (prefillName !== undefined)
|
if (prefillName !== undefined)
|
||||||
{
|
{
|
||||||
$('#name').val(prefillName);
|
$('#name').val(prefillName);
|
||||||
$('#name').focus();
|
$('#name').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefillBarcode = Grocy.GetUriParam('prefillbarcode');
|
var prefillBarcode = GetUriParam('prefillbarcode');
|
||||||
if (prefillBarcode !== undefined)
|
if (prefillBarcode !== undefined)
|
||||||
{
|
{
|
||||||
$('#barcode-taginput').tagsManager('pushTag', prefillBarcode);
|
$('#barcode-taginput').tagsManager('pushTag', prefillBarcode);
|