Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
580bd5ac0c | ||
|
2bf3448d18 | ||
|
e9bc51ca3d | ||
|
5ddae116e0 | ||
|
13566bc6fd | ||
|
642f95a3f8 | ||
|
5a1d21ef31 | ||
|
7dcd39f82f | ||
|
655aa89bd6 | ||
|
feb88ab685 | ||
|
79b4bad014 | ||
|
bcd5092427 | ||
|
554a83fa01 | ||
|
57acb62520 | ||
|
52ed5f2285 | ||
|
dd1d253ea5 | ||
|
e40979a874 | ||
|
2ddbc2656b | ||
|
7351fce395 |
201
.gitignore
vendored
@@ -1,202 +1,3 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## 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
|
||||
/public/bower_components
|
||||
/vendor
|
||||
/.release
|
||||
/composer.phar
|
||||
/composer.lock
|
||||
|
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"phpserver.relativePath": "public"
|
||||
}
|
148
Grocy.php
@@ -1,148 +0,0 @@
|
||||
<?php
|
||||
|
||||
class Grocy
|
||||
{
|
||||
private static $DbConnectionRaw;
|
||||
/**
|
||||
* @return PDO
|
||||
*/
|
||||
public static function GetDbConnectionRaw($doMigrations = false)
|
||||
{
|
||||
if ($doMigrations === true)
|
||||
{
|
||||
self::$DbConnectionRaw = null;
|
||||
}
|
||||
|
||||
if (self::$DbConnectionRaw == null)
|
||||
{
|
||||
$pdo = new PDO('sqlite:' . __DIR__ . '/data/grocy.db');
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
if ($doMigrations === true)
|
||||
{
|
||||
Grocy::ExecuteDbStatement($pdo, "CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))");
|
||||
GrocyDbMigrator::MigrateDb($pdo);
|
||||
|
||||
if (self::IsDemoInstallation())
|
||||
{
|
||||
GrocyDemoDataGenerator::PopulateDemoData($pdo);
|
||||
}
|
||||
}
|
||||
|
||||
self::$DbConnectionRaw = $pdo;
|
||||
}
|
||||
|
||||
return self::$DbConnectionRaw;
|
||||
}
|
||||
|
||||
private static $DbConnection;
|
||||
/**
|
||||
* @return LessQL\Database
|
||||
*/
|
||||
public static function GetDbConnection($doMigrations = false)
|
||||
{
|
||||
if ($doMigrations === true)
|
||||
{
|
||||
self::$DbConnection = null;
|
||||
}
|
||||
|
||||
if (self::$DbConnection == null)
|
||||
{
|
||||
self::$DbConnection = new LessQL\Database(self::GetDbConnectionRaw($doMigrations));
|
||||
}
|
||||
|
||||
return self::$DbConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public static function ExecuteDbStatement(PDO $pdo, string $sql)
|
||||
{
|
||||
if ($pdo->exec($sql) === false)
|
||||
{
|
||||
throw new Exception($pdo->errorInfo());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean|PDOStatement
|
||||
*/
|
||||
public static function ExecuteDbQuery(PDO $pdo, string $sql)
|
||||
{
|
||||
if (self::ExecuteDbStatement($pdo, $sql) === true)
|
||||
{
|
||||
return $pdo->query($sql);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public static function IsDemoInstallation()
|
||||
{
|
||||
return file_exists(__DIR__ . '/data/demo.txt');
|
||||
}
|
||||
|
||||
private static $InstalledVersion;
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function GetInstalledVersion()
|
||||
{
|
||||
if (self::$InstalledVersion == null)
|
||||
{
|
||||
self::$InstalledVersion = preg_replace("/\r|\n/", '', file_get_contents(__DIR__ . '/version.txt'));
|
||||
}
|
||||
|
||||
return self::$InstalledVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public static function IsValidSession($sessionKey)
|
||||
{
|
||||
if ($sessionKey === null || empty($sessionKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return file_exists(__DIR__ . "/data/sessions/$sessionKey.txt");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function CreateSession()
|
||||
{
|
||||
if (!file_exists(__DIR__ . '/data/sessions'))
|
||||
{
|
||||
mkdir(__DIR__ . '/data/sessions');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
foreach (new FilesystemIterator(__DIR__ . '/data/sessions') as $file)
|
||||
{
|
||||
if ($now - $file->getCTime() >= 2678400) //31 days
|
||||
{
|
||||
unlink(__DIR__ . '/data/sessions/' . $file->getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
$newSessionKey = uniqid() . uniqid() . uniqid();
|
||||
file_put_contents(__DIR__ . "/data/sessions/$newSessionKey.txt", '');
|
||||
return $newSessionKey;
|
||||
}
|
||||
|
||||
public static function RemoveSession($sessionKey)
|
||||
{
|
||||
unlink(__DIR__ . "/data/sessions/$sessionKey.txt");
|
||||
}
|
||||
}
|
@@ -1,174 +0,0 @@
|
||||
<?php
|
||||
|
||||
class GrocyDbMigrator
|
||||
{
|
||||
public static function MigrateDb(PDO $pdo)
|
||||
{
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 1, "
|
||||
CREATE TABLE products (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
location_id INTEGER NOT NULL,
|
||||
qu_id_purchase INTEGER NOT NULL,
|
||||
qu_id_stock INTEGER NOT NULL,
|
||||
qu_factor_purchase_to_stock REAL NOT NULL,
|
||||
barcode TEXT,
|
||||
min_stock_amount INTEGER NOT NULL DEFAULT 0,
|
||||
default_best_before_days INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 2, "
|
||||
CREATE TABLE locations (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 3, "
|
||||
CREATE TABLE quantity_units (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 4, "
|
||||
CREATE TABLE stock (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
best_before_date DATE,
|
||||
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
|
||||
stock_id TEXT NOT NULL,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 5, "
|
||||
CREATE TABLE stock_log (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
best_before_date DATE,
|
||||
purchased_date DATE,
|
||||
used_date DATE,
|
||||
spoiled INTEGER NOT NULL DEFAULT 0,
|
||||
stock_id TEXT NOT NULL,
|
||||
transaction_type TEXT NOT NULL,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 6, "
|
||||
INSERT INTO locations (name, description) VALUES ('DefaultLocation', 'This is the first default location, edit or delete it');
|
||||
INSERT INTO quantity_units (name, description) VALUES ('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
|
||||
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
|
||||
INSERT INTO products (name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 7, "
|
||||
CREATE VIEW stock_missing_products
|
||||
AS
|
||||
SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing
|
||||
FROM products p
|
||||
LEFT JOIN stock s
|
||||
ON p.id = s.product_id
|
||||
WHERE p.min_stock_amount != 0
|
||||
GROUP BY p.id
|
||||
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount;"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 8, "
|
||||
CREATE VIEW stock_current
|
||||
AS
|
||||
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||
FROM stock
|
||||
GROUP BY product_id
|
||||
ORDER BY MIN(best_before_date) ASC;"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 9, "
|
||||
CREATE TABLE shopping_list (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL UNIQUE,
|
||||
amount INTEGER NOT NULL DEFAULT 0,
|
||||
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 10, "
|
||||
CREATE TABLE habits (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
period_type TEXT NOT NULL,
|
||||
period_days INTEGER,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 11, "
|
||||
CREATE TABLE habits_log (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
habit_id INTEGER NOT NULL,
|
||||
tracked_time DATETIME,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 12, "
|
||||
CREATE VIEW habits_current
|
||||
AS
|
||||
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
||||
FROM habits_log
|
||||
GROUP BY habit_id
|
||||
ORDER BY MAX(tracked_time) DESC;"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 13, "
|
||||
CREATE TABLE batteries (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
used_in TEXT,
|
||||
charge_interval_days INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 14, "
|
||||
CREATE TABLE battery_charge_cycles (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
battery_id TEXT NOT NULL,
|
||||
tracked_time DATETIME,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)"
|
||||
);
|
||||
|
||||
self::ExecuteMigrationWhenNeeded($pdo, 15, "
|
||||
CREATE VIEW batteries_current
|
||||
AS
|
||||
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
||||
FROM battery_charge_cycles
|
||||
GROUP BY battery_id
|
||||
ORDER BY MAX(tracked_time) DESC;"
|
||||
);
|
||||
}
|
||||
|
||||
private static function ExecuteMigrationWhenNeeded(PDO $pdo, int $migrationId, string $sql)
|
||||
{
|
||||
$rowCount = Grocy::ExecuteDbQuery($pdo, 'SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn();
|
||||
if (intval($rowCount) === 0)
|
||||
{
|
||||
Grocy::ExecuteDbStatement($pdo, $sql);
|
||||
Grocy::ExecuteDbStatement($pdo, 'INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
class GrocyLogicBatteries
|
||||
{
|
||||
public static function GetCurrent()
|
||||
{
|
||||
$sql = 'SELECT * from batteries_current';
|
||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public static function GetNextChargeTime(int $batteryId)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$battery = $db->batteries($batteryId);
|
||||
$batteryLastLogRow = Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), "SELECT * from batteries_current WHERE battery_id = $batteryId LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
if ($battery->charge_interval_days > 0)
|
||||
{
|
||||
return date('Y-m-d H:i:s', strtotime('+' . $battery->charge_interval_days . ' day', strtotime($batteryLastLogRow->last_tracked_time)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function GetBatteryDetails(int $batteryId)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$battery = $db->batteries($batteryId);
|
||||
$batteryChargeCylcesCount = $db->battery_charge_cycles()->where('battery_id', $batteryId)->count();
|
||||
$batteryLastChargedTime = $db->battery_charge_cycles()->where('battery_id', $batteryId)->max('tracked_time');
|
||||
|
||||
return array(
|
||||
'battery' => $battery,
|
||||
'last_charged' => $batteryLastChargedTime,
|
||||
'charge_cycles_count' => $batteryChargeCylcesCount
|
||||
);
|
||||
}
|
||||
|
||||
public static function TrackChargeCycle(int $batteryId, string $trackedTime)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$logRow = $db->battery_charge_cycles()->createRow(array(
|
||||
'battery_id' => $batteryId,
|
||||
'tracked_time' => $trackedTime
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
class GrocyLogicHabits
|
||||
{
|
||||
const HABIT_TYPE_MANUALLY = 'manually';
|
||||
const HABIT_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
||||
|
||||
public static function GetCurrentHabits()
|
||||
{
|
||||
$sql = 'SELECT * from habits_current';
|
||||
return Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), $sql)->fetchAll(PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public static function GetNextHabitTime(int $habitId)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$habit = $db->habits($habitId);
|
||||
$habitLastLogRow = Grocy::ExecuteDbQuery(Grocy::GetDbConnectionRaw(), "SELECT * from habits_current WHERE habit_id = $habitId LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
switch ($habit->period_type)
|
||||
{
|
||||
case self::HABIT_TYPE_MANUALLY:
|
||||
return date('Y-m-d H:i:s');
|
||||
case self::HABIT_TYPE_DYNAMIC_REGULAR:
|
||||
return date('Y-m-d H:i:s', strtotime('+' . $habit->period_days . ' day', strtotime($habitLastLogRow->last_tracked_time)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function GetHabitDetails(int $habitId)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$habit = $db->habits($habitId);
|
||||
$habitTrackedCount = $db->habits_log()->where('habit_id', $habitId)->count();
|
||||
$habitLastTrackedTime = $db->habits_log()->where('habit_id', $habitId)->max('tracked_time');
|
||||
|
||||
return array(
|
||||
'habit' => $habit,
|
||||
'last_tracked' => $habitLastTrackedTime,
|
||||
'tracked_count' => $habitTrackedCount
|
||||
);
|
||||
}
|
||||
|
||||
public static function TrackHabit(int $habitId, string $trackedTime)
|
||||
{
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$logRow = $db->habits_log()->createRow(array(
|
||||
'habit_id' => $habitId,
|
||||
'tracked_time' => $trackedTime
|
||||
));
|
||||
$logRow->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
class GrocyPhpHelper
|
||||
{
|
||||
public static function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
|
||||
{
|
||||
foreach($array as $object)
|
||||
{
|
||||
if($object->{$propertyName} == $propertyValue)
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==')
|
||||
{
|
||||
$returnArray = array();
|
||||
|
||||
foreach($array as $object)
|
||||
{
|
||||
switch($operator)
|
||||
{
|
||||
case '==':
|
||||
if($object->{$propertyName} == $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if($object->{$propertyName} > $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
if($object->{$propertyName} < $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnArray;
|
||||
}
|
||||
|
||||
public static function SumArrayValue($array, $propertyName)
|
||||
{
|
||||
$sum = 0;
|
||||
foreach($array as $object)
|
||||
{
|
||||
$sum += $object->{$propertyName};
|
||||
}
|
||||
|
||||
return $sum;
|
||||
}
|
||||
|
||||
public static function GetClassConstants($className)
|
||||
{
|
||||
$r = new ReflectionClass($className);
|
||||
return $r->getConstants();
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ For now my main focus is on stock management, ERP your fridge!
|
||||
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
||||
|
||||
## How to install
|
||||
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP enabled webserver, copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually.
|
||||
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP (7.0 or later required) enabled webserver (root is the `/public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually.
|
||||
|
||||
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
||||
|
||||
|
47
app.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use \Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use \Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
use \Grocy\Middleware\SessionAuthMiddleware;
|
||||
use \Grocy\Middleware\JsonMiddleware;
|
||||
use \Grocy\Middleware\CliMiddleware;
|
||||
|
||||
use \Grocy\Services\ApplicationService;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/data/config.php';
|
||||
require_once __DIR__ . '/extensions.php';
|
||||
|
||||
// Setup base application
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
$appContainer = new \Slim\Container([
|
||||
'settings' => [
|
||||
'displayErrorDetails' => true,
|
||||
'determineRouteBeforeAppMiddleware' => true
|
||||
],
|
||||
'view' => function($container)
|
||||
{
|
||||
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
||||
}
|
||||
]);
|
||||
|
||||
$app = new \Slim\App($appContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
$app = new \Slim\App();
|
||||
$app->add(\pavlakis\cli\CliRequest::class);
|
||||
}
|
||||
|
||||
// Add session handling if this is not a demo installation
|
||||
$applicationService = new ApplicationService();
|
||||
if (!$applicationService->IsDemoInstallation())
|
||||
{
|
||||
$app->add(SessionAuthMiddleware::class);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/routes.php';
|
||||
|
||||
$app->run();
|
@@ -8,5 +8,6 @@ for /f "tokens=*" %%a in ('type version.txt') do set version=%%a
|
||||
|
||||
del "%releasePath%\grocy_%version%.zip"
|
||||
"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" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions
|
||||
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
|
||||
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
|
||||
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions data\viewcache\*
|
||||
|
@@ -1,8 +1,15 @@
|
||||
{
|
||||
"require": {
|
||||
"slim/slim": "^3.8",
|
||||
"slim/php-view": "^2.2",
|
||||
"morris/lessql": "^0.3.4",
|
||||
"pavlakis/slim-cli": "^1.0"
|
||||
"pavlakis/slim-cli": "^1.0",
|
||||
"rubellum/slim-blade-view": "^0.1.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Grocy\\Services\\": "services/",
|
||||
"Grocy\\Controllers\\": "controllers/",
|
||||
"Grocy\\Middleware\\": "middleware/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1240
composer.lock
generated
Normal file
11
controllers/BaseApiController.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
class BaseApiController extends BaseController
|
||||
{
|
||||
protected function ApiResponse($response)
|
||||
{
|
||||
return json_encode($response);
|
||||
}
|
||||
}
|
22
controllers/BaseController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\DatabaseService;
|
||||
use \Grocy\Services\ApplicationService;
|
||||
|
||||
class BaseController
|
||||
{
|
||||
public function __construct(\Slim\Container $container) {
|
||||
$this->AppContainer = $container;
|
||||
|
||||
$databaseService = new DatabaseService();
|
||||
$this->Database = $databaseService->GetDbConnection();
|
||||
|
||||
$applicationService = new ApplicationService();
|
||||
$container->view->set('version', $applicationService->GetInstalledVersion());
|
||||
}
|
||||
|
||||
protected $AppContainer;
|
||||
protected $Database;
|
||||
}
|
32
controllers/BatteriesApiController.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\BatteriesService;
|
||||
|
||||
class BatteriesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->BatteriesService = new BatteriesService();
|
||||
}
|
||||
|
||||
protected $BatteriesService;
|
||||
|
||||
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||
{
|
||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||
}
|
||||
|
||||
return $this->ApiResponse(array('success' => $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime)));
|
||||
}
|
||||
|
||||
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
|
||||
}
|
||||
}
|
62
controllers/BatteriesController.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\BatteriesService;
|
||||
|
||||
class BatteriesController extends BaseController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->BatteriesService = new BatteriesService();
|
||||
}
|
||||
|
||||
protected $BatteriesService;
|
||||
|
||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$nextChargeTimes = array();
|
||||
foreach($this->Database->batteries() as $battery)
|
||||
{
|
||||
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
|
||||
}
|
||||
|
||||
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
||||
'batteries' => $this->Database->batteries(),
|
||||
'current' => $this->BatteriesService->GetCurrent(),
|
||||
'nextChargeTimes' => $nextChargeTimes
|
||||
]);
|
||||
}
|
||||
|
||||
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'batterytracking', [
|
||||
'batteries' => $this->Database->batteries()
|
||||
]);
|
||||
}
|
||||
|
||||
public function BatteriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'batteries', [
|
||||
'batteries' => $this->Database->batteries()
|
||||
]);
|
||||
}
|
||||
|
||||
public function BatteryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['batteryId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'batteryform', [
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'batteryform', [
|
||||
'battery' => $this->Database->batteries($args['batteryId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
19
controllers/CliController.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\ApplicationService;
|
||||
use \Grocy\Services\DatabaseMigrationService;
|
||||
|
||||
class CliController extends BaseController
|
||||
{
|
||||
public function RecreateDemo(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$applicationService = new ApplicationService();
|
||||
if ($applicationService->IsDemoInstallation())
|
||||
{
|
||||
$databaseMigrationService = new DatabaseMigrationService();
|
||||
$databaseMigrationService->RecreateDemo();
|
||||
}
|
||||
}
|
||||
}
|
40
controllers/GenericEntityApiController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
class GenericEntityApiController extends BaseApiController
|
||||
{
|
||||
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->Database->{$args['entity']}());
|
||||
}
|
||||
|
||||
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
||||
}
|
||||
|
||||
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
|
||||
$newRow->save();
|
||||
$success = $newRow->isClean();
|
||||
return $this->ApiResponse(array('success' => $success));
|
||||
}
|
||||
|
||||
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||
$row->update($request->getParsedBody());
|
||||
$success = $row->isClean();
|
||||
return $this->ApiResponse(array('success' => $success));
|
||||
}
|
||||
|
||||
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||
$row->delete();
|
||||
$success = $row->isClean();
|
||||
return $this->ApiResponse(array('success' => $success));
|
||||
}
|
||||
}
|
32
controllers/HabitsApiController.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\HabitsService;
|
||||
|
||||
class HabitsApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->HabitsService = new HabitsService();
|
||||
}
|
||||
|
||||
protected $HabitsService;
|
||||
|
||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||
{
|
||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||
}
|
||||
|
||||
return $this->ApiResponse(array('success' => $this->HabitsService->TrackHabit($args['habitId'], $trackedTime)));
|
||||
}
|
||||
|
||||
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
||||
}
|
||||
}
|
64
controllers/HabitsController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\HabitsService;
|
||||
|
||||
class HabitsController extends BaseController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->HabitsService = new HabitsService();
|
||||
}
|
||||
|
||||
protected $HabitsService;
|
||||
|
||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$nextHabitTimes = array();
|
||||
foreach($this->Database->habits() as $habit)
|
||||
{
|
||||
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
|
||||
}
|
||||
|
||||
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
||||
'habits' => $this->Database->habits(),
|
||||
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
||||
'nextHabitTimes' => $nextHabitTimes
|
||||
]);
|
||||
}
|
||||
|
||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'habittracking', [
|
||||
'habits' => $this->Database->habits()
|
||||
]);
|
||||
}
|
||||
|
||||
public function HabitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'habits', [
|
||||
'habits' => $this->Database->habits()
|
||||
]);
|
||||
}
|
||||
|
||||
public function HabitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['habitId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'habitform', [
|
||||
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'habitform', [
|
||||
'habit' => $this->Database->habits($args['habitId']),
|
||||
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
69
controllers/LoginController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\SessionService;
|
||||
use \Grocy\Services\ApplicationService;
|
||||
use \Grocy\Services\DatabaseMigrationService;
|
||||
use \Grocy\Services\DemoDataGeneratorService;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->SessionService = new SessionService();
|
||||
}
|
||||
|
||||
protected $SessionService;
|
||||
|
||||
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$postParams = $request->getParsedBody();
|
||||
if (isset($postParams['username']) && isset($postParams['password']))
|
||||
{
|
||||
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
||||
{
|
||||
$sessionKey = $this->SessionService->CreateSession();
|
||||
setcookie('grocy_session', $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
||||
|
||||
return $response->withRedirect('/');
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withRedirect('/login?invalid=true');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withRedirect('/login?invalid=true');
|
||||
}
|
||||
}
|
||||
|
||||
public function LoginPage(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'login');
|
||||
}
|
||||
|
||||
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$this->SessionService->RemoveSession($_COOKIE['grocy_session']);
|
||||
return $response->withRedirect('/');
|
||||
}
|
||||
|
||||
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
// Schema migration is done here
|
||||
$databaseMigrationService = new DatabaseMigrationService();
|
||||
$databaseMigrationService->MigrateDatabase();
|
||||
|
||||
$applicationService = new ApplicationService();
|
||||
if ($applicationService->IsDemoInstallation())
|
||||
{
|
||||
$demoDataGeneratorService = new DemoDataGeneratorService();
|
||||
$demoDataGeneratorService->PopulateDemoData();
|
||||
}
|
||||
|
||||
return $response->withRedirect('/stockoverview');
|
||||
}
|
||||
}
|
77
controllers/StockApiController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\StockService;
|
||||
|
||||
class StockApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->StockService = new StockService();
|
||||
}
|
||||
|
||||
protected $StockService;
|
||||
|
||||
public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
|
||||
}
|
||||
|
||||
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$bestBeforeDate = date('Y-m-d');
|
||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||
{
|
||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||
}
|
||||
|
||||
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||
{
|
||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||
}
|
||||
|
||||
return $this->ApiResponse(array('success' => $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType)));
|
||||
}
|
||||
|
||||
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$spoiled = false;
|
||||
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
|
||||
{
|
||||
$spoiled = true;
|
||||
}
|
||||
|
||||
$transactionType = StockService::TRANSACTION_TYPE_CONSUME;
|
||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||
{
|
||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||
}
|
||||
|
||||
return $this->ApiResponse(array('success' => $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType)));
|
||||
}
|
||||
|
||||
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$bestBeforeDate = date('Y-m-d');
|
||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||
{
|
||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||
}
|
||||
|
||||
return $this->ApiResponse(array('success' => $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate)));
|
||||
}
|
||||
|
||||
public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
||||
}
|
||||
|
||||
public function AddmissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$this->StockService->AddMissingProductsToShoppingList();
|
||||
return $this->ApiResponse(array('success' => true));
|
||||
}
|
||||
}
|
155
controllers/StockController.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\StockService;
|
||||
|
||||
class StockController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->StockService = new StockService();
|
||||
}
|
||||
|
||||
protected $StockService;
|
||||
|
||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'stockoverview', [
|
||||
'products' => $this->Database->products(),
|
||||
'quantityunits' => $this->Database->quantity_units(),
|
||||
'currentStock' => $this->StockService->GetCurrentStock(),
|
||||
'missingProducts' => $this->StockService->GetMissingProducts()
|
||||
]);
|
||||
}
|
||||
|
||||
public function Purchase(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'purchase', [
|
||||
'products' => $this->Database->products()
|
||||
]);
|
||||
}
|
||||
|
||||
public function Consume(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'consume', [
|
||||
'products' => $this->Database->products()
|
||||
]);
|
||||
}
|
||||
|
||||
public function Inventory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'inventory', [
|
||||
'products' => $this->Database->products()
|
||||
]);
|
||||
}
|
||||
|
||||
public function ShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'shoppinglist', [
|
||||
'listItems' => $this->Database->shopping_list(),
|
||||
'products' => $this->Database->products(),
|
||||
'quantityunits' => $this->Database->quantity_units(),
|
||||
'missingProducts' => $this->StockService->GetMissingProducts()
|
||||
]);
|
||||
}
|
||||
|
||||
public function ProductsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'products', [
|
||||
'products' => $this->Database->products(),
|
||||
'locations' => $this->Database->locations(),
|
||||
'quantityunits' => $this->Database->quantity_units()
|
||||
]);
|
||||
}
|
||||
|
||||
public function LocationsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'locations', [
|
||||
'locations' => $this->Database->locations()
|
||||
]);
|
||||
}
|
||||
|
||||
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'quantityunits', [
|
||||
'quantityunits' => $this->Database->quantity_units()
|
||||
]);
|
||||
}
|
||||
|
||||
public function ProductEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['productId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'productform', [
|
||||
'locations' => $this->Database->locations(),
|
||||
'quantityunits' => $this->Database->quantity_units(),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'productform', [
|
||||
'product' => $this->Database->products($args['productId']),
|
||||
'locations' => $this->Database->locations(),
|
||||
'quantityunits' => $this->Database->quantity_units(),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function LocationEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['locationId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'locationform', [
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'locationform', [
|
||||
'location' => $this->Database->locations($args['locationId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['quantityunitId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'quantityunitform', [
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'quantityunitform', [
|
||||
'quantityunit' => $this->Database->quantity_units($args['quantityunitId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function ShoppingListItemEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if ($args['itemId'] == 'new')
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'shoppinglistform', [
|
||||
'products' => $this->Database->products(),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->AppContainer->view->render($response, 'shoppinglistform', [
|
||||
'listItem' => $this->Database->shopping_list($args['itemId']),
|
||||
'products' => $this->Database->products(),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
1
data/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!viewcache
|
||||
|
2
data/viewcache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
63
extensions.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue)
|
||||
{
|
||||
foreach($array as $object)
|
||||
{
|
||||
if($object->{$propertyName} == $propertyValue)
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==')
|
||||
{
|
||||
$returnArray = array();
|
||||
|
||||
foreach($array as $object)
|
||||
{
|
||||
switch($operator)
|
||||
{
|
||||
case '==':
|
||||
if($object->{$propertyName} == $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if($object->{$propertyName} > $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
if($object->{$propertyName} < $propertyValue)
|
||||
{
|
||||
$returnArray[] = $object;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnArray;
|
||||
}
|
||||
|
||||
function SumArrayValue($array, $propertyName)
|
||||
{
|
||||
$sum = 0;
|
||||
foreach($array as $object)
|
||||
{
|
||||
$sum += $object->{$propertyName};
|
||||
}
|
||||
|
||||
return $sum;
|
||||
}
|
||||
|
||||
function GetClassConstants($className)
|
||||
{
|
||||
$r = new ReflectionClass($className);
|
||||
return $r->getConstants();
|
||||
}
|
531
index.php
@@ -1,531 +0,0 @@
|
||||
<?php
|
||||
|
||||
use \Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use \Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Views\PhpRenderer;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/data/config.php';
|
||||
require_once __DIR__ . '/Grocy.php';
|
||||
require_once __DIR__ . '/GrocyDbMigrator.php';
|
||||
require_once __DIR__ . '/GrocyDemoDataGenerator.php';
|
||||
require_once __DIR__ . '/GrocyLogicStock.php';
|
||||
require_once __DIR__ . '/GrocyLogicHabits.php';
|
||||
require_once __DIR__ . '/GrocyLogicBatteries.php';
|
||||
require_once __DIR__ . '/GrocyPhpHelper.php';
|
||||
|
||||
$app = new \Slim\App;
|
||||
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
$app = new \Slim\App(new \Slim\Container([
|
||||
'settings' => [
|
||||
'displayErrorDetails' => true,
|
||||
'determineRouteBeforeAppMiddleware' => true
|
||||
],
|
||||
]));
|
||||
$container = $app->getContainer();
|
||||
$container['renderer'] = new PhpRenderer('./views');
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli')
|
||||
{
|
||||
$app->add(new \pavlakis\cli\CliRequest());
|
||||
}
|
||||
|
||||
if (!Grocy::IsDemoInstallation())
|
||||
{
|
||||
$sessionMiddleware = function(Request $request, Response $response, callable $next)
|
||||
{
|
||||
$route = $request->getAttribute('route');
|
||||
$routeName = $route->getName();
|
||||
|
||||
if ((!isset($_COOKIE['grocy_session']) || !Grocy::IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login')
|
||||
{
|
||||
$response = $response->withRedirect('/login');
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
};
|
||||
|
||||
$app->add($sessionMiddleware);
|
||||
}
|
||||
|
||||
$db = Grocy::GetDbConnection();
|
||||
|
||||
$app->get('/login', function(Request $request, Response $response)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Login',
|
||||
'contentPage' => 'login.php'
|
||||
]);
|
||||
})->setName('login');
|
||||
|
||||
$app->post('/login', function(Request $request, Response $response)
|
||||
{
|
||||
$postParams = $request->getParsedBody();
|
||||
if (isset($postParams['username']) && isset($postParams['password']))
|
||||
{
|
||||
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
||||
{
|
||||
$sessionKey = Grocy::CreateSession();
|
||||
setcookie('grocy_session', $sessionKey, time()+2592000); //30 days
|
||||
|
||||
return $response->withRedirect('/');
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withRedirect('/login?invalid=true');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withRedirect('/login?invalid=true');
|
||||
}
|
||||
})->setName('login');
|
||||
|
||||
$app->get('/logout', function(Request $request, Response $response)
|
||||
{
|
||||
Grocy::RemoveSession($_COOKIE['grocy_session']);
|
||||
return $response->withRedirect('/');
|
||||
});
|
||||
|
||||
$app->get('/', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
$db = Grocy::GetDbConnection(true); //For database schema migration
|
||||
|
||||
return $response->withRedirect('/stockoverview');
|
||||
});
|
||||
|
||||
$app->get('/stockoverview', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Stock overview',
|
||||
'contentPage' => 'stockoverview.php',
|
||||
'products' => $db->products(),
|
||||
'quantityunits' => $db->quantity_units(),
|
||||
'currentStock' => GrocyLogicStock::GetCurrentStock(),
|
||||
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/habitsoverview', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Habits overview',
|
||||
'contentPage' => 'habitsoverview.php',
|
||||
'habits' => $db->habits(),
|
||||
'currentHabits' => GrocyLogicHabits::GetCurrentHabits(),
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/batteriesoverview', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Batteries overview',
|
||||
'contentPage' => 'batteriesoverview.php',
|
||||
'batteries' => $db->batteries(),
|
||||
'current' => GrocyLogicBatteries::GetCurrent(),
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/purchase', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Purchase',
|
||||
'contentPage' => 'purchase.php',
|
||||
'products' => $db->products()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/consume', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Consume',
|
||||
'contentPage' => 'consume.php',
|
||||
'products' => $db->products()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/inventory', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Inventory',
|
||||
'contentPage' => 'inventory.php',
|
||||
'products' => $db->products()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/shoppinglist', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Shopping list',
|
||||
'contentPage' => 'shoppinglist.php',
|
||||
'listItems' => $db->shopping_list(),
|
||||
'products' => $db->products(),
|
||||
'quantityunits' => $db->quantity_units(),
|
||||
'missingProducts' => GrocyLogicStock::GetMissingProducts()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/habittracking', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Habit tracking',
|
||||
'contentPage' => 'habittracking.php',
|
||||
'habits' => $db->habits()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/batterytracking', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Battery tracking',
|
||||
'contentPage' => 'batterytracking.php',
|
||||
'batteries' => $db->batteries()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/products', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Products',
|
||||
'contentPage' => 'products.php',
|
||||
'products' => $db->products(),
|
||||
'locations' => $db->locations(),
|
||||
'quantityunits' => $db->quantity_units()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/locations', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Locations',
|
||||
'contentPage' => 'locations.php',
|
||||
'locations' => $db->locations()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/quantityunits', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Quantity units',
|
||||
'contentPage' => 'quantityunits.php',
|
||||
'quantityunits' => $db->quantity_units()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/habits', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Habits',
|
||||
'contentPage' => 'habits.php',
|
||||
'habits' => $db->habits()
|
||||
]);
|
||||
});
|
||||
|
||||
$app->get('/batteries', function(Request $request, Response $response) use($db)
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Batteries',
|
||||
'contentPage' => 'batteries.php',
|
||||
'batteries' => $db->batteries()
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
$app->get('/product/{productId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['productId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Create product',
|
||||
'contentPage' => 'productform.php',
|
||||
'locations' => $db->locations(),
|
||||
'quantityunits' => $db->quantity_units(),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit product',
|
||||
'contentPage' => 'productform.php',
|
||||
'product' => $db->products($args['productId']),
|
||||
'locations' => $db->locations(),
|
||||
'quantityunits' => $db->quantity_units(),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->get('/location/{locationId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['locationId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Create location',
|
||||
'contentPage' => 'locationform.php',
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit location',
|
||||
'contentPage' => 'locationform.php',
|
||||
'location' => $db->locations($args['locationId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->get('/quantityunit/{quantityunitId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['quantityunitId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Create quantity unit',
|
||||
'contentPage' => 'quantityunitform.php',
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit quantity unit',
|
||||
'contentPage' => 'quantityunitform.php',
|
||||
'quantityunit' => $db->quantity_units($args['quantityunitId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->get('/habit/{habitId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['habitId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Create habit',
|
||||
'contentPage' => 'habitform.php',
|
||||
'periodTypes' => GrocyPhpHelper::GetClassConstants('GrocyLogicHabits'),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit habit',
|
||||
'contentPage' => 'habitform.php',
|
||||
'habit' => $db->habits($args['habitId']),
|
||||
'periodTypes' => GrocyPhpHelper::GetClassConstants('GrocyLogicHabits'),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->get('/battery/{batteryId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['batteryId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Create battery',
|
||||
'contentPage' => 'batteryform.php',
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit battery',
|
||||
'contentPage' => 'batteryform.php',
|
||||
'battery' => $db->batteries($args['batteryId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->get('/shoppinglistitem/{itemId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
if ($args['itemId'] == 'new')
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Add shopping list item',
|
||||
'contentPage' => 'shoppinglistform.php',
|
||||
'products' => $db->products(),
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderer->render($response, '/layout.php', [
|
||||
'title' => 'Edit shopping list item',
|
||||
'contentPage' => 'shoppinglistform.php',
|
||||
'listItem' => $db->shopping_list($args['itemId']),
|
||||
'products' => $db->products(),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
$app->group('/api', function() use($db)
|
||||
{
|
||||
$this->get('/get-objects/{entity}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
echo json_encode($db->{$args['entity']}());
|
||||
});
|
||||
|
||||
$this->get('/get-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
echo json_encode($db->{$args['entity']}($args['objectId']));
|
||||
});
|
||||
|
||||
$this->post('/add-object/{entity}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
$newRow = $db->{$args['entity']}()->createRow($request->getParsedBody());
|
||||
$newRow->save();
|
||||
$success = $newRow->isClean();
|
||||
echo json_encode(array('success' => $success));
|
||||
});
|
||||
|
||||
$this->post('/edit-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
$row = $db->{$args['entity']}($args['objectId']);
|
||||
$row->update($request->getParsedBody());
|
||||
$success = $row->isClean();
|
||||
echo json_encode(array('success' => $success));
|
||||
});
|
||||
|
||||
$this->get('/delete-object/{entity}/{objectId}', function(Request $request, Response $response, $args) use($db)
|
||||
{
|
||||
$row = $db->{$args['entity']}($args['objectId']);
|
||||
$row->delete();
|
||||
$success = $row->isClean();
|
||||
echo json_encode(array('success' => $success));
|
||||
});
|
||||
|
||||
$this->get('/stock/add-product/{productId}/{amount}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
$bestBeforeDate = date('Y-m-d');
|
||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||
{
|
||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||
}
|
||||
|
||||
$transactionType = GrocyLogicStock::TRANSACTION_TYPE_PURCHASE;
|
||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||
{
|
||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => GrocyLogicStock::AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType)));
|
||||
});
|
||||
|
||||
$this->get('/stock/consume-product/{productId}/{amount}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
$spoiled = false;
|
||||
if (isset($request->getQueryParams()['spoiled']) && !empty($request->getQueryParams()['spoiled']) && $request->getQueryParams()['spoiled'] == '1')
|
||||
{
|
||||
$spoiled = true;
|
||||
}
|
||||
|
||||
$transactionType = GrocyLogicStock::TRANSACTION_TYPE_CONSUME;
|
||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||
{
|
||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => GrocyLogicStock::ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType)));
|
||||
});
|
||||
|
||||
$this->get('/stock/inventory-product/{productId}/{newAmount}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
$bestBeforeDate = date('Y-m-d');
|
||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
||||
{
|
||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => GrocyLogicStock::InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate)));
|
||||
});
|
||||
|
||||
$this->get('/stock/get-product-details/{productId}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
echo json_encode(GrocyLogicStock::GetProductDetails($args['productId']));
|
||||
});
|
||||
|
||||
$this->get('/stock/get-current-stock', function(Request $request, Response $response)
|
||||
{
|
||||
echo json_encode(GrocyLogicStock::GetCurrentStock());
|
||||
});
|
||||
|
||||
$this->get('/stock/add-missing-products-to-shoppinglist', function(Request $request, Response $response)
|
||||
{
|
||||
GrocyLogicStock::AddMissingProductsToShoppingList();
|
||||
echo json_encode(array('success' => true));
|
||||
});
|
||||
|
||||
$this->get('/habits/track-habit/{habitId}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||
{
|
||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => GrocyLogicHabits::TrackHabit($args['habitId'], $trackedTime)));
|
||||
});
|
||||
|
||||
$this->get('/habits/get-habit-details/{habitId}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
echo json_encode(GrocyLogicHabits::GetHabitDetails($args['habitId']));
|
||||
});
|
||||
|
||||
$this->get('/batteries/track-charge-cycle/{batteryId}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
||||
{
|
||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => GrocyLogicBatteries::TrackChargeCycle($args['batteryId'], $trackedTime)));
|
||||
});
|
||||
|
||||
$this->get('/batteries/get-battery-details/{batteryId}', function(Request $request, Response $response, $args)
|
||||
{
|
||||
echo json_encode(GrocyLogicBatteries::GetBatteryDetails($args['batteryId']));
|
||||
});
|
||||
})->add(function($request, $response, $next)
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
$app->group('/cli', function()
|
||||
{
|
||||
$this->get('/recreatedemo', function(Request $request, Response $response)
|
||||
{
|
||||
if (Grocy::IsDemoInstallation())
|
||||
{
|
||||
GrocyDemoDataGenerator::RecreateDemo();
|
||||
}
|
||||
});
|
||||
})->add(function($request, $response, $next)
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
echo 'Please call this only from CLI';
|
||||
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
|
||||
}
|
||||
|
||||
return $response->withHeader('Content-Type', 'text/plain');
|
||||
});
|
||||
|
||||
$app->run();
|
12
middleware/BaseMiddleware.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Middleware;
|
||||
|
||||
class BaseMiddleware
|
||||
{
|
||||
public function __construct(\Slim\Container $container) {
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
protected $container;
|
||||
}
|
20
middleware/CliMiddleware.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Middleware;
|
||||
|
||||
class CliMiddleware extends BaseMiddleware
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||
{
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
$response->write('Please call this only from CLI');
|
||||
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = $next($request, $response, $next);
|
||||
return $response->withHeader('Content-Type', 'text/plain');
|
||||
}
|
||||
}
|
||||
}
|
12
middleware/JsonMiddleware.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Middleware;
|
||||
|
||||
class JsonMiddleware extends BaseMiddleware
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||
{
|
||||
$response = $next($request, $response, $next);
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
33
middleware/SessionAuthMiddleware.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Middleware;
|
||||
|
||||
use \Grocy\Services\SessionService;
|
||||
|
||||
class SessionAuthMiddleware extends BaseMiddleware
|
||||
{
|
||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||
{
|
||||
$route = $request->getAttribute('route');
|
||||
$routeName = $route->getName();
|
||||
|
||||
if ($routeName === 'root')
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sessionService = new SessionService();
|
||||
if ((!isset($_COOKIE['grocy_session']) || !$sessionService->IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login')
|
||||
{
|
||||
$response = $response->withRedirect('/login');
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = $next($request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
13
migrations/0001.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE products (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
location_id INTEGER NOT NULL,
|
||||
qu_id_purchase INTEGER NOT NULL,
|
||||
qu_id_stock INTEGER NOT NULL,
|
||||
qu_factor_purchase_to_stock REAL NOT NULL,
|
||||
barcode TEXT,
|
||||
min_stock_amount INTEGER NOT NULL DEFAULT 0,
|
||||
default_best_before_days INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0002.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE locations (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0003.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE quantity_units (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
9
migrations/0004.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE stock (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
best_before_date DATE,
|
||||
purchased_date DATE DEFAULT (datetime('now', 'localtime')),
|
||||
stock_id TEXT NOT NULL,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
12
migrations/0005.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE stock_log (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
best_before_date DATE,
|
||||
purchased_date DATE,
|
||||
used_date DATE,
|
||||
spoiled INTEGER NOT NULL DEFAULT 0,
|
||||
stock_id TEXT NOT NULL,
|
||||
transaction_type TEXT NOT NULL,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
19
migrations/0006.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
INSERT INTO locations
|
||||
(name, description)
|
||||
VALUES
|
||||
('DefaultLocation', 'This is the first default location, edit or delete it');
|
||||
|
||||
INSERT INTO quantity_units
|
||||
(name, description)
|
||||
VALUES
|
||||
('DefaultQuantityUnit', 'This is the first default quantity unit, edit or delete it');
|
||||
|
||||
INSERT INTO products
|
||||
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
|
||||
VALUES
|
||||
('DefaultProduct1', 'This is the first default product, edit or delete it', 1, 1, 1, 1);
|
||||
|
||||
INSERT INTO products
|
||||
(name, description, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock)
|
||||
VALUES
|
||||
('DefaultProduct2', 'This is the second default product, edit or delete it', 1, 1, 1, 1);
|
9
migrations/0007.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE VIEW stock_missing_products
|
||||
AS
|
||||
SELECT p.id, MAX(p.name) AS name, p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing
|
||||
FROM products p
|
||||
LEFT JOIN stock s
|
||||
ON p.id = s.product_id
|
||||
WHERE p.min_stock_amount != 0
|
||||
GROUP BY p.id
|
||||
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount
|
6
migrations/0008.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE VIEW stock_current
|
||||
AS
|
||||
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||
FROM stock
|
||||
GROUP BY product_id
|
||||
ORDER BY MIN(best_before_date) ASC
|
7
migrations/0009.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE TABLE shopping_list (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER NOT NULL UNIQUE,
|
||||
amount INTEGER NOT NULL DEFAULT 0,
|
||||
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
8
migrations/0010.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE habits (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
period_type TEXT NOT NULL,
|
||||
period_days INTEGER,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0011.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE habits_log (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
habit_id INTEGER NOT NULL,
|
||||
tracked_time DATETIME,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0012.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE VIEW habits_current
|
||||
AS
|
||||
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
||||
FROM habits_log
|
||||
GROUP BY habit_id
|
||||
ORDER BY MAX(tracked_time) DESC
|
8
migrations/0013.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE batteries (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
used_in TEXT,
|
||||
charge_interval_days INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0014.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE battery_charge_cycles (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
battery_id TEXT NOT NULL,
|
||||
tracked_time DATETIME,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
6
migrations/0015.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE VIEW batteries_current
|
||||
AS
|
||||
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
||||
FROM battery_charge_cycles
|
||||
GROUP BY battery_id
|
||||
ORDER BY MAX(tracked_time) DESC
|
1
migrations/0016.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE shopping_list RENAME TO shopping_list_old
|
8
migrations/0017.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE shopping_list (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INTEGER,
|
||||
note TEXT,
|
||||
amount INTEGER NOT NULL DEFAULT 0,
|
||||
amount_autoadded INTEGER NOT NULL DEFAULT 0,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
4
migrations/0018.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
INSERT INTO shopping_list
|
||||
(product_id, amount, amount_autoadded, row_created_timestamp)
|
||||
SELECT product_id, amount, amount_autoadded, row_created_timestamp
|
||||
FROM shopping_list_old
|
1
migrations/0019.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE shopping_list_old
|
6
migrations/0020.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE sessions (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
session_key TEXT NOT NULL UNIQUE,
|
||||
expires DATETIME,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||
)
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -22,8 +22,8 @@
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
border-right: 1px solid #5e5e5e;
|
||||
background-color: #e5e5e5;
|
||||
border-right: 2px solid #d6d6d6;
|
||||
min-width: 220px;
|
||||
max-width: 260px;
|
||||
}
|
||||
@@ -42,13 +42,29 @@
|
||||
.nav-sidebar > li > a {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-sidebar > li > a:hover {
|
||||
box-shadow: inset 4px 0 0 #337ab7;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-sidebar > li > a:focus {
|
||||
box-shadow: inset 4px 0 0 #ab2230;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-sidebar > .active > a,
|
||||
.nav-sidebar > .active > a:hover,
|
||||
.nav-sidebar > .active > a:focus {
|
||||
color: #fff;
|
||||
background-color: #5e5e5e;
|
||||
background-color: #d6d6d6;
|
||||
box-shadow: inset 4px 0 0 #ab2230;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.navbar-default {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.main {
|
||||
@@ -67,36 +83,39 @@
|
||||
}
|
||||
|
||||
.nav-copyright {
|
||||
color: #b3b3b1;
|
||||
color: #a7a7a7;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.discrete-link {
|
||||
color: inherit;
|
||||
color: inherit !important;
|
||||
transition: all 0.3s !important;
|
||||
}
|
||||
|
||||
a.discrete-link:hover {
|
||||
color: #5cb85c;
|
||||
text-decoration: none;
|
||||
color: #337ab7 !important;
|
||||
text-decoration: none !important;
|
||||
transition: all 0.3s !important;
|
||||
}
|
||||
|
||||
a.discrete-link:focus {
|
||||
color: #337ab7;
|
||||
text-decoration: none;
|
||||
color: #ab2230 !important;
|
||||
text-decoration: none !important;
|
||||
transition: all 0.3s !important;
|
||||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
border-bottom: solid;
|
||||
border-color: #5e5e5e;
|
||||
border-bottom: 2px solid;
|
||||
border-color: #d6d6d6;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
letter-spacing: -2px;
|
||||
letter-spacing: -5px;
|
||||
font-size: 2.2em;
|
||||
font-family: 'Arial', sans-serif;
|
||||
color: inherit !important;
|
||||
|
||||
}
|
||||
|
||||
.table td.fit-content,
|
||||
@@ -144,3 +163,7 @@ a.discrete-link:focus {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.well {
|
||||
background-color: #e5e5e5;
|
||||
}
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
3
public/index.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../app.php';
|
33
public/js/extensions.js
Normal file
@@ -0,0 +1,33 @@
|
||||
EmptyElementWhenMatches = function(selector, text)
|
||||
{
|
||||
if ($(selector).text() === text)
|
||||
{
|
||||
$(selector).text('');
|
||||
}
|
||||
};
|
||||
|
||||
String.prototype.contains = function(search)
|
||||
{
|
||||
return this.toLowerCase().indexOf(search.toLowerCase()) !== -1;
|
||||
};
|
||||
|
||||
String.prototype.isEmpty = function()
|
||||
{
|
||||
return (this.length === 0 || !this.trim());
|
||||
};
|
||||
|
||||
GetUriParam = function(key)
|
||||
{
|
||||
var currentUri = decodeURIComponent(window.location.search.substring(1));
|
||||
var vars = currentUri.split('&');
|
||||
|
||||
for (i = 0; i < vars.length; i++)
|
||||
{
|
||||
var currentParam = vars[i].split('=');
|
||||
|
||||
if (currentParam[0] === key)
|
||||
{
|
||||
return currentParam[1] === undefined ? true : currentParam[1];
|
||||
}
|
||||
}
|
||||
};
|
@@ -1,9 +1,13 @@
|
||||
var Grocy = { };
|
||||
Grocy.Components = { };
|
||||
|
||||
$(function()
|
||||
{
|
||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ContentPage + "']");
|
||||
menuItem.addClass('active');
|
||||
if (!Grocy.ActiveNav.isEmpty())
|
||||
{
|
||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||
menuItem.addClass('active');
|
||||
}
|
||||
|
||||
$.timeago.settings.allowFuture = true;
|
||||
$('time.timeago').timeago();
|
||||
@@ -67,37 +71,3 @@ Grocy.PostJson = function(url, jsonData, success, error)
|
||||
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));
|
||||
}
|
@@ -39,23 +39,8 @@ $('#battery_id').on('change', function(e)
|
||||
|
||||
if (batteryId)
|
||||
{
|
||||
Grocy.FetchJson('/api/batteries/get-battery-details/' + batteryId,
|
||||
function(batteryDetails)
|
||||
{
|
||||
$('#selected-battery-name').text(batteryDetails.battery.name);
|
||||
$('#selected-battery-last-charged').text((batteryDetails.last_charged || 'never'));
|
||||
$('#selected-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
||||
$('#selected-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
||||
|
||||
$('#tracked_time').focus();
|
||||
|
||||
Grocy.EmptyElementWhenMatches('#selected-battery-last-charged-timeago', 'NaN years ago');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
Grocy.Components.BatteryCard.Refresh(batteryId);
|
||||
$('#tracked_time').focus();
|
||||
}
|
||||
});
|
||||
|
20
public/viewjs/components/batterycard.js
Normal file
@@ -0,0 +1,20 @@
|
||||
Grocy.Components.BatteryCard = { };
|
||||
|
||||
Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
||||
{
|
||||
Grocy.FetchJson('/api/batteries/get-battery-details/' + batteryId,
|
||||
function(batteryDetails)
|
||||
{
|
||||
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
||||
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || 'never'));
|
||||
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
||||
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
||||
|
||||
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', 'NaN years ago');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
91
public/viewjs/components/datepicker.js
Normal file
@@ -0,0 +1,91 @@
|
||||
$(function()
|
||||
{
|
||||
$('.datepicker').datepicker(
|
||||
{
|
||||
format: 'yyyy-mm-dd',
|
||||
startDate: '+0d',
|
||||
todayHighlight: true,
|
||||
autoclose: true,
|
||||
calendarWeeks: true,
|
||||
orientation: 'bottom auto',
|
||||
weekStart: 1,
|
||||
showOnFocus: false
|
||||
});
|
||||
$('.datepicker').trigger('change');
|
||||
|
||||
EmptyElementWhenMatches('#datepicker-timeago', 'NaN years ago');
|
||||
});
|
||||
|
||||
$('.datepicker').on('keydown', function(e)
|
||||
{
|
||||
if (e.keyCode === 13) //Enter
|
||||
{
|
||||
$('.datepicker').trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
$('.datepicker').on('keypress', function(e)
|
||||
{
|
||||
var element = $(e.target);
|
||||
var value = element.val();
|
||||
var dateObj = moment(element.val(), 'YYYY-MM-DD', true);
|
||||
|
||||
$('.datepicker').datepicker('hide');
|
||||
|
||||
//If input is empty and any arrow key is pressed, set date to today
|
||||
if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
|
||||
{
|
||||
dateObj = moment(new Date(), 'YYYY-MM-DD', true);
|
||||
}
|
||||
|
||||
if (dateObj.isValid())
|
||||
{
|
||||
if (e.keyCode === 38) //Up
|
||||
{
|
||||
element.val(dateObj.add(-1, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 40) //Down
|
||||
{
|
||||
element.val(dateObj.add(1, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 37) //Left
|
||||
{
|
||||
element.val(dateObj.add(-1, 'weeks').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 39) //Right
|
||||
{
|
||||
element.val(dateObj.add(1, 'weeks').format('YYYY-MM-DD'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.datepicker').on('change', function(e)
|
||||
{
|
||||
var value = $('.datepicker').val();
|
||||
var now = new Date();
|
||||
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
|
||||
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
|
||||
|
||||
if (value === 'x' || value === 'X') {
|
||||
value = '29991231';
|
||||
}
|
||||
|
||||
if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
|
||||
{
|
||||
value = (new Date()).getFullYear().toString() + value;
|
||||
}
|
||||
|
||||
if (value.length === 8 && $.isNumeric(value))
|
||||
{
|
||||
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||
$('.datepicker').val(value);
|
||||
}
|
||||
|
||||
$('#datepicker-timeago').text($.timeago($('.datepicker').val()));
|
||||
EmptyElementWhenMatches('#datepicker-timeago', 'NaN years ago');
|
||||
});
|
||||
|
||||
$('#datepicker-button').on('click', function(e)
|
||||
{
|
||||
$('.datepicker').datepicker('show');
|
||||
});
|
10
public/viewjs/components/datetimepicker.js
Normal file
@@ -0,0 +1,10 @@
|
||||
$(function()
|
||||
{
|
||||
$('.datetimepicker').datetimepicker(
|
||||
{
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
showTodayButton: true,
|
||||
calendarWeeks: true,
|
||||
maxDate: moment()
|
||||
});
|
||||
});
|
20
public/viewjs/components/habitcard.js
Normal file
@@ -0,0 +1,20 @@
|
||||
Grocy.Components.HabitCard = { };
|
||||
|
||||
Grocy.Components.HabitCard.Refresh = function (habitId)
|
||||
{
|
||||
Grocy.FetchJson('/api/habits/get-habit-details/' + habitId,
|
||||
function(habitDetails)
|
||||
{
|
||||
$('#habitcard-habit-name').text(habitDetails.habit.name);
|
||||
$('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || 'never'));
|
||||
$('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
|
||||
$('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0'));
|
||||
|
||||
EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', 'NaN years ago');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
25
public/viewjs/components/productcard.js
Normal file
@@ -0,0 +1,25 @@
|
||||
Grocy.Components.ProductCard = { };
|
||||
|
||||
Grocy.Components.ProductCard.Refresh = function(productId)
|
||||
{
|
||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||
function(productDetails)
|
||||
{
|
||||
$('#productcard-product-name').text(productDetails.product.name);
|
||||
$('#productcard-product-stock-amount').text(productDetails.stock_amount || '0');
|
||||
$('#productcard-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
|
||||
$('#productcard-product-stock-qu-name2').text(productDetails.quantity_unit_stock.name);
|
||||
$('#productcard-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
|
||||
$('#productcard-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
||||
$('#productcard-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
|
||||
$('#productcard-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
||||
|
||||
EmptyElementWhenMatches('#productcard-product-last-purchased-timeago', 'NaN years ago');
|
||||
EmptyElementWhenMatches('#productcard-product-last-used-timeago', 'NaN years ago');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
};
|
@@ -44,24 +44,15 @@ $('#product_id').on('change', function(e)
|
||||
|
||||
if (productId)
|
||||
{
|
||||
Grocy.Components.ProductCard.Refresh(productId);
|
||||
|
||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||
function (productDetails)
|
||||
{
|
||||
$('#selected-product-name').text(productDetails.product.name);
|
||||
$('#selected-product-stock-amount').text(productDetails.stock_amount || '0');
|
||||
$('#selected-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
|
||||
$('#selected-product-stock-qu-name2').text(productDetails.quantity_unit_stock.name);
|
||||
$('#selected-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
|
||||
$('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
||||
$('#selected-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
|
||||
$('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
||||
$('#amount').attr('max', productDetails.stock_amount);
|
||||
$('#consume-form').validator('update');
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
||||
|
||||
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
||||
Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago');
|
||||
|
||||
if ((productDetails.stock_amount || 0) === 0)
|
||||
{
|
||||
$('#product_id').val('');
|
||||
@@ -124,6 +115,10 @@ $(function()
|
||||
{
|
||||
$('#product_id_text_input').focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).select();
|
||||
}
|
||||
});
|
||||
|
||||
$('#consume-form input').keydown(function(event)
|
88
public/viewjs/habittracking.js
Normal file
@@ -0,0 +1,88 @@
|
||||
$('#save-habittracking-button').on('click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var jsonForm = $('#habittracking-form').serializeJSON();
|
||||
|
||||
Grocy.FetchJson('/api/habits/get-habit-details/' + jsonForm.habit_id,
|
||||
function (habitDetails)
|
||||
{
|
||||
Grocy.FetchJson('/api/habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + $('#tracked_time').val(),
|
||||
function(result)
|
||||
{
|
||||
toastr.success('Tracked execution of habit ' + habitDetails.habit.name + ' on ' + $('#tracked_time').val());
|
||||
|
||||
$('#habit_id').val('');
|
||||
$('#habit_id_text_input').focus();
|
||||
$('#habit_id_text_input').val('');
|
||||
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||
$('#tracked_time').trigger('change');
|
||||
$('#habit_id_text_input').trigger('change');
|
||||
$('#habittracking-form').validator('validate');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#habit_id').on('change', function(e)
|
||||
{
|
||||
var habitId = $(e.target).val();
|
||||
|
||||
if (habitId)
|
||||
{
|
||||
Grocy.Components.HabitCard.Refresh(habitId);
|
||||
$('#tracked_time').focus();
|
||||
}
|
||||
});
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||
$('#tracked_time').trigger('change');
|
||||
|
||||
$('#tracked_time').on('focus', function(e)
|
||||
{
|
||||
if ($('#habit_id_text_input').val().length === 0)
|
||||
{
|
||||
$('#habit_id_text_input').focus();
|
||||
}
|
||||
});
|
||||
|
||||
$('.combobox').combobox({
|
||||
appendId: '_text_input'
|
||||
});
|
||||
|
||||
$('#habit_id').val('');
|
||||
$('#habit_id_text_input').focus();
|
||||
$('#habit_id_text_input').val('');
|
||||
$('#habit_id_text_input').trigger('change');
|
||||
|
||||
$('#habittracking-form').validator();
|
||||
$('#habittracking-form').validator('validate');
|
||||
|
||||
$('#habittracking-form input').keydown(function(event)
|
||||
{
|
||||
if (event.keyCode === 13) //Enter
|
||||
{
|
||||
if ($('#habittracking-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
|
||||
{
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#tracked_time').on('keypress', function(e)
|
||||
{
|
||||
$('#habittracking-form').validator('validate');
|
||||
});
|
@@ -10,7 +10,7 @@
|
||||
Grocy.FetchJson('/api/stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
||||
function(result)
|
||||
{
|
||||
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
|
||||
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||
if (addBarcode !== undefined)
|
||||
{
|
||||
var existingBarcodes = productDetails.product.barcode || '';
|
||||
@@ -33,7 +33,6 @@
|
||||
}
|
||||
|
||||
toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name);
|
||||
Grocy.Wait(1000);
|
||||
|
||||
if (addBarcode !== undefined)
|
||||
{
|
||||
@@ -70,24 +69,15 @@ $('#product_id').on('change', function(e)
|
||||
|
||||
if (productId)
|
||||
{
|
||||
Grocy.Components.ProductCard.Refresh(productId);
|
||||
|
||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
||||
function(productDetails)
|
||||
{
|
||||
$('#selected-product-name').text(productDetails.product.name);
|
||||
$('#selected-product-stock-amount').text(productDetails.stock_amount || '0');
|
||||
$('#selected-product-stock-qu-name').text(productDetails.quantity_unit_stock.name);
|
||||
$('#selected-product-purchase-qu-name').text(productDetails.quantity_unit_purchase.name);
|
||||
$('#selected-product-last-purchased').text((productDetails.last_purchased || 'never').substring(0, 10));
|
||||
$('#selected-product-last-purchased-timeago').text($.timeago(productDetails.last_purchased || ''));
|
||||
$('#selected-product-last-used').text((productDetails.last_used || 'never').substring(0, 10));
|
||||
$('#selected-product-last-used-timeago').text($.timeago(productDetails.last_used || ''));
|
||||
$('#new_amount').attr('not-equal', productDetails.stock_amount);
|
||||
$('#new_amount_qu_unit').text(productDetails.quantity_unit_stock.name);
|
||||
|
||||
$('#new_amount').focus();
|
||||
|
||||
Grocy.EmptyElementWhenMatches('#selected-product-last-purchased-timeago', 'NaN years ago');
|
||||
Grocy.EmptyElementWhenMatches('#selected-product-last-used-timeago', 'NaN years ago');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
@@ -99,19 +89,6 @@ $('#product_id').on('change', function(e)
|
||||
|
||||
$(function()
|
||||
{
|
||||
$('.datepicker').datepicker(
|
||||
{
|
||||
format: 'yyyy-mm-dd',
|
||||
startDate: '+0d',
|
||||
todayHighlight: true,
|
||||
autoclose: true,
|
||||
calendarWeeks: true,
|
||||
orientation: 'bottom auto',
|
||||
weekStart: 1,
|
||||
showOnFocus: false
|
||||
});
|
||||
$('.datepicker').trigger('change');
|
||||
|
||||
$('.combobox').combobox({
|
||||
appendId: '_text_input'
|
||||
});
|
||||
@@ -121,7 +98,7 @@ $(function()
|
||||
var input = $('#product_id_text_input').val().toString();
|
||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
|
||||
|
||||
if (Grocy.GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
|
||||
if (GetUriParam('addbarcodetoselection') === undefined && possibleOptionElement.length > 0)
|
||||
{
|
||||
$('#product_id').val(possibleOptionElement.val());
|
||||
$('#product_id').data('combobox').refresh();
|
||||
@@ -130,7 +107,7 @@ $(function()
|
||||
else
|
||||
{
|
||||
var optionElement = $("#product_id option:contains('" + input + "')").first();
|
||||
if (input.length > 0 && optionElement.length === 0 && Grocy.GetUriParam('addbarcodetoselection') === undefined )
|
||||
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
||||
{
|
||||
bootbox.dialog({
|
||||
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
||||
@@ -228,6 +205,10 @@ $(function()
|
||||
{
|
||||
$('#product_id_text_input').focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).select();
|
||||
}
|
||||
});
|
||||
|
||||
$('#inventory-form input').keydown(function(event)
|
||||
@@ -242,7 +223,7 @@ $(function()
|
||||
}
|
||||
});
|
||||
|
||||
var prefillProduct = Grocy.GetUriParam('createdproduct');
|
||||
var prefillProduct = GetUriParam('createdproduct');
|
||||
if (prefillProduct !== undefined)
|
||||
{
|
||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + prefillProduct + "']").first();
|
||||
@@ -260,7 +241,7 @@ $(function()
|
||||
}
|
||||
}
|
||||
|
||||
var addBarcode = Grocy.GetUriParam('addbarcodetoselection');
|
||||
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||
if (addBarcode !== undefined)
|
||||
{
|
||||
$('#addbarcodetoselection').text(addBarcode);
|
||||
@@ -269,73 +250,29 @@ $(function()
|
||||
}
|
||||
});
|
||||
|
||||
$('#best_before_date-datepicker-button').on('click', function(e)
|
||||
$('#new_amount').on('keypress', function(e)
|
||||
{
|
||||
$('.datepicker').datepicker('show');
|
||||
$('#new_amount').trigger('change');
|
||||
});
|
||||
|
||||
|
||||
$('#best_before_date').on('change', function(e)
|
||||
{
|
||||
var value = $('#best_before_date').val();
|
||||
var now = new Date();
|
||||
var centuryStart = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '00');
|
||||
var centuryEnd = Number.parseInt(now.getFullYear().toString().substring(0, 2) + '99');
|
||||
|
||||
if (value === 'x' || value === 'X')
|
||||
{
|
||||
value = '29991231';
|
||||
}
|
||||
|
||||
if (value.length === 4 && !(Number.parseInt(value) > centuryStart && Number.parseInt(value) < centuryEnd))
|
||||
{
|
||||
value = (new Date()).getFullYear().toString() + value;
|
||||
}
|
||||
|
||||
if (value.length === 8 && $.isNumeric(value))
|
||||
{
|
||||
value = value.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
|
||||
$('#best_before_date').val(value);
|
||||
$('#inventory-form').validator('validate');
|
||||
}
|
||||
$('#inventory-form').validator('validate');
|
||||
});
|
||||
|
||||
$('#best_before_date').on('keypress', function(e)
|
||||
{
|
||||
var element = $(e.target);
|
||||
var value = element.val();
|
||||
var dateObj = moment(element.val(), 'YYYY-MM-DD', true);
|
||||
|
||||
$('.datepicker').datepicker('hide');
|
||||
|
||||
//If input is empty and any arrow key is pressed, set date to today
|
||||
if (value.length === 0 && (e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39))
|
||||
{
|
||||
dateObj = moment(new Date(), 'YYYY-MM-DD', true);
|
||||
}
|
||||
|
||||
if (dateObj.isValid())
|
||||
{
|
||||
if (e.keyCode === 38) //Up
|
||||
{
|
||||
element.val(dateObj.add(-1, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 40) //Down
|
||||
{
|
||||
element.val(dateObj.add(1, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 37) //Left
|
||||
{
|
||||
element.val(dateObj.add(-1, 'weeks').format('YYYY-MM-DD'));
|
||||
}
|
||||
else if (e.keyCode === 39) //Right
|
||||
{
|
||||
element.val(dateObj.add(1, 'weeks').format('YYYY-MM-DD'));
|
||||
}
|
||||
}
|
||||
|
||||
$('#inventory-form').validator('validate');
|
||||
});
|
||||
|
||||
$('#best_before_date').on('keydown', function(e)
|
||||
{
|
||||
if (e.keyCode === 13) //Enter
|
||||
{
|
||||
$('#best_before_date').trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
$('#new_amount').on('change', function(e)
|
||||
{
|
||||
if ($('#product_id').parent().hasClass('has-error'))
|
@@ -4,7 +4,7 @@
|
||||
|
||||
$('#username').focus();
|
||||
|
||||
if (Grocy.GetUriParam('invalid') === 'true')
|
||||
if (GetUriParam('invalid') === 'true')
|
||||
{
|
||||
$('#login-error').text('Invalid credentials, please try again.');
|
||||
$('#login-error').show();
|
@@ -3,7 +3,7 @@
|
||||
e.preventDefault();
|
||||
|
||||
var redirectDestination = '/products';
|
||||
var returnTo = Grocy.GetUriParam('returnto');
|
||||
var returnTo = GetUriParam('returnto');
|
||||
if (returnTo !== undefined)
|
||||
{
|
||||
redirectDestination = returnTo + '?createdproduct=' + encodeURIComponent($('#name').val());
|
||||
@@ -69,14 +69,14 @@ $(function()
|
||||
$('#product-form').validator();
|
||||
$('#product-form').validator('validate');
|
||||
|
||||
var prefillName = Grocy.GetUriParam('prefillname');
|
||||
var prefillName = GetUriParam('prefillname');
|
||||
if (prefillName !== undefined)
|
||||
{
|
||||
$('#name').val(prefillName);
|
||||
$('#name').focus();
|
||||
}
|
||||
|
||||
var prefillBarcode = Grocy.GetUriParam('prefillbarcode');
|
||||
var prefillBarcode = GetUriParam('prefillbarcode');
|
||||
if (prefillBarcode !== undefined)
|
||||
{
|
||||
$('#barcode-taginput').tagsManager('pushTag', prefillBarcode);
|