Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0954b5a741 | ||
|
02b6c3b721 | ||
|
6fa4e13ba2 | ||
|
9837f79f9c | ||
|
6e4cd22118 | ||
|
ca00dd8e2d | ||
|
5455ec7bde | ||
|
2e7af1b050 | ||
|
89bae8d25e | ||
|
5b5c272909 | ||
|
3e394a3840 | ||
|
ab8094e1c0 | ||
|
bbb5f1c7c7 | ||
|
b607f188af | ||
|
9ab1a674fe | ||
|
2f0a1391b7 | ||
|
a9a1358b08 | ||
|
4853174d03 | ||
|
538d789366 | ||
|
0751919b82 | ||
|
99b2a84667 | ||
|
9bd6aac09c | ||
|
7be35a90c1 | ||
|
eae5b8bad9 | ||
|
0c85342404 | ||
|
9ddcdb3ab2 | ||
|
1c537cf5da | ||
|
607a90cccc | ||
|
3d1c6fc5f0 | ||
|
df1d3677e8 | ||
|
4da2ac9b35 | ||
|
b4ae7d8538 | ||
|
870b679e0e | ||
|
4656a85732 | ||
|
4949913ccb | ||
|
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,90 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyDemoDataGenerator
|
|
||||||
{
|
|
||||||
public static function PopulateDemoData(PDO $pdo)
|
|
||||||
{
|
|
||||||
$rowCount = Grocy::ExecuteDbQuery($pdo, 'SELECT COUNT(*) FROM migrations WHERE migration = -1')->fetchColumn();
|
|
||||||
if (intval($rowCount) === 0)
|
|
||||||
{
|
|
||||||
$sql = "
|
|
||||||
UPDATE locations SET name = 'Vorratskammer', description = '' WHERE id = 1;
|
|
||||||
INSERT INTO locations (name) VALUES ('Süßigkeitenschrank'); --2
|
|
||||||
INSERT INTO locations (name) VALUES ('Konservenschrank'); --3
|
|
||||||
INSERT INTO locations (name) VALUES ('Kühlschrank'); --4
|
|
||||||
|
|
||||||
UPDATE quantity_units SET name = 'Stück' WHERE id = 1;
|
|
||||||
INSERT INTO quantity_units (name) VALUES ('Packung'); --2
|
|
||||||
INSERT INTO quantity_units (name) VALUES ('Glas'); --3
|
|
||||||
INSERT INTO quantity_units (name) VALUES ('Dose'); --4
|
|
||||||
INSERT INTO quantity_units (name) VALUES ('Becher'); --5
|
|
||||||
INSERT INTO quantity_units (name) VALUES ('Bund'); --6
|
|
||||||
|
|
||||||
DELETE FROM products WHERE id IN (1, 2);
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('Gummibärchen', 2, 2, 2, 1, 8); --3
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('Chips', 2, 2, 2, 1, 10); --4
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Eier', 1, 2, 1, 10); --5
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Nudeln', 1, 2, 2, 1); --6
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Essiggurken', 3, 3, 3, 1); --7
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gulaschsuppe', 3, 4, 4, 1); --8
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Joghurt', 4, 5, 5, 1); --9
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Käse', 4, 2, 2, 1); --10
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Aufschnitt', 4, 2, 2, 1); --11
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Paprika', 4, 1, 1, 1); --12
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Gurke', 4, 1, 1, 1); --13
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Radieschen', 4, 6, 6, 1); --14
|
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('Tomate', 4, 1, 1, 1); --15
|
|
||||||
|
|
||||||
INSERT INTO habits (name, period_type, period_days) VALUES ('Changed towels in the bathroom', 'manually', 5); --1
|
|
||||||
INSERT INTO habits (name, period_type, period_days) VALUES ('Cleaned the kitchen floor', 'dynamic-regular', 7); --2
|
|
||||||
|
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('Battery1', 'Warranty ends 2022', 'TV remote control'); --1
|
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('Battery2', 'Warranty ends 2022', 'Alarm clock'); --2
|
|
||||||
INSERT INTO batteries (name, description, used_in, charge_interval_days) VALUES ('Battery3', 'Warranty ends 2022', 'Heat remote control', 60); --3
|
|
||||||
|
|
||||||
INSERT INTO migrations (migration) VALUES (-1);
|
|
||||||
";
|
|
||||||
|
|
||||||
Grocy::ExecuteDbStatement($pdo, $sql);
|
|
||||||
|
|
||||||
GrocyLogicStock::AddProduct(3, 5, date('Y-m-d', strtotime('+180 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(4, 5, date('Y-m-d', strtotime('+180 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(5, 5, date('Y-m-d', strtotime('+20 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(6, 5, date('Y-m-d', strtotime('+600 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(7, 5, date('Y-m-d', strtotime('+800 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(8, 5, date('Y-m-d', strtotime('+900 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(9, 5, date('Y-m-d', strtotime('+14 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(10, 5, date('Y-m-d', strtotime('+21 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(11, 5, date('Y-m-d', strtotime('+10 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(12, 5, date('Y-m-d', strtotime('+2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(13, 5, date('Y-m-d', strtotime('-2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(14, 5, date('Y-m-d', strtotime('+2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddProduct(15, 5, date('Y-m-d', strtotime('-2 days')), GrocyLogicStock::TRANSACTION_TYPE_PURCHASE);
|
|
||||||
GrocyLogicStock::AddMissingProductsToShoppingList();
|
|
||||||
|
|
||||||
GrocyLogicHabits::TrackHabit(1, date('Y-m-d H:i:s', strtotime('-5 days')));
|
|
||||||
GrocyLogicHabits::TrackHabit(1, date('Y-m-d H:i:s', strtotime('-10 days')));
|
|
||||||
GrocyLogicHabits::TrackHabit(1, date('Y-m-d H:i:s', strtotime('-15 days')));
|
|
||||||
GrocyLogicHabits::TrackHabit(2, date('Y-m-d H:i:s', strtotime('-10 days')));
|
|
||||||
GrocyLogicHabits::TrackHabit(2, date('Y-m-d H:i:s', strtotime('-20 days')));
|
|
||||||
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-150 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-100 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-50 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(2, date('Y-m-d H:i:s', strtotime('-200 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(2, date('Y-m-d H:i:s', strtotime('-150 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(2, date('Y-m-d H:i:s', strtotime('-100 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(2, date('Y-m-d H:i:s', strtotime('-50 days')));
|
|
||||||
GrocyLogicBatteries::TrackChargeCycle(3, date('Y-m-d H:i:s', strtotime('-65 days')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function RecreateDemo()
|
|
||||||
{
|
|
||||||
unlink(__DIR__ . '/data/grocy.db');
|
|
||||||
|
|
||||||
$db = Grocy::GetDbConnectionRaw(true);
|
|
||||||
self::PopulateDemoData($db);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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,191 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class GrocyLogicStock
|
|
||||||
{
|
|
||||||
const TRANSACTION_TYPE_PURCHASE = 'purchase';
|
|
||||||
const TRANSACTION_TYPE_CONSUME = 'consume';
|
|
||||||
const TRANSACTION_TYPE_INVENTORY_CORRECTION = 'inventory-correction';
|
|
||||||
|
|
||||||
public static function GetCurrentStock()
|
|
||||||
{
|
|
||||||
$sql = 'SELECT * from stock_current';
|
|
||||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetMissingProducts()
|
|
||||||
{
|
|
||||||
$sql = 'SELECT * from stock_missing_products';
|
|
||||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function GetProductDetails(int $productId)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$product = $db->products($productId);
|
|
||||||
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
|
|
||||||
$productLastPurchased = $db->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date');
|
|
||||||
$productLastUsed = $db->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->max('used_date');
|
|
||||||
$quPurchase = $db->quantity_units($product->qu_id_purchase);
|
|
||||||
$quStock = $db->quantity_units($product->qu_id_stock);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'product' => $product,
|
|
||||||
'last_purchased' => $productLastPurchased,
|
|
||||||
'last_used' => $productLastUsed,
|
|
||||||
'stock_amount' => $productStockAmount,
|
|
||||||
'quantity_unit_purchase' => $quPurchase,
|
|
||||||
'quantity_unit_stock' => $quStock
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType)
|
|
||||||
{
|
|
||||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
$stockId = uniqid();
|
|
||||||
|
|
||||||
$logRow = $db->stock_log()->createRow(array(
|
|
||||||
'product_id' => $productId,
|
|
||||||
'amount' => $amount,
|
|
||||||
'best_before_date' => $bestBeforeDate,
|
|
||||||
'purchased_date' => date('Y-m-d'),
|
|
||||||
'stock_id' => $stockId,
|
|
||||||
'transaction_type' => $transactionType
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
$stockRow = $db->stock()->createRow(array(
|
|
||||||
'product_id' => $productId,
|
|
||||||
'amount' => $amount,
|
|
||||||
'best_before_date' => $bestBeforeDate,
|
|
||||||
'purchased_date' => date('Y-m-d'),
|
|
||||||
'stock_id' => $stockId,
|
|
||||||
));
|
|
||||||
$stockRow->save();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.AddProduct)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType)
|
|
||||||
{
|
|
||||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
|
|
||||||
$potentialStockEntries = $db->stock()->where('product_id', $productId)->orderBy('best_before_date', 'ASC')->orderBy('purchased_date', 'ASC')->fetchAll(); //First expiring first, then first in first out
|
|
||||||
|
|
||||||
if ($amount > $productStockAmount)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($potentialStockEntries as $stockEntry)
|
|
||||||
{
|
|
||||||
if ($amount == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($amount >= $stockEntry->amount) //Take the whole stock entry
|
|
||||||
{
|
|
||||||
$logRow = $db->stock_log()->createRow(array(
|
|
||||||
'product_id' => $stockEntry->product_id,
|
|
||||||
'amount' => $stockEntry->amount * -1,
|
|
||||||
'best_before_date' => $stockEntry->best_before_date,
|
|
||||||
'purchased_date' => $stockEntry->purchased_date,
|
|
||||||
'used_date' => date('Y-m-d'),
|
|
||||||
'spoiled' => $spoiled,
|
|
||||||
'stock_id' => $stockEntry->stock_id,
|
|
||||||
'transaction_type' => $transactionType
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
$amount -= $stockEntry->amount;
|
|
||||||
$stockEntry->delete();
|
|
||||||
}
|
|
||||||
else //Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
|
|
||||||
{
|
|
||||||
$logRow = $db->stock_log()->createRow(array(
|
|
||||||
'product_id' => $stockEntry->product_id,
|
|
||||||
'amount' => $amount * -1,
|
|
||||||
'best_before_date' => $stockEntry->best_before_date,
|
|
||||||
'purchased_date' => $stockEntry->purchased_date,
|
|
||||||
'used_date' => date('Y-m-d'),
|
|
||||||
'spoiled' => $spoiled,
|
|
||||||
'stock_id' => $stockEntry->stock_id,
|
|
||||||
'transaction_type' => $transactionType
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
$restStockAmount = $stockEntry->amount - $amount;
|
|
||||||
$amount = 0;
|
|
||||||
|
|
||||||
$stockEntry->update(array(
|
|
||||||
'amount' => $restStockAmount
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Transaction type $transactionType is not valid (GrocyLogicStock.ConsumeProduct)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function InventoryProduct(int $productId, int $newAmount, string $bestBeforeDate)
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
$productStockAmount = $db->stock()->where('product_id', $productId)->sum('amount');
|
|
||||||
|
|
||||||
if ($newAmount > $productStockAmount)
|
|
||||||
{
|
|
||||||
$amountToAdd = $newAmount - $productStockAmount;
|
|
||||||
self::AddProduct($productId, $amountToAdd, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
|
|
||||||
}
|
|
||||||
else if ($newAmount < $productStockAmount)
|
|
||||||
{
|
|
||||||
$amountToRemove = $productStockAmount - $newAmount;
|
|
||||||
self::ConsumeProduct($productId, $amountToRemove, false, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function AddMissingProductsToShoppingList()
|
|
||||||
{
|
|
||||||
$db = Grocy::GetDbConnection();
|
|
||||||
|
|
||||||
$missingProducts = self::GetMissingProducts();
|
|
||||||
foreach ($missingProducts as $missingProduct)
|
|
||||||
{
|
|
||||||
$product = $db->products()->where('id', $missingProduct->id)->fetch();
|
|
||||||
$amount = ceil($missingProduct->amount_missing / $product->qu_factor_purchase_to_stock);
|
|
||||||
|
|
||||||
$alreadyExistingEntry = $db->shopping_list()->where('product_id', $missingProduct->id)->fetch();
|
|
||||||
if ($alreadyExistingEntry) //Update
|
|
||||||
{
|
|
||||||
$alreadyExistingEntry->update(array(
|
|
||||||
'amount_autoadded' => $amount
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else //Insert
|
|
||||||
{
|
|
||||||
$shoppinglistRow = $db->shopping_list()->createRow(array(
|
|
||||||
'product_id' => $missingProduct->id,
|
|
||||||
'amount_autoadded' => $amount
|
|
||||||
));
|
|
||||||
$shoppinglistRow->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
53
README.md
@@ -1,22 +1,63 @@
|
|||||||
# grocy
|
# grocy
|
||||||
ERP beyond your fridge
|
ERP beyond your fridge
|
||||||
|
|
||||||
|
## Give it a try
|
||||||
|
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing.
|
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing.
|
||||||
|
|
||||||
## What it is about
|
## What it is about
|
||||||
For now my main focus is on stock management, ERP your fridge!
|
For now my main focus is on stock management, ERP your fridge!
|
||||||
|
|
||||||
# Give it a try
|
|
||||||
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 (currently only tested with PHP 7.2) enabled webserver (webservers root should point to 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.
|
||||||
|
|
||||||
|
Default login is user `admin` with password `admin` - see the `data/config.php` file. 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.
|
||||||
|
|
||||||
## Notes about barcode readers
|
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php`.
|
||||||
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter this is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
|
|
||||||
|
## How to update
|
||||||
|
Just overwrite everything with the latest release while keeping the `/data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (it will show up as an error if something is missing there).
|
||||||
|
|
||||||
|
## Things worth to know
|
||||||
|
|
||||||
|
### REST API & data model documentation
|
||||||
|
See the integrated Swagger UI instance on [/api](https://demo-en.grocy.info/api).
|
||||||
|
|
||||||
|
### Barcode readers
|
||||||
|
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter which is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
|
||||||
|
|
||||||
|
### Input shorthands for date fields
|
||||||
|
For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization.
|
||||||
|
The following shorthands are available:
|
||||||
|
- `MMDD` gets expanded to the given day on the current year in proper notation
|
||||||
|
- Example: `0517` will be converted to `2018-05-17`
|
||||||
|
- `YYYYMMDD` gets expanded to the proper ISO-8601 notation
|
||||||
|
- Example: `20190417` will be converted to `2019-04-17`
|
||||||
|
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
|
||||||
|
- Down/up arrow keys will increase/decrease the date by one day
|
||||||
|
- Right/left arrow keys will increase/decrease the date by 1 week
|
||||||
|
|
||||||
|
### Keyboard shorthands for buttons
|
||||||
|
Wherever a button contains a bold highlighted letter, this is a shortcut key.
|
||||||
|
Example: Button "Add as new **p**roduct" can be "pressed" by using the `P` key on your keyboard.
|
||||||
|
|
||||||
|
### Barcode lookup via external services
|
||||||
|
Products can be directly added to the database via looking them up against external services by a barcode.
|
||||||
|
This is currently only possible through the REST API.
|
||||||
|
There is no plugin included for any service, see the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php`.
|
||||||
|
|
||||||
|
### Database migrations
|
||||||
|
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
|
||||||
|
|
||||||
|
### Demo mode
|
||||||
|
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
|
||||||
|
|
||||||
|
### Other things
|
||||||
|
When the file `data/add_before_end_body.html` exists, the contents of the file be added just before `</body>` on every page, useful for your own JS/CSS without to have to modify the application itself.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
#### Dashboard
|
#### Dashboard
|
||||||
|
44
app.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use \Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
|
use \Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
|
||||||
|
use \Grocy\Helpers\UrlManager;
|
||||||
|
use \Grocy\Controllers\LoginController;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/data/config.php';
|
||||||
|
|
||||||
|
// Setup base application
|
||||||
|
$appContainer = new \Slim\Container([
|
||||||
|
'settings' => [
|
||||||
|
'displayErrorDetails' => true,
|
||||||
|
'determineRouteBeforeAppMiddleware' => true
|
||||||
|
],
|
||||||
|
'view' => function($container)
|
||||||
|
{
|
||||||
|
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
||||||
|
},
|
||||||
|
'LoginControllerInstance' => function($container)
|
||||||
|
{
|
||||||
|
return new LoginController($container, 'grocy_session');
|
||||||
|
},
|
||||||
|
'UrlManager' => function($container)
|
||||||
|
{
|
||||||
|
return new UrlManager(BASE_URL);
|
||||||
|
},
|
||||||
|
'ApiKeyHeaderName' => function($container)
|
||||||
|
{
|
||||||
|
return 'GROCY-API-KEY';
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
$app = new \Slim\App($appContainer);
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli')
|
||||||
|
{
|
||||||
|
$app->add(\pavlakis\cli\CliRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/routes.php';
|
||||||
|
|
||||||
|
$app->run();
|
@@ -17,6 +17,9 @@
|
|||||||
"jquery-timeago": "^1.6.1",
|
"jquery-timeago": "^1.6.1",
|
||||||
"toastr": "^2.1.3",
|
"toastr": "^2.1.3",
|
||||||
"tagmanager": "^3.0.2",
|
"tagmanager": "^3.0.2",
|
||||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47"
|
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
||||||
|
"swagger-ui": "^3.13.4",
|
||||||
|
"jquery-ui": "^1.12.1",
|
||||||
|
"bootstrap-side-navbar": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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,21 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
"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",
|
||||||
|
"tuupola/cors-middleware": "^0.7.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Grocy\\Services\\": "services/",
|
||||||
|
"Grocy\\Controllers\\": "controllers/",
|
||||||
|
"Grocy\\Middleware\\": "middleware/",
|
||||||
|
"Grocy\\Helpers\\": "helpers/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"helpers/extensions.php"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1610
composer.lock
generated
Normal file
@@ -1,4 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
# Login credentials
|
||||||
define('HTTP_USER', 'admin');
|
define('HTTP_USER', 'admin');
|
||||||
define('HTTP_PASSWORD', 'admin');
|
define('HTTP_PASSWORD', 'admin');
|
||||||
|
|
||||||
|
# Either "production" or "dev"
|
||||||
|
define('MODE', 'production');
|
||||||
|
|
||||||
|
# Either "en" or "de" or the filename (without extension) of
|
||||||
|
# one of the other available localization files in the "/localization" directory
|
||||||
|
define('CULTURE', 'en');
|
||||||
|
|
||||||
|
# The base url of your installation,
|
||||||
|
# should be just "/" when running directly under the root of a (sub)domain
|
||||||
|
# or for example "https:/example.com/grocy" when using a subdirectory
|
||||||
|
define('BASE_URL', '/');
|
||||||
|
|
||||||
|
# The plugin to use for external barcode lookups,
|
||||||
|
# must be the filename without .php extension and must be located in /data/plugins,
|
||||||
|
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
||||||
|
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
|
|
||||||
|
# If, however, your webserver does not support URL rewriting,
|
||||||
|
# set this to true
|
||||||
|
define('DISABLE_URL_REWRITING', false);
|
||||||
|
28
controllers/BaseApiController.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
class BaseApiController extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $OpenApiSpec;
|
||||||
|
|
||||||
|
protected function ApiResponse($data)
|
||||||
|
{
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function VoidApiActionResponse($response, $success = true, $status = 200, $errorMessage = '')
|
||||||
|
{
|
||||||
|
return $response->withStatus($status)->withJson(array(
|
||||||
|
'success' => $success,
|
||||||
|
'error_message' => $errorMessage
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
36
controllers/BaseController.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\DatabaseService;
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
use \Grocy\Services\LocalizationService;
|
||||||
|
|
||||||
|
class BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container) {
|
||||||
|
$databaseService = new DatabaseService();
|
||||||
|
$this->Database = $databaseService->GetDbConnection();
|
||||||
|
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
|
$container->view->set('version', $versionInfo->Version);
|
||||||
|
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
||||||
|
|
||||||
|
$localizationService = new LocalizationService(CULTURE);
|
||||||
|
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
||||||
|
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
|
||||||
|
{
|
||||||
|
return $localizationService->Localize($text, ...$placeholderValues);
|
||||||
|
});
|
||||||
|
$container->view->set('U', function($relativePath, $isResource = false) use($container)
|
||||||
|
{
|
||||||
|
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->AppContainer = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $AppContainer;
|
||||||
|
protected $Database;
|
||||||
|
}
|
47
controllers/BatteriesApiController.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
controllers/BatteriesController.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
||||||
|
$countOverdue = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
|
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
||||||
|
'batteries' => $this->Database->batteries(),
|
||||||
|
'current' => $this->BatteriesService->GetCurrent(),
|
||||||
|
'nextChargeTimes' => $nextChargeTimes,
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
||||||
|
'countOverdue' => $countOverdue
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
controllers/GenericEntityApiController.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
class GenericEntityApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
|
||||||
|
$newRow->save();
|
||||||
|
$success = $newRow->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
|
$row->update($request->getParsedBody());
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
|
$row->delete();
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function IsValidEntity($entity)
|
||||||
|
{
|
||||||
|
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
|
||||||
|
}
|
||||||
|
}
|
47
controllers/HabitsApiController.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
controllers/HabitsController.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
||||||
|
$countOverdue = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
|
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
||||||
|
'habits' => $this->Database->habits(),
|
||||||
|
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
||||||
|
'nextHabitTimes' => $nextHabitTimes,
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
||||||
|
'countOverdue' => $countOverdue
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
controllers/LoginController.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?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, string $sessionCookieName)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionService = new SessionService();
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionService;
|
||||||
|
protected $SessionCookieName;
|
||||||
|
|
||||||
|
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($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
||||||
|
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/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[$this->SessionCookieName]);
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
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($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetSessionCookieName()
|
||||||
|
{
|
||||||
|
return $this->SessionCookieName;
|
||||||
|
}
|
||||||
|
}
|
48
controllers/OpenApiController.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
use \Grocy\Services\ApiKeyService;
|
||||||
|
|
||||||
|
class OpenApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->ApiKeyService = new ApiKeyService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ApiKeyService;
|
||||||
|
|
||||||
|
public function DocumentationUi(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'openapiui');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DocumentationSpec(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
|
||||||
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
|
$this->OpenApiSpec->info->version = $versionInfo->Version;
|
||||||
|
$this->OpenApiSpec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->UrlManager->ConstructUrl('/manageapikeys'), $this->OpenApiSpec->info->description);
|
||||||
|
$this->OpenApiSpec->servers[0]->url = $this->AppContainer->UrlManager->ConstructUrl('/api');
|
||||||
|
|
||||||
|
return $this->ApiResponse($this->OpenApiSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'manageapikeys', [
|
||||||
|
'apiKeys' => $this->Database->api_keys()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CreateNewApiKey(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$newApiKey = $this->ApiKeyService->CreateApiKey();
|
||||||
|
$newApiKeyId = $this->ApiKeyService->GetApiKeyId($newApiKey);
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId"));
|
||||||
|
}
|
||||||
|
}
|
126
controllers/StockApiController.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$addFoundProduct = false;
|
||||||
|
if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1))
|
||||||
|
{
|
||||||
|
$addFoundProduct = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse($this->StockService->ExternalBarcodeLookup($args['barcode'], $addFoundProduct));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
controllers/StockController.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
$currentStock = $this->StockService->GetCurrentStock();
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countExpiringNextXDays = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('+5 days')), '<'));
|
||||||
|
$countAlreadyExpired = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
|
return $this->AppContainer->view->render($response, 'stockoverview', [
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'currentStock' => $currentStock,
|
||||||
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countExpiringNextXDays' => $countExpiringNextXDays,
|
||||||
|
'countAlreadyExpired' => $countAlreadyExpired
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
data/.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!viewcache
|
||||||
|
!plugins
|
||||||
|
3
data/plugins/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!DemoBarcodeLookupPlugin.php
|
78
data/plugins/DemoBarcodeLookupPlugin.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use \Grocy\Helpers\BaseBarcodeLookupPlugin;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This class must extend BaseBarcodeLookupPlugin (in namespace \Grocy\Helpers)
|
||||||
|
*/
|
||||||
|
class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
To use this plugin, configure it in data/config.php like this:
|
||||||
|
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
To try it:
|
||||||
|
Call the API function at /api/stock/external-barcode-lookup/{barcode}
|
||||||
|
|
||||||
|
When you also add ?add=true as a query parameter to the API call,
|
||||||
|
on a successful lookup the product is added to the database and in the output
|
||||||
|
the new product id is included (automatically, nothing to do here in the plugin)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Provided references:
|
||||||
|
|
||||||
|
$this->Locations contains all locations
|
||||||
|
$this->QuantityUnits contains all quantity units
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Useful hints:
|
||||||
|
|
||||||
|
Get a quantity unit by name:
|
||||||
|
$quantityUnit = FindObjectInArrayByPropertyValue($this->QuantityUnits, 'name', 'Piece');
|
||||||
|
|
||||||
|
Get a location by name:
|
||||||
|
$location = FindObjectInArrayByPropertyValue($this->Locations, 'name', 'Fridge');
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This class must implement the protected abstract function ExecuteLookup($barcode),
|
||||||
|
which is called with the barcode that needs to be looked up and must return an
|
||||||
|
associative array of the product model or null, when nothing was found for the barcode.
|
||||||
|
|
||||||
|
The returned array must contain at least these properties:
|
||||||
|
array(
|
||||||
|
'name' => '',
|
||||||
|
'location_id' => 1, // A valid id of a location object, check against $this->Locations
|
||||||
|
'qu_id_purchase' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
||||||
|
'qu_id_stock' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
||||||
|
'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same
|
||||||
|
'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
protected function ExecuteLookup($barcode)
|
||||||
|
{
|
||||||
|
if ($barcode === 'x') // Demonstration when nothing is found
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
elseif ($barcode === 'e') // Demonstration when an error occurred
|
||||||
|
{
|
||||||
|
throw new \Exception('This is the error message from the plugin...');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'name' => 'LookedUpProduct_' . RandomString(5),
|
||||||
|
'location_id' => $this->Locations[0]->id,
|
||||||
|
'qu_id_purchase' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_id_stock' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_factor_purchase_to_stock' => 1,
|
||||||
|
'barcode' => $barcode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
data/viewcache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
103
grocy.js
@@ -1,103 +0,0 @@
|
|||||||
var Grocy = { };
|
|
||||||
|
|
||||||
$(function()
|
|
||||||
{
|
|
||||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ContentPage + "']");
|
|
||||||
menuItem.addClass('active');
|
|
||||||
|
|
||||||
$.timeago.settings.allowFuture = true;
|
|
||||||
$('time.timeago').timeago();
|
|
||||||
});
|
|
||||||
|
|
||||||
Grocy.FetchJson = function(url, success, error)
|
|
||||||
{
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function()
|
|
||||||
{
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE)
|
|
||||||
{
|
|
||||||
if (xhr.status === 200)
|
|
||||||
{
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
success(JSON.parse(xhr.responseText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
error(xhr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
xhr.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
Grocy.PostJson = function(url, jsonData, success, error)
|
|
||||||
{
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function()
|
|
||||||
{
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE)
|
|
||||||
{
|
|
||||||
if (xhr.status === 200)
|
|
||||||
{
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
success(JSON.parse(xhr.responseText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
error(xhr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('POST', url, true);
|
|
||||||
xhr.setRequestHeader('Content-type', 'application/json');
|
|
||||||
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));
|
|
||||||
}
|
|
1384
grocy.openapi.json
Normal file
80
helpers/BaseBarcodeLookupPlugin.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Helpers;
|
||||||
|
|
||||||
|
abstract class BaseBarcodeLookupPlugin
|
||||||
|
{
|
||||||
|
final public function __construct($locations, $quantityUnits)
|
||||||
|
{
|
||||||
|
$this->Locations = $locations;
|
||||||
|
$this->QuantityUnits = $quantityUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $Locations;
|
||||||
|
protected $QuantityUnits;
|
||||||
|
|
||||||
|
abstract protected function ExecuteLookup($barcode);
|
||||||
|
|
||||||
|
final public function Lookup($barcode)
|
||||||
|
{
|
||||||
|
$pluginOutput = $this->ExecuteLookup($barcode);
|
||||||
|
|
||||||
|
if ($pluginOutput === null)
|
||||||
|
{
|
||||||
|
return $pluginOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin must return an associative array
|
||||||
|
if (!is_array($pluginOutput))
|
||||||
|
{
|
||||||
|
throw new \Exception('Plugin output must be an associative array');
|
||||||
|
}
|
||||||
|
if (!IsAssociativeArray($pluginOutput)) // $pluginOutput is at least an indexed array here
|
||||||
|
{
|
||||||
|
throw new \Exception('Plugin output must be an associative array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for minimum needed properties
|
||||||
|
$minimunNeededProperties = array(
|
||||||
|
'name',
|
||||||
|
'location_id',
|
||||||
|
'qu_id_purchase',
|
||||||
|
'qu_id_stock',
|
||||||
|
'qu_factor_purchase_to_stock',
|
||||||
|
'barcode'
|
||||||
|
);
|
||||||
|
foreach ($minimunNeededProperties as $prop)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($prop, $pluginOutput))
|
||||||
|
{
|
||||||
|
throw new \Exception("Plugin output does not provide needed property $prop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $pluginOutput contains all needed properties here
|
||||||
|
|
||||||
|
// Check referenced entity ids are valid
|
||||||
|
$locationId = $pluginOutput['location_id'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $locationId is not a valid location id");
|
||||||
|
}
|
||||||
|
$quIdPurchase = $pluginOutput['qu_id_purchase'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $quIdPurchase is not a valid quantity unit id");
|
||||||
|
}
|
||||||
|
$quIdStock = $pluginOutput['qu_id_stock'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $quIdStock is not a valid quantity unit id");
|
||||||
|
}
|
||||||
|
$quFactor = $pluginOutput['qu_factor_purchase_to_stock'];
|
||||||
|
if (empty($quFactor) || !is_numeric($quFactor))
|
||||||
|
{
|
||||||
|
throw new \Exception('Quantity unit factor is empty or not a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pluginOutput;
|
||||||
|
}
|
||||||
|
}
|
37
helpers/UrlManager.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Helpers;
|
||||||
|
|
||||||
|
class UrlManager
|
||||||
|
{
|
||||||
|
public function __construct(string $basePath)
|
||||||
|
{
|
||||||
|
if ($basePath === '/')
|
||||||
|
{
|
||||||
|
$this->BasePath = $this->GetBaseUrl();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->BasePath = $basePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $BasePath;
|
||||||
|
|
||||||
|
public function ConstructUrl($relativePath, $isResource = false)
|
||||||
|
{
|
||||||
|
if (DISABLE_URL_REWRITING === false || $isResource === true)
|
||||||
|
{
|
||||||
|
return rtrim($this->BasePath, '/') . $relativePath;
|
||||||
|
}
|
||||||
|
else // Is not a resource and URL rewriting is disabled
|
||||||
|
{
|
||||||
|
return rtrim($this->BasePath, '/') . '/index.php' . $relativePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetBaseUrl()
|
||||||
|
{
|
||||||
|
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
||||||
|
}
|
||||||
|
}
|
112
helpers/extensions.php
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?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 FindAllItemsInArrayByValue($array, $value, $operator = '==')
|
||||||
|
{
|
||||||
|
$returnArray = array();
|
||||||
|
|
||||||
|
foreach($array as $item)
|
||||||
|
{
|
||||||
|
switch($operator)
|
||||||
|
{
|
||||||
|
case '==':
|
||||||
|
if($item == $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if($item > $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
if($item < $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||||
|
{
|
||||||
|
$randomString = '';
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
{
|
||||||
|
$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsAssociativeArray(array $array)
|
||||||
|
{
|
||||||
|
$keys = array_keys($array);
|
||||||
|
return array_keys($keys) !== $keys;
|
||||||
|
}
|
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();
|
|
176
localization/de.php
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Stock overview' => 'Bestand',
|
||||||
|
'#1 products with #2 units in stock' => '#1 Produkte (#2 Einheiten) vorrätig',
|
||||||
|
'#1 products expiring within the next #2 days' => '#1 Produkte laufen innerhalb der nächsten #2 Tage ab',
|
||||||
|
'#1 products are already expired' => '#1 Produkte sind bereits abgelaufen',
|
||||||
|
'#1 products are below defined min. stock amount' => '#1 Produkte sind unter Mindestbestand',
|
||||||
|
'Product' => 'Produkt',
|
||||||
|
'Amount' => 'Menge',
|
||||||
|
'Next best before date' => 'Nächstes MHD',
|
||||||
|
'Logout' => 'Abmelden',
|
||||||
|
'Habits overview' => 'Gewohnheiten',
|
||||||
|
'Batteries overview' => 'Batterien',
|
||||||
|
'Purchase' => 'Einkauf',
|
||||||
|
'Consume' => 'Verbrauch',
|
||||||
|
'Inventory' => 'Inventur',
|
||||||
|
'Shopping list' => 'Einkaufszettel',
|
||||||
|
'Habit tracking' => 'Gewohnheit-Ausführung',
|
||||||
|
'Battery tracking' => 'Batterie-Ladzyklus',
|
||||||
|
'Products' => 'Produkte',
|
||||||
|
'Locations' => 'Standorte',
|
||||||
|
'Quantity units' => 'Mengeneinheiten',
|
||||||
|
'Habits' => 'Gewohnheiten',
|
||||||
|
'Batteries' => 'Batterien',
|
||||||
|
'Habit' => 'Gewohnheit',
|
||||||
|
'Next estimated tracking' => 'Nächste geplante Ausführung',
|
||||||
|
'Last tracked' => 'Zuletzt ausgeführt',
|
||||||
|
'Battery' => 'Batterie',
|
||||||
|
'Last charged' => 'Zuletzt geladen',
|
||||||
|
'Next planned charge cycle' => 'Nächster geplanter Ladezyklus',
|
||||||
|
'Best before' => 'MHD',
|
||||||
|
'OK' => 'OK',
|
||||||
|
'Product overview' => 'Produktübersicht',
|
||||||
|
'Stock quantity unit' => 'Mengeneinheit Bestand',
|
||||||
|
'Stock amount' => 'Bestand',
|
||||||
|
'Last purchased' => 'Zuletzt gekauft',
|
||||||
|
'Last used' => 'Zuletzt benutzt',
|
||||||
|
'Spoiled' => 'Verdorben',
|
||||||
|
'Barcode lookup is disabled' => 'Barcode-Suche ist deaktiviert',
|
||||||
|
'will be added to the list of barcodes for the selected product on submit' => 'wird der Liste der Barcodes für das ausgewählte Produkt beim Speichern hinzugefügt',
|
||||||
|
'New amount' => 'Neue Menge',
|
||||||
|
'Note' => 'Notiz',
|
||||||
|
'Tracked time' => 'Ausführungszeit',
|
||||||
|
'Habit overview' => 'Gewohnheit Übersicht',
|
||||||
|
'Tracked count' => 'Ausführungsanzahl',
|
||||||
|
'Battery overview' => 'Batterie Übersicht',
|
||||||
|
'Charge cycles count' => 'Ladezyklen',
|
||||||
|
'Create shopping list item' => 'Einkaufszettel Eintrag erstellen',
|
||||||
|
'Edit shopping list item' => 'Einkaufszettel Eintrag bearbeiten',
|
||||||
|
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 Einheiten wurden automatisch hinzugefügt und gelten zusätzlich der hier eingegebenen Menge',
|
||||||
|
'Save' => 'Speichern',
|
||||||
|
'Add' => 'Hinzufügen',
|
||||||
|
'Name' => 'Name',
|
||||||
|
'Location' => 'Standort',
|
||||||
|
'Min. stock amount' => 'Mindestbestand',
|
||||||
|
'QU purchase' => 'ME Einkauf',
|
||||||
|
'QU stock' => 'ME Bestand',
|
||||||
|
'QU factor' => 'ME-Faktor',
|
||||||
|
'Description' => 'Beschreibung',
|
||||||
|
'Create product' => 'Produkt erstellen',
|
||||||
|
'Barcode(s)' => 'Barcode(s)',
|
||||||
|
'Minimum stock amount' => 'Mindestbestand',
|
||||||
|
'Default best before days' => 'Standard Haltbarkeit in Tagen',
|
||||||
|
'Quantity unit purchase' => 'Mengeneinheit Einkauf',
|
||||||
|
'Quantity unit stock' => 'Mengeneinheit Bestand',
|
||||||
|
'Factor purchase to stock quantity unit' => 'Faktor Mengeneinheit Einkauf zu Mengeneinheit Bestand',
|
||||||
|
'Create location' => 'Standort erstellen',
|
||||||
|
'Create quantity unit' => 'Mengeneinheit erstellen',
|
||||||
|
'Period type' => 'Periodentyp',
|
||||||
|
'Period days' => 'Tage/Periode',
|
||||||
|
'Create habit' => 'Gewohnheit erstellen',
|
||||||
|
'Used in' => 'Benutzt in',
|
||||||
|
'Create battery' => 'Batterie erstellen',
|
||||||
|
'Edit battery' => 'Batterie bearbeiten',
|
||||||
|
'Edit habit' => 'Gewohnheit bearbeiten',
|
||||||
|
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
|
||||||
|
'Edit product' => 'Produkt bearbeiten',
|
||||||
|
'Edit location' => 'Standort bearbeiten',
|
||||||
|
'Record data' => 'Daten erfassen',
|
||||||
|
'Manage master data' => 'Stammdaten verwalten',
|
||||||
|
'This will apply to added products' => 'Dies gilt für hinzugefügte Produkte',
|
||||||
|
'never' => 'nie',
|
||||||
|
'Add products that are below defined min. stock amount' => 'Produkte unter Mindestbestand hinzufügen',
|
||||||
|
'For purchases this amount of days will be added to today for the best before date suggestion' => 'Bei Einkäufen wird hierauf basierend das MHD vorausgefüllt',
|
||||||
|
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Das bedeutet 1 #1 im Einkauf entsprechen #2 #3 im Bestand',
|
||||||
|
'Login' => 'Anmelden',
|
||||||
|
'Username' => 'Benutzername',
|
||||||
|
'Password' => 'Passwort',
|
||||||
|
'Invalid credentials, please try again' => 'Ungültige Zugangsdaten, bitte versuche es erneut',
|
||||||
|
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
||||||
|
'Yes' => 'Ja',
|
||||||
|
'No' => 'Nein',
|
||||||
|
'Are you sure to delete habit "#1"?' => 'Gewohnheit "#1" wirklich löschen?',
|
||||||
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
|
||||||
|
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
||||||
|
'Cancel' => 'Abbrechen',
|
||||||
|
'Add as new product' => 'Als neues Produkt hinzufügen',
|
||||||
|
'Add as barcode to existing product' => 'Barcode vorhandenem Produkt zuweisen',
|
||||||
|
'Add as new product and prefill barcode' => 'Neues Produkt erstellen und Barcode vorbelegen',
|
||||||
|
'Are you sure to delete quantity unit "#1"?' => 'Mengeneinheit "#1" wirklich löschen?',
|
||||||
|
'Are you sure to delete product "#1"?' => 'Produkt "#1" wirklich löschen?',
|
||||||
|
'Are you sure to delete location "#1"?' => 'Standort "#1" wirklich löschen?',
|
||||||
|
'Manage API keys' => 'API-Keys verwalten',
|
||||||
|
'REST API & data model documentation' => 'REST-API & Datenmodell Dokumentation',
|
||||||
|
'API keys' => 'API-Keys',
|
||||||
|
'Create new API key' => 'Neuen API-Key erstellen',
|
||||||
|
'API key' => 'API-Key',
|
||||||
|
'Expires' => 'Läuft ab',
|
||||||
|
'Created' => 'Erstellt',
|
||||||
|
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
|
||||||
|
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
|
||||||
|
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
|
||||||
|
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Gewohnheit #1 Tage nach der letzten Ausführung geplant wird',
|
||||||
|
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
||||||
|
'About grocy' => 'Über grocy',
|
||||||
|
'Close' => 'Schließen',
|
||||||
|
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
|
||||||
|
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
|
||||||
|
'#1 habits are due to be done within the next #2 days' => '#1 Gewohnheiten stehen in den nächsten #2 Tagen an',
|
||||||
|
'#1 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig',
|
||||||
|
'Released on' => 'Veröffentlicht am',
|
||||||
|
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
||||||
|
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
|
||||||
|
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
|
||||||
|
'Tracked execution of habit #1 on #2' => 'Ausführung von #1 am #2 erfasst',
|
||||||
|
'Tracked charge cylce of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
|
||||||
|
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
||||||
|
'All' => 'Alle',
|
||||||
|
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
||||||
|
'Track execution of habit #1' => 'Erfasse eine Ausführung von #1',
|
||||||
|
|
||||||
|
//Constants
|
||||||
|
'manually' => 'Manuell',
|
||||||
|
'dynamic-regular' => 'Dynamisch regelmäßig',
|
||||||
|
|
||||||
|
//Technical component translations
|
||||||
|
'timeago_locale' => 'de',
|
||||||
|
'timeago_nan' => 'vor NaN Jahren',
|
||||||
|
'moment_locale' => 'de',
|
||||||
|
'bootstrap_datepicker_locale' => 'de',
|
||||||
|
'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}',
|
||||||
|
|
||||||
|
//Demo data
|
||||||
|
'Cookies' => 'Cookies',
|
||||||
|
'Chocolate' => 'Schokolade',
|
||||||
|
'Pantry' => 'Vorratskammer',
|
||||||
|
'Candy cupboard' => 'Süßigkeitenschrank',
|
||||||
|
'Tinned food cupboard' => 'Konservenschrank',
|
||||||
|
'Fridge' => 'Kühlschrank',
|
||||||
|
'Piece' => 'Stück',
|
||||||
|
'Pack' => 'Packung',
|
||||||
|
'Glass' => 'Glas',
|
||||||
|
'Tin' => 'Dose',
|
||||||
|
'Can' => 'Becher',
|
||||||
|
'Bunch' => 'Bund',
|
||||||
|
'Gummy bears' => 'Gummibärchen',
|
||||||
|
'Crisps' => 'Chips',
|
||||||
|
'Eggs' => 'Eier',
|
||||||
|
'Noodles' => 'Nudeln',
|
||||||
|
'Pickles' => 'Essiggurken',
|
||||||
|
'Gulash soup' => 'Gulaschsuppe',
|
||||||
|
'Yogurt' => 'Joghurt',
|
||||||
|
'Cheese' => 'Käse',
|
||||||
|
'Cold cuts' => 'Aufschnitt',
|
||||||
|
'Paprika' => 'Paprika',
|
||||||
|
'Cucumber' => 'Gurke',
|
||||||
|
'Radish' => 'Radieschen',
|
||||||
|
'Tomato' => 'Tomaten',
|
||||||
|
'Changed towels in the bathroom' => 'Handtücher im Bad gewechselt',
|
||||||
|
'Cleaned the kitchen floor' => 'Küchenboden gewischt',
|
||||||
|
'Warranty ends' => 'Garantie endet',
|
||||||
|
'TV remote control' => 'TV Fernbedienung',
|
||||||
|
'Alarm clock' => 'Wecker',
|
||||||
|
'Heat remote control' => 'Fernbedienung Heizung'
|
||||||
|
);
|
14
localization/en.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
//Constants
|
||||||
|
'manually' => 'Manually',
|
||||||
|
'dynamic-regular' => 'Dynamic regular',
|
||||||
|
|
||||||
|
//Technical component translations
|
||||||
|
'timeago_locale' => 'en',
|
||||||
|
'timeago_nan' => 'NaN years ago',
|
||||||
|
'moment_locale' => '',
|
||||||
|
'bootstrap_datepicker_locale' => '',
|
||||||
|
'datatables_localization' => '{"sEmptyTable":"No data available in table","sInfo":"Showing _START_ to _END_ of _TOTAL_ entries","sInfoEmpty":"Showing 0 to 0 of 0 entries","sInfoFiltered":"(filtered from _MAX_ total entries)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Show _MENU_ entries","sLoadingRecords":"Loading...","sProcessing":"Processing...","sSearch":"Search:","sZeroRecords":"No matching records found","oPaginate":{"sFirst":"First","sLast":"Last","sNext":"Next","sPrevious":"Previous"},"oAria":{"sSortAscending":": activate to sort column ascending","sSortDescending":": activate to sort column descending"}}'
|
||||||
|
);
|
58
middleware/ApiKeyAuthMiddleware.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\SessionService;
|
||||||
|
use \Grocy\Services\ApiKeyService;
|
||||||
|
|
||||||
|
class ApiKeyAuthMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container, string $sessionCookieName, string $apiKeyHeaderName)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
|
$this->ApiKeyHeaderName = $apiKeyHeaderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionCookieName;
|
||||||
|
protected $ApiKeyHeaderName;
|
||||||
|
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
$route = $request->getAttribute('route');
|
||||||
|
$routeName = $route->getName();
|
||||||
|
|
||||||
|
if ($this->ApplicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$validSession = true;
|
||||||
|
$validApiKey = true;
|
||||||
|
|
||||||
|
$sessionService = new SessionService();
|
||||||
|
if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName]))
|
||||||
|
{
|
||||||
|
$validSession = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiKeyService = new ApiKeyService();
|
||||||
|
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName)))
|
||||||
|
{
|
||||||
|
$validApiKey = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$validSession && !$validApiKey)
|
||||||
|
{
|
||||||
|
$response = $response->withStatus(401);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
17
middleware/BaseMiddleware.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
|
||||||
|
class BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
$this->AppContainer = $container;
|
||||||
|
$this->ApplicationService = new ApplicationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $AppContainer;
|
||||||
|
protected $ApplicationService;
|
||||||
|
}
|
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);
|
||||||
|
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);
|
||||||
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
}
|
44
middleware/SessionAuthMiddleware.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\SessionService;
|
||||||
|
|
||||||
|
class SessionAuthMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container, string $sessionCookieName)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionCookieName;
|
||||||
|
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
$route = $request->getAttribute('route');
|
||||||
|
$routeName = $route->getName();
|
||||||
|
|
||||||
|
if ($routeName === 'root' || $this->ApplicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
define('AUTHENTICATED', $this->ApplicationService->IsDemoInstallation());
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sessionService = new SessionService();
|
||||||
|
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
|
||||||
|
{
|
||||||
|
define('AUTHENTICATED', false);
|
||||||
|
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
define('AUTHENTICATED', $routeName !== 'login');
|
||||||
|
$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'))
|
||||||
|
)
|
11
migrations/0021.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
DELETE FROM locations
|
||||||
|
WHERE name = 'DefaultLocation';
|
||||||
|
|
||||||
|
DELETE FROM quantity_units
|
||||||
|
WHERE name = 'DefaultQuantityUnit';
|
||||||
|
|
||||||
|
DELETE FROM products
|
||||||
|
WHERE name = 'DefaultProduct1';
|
||||||
|
|
||||||
|
DELETE FROM products
|
||||||
|
WHERE name = 'DefaultProduct2';
|
7
migrations/0022.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE api_keys (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
api_key TEXT NOT NULL UNIQUE,
|
||||||
|
expires DATETIME,
|
||||||
|
last_used DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
1
migrations/0023.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DELETE FROM sessions
|
2
migrations/0024.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE sessions
|
||||||
|
ADD COLUMN last_used DATETIME
|
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 |
184
public/css/grocy.css
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
body {
|
||||||
|
padding-top: 50px;
|
||||||
|
font-family: 'Noto Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-fixed-top {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-color: #d6d6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: -5px;
|
||||||
|
font-size: 2.2em;
|
||||||
|
color: #0b024c !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding-left: 5px !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-fixed-side {
|
||||||
|
top: 51px;
|
||||||
|
padding-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
border-right: 2px solid #d6d6d6;
|
||||||
|
max-width: 260px;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#navbar-mobile {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-copyright {
|
||||||
|
padding-bottom: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar-brand {
|
||||||
|
margin-left: 25px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > li > a {
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-left: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > li > a:hover {
|
||||||
|
box-shadow: inset 5px 0 0 #337ab7;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > li > a:focus {
|
||||||
|
box-shadow: inset 5px 0 0 #ab2230;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav > .active > a,
|
||||||
|
.sidebar-nav > .active > a:hover,
|
||||||
|
.sidebar-nav > .active > a:focus {
|
||||||
|
background-color: #d6d6d6;
|
||||||
|
box-shadow: inset 5px 0 0 #ab2230;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav > li.disabled > a,
|
||||||
|
.navbar-default .navbar-nav > .disabled > a {
|
||||||
|
color: #a7a7a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-copyright {
|
||||||
|
color: #a7a7a7;
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-copyright > li > a {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-default .navbar-nav > .open > a {
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu > li > a:hover,
|
||||||
|
.dropdown-menu > li > a:focus {
|
||||||
|
background-color: #e5e5e5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.well {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
padding-right: 25px;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-button {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discrete-link {
|
||||||
|
color: inherit !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discrete-link:hover {
|
||||||
|
color: #337ab7 !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discrete-link:focus {
|
||||||
|
color: #ab2230 !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: all 0.3s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td.fit-content,
|
||||||
|
.table th.fit-content {
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataTables_info,
|
||||||
|
.dataTables_length,
|
||||||
|
.dataTables_filter {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeago-contextual {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled,
|
||||||
|
.no-real-button {
|
||||||
|
pointer-events: none;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discrete-content-separator-2x {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-bg {
|
||||||
|
background-color: #fcf8e3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-bg {
|
||||||
|
background-color: #f2dede !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-bg {
|
||||||
|
background-color: #afd9ee !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toast-container > div {
|
||||||
|
opacity: 1;
|
||||||
|
filter: alpha(opacity=100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-success {
|
||||||
|
background-color: #4c994c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toast-container > div {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
BIN
public/img/grocy.png
Normal file
After Width: | Height: | Size: 2.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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
102
public/js/grocy.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
L = function(text, ...placeholderValues)
|
||||||
|
{
|
||||||
|
var localizedText = Grocy.LocalizationStrings[text];
|
||||||
|
if (localizedText === undefined)
|
||||||
|
{
|
||||||
|
localizedText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < placeholderValues.length; i++)
|
||||||
|
{
|
||||||
|
localizedText = localizedText.replace('#' + (i + 1), placeholderValues[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return localizedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
U = function(relativePath)
|
||||||
|
{
|
||||||
|
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Grocy.ActiveNav.isEmpty())
|
||||||
|
{
|
||||||
|
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||||
|
menuItem.addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
$.timeago.settings.allowFuture = true;
|
||||||
|
RefreshContextualTimeago = function()
|
||||||
|
{
|
||||||
|
$('time.timeago').timeago();
|
||||||
|
}
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
|
||||||
|
toastr.options = {
|
||||||
|
toastClass: 'alert',
|
||||||
|
closeButton: true,
|
||||||
|
timeOut: 20000,
|
||||||
|
extendedTimeOut: 5000
|
||||||
|
};
|
||||||
|
|
||||||
|
Grocy.Api = { };
|
||||||
|
Grocy.Api.Get = function(apiFunction, success, error)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/' + apiFunction);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE)
|
||||||
|
{
|
||||||
|
if (xhr.status === 200)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
success(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
error(xhr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
Grocy.Api.Post = function(apiFunction, jsonData, success, error)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/' + apiFunction);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE)
|
||||||
|
{
|
||||||
|
if (xhr.status === 200)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
success(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
error(xhr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/json');
|
||||||
|
xhr.send(JSON.stringify(jsonData));
|
||||||
|
};
|
44
public/viewjs/batteries.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
$(document).on('click', '.battery-delete-button', function(e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-battery-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-battery-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete battery "#1"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: L('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: L('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('delete-object/batteries/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/batteries');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#batteries-table').DataTable({
|
||||||
|
'pageLength': 50,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization'))
|
||||||
|
});
|