Compare commits

..

17 Commits

Author SHA1 Message Date
Bernd Bestel
756ec319cc Update dependencies for next release 2018-09-30 20:11:24 +02:00
Bernd Bestel
ba2d32be60 Fixes for auto night mode (references #71) 2018-09-30 19:31:03 +02:00
Bernd Bestel
7cc09cec67 The last fix (maybe) for auto night mode handling (references #71) 2018-09-30 18:07:28 +02:00
Bernd Bestel
8b815fce93 Finalize auto night mode feature (references #71) 2018-09-30 18:02:59 +02:00
Bernd Bestel
f1c78659be Optimize user settings 2018-09-30 17:14:04 +02:00
Bernd Bestel
5c79a80f7a Prepared auto night mode configuration option (references #71) 2018-09-30 13:33:21 +02:00
Bernd Bestel
f451e65278 Also log missing localization found in frontend (only when MODE == dev) 2018-09-30 13:02:07 +02:00
Bernd Bestel
176333df5b Save night mode enabled state and apply night mode class to <body> on server side (references #71) 2018-09-30 11:25:07 +02:00
Bernd Bestel
d4227d2e41 Make auto reloading the page on external database changes configurable (closes #74) 2018-09-30 11:17:28 +02:00
Bernd Bestel
0bbd2d9880 Prepare user settings API (references #74 and #71) 2018-09-30 10:47:56 +02:00
Bernd Bestel
b81316bd60 Include products which are not in stock currently but below min. stock amount on stock overview page (fixes #69) 2018-09-30 10:13:37 +02:00
Bernd Bestel
d11dcb38fe Only reload the page on external changes when there is no unsaved form data (fixes #73) 2018-09-30 09:57:42 +02:00
Bernd Bestel
77d82f22dc Fixed scrolling did not work when showing a recipe in fullscreen mode (fixes #76) 2018-09-30 09:41:22 +02:00
Talmai Oliveira
be326a5211 Grocy docker patch (#78)
* typo corrections

* more typos

* initial work towards dockerized version of grocy

* placeholder for future README

* fully working dockerized grocy

* updated final size of docker images
2018-09-30 09:31:16 +02:00
Marius Boro
83624eaf27 Update no.php (#75) 2018-09-30 09:22:30 +02:00
Bernd Bestel
055619d275 Tweaked grocy_night_mode.css slightly (references #71) 2018-09-29 16:35:17 +02:00
Bernd Bestel
cda3dde120 Quick test implementation of night (references #71) 2018-09-29 15:39:16 +02:00
36 changed files with 1052 additions and 47 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
.vscode
.gitignore
build.bat
Dockerfile
.DS_store

58
Dockerfile-grocy Normal file
View File

@@ -0,0 +1,58 @@
FROM php:7.2-fpm-alpine
MAINTAINER Talmai Oliveira <to@talm.ai>
RUN apk update && \
apk upgrade && \
apk add --update yarn git &&\
mkdir -p /www && \
# Set environments
sed -i "s|;*daemonize\s*=\s*yes|daemonize = no|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*listen\s*=\s*127.0.0.1:9000|listen = 9000|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*listen\s*=\s*/||g" /usr/local/etc/php-fpm.conf && \
# sed -i "s|;*log_level\s*=\s*notice|log_level = debug|g" /usr/local/etc/php-fpm.conf && \
sed -i "s|;*chdir\s*=\s*/var/www|chdir = /www|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*access.log\s*=\s*log/\$pool.access.log|access.log = \$pool.access.log|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*pm.status_path\s*=\s*/status|pm.status_path = /status|g" /usr/local/etc/php-fpm.d/www.conf && \
# sed -i "s|;*memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*post_max_size =.*|post_max_size = ${PHP_MAX_POST}|i" /usr/local/etc/php.ini && \
# sed -i "s|;*cgi.fix_pathinfo=.*|cgi.fix_pathinfo= 0|i" /usr/local/etc/php.ini && \
wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet && \
# Cleaning up
rm -rf /var/cache/apk/*
COPY public /www/public
COPY info.php /www/public
COPY controllers /www/controllers
COPY data /www/data
COPY helpers /www/helpers
COPY localization/ /www/localization
COPY middleware/ /www/middleware
COPY migrations/ /www/migrations
COPY publication_assets/ /www/publication_assets
COPY services/ /www/services
COPY views/ /www/views
COPY .yarnrc /www/
COPY *.php /www/
COPY *.json /www/
COPY composer.* /root/.composer/
COPY *yarn* /www/
COPY *.sh /www/
# run php composer.phar with -vvv for extra debug information
RUN cd /var/www/html && \
php composer.phar --working-dir=/www/ -n install && \
cp /www/config-dist.php /www/data/config.php && \
cd /www && \
yarn install && \
chown www-data:www-data -R /www/
# Set Workdir
WORKDIR /www/public
# Expose volumes
VOLUME ["/www"]
# Expose ports
EXPOSE 9000

32
Dockerfile-grocy-nginx Normal file
View File

@@ -0,0 +1,32 @@
FROM alpine:latest
MAINTAINER Talmai Oliveira <to@talm.ai>
RUN apk update && \
apk upgrade && \
apk add --update openssl nginx && \
mkdir -p /etc/nginx/certificates && \
mkdir -p /var/run/nginx && \
mkdir -p /usr/share/nginx/html && \
openssl req \
-x509 \
-newkey rsa:2048 \
-keyout /etc/nginx/certificates/key.pem \
-out /etc/nginx/certificates/cert.pem \
-days 365 \
-nodes \
-subj /CN=localhost && \
rm -rf /var/cache/apk/*
COPY docker_nginx/nginx.conf /etc/nginx/nginx.conf
COPY docker_nginx/common.conf /etc/nginx/common.conf
COPY docker_nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
COPY docker_nginx/conf.d/ssl.conf /etc/nginx/conf.d/ssl.conf
# Expose volumes
VOLUME ["/etc/nginx/conf.d", "/var/log/nginx"]
# Expose ports
EXPOSE 80 443
# Entry point
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

View File

@@ -6,7 +6,7 @@ ERP beyond your fridge
- Public demo of the latest pre-release version (current master branch) &rarr; [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info)
## Motivation
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing. ERP your fridge!
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
## How to install
> **NEW**
@@ -23,6 +23,16 @@ If you use nginx as your webserver, please include `try_files $uri /index.php;`
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`).
## How to run using Docker
The docker images build are based on [Alpine](https://hub.docker.com/_/alpine/), with an extremelly low footprint (less than 10 MB for nginx, and less than 70MB for grocy with php-fm. That number is eventually bumped up to 353MB after all the dependencies are downloaded, however). Anyhow, to run using docker just do the following:
```
> docker-compose up
```
And grocy should be accessible via `http(s)://localhost/`. The https option will work. However, since the certificate is self-signed, most browsers will complain.
## How to update
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.

42
composer.lock generated
View File

@@ -159,7 +159,7 @@
},
{
"name": "illuminate/container",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/container.git",
@@ -203,7 +203,7 @@
},
{
"name": "illuminate/contracts",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
@@ -247,7 +247,7 @@
},
{
"name": "illuminate/events",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/events.git",
@@ -292,7 +292,7 @@
},
{
"name": "illuminate/filesystem",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/filesystem.git",
@@ -344,7 +344,7 @@
},
{
"name": "illuminate/support",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
@@ -403,7 +403,7 @@
},
{
"name": "illuminate/view",
"version": "v5.7.5",
"version": "v5.7.6",
"source": {
"type": "git",
"url": "https://github.com/illuminate/view.git",
@@ -1167,16 +1167,16 @@
},
{
"name": "symfony/debug",
"version": "v4.1.4",
"version": "v4.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "47ead688f1f2877f3f14219670f52e4722ee7052"
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/47ead688f1f2877f3f14219670f52e4722ee7052",
"reference": "47ead688f1f2877f3f14219670f52e4722ee7052",
"url": "https://api.github.com/repos/symfony/debug/zipball/b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
"shasum": ""
},
"require": {
@@ -1219,20 +1219,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2018-08-03T11:13:38+00:00"
"time": "2018-09-22T19:04:12+00:00"
},
{
"name": "symfony/finder",
"version": "v4.1.4",
"version": "v4.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068"
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
"url": "https://api.github.com/repos/symfony/finder/zipball/f0b042d445c155501793e7b8007457f9f5bb1c8c",
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c",
"shasum": ""
},
"require": {
@@ -1268,7 +1268,7 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T11:24:31+00:00"
"time": "2018-09-21T12:49:42+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -1331,16 +1331,16 @@
},
{
"name": "symfony/translation",
"version": "v4.1.4",
"version": "v4.1.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "fa2182669f7983b7aa5f1a770d053f79f0ef144f"
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/fa2182669f7983b7aa5f1a770d053f79f0ef144f",
"reference": "fa2182669f7983b7aa5f1a770d053f79f0ef144f",
"url": "https://api.github.com/repos/symfony/translation/zipball/6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
"shasum": ""
},
"require": {
@@ -1396,7 +1396,7 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2018-08-07T12:45:11+00:00"
"time": "2018-09-21T12:49:42+00:00"
},
{
"name": "tuupola/callable-handler",

View File

@@ -7,7 +7,7 @@ Setting('MODE', 'production');
# one of the other available localization files in the "/localization" directory
Setting('CULTURE', 'en');
# To keep it simpel, grocy does not handle any currency conversions,
# To keep it simple: grocy does not handle any currency conversions,
# this here is used to format all money values,
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
Setting('CURRENCY', '$');
@@ -25,3 +25,20 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
# If, however, your webserver does not support URL rewriting,
# set this to true
Setting('DISABLE_URL_REWRITING', false);
# Default user settings
# These settings can be changed per user, here the defaults
# are defined which are used when the user has not changed the setting so far
# Night mode related
DefaultUserSetting('night_mode_enabled', false); // If night mode is enabled always
DefaultUserSetting('auto_night_mode_enabled', false); // If night mode is enabled automatically when inside a given time range (see the two settings below)
DefaultUserSetting('auto_night_mode_time_range_from', "20:00"); // Format HH:mm
DefaultUserSetting('auto_night_mode_time_range_to', "07:00"); // Format HH:mm
DefaultUserSetting('auto_night_mode_time_range_goes_over_midnight', true); // If the time range above goes over midnight
DefaultUserSetting('currently_inside_night_mode_range', false); // If we're currently inside of night mode time range (this is not user configurable, but stored as a user setting because it's evaluated client side to be able to use the client time instead of the maybe different server time)
# If the page should be automatically reloaded when there was
# an external change
DefaultUserSetting('auto_reload_on_db_change', true);

View File

@@ -5,6 +5,7 @@ namespace Grocy\Controllers;
use \Grocy\Services\DatabaseService;
use \Grocy\Services\ApplicationService;
use \Grocy\Services\LocalizationService;
use \Grocy\Services\UsersService;
class BaseController
{
@@ -41,6 +42,15 @@ class BaseController
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
});
try {
$usersService = new UsersService();
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
}
catch (\Exception $ex)
{
// Happens when database is not initialised or migrated...
}
$this->AppContainer = $container;
}

View File

@@ -20,4 +20,22 @@ class SystemApiController extends BaseApiController
'changed_time' => $this->DatabaseService->GetDbChangedTime()
));
}
public function LogMissingLocalization(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
if (GROCY_MODE === 'dev')
{
try
{
$requestBody = $request->getParsedBody();
$this->LocalizationService->LogMissingLocalization(GROCY_CULTURE, $requestBody['text']);
return $this->ApiResponse(array('success' => true));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}
}

View File

@@ -68,4 +68,32 @@ class UsersApiController extends BaseApiController
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function GetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$value = $this->UsersService->GetUserSetting(GROCY_USER_ID, $args['settingKey']);
return $this->ApiResponse(array('value' => $value));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
public function SetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{
try
{
$requestBody = $request->getParsedBody();
$value = $this->UsersService->SetUserSetting(GROCY_USER_ID, $args['settingKey'], $requestBody['value']);
return $this->ApiResponse(array('success' => true));
}
catch (\Exception $ex)
{
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
}
}
}

30
docker-compose.yml Normal file
View File

@@ -0,0 +1,30 @@
# Usage:
# docker-compose build && docker-compose up
version: '2'
services:
grocy-nginx:
build:
context: .
dockerfile: Dockerfile-grocy-nginx
depends_on:
- grocy
ports:
- '80:80'
- '443:443'
volumes_from:
- grocy
container_name: grocy-nginx
grocy:
build:
context: .
dockerfile: Dockerfile-grocy
expose:
- 9000
environment:
PHP_MEMORY_LIMIT: 512M
MAX_UPLOAD: 50M
PHP_MAX_FILE_UPLOAD: 200
PHP_MAX_POST: 100M
container_name: grocy

28
docker_nginx/common.conf Normal file
View File

@@ -0,0 +1,28 @@
index index.php index.html index.htm;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
fastcgi_pass grocy:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}

View File

@@ -0,0 +1,8 @@
server {
listen 80 default_server;
server_name _;
root /www/public; # see: volumes_from
include /etc/nginx/common.conf;
}

View File

@@ -0,0 +1,20 @@
server {
listen 443 ssl;
server_name _;
root /www/public; # see: volumes_from
ssl_certificate /etc/nginx/certificates/cert.pem;
ssl_certificate_key /etc/nginx/certificates/key.pem;
error_log /var/log/nginx/error.log;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
include /etc/nginx/common.conf;
}

42
docker_nginx/nginx.conf Normal file
View File

@@ -0,0 +1,42 @@
user nobody;
worker_processes 1;
pid /var/run/nginx/nginx.pid;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
#tcp_nopush on;
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 50M;
large_client_header_buffers 2 1k;
gzip on;
gzip_comp_level 2;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/x-javascript text/xml text/css application/xml;
access_log on;
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -44,6 +44,47 @@
}
}
},
"/system/log-missing-localization": {
"post": {
"description": "Logs a missing localization string (only when MODE == 'dev', so should only be called then)",
"tags": [
"System"
],
"requestBody": {
"description": "A valid MissingLocalizationRequest object",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MissingLocalizationRequest"
}
}
}
},
"responses": {
"200": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VoidApiActionResponse"
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
}
}
}
}
}
}
},
"/get-objects/{entity}": {
"get": {
"description": "Returns all objects of the given entity",
@@ -617,6 +658,97 @@
}
}
},
"/user/settings/{settingKey}": {
"get": {
"description": "Gets the given setting of the currently logged on user",
"tags": [
"User settings"
],
"parameters": [
{
"in": "path",
"name": "settingKey",
"required": true,
"description": "The key of the user setting",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "A UserSetting object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSetting"
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
}
}
}
}
}
},
"post": {
"description": "Sets the given setting of the currently logged on user",
"tags": [
"User settings"
],
"requestBody": {
"description": "A valid UserSetting object",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSetting"
}
}
}
},
"parameters": [
{
"in": "path",
"name": "settingKey",
"required": true,
"description": "The key of the user setting",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VoidApiActionResponse"
}
}
}
},
"400": {
"description": "A VoidApiActionResponse object",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
}
}
}
}
}
}
},
"/stock/add-product/{productId}/{amount}": {
"get": {
"description": "Adds the the given amount of the given product to stock",
@@ -2098,6 +2230,22 @@
"format": "date-time"
}
}
},
"UserSetting": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
},
"MissingLocalizationRequest": {
"type": "object",
"properties": {
"text": {
"type": "string"
}
}
}
},
"examples": {

View File

@@ -145,6 +145,17 @@ function Setting(string $name, $value)
}
}
global $GROCY_DEFAULT_USER_SETTINGS;
$GROCY_DEFAULT_USER_SETTINGS = array();
function DefaultUserSetting(string $name, $value)
{
global $GROCY_DEFAULT_USER_SETTINGS;
if (!array_key_exists($name, $GROCY_DEFAULT_USER_SETTINGS))
{
$GROCY_DEFAULT_USER_SETTINGS[$name] = $value;
}
}
function GetUserDisplayName($user)
{
$displayName = '';

6
info.php Normal file
View File

@@ -0,0 +1,6 @@
<?php
// Show all information, defaults to INFO_ALL
phpinfo();
?>

View File

@@ -249,6 +249,14 @@ return array(
'Already expired' => 'Bereits abgelaufen',
'Due soon' => 'Bald fällig',
'Overdue' => 'Überfällig',
'View settings' => 'xxx',
'Auto reload on external changes' => 'Autom. akt. bei externen Änderungen',
'Enable night mode' => 'Nachtmodus aktivieren',
'Auto enable in time range' => 'Autom. akt. in diesem Zeitraum',
'From' => 'Von',
'in format' => 'im Format',
'To' => 'Bis',
'Time range goes over midnight' => 'Zeitraum geht über Mitternacht',
//Constants
'manually' => 'Manuell',

View File

@@ -12,7 +12,7 @@ return array(
'Chores overview' => 'Oversikt Husarbeid',
'Batteries overview' => 'Oversikt Batteri',
'Purchase' => 'Innkjøp',
'Consume' => 'Forbrukt',
'Consume' => 'Forbruk produkt',
'Inventory' => 'Endre Husholdning',
'Shopping list' => 'Handleliste',
'Chore tracking' => 'Logge Husarbeid',
@@ -124,7 +124,7 @@ return array(
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
'Tracked execution of chore #1 on #2' => 'Utførte husarbeid oppgave "#1" den #2',
'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2',
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
'Consume all #1 which are currently in stock' => 'Forbruk alle #1 som er i husholdningen',
'All' => 'Alle',
'Track charge cycle of battery #1' => '#1 ladet',
'Track execution of chore #1' => 'Utfør husarbeid oppgave #1',
@@ -212,7 +212,7 @@ return array(
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Huk av hvis du ønsker å bruke mindre enn forpakningsstørrelse i husholdningen',
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Er du sikker du ønsker å forbruke alle ingredienser for "#1" oppskriften? (Ingredienser merket med "bruke mindre enn forpakningsstørrelse i husholdningen" blir ignorert',
'Removed all ingredients of recipe "#1" from stock' => 'Fjern alle ingredienser for "#1" oppskriften fra husholdningen.',
'Consume all ingredients needed by this recipe' => 'Konsumer alle ingredienser for denne oppskriften',
'Consume all ingredients needed by this recipe' => 'Forbruk alle ingredienser for denne oppskriften',
'Click to show technical details' => 'Klikk for å vise teknisk informasjon',
'Error while saving, probably this item already exists' => 'Kunne ikke lagre, produkt er lagt til fra før',
'Error details' => 'Detaljer om feil',

27
migrations/0038.sql Normal file
View File

@@ -0,0 +1,27 @@
DROP VIEW stock_missing_products;
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,
CASE WHEN s.id IS NOT NULL THEN 1 ELSE 0 END AS is_partly_in_stock
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;
DROP VIEW stock_current;
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
UNION
SELECT id, 0, null
FROM stock_missing_products
WHERE is_partly_in_stock = 0;

10
migrations/0039.sql Normal file
View File

@@ -0,0 +1,10 @@
CREATE TABLE user_settings (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
user_id INTEGER NOT NULL,
key TEXT NOT NULL,
value TEXT,
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
row_updated_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
UNIQUE(user_id, key)
);

View File

@@ -50,12 +50,13 @@ a.discrete-link:focus {
}
.fullscreen {
z-index: 9999;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
overflow: auto;
}
.form-check-input.is-valid ~ .form-check-label,

View File

@@ -0,0 +1,199 @@
body.night-mode {
color: #c1c1c1;
}
.night-mode .table-info,
.night-mode .table-info > td,
.night-mode .table-info > th {
background-color: #07373f;
}
.night-mode .btn,
.night-mode .nav-link,
.night-mode #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link::after,
.night-mode .dropdown-item {
color: #c1c1c1 !important;
}
.night-mode .btn-outline-dark {
border-color: #c1c1c1;
}
.night-mode .btn-info {
color: #c1c1c1;
background-color: #07373f;
border-color: #07373f;
}
.night-mode .btn-warning {
color: #c1c1c1;
background-color: #473604;
border-color: #473604;
}
.night-mode .btn-danger {
color: #c1c1c1;
background-color: #471116;
border-color: #471116;
}
.night-mode .btn-success {
color: #c1c1c1;
background-color: #0d3a18;
border-color: #0d3a18;
}
.night-mode .form-control {
color: #495057;
background-color: #333131;
border: 1px solid #ced4da;
}
.night-mode .content-wrapper {
background: #333131;
}
.night-mode table.dataTable tr.group td {
font-weight: bold;
background-color: #333131;
}
.night-mode .table-danger,
.night-mode .table-danger > td,
.night-mode .table-danger > th {
background-color: #471116;
}
.night-mode .table-warning,
.night-mode .table-warning > td,
.night-mode .table-warning > th {
background-color: #473604;
}
.night-mode .bg-warning {
background-color: #473604!important;
}
.night-mode .bg-info {
background-color: #07373f!important;
}
.night-mode .bg-danger {
background-color: #471116!important;
}
.night-mode .form-control:focus {
color: #495057;
background-color: #333131;
border-color: #80bdff;
}
.night-mode .dropdown-item:focus,
.night-mode .dropdown-item:hover {
color: #16181b;
background-color: #333131;
}
.night-mode .dropdown-item {
color: #7c7b6f;
background-color: #333131;
}
.night-mode .list-group-item {
background-color: #333131;
}
.night-mode .modal-content {
background-color: #1a1919;
border: 1px solid rgba(186, 189, 189, 0.66);
}
.night-mode .modal-footer {
border-top: 1px solid #6f7173;
}
.night-mode .container-fluid {
background-color: #333131;
}
.night-mode a.discrete-link:hover {
color: #16354f !important;
text-decoration: none !important;
background-color: #333131;
}
.night-mode a.discrete-link:focus {
color: #3a0b0f !important;
background-color: #333131;
}
.night-mode .card {
border: 2px solid;
border-color: #383838;
background-color: #333131;
}
.night-mode .card-header {
background-color: #333131;
}
.night-mode #mainNav {
background-color: #333131 !important;
border-bottom: 2px solid !important;
border-color: #383838 !important;
}
.night-mode .navbar-sidenav,
.night-mode .sidenav-second-level {
background-color: #333131 !important;
border-right: 2px solid !important;
border-color: #383838 !important;
}
.night-mode .navbar-nav .dropdown-menu {
background-color: #333131 !important;
}
.night-mode .navbar-nav .dropdown-divider {
border-color: #383838 !important;
background-color: #333131;
}
.night-mode .sidenav-toggler {
background-color: #383838 !important;
border-right: 2px solid !important;
border-color: #383838 !important;
}
.night-mode .navbar-sidenav > li:hover,
.night-mode .sidenav-second-level > li:hover,
.night-mode .navbar-nav .dropdown-item:hover {
box-shadow: inset 5px 0 0 #112a3f !important;
background-color: #383838 !important;
color: #c1c1c1 !important;
}
.night-mode .navbar-sidenav > li > a:focus,
.night-mode .sidenav-second-level > li > a:focus,
.night-mode .navbar-nav .dropdown-item:focus {
box-shadow: inset 5px 0 0 #350a0f !important;
background-color: #383838 !important;
color: #c1c1c1 !important;
}
.night-mode .active-page {
box-shadow: inset 5px 0 0 #350a0f !important;
background-color: #383838 !important;
}
.night-mode .toast-success {
background-color: #092810;
}
.night-mode .toast-error {
background-color: #471015;
}
.night-mode .typeahead .active {
background-color: #333131;
}

View File

@@ -41,3 +41,16 @@ IsTouchInputDevice = function()
return false;
}
BoolVal = function(test)
{
var anything = test.toString().toLowerCase();
if (anything === true || anything === "true" || anything === "1" || anything === "on")
{
return true;
}
else
{
return false;
}
}

View File

@@ -3,6 +3,22 @@
var localizedText = Grocy.LocalizationStrings[text];
if (localizedText === undefined)
{
if (Grocy.Mode === 'dev')
{
jsonData = {};
jsonData.text = text;
Grocy.Api.Post('system/log-missing-localization', jsonData,
function(result)
{
// Nothing to do...
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
localizedText = text;
}
@@ -191,3 +207,44 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
console.error(exception);
}
$("form").on("keyup paste", "input, textarea", function()
{
$(this).closest("form").addClass("is-dirty");
});
$("form").on("click", "select", function()
{
$(this).closest("form").addClass("is-dirty");
});
// Auto saving user setting controls
$(".user-setting-control").on("change", function()
{
var element = $(this);
var inputType = element.attr("type").toLowerCase();
var settingKey = element.attr("data-setting-key");
if (inputType === "checkbox")
{
value = element.is(":checked");
}
else
{
var value = element.val();
}
Grocy.UserSettings[settingKey] = value;
jsonData = { };
jsonData.value = value;
Grocy.Api.Post('user/settings/' + settingKey, jsonData,
function(result)
{
// Nothing to do...
},
function(xhr)
{
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
});

View File

@@ -10,7 +10,8 @@
);
// Check if the database has changed once a minute
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds,
// when there is no unsaved form data and when the user enabled auto reloading
setInterval(function()
{
Grocy.Api.Get('system/get-db-changed-time',
@@ -21,7 +22,10 @@ setInterval(function()
{
if (Grocy.IdleTime >= 50)
{
window.location.reload();
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0)
{
window.location.reload();
}
}
Grocy.DatabaseChangedTime = newDbChangedTime;
@@ -51,3 +55,8 @@ setInterval(function()
{
Grocy.IdleTime += 1;
}, 1000);
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change))
{
$("#auto-reload-enabled").prop("checked", true);
}

View File

@@ -0,0 +1,106 @@
$("#night-mode-enabled").on("change", function()
{
var value = $(this).is(":checked");
if (value)
{
$("body").addClass("night-mode");
}
else
{
$("body").removeClass("night-mode");
}
});
$("#auto-night-mode-enabled").on("change", function()
{
var value = $(this).is(":checked");
$("#auto-night-mode-time-range-from").prop("readonly", !value);
$("#auto-night-mode-time-range-to").prop("readonly", !value);
if (!value && !BoolVal(Grocy.UserSettings.night_mode_enabled))
{
$("body").removeClass("night-mode");
}
});
$(document).on("keyup", "#auto-night-mode-time-range-from, #auto-night-mode-time-range-to", function()
{
var value = $(this).val();
var valueIsValid = moment(value, "HH:mm", true).isValid();
if (valueIsValid)
{
$(this).removeClass("bg-danger");
}
else
{
$(this).addClass("bg-danger");
}
CheckNightMode();
});
$("#auto-night-mode-time-range-goes-over-midgnight").on("change", function()
{
CheckNightMode();
});
$("#night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.night_mode_enabled));
$("#auto-night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_enabled));
$("#auto-night-mode-time-range-goes-over-midgnight").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight));
$("#auto-night-mode-enabled").trigger("change");
$("#auto-night-mode-time-range-from").val(Grocy.UserSettings.auto_night_mode_time_range_from);
$("#auto-night-mode-time-range-from").trigger("keyup");
$("#auto-night-mode-time-range-to").val(Grocy.UserSettings.auto_night_mode_time_range_to);
$("#auto-night-mode-time-range-to").trigger("keyup");
function CheckNightMode()
{
if (!BoolVal(Grocy.UserSettings.auto_night_mode_enabled))
{
return;
}
var start = moment(Grocy.UserSettings.auto_night_mode_time_range_from, "HH:mm", true);
var end = moment(Grocy.UserSettings.auto_night_mode_time_range_to, "HH:mm", true);
var now = moment();
if (!start.isValid() || !end.isValid)
{
return;
}
if (BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight))
{
end.add(1, "day");
}
if (start.isSameOrBefore(now) && end.isSameOrAfter(now)) // We're INSIDE of night mode time range
{
if (!$("body").hasClass("night-mode"))
{
$("body").addClass("night-mode");
$("#currently-inside-night-mode-range").prop("checked", true);
$("#currently-inside-night-mode-range").trigger("change");
}
}
else // We're OUTSIDE of night mode time range
{
if ($("body").hasClass("night-mode"))
{
$("body").removeClass("night-mode");
$("#currently-inside-night-mode-range").prop("checked", false);
$("#currently-inside-night-mode-range").trigger("change");
}
}
}
CheckNightMode();
if (Grocy.Mode === "production")
{
setInterval(CheckNightMode, 60000);
}
else
{
setInterval(CheckNightMode, 4000);
}

View File

@@ -4,7 +4,6 @@
var jsonData = $('#recipe-pos-form').serializeJSON({ checkboxUncheckedValue: "0" });
jsonData.recipe_id = Grocy.EditObjectParentId;
console.log(jsonData);
if (Grocy.EditMode === 'create')
{
Grocy.Api.Post('add-object/recipes_pos', jsonData,

View File

@@ -158,4 +158,5 @@ recipesTables.on('select', function(e, dt, type, indexes)
$("#selectedRecipeToggleFullscreenButton").on('click', function(e)
{
$("#selectedRecipeCard").toggleClass("fullscreen");
$("#selectedRecipeCard .card-header").toggleClass("fixed-top");
});

View File

@@ -81,6 +81,7 @@ $app->group('/api', function()
// System
$this->get('/system/get-db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime');
$this->post('/system/log-missing-localization', '\Grocy\Controllers\SystemApiController:LogMissingLocalization');
// Files
$this->post('/files/upload/{group}', '\Grocy\Controllers\FilesApiController:Upload');
@@ -91,6 +92,10 @@ $app->group('/api', function()
$this->post('/users/edit/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
$this->get('/users/delete/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
// User
$this->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
$this->post('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');
// Stock
$this->get('/stock/add-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:AddProduct');
$this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');

View File

@@ -34,7 +34,7 @@ class LocalizationService
}
}
private function LogMissingLocalization(string $culture, string $text)
public function LogMissingLocalization(string $culture, string $text)
{
$file = GROCY_DATAPATH . "/missing_translations_$culture.json";

View File

@@ -50,6 +50,55 @@ class UsersService extends BaseService
return $returnUsers;
}
public function GetUserSetting($userId, $settingKey)
{
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
if ($settingRow !== null)
{
return $settingRow->value;
}
else
{
return null;
}
}
public function GetUserSettings($userId)
{
$settings = array();
$settingRows = $this->Database->user_settings()->where('user_id = :1', $userId)->fetchAll();
foreach ($settingRows as $settingRow)
{
$settings[$settingRow->key] = $settingRow->value;
}
// Use the configured default values for all missing settings
global $GROCY_DEFAULT_USER_SETTINGS;
return array_merge($GROCY_DEFAULT_USER_SETTINGS, $settings);
}
public function SetUserSetting($userId, $settingKey, $settingValue)
{
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
if ($settingRow !== null)
{
$settingRow->update(array(
'value' => $settingValue,
'row_updated_timestamp' => date('Y-m-d H:i:s')
));
}
else
{
$settingRow = $this->Database->user_settings()->createRow(array(
'user_id' => $userId,
'key' => $settingKey,
'value' => $settingValue
));
$settingRow->save();
}
}
private function UserExists($userId)
{
$userRow = $this->Database->users()->where('id = :1', $userId)->fetch();

View File

@@ -1,4 +1,4 @@
{
"Version": "1.19.2",
"ReleaseDate": "2018-09-29"
"Version": "1.20.0",
"ReleaseDate": "2018-09-30"
}

View File

@@ -26,6 +26,7 @@
<link href="{{ $U('/node_modules/tempusdominus-bootstrap-4/build/css/tempusdominus-bootstrap-4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
<link href="{{ $U('/components_unmanaged/noto-sans-v6-latin/noto-sans-v6-latin.css?v=', true) }}{{ $version }}" rel="stylesheet">
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}" rel="stylesheet">
<link href="{{ $U('/css/grocy_night_mode.css?v=', true) }}{{ $version }}" rel="stylesheet">
@stack('pageStyles')
@if(file_exists(GROCY_DATAPATH . '/custom_css.html'))
@@ -35,15 +36,17 @@
<script>
var Grocy = { };
Grocy.Components = { };
Grocy.Mode = '{{ GROCY_MODE }}';
Grocy.BaseUrl = '{{ $U('/') }}';
Grocy.LocalizationStrings = {!! json_encode($localizationStrings) !!};
Grocy.ActiveNav = '@yield('activeNav', '')';
Grocy.Culture = '{{ GROCY_CULTURE }}';
Grocy.Currency = '{{ GROCY_CURRENCY }}';
Grocy.UserSettings = {!! json_encode($userSettings) !!};
</script>
</head>
<body class="fixed-nav">
<body class="fixed-nav @if(boolval($userSettings['night_mode_enabled']) || (boolval($userSettings['auto_night_mode_enabled']) && boolval($userSettings['currently_inside_night_mode_range']))) night-mode @endif">
<nav id="mainNav" class="navbar navbar-expand-lg navbar-light fixed-top">
<a class="navbar-brand py-0" href="{{ $U('/') }}"><img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}" height="30"></a>
@@ -195,6 +198,51 @@
</li>
@endif
@if(GROCY_AUTHENTICATED === true)
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-sliders-h"></i> <span class="d-inline d-lg-none">{{ $L('View settings') }}</span></a>
<div class="dropdown-menu dropdown-menu-right">
<div class="dropdown-item">
<div class="form-check">
<input class="form-check-input user-setting-control" type="checkbox" id="auto-reload-enabled" data-setting-key="auto_reload_on_db_change">
<label class="form-check-label" for="auto-reload-enabled">
{{ $L('Auto reload on external changes') }}
</label>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item">
<div class="form-check">
<input class="form-check-input user-setting-control" type="checkbox" id="night-mode-enabled" data-setting-key="night_mode_enabled">
<label class="form-check-label" for="night-mode-enabled">
{{ $L('Enable night mode') }}
</label>
</div>
</div>
<div class="dropdown-item">
<div class="form-check">
<input class="form-check-input user-setting-control" type="checkbox" id="auto-night-mode-enabled" data-setting-key="auto_night_mode_enabled">
<label class="form-check-label" for="auto-night-mode-enabled">
{{ $L('Auto enable in time range') }}
</label>
</div>
<div class="form-inline">
<input type="text" class="form-control my-1 user-setting-control" readonly id="auto-night-mode-time-range-from" placeholder="{{ $L('From') }} ({{ $L('in format') }} HH:mm)" data-setting-key="auto_night_mode_time_range_from">
<input type="text" class="form-control user-setting-control" readonly id="auto-night-mode-time-range-to" placeholder="{{ $L('To') }} ({{ $L('in format') }} HH:mm)" data-setting-key="auto_night_mode_time_range_to">
</div>
<div class="form-check mt-1">
<input class="form-check-input user-setting-control" type="checkbox" id="auto-night-mode-time-range-goes-over-midgnight" data-setting-key="auto_night_mode_time_range_goes_over_midnight">
<label class="form-check-label" for="auto-night-mode-time-range-goes-over-midgnight">
{{ $L('Time range goes over midnight') }}
</label>
</div>
<input class="form-check-input d-none user-setting-control" type="checkbox" id="currently-inside-night-mode-range" data-setting-key="currently_inside_night_mode_range">
</div>
</div>
</li>
@endif
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-wrench"></i> <span class="d-inline d-lg-none">{{ $L('Settings') }}</span></a>
@@ -270,6 +318,7 @@
<script src="{{ $U('/js/extensions.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy_dbchangedhandling.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy_nightmode.js?v=', true) }}{{ $version }}"></script>
@stack('pageScripts')
@stack('componentScripts')
<script src="{{ $U('/viewjs', true) }}/@yield('viewJsName').js?v={{ $version }}"></script>

View File

@@ -58,16 +58,16 @@
</thead>
<tbody>
@foreach($currentStock as $currentStockEntry)
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days'))) table-danger @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days"))) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
<td class="fit-content">
<a class="btn btn-success btn-sm product-consume-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Consume #3 #1 of #2', FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, 1) }}"
<a class="btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Consume #3 #1 of #2', FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, 1) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="1">
<i class="fas fa-utensils"></i> 1
</a>
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="btn btn-danger btn-sm product-consume-button" href="#" data-toggle="tooltip" data-placement="right" title="{{ $L('Consume all #1 which are currently in stock', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $L('Consume all #1 which are currently in stock', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
@@ -89,7 +89,7 @@
{{ FindObjectInArrayByPropertyValue($locations, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->location_id)->name }}
</td>
<td class="d-none">
@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days'))) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days"))) expiring @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) belowminstockamount @endif
@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days')) && $currentStockEntry->amount > 0) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) expiring @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) belowminstockamount @endif
</td>
</tr>
@endforeach

View File

@@ -4,13 +4,13 @@
"@danielfarrell/bootstrap-combobox@https://github.com/berrnd/bootstrap-combobox.git#master":
version "1.1.8"
resolved "https://github.com/berrnd/bootstrap-combobox.git#d5a43b011d4d2c86537df26e15d2caa51be6a15f"
resolved "https://github.com/berrnd/bootstrap-combobox.git#fcf0110146f4daab94888234c57d198b4ca5f129"
"@fortawesome/fontawesome-free@^5.1.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9"
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2", "tagmanager@https://github.com/max-favilli/tagmanager.git#3.0.2":
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2":
version "3.0.1"
resolved "https://github.com/max-favilli/tagmanager.git#df9eb9935c8585a392dfc00602f890caf233fa94"
dependencies:
@@ -203,8 +203,8 @@ startbootstrap-sb-admin@^4.0.0:
jquery.easing "^1.4.1"
swagger-ui-dist@^3.17.3:
version "3.19.0"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.19.0.tgz#95942ce1a556e7fe2705d7c92c6004a628d53207"
version "3.19.1"
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.19.1.tgz#ee176cfb070470bb81ba98a686e808035a882cb1"
tempusdominus-bootstrap-4@^5.0.1:
version "5.1.1"