mirror of
https://github.com/grocy/grocy.git
synced 2025-09-18 18:46:51 +00:00
Compare commits
238 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f4534a4bfb | ||
|
89553b7fa0 | ||
|
364f6b2051 | ||
|
fe83e2fa6f | ||
|
1f3dd58ddf | ||
|
da98efa833 | ||
|
3e6cf545d7 | ||
|
1080c3486c | ||
|
cd7b6b686d | ||
|
b84e6da0dd | ||
|
fc3a4c6899 | ||
|
12a2cb0bdf | ||
|
57a0864465 | ||
|
b3da837ede | ||
|
3de3e03ab3 | ||
|
78865a9d3c | ||
|
5b3230d63d | ||
|
04c93d937e | ||
|
5318e79f55 | ||
|
7bf4421d44 | ||
|
366152c049 | ||
|
70c00e81d9 | ||
|
f13abf483e | ||
|
0e723a0a9b | ||
|
4a35477c35 | ||
|
df7d360516 | ||
|
03eaa6c79f | ||
|
132999ce36 | ||
|
188407e3c7 | ||
|
8cf68ade30 | ||
|
d62657c698 | ||
|
3262e534dc | ||
|
6202e8bda7 | ||
|
9984e8f218 | ||
|
b0c91f6ad1 | ||
|
9e24586190 | ||
|
7ba6fc875b | ||
|
3b10906e78 | ||
|
ebd24bf30e | ||
|
ebd9b1b851 | ||
|
b242a5de52 | ||
|
81ec011095 | ||
|
2a371cc081 | ||
|
edb986ce24 | ||
|
f90faca62e | ||
|
6090ac621e | ||
|
ae58606d04 | ||
|
bb9caf9cc9 | ||
|
9dd57decdf | ||
|
f1fc0ee549 | ||
|
fcdeb33426 | ||
|
44cd26ae77 | ||
|
04f34ea6b0 | ||
|
e5fb609c8e | ||
|
c675b534ef | ||
|
6c74881f95 | ||
|
756ec319cc | ||
|
ba2d32be60 | ||
|
7cc09cec67 | ||
|
8b815fce93 | ||
|
f1c78659be | ||
|
5c79a80f7a | ||
|
f451e65278 | ||
|
176333df5b | ||
|
d4227d2e41 | ||
|
0bbd2d9880 | ||
|
b81316bd60 | ||
|
d11dcb38fe | ||
|
77d82f22dc | ||
|
be326a5211 | ||
|
83624eaf27 | ||
|
055619d275 | ||
|
cda3dde120 | ||
|
5a0b862d22 | ||
|
bb5fd8360b | ||
|
d7180bd7b2 | ||
|
8c9b0dedb2 | ||
|
9c2c2c1fa2 | ||
|
596dc9e36d | ||
|
b2019ba42d | ||
|
003d4a567a | ||
|
5112e0f551 | ||
|
8008fcdc65 | ||
|
8d41dcc650 | ||
|
037d024862 | ||
|
03ca5cd45b | ||
|
60d47bef84 | ||
|
98a7bcb044 | ||
|
7401971884 | ||
|
067a10e1b2 | ||
|
ddfe33fab6 | ||
|
2a0ec30bb0 | ||
|
8540fc44f3 | ||
|
66095738e3 | ||
|
e472711d23 | ||
|
8e054a4981 | ||
|
feb28211d8 | ||
|
06f25b7006 | ||
|
f85a67a1ff | ||
|
6fe0100927 | ||
|
bcb359e317 | ||
|
4075067a10 | ||
|
bd3c63218b | ||
|
27daf384da | ||
|
905fc0f357 | ||
|
9cd0e4ab2d | ||
|
6b38cd450f | ||
|
bb60f5f043 | ||
|
e777be4d3b | ||
|
8a71d55f0f | ||
|
b01b49d10c | ||
|
496594d898 | ||
|
1d5e82c341 | ||
|
a9b696f41c | ||
|
e50b1eb359 | ||
|
92e0245387 | ||
|
67d0d3c3d6 | ||
|
23bcbc23e9 | ||
|
085d9a0bc7 | ||
|
368df142cf | ||
|
d38edabb14 | ||
|
4426a10e2e | ||
|
931dc9d243 | ||
|
c5b8893008 | ||
|
c27f41aee4 | ||
|
ef043b38ce | ||
|
bb261f99c4 | ||
|
48ca0f2ac7 | ||
|
b7f0b06684 | ||
|
324487d395 | ||
|
9a8c61497b | ||
|
bc7afe4bdd | ||
|
bb5dcb2434 | ||
|
71b9d11ff5 | ||
|
3e73a44576 | ||
|
dedfe3a854 | ||
|
c4b0ef4d49 | ||
|
339d81318f | ||
|
282ee0885b | ||
|
5833364e51 | ||
|
525f1705d1 | ||
|
5a13cb5ffe | ||
|
e830805443 | ||
|
ca3f28b615 | ||
|
6081b8ee67 | ||
|
7eef4acd81 | ||
|
678579e933 | ||
|
4cc2d39063 | ||
|
14cc153422 | ||
|
f5b5c4c7e1 | ||
|
88b76a52a5 | ||
|
a4a25af460 | ||
|
41a72d11da | ||
|
c8236b101b | ||
|
ef1df0a446 | ||
|
5c4953b9b2 | ||
|
ccaf2411fe | ||
|
bce8bd6b35 | ||
|
66c07887cb | ||
|
be99880ce4 | ||
|
e026609972 | ||
|
3474f55866 | ||
|
f583810d5c | ||
|
419445f5ae | ||
|
c64eb27ca1 | ||
|
f4eb5196f7 | ||
|
9e493430d8 | ||
|
7690eedd70 | ||
|
aaa270a52f | ||
|
6f47a5415c | ||
|
42c1709633 | ||
|
4685ff4145 | ||
|
249b01d7a8 | ||
|
bcbdf58376 | ||
|
7f8540ff4e | ||
|
b52ab91606 | ||
|
7246ac55b6 | ||
|
848931da21 | ||
|
bf4092e746 | ||
|
7cee18c926 | ||
|
e9a4b43268 | ||
|
b1522742cc | ||
|
ecdaaab789 | ||
|
3379942086 | ||
|
12eaa8c074 | ||
|
c6310d636d | ||
|
9bedc6a138 | ||
|
70dbc6018f | ||
|
3afeb44b1d | ||
|
3131b8965e | ||
|
bbc2fc9e42 | ||
|
3b4141eb4d | ||
|
5f826be82c | ||
|
db9ee93d2b | ||
|
1eabd29105 | ||
|
dc05c56440 | ||
|
cb88ab2080 | ||
|
254e1a9bc1 | ||
|
0fc7c297bf | ||
|
82bfb6a3c3 | ||
|
277c622475 | ||
|
091a0f3efe | ||
|
823c76aa08 | ||
|
37dee2a50b | ||
|
ea0f5101ec | ||
|
be650d093d | ||
|
734814d96b | ||
|
d9246b9b42 | ||
|
70e7e630c3 | ||
|
71fc49252f | ||
|
aa0771877f | ||
|
e5a4d11c0b | ||
|
909949a9e1 | ||
|
f018696219 | ||
|
347a47d0d2 | ||
|
c3de4b86b0 | ||
|
594e77ca41 | ||
|
31ce7a13ea | ||
|
5d762001c8 | ||
|
bc3d339d9c | ||
|
33e5ed9ddc | ||
|
2d712b0ef7 | ||
|
13d81a4e4b | ||
|
8d917aee12 | ||
|
09b2cfc46a | ||
|
789e475207 | ||
|
eec5105e5b | ||
|
82f7b2109c | ||
|
840dd58c03 | ||
|
37d1377f99 | ||
|
882a3545e5 | ||
|
778191fd11 | ||
|
71701804ea | ||
|
306c404362 | ||
|
4fab4f87d3 | ||
|
54717a81b1 | ||
|
eca299454b | ||
|
c58083f84a |
8
.bowerrc
8
.bowerrc
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"directory": "public/bower_components",
|
|
||||||
"registry": {
|
|
||||||
"search": [
|
|
||||||
"https://registry.bower.io"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
.vscode
|
||||||
|
.gitignore
|
||||||
|
build.bat
|
||||||
|
Dockerfile
|
||||||
|
.DS_store
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
/public/bower_components
|
/public/node_modules
|
||||||
/vendor
|
/vendor
|
||||||
/.release
|
/.release
|
||||||
|
embedded.txt
|
4
.yarnrc
Normal file
4
.yarnrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
--modules-folder public/node_modules
|
||||||
|
--install.production true
|
||||||
|
--install.ignore-scripts true
|
||||||
|
--install.ignore-optional true
|
58
Dockerfile-grocy
Normal file
58
Dockerfile-grocy
Normal 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
32
Dockerfile-grocy-nginx
Normal 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;"]
|
54
README.md
54
README.md
@@ -2,25 +2,47 @@
|
|||||||
ERP beyond your fridge
|
ERP beyond your fridge
|
||||||
|
|
||||||
## Give it a try
|
## Give it a try
|
||||||
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
- Public demo of the latest stable version → [https://demo.grocy.info](https://demo.grocy.info)
|
||||||
|
- Public demo of the latest pre-release version (current master branch) → [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info)
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing.
|
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
|
||||||
|
|
||||||
## What it is about
|
|
||||||
For now my main focus is on stock management, ERP your fridge!
|
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP (currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `/public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go.
|
> **NEW**
|
||||||
|
>
|
||||||
|
> There is now grocy-desktop if you want to run grocy without a webserver just like a normal (windows) desktop application.
|
||||||
|
>
|
||||||
|
> See https://github.com/berrnd/grocy-desktop or directly download the [latest release](https://releases.grocy.info/latest-desktop) - the installation is nothing more than just clicking 2 times "next"...
|
||||||
|
|
||||||
Default login is user `admin` with password `admin` - see the `data/config.php` file. Alternatively clone this repository and install Composer and Bower dependencies manually.
|
Just unpack the [latest release](https://releases.grocy.info/latest) on your PHP (SQLite extension required, currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go, (to make writable `chown -R www-data:www-data data/`). Default login is user `admin` with password `admin`, please change the password immediately (see user menu).
|
||||||
|
|
||||||
|
Alternatively clone this repository and install Composer and Yarn dependencies manually.
|
||||||
|
|
||||||
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
||||||
|
|
||||||
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.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
|
## How to update
|
||||||
Just overwrite everything with the latest release while keeping the `/data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (it will show up as an error if something is missing there).
|
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`.
|
||||||
|
|
||||||
|
If you run grocy on Linux, there is also `update.sh` (remember to make the script executable, `chmod +x update.sh` and ensure that you have `unzip` installed) which does exactly this and additionally creates a backup (`.tgz` archive) of the current installation in `data/backups` (backups older than 60 days will be deleted during the update).
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
grocy is fully localizable - the default language is English (integrated into code), a German localization is always maintained by me. There is one file per language in the `localization` directory, if you want to create a translation, it's best to copy `localization/de.php` to a new one (e. g. `localization/it.php`) and translating all strings there. (Language can be changed in `data/config.php`, e. g. `Setting('CULTURE', 'it');`)
|
||||||
|
|
||||||
|
### Maintaining your own localization
|
||||||
|
As the German translation will always be the most complete one, for maintaining your localization it would be easiest when you compare your localization with the German one with a diff tool of your choice.
|
||||||
|
|
||||||
## Things worth to know
|
## Things worth to know
|
||||||
|
|
||||||
@@ -33,10 +55,12 @@ Some fields also allow to select a value by scanning a barcode. It works best wh
|
|||||||
### Input shorthands for date fields
|
### Input shorthands for date fields
|
||||||
For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization.
|
For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization.
|
||||||
The following shorthands are available:
|
The following shorthands are available:
|
||||||
- `MMDD` gets expanded to the given day on the current year in proper notation
|
- `MMDD` gets expanded to the given day on the current year, if > today, or to the given day next year, if < today, in proper notation
|
||||||
- Example: `0517` will be converted to `2018-05-17`
|
- Example: `0517` will be converted to `2018-05-17`
|
||||||
- `YYYYMMDD` gets expanded to the proper ISO-8601 notation
|
- `YYYYMMDD` gets expanded to the proper ISO-8601 notation
|
||||||
- Example: `20190417` will be converted to `2019-04-17`
|
- Example: `20190417` will be converted to `2019-04-17`
|
||||||
|
- `YYYYMMe` or `YYYYMM+` gets expanded to the end of the given month in the given year in proper notation
|
||||||
|
- Example: `201807e` will be converted to `2018-07-31`
|
||||||
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
|
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
|
||||||
- Down/up arrow keys will increase/decrease the date by one day
|
- Down/up arrow keys will increase/decrease the date by one day
|
||||||
- Right/left arrow keys will increase/decrease the date by 1 week
|
- Right/left arrow keys will increase/decrease the date by 1 week
|
||||||
@@ -53,11 +77,17 @@ There is no plugin included for any service, see the reference implementation in
|
|||||||
### Database migrations
|
### Database migrations
|
||||||
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
|
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
|
||||||
|
|
||||||
|
### Adding your own CSS or JS without to have to modify the application itself
|
||||||
|
- When the file `data/custom_js.html` exists, the contents of the file will be added just before `</body>` (end of body) on every page
|
||||||
|
- When the file `data/custom_css.html` exists, the contents of the file will be added just before `</head>` (end of head) on every page
|
||||||
|
|
||||||
### Demo mode
|
### Demo mode
|
||||||
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
|
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
|
||||||
|
|
||||||
### Other things
|
### Embedded mode
|
||||||
When the file `data/add_before_end_body.html` exists, the contents of the file be added just before `</body>` on every page, useful for your own JS/CSS without to have to modify the application itself.
|
When the file `embedded.txt` exists, it must contain a valid and writable path which will be used as the data directory instead of `data` and authentication will be disabled (used in [grocy-desktop](https://github.com/berrnd/grocy-desktop)).
|
||||||
|
|
||||||
|
In embedded mode, settings can be overridden by text files in `data/settingoverrides`, the file name must be `<SettingName>.txt` (e. g. `BASE_URL.txt`) and the content must be the setting value (normally one single line).
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
#### Dashboard
|
#### Dashboard
|
||||||
|
43
app.php
43
app.php
@@ -6,8 +6,39 @@ use \Psr\Http\Message\ResponseInterface as Response;
|
|||||||
use \Grocy\Helpers\UrlManager;
|
use \Grocy\Helpers\UrlManager;
|
||||||
use \Grocy\Controllers\LoginController;
|
use \Grocy\Controllers\LoginController;
|
||||||
|
|
||||||
|
// Definitions for embedded mode
|
||||||
|
if (file_exists(__DIR__ . '/embedded.txt'))
|
||||||
|
{
|
||||||
|
define('GROCY_IS_EMBEDDED_INSTALL', true);
|
||||||
|
define('GROCY_DATAPATH', file_get_contents(__DIR__ . '/embedded.txt'));
|
||||||
|
define('GROCY_USER_ID', 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
define('GROCY_IS_EMBEDDED_INSTALL', false);
|
||||||
|
define('GROCY_DATAPATH', __DIR__ . '/data');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Definitions for demo mode
|
||||||
|
if (file_exists(GROCY_DATAPATH . '/demo.txt'))
|
||||||
|
{
|
||||||
|
define('GROCY_IS_DEMO_INSTALL', true);
|
||||||
|
if (!defined('GROCY_USER_ID'))
|
||||||
|
{
|
||||||
|
define('GROCY_USER_ID', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
define('GROCY_IS_DEMO_INSTALL', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load composer dependencies
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
require_once __DIR__ . '/data/config.php';
|
|
||||||
|
// Load config files
|
||||||
|
require_once GROCY_DATAPATH . '/config.php';
|
||||||
|
require_once __DIR__ . '/config-dist.php'; //For not in own config defined values we use the default ones
|
||||||
|
|
||||||
// Setup base application
|
// Setup base application
|
||||||
$appContainer = new \Slim\Container([
|
$appContainer = new \Slim\Container([
|
||||||
@@ -17,7 +48,7 @@ $appContainer = new \Slim\Container([
|
|||||||
],
|
],
|
||||||
'view' => function($container)
|
'view' => function($container)
|
||||||
{
|
{
|
||||||
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
return new \Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache');
|
||||||
},
|
},
|
||||||
'LoginControllerInstance' => function($container)
|
'LoginControllerInstance' => function($container)
|
||||||
{
|
{
|
||||||
@@ -25,7 +56,7 @@ $appContainer = new \Slim\Container([
|
|||||||
},
|
},
|
||||||
'UrlManager' => function($container)
|
'UrlManager' => function($container)
|
||||||
{
|
{
|
||||||
return new UrlManager(BASE_URL);
|
return new UrlManager(GROCY_BASE_URL);
|
||||||
},
|
},
|
||||||
'ApiKeyHeaderName' => function($container)
|
'ApiKeyHeaderName' => function($container)
|
||||||
{
|
{
|
||||||
@@ -34,11 +65,7 @@ $appContainer = new \Slim\Container([
|
|||||||
]);
|
]);
|
||||||
$app = new \Slim\App($appContainer);
|
$app = new \Slim\App($appContainer);
|
||||||
|
|
||||||
if (PHP_SAPI === 'cli')
|
// Load routes from separate file
|
||||||
{
|
|
||||||
$app->add(\pavlakis\cli\CliRequest::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/routes.php';
|
require_once __DIR__ . '/routes.php';
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
|
25
bower.json
25
bower.json
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "grocy",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bootstrap": "^3.3.7",
|
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"bootbox": "^4.4.0",
|
|
||||||
"jquery.serializeJSON": "^2.8.1",
|
|
||||||
"bootstrap-validator": "^0.11.9",
|
|
||||||
"bootstrap-datepicker": "^1.7.1",
|
|
||||||
"moment": "^2.18.1",
|
|
||||||
"bootstrap-combobox": "^1.1.8",
|
|
||||||
"datatables.net": "^1.10.15",
|
|
||||||
"datatables.net-bs": "^2.1.1",
|
|
||||||
"datatables.net-responsive": "^2.1.1",
|
|
||||||
"datatables.net-responsive-bs": "^2.1.1",
|
|
||||||
"jquery-timeago": "^1.6.1",
|
|
||||||
"toastr": "^2.1.3",
|
|
||||||
"tagmanager": "^3.0.2",
|
|
||||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
|
||||||
"swagger-ui": "^3.13.4",
|
|
||||||
"jquery-ui": "^1.12.1",
|
|
||||||
"bootstrap-side-navbar": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,10 +4,10 @@ if %projectPath:~-1%==\ set projectPath=%projectPath:~0,-1%
|
|||||||
set releasePath=%projectPath%\.release
|
set releasePath=%projectPath%\.release
|
||||||
mkdir "%releasePath%"
|
mkdir "%releasePath%"
|
||||||
|
|
||||||
for /f "tokens=*" %%a in ('type version.txt') do set version=%%a
|
for /f "tokens=*" %%a in ('build_tools\jq.exe .Version version.json --raw-output') do set version=%%a
|
||||||
|
|
||||||
del "%releasePath%\grocy_%version%.zip"
|
del "%releasePath%\grocy_%version%.zip"
|
||||||
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!bower.json -xr!publication_assets
|
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!publication_assets
|
||||||
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
|
"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" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
|
||||||
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions data\viewcache\*
|
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\storage data\viewcache\*
|
||||||
|
BIN
build_tools/jq.exe
Normal file
BIN
build_tools/jq.exe
Normal file
Binary file not shown.
@@ -3,7 +3,6 @@
|
|||||||
"php": ">=7.2",
|
"php": ">=7.2",
|
||||||
"slim/slim": "^3.8",
|
"slim/slim": "^3.8",
|
||||||
"morris/lessql": "^0.3.4",
|
"morris/lessql": "^0.3.4",
|
||||||
"pavlakis/slim-cli": "^1.0",
|
|
||||||
"rubellum/slim-blade-view": "^0.1.1",
|
"rubellum/slim-blade-view": "^0.1.1",
|
||||||
"tuupola/cors-middleware": "^0.7.0"
|
"tuupola/cors-middleware": "^0.7.0"
|
||||||
},
|
},
|
||||||
|
273
composer.lock
generated
273
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "42031c0b205b7ce7efb4b6eb95a0096a",
|
"content-hash": "c1bc4c17739e9d0ee8b33628f6d4b9a4",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "container-interop/container-interop",
|
"name": "container-interop/container-interop",
|
||||||
@@ -154,31 +154,33 @@
|
|||||||
"request",
|
"request",
|
||||||
"response"
|
"response"
|
||||||
],
|
],
|
||||||
|
"abandoned": "psr/http-factory",
|
||||||
"time": "2017-03-24T14:48:51+00:00"
|
"time": "2017-03-24T14:48:51+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/container",
|
"name": "illuminate/container",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/container.git",
|
"url": "https://github.com/illuminate/container.git",
|
||||||
"reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a"
|
"reference": "4c90c3d3ba88e52da152e885d24c9f891a2ec545"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/container/zipball/4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a",
|
"url": "https://api.github.com/repos/illuminate/container/zipball/4c90c3d3ba88e52da152e885d24c9f891a2ec545",
|
||||||
"reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a",
|
"reference": "4c90c3d3ba88e52da152e885d24c9f891a2ec545",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/container": "~1.0"
|
"psr/container": "^1.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -198,31 +200,31 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Container package.",
|
"description": "The Illuminate Container package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-01-21T02:13:38+00:00"
|
"time": "2018-10-18T03:39:45+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/contracts",
|
"name": "illuminate/contracts",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/contracts.git",
|
"url": "https://github.com/illuminate/contracts.git",
|
||||||
"reference": "322ec80498b3bf85bc4025d028e130a9b50242b9"
|
"reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/322ec80498b3bf85bc4025d028e130a9b50242b9",
|
"url": "https://api.github.com/repos/illuminate/contracts/zipball/64df81d3382d876f1c1d3d5481d89c93b61b8279",
|
||||||
"reference": "322ec80498b3bf85bc4025d028e130a9b50242b9",
|
"reference": "64df81d3382d876f1c1d3d5481d89c93b61b8279",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/container": "~1.0",
|
"psr/container": "^1.0",
|
||||||
"psr/simple-cache": "~1.0"
|
"psr/simple-cache": "^1.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -242,32 +244,32 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Contracts package.",
|
"description": "The Illuminate Contracts package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-04-07T17:05:26+00:00"
|
"time": "2018-10-08T13:34:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/events",
|
"name": "illuminate/events",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/events.git",
|
"url": "https://github.com/illuminate/events.git",
|
||||||
"reference": "b6e73ed40478cef2ef98d5ddb27f333291606cea"
|
"reference": "a8e5e3d601ad7f3571428176a578ddf03ce649d8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/events/zipball/b6e73ed40478cef2ef98d5ddb27f333291606cea",
|
"url": "https://api.github.com/repos/illuminate/events/zipball/a8e5e3d601ad7f3571428176a578ddf03ce649d8",
|
||||||
"reference": "b6e73ed40478cef2ef98d5ddb27f333291606cea",
|
"reference": "a8e5e3d601ad7f3571428176a578ddf03ce649d8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/container": "5.6.*",
|
"illuminate/container": "5.7.*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3"
|
"php": "^7.1.3"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -287,39 +289,39 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Events package.",
|
"description": "The Illuminate Events package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-02-26T19:00:55+00:00"
|
"time": "2018-10-06T18:48:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/filesystem",
|
"name": "illuminate/filesystem",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/filesystem.git",
|
"url": "https://github.com/illuminate/filesystem.git",
|
||||||
"reference": "c9ab9376076cedd88a374d7281d62b619634d578"
|
"reference": "cbb5650be36d7370f7ae5f039d2143952fa58f51"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/c9ab9376076cedd88a374d7281d62b619634d578",
|
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/cbb5650be36d7370f7ae5f039d2143952fa58f51",
|
||||||
"reference": "c9ab9376076cedd88a374d7281d62b619634d578",
|
"reference": "cbb5650be36d7370f7ae5f039d2143952fa58f51",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/finder": "~4.0"
|
"symfony/finder": "^4.1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).",
|
"league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).",
|
||||||
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
|
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).",
|
||||||
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (~1.0).",
|
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).",
|
||||||
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
|
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).",
|
||||||
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (~1.0)."
|
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0)."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -339,41 +341,43 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Filesystem package.",
|
"description": "The Illuminate Filesystem package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-04-06T13:15:37+00:00"
|
"time": "2018-10-24T12:49:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/support",
|
"name": "illuminate/support",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/support.git",
|
"url": "https://github.com/illuminate/support.git",
|
||||||
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085"
|
"reference": "45bfc0cd080c51946f61c04e324c2b4c6df58a9d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/support/zipball/cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
"url": "https://api.github.com/repos/illuminate/support/zipball/45bfc0cd080c51946f61c04e324c2b4c6df58a9d",
|
||||||
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
"reference": "45bfc0cd080c51946f61c04e324c2b4c6df58a9d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"doctrine/inflector": "~1.1",
|
"doctrine/inflector": "^1.1",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"nesbot/carbon": "^1.24.1",
|
"nesbot/carbon": "^1.26.3",
|
||||||
"php": "^7.1.3"
|
"php": "^7.1.3"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"tightenco/collect": "<5.5.33"
|
"tightenco/collect": "<5.5.33"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"illuminate/filesystem": "Required to use the composer class (5.6.*).",
|
"illuminate/filesystem": "Required to use the composer class (5.7.*).",
|
||||||
"symfony/process": "Required to use the composer class (~4.0).",
|
"moontoast/math": "Required to use ordered UUIDs (^1.1).",
|
||||||
"symfony/var-dumper": "Required to use the dd function (~4.0)."
|
"ramsey/uuid": "Required to use Str::uuid() (^3.7).",
|
||||||
|
"symfony/process": "Required to use the composer class (^4.1).",
|
||||||
|
"symfony/var-dumper": "Required to use the dd function (^4.1)."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -396,35 +400,35 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Support package.",
|
"description": "The Illuminate Support package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-04-17T12:26:47+00:00"
|
"time": "2018-10-22T17:36:06+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/view",
|
"name": "illuminate/view",
|
||||||
"version": "v5.6.17",
|
"version": "v5.7.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/view.git",
|
"url": "https://github.com/illuminate/view.git",
|
||||||
"reference": "54eaf45ee7946d8f8cde13d5e89c5ea2e997040d"
|
"reference": "97dbb6910aa5df5a7414877da89b7520f4260a58"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/view/zipball/54eaf45ee7946d8f8cde13d5e89c5ea2e997040d",
|
"url": "https://api.github.com/repos/illuminate/view/zipball/97dbb6910aa5df5a7414877da89b7520f4260a58",
|
||||||
"reference": "54eaf45ee7946d8f8cde13d5e89c5ea2e997040d",
|
"reference": "97dbb6910aa5df5a7414877da89b7520f4260a58",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/container": "5.6.*",
|
"illuminate/container": "5.7.*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/events": "5.6.*",
|
"illuminate/events": "5.7.*",
|
||||||
"illuminate/filesystem": "5.6.*",
|
"illuminate/filesystem": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/debug": "~4.0"
|
"symfony/debug": "^4.1"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -444,7 +448,7 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate View package.",
|
"description": "The Illuminate View package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-04-03T12:56:35+00:00"
|
"time": "2018-10-11T15:32:19+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "morris/lessql",
|
"name": "morris/lessql",
|
||||||
@@ -496,16 +500,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "neomerx/cors-psr7",
|
"name": "neomerx/cors-psr7",
|
||||||
"version": "v1.0.12",
|
"version": "v1.0.13",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/neomerx/cors-psr7.git",
|
"url": "https://github.com/neomerx/cors-psr7.git",
|
||||||
"reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35"
|
"reference": "2556e2013f16a55532c95928455257d5b6bbc6e2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/24944f39483d1a89f66ae9d58cca9f82b8815b35",
|
"url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/2556e2013f16a55532c95928455257d5b6bbc6e2",
|
||||||
"reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35",
|
"reference": "2556e2013f16a55532c95928455257d5b6bbc6e2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -514,7 +518,7 @@
|
|||||||
"psr/log": "^1.0"
|
"psr/log": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mockery/mockery": "^0.9.9",
|
"mockery/mockery": "^1.0",
|
||||||
"phpunit/phpunit": "^5.7",
|
"phpunit/phpunit": "^5.7",
|
||||||
"scrutinizer/ocular": "^1.1",
|
"scrutinizer/ocular": "^1.1",
|
||||||
"squizlabs/php_codesniffer": "^3.0"
|
"squizlabs/php_codesniffer": "^3.0"
|
||||||
@@ -547,20 +551,20 @@
|
|||||||
"w3.org",
|
"w3.org",
|
||||||
"www.w3.org"
|
"www.w3.org"
|
||||||
],
|
],
|
||||||
"time": "2017-09-03T22:31:57+00:00"
|
"time": "2018-05-23T16:10:11+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "1.26.4",
|
"version": "1.34.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||||
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b"
|
"reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33",
|
||||||
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
"reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -572,6 +576,13 @@
|
|||||||
"phpunit/phpunit": "^4.8.35 || ^5.7"
|
"phpunit/phpunit": "^4.8.35 || ^5.7"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Carbon\\Laravel\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"": "src/"
|
"": "src/"
|
||||||
@@ -595,7 +606,7 @@
|
|||||||
"datetime",
|
"datetime",
|
||||||
"time"
|
"time"
|
||||||
],
|
],
|
||||||
"time": "2018-04-17T15:35:42+00:00"
|
"time": "2018-09-20T19:36:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/fast-route",
|
"name": "nikic/fast-route",
|
||||||
@@ -643,55 +654,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2018-02-13T20:26:39+00:00"
|
"time": "2018-02-13T20:26:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "pavlakis/slim-cli",
|
|
||||||
"version": "1.0.4",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/pavlakis/slim-cli.git",
|
|
||||||
"reference": "603933a54e391b3c70c573206cce543b75d8b1db"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/pavlakis/slim-cli/zipball/603933a54e391b3c70c573206cce543b75d8b1db",
|
|
||||||
"reference": "603933a54e391b3c70c573206cce543b75d8b1db",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^5.5|^5.6|^7.0|^7.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^4.0",
|
|
||||||
"slim/slim": "^3.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"pavlakis\\cli\\tests\\": "tests/phpunit",
|
|
||||||
"pavlakis\\cli\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"BSD-3-Clause"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Antonis Pavlakis",
|
|
||||||
"email": "adoni@pavlakis.info",
|
|
||||||
"homepage": "http://pavlakis.info"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Making a mock GET request through the CLI and enabling the same application entry point on CLI scripts.",
|
|
||||||
"homepage": "http://github.com/pavlakis/slim-cli",
|
|
||||||
"keywords": [
|
|
||||||
"cli",
|
|
||||||
"framework",
|
|
||||||
"middleware",
|
|
||||||
"slim"
|
|
||||||
],
|
|
||||||
"time": "2017-01-30T22:50:06+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "philo/laravel-blade",
|
"name": "philo/laravel-blade",
|
||||||
"version": "v3.1",
|
"version": "v3.1",
|
||||||
@@ -1135,16 +1097,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slim/slim",
|
"name": "slim/slim",
|
||||||
"version": "3.10.0",
|
"version": "3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/slimphp/Slim.git",
|
"url": "https://github.com/slimphp/Slim.git",
|
||||||
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748"
|
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
|
||||||
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1202,20 +1164,20 @@
|
|||||||
"micro",
|
"micro",
|
||||||
"router"
|
"router"
|
||||||
],
|
],
|
||||||
"time": "2018-04-19T19:29:08+00:00"
|
"time": "2018-09-16T10:54:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/debug",
|
"name": "symfony/debug",
|
||||||
"version": "v4.0.8",
|
"version": "v4.1.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/debug.git",
|
"url": "https://github.com/symfony/debug.git",
|
||||||
"reference": "5961d02d48828671f5d8a7805e06579d692f6ede"
|
"reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede",
|
"url": "https://api.github.com/repos/symfony/debug/zipball/e3f76ce6198f81994e019bb2b4e533e9de1b9b90",
|
||||||
"reference": "5961d02d48828671f5d8a7805e06579d692f6ede",
|
"reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1231,7 +1193,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "4.0-dev"
|
"dev-master": "4.1-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -1258,20 +1220,20 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Debug Component",
|
"description": "Symfony Debug Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-04-03T05:24:00+00:00"
|
"time": "2018-10-02T16:36:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v4.0.8",
|
"version": "v4.1.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49"
|
"reference": "1f17195b44543017a9c9b2d437c670627e96ad06"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06",
|
||||||
"reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49",
|
"reference": "1f17195b44543017a9c9b2d437c670627e96ad06",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1280,7 +1242,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "4.0-dev"
|
"dev-master": "4.1-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -1307,20 +1269,20 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Finder Component",
|
"description": "Symfony Finder Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-04-04T05:10:37+00:00"
|
"time": "2018-10-03T08:47:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.7.0",
|
"version": "v1.9.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
|
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
|
||||||
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
|
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1332,7 +1294,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.7-dev"
|
"dev-master": "1.9-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -1366,20 +1328,20 @@
|
|||||||
"portable",
|
"portable",
|
||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"time": "2018-01-30T19:27:44+00:00"
|
"time": "2018-08-06T14:22:27+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/translation",
|
"name": "symfony/translation",
|
||||||
"version": "v4.0.8",
|
"version": "v4.1.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/translation.git",
|
"url": "https://github.com/symfony/translation.git",
|
||||||
"reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938"
|
"reference": "9f0b61e339160a466ebcde167a6c5521c810e304"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e20a9b7f9f62cb33a11638b345c248e7d510c938",
|
"url": "https://api.github.com/repos/symfony/translation/zipball/9f0b61e339160a466ebcde167a6c5521c810e304",
|
||||||
"reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938",
|
"reference": "9f0b61e339160a466ebcde167a6c5521c810e304",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1394,20 +1356,21 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
"symfony/config": "~3.4|~4.0",
|
"symfony/config": "~3.4|~4.0",
|
||||||
|
"symfony/console": "~3.4|~4.0",
|
||||||
"symfony/dependency-injection": "~3.4|~4.0",
|
"symfony/dependency-injection": "~3.4|~4.0",
|
||||||
"symfony/finder": "~2.8|~3.0|~4.0",
|
"symfony/finder": "~2.8|~3.0|~4.0",
|
||||||
"symfony/intl": "~3.4|~4.0",
|
"symfony/intl": "~3.4|~4.0",
|
||||||
"symfony/yaml": "~3.4|~4.0"
|
"symfony/yaml": "~3.4|~4.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"psr/log": "To use logging capability in translator",
|
"psr/log-implementation": "To use logging capability in translator",
|
||||||
"symfony/config": "",
|
"symfony/config": "",
|
||||||
"symfony/yaml": ""
|
"symfony/yaml": ""
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "4.0-dev"
|
"dev-master": "4.1-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -1434,7 +1397,7 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Translation Component",
|
"description": "Symfony Translation Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-02-22T10:50:29+00:00"
|
"time": "2018-10-02T16:36:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tuupola/callable-handler",
|
"name": "tuupola/callable-handler",
|
||||||
@@ -1604,7 +1567,7 @@
|
|||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": ">=7.0"
|
"php": ">=7.2"
|
||||||
},
|
},
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +1,47 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
# Login credentials
|
# Either "production", "dev" or "prerelease"
|
||||||
define('HTTP_USER', 'admin');
|
Setting('MODE', 'production');
|
||||||
define('HTTP_PASSWORD', 'admin');
|
|
||||||
|
|
||||||
# Either "production" or "dev"
|
|
||||||
define('MODE', 'production');
|
|
||||||
|
|
||||||
# Either "en" or "de" or the filename (without extension) of
|
# Either "en" or "de" or the filename (without extension) of
|
||||||
# one of the other available localization files in the "/localization" directory
|
# one of the other available localization files in the "/localization" directory
|
||||||
define('CULTURE', 'en');
|
Setting('CULTURE', 'en');
|
||||||
|
|
||||||
|
# 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', '$');
|
||||||
|
|
||||||
# The base url of your installation,
|
# The base url of your installation,
|
||||||
# should be just "/" when running directly under the root of a (sub)domain
|
# should be just "/" when running directly under the root of a (sub)domain
|
||||||
# or for example "https:/example.com/grocy" when using a subdirectory
|
# or for example "https:/example.com/grocy" when using a subdirectory
|
||||||
define('BASE_URL', '/');
|
Setting('BASE_URL', '/');
|
||||||
|
|
||||||
# The plugin to use for external barcode lookups,
|
# The plugin to use for external barcode lookups,
|
||||||
# must be the filename without .php extension and must be located in /data/plugins,
|
# must be the filename without .php extension and must be located in /data/plugins,
|
||||||
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
||||||
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
|
|
||||||
# If, however, your webserver does not support URL rewriting,
|
# If, however, your webserver does not support URL rewriting,
|
||||||
# set this to true
|
# set this to true
|
||||||
define('DISABLE_URL_REWRITING', false);
|
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)
|
||||||
|
DefaultUserSetting('product_presets_location_id', -1); // Default location id for new products (-1 means no location is preset)
|
||||||
|
DefaultUserSetting('product_presets_product_group_id', -1); // Default product group id for new products (-1 means no product group is preset)
|
||||||
|
DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for new products (-1 means no quantity unit is preset)
|
||||||
|
|
||||||
|
# If the page should be automatically reloaded when there was
|
||||||
|
# an external change
|
||||||
|
DefaultUserSetting('auto_reload_on_db_change', true);
|
||||||
|
@@ -5,6 +5,7 @@ namespace Grocy\Controllers;
|
|||||||
use \Grocy\Services\DatabaseService;
|
use \Grocy\Services\DatabaseService;
|
||||||
use \Grocy\Services\ApplicationService;
|
use \Grocy\Services\ApplicationService;
|
||||||
use \Grocy\Services\LocalizationService;
|
use \Grocy\Services\LocalizationService;
|
||||||
|
use \Grocy\Services\UsersService;
|
||||||
|
|
||||||
class BaseController
|
class BaseController
|
||||||
{
|
{
|
||||||
@@ -12,12 +13,25 @@ class BaseController
|
|||||||
$databaseService = new DatabaseService();
|
$databaseService = new DatabaseService();
|
||||||
$this->Database = $databaseService->GetDbConnection();
|
$this->Database = $databaseService->GetDbConnection();
|
||||||
|
|
||||||
|
$localizationService = new LocalizationService(GROCY_CULTURE);
|
||||||
|
$this->LocalizationService = $localizationService;
|
||||||
|
|
||||||
|
if (GROCY_MODE === 'prerelease')
|
||||||
|
{
|
||||||
|
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
||||||
|
$commitDate = trim(exec('git log --date=iso --pretty="%cd" -n1 HEAD'));
|
||||||
|
|
||||||
|
$container->view->set('version', "pre-release-$commitHash");
|
||||||
|
$container->view->set('releaseDate', \substr($commitDate, 0, 19));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$applicationService = new ApplicationService();
|
$applicationService = new ApplicationService();
|
||||||
$versionInfo = $applicationService->GetInstalledVersion();
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
$container->view->set('version', $versionInfo->Version);
|
$container->view->set('version', $versionInfo->Version);
|
||||||
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
||||||
|
}
|
||||||
|
|
||||||
$localizationService = new LocalizationService(CULTURE);
|
|
||||||
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
||||||
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
|
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
|
||||||
{
|
{
|
||||||
@@ -28,9 +42,21 @@ class BaseController
|
|||||||
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
$usersService = new UsersService();
|
||||||
|
if (defined('GROCY_USER_ID')) {
|
||||||
|
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
// Happens when database is not initialised or migrated...
|
||||||
|
}
|
||||||
|
|
||||||
$this->AppContainer = $container;
|
$this->AppContainer = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $AppContainer;
|
protected $AppContainer;
|
||||||
protected $Database;
|
protected $Database;
|
||||||
|
protected $LocalizationService;
|
||||||
}
|
}
|
||||||
|
@@ -17,15 +17,15 @@ class BatteriesApiController extends BaseApiController
|
|||||||
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function TrackChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$trackedTime = date('Y-m-d H:i:s');
|
$trackedTime = date('Y-m-d H:i:s');
|
||||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
|
||||||
{
|
{
|
||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
$chargeCycleId = $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->ApiResponse(array('charge_cycle_id' => $chargeCycleId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
{
|
{
|
||||||
@@ -44,4 +44,22 @@ class BatteriesApiController extends BaseApiController
|
|||||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->BatteriesService->GetCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function UndoChargeCycle(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->ApiResponse($this->BatteriesService->UndoChargeCycle($args['chargeCycleId']));
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,22 +16,10 @@ class BatteriesController extends BaseController
|
|||||||
|
|
||||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$nextChargeTimes = array();
|
|
||||||
foreach($this->Database->batteries() as $battery)
|
|
||||||
{
|
|
||||||
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nextXDays = 5;
|
|
||||||
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
|
||||||
$countOverdue = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
|
||||||
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
||||||
'batteries' => $this->Database->batteries()->orderBy('name'),
|
'batteries' => $this->Database->batteries()->orderBy('name'),
|
||||||
'current' => $this->BatteriesService->GetCurrent(),
|
'current' => $this->BatteriesService->GetCurrent(),
|
||||||
'nextChargeTimes' => $nextChargeTimes,
|
'nextXDays' => 5
|
||||||
'nextXDays' => $nextXDays,
|
|
||||||
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
|
||||||
'countOverdue' => $countOverdue
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,4 +53,12 @@ class BatteriesController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'batteriesjournal', [
|
||||||
|
'chargeCycles' => $this->Database->battery_charge_cycles()->orderBy('tracked_time', 'DESC'),
|
||||||
|
'batteries' => $this->Database->batteries()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
71
controllers/ChoresApiController.php
Normal file
71
controllers/ChoresApiController.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ChoresService;
|
||||||
|
|
||||||
|
class ChoresApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->ChoresService = new ChoresService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ChoresService;
|
||||||
|
|
||||||
|
public function TrackChoreExecution(\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']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
|
||||||
|
{
|
||||||
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$doneBy = GROCY_USER_ID;
|
||||||
|
if (isset($request->getQueryParams()['done_by']) && !empty($request->getQueryParams()['done_by']))
|
||||||
|
{
|
||||||
|
$doneBy = $request->getQueryParams()['done_by'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$choreExecutionId = $this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||||
|
return $this->ApiResponse(array('chore_execution_id' => $choreExecutionId));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChoreDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->ChoresService->GetCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function UndoChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->ApiResponse($this->ChoresService->UndoChoreExecution($args['executionId']));
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
controllers/ChoresController.php
Normal file
68
controllers/ChoresController.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ChoresService;
|
||||||
|
|
||||||
|
class ChoresController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->ChoresService = new ChoresService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ChoresService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choresoverview', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'currentChores' => $this->ChoresService->GetCurrent(),
|
||||||
|
'nextXDays' => 5
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choretracking', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'chores', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choresjournal', [
|
||||||
|
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['choreId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choreform', [
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choreform', [
|
||||||
|
'chore' => $this->Database->chores($args['choreId']),
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,19 +0,0 @@
|
|||||||
<?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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
31
controllers/EquipmentController.php
Normal file
31
controllers/EquipmentController.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
class EquipmentController extends BaseController
|
||||||
|
{
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'equipment', [
|
||||||
|
'equipment' => $this->Database->equipment()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function EditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['equipmentId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'equipmentform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'equipmentform', [
|
||||||
|
'equipment' => $this->Database->equipment($args['equipmentId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
controllers/FilesApiController.php
Normal file
99
controllers/FilesApiController.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\FilesService;
|
||||||
|
|
||||||
|
class FilesApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->FilesService = new FilesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $FilesService;
|
||||||
|
|
||||||
|
public function UploadFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
|
||||||
|
{
|
||||||
|
$fileName = $request->getQueryParams()['file_name'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \Exception('file_name query parameter missing or contains an invalid filename');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->getBody()->getContents();
|
||||||
|
file_put_contents($this->FilesService->GetFilePath($args['group'], $fileName), $data);
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ServeFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
|
||||||
|
{
|
||||||
|
$fileName = $request->getQueryParams()['file_name'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \Exception('file_name query parameter missing or contains an invalid filename');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);
|
||||||
|
|
||||||
|
if (file_exists($filePath))
|
||||||
|
{
|
||||||
|
$response->write(file_get_contents($filePath));
|
||||||
|
$response = $response->withHeader('Content-Type', mime_content_type($filePath));
|
||||||
|
return $response->withHeader('Content-Disposition', 'inline; filename="' . $fileName . '"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 404, 'File not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DeleteFile(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
|
||||||
|
{
|
||||||
|
$fileName = $request->getQueryParams()['file_name'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \Exception('file_name query parameter missing or contains an invalid filename');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = $this->FilesService->GetFilePath($args['group'], $fileName);
|
||||||
|
if (file_exists($filePath))
|
||||||
|
{
|
||||||
|
unlink($filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,7 +6,7 @@ class GenericEntityApiController extends BaseApiController
|
|||||||
{
|
{
|
||||||
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
if ($this->IsValidEntity($args['entity']))
|
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->Database->{$args['entity']}());
|
return $this->ApiResponse($this->Database->{$args['entity']}());
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ class GenericEntityApiController extends BaseApiController
|
|||||||
|
|
||||||
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
if ($this->IsValidEntity($args['entity']))
|
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
||||||
}
|
}
|
||||||
@@ -77,4 +77,9 @@ class GenericEntityApiController extends BaseApiController
|
|||||||
{
|
{
|
||||||
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
|
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function IsEntityWithPreventedListing($entity)
|
||||||
|
{
|
||||||
|
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntitiesPreventListing->enum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Grocy\Controllers;
|
|
||||||
|
|
||||||
use \Grocy\Services\HabitsService;
|
|
||||||
|
|
||||||
class HabitsApiController extends BaseApiController
|
|
||||||
{
|
|
||||||
public function __construct(\Slim\Container $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
$this->HabitsService = new HabitsService();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected $HabitsService;
|
|
||||||
|
|
||||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
$trackedTime = date('Y-m-d H:i:s');
|
|
||||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']))
|
|
||||||
{
|
|
||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime);
|
|
||||||
return $this->VoidApiActionResponse($response);
|
|
||||||
}
|
|
||||||
catch (\Exception $ex)
|
|
||||||
{
|
|
||||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
|
||||||
}
|
|
||||||
catch (\Exception $ex)
|
|
||||||
{
|
|
||||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,70 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Grocy\Controllers;
|
|
||||||
|
|
||||||
use \Grocy\Services\HabitsService;
|
|
||||||
|
|
||||||
class HabitsController extends BaseController
|
|
||||||
{
|
|
||||||
public function __construct(\Slim\Container $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
$this->HabitsService = new HabitsService();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected $HabitsService;
|
|
||||||
|
|
||||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
$nextHabitTimes = array();
|
|
||||||
foreach($this->Database->habits() as $habit)
|
|
||||||
{
|
|
||||||
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nextXDays = 5;
|
|
||||||
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
|
||||||
$countOverdue = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
|
||||||
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name'),
|
|
||||||
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
|
||||||
'nextHabitTimes' => $nextHabitTimes,
|
|
||||||
'nextXDays' => $nextXDays,
|
|
||||||
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
|
||||||
'countOverdue' => $countOverdue
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habittracking', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function HabitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habits', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
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'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,7 +3,6 @@
|
|||||||
namespace Grocy\Controllers;
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
use \Grocy\Services\SessionService;
|
use \Grocy\Services\SessionService;
|
||||||
use \Grocy\Services\ApplicationService;
|
|
||||||
use \Grocy\Services\DatabaseMigrationService;
|
use \Grocy\Services\DatabaseMigrationService;
|
||||||
use \Grocy\Services\DemoDataGeneratorService;
|
use \Grocy\Services\DemoDataGeneratorService;
|
||||||
|
|
||||||
@@ -24,10 +23,21 @@ class LoginController extends BaseController
|
|||||||
$postParams = $request->getParsedBody();
|
$postParams = $request->getParsedBody();
|
||||||
if (isset($postParams['username']) && isset($postParams['password']))
|
if (isset($postParams['username']) && isset($postParams['password']))
|
||||||
{
|
{
|
||||||
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
$user = $this->Database->users()->where('username', $postParams['username'])->fetch();
|
||||||
|
$inputPassword = $postParams['password'];
|
||||||
|
$stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on';
|
||||||
|
|
||||||
|
if ($user !== null && password_verify($inputPassword, $user->password))
|
||||||
{
|
{
|
||||||
$sessionKey = $this->SessionService->CreateSession();
|
$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently);
|
||||||
setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
setcookie($this->SessionCookieName, $sessionKey, intval(time() + 31220640000)); // Cookie expires in 999 years, but session validity is up to SessionService
|
||||||
|
|
||||||
|
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
|
||||||
|
{
|
||||||
|
$user->update(array(
|
||||||
|
'password' => password_hash($inputPassword, PASSWORD_DEFAULT)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
||||||
}
|
}
|
||||||
@@ -59,8 +69,7 @@ class LoginController extends BaseController
|
|||||||
$databaseMigrationService = new DatabaseMigrationService();
|
$databaseMigrationService = new DatabaseMigrationService();
|
||||||
$databaseMigrationService->MigrateDatabase();
|
$databaseMigrationService->MigrateDatabase();
|
||||||
|
|
||||||
$applicationService = new ApplicationService();
|
if (GROCY_IS_DEMO_INSTALL)
|
||||||
if ($applicationService->IsDemoInstallation())
|
|
||||||
{
|
{
|
||||||
$demoDataGeneratorService = new DemoDataGeneratorService();
|
$demoDataGeneratorService = new DemoDataGeneratorService();
|
||||||
$demoDataGeneratorService->PopulateDemoData();
|
$demoDataGeneratorService->PopulateDemoData();
|
||||||
|
@@ -35,7 +35,8 @@ class OpenApiController extends BaseApiController
|
|||||||
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->AppContainer->view->render($response, 'manageapikeys', [
|
return $this->AppContainer->view->render($response, 'manageapikeys', [
|
||||||
'apiKeys' => $this->Database->api_keys()
|
'apiKeys' => $this->Database->api_keys(),
|
||||||
|
'users' => $this->Database->users()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
controllers/RecipesApiController.php
Normal file
35
controllers/RecipesApiController.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\RecipesService;
|
||||||
|
|
||||||
|
class RecipesApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->RecipesService = new RecipesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $RecipesService;
|
||||||
|
|
||||||
|
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ConsumeRecipe(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->RecipesService->ConsumeRecipe($args['recipeId']);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
controllers/RecipesController.php
Normal file
102
controllers/RecipesController.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\RecipesService;
|
||||||
|
|
||||||
|
class RecipesController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->RecipesService = new RecipesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $RecipesService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$recipes = $this->Database->recipes()->orderBy('name');
|
||||||
|
|
||||||
|
$selectedRecipe = null;
|
||||||
|
$selectedRecipePositions = null;
|
||||||
|
if (isset($request->getQueryParams()['recipe']))
|
||||||
|
{
|
||||||
|
$selectedRecipe = $this->Database->recipes($request->getQueryParams()['recipe']);
|
||||||
|
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $request->getQueryParams()['recipe'])->orderBy('ingredient_group');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach ($recipes as $recipe)
|
||||||
|
{
|
||||||
|
$selectedRecipe = $recipe;
|
||||||
|
$selectedRecipePositions = $this->Database->recipes_pos()->where('recipe_id', $recipe->id)->orderBy('ingredient_group');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedRecipeSubRecipes = $this->Database->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll();
|
||||||
|
$selectedRecipeSubRecipesPositions = $this->Database->recipes_pos()->where('recipe_id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll();
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'recipes', [
|
||||||
|
'recipes' => $recipes,
|
||||||
|
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
|
||||||
|
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
|
||||||
|
'selectedRecipe' => $selectedRecipe,
|
||||||
|
'selectedRecipePositions' => $selectedRecipePositions,
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'selectedRecipeSubRecipes' => $selectedRecipeSubRecipes,
|
||||||
|
'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RecipeEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$recipeId = $args['recipeId'];
|
||||||
|
if ($recipeId == 'new')
|
||||||
|
{
|
||||||
|
$newRecipe = $this->Database->recipes()->createRow(array(
|
||||||
|
'name' => $this->LocalizationService->Localize('New recipe')
|
||||||
|
));
|
||||||
|
$newRecipe->save();
|
||||||
|
|
||||||
|
$recipeId = $this->Database->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'recipeform', [
|
||||||
|
'recipe' => $this->Database->recipes($recipeId),
|
||||||
|
'recipePositions' => $this->Database->recipes_pos()->where('recipe_id', $recipeId),
|
||||||
|
'mode' => 'edit',
|
||||||
|
'products' => $this->Database->products(),
|
||||||
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
|
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
|
||||||
|
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
|
||||||
|
'recipes' => $this->Database->recipes()->orderBy('name'),
|
||||||
|
'recipeNestings' => $this->Database->recipes_nestings()->where('recipe_id', $recipeId)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RecipePosEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['recipePosId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'recipeposform', [
|
||||||
|
'mode' => 'create',
|
||||||
|
'recipe' => $this->Database->recipes($args['recipeId']),
|
||||||
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
|
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'recipeposform', [
|
||||||
|
'mode' => 'edit',
|
||||||
|
'recipe' => $this->Database->recipes($args['recipeId']),
|
||||||
|
'recipePos' => $this->Database->recipes_pos($args['recipePosId']),
|
||||||
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
|
'quantityUnits' => $this->Database->quantity_units()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -26,14 +26,32 @@ class StockApiController extends BaseApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ProductPriceHistory(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->StockService->GetProductPriceHistory($args['productId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$bestBeforeDate = date('Y-m-d');
|
$bestBeforeDate = date('Y-m-d');
|
||||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
|
||||||
{
|
{
|
||||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$price = null;
|
||||||
|
if (isset($request->getQueryParams()['price']) && !empty($request->getQueryParams()['price']) && is_numeric($request->getQueryParams()['price']))
|
||||||
|
{
|
||||||
|
$price = $request->getQueryParams()['price'];
|
||||||
|
}
|
||||||
|
|
||||||
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
$transactionType = StockService::TRANSACTION_TYPE_PURCHASE;
|
||||||
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
if (isset($request->getQueryParams()['transactiontype']) && !empty($request->getQueryParams()['transactiontype']))
|
||||||
{
|
{
|
||||||
@@ -42,8 +60,8 @@ class StockApiController extends BaseApiController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType);
|
$bookingId = $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType, date('Y-m-d'), $price);
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
{
|
{
|
||||||
@@ -67,8 +85,8 @@ class StockApiController extends BaseApiController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
$bookingId = $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
{
|
{
|
||||||
@@ -79,15 +97,15 @@ class StockApiController extends BaseApiController
|
|||||||
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$bestBeforeDate = date('Y-m-d');
|
$bestBeforeDate = date('Y-m-d');
|
||||||
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']))
|
if (isset($request->getQueryParams()['bestbeforedate']) && !empty($request->getQueryParams()['bestbeforedate']) && IsIsoDate($request->getQueryParams()['bestbeforedate']))
|
||||||
{
|
{
|
||||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
$bookingId = $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->ApiResponse(array('booking_id' => $bookingId));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
{
|
{
|
||||||
@@ -100,12 +118,36 @@ class StockApiController extends BaseApiController
|
|||||||
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function CurrentVolatilStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$nextXDays = 5;
|
||||||
|
if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days']))
|
||||||
|
{
|
||||||
|
$nextXDays = $request->getQueryParams()['expiring_days'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$expiringProducts = $this->StockService->GetExpiringProducts($nextXDays, true);
|
||||||
|
$expiredProducts = $this->StockService->GetExpiringProducts(-1);
|
||||||
|
$missingProducts = $this->StockService->GetMissingProducts();
|
||||||
|
return $this->ApiResponse(array(
|
||||||
|
'expiring_products' => $expiringProducts,
|
||||||
|
'expired_products' => $expiredProducts,
|
||||||
|
'missing_products' => $missingProducts
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$this->StockService->AddMissingProductsToShoppingList();
|
$this->StockService->AddMissingProductsToShoppingList();
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->VoidApiActionResponse($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ClearShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$this->StockService->ClearShoppingList();
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -123,4 +165,17 @@ class StockApiController extends BaseApiController
|
|||||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function UndoBooking(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->ApiResponse($this->StockService->UndoBooking($args['bookingId']));
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,19 +17,14 @@ class StockController extends BaseController
|
|||||||
|
|
||||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$currentStock = $this->StockService->GetCurrentStock();
|
|
||||||
$nextXDays = 5;
|
|
||||||
$countExpiringNextXDays = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('+5 days')), '<'));
|
|
||||||
$countAlreadyExpired = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('-1 days')), '<'));
|
|
||||||
return $this->AppContainer->view->render($response, 'stockoverview', [
|
return $this->AppContainer->view->render($response, 'stockoverview', [
|
||||||
'products' => $this->Database->products()->orderBy('name'),
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'currentStock' => $currentStock,
|
'currentStock' => $this->StockService->GetCurrentStock(),
|
||||||
'missingProducts' => $this->StockService->GetMissingProducts(),
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
'nextXDays' => $nextXDays,
|
'nextXDays' => 5,
|
||||||
'countExpiringNextXDays' => $countExpiringNextXDays,
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
'countAlreadyExpired' => $countAlreadyExpired
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +55,8 @@ class StockController extends BaseController
|
|||||||
'listItems' => $this->Database->shopping_list(),
|
'listItems' => $this->Database->shopping_list(),
|
||||||
'products' => $this->Database->products()->orderBy('name'),
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
'missingProducts' => $this->StockService->GetMissingProducts()
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +65,17 @@ class StockController extends BaseController
|
|||||||
return $this->AppContainer->view->render($response, 'products', [
|
return $this->AppContainer->view->render($response, 'products', [
|
||||||
'products' => $this->Database->products()->orderBy('name'),
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ProductDefaults(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productpresets', [
|
||||||
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +86,13 @@ class StockController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ProductGroupsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroups', [
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->AppContainer->view->render($response, 'quantityunits', [
|
return $this->AppContainer->view->render($response, 'quantityunits', [
|
||||||
@@ -94,6 +107,7 @@ class StockController extends BaseController
|
|||||||
return $this->AppContainer->view->render($response, 'productform', [
|
return $this->AppContainer->view->render($response, 'productform', [
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productgroups' => $this->Database->product_groups()->orderBy('name'),
|
||||||
'mode' => 'create'
|
'mode' => 'create'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -103,6 +117,7 @@ class StockController extends BaseController
|
|||||||
'product' => $this->Database->products($args['productId']),
|
'product' => $this->Database->products($args['productId']),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productgroups' => $this->Database->product_groups()->orderBy('name'),
|
||||||
'mode' => 'edit'
|
'mode' => 'edit'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -125,6 +140,23 @@ class StockController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ProductGroupEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['productGroupId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroupform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroupform', [
|
||||||
|
'group' => $this->Database->product_groups($args['productGroupId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
if ($args['quantityunitId'] == 'new')
|
if ($args['quantityunitId'] == 'new')
|
||||||
@@ -160,4 +192,13 @@ class StockController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function Journal(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'stockjournal', [
|
||||||
|
'stockLog' => $this->Database->stock_log()->orderBy('row_created_timestamp', 'DESC'),
|
||||||
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
41
controllers/SystemApiController.php
Normal file
41
controllers/SystemApiController.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\DatabaseService;
|
||||||
|
|
||||||
|
class SystemApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->DatabaseService = new DatabaseService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $DatabaseService;
|
||||||
|
|
||||||
|
public function GetDbChangedTime(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse(array(
|
||||||
|
'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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
controllers/TasksApiController.php
Normal file
40
controllers/TasksApiController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\TasksService;
|
||||||
|
|
||||||
|
class TasksApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->TasksService = new TasksService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $TasksService;
|
||||||
|
|
||||||
|
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->TasksService->GetCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function MarkTaskAsCompleted(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$doneTime = date('Y-m-d H:i:s');
|
||||||
|
if (isset($request->getQueryParams()['done_time']) && !empty($request->getQueryParams()['done_time']) && IsIsoDateTime($request->getQueryParams()['done_time']))
|
||||||
|
{
|
||||||
|
$doneTime = $request->getQueryParams()['done_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->TasksService->MarkTaskAsCompleted($args['taskId'], $doneTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
controllers/TasksController.php
Normal file
80
controllers/TasksController.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\TasksService;
|
||||||
|
|
||||||
|
class TasksController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->TasksService = new TasksService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $TasksService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['include_done']))
|
||||||
|
{
|
||||||
|
$tasks = $this->Database->tasks()->orderBy('name');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$tasks = $this->TasksService->GetCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'tasks', [
|
||||||
|
'tasks' => $tasks,
|
||||||
|
'nextXDays' => 5,
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['taskId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskform', [
|
||||||
|
'mode' => 'create',
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskform', [
|
||||||
|
'task' => $this->Database->tasks($args['taskId']),
|
||||||
|
'mode' => 'edit',
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategories', [
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskCategoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['categoryId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategoryform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategoryform', [
|
||||||
|
'category' => $this->Database->task_categories($args['categoryId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
controllers/UsersApiController.php
Normal file
99
controllers/UsersApiController.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\UsersService;
|
||||||
|
|
||||||
|
class UsersApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->UsersService = new UsersService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $UsersService;
|
||||||
|
|
||||||
|
public function GetUsers(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->UsersService->GetUsersAsDto());
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CreateUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->UsersService->CreateUser($requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DeleteUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->UsersService->DeleteUser($args['userId']);
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function EditUser(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->UsersService->EditUser($args['userId'], $requestBody['username'], $requestBody['first_name'], $requestBody['last_name'], $requestBody['password']);
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
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
controllers/UsersController.php
Normal file
30
controllers/UsersController.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
class UsersController extends BaseController
|
||||||
|
{
|
||||||
|
public function UsersList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'users', [
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function UserEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['userId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'userform', [
|
||||||
|
'user' => $this->Database->users($args['userId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,7 @@ class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
To use this plugin, configure it in data/config.php like this:
|
To use this plugin, configure it in data/config.php like this:
|
||||||
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal 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
28
docker_nginx/common.conf
Normal 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;
|
||||||
|
}
|
8
docker_nginx/conf.d/default.conf
Normal file
8
docker_nginx/conf.d/default.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /www/public; # see: volumes_from
|
||||||
|
|
||||||
|
include /etc/nginx/common.conf;
|
||||||
|
}
|
20
docker_nginx/conf.d/ssl.conf
Normal file
20
docker_nginx/conf.d/ssl.conf
Normal 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
42
docker_nginx/nginx.conf
Normal 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;
|
||||||
|
}
|
1206
grocy.openapi.json
1206
grocy.openapi.json
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ class UrlManager
|
|||||||
|
|
||||||
public function ConstructUrl($relativePath, $isResource = false)
|
public function ConstructUrl($relativePath, $isResource = false)
|
||||||
{
|
{
|
||||||
if (DISABLE_URL_REWRITING === false || $isResource === true)
|
if (GROCY_DISABLE_URL_REWRITING === false || $isResource === true)
|
||||||
{
|
{
|
||||||
return rtrim($this->BasePath, '/') . $relativePath;
|
return rtrim($this->BasePath, '/') . $relativePath;
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,11 @@ class UrlManager
|
|||||||
|
|
||||||
private function GetBaseUrl()
|
private function GetBaseUrl()
|
||||||
{
|
{
|
||||||
|
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
|
||||||
|
{
|
||||||
|
$_SERVER['HTTPS'] = 'on';
|
||||||
|
}
|
||||||
|
|
||||||
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -110,3 +110,92 @@ function IsAssociativeArray(array $array)
|
|||||||
$keys = array_keys($array);
|
$keys = array_keys($array);
|
||||||
return array_keys($keys) !== $keys;
|
return array_keys($keys) !== $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function IsIsoDate($dateString)
|
||||||
|
{
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d', $dateString);
|
||||||
|
return $d && $d->format('Y-m-d') === $dateString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsIsoDateTime($dateTimeString)
|
||||||
|
{
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d H:i:s', $dateTimeString);
|
||||||
|
return $d && $d->format('Y-m-d H:i:s') === $dateTimeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BoolToString(bool $bool)
|
||||||
|
{
|
||||||
|
return $bool ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
function Setting(string $name, $value)
|
||||||
|
{
|
||||||
|
if (!defined('GROCY_' . $name))
|
||||||
|
{
|
||||||
|
// The content of a $name.txt file in /data/settingoverrides can overwrite the given setting (for embedded mode)
|
||||||
|
$settingOverrideFile = GROCY_DATAPATH . '/settingoverrides/' . $name . '.txt';
|
||||||
|
if (file_exists($settingOverrideFile))
|
||||||
|
{
|
||||||
|
define('GROCY_' . $name, file_get_contents($settingOverrideFile));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
define('GROCY_' . $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 = '';
|
||||||
|
|
||||||
|
if (empty($user->first_name) && !empty($user->last_name))
|
||||||
|
{
|
||||||
|
$displayName = $user->last_name;
|
||||||
|
}
|
||||||
|
elseif (empty($user->last_name) && !empty($user->first_name))
|
||||||
|
{
|
||||||
|
$displayName = $user->first_name;
|
||||||
|
}
|
||||||
|
elseif (!empty($user->last_name) && !empty($user->first_name))
|
||||||
|
{
|
||||||
|
$displayName = $user->first_name . ' ' . $user->last_name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$displayName = $user->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Pluralize($number, $singularForm, $pluralForm)
|
||||||
|
{
|
||||||
|
$text = $singularForm;
|
||||||
|
if ($number != 1 && $pluralForm !== null && !empty($pluralForm))
|
||||||
|
{
|
||||||
|
$text = $pluralForm;
|
||||||
|
}
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsValidFileName($fileName)
|
||||||
|
{
|
||||||
|
if(preg_match('=^[^/?*;:{}\\\\]+\.[^/?*;:{}\\\\]+$=', $fileName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
6
info.php
Normal file
6
info.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Show all information, defaults to INFO_ALL
|
||||||
|
phpinfo();
|
||||||
|
|
||||||
|
?>
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'Stock overview' => 'Bestand',
|
'Stock overview' => 'Bestand',
|
||||||
'#1 products with #2 units in stock' => '#1 Produkte (#2 Einheiten) vorrätig',
|
|
||||||
'#1 products expiring within the next #2 days' => '#1 Produkte laufen innerhalb der nächsten #2 Tage ab',
|
'#1 products expiring within the next #2 days' => '#1 Produkte laufen innerhalb der nächsten #2 Tage ab',
|
||||||
'#1 products are already expired' => '#1 Produkte sind bereits abgelaufen',
|
'#1 products are already expired' => '#1 Produkte sind bereits abgelaufen',
|
||||||
'#1 products are below defined min. stock amount' => '#1 Produkte sind unter Mindestbestand',
|
'#1 products are below defined min. stock amount' => '#1 Produkte sind unter Mindestbestand',
|
||||||
@@ -10,20 +9,20 @@ return array(
|
|||||||
'Amount' => 'Menge',
|
'Amount' => 'Menge',
|
||||||
'Next best before date' => 'Nächstes MHD',
|
'Next best before date' => 'Nächstes MHD',
|
||||||
'Logout' => 'Abmelden',
|
'Logout' => 'Abmelden',
|
||||||
'Habits overview' => 'Gewohnheiten',
|
'Chores overview' => 'Hausarbeiten',
|
||||||
'Batteries overview' => 'Batterien',
|
'Batteries overview' => 'Batterien',
|
||||||
'Purchase' => 'Einkauf',
|
'Purchase' => 'Einkauf',
|
||||||
'Consume' => 'Verbrauch',
|
'Consume' => 'Verbrauch',
|
||||||
'Inventory' => 'Inventur',
|
'Inventory' => 'Inventur',
|
||||||
'Shopping list' => 'Einkaufszettel',
|
'Shopping list' => 'Einkaufszettel',
|
||||||
'Habit tracking' => 'Gewohnheit-Ausführung',
|
'Chore tracking' => 'Hausarbeiten-Ausführung',
|
||||||
'Battery tracking' => 'Batterie-Ladzyklus',
|
'Battery tracking' => 'Batterie-Ladzyklus',
|
||||||
'Products' => 'Produkte',
|
'Products' => 'Produkte',
|
||||||
'Locations' => 'Standorte',
|
'Locations' => 'Standorte',
|
||||||
'Quantity units' => 'Mengeneinheiten',
|
'Quantity units' => 'Mengeneinheiten',
|
||||||
'Habits' => 'Gewohnheiten',
|
'Chores' => 'Hausarbeiten',
|
||||||
'Batteries' => 'Batterien',
|
'Batteries' => 'Batterien',
|
||||||
'Habit' => 'Gewohnheit',
|
'Chore' => 'Hausarbeit',
|
||||||
'Next estimated tracking' => 'Nächste geplante Ausführung',
|
'Next estimated tracking' => 'Nächste geplante Ausführung',
|
||||||
'Last tracked' => 'Zuletzt ausgeführt',
|
'Last tracked' => 'Zuletzt ausgeführt',
|
||||||
'Battery' => 'Batterie',
|
'Battery' => 'Batterie',
|
||||||
@@ -42,7 +41,7 @@ return array(
|
|||||||
'New amount' => 'Neue Menge',
|
'New amount' => 'Neue Menge',
|
||||||
'Note' => 'Notiz',
|
'Note' => 'Notiz',
|
||||||
'Tracked time' => 'Ausführungszeit',
|
'Tracked time' => 'Ausführungszeit',
|
||||||
'Habit overview' => 'Gewohnheit Übersicht',
|
'Chore overview' => 'Hausarbeit Übersicht',
|
||||||
'Tracked count' => 'Ausführungsanzahl',
|
'Tracked count' => 'Ausführungsanzahl',
|
||||||
'Battery overview' => 'Batterie Übersicht',
|
'Battery overview' => 'Batterie Übersicht',
|
||||||
'Charge cycles count' => 'Ladezyklen',
|
'Charge cycles count' => 'Ladezyklen',
|
||||||
@@ -69,11 +68,11 @@ return array(
|
|||||||
'Create quantity unit' => 'Mengeneinheit erstellen',
|
'Create quantity unit' => 'Mengeneinheit erstellen',
|
||||||
'Period type' => 'Periodentyp',
|
'Period type' => 'Periodentyp',
|
||||||
'Period days' => 'Tage/Periode',
|
'Period days' => 'Tage/Periode',
|
||||||
'Create habit' => 'Gewohnheit erstellen',
|
'Create chore' => 'Hausarbeit erstellen',
|
||||||
'Used in' => 'Benutzt in',
|
'Used in' => 'Benutzt in',
|
||||||
'Create battery' => 'Batterie erstellen',
|
'Create battery' => 'Batterie erstellen',
|
||||||
'Edit battery' => 'Batterie bearbeiten',
|
'Edit battery' => 'Batterie bearbeiten',
|
||||||
'Edit habit' => 'Gewohnheit bearbeiten',
|
'Edit chore' => 'Hausarbeit bearbeiten',
|
||||||
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
|
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
|
||||||
'Edit product' => 'Produkt bearbeiten',
|
'Edit product' => 'Produkt bearbeiten',
|
||||||
'Edit location' => 'Standort bearbeiten',
|
'Edit location' => 'Standort bearbeiten',
|
||||||
@@ -91,7 +90,7 @@ return array(
|
|||||||
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
||||||
'Yes' => 'Ja',
|
'Yes' => 'Ja',
|
||||||
'No' => 'Nein',
|
'No' => 'Nein',
|
||||||
'Are you sure to delete habit "#1"?' => 'Gewohnheit "#1" wirklich löschen?',
|
'Are you sure to delete chore "#1"?' => 'Hausarbeit "#1" wirklich löschen?',
|
||||||
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
|
||||||
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
||||||
'Cancel' => 'Abbrechen',
|
'Cancel' => 'Abbrechen',
|
||||||
@@ -111,36 +110,213 @@ return array(
|
|||||||
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
|
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
|
||||||
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
|
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
|
||||||
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
|
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
|
||||||
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Gewohnheit #1 Tage nach der letzten Ausführung geplant wird',
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Hausarbeit #1 Tage nach der letzten Ausführung geplant wird',
|
||||||
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
||||||
'About grocy' => 'Über grocy',
|
'About grocy' => 'Über grocy',
|
||||||
'Close' => 'Schließen',
|
'Close' => 'Schließen',
|
||||||
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
|
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
|
||||||
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
|
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
|
||||||
'#1 habits are due to be done within the next #2 days' => '#1 Gewohnheiten stehen in den nächsten #2 Tagen an',
|
'#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an',
|
||||||
'#1 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig',
|
'#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig',
|
||||||
'Released on' => 'Veröffentlicht am',
|
'Released on' => 'Veröffentlicht am',
|
||||||
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
||||||
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
|
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
|
||||||
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
|
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
|
||||||
'Tracked execution of habit #1 on #2' => 'Ausführung von #1 am #2 erfasst',
|
'Tracked execution of chore #1 on #2' => 'Ausführung von #1 am #2 erfasst',
|
||||||
'Tracked charge cylce of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
|
'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
|
||||||
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
||||||
'All' => 'Alle',
|
'All' => 'Alle',
|
||||||
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
||||||
'Track execution of habit #1' => 'Erfasse eine Ausführung von #1',
|
'Track execution of chore #1' => 'Erfasse eine Ausführung von #1',
|
||||||
'Filter by location' => 'Nach Standort filtern',
|
'Filter by location' => 'Nach Standort filtern',
|
||||||
|
'Search' => 'Suche',
|
||||||
|
'Not logged in' => 'Nicht angemeldet',
|
||||||
|
'You have to select a product' => 'Ein Produkt muss ausgewählt werden',
|
||||||
|
'You have to select a chore' => 'Eine Hausarbeit muss ausgewählt werden',
|
||||||
|
'You have to select a battery' => 'Eine Batterie muss ausgewählt werden',
|
||||||
|
'A name is required' => 'Ein Name ist erforderlich',
|
||||||
|
'A location is required' => 'Ein Standort ist erforderlich',
|
||||||
|
'The amount cannot be lower than #1' => 'Die Menge darf nicht kleiner als #1 sein',
|
||||||
|
'This cannot be negative' => 'Dies darf nicht negativ sein',
|
||||||
|
'A quantity unit is required' => 'Eine Mengeneinheit muss ausgewählt werden',
|
||||||
|
'A period type is required' => 'Eine Periodentyp muss ausgewählt werden',
|
||||||
|
'A best before date is required and must be later than today' => 'Ein Mindesthaltbarkeitsdatum ist erforderlich und muss später als heute sein',
|
||||||
|
'Settings' => 'Einstellungen',
|
||||||
|
'This can only be before now' => 'Dies kann nur vor jetzt sein',
|
||||||
|
'Calendar' => 'Kalender',
|
||||||
|
'Recipes' => 'Rezepte',
|
||||||
|
'Edit recipe' => 'Rezept bearbeiten',
|
||||||
|
'New recipe' => 'Neues Rezept',
|
||||||
|
'Ingredients list' => 'Zutatenliste',
|
||||||
|
'Add recipe ingredient' => 'Rezeptzutat hinzufügen',
|
||||||
|
'Edit recipe ingredient' => 'Rezeptzutat bearbeiten',
|
||||||
|
'Are you sure to delete recipe "#1"?' => 'Rezept "#1" wirklich löschen?',
|
||||||
|
'Are you sure to delete recipe ingredient "#1"?' => 'Rezeptzutat "#1" wirklich löschen?',
|
||||||
|
'Are you sure to empty the shopping list?' => 'Sicher, dass den Einkaufszettel geleert werden soll?',
|
||||||
|
'Clear list' => 'Liste leeren',
|
||||||
|
'Requirements fulfilled' => 'Bedarf im Bestand',
|
||||||
|
'Put missing products on shopping list' => 'Fehlende Produkte auf den Einkaufszettel setzen',
|
||||||
|
'Not enough in stock, #1 ingredients missing' => 'Nicht ausreichend im Bestand, #1 Zutaten fehlen',
|
||||||
|
'Enough in stock' => 'Bestand reicht aus',
|
||||||
|
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Bestand nicht ausreichend, #1 Zutaten fehlen, stehen aber bereits auf dem Einkaufszettel',
|
||||||
|
'Expand to fullscreen' => 'Auf ganzen Bildschirm vergrößern',
|
||||||
|
'Ingredients' => 'Zutaten',
|
||||||
|
'Preparation' => 'Zubereitung',
|
||||||
|
'Recipe' => 'Rezept',
|
||||||
|
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Nicht ausreichend im Bestand, #1 fehlen, #2 stehen bereits auf dem Einkaufszettel',
|
||||||
|
'Show notes' => 'Notizen anzeigen',
|
||||||
|
'Put missing amount on shopping list' => 'Fehlende Menge auf den Einkaufszettel setzen',
|
||||||
|
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Sicher alle fehlenden Zutaten für Rezept "#1" auf die Einkaufsliste zu setzen?',
|
||||||
|
'Added for recipe #1' => 'Hinzugefügt für Rezept #1',
|
||||||
|
'Manage users' => 'Benutzer verwalten',
|
||||||
|
'User' => 'Benutzer',
|
||||||
|
'Users' => 'Benutzer',
|
||||||
|
'Are you sure to delete user "#1"?' => 'Benutzer "#1" wirklich löschen?',
|
||||||
|
'Create user' => 'Benutzer erstellen',
|
||||||
|
'Edit user' => 'Benutzer bearbeiten',
|
||||||
|
'First name' => 'Vorname',
|
||||||
|
'Last name' => 'Nachname',
|
||||||
|
'A username is required' => 'Ein Benutzername ist erforderlich',
|
||||||
|
'Confirm password' => 'Passwort bestätigen',
|
||||||
|
'Passwords do not match' => 'Passwörter stimmen nicht überein',
|
||||||
|
'Change password' => 'Passwort ändern',
|
||||||
|
'Done by' => 'Ausgeführt von',
|
||||||
|
'Last done by' => 'Zuletzt ausgeführt von',
|
||||||
|
'Unknown' => 'Unbekannt',
|
||||||
|
'Filter by chore' => 'Nach Hausarbeit filtern',
|
||||||
|
'Chores journal' => 'Hausarbeitenjournal',
|
||||||
|
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
|
||||||
|
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
|
||||||
|
'Last price' => 'Letzter Preis',
|
||||||
|
'Price history' => 'Preisentwicklung',
|
||||||
|
'No price history available' => 'Keine Preisdaten verfügbar',
|
||||||
|
'Price' => 'Preis',
|
||||||
|
'in #1 per purchase quantity unit' => 'in #1 pro Einkaufsmengeneinheit',
|
||||||
|
'The price cannot be lower than #1' => 'Der Preis darf nicht niedriger als #1 sein',
|
||||||
|
'#1 product expires within the next #2 days' => '#1 Produkt läuft innerhalb der nächsten #2 Tage ab',
|
||||||
|
'#1 product is already expired' => '#1 Produkt ist bereits abgelaufen',
|
||||||
|
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
|
||||||
|
'Unit' => 'Einheit',
|
||||||
|
'Units' => 'Einheiten',
|
||||||
|
'#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an',
|
||||||
|
'#1 chore is overdue to be done' => '#1 Hausarbeit ist überfällig',
|
||||||
|
'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden',
|
||||||
|
'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig',
|
||||||
|
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge',
|
||||||
|
'in singular form' => 'in der Einzahl',
|
||||||
|
'in plural form' => 'in der Mehrzahl',
|
||||||
|
'Never expires' => 'Läuft nie ab',
|
||||||
|
'This cannot be lower than #1' => 'Dies darf nicht kleiner als #1 sein',
|
||||||
|
'-1 means that this product never expires' => '-1 bedeuet, dass dieses Produkt niemals abläuft',
|
||||||
|
'Quantity unit' => 'Mengeneinheit',
|
||||||
|
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Nur prüfen, ob eine einzelne Einheit vorrätig ist (eine abweichende Mengeneinheit kann dann oben verwendet werden)',
|
||||||
|
'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)?' => 'Sicher, dass alle Zutaten die vom Rezept "#1" benötigt werden aus dem Bestand entfernt werden sollen (Zutaten markiert mit "nur prüfen, ob eine einzelne Einheit vorrätig ist" werden ignoriert)?',
|
||||||
|
'Removed all ingredients of recipe "#1" from stock' => 'Alle Zutaten, die vom Rezept "#1" benötigt werden, wurdem aus dem Bestand entfernt',
|
||||||
|
'Consume all ingredients needed by this recipe' => 'Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand enternen',
|
||||||
|
'Click to show technical details' => 'Klick um technische Details anzuzeigen',
|
||||||
|
'Error while saving, probably this item already exists' => 'Fehler beim Speichern, möglicherweise existiert das Element bereits',
|
||||||
|
'Error details' => 'Fehlerdetails',
|
||||||
|
'Tasks' => 'Aufgaben',
|
||||||
|
'Show done tasks' => 'Erledigte Aufgaben anzeigen',
|
||||||
|
'Task' => 'Aufgabe',
|
||||||
|
'Due' => 'Fällig',
|
||||||
|
'Assigned to' => 'Zugewiesen an',
|
||||||
|
'Mark task "#1" as completed' => 'Aufgabe "#1" als erledigt markieren',
|
||||||
|
'Uncategorized' => 'Nicht kategorisiert',
|
||||||
|
'Task categories' => 'Aufgabenkategorien',
|
||||||
|
'Create task' => 'Aufgabe erstellen',
|
||||||
|
'A due date is required' => 'Ein Fälligkeitsdatum ist erforderlich',
|
||||||
|
'Category' => 'Kategorie',
|
||||||
|
'Edit task' => 'Aufgabe bearbeiten',
|
||||||
|
'Are you sure to delete task "#1"?' => 'Aufgabe "#1" wirklich löschen?',
|
||||||
|
'#1 task is due to be done within the next #2 days' => '#1 Aufgabe steht in den nächsten #2 Tagen an',
|
||||||
|
'#1 tasks are due to be done within the next #2 days' => '#1 Aufgaben stehen in den nächsten #2 Tagen an',
|
||||||
|
'#1 task is overdue to be done' => '#1 Aufgabe ist überfällig',
|
||||||
|
'#1 tasks are overdue to be done' => '#1 Aufgaben sind überfällig',
|
||||||
|
'Edit task category' => 'Aufgabenkategorie bearbeiten',
|
||||||
|
'Create task category' => 'Aufgabenkategorie erstellen',
|
||||||
|
'Product groups' => 'Produktgruppen',
|
||||||
|
'Ungrouped' => 'Ungruppiert',
|
||||||
|
'Create product group' => 'Produktgruppe erstellen',
|
||||||
|
'Edit product group' => 'Produktgruppe bearbeiten',
|
||||||
|
'Product group' => 'Produktgruppe',
|
||||||
|
'Are you sure to delete product group "#1"?' => 'Produktgruppe "#1" wirklich löschen?',
|
||||||
|
'Stay logged in permanently' => 'Dauerhaft angemeldet bleiben',
|
||||||
|
'When not set, you will get logged out at latest after 30 days' => 'Wenn nicht gesetzt, wirst du spätestens nach 30 Tagen automatisch abgemeldet',
|
||||||
|
'Filter by status' => 'Nach Status filtern',
|
||||||
|
'Below min. stock amount' => 'Unter Mindestbestand',
|
||||||
|
'Expiring soon' => 'Bald ablaufend',
|
||||||
|
'Already expired' => 'Bereits abgelaufen',
|
||||||
|
'Due soon' => 'Bald fällig',
|
||||||
|
'Overdue' => 'Überfällig',
|
||||||
|
'View settings' => 'Ansichtseinstellungen',
|
||||||
|
'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',
|
||||||
|
'Product picture' => 'Produktbild',
|
||||||
|
'No file selected' => 'Keine Datei ausgewählt',
|
||||||
|
'If you don\'t select a file, the current picture will not be altered' => 'Wenn du keine Datei auswählst, wird das aktuelle Bild nicht verändert',
|
||||||
|
'Current picture' => 'Aktuelles Bild',
|
||||||
|
'Delete' => 'Löschen',
|
||||||
|
'The current picture will be deleted when you save the product' => 'Das aktuelle Bild wird beim Speichern des Produkts gelöscht',
|
||||||
|
'Select file' => 'Datei auswählen',
|
||||||
|
'Image of product #1' => 'Bild des Produkts #1',
|
||||||
|
'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'Dieses Produkt kann nicht gelöscht werden, da es auf Lager ist, bitte zuerst den Bestand entfernen.',
|
||||||
|
'Delete not possible' => 'Löschen nicht möglich',
|
||||||
|
'Equipment' => 'Ausstattung',
|
||||||
|
'Instruction manual' => 'Bedienungsanleitung',
|
||||||
|
'The selected equipment has no instruction manual' => 'Das ausgewählte Gerät hat keine Bedienungsanleitung',
|
||||||
|
'Notes' => 'Notizen',
|
||||||
|
'Edit equipment' => 'Geräte bearbeiten',
|
||||||
|
'Create equipment' => 'Geräte erstellen',
|
||||||
|
'If you don\'t select a file, the current instruction manual will not be altered' => 'Wenn du keine Datei auswählst, wird die aktuelle Bedienungsanleitung nicht verändert',
|
||||||
|
'Current instruction manual' => 'Aktuelle Bedienungsanleitung',
|
||||||
|
'No instruction manual available' => 'Keine Bedienungsanleitung vorhanden',
|
||||||
|
'The current instruction manual will be deleted when you save the equipment' => 'Die aktuelle Bedienungsanleitung wird beim Speichern des Geräts gelöscht',
|
||||||
|
'No picture available' => 'Kein Bild vorhanden',
|
||||||
|
'Filter by product group' => 'Nach Produktgruppe filtern',
|
||||||
|
'Presets for new products' => 'Vorgaben für neue Produkte',
|
||||||
|
'Included recipes' => 'Enthaltene Rezepte',
|
||||||
|
'A recipe is required' => 'Ein Rezept ist erforderlich',
|
||||||
|
'Add included recipe' => 'Enthaltenes Rezept hinzufügen',
|
||||||
|
'Edit included recipe' => 'Enthaltenes Rezept bearbeiten',
|
||||||
|
'Group' => 'Gruppe',
|
||||||
|
'This will be used as a headline to group ingredients together' => 'Dies wird als Überschrift verwendet, um Zutaten zusammenzufassen',
|
||||||
|
'Journal' => 'Journal',
|
||||||
|
'Stock journal' => 'Bestandsjournal',
|
||||||
|
'Filter by product' => 'Nach Produkt filtern',
|
||||||
|
'Booking time' => 'Buchungszeit',
|
||||||
|
'Booking type' => 'Buchungsart',
|
||||||
|
'Undo booking' => 'Buchung rückgängig machen',
|
||||||
|
'Undone on' => 'Rückgängig gemacht am',
|
||||||
|
'Batteries journal' => 'Batteriejournal',
|
||||||
|
'Filter by battery' => 'Nach Batterie filtern',
|
||||||
|
'Undo charge cycle' => 'Ladezyklus rückgängig machen',
|
||||||
|
'Undo chore execution' => 'Ausführung rückgängig machen',
|
||||||
|
'Chore execution successfully undone' => 'Ausführung erfolgreich rückgängig gemacht',
|
||||||
|
'Undo' => 'Rückgängig machen',
|
||||||
|
'Booking successfully undone' => 'Buchung erfolgreich rückgängig gemacht',
|
||||||
|
'Charge cycle successfully undone' => 'Ladezyklus erfolgreich rückgängig gemacht',
|
||||||
|
|
||||||
//Constants
|
//Constants - Chore types
|
||||||
'manually' => 'Manuell',
|
'manually' => 'Manuell',
|
||||||
'dynamic-regular' => 'Dynamisch regelmäßig',
|
'dynamic-regular' => 'Dynamisch regelmäßig',
|
||||||
|
|
||||||
|
//Constants - Stock transaction types
|
||||||
|
'purchase' => 'Einkauf',
|
||||||
|
'consume' => 'Verbrauch',
|
||||||
|
'inventory-correction' => 'Inventur-Korrektur',
|
||||||
|
|
||||||
//Technical component translations
|
//Technical component translations
|
||||||
'timeago_locale' => 'de',
|
'timeago_locale' => 'de',
|
||||||
'timeago_nan' => 'vor NaN Jahren',
|
'timeago_nan' => 'vor NaN Jahren',
|
||||||
'moment_locale' => 'de',
|
'moment_locale' => 'de',
|
||||||
'bootstrap_datepicker_locale' => 'de',
|
|
||||||
'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}',
|
'datatables_localization' => '{"sEmptyTable":"Keine Daten in der Tabelle vorhanden","sInfo":"_START_ bis _END_ von _TOTAL_ Einträgen","sInfoEmpty":"Keine Daten vorhanden","sInfoFiltered":"(gefiltert von _MAX_ Einträgen)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ Einträge anzeigen","sLoadingRecords":"Wird geladen ..","sProcessing":"Bitte warten ..","sSearch":"Suchen","sZeroRecords":"Keine Einträge vorhanden","oPaginate":{"sFirst":"Erste","sPrevious":"Zurück","sNext":"Nächste","sLast":"Letzte"},"oAria":{"sSortAscending":": aktivieren, um Spalte aufsteigend zu sortieren","sSortDescending":": aktivieren, um Spalte absteigend zu sortieren"},"select":{"rows":{"0":"Zum Auswählen auf eine Zeile klicken","1":"1 Zeile ausgewählt","_":"%d Zeilen ausgewählt"}},"buttons":{"print":"Drucken","colvis":"Spalten","copy":"Kopieren","copyTitle":"In Zwischenablage kopieren","copyKeys":"Taste <i>ctrl</i> oder <i>⌘</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.","copySuccess":{"1":"1 Spalte kopiert","_":"%d Spalten kopiert"}}}',
|
||||||
|
'summernote_locale' => 'de-DE',
|
||||||
|
|
||||||
//Demo data
|
//Demo data
|
||||||
'Cookies' => 'Cookies',
|
'Cookies' => 'Cookies',
|
||||||
@@ -150,11 +326,17 @@ return array(
|
|||||||
'Tinned food cupboard' => 'Konservenschrank',
|
'Tinned food cupboard' => 'Konservenschrank',
|
||||||
'Fridge' => 'Kühlschrank',
|
'Fridge' => 'Kühlschrank',
|
||||||
'Piece' => 'Stück',
|
'Piece' => 'Stück',
|
||||||
|
'Pieces' => 'Stücke',
|
||||||
'Pack' => 'Packung',
|
'Pack' => 'Packung',
|
||||||
|
'Packs' => 'Packungen',
|
||||||
'Glass' => 'Glas',
|
'Glass' => 'Glas',
|
||||||
|
'Glasses' => 'Gläser',
|
||||||
'Tin' => 'Dose',
|
'Tin' => 'Dose',
|
||||||
|
'Tins' => 'Dosen',
|
||||||
'Can' => 'Becher',
|
'Can' => 'Becher',
|
||||||
|
'Cans' => 'Becher',
|
||||||
'Bunch' => 'Bund',
|
'Bunch' => 'Bund',
|
||||||
|
'Bunches' => 'Bunde',
|
||||||
'Gummy bears' => 'Gummibärchen',
|
'Gummy bears' => 'Gummibärchen',
|
||||||
'Crisps' => 'Chips',
|
'Crisps' => 'Chips',
|
||||||
'Eggs' => 'Eier',
|
'Eggs' => 'Eier',
|
||||||
@@ -173,5 +355,50 @@ return array(
|
|||||||
'Warranty ends' => 'Garantie endet',
|
'Warranty ends' => 'Garantie endet',
|
||||||
'TV remote control' => 'TV Fernbedienung',
|
'TV remote control' => 'TV Fernbedienung',
|
||||||
'Alarm clock' => 'Wecker',
|
'Alarm clock' => 'Wecker',
|
||||||
'Heat remote control' => 'Fernbedienung Heizung'
|
'Heat remote control' => 'Fernbedienung Heizung',
|
||||||
|
'Lawn mowed in the garden' => 'Rasen im Garten gemäht',
|
||||||
|
'Some good snacks' => 'Paar gute Snacks',
|
||||||
|
'Pizza dough' => 'Pizzateig',
|
||||||
|
'Sieved tomatoes' => 'Passierte Tomaten',
|
||||||
|
'Salami' => 'Salami',
|
||||||
|
'Toast' => 'Toast',
|
||||||
|
'Minced meat' => 'Hackfleisch',
|
||||||
|
'Pizza' => 'Pizza',
|
||||||
|
'Spaghetti bolognese' => 'Spaghetti Bolognese',
|
||||||
|
'Sandwiches' => 'Belegte Toasts',
|
||||||
|
'English' => 'Englisch',
|
||||||
|
'German' => 'Deutsch',
|
||||||
|
'Italian' => 'Italienisch',
|
||||||
|
'Demo in different language' => 'Demo in anderer Sprache',
|
||||||
|
'This is the note content of the recipe ingredient' => 'Dies ist der Inhalt der Notiz der Zutat',
|
||||||
|
'Demo User' => 'Demo Benutzer',
|
||||||
|
'Gram' => 'Gramm',
|
||||||
|
'Grams' => 'Gramm',
|
||||||
|
'Flour' => 'Mehl',
|
||||||
|
'Pancakes' => 'Pfannkuchen',
|
||||||
|
'Sugar' => 'Zucker',
|
||||||
|
'Home' => 'Zuhause',
|
||||||
|
'Life' => 'Leben',
|
||||||
|
'Projects' => 'Projekte',
|
||||||
|
'Repair the garage door' => 'Garagentor reparieren',
|
||||||
|
'Fork and improve grocy' => 'grocy forken und verbessern',
|
||||||
|
'Find a solution for what to do when I forget the door keys' => 'Eine Lösung für "Haustürschlüssel vergessen" finden',
|
||||||
|
'Sweets' => 'Süßigkeiten',
|
||||||
|
'Bakery products' => 'Bäckerei Produkte',
|
||||||
|
'Tinned food' => 'Konservern',
|
||||||
|
'Butchery products' => 'Metzgerei',
|
||||||
|
'Vegetables/Fruits' => 'Obst/Gemüse',
|
||||||
|
'Refrigerated products' => 'Kühlregal',
|
||||||
|
'Coffee machine' => 'Kaffeemaschine',
|
||||||
|
'Dishwasher' => 'Spülmaschine',
|
||||||
|
'Liter' => 'Liter',
|
||||||
|
'Liters' => 'Liter',
|
||||||
|
'Bottle' => 'Flasche',
|
||||||
|
'Bottles' => 'Flaschen',
|
||||||
|
'Milk' => 'Milch',
|
||||||
|
'Chocolate sauce' => 'Schokoladensoße',
|
||||||
|
'Milliliters' => 'Milliliter',
|
||||||
|
'Milliliter' => 'Milliliter',
|
||||||
|
'Bottom' => 'Boden',
|
||||||
|
'Topping' => 'Belag'
|
||||||
);
|
);
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
//Constants
|
//Constants - Chore types
|
||||||
'manually' => 'Manually',
|
'manually' => 'Manually',
|
||||||
'dynamic-regular' => 'Dynamic regular',
|
'dynamic-regular' => 'Dynamic regular',
|
||||||
|
|
||||||
|
//Constants - Stock transaction types
|
||||||
|
'purchase' => 'Purchase',
|
||||||
|
'consume' => 'Consume',
|
||||||
|
'inventory-correction' => 'Inventory correction',
|
||||||
|
|
||||||
//Technical component translations
|
//Technical component translations
|
||||||
'timeago_locale' => 'en',
|
'timeago_locale' => 'en',
|
||||||
'timeago_nan' => 'NaN years ago',
|
'timeago_nan' => 'NaN years ago',
|
||||||
'moment_locale' => '',
|
'moment_locale' => '',
|
||||||
'bootstrap_datepicker_locale' => '',
|
'datatables_localization' => '{"sEmptyTable":"No data available in table","sInfo":"Showing _START_ to _END_ of _TOTAL_ entries","sInfoEmpty":"Showing 0 to 0 of 0 entries","sInfoFiltered":"(filtered from _MAX_ total entries)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Show _MENU_ entries","sLoadingRecords":"Loading...","sProcessing":"Processing...","sSearch":"Search:","sZeroRecords":"No matching records found","oPaginate":{"sFirst":"First","sLast":"Last","sNext":"Next","sPrevious":"Previous"},"oAria":{"sSortAscending":": activate to sort column ascending","sSortDescending":": activate to sort column descending"}}',
|
||||||
'datatables_localization' => '{"sEmptyTable":"No data available in table","sInfo":"Showing _START_ to _END_ of _TOTAL_ entries","sInfoEmpty":"Showing 0 to 0 of 0 entries","sInfoFiltered":"(filtered from _MAX_ total entries)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Show _MENU_ entries","sLoadingRecords":"Loading...","sProcessing":"Processing...","sSearch":"Search:","sZeroRecords":"No matching records found","oPaginate":{"sFirst":"First","sLast":"Last","sNext":"Next","sPrevious":"Previous"},"oAria":{"sSortAscending":": activate to sort column ascending","sSortDescending":": activate to sort column descending"}}'
|
'summernote_locale' => ''
|
||||||
);
|
);
|
||||||
|
193
localization/it.php
Normal file
193
localization/it.php
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Stock overview' => 'Dispensa',
|
||||||
|
'#1 products expiring within the next #2 days' => '#1 prodotti scadranno tra #2 giorni',
|
||||||
|
'#1 products are already expired' => '#1 prodotti scaduti',
|
||||||
|
'#1 products are below defined min. stock amount' => '#1 prodotti sotto il limite minimo',
|
||||||
|
'Product' => 'prodotto',
|
||||||
|
'Amount' => 'quantità',
|
||||||
|
'Next best before date' => 'Prossima data di scadenza',
|
||||||
|
'Logout' => 'Logout',
|
||||||
|
'Chores overview' => 'Riepilogo delle abitudini',
|
||||||
|
'Batteries overview' => 'Riepilogo delle batterie',
|
||||||
|
'Purchase' => 'Acquisti',
|
||||||
|
'Consume' => 'Consumi',
|
||||||
|
'Inventory' => 'Inventario',
|
||||||
|
'Shopping list' => 'Lista della spesa',
|
||||||
|
'Chore tracking' => 'Dati abitudini',
|
||||||
|
'Battery tracking' => 'Dati batterie',
|
||||||
|
'Products' => 'Prodotti',
|
||||||
|
'Locations' => 'Posizioni',
|
||||||
|
'Quantity units' => 'Unità di misura',
|
||||||
|
'Chores' => 'Abitudini',
|
||||||
|
'Batteries' => 'Batterie',
|
||||||
|
'Chore' => 'Abitudine',
|
||||||
|
'Next estimated tracking' => 'Prossima esecuzione',
|
||||||
|
'Last tracked' => 'Ultima esecuzione',
|
||||||
|
'Battery' => 'Batterie',
|
||||||
|
'Last charged' => 'Ultima ricarica',
|
||||||
|
'Next planned charge cycle' => 'Prossima ricarica',
|
||||||
|
'Best before' => 'Data di scadenza',
|
||||||
|
'OK' => 'OK',
|
||||||
|
'Product overview' => 'Riepilogo dei prodotti',
|
||||||
|
'Stock quantity unit' => 'Unità di misura',
|
||||||
|
'Stock amount' => 'Quantità',
|
||||||
|
'Last purchased' => 'Ultimo acquisto',
|
||||||
|
'Last used' => 'Ultimo utilizzo',
|
||||||
|
'Spoiled' => 'Scaduto',
|
||||||
|
'Barcode lookup is disabled' => 'I codici a barre sono disabilitati',
|
||||||
|
'will be added to the list of barcodes for the selected product on submit' => 'sarà aggiunto alla lista dei codici a barre per questo prodotto',
|
||||||
|
'New amount' => 'Nuova quantità',
|
||||||
|
'Note' => 'Nota',
|
||||||
|
'Tracked time' => 'Ora di esecuzione',
|
||||||
|
'Chore overview' => 'Riepilogo dell\'abitudine',
|
||||||
|
'Tracked count' => 'Numero di esecuzioni',
|
||||||
|
'Battery overview' => 'Riepilogo della batteria',
|
||||||
|
'Charge cycles count' => 'Numero di ricariche',
|
||||||
|
'Create shopping list item' => 'Aggiungi un prodotto alla lista della spesa',
|
||||||
|
'Edit shopping list item' => 'Modifica un\'entrata della lista della spesa',
|
||||||
|
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 sono state aggiunte automaticamente',
|
||||||
|
'Save' => 'Salva',
|
||||||
|
'Add' => 'Aggiungi',
|
||||||
|
'Name' => 'Nome',
|
||||||
|
'Location' => 'Posizione',
|
||||||
|
'Min. stock amount' => 'Quantità minima',
|
||||||
|
'QU purchase' => 'Unità di acquisto',
|
||||||
|
'QU stock' => 'Unità di dispensa',
|
||||||
|
'QU factor' => 'Fattore di conversione',
|
||||||
|
'Description' => 'Descrizione',
|
||||||
|
'Create product' => 'Aggiungi prodotto',
|
||||||
|
'Barcode(s)' => 'Codice a barre',
|
||||||
|
'Minimum stock amount' => 'Quantità minima',
|
||||||
|
'Default best before days' => 'Data di scadenza standard in giorni',
|
||||||
|
'Quantity unit purchase' => 'Unità di acquisto',
|
||||||
|
'Quantity unit stock' => 'Unità di dispensa',
|
||||||
|
'Factor purchase to stock quantity unit' => 'Fattore di conversione tra quantità di acquisto e di dispensa',
|
||||||
|
'Create location' => 'Aggiungi posizione',
|
||||||
|
'Create quantity unit' => 'Aggiungi unità di misura',
|
||||||
|
'Period type' => 'Tipo di ripetizione',
|
||||||
|
'Period days' => 'Periodo in giorni',
|
||||||
|
'Create chore' => 'Aggiungi abitudine',
|
||||||
|
'Used in' => 'Usato in',
|
||||||
|
'Create battery' => 'Aggiungi batteria',
|
||||||
|
'Edit battery' => 'Modifica batteria',
|
||||||
|
'Edit chore' => 'Modifica abitudine',
|
||||||
|
'Edit quantity unit' => 'Modifica unità di misura',
|
||||||
|
'Edit product' => 'Modifica prodotto',
|
||||||
|
'Edit location' => 'Modifica posizione',
|
||||||
|
'Record data' => 'Registra dati',
|
||||||
|
'Manage master data' => 'Gestisci dati',
|
||||||
|
'This will apply to added products' => 'Verrà applicato ai prodotti aggiunti',
|
||||||
|
'never' => 'mai',
|
||||||
|
'Add products that are below defined min. stock amount' => 'Aggiungi prodotti sotti il limite minimo',
|
||||||
|
'For purchases this amount of days will be added to today for the best before date suggestion' => 'Questo numero di giorni verrà aggiunto alla data di acquisto per la data di scadenza',
|
||||||
|
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Questo significa che 1 #1 acquistato diventerà #2 #3 in dispensa',
|
||||||
|
'Login' => 'Login',
|
||||||
|
'Username' => 'Username',
|
||||||
|
'Password' => 'Password',
|
||||||
|
'Invalid credentials, please try again' => 'Credenziali non valide, per favore riprova',
|
||||||
|
'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?',
|
||||||
|
'Yes' => 'Si',
|
||||||
|
'No' => 'No',
|
||||||
|
'Are you sure to delete chore "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?',
|
||||||
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?',
|
||||||
|
'Create or assign product' => 'Aggiungi o assegna prodotto',
|
||||||
|
'Cancel' => 'Annulla',
|
||||||
|
'Add as new product' => 'Aggiungi come nuovo prodotto',
|
||||||
|
'Add as barcode to existing product' => 'Assegna il codice a barre ad un prodotto',
|
||||||
|
'Add as new product and prefill barcode' => 'Aggiungi come nuovo prodotto ed assegna il codice a barre',
|
||||||
|
'Are you sure to delete quantity unit "#1"?' => 'Sei sicuro di voler eliminare l\'unità di misura "#1"?',
|
||||||
|
'Are you sure to delete product "#1"?' => 'Sei sicuro di voler eliminare il prodotto "#1"?',
|
||||||
|
'Are you sure to delete location "#1"?' => 'Sei sicuro di voler eliminare la posizione "#1"?',
|
||||||
|
'Manage API keys' => 'Gestisci le chiavi API',
|
||||||
|
'REST API & data model documentation' => 'REST API & Documentazione del modello di dati',
|
||||||
|
'API keys' => 'Chiavi API',
|
||||||
|
'Create new API key' => 'Crea nuova chiave API',
|
||||||
|
'API key' => 'Chiave API',
|
||||||
|
'Expires' => 'Scade il',
|
||||||
|
'Created' => 'Creata il',
|
||||||
|
'This product is not in stock' => 'Questo prodotto non è in dispensa',
|
||||||
|
'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa',
|
||||||
|
'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa',
|
||||||
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente',
|
||||||
|
'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa',
|
||||||
|
'About grocy' => 'Riguardo grocy',
|
||||||
|
'Close' => 'Chiudi',
|
||||||
|
'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni',
|
||||||
|
'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate',
|
||||||
|
'#1 chores are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
|
||||||
|
'#1 chores are overdue to be done' => '#1 abitudini da eseguire',
|
||||||
|
'Released on' => 'Rilasciato il',
|
||||||
|
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
|
||||||
|
'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3',
|
||||||
|
'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3',
|
||||||
|
'Tracked execution of chore #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2',
|
||||||
|
'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2',
|
||||||
|
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
|
||||||
|
'All' => 'Tutto',
|
||||||
|
'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1',
|
||||||
|
'Track execution of chore #1' => 'Registra l\'esecuzione dell\'abitudine #1',
|
||||||
|
'Filter by location' => 'Filtra per posizione',
|
||||||
|
'Search' => 'Cerca',
|
||||||
|
'Not logged in' => 'Non autenticato',
|
||||||
|
'You have to select a product' => 'Devi selezionare un prodotto',
|
||||||
|
'You have to select a chore' => 'Devi selezionare un\'abitudine',
|
||||||
|
'You have to select a battery' => 'Devi selezionare una batteria',
|
||||||
|
'A name is required' => 'Inserisci un nome',
|
||||||
|
'A location is required' => 'Inserisci la posizione',
|
||||||
|
'The amount cannot be lower than #1' => 'La quantità non può essere minore di #1',
|
||||||
|
'This cannot be negative' => 'Il numero non può essere negativo',
|
||||||
|
'A quantity unit is required' => 'Inserisci un\'unità di misura',
|
||||||
|
'A period type is required' => 'Seleziona un tipo di ripetizione',
|
||||||
|
|
||||||
|
//Constants
|
||||||
|
'manually' => 'Manualmente',
|
||||||
|
'dynamic-regular' => 'Regolatore dinamico',
|
||||||
|
|
||||||
|
//Technical component translations
|
||||||
|
'timeago_locale' => 'it',
|
||||||
|
'timeago_nan' => 'NaN anni fa',
|
||||||
|
'moment_locale' => 'it',
|
||||||
|
'datatables_localization' => '{"sEmptyTable":"Nessun dato disponibile","sInfo":"Mostrando da _START_ a _END_ di _TOTAL_ voci","sInfoEmpty":"Mostrando da 0 a 0 di 0 voci","sInfoFiltered":"(Filtrato da _MAX_ voci totali)","sInfoPostFix":"","sInfoThousands":",","sLengthMenu":"Mostra _MENU_ voci","sLoadingRecords":"Caricando...","sProcessing":"Calcolando...","sSearch":"Cerca:","sZeroRecords":"Nessun risultato trovato","oPaginate":{"sFirst":"Prima","sLast":"Ultima","sNext":"Prossima","sPrevious":"Precedente"},"oAria":{"sSortAscending":": ordine crescente","sSortDescending":": ordine decrescente"}}',
|
||||||
|
'summernote_locale' => 'it-IT',
|
||||||
|
|
||||||
|
//Demo data
|
||||||
|
'Cookies' => 'Biscotti',
|
||||||
|
'Chocolate' => 'Cioccolato',
|
||||||
|
'Pantry' => 'Vorratskammer',
|
||||||
|
'Candy cupboard' => 'Süßigkeitenschrank',
|
||||||
|
'Tinned food cupboard' => 'Konservenschrank',
|
||||||
|
'Fridge' => 'Kühlschrank',
|
||||||
|
'Piece' => 'Pezzo',
|
||||||
|
'Pieces' => 'Pezzi',
|
||||||
|
'Pack' => 'Pacco',
|
||||||
|
'Packs' => 'Pacchi',
|
||||||
|
'Glass' => 'Bicchiere',
|
||||||
|
'Glasses' => 'Bicchieri',
|
||||||
|
'Tin' => 'Scatola',
|
||||||
|
'Tins' => 'Scatole',
|
||||||
|
'Can' => 'Lattina',
|
||||||
|
'Cans' => 'Lattine',
|
||||||
|
'Bunch' => 'Cespo',
|
||||||
|
'Bunches' => 'Cespi',
|
||||||
|
'Gummy bears' => 'Caramelle',
|
||||||
|
'Crisps' => 'Patatine',
|
||||||
|
'Eggs' => 'Uova',
|
||||||
|
'Noodles' => 'Spaghetti',
|
||||||
|
'Pickles' => 'Marmellata',
|
||||||
|
'Gulash soup' => 'Dado',
|
||||||
|
'Yogurt' => 'Yogurt',
|
||||||
|
'Cheese' => 'Parmigiano',
|
||||||
|
'Cold cuts' => 'Pancetta',
|
||||||
|
'Paprika' => 'Pepe',
|
||||||
|
'Cucumber' => 'Zucchine',
|
||||||
|
'Radish' => 'Radicchio',
|
||||||
|
'Tomato' => 'Pomodori',
|
||||||
|
'Changed towels in the bathroom' => 'Cambiare asciugamani in bagno',
|
||||||
|
'Cleaned the kitchen floor' => 'Pulire la cucina',
|
||||||
|
'Warranty ends' => 'Scadenza dalla garanzia',
|
||||||
|
'TV remote control' => 'Telecomando',
|
||||||
|
'Alarm clock' => 'Sveglia',
|
||||||
|
'Heat remote control' => 'Termostato'
|
||||||
|
);
|
366
localization/no.php
Normal file
366
localization/no.php
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Stock overview' => 'Husholdning',
|
||||||
|
'#1 products expiring within the next #2 days' => '#1 Produkt som går ut på dato innen de neste #2 dagene',
|
||||||
|
'#1 products are already expired' => '#1 Produkt som har gått ut på dato',
|
||||||
|
'#1 products are below defined min. stock amount' => '#1 Produkt under minimum husholdningsnivå',
|
||||||
|
'Product' => 'Produkt',
|
||||||
|
'Amount' => 'Antall',
|
||||||
|
'Next best before date' => 'Kommende best før dato',
|
||||||
|
'Logout' => 'Logg ut',
|
||||||
|
'Chores overview' => 'Oversikt husarbeid',
|
||||||
|
'Batteries overview' => 'Oversikt batteri',
|
||||||
|
'Purchase' => 'Innkjøp',
|
||||||
|
'Consume' => 'Forbruk produkt',
|
||||||
|
'Inventory' => 'Endre husholdning',
|
||||||
|
'Shopping list' => 'Handleliste',
|
||||||
|
'Chore tracking' => 'Logge husarbeid',
|
||||||
|
'Battery tracking' => 'Batteri ladesyklus',
|
||||||
|
'Products' => 'Produkter',
|
||||||
|
'Locations' => 'Lokasjoner',
|
||||||
|
'Quantity units' => 'Forpakning',
|
||||||
|
'Chores' => 'Husarbeid',
|
||||||
|
'Batteries' => 'Batterier',
|
||||||
|
'Chore' => 'Husarbeid',
|
||||||
|
'Next estimated tracking' => 'Neste handling',
|
||||||
|
'Last tracked' => 'Sist logget',
|
||||||
|
'Battery' => 'Batteri',
|
||||||
|
'Last charged' => 'Sist ladet',
|
||||||
|
'Next planned charge cycle' => 'Neste planlagte ladesyklus',
|
||||||
|
'Best before' => 'Best før',
|
||||||
|
'OK' => 'OK',
|
||||||
|
'Product overview' => 'Produkt oversikt',
|
||||||
|
'Stock quantity unit' => 'Forpakningstype i husholdningen',
|
||||||
|
'Stock amount' => 'Husholdning',
|
||||||
|
'Last purchased' => 'Sist kjøpt',
|
||||||
|
'Last used' => 'Sist brukt',
|
||||||
|
'Spoiled' => 'Produkt har gått ut på dato',
|
||||||
|
'Barcode lookup is disabled' => 'Strekkodesøk deaktivert',
|
||||||
|
'will be added to the list of barcodes for the selected product on submit' => 'Blir lagt til liste over strekkoder når produkt blir lagt inn.',
|
||||||
|
'New amount' => 'Nytt antall',
|
||||||
|
'Note' => 'Info',
|
||||||
|
'Tracked time' => 'Tid utført/ ladet',
|
||||||
|
'Chore overview' => 'Oversikt husarbeid',
|
||||||
|
'Tracked count' => 'Antall utførelser/ ladninger',
|
||||||
|
'Battery overview' => 'Batteri oversikt',
|
||||||
|
'Charge cycles count' => 'Antall ladesykluser',
|
||||||
|
'Create shopping list item' => 'Opprett handelisteoppføring',
|
||||||
|
'Edit shopping list item' => 'Endre på handlelistoppføring',
|
||||||
|
'#1 units were automatically added and will apply in addition to the amount entered here' => '#1 enheter ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
|
||||||
|
'Save' => 'Lagre',
|
||||||
|
'Add' => 'Legg til',
|
||||||
|
'Name' => 'Navn',
|
||||||
|
'Location' => 'Lokasjon',
|
||||||
|
'Min. stock amount' => 'Minimums antall for husholdingen',
|
||||||
|
'QU purchase' => 'Forpakingsfaktor innkjøp',
|
||||||
|
'QU stock' => 'Forpakingsfaktor husholdning',
|
||||||
|
'QU factor' => 'Forpakingsfaktor',
|
||||||
|
'Description' => 'Beskrivelse',
|
||||||
|
'Create product' => 'Opprett produkt',
|
||||||
|
'Barcode(s)' => 'Strekkode(r)',
|
||||||
|
'Minimum stock amount' => 'Minimums antall for husholdningen',
|
||||||
|
'Default best before days' => 'Standard antall dager best før',
|
||||||
|
'Quantity unit purchase' => 'Forpakning kjøpt',
|
||||||
|
'Quantity unit stock' => 'Forpakning husholdning',
|
||||||
|
'Factor purchase to stock quantity unit' => 'Innkjøpsfaktor for forpakning',
|
||||||
|
'Create location' => 'Opprett lokasjon',
|
||||||
|
'Create quantity unit' => 'Opprett forpakning',
|
||||||
|
'Period type' => 'Gjentakelse',
|
||||||
|
'Period days' => 'Antall dager for gjentakelse',
|
||||||
|
'Create chore' => 'Opprett husarbeid oppgave',
|
||||||
|
'Used in' => 'Brukt',
|
||||||
|
'Create battery' => 'Opprett batteri',
|
||||||
|
'Edit battery' => 'Endre batteri',
|
||||||
|
'Edit chore' => 'Endre husarbeid oppgave',
|
||||||
|
'Edit quantity unit' => 'Endre forpakning',
|
||||||
|
'Edit product' => 'Endre produkt',
|
||||||
|
'Edit location' => 'Endre lokasjon',
|
||||||
|
'Record data' => 'Logg handlinger',
|
||||||
|
'Manage master data' => 'Administrer masterdata',
|
||||||
|
'This will apply to added products' => 'Dette vil gjelde for produkt som blir lagt til',
|
||||||
|
'never' => 'aldri',
|
||||||
|
'Add products that are below defined min. stock amount' => 'Legg til produkt som er under minimumsnivå for husholdningen',
|
||||||
|
'For purchases this amount of days will be added to today for the best before date suggestion' => 'For innkjøp vil dette antallet dager legges til bestfør forslaget',
|
||||||
|
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Dette betyr at 1 #1 innkjøp vil bli omgjort til #2 #3 husholdning',
|
||||||
|
'Login' => 'Logg inn',
|
||||||
|
'Username' => 'Brukernavn',
|
||||||
|
'Password' => 'Passord',
|
||||||
|
'Invalid credentials, please try again' => 'Feil brukernavn og/eller passord, prøv igjen',
|
||||||
|
'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette batteri "#1"?',
|
||||||
|
'Yes' => 'Ja',
|
||||||
|
'No' => 'Nei',
|
||||||
|
'Are you sure to delete chore "#1"?' => 'Er du sikker på du ønsker å slette husarbeid oppgave "#1"?',
|
||||||
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?',
|
||||||
|
'Create or assign product' => 'Opprett eller tildel til et produkt',
|
||||||
|
'Cancel' => 'Avbryt',
|
||||||
|
'Add as new product' => 'Legg til som nytt produkt',
|
||||||
|
'Add as barcode to existing product' => 'Legg til strekkode til allerede eksisterende produkt',
|
||||||
|
'Add as new product and prefill barcode' => 'Legg til som nytt produkt med forhåndsfylt strekkode',
|
||||||
|
'Are you sure to delete quantity unit "#1"?' => 'Er du sikker du ønsker å slette forpakning "#1"?',
|
||||||
|
'Are you sure to delete product "#1"?' => 'Er du sikker du ønsker å slette produkt "#1"?',
|
||||||
|
'Are you sure to delete location "#1"?' => 'Er du sikker du ønsker å slette lokasjon "#1"?',
|
||||||
|
'Manage API keys' => 'Administrer API-Keys',
|
||||||
|
'REST API & data model documentation' => 'REST-API & Datamodell Dokumentasjon',
|
||||||
|
'API keys' => 'API-Keys',
|
||||||
|
'Create new API key' => 'Opprett ny API-Key',
|
||||||
|
'API key' => 'API-Key',
|
||||||
|
'Expires' => 'Går ut',
|
||||||
|
'Created' => 'Opprettet',
|
||||||
|
'This product is not in stock' => 'Dette produktet er ikke i husholdningen',
|
||||||
|
'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen',
|
||||||
|
'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen',
|
||||||
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husarbeid oppgaven er logget #1 dag etter den sist var logget',
|
||||||
|
'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen',
|
||||||
|
'About grocy' => 'Om Grocy',
|
||||||
|
'Close' => 'Lukk',
|
||||||
|
'#1 batteries are due to be charged within the next #2 days' => '#1 batteri må lades innen de #2 neste dagene',
|
||||||
|
'#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp',
|
||||||
|
'#1 chores are due to be done within the next #2 days' => '#1 husarbeids oppgaver skal gjøres inne de #2 neste dagene',
|
||||||
|
'#1 chores are overdue to be done' => '#1 husarbeids oppgaver har gått over fristen for utførelse',
|
||||||
|
'Released on' => 'Utgitt',
|
||||||
|
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
|
||||||
|
'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen',
|
||||||
|
'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' => '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',
|
||||||
|
'Filter by location' => 'Filtrér etter lokasjon',
|
||||||
|
'Search' => 'Søk',
|
||||||
|
'Not logged in' => 'Ikke logget inn',
|
||||||
|
'You have to select a product' => 'Du må velge et produkt',
|
||||||
|
'You have to select a chore' => 'Du må velge en husarbeids oppgave',
|
||||||
|
'You have to select a battery' => 'Du må velge et batteri',
|
||||||
|
'A name is required' => 'Vennligst fyll inn et navn',
|
||||||
|
'A location is required' => 'En lokasjon kreves',
|
||||||
|
'The amount cannot be lower than #1' => 'Antallet kan ikke være lavere enn #1',
|
||||||
|
'This cannot be negative' => 'Dette kan ikke være negativt',
|
||||||
|
'A quantity unit is required' => 'Forpakning antall/størrelse kreves',
|
||||||
|
'A period type is required' => 'En periodetype kreves',
|
||||||
|
'A best before date is required and must be later than today' => 'En best før dato kreves, denne må være senere enn i dag',
|
||||||
|
'Settings' => 'Innstillinger',
|
||||||
|
'This can only be before now' => 'Dette kan kun være før nå',
|
||||||
|
'Calendar' => 'Kalender',
|
||||||
|
'Recipes' => 'Oppskrifter',
|
||||||
|
'Edit recipe' => 'Endre oppskrift',
|
||||||
|
'New recipe' => 'Ny oppskrift',
|
||||||
|
'Ingredients list' => 'Liste over ingredienser',
|
||||||
|
'Add recipe ingredient' => 'Legg ingrediens til oppskrift',
|
||||||
|
'Edit recipe ingredient' => 'Endre ingrediens i oppskrift',
|
||||||
|
'Are you sure to delete recipe "#1"?' => 'Er du sikker du ønsker å slette oppskrift "#1"?',
|
||||||
|
'Are you sure to delete recipe ingredient "#1"?' => 'Er du sikker du ønsker å slette ingrediens "#1" fra oppskriften?',
|
||||||
|
'Are you sure to empty the shopping list?' => 'Er du sikker du ønsker å slette handlelisten?',
|
||||||
|
'Clear list' => 'Slett handleliste',
|
||||||
|
'Requirements fulfilled' => 'Har jeg alt jeg trenger for denne oppskriften?',
|
||||||
|
'Put missing products on shopping list' => 'Legg manglende produkter til handlelisten',
|
||||||
|
'Not enough in stock, #1 ingredients missing' => 'Ikke nok i husholdningen, #1 ingredienser mangler',
|
||||||
|
'Enough in stock' => 'Nok i husholdningen',
|
||||||
|
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Ikke nok i husholdningen, #1 ingrediens mangler, men denne er på handelisten',
|
||||||
|
'Expand to fullscreen' => 'Full skjerm',
|
||||||
|
'Ingredients' => 'Ingredienser',
|
||||||
|
'Preparation' => 'Forberedelse / Slik gjør du',
|
||||||
|
'Recipe' => 'Oppskrift',
|
||||||
|
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Ikke nok i husholdningen, mangler #1, er #2 på handlelisten',
|
||||||
|
'Show notes' => 'Vis notater',
|
||||||
|
'Put missing amount on shopping list' => 'Legg manglende til handlelisten',
|
||||||
|
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Er du sikker du ønsker å legge alle manglende ingredienser til oppskrift "#1"?',
|
||||||
|
'Added for recipe #1' => 'Lagt til fra oppskrift "#1"',
|
||||||
|
'Manage users' => 'Administrer brukere',
|
||||||
|
'User' => 'Bruker',
|
||||||
|
'Users' => 'Brukere',
|
||||||
|
'Are you sure to delete user "#1"?' => 'Er du sikker på du ønsker å slette bruker, "#1"?',
|
||||||
|
'Create user' => 'Legg til bruker',
|
||||||
|
'Edit user' => 'Endre på bruker',
|
||||||
|
'First name' => 'Fornavn',
|
||||||
|
'Last name' => 'Etternavn',
|
||||||
|
'A username is required' => 'Et brukernavn er nødvendig',
|
||||||
|
'Confirm password' => 'Bekreft passord',
|
||||||
|
'Passwords do not match' => 'Passord er ikke like',
|
||||||
|
'Change password' => 'Endre passord',
|
||||||
|
'Done by' => 'Utført av',
|
||||||
|
'Last done by' => 'Sist utført av',
|
||||||
|
'Unknown' => 'Ukjent',
|
||||||
|
'Filter by chore' => 'Filtrér husarbeid',
|
||||||
|
'Chores journal' => 'Statistikk husarbeid',
|
||||||
|
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
|
||||||
|
'Charge cycle interval (days)' => 'Ladesyklysintervall (dager)',
|
||||||
|
'Last price' => 'Siste pris',
|
||||||
|
'Price history' => 'Prishistorikk',
|
||||||
|
'No price history available' => 'Ingen prishistorikk tilgjengelig',
|
||||||
|
'Price' => 'Pris',
|
||||||
|
'in #1 per purchase quantity unit' => 'I #1 per kjøpt forpakning ',
|
||||||
|
'The price cannot be lower than #1' => 'Prisen kan ikke være lavere enn #1',
|
||||||
|
'#1 product expires within the next #2 days' => '#1 Produkt går ut på dato innen de #2 neste dagene',
|
||||||
|
'#1 product is already expired' => '#1 Produkt er allerede gått ut på dato',
|
||||||
|
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
|
||||||
|
'Unit' => 'Enhet',
|
||||||
|
'Units' => 'Enheter',
|
||||||
|
'#1 chore is due to be done within the next #2 days' => '#1 husarbeid oppgave skal gjøres inne de #2 neste dagene',
|
||||||
|
'#1 chore is overdue to be done' => '#1 husarbeid oppgave har gått over fristen for utførelse',
|
||||||
|
'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager',
|
||||||
|
'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades',
|
||||||
|
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
|
||||||
|
'in singular form' => 'I entall',
|
||||||
|
'in plural form' => 'I flertall',
|
||||||
|
'Never expires' => 'Går ikke ut på dato',
|
||||||
|
'This cannot be lower than #1' => 'Dette kan ikke være lavere enn #1',
|
||||||
|
'-1 means that this product never expires' => '-1 Betyr at dette produktet aldri går ut på dato',
|
||||||
|
'Quantity unit' => 'Forpakning',
|
||||||
|
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Ønsker du å bruke mindre enn forpakningsstørrelse?',
|
||||||
|
'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 "Ønsker du å bruke mindre enn forpakningsstørrelse?" 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' => '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',
|
||||||
|
'Tasks' => 'Oppgaver',
|
||||||
|
'Show done tasks' => 'Vis ferdige oppgaver',
|
||||||
|
'Task' => 'Oppgave',
|
||||||
|
'Due' => 'Forfall',
|
||||||
|
'Assigned to' => 'Tildelt',
|
||||||
|
'Mark task "#1" as completed' => 'Merk oppgave "#1" som ferdig',
|
||||||
|
'Uncategorized' => 'Mangler kategori',
|
||||||
|
'Task categories' => 'Oppgave kategorier',
|
||||||
|
'Create task' => 'Opprett en oppgave',
|
||||||
|
'A due date is required' => 'En forfallsdato kreves',
|
||||||
|
'Category' => 'Kategori',
|
||||||
|
'Edit task' => 'Endre oppgave',
|
||||||
|
'Are you sure to delete task "#1"?' => 'Er du sikker du ønsker slette oppgave "#1"?',
|
||||||
|
'#1 task is due to be done within the next #2 days' => '#1 oppgave har utførelse forfall innen de neste #2 dagene',
|
||||||
|
'#1 tasks are due to be done within the next #2 days' => '#1 oppgaver har utførelse forfall innen de neste #2 dagene',
|
||||||
|
'#1 task is overdue to be done' => '#1 oppgave har forfalt utførelse dato',
|
||||||
|
'#1 tasks are overdue to be done' => '#1 oppgaver har forfalt utførelse dato',
|
||||||
|
'Edit task category' => 'Endre oppgave kategori',
|
||||||
|
'Create task category' => 'Opprett oppgave kategori',
|
||||||
|
'Product groups' => 'Produktgrupper',
|
||||||
|
'Ungrouped' => 'Mangler gruppe',
|
||||||
|
'Create product group' => 'Opprett produkt gruppe',
|
||||||
|
'Edit product group' => 'Endre produkt gruppe',
|
||||||
|
'Product group' => 'Produktgruppe',
|
||||||
|
'Are you sure to delete product group "#1"?' => 'Er du sikker du ønsker å slette produktgruppe "#1"?',
|
||||||
|
'Stay logged in permanently' => 'Alltid være innlogget',
|
||||||
|
'When not set, you will get logged out at latest after 30 days' => 'Når den ikke er satt vil du bli logget ut etter 30 dager',
|
||||||
|
'Filter by status' => 'Filtrér etter status',
|
||||||
|
'Below min. stock amount' => 'Under under minimum husholdningsnivå',
|
||||||
|
'Expiring soon' => 'Går snart ut på dato',
|
||||||
|
'Already expired' => 'Utgått på dato',
|
||||||
|
'Due soon' => 'Forfaller snart',
|
||||||
|
'Overdue' => 'Forfalt',
|
||||||
|
'View settings' => 'Se instillinger',
|
||||||
|
'Auto reload on external changes' => 'Automatisk fornying ved ekstern endring',
|
||||||
|
'Enable night mode' => 'Aktiver nattmodus',
|
||||||
|
'Auto enable in time range' => 'Automatisk aktivering i tidsrommet',
|
||||||
|
'From' => 'Fra',
|
||||||
|
'in format' => 'format',
|
||||||
|
'To' => 'Til',
|
||||||
|
'Time range goes over midnight' => 'Tidsrommet går over midnatt',
|
||||||
|
'Product picture' => 'Produktbilde',
|
||||||
|
'No file selected' => 'Ingen fil merket',
|
||||||
|
'If you don\'t select a file, the current picture will not be altered' => 'Hvis du ikke velger et bilde, vil nåværende bilde ikke bli endret',
|
||||||
|
'Current picture' => 'Nåværende bilde',
|
||||||
|
'Delete' => 'Slett',
|
||||||
|
'The current picture will be deleted when you save the product' => 'Nåværende bilde vil bli slettet når du lagrer produktet',
|
||||||
|
'Select file' => 'Velg fil',
|
||||||
|
'Image of product #1' => 'Bilde av produkt #1',
|
||||||
|
'This product cannot be deleted because it is in stock, please remove the stock amount first.' => 'Dette produktet kan ikke slettes fordi det er gjenværende produkter i husholdningen',
|
||||||
|
'Delete not possible' => 'Ikke mulig å slette',
|
||||||
|
'Equipment' => 'Utstyr',
|
||||||
|
'Instruction manual' => 'Instruksjonsmanual',
|
||||||
|
'The selected equipment has no instruction manual' => 'Merket utstyr har ingen instruksjonsmanual',
|
||||||
|
'Notes' => 'Notater',
|
||||||
|
'Edit equipment' => 'Endre utstyr',
|
||||||
|
'Create equipment' => 'Opprett utstyr',
|
||||||
|
'If you don\'t select a file, the current instruction manual will not be altered' => 'Hvis du ikke velger en instruksjonsmanual, vil nåværende instruksjonsmanual ikke bli endret',
|
||||||
|
'Current instruction manual' => 'Nåværende instruksjonsmanual',
|
||||||
|
'No instruction manual available' => 'Ingen instruksjonsmanual tilgjengelig',
|
||||||
|
'The current instruction manual will be deleted when you save the equipment' => 'Nåværende instruksjonsmanual vil bli slettet når du lagrer utstyret',
|
||||||
|
'No picture available' => 'Ingen bilde tilgjengelig',
|
||||||
|
|
||||||
|
//Constants
|
||||||
|
'manually' => 'Manuel',
|
||||||
|
'dynamic-regular' => 'Automatisk',
|
||||||
|
|
||||||
|
//Technical component translations
|
||||||
|
'timeago_locale' => 'no',
|
||||||
|
'timeago_nan' => 'for NaN År',
|
||||||
|
'moment_locale' => 'nb',
|
||||||
|
'datatables_localization' => '{"sEmptyTable":"Det finnes ingen data i tabellen","sInfo":"_START_ fra _END_ til _TOTAL_ skriv","sInfoEmpty":"Ingen data tilgjengelign","sInfoFiltered":"(filtrert fra _MAX_ skriv)","sInfoPostFix":"","sInfoThousands":".","sLengthMenu":"_MENU_ registrer deg","sLoadingRecords":"Laster ..","sProcessing":"Vennligst vent ..","sSearch":"Søk","sZeroRecords":"Ingen oppføringer tilgjengelig","oPaginate":{"sFirst":"Første","sPrevious":"Bakover","sNext":"Neste","sLast":"Siste"},"oAria":{"sSortAscending":": Sortér stigende","sSortDescending":": Sortér synkende"},"select":{"rows":{"0":"klikk på en linje for å velge","1":"1 linje valgt","_":"%d linger valgt"}},"buttons":{"print":"Print","colvis":"Søyle","copy":"Kopi","copyTitle":"Kopier til utklippstavlen","copyKeys":"Trykk <i>ctrl</i> eller <i>⌘</i> + <i>C</i> for å kopiere tabell<br> til utklipptavlen.<br><br>For å avbryte, klikke på meldingen eller trykk på ESC.","copySuccess":{"1":"1 Kolonne kopiert","_":"%d kolonne kopiert"}}}',
|
||||||
|
'summernote_locale' => 'nb-NO',
|
||||||
|
|
||||||
|
//Demo data
|
||||||
|
'Cookies' => 'Cookies',
|
||||||
|
'Chocolate' => 'Sjokolade',
|
||||||
|
'Pantry' => 'Spiskammers',
|
||||||
|
'Candy cupboard' => 'Godteriskapet',
|
||||||
|
'Tinned food cupboard' => 'Boksematskapet',
|
||||||
|
'Fridge' => 'Kjøleskapet',
|
||||||
|
'Piece' => 'Ett',
|
||||||
|
'Pieces' => 'Flere',
|
||||||
|
'Pack' => 'Pakke',
|
||||||
|
'Packs' => 'Pakker',
|
||||||
|
'Glass' => 'Glass',
|
||||||
|
'Glasses' => 'Glass',
|
||||||
|
'Tin' => 'Hermetikkboks',
|
||||||
|
'Tins' => 'Hermetikkbokser',
|
||||||
|
'Can' => 'Boks',
|
||||||
|
'Cans' => 'Bokser',
|
||||||
|
'Bunch' => 'Klase',
|
||||||
|
'Bunches' => 'Klaser',
|
||||||
|
'Gummy bears' => 'Vingummibjørner',
|
||||||
|
'Crisps' => 'Chips',
|
||||||
|
'Eggs' => 'Egg',
|
||||||
|
'Noodles' => 'Nuddler',
|
||||||
|
'Pickles' => 'Sur agurk',
|
||||||
|
'Gulash soup' => 'Gulasj suppe',
|
||||||
|
'Yogurt' => 'Yoghurt',
|
||||||
|
'Cheese' => 'Ost',
|
||||||
|
'Cold cuts' => 'Kjøttpålegg',
|
||||||
|
'Paprika' => 'Paprika',
|
||||||
|
'Cucumber' => 'Agurk',
|
||||||
|
'Radish' => 'Reddik',
|
||||||
|
'Tomato' => 'Tomat',
|
||||||
|
'Changed towels in the bathroom' => 'Bytt handklær på badet',
|
||||||
|
'Cleaned the kitchen floor' => 'Vasket kjøkkengulvet',
|
||||||
|
'Warranty ends' => 'Garanti utgår',
|
||||||
|
'TV remote control' => 'Fjernkontroll for TV',
|
||||||
|
'Alarm clock' => 'Alarmklokke',
|
||||||
|
'Heat remote control' => 'Fjernkontroll for termostat',
|
||||||
|
'Lawn mowed in the garden' => 'Kuttet gresset i hagen',
|
||||||
|
'Some good snacks' => 'Noen gode snacks',
|
||||||
|
'Pizza dough' => 'Pizzadeig',
|
||||||
|
'Sieved tomatoes' => 'Tomatpuré',
|
||||||
|
'Salami' => 'Salami',
|
||||||
|
'Toast' => 'Ristet brød',
|
||||||
|
'Minced meat' => 'Kjøttdeig',
|
||||||
|
'Pizza' => 'Pizza',
|
||||||
|
'Spaghetti bolognese' => 'Spaghetti Bolognese',
|
||||||
|
'Sandwiches' => 'Smørbrød',
|
||||||
|
'English' => 'Engelsk',
|
||||||
|
'German' => 'Tysk',
|
||||||
|
'Italian' => 'Italiensk',
|
||||||
|
'Demo in different language' => 'Demo i annet språk',
|
||||||
|
'This is the note content of the recipe ingredient' => 'Dette er notisen for ingrediensen i oppskriften',
|
||||||
|
'Demo User' => 'Demo Bruker',
|
||||||
|
'Gram' => 'Gram',
|
||||||
|
'Grams' => 'Gram',
|
||||||
|
'Flour' => 'Mel',
|
||||||
|
'Pancakes' => 'Pannekaker',
|
||||||
|
'Sugar' => 'Sukker',
|
||||||
|
'Home' => 'Hus',
|
||||||
|
'Life' => 'Livstil',
|
||||||
|
'Projects' => 'Projekter',
|
||||||
|
'Repair the garage door' => 'Reparere garasjedøren',
|
||||||
|
'Fork and improve grocy' => 'Fork og forbedre grocy',
|
||||||
|
'Find a solution for what to do when I forget the door keys' => 'Finne på løsning for hva jeg skal gjøre når jeg mister dørnøklene',
|
||||||
|
'Sweets' => 'Godteri',
|
||||||
|
'Bakery products' => 'Bakevarer',
|
||||||
|
'Tinned food' => 'Boksemat',
|
||||||
|
'Butchery products' => 'Kjøtt/ Ferskvare',
|
||||||
|
'Vegetables/Fruits' => 'Frukt/ Grønnsaker',
|
||||||
|
'Refrigerated products' => 'Frysedisk',
|
||||||
|
'Coffee machine' => 'Kaffetrakter',
|
||||||
|
'Dishwasher' => 'Oppvaskmaskin'
|
||||||
|
);
|
@@ -22,8 +22,9 @@ class ApiKeyAuthMiddleware extends BaseMiddleware
|
|||||||
$route = $request->getAttribute('route');
|
$route = $request->getAttribute('route');
|
||||||
$routeName = $route->getName();
|
$routeName = $route->getName();
|
||||||
|
|
||||||
if ($this->ApplicationService->IsDemoInstallation())
|
if (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
|
||||||
{
|
{
|
||||||
|
define('GROCY_AUTHENTICATED', true);
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -45,10 +46,23 @@ class ApiKeyAuthMiddleware extends BaseMiddleware
|
|||||||
|
|
||||||
if (!$validSession && !$validApiKey)
|
if (!$validSession && !$validApiKey)
|
||||||
{
|
{
|
||||||
|
define('GROCY_AUTHENTICATED', false);
|
||||||
$response = $response->withStatus(401);
|
$response = $response->withStatus(401);
|
||||||
}
|
}
|
||||||
else
|
elseif ($validApiKey)
|
||||||
{
|
{
|
||||||
|
$user = $apiKeyService->GetUserByApiKey($request->getHeaderLine($this->ApiKeyHeaderName));
|
||||||
|
define('GROCY_AUTHENTICATED', true);
|
||||||
|
define('GROCY_USER_ID', $user->id);
|
||||||
|
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
elseif ($validSession)
|
||||||
|
{
|
||||||
|
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
|
||||||
|
define('GROCY_AUTHENTICATED', true);
|
||||||
|
define('GROCY_USER_ID', $user->id);
|
||||||
|
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Grocy\Middleware;
|
|
||||||
|
|
||||||
class CliMiddleware extends BaseMiddleware
|
|
||||||
{
|
|
||||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
|
||||||
{
|
|
||||||
if (PHP_SAPI !== 'cli')
|
|
||||||
{
|
|
||||||
$response->write('Please call this only from CLI');
|
|
||||||
return $response->withHeader('Content-Type', 'text/plain')->withStatus(400);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$response = $next($request, $response);
|
|
||||||
return $response->withHeader('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,6 +7,14 @@ class JsonMiddleware extends BaseMiddleware
|
|||||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
{
|
{
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
|
|
||||||
|
if ($response->hasHeader('Content-Disposition'))
|
||||||
|
{
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return $response->withHeader('Content-Type', 'application/json');
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
namespace Grocy\Middleware;
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
use \Grocy\Services\SessionService;
|
use \Grocy\Services\SessionService;
|
||||||
|
use \Grocy\Services\LocalizationService;
|
||||||
|
|
||||||
class SessionAuthMiddleware extends BaseMiddleware
|
class SessionAuthMiddleware extends BaseMiddleware
|
||||||
{
|
{
|
||||||
@@ -18,23 +19,41 @@ class SessionAuthMiddleware extends BaseMiddleware
|
|||||||
{
|
{
|
||||||
$route = $request->getAttribute('route');
|
$route = $request->getAttribute('route');
|
||||||
$routeName = $route->getName();
|
$routeName = $route->getName();
|
||||||
|
$sessionService = new SessionService();
|
||||||
|
|
||||||
if ($routeName === 'root' || $this->ApplicationService->IsDemoInstallation())
|
if ($routeName === 'root')
|
||||||
{
|
{
|
||||||
define('AUTHENTICATED', $this->ApplicationService->IsDemoInstallation());
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
elseif (GROCY_IS_DEMO_INSTALL || GROCY_IS_EMBEDDED_INSTALL)
|
||||||
|
{
|
||||||
|
$user = $sessionService->GetDefaultUser();
|
||||||
|
define('GROCY_AUTHENTICATED', true);
|
||||||
|
define('GROCY_USER_USERNAME', $user->username);
|
||||||
|
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$sessionService = new SessionService();
|
|
||||||
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
|
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
|
||||||
{
|
{
|
||||||
define('AUTHENTICATED', false);
|
define('GROCY_AUTHENTICATED', false);
|
||||||
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
|
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
define('AUTHENTICATED', $routeName !== 'login');
|
if ($routeName !== 'login')
|
||||||
|
{
|
||||||
|
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
|
||||||
|
define('GROCY_AUTHENTICATED', true);
|
||||||
|
define('GROCY_USER_USERNAME', $user->username);
|
||||||
|
define('GROCY_USER_ID', $user->id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
define('GROCY_AUTHENTICATED', false);
|
||||||
|
}
|
||||||
|
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,4 +3,3 @@ AS
|
|||||||
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||||
FROM stock
|
FROM stock
|
||||||
GROUP BY product_id
|
GROUP BY product_id
|
||||||
ORDER BY MIN(best_before_date) ASC
|
|
||||||
|
@@ -3,4 +3,3 @@ AS
|
|||||||
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
||||||
FROM habits_log
|
FROM habits_log
|
||||||
GROUP BY habit_id
|
GROUP BY habit_id
|
||||||
ORDER BY MAX(tracked_time) DESC
|
|
||||||
|
@@ -3,4 +3,3 @@ AS
|
|||||||
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
||||||
FROM battery_charge_cycles
|
FROM battery_charge_cycles
|
||||||
GROUP BY battery_id
|
GROUP BY battery_id
|
||||||
ORDER BY MAX(tracked_time) DESC
|
|
||||||
|
50
migrations/0025.sql
Normal file
50
migrations/0025.sql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
CREATE TABLE recipes (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE recipes_pos (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
recipe_id INTEGER NOT NULL,
|
||||||
|
product_id INTEGER NOT NULL,
|
||||||
|
amount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW recipes_fulfillment
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
r.id AS recipe_id,
|
||||||
|
rp.id AS recipe_pos_id,
|
||||||
|
rp.product_id AS product_id,
|
||||||
|
rp.amount AS recipe_amount,
|
||||||
|
IFNULL(sc.amount, 0) AS stock_amount,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) - IFNULL(rp.amount, 0) < 0 THEN ABS(IFNULL(sc.amount, 0) - IFNULL(rp.amount, 0)) ELSE 0 END AS missing_amount,
|
||||||
|
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list
|
||||||
|
FROM recipes r
|
||||||
|
JOIN recipes_pos rp
|
||||||
|
ON r.id = rp.recipe_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT product_id, SUM(amount + amount_autoadded) AS amount
|
||||||
|
FROM shopping_list
|
||||||
|
GROUP BY product_id) sl
|
||||||
|
ON rp.product_id = sl.product_id
|
||||||
|
LEFT JOIN stock_current sc
|
||||||
|
ON rp.product_id = sc.product_id;
|
||||||
|
|
||||||
|
CREATE VIEW recipes_fulfillment_sum
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
r.id AS recipe_id,
|
||||||
|
IFNULL(MIN(rf.need_fulfilled), 1) AS need_fulfilled,
|
||||||
|
IFNULL(MIN(rf.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
|
||||||
|
(SELECT COUNT(*) FROM recipes_fulfillment WHERE recipe_id = rf.recipe_id AND need_fulfilled = 0 AND recipe_pos_id IS NOT NULL) AS missing_products_count
|
||||||
|
FROM recipes r
|
||||||
|
LEFT JOIN recipes_fulfillment rf
|
||||||
|
ON rf.recipe_id = r.id
|
||||||
|
GROUP BY r.id;
|
20
migrations/0026.sql
Normal file
20
migrations/0026.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
first_name TEXT,
|
||||||
|
last_name TEXT,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP TABLE sessions;
|
||||||
|
|
||||||
|
CREATE TABLE sessions (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
session_key TEXT NOT NULL UNIQUE,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
expires DATETIME,
|
||||||
|
last_used DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
||||||
|
|
24
migrations/0027.php
Normal file
24
migrations/0027.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// This is executed inside DatabaseMigrationService class/context
|
||||||
|
|
||||||
|
$db = $this->DatabaseService->GetDbConnection();
|
||||||
|
|
||||||
|
if (defined('GROCY_HTTP_USER'))
|
||||||
|
{
|
||||||
|
// Migrate old user defined in config file to database
|
||||||
|
$newUserRow = $db->users()->createRow(array(
|
||||||
|
'username' => GROCY_HTTP_USER,
|
||||||
|
'password' => password_hash(GROCY_HTTP_PASSWORD, PASSWORD_DEFAULT)
|
||||||
|
));
|
||||||
|
$newUserRow->save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create default user "admin" with password "admin"
|
||||||
|
$newUserRow = $db->users()->createRow(array(
|
||||||
|
'username' => 'admin',
|
||||||
|
'password' => password_hash('admin', PASSWORD_DEFAULT)
|
||||||
|
));
|
||||||
|
$newUserRow->save();
|
||||||
|
}
|
13
migrations/0028.sql
Normal file
13
migrations/0028.sql
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
ALTER TABLE habits_log
|
||||||
|
ADD done_by_user_id INTEGER;
|
||||||
|
|
||||||
|
DROP TABLE api_keys;
|
||||||
|
|
||||||
|
CREATE TABLE api_keys (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
api_key TEXT NOT NULL UNIQUE,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
expires DATETIME,
|
||||||
|
last_used DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
5
migrations/0029.sql
Normal file
5
migrations/0029.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE stock
|
||||||
|
ADD price DECIMAL(15, 2);
|
||||||
|
|
||||||
|
ALTER TABLE stock_log
|
||||||
|
ADD price DECIMAL(15, 2);
|
2
migrations/0030.sql
Normal file
2
migrations/0030.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE quantity_units
|
||||||
|
ADD name_plural TEXT;
|
32
migrations/0031.php
Normal file
32
migrations/0031.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// This is executed inside DatabaseMigrationService class/context
|
||||||
|
|
||||||
|
use \Grocy\Services\LocalizationService;
|
||||||
|
$localizationService = new LocalizationService(GROCY_CULTURE);
|
||||||
|
|
||||||
|
$db = $this->DatabaseService->GetDbConnection();
|
||||||
|
|
||||||
|
if ($db->quantity_units()->count() === 0)
|
||||||
|
{
|
||||||
|
// Create 2 default quantity units
|
||||||
|
$newRow = $db->quantity_units()->createRow(array(
|
||||||
|
'name' => $localizationService->Localize('Piece'),
|
||||||
|
'name_plural' => $localizationService->Localize('Pieces')
|
||||||
|
));
|
||||||
|
$newRow->save();
|
||||||
|
$newRow = $db->quantity_units()->createRow(array(
|
||||||
|
'name' => $localizationService->Localize('Pack'),
|
||||||
|
'name_plural' => $localizationService->Localize('Packs')
|
||||||
|
));
|
||||||
|
$newRow->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($db->locations()->count() === 0)
|
||||||
|
{
|
||||||
|
// Create a default location
|
||||||
|
$newRow = $db->locations()->createRow(array(
|
||||||
|
'name' => $localizationService->Localize('Fridge')
|
||||||
|
));
|
||||||
|
$newRow->save();
|
||||||
|
}
|
20
migrations/0032.sql
Normal file
20
migrations/0032.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
DROP VIEW habits_current;
|
||||||
|
CREATE VIEW habits_current
|
||||||
|
AS
|
||||||
|
SELECT habit_id, MAX(tracked_time) AS last_tracked_time
|
||||||
|
FROM habits_log
|
||||||
|
GROUP BY habit_id;
|
||||||
|
|
||||||
|
DROP VIEW batteries_current;
|
||||||
|
CREATE VIEW batteries_current
|
||||||
|
AS
|
||||||
|
SELECT battery_id, MAX(tracked_time) AS last_tracked_time
|
||||||
|
FROM battery_charge_cycles
|
||||||
|
GROUP BY battery_id;
|
29
migrations/0033.sql
Normal file
29
migrations/0033.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
DROP VIEW habits_current;
|
||||||
|
CREATE VIEW habits_current
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
h.id AS habit_id,
|
||||||
|
MAX(l.tracked_time) AS last_tracked_time,
|
||||||
|
CASE h.period_type
|
||||||
|
WHEN 'manually' THEN '2999-12-31 23:59:59'
|
||||||
|
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
|
||||||
|
END AS next_estimated_execution_time
|
||||||
|
FROM habits h
|
||||||
|
LEFT JOIN habits_log l
|
||||||
|
ON h.id = l.habit_id
|
||||||
|
GROUP BY h.id, h.period_days;
|
||||||
|
|
||||||
|
DROP VIEW batteries_current;
|
||||||
|
CREATE VIEW batteries_current
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
b.id AS battery_id,
|
||||||
|
MAX(l.tracked_time) AS last_tracked_time,
|
||||||
|
CASE WHEN b.charge_interval_days = 0
|
||||||
|
THEN '2999-12-31 23:59:59'
|
||||||
|
ELSE datetime(MAX(l.tracked_time), '+' || CAST(b.charge_interval_days AS TEXT) || ' day')
|
||||||
|
END AS next_estimated_charge_time
|
||||||
|
FROM batteries b
|
||||||
|
LEFT JOIN battery_charge_cycles l
|
||||||
|
ON b.id = l.battery_id
|
||||||
|
GROUP BY b.id, b.charge_interval_days;
|
41
migrations/0034.sql
Normal file
41
migrations/0034.sql
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
ALTER TABLE recipes_pos
|
||||||
|
ADD qu_id INTEGER;
|
||||||
|
|
||||||
|
UPDATE recipes_pos
|
||||||
|
SET qu_id = (SELECT qu_id_stock FROM products where id = product_id);
|
||||||
|
|
||||||
|
CREATE TRIGGER recipes_pos_qu_id_default AFTER INSERT ON recipes_pos
|
||||||
|
BEGIN
|
||||||
|
UPDATE recipes_pos
|
||||||
|
SET qu_id = (SELECT qu_id_stock FROM products where id = product_id)
|
||||||
|
WHERE qu_id IS NULL
|
||||||
|
AND id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
ALTER TABLE recipes_pos
|
||||||
|
ADD only_check_single_unit_in_stock TINYINT NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
DROP VIEW recipes_fulfillment;
|
||||||
|
CREATE VIEW recipes_fulfillment
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
r.id AS recipe_id,
|
||||||
|
rp.id AS recipe_pos_id,
|
||||||
|
rp.product_id AS product_id,
|
||||||
|
rp.amount AS recipe_amount,
|
||||||
|
IFNULL(sc.amount, 0) AS stock_amount,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END < 0 THEN ABS(IFNULL(sc.amount, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END) ELSE 0 END AS missing_amount,
|
||||||
|
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
|
||||||
|
CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
|
||||||
|
rp.qu_id
|
||||||
|
FROM recipes r
|
||||||
|
JOIN recipes_pos rp
|
||||||
|
ON r.id = rp.recipe_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT product_id, SUM(amount + amount_autoadded) AS amount
|
||||||
|
FROM shopping_list
|
||||||
|
GROUP BY product_id) sl
|
||||||
|
ON rp.product_id = sl.product_id
|
||||||
|
LEFT JOIN stock_current sc
|
||||||
|
ON rp.product_id = sc.product_id;
|
31
migrations/0035.sql
Normal file
31
migrations/0035.sql
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
ALTER TABLE habits RENAME TO chores;
|
||||||
|
|
||||||
|
CREATE TABLE chores_log (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
chore_id INTEGER NOT NULL,
|
||||||
|
tracked_time DATETIME,
|
||||||
|
done_by_user_id INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO chores_log
|
||||||
|
(chore_id, tracked_time, done_by_user_id, row_created_timestamp)
|
||||||
|
SELECT habit_id, tracked_time, done_by_user_id, row_created_timestamp
|
||||||
|
FROM habits_log;
|
||||||
|
|
||||||
|
DROP TABLE habits_log;
|
||||||
|
|
||||||
|
DROP VIEW habits_current;
|
||||||
|
CREATE VIEW chores_current
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
h.id AS chore_id,
|
||||||
|
MAX(l.tracked_time) AS last_tracked_time,
|
||||||
|
CASE h.period_type
|
||||||
|
WHEN 'manually' THEN '2999-12-31 23:59:59'
|
||||||
|
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
|
||||||
|
END AS next_estimated_execution_time
|
||||||
|
FROM chores h
|
||||||
|
LEFT JOIN chores_log l
|
||||||
|
ON h.id = l.chore_id
|
||||||
|
GROUP BY h.id, h.period_days;
|
24
migrations/0036.sql
Normal file
24
migrations/0036.sql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE tasks (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
due_date DATETIME,
|
||||||
|
done TINYINT NOT NULL DEFAULT 0 CHECK(done IN (0, 1)),
|
||||||
|
done_timestamp DATETIME,
|
||||||
|
category_id INTEGER,
|
||||||
|
assigned_to_user_id INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE task_categories (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW tasks_current
|
||||||
|
AS
|
||||||
|
SELECT *
|
||||||
|
FROM tasks
|
||||||
|
WHERE done = 0;
|
9
migrations/0037.sql
Normal file
9
migrations/0037.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE products
|
||||||
|
ADD product_group_id INTEGER;
|
||||||
|
|
||||||
|
CREATE TABLE product_groups (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
27
migrations/0038.sql
Normal file
27
migrations/0038.sql
Normal 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
10
migrations/0039.sql
Normal 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)
|
||||||
|
);
|
2
migrations/0040.sql
Normal file
2
migrations/0040.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE products
|
||||||
|
ADD picture_file_name TEXT;
|
7
migrations/0041.sql
Normal file
7
migrations/0041.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE equipment (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
instruction_manual_file_name TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
7
migrations/0042.sql
Normal file
7
migrations/0042.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TRIGGER cascade_change_qu_id_stock AFTER UPDATE ON products
|
||||||
|
BEGIN
|
||||||
|
UPDATE recipes_pos
|
||||||
|
SET qu_id = (SELECT qu_id_stock FROM products WHERE id = NEW.id)
|
||||||
|
WHERE product_id IN (SELECT id FROM products WHERE id = NEW.id)
|
||||||
|
AND only_check_single_unit_in_stock = 0;
|
||||||
|
END;
|
43
migrations/0043.sql
Normal file
43
migrations/0043.sql
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
CREATE TABLE recipes_nestings (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
recipe_id INTEGER NOT NULL,
|
||||||
|
includes_recipe_id INTEGER NOT NULL,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||||
|
|
||||||
|
UNIQUE(recipe_id, includes_recipe_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW recipes_nestings_resolved
|
||||||
|
AS
|
||||||
|
WITH RECURSIVE r1(recipe_id, includes_recipe_id)
|
||||||
|
AS (
|
||||||
|
SELECT id, id
|
||||||
|
FROM recipes
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT rn.recipe_id, r1.includes_recipe_id
|
||||||
|
FROM recipes_nestings rn, r1 r1
|
||||||
|
WHERE rn.includes_recipe_id = r1.recipe_id
|
||||||
|
LIMIT 100 -- This is just a safety limit to prevent infinite loops due to infinite nested recipes
|
||||||
|
)
|
||||||
|
SELECT *
|
||||||
|
FROM r1;
|
||||||
|
|
||||||
|
DROP VIEW recipes_fulfillment_sum;
|
||||||
|
CREATE VIEW recipes_fulfillment_sum
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
r.id AS recipe_id,
|
||||||
|
IFNULL(MIN(rf.need_fulfilled), 1) AS need_fulfilled,
|
||||||
|
IFNULL(MIN(rf.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
|
||||||
|
(SELECT COUNT(*) FROM recipes_fulfillment WHERE recipe_id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved rnr2 WHERE rnr2.recipe_id = r.id) AND need_fulfilled = 0 AND recipe_pos_id IS NOT NULL) AS missing_products_count
|
||||||
|
FROM recipes r
|
||||||
|
LEFT JOIN recipes_nestings_resolved rnr
|
||||||
|
ON r.id = rnr.recipe_id
|
||||||
|
LEFT JOIN recipes_fulfillment rf
|
||||||
|
ON rnr.includes_recipe_id = rf.recipe_id
|
||||||
|
GROUP BY r.id;
|
||||||
|
|
||||||
|
ALTER TABLE recipes_pos
|
||||||
|
ADD ingredient_group TEXT;
|
26
migrations/0044.sql
Normal file
26
migrations/0044.sql
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
ALTER TABLE stock_log
|
||||||
|
ADD undone TINYINT NOT NULL DEFAULT 0 CHECK(undone IN (0, 1));
|
||||||
|
|
||||||
|
UPDATE stock_log
|
||||||
|
SET undone = 0;
|
||||||
|
|
||||||
|
ALTER TABLE stock_log
|
||||||
|
ADD undone_timestamp DATETIME;
|
||||||
|
|
||||||
|
ALTER TABLE chores_log
|
||||||
|
ADD undone TINYINT NOT NULL DEFAULT 0 CHECK(undone IN (0, 1));
|
||||||
|
|
||||||
|
UPDATE chores_log
|
||||||
|
SET undone = 0;
|
||||||
|
|
||||||
|
ALTER TABLE chores_log
|
||||||
|
ADD undone_timestamp DATETIME;
|
||||||
|
|
||||||
|
ALTER TABLE battery_charge_cycles
|
||||||
|
ADD undone TINYINT NOT NULL DEFAULT 0 CHECK(undone IN (0, 1));
|
||||||
|
|
||||||
|
UPDATE battery_charge_cycles
|
||||||
|
SET undone = 0;
|
||||||
|
|
||||||
|
ALTER TABLE battery_charge_cycles
|
||||||
|
ADD undone_timestamp DATETIME;
|
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "grocy",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@danielfarrell/bootstrap-combobox": "https://github.com/berrnd/bootstrap-combobox.git#master",
|
||||||
|
"@fortawesome/fontawesome-free": "^5.1.0",
|
||||||
|
"TagManager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
|
||||||
|
"bootbox": "https://github.com/makeusabrew/bootbox.git#v5.x",
|
||||||
|
"bootstrap": "^4.1.1",
|
||||||
|
"chart.js": "^2.7.2",
|
||||||
|
"datatables.net": "^1.10.19",
|
||||||
|
"datatables.net-bs4": "^1.10.19",
|
||||||
|
"datatables.net-colreorder": "^1.5.1",
|
||||||
|
"datatables.net-colreorder-bs4": "^1.5.1",
|
||||||
|
"datatables.net-responsive": "^2.2.3",
|
||||||
|
"datatables.net-responsive-bs4": "^2.2.3",
|
||||||
|
"datatables.net-rowgroup": "^1.0.4",
|
||||||
|
"datatables.net-rowgroup-bs4": "^1.0.4",
|
||||||
|
"datatables.net-select": "^1.2.7",
|
||||||
|
"datatables.net-select-bs4": "^1.2.7",
|
||||||
|
"jquery": "^3.3.1",
|
||||||
|
"jquery-serializejson": "^2.8.1",
|
||||||
|
"jquery-ui-dist": "^1.12.1",
|
||||||
|
"moment": "^2.22.2",
|
||||||
|
"startbootstrap-sb-admin": "^4.0.0",
|
||||||
|
"summernote": "^0.8.10",
|
||||||
|
"swagger-ui-dist": "^3.17.3",
|
||||||
|
"tagmanager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
|
||||||
|
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||||
|
"timeago": "^1.6.3",
|
||||||
|
"toastr": "^2.1.4"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,118 +1,22 @@
|
|||||||
body {
|
/* Main style customizations */
|
||||||
padding-top: 50px;
|
body {
|
||||||
font-family: 'Noto Sans', sans-serif;
|
font-family: 'Noto Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-fixed-top {
|
.content-text {
|
||||||
background-color: #e5e5e5;
|
font-size: 0.85rem;
|
||||||
border-bottom: 2px solid;
|
|
||||||
border-color: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-weight: bold;
|
|
||||||
letter-spacing: -5px;
|
|
||||||
font-size: 2.2em;
|
|
||||||
color: #0b024c !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
padding-left: 5px !important;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-fixed-side {
|
|
||||||
top: 51px;
|
|
||||||
padding-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
border-right: 2px solid #d6d6d6;
|
|
||||||
max-width: 260px;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
#navbar-mobile {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-copyright {
|
|
||||||
padding-bottom: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.navbar-brand {
|
|
||||||
margin-left: 25px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-nav > li > a {
|
|
||||||
padding-right: 20px;
|
|
||||||
padding-left: 20px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-nav > li > a:hover {
|
|
||||||
box-shadow: inset 5px 0 0 #337ab7;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-nav > li > a:focus {
|
|
||||||
box-shadow: inset 5px 0 0 #ab2230;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-nav > .active > a,
|
|
||||||
.sidebar-nav > .active > a:hover,
|
|
||||||
.sidebar-nav > .active > a:focus {
|
|
||||||
background-color: #d6d6d6;
|
|
||||||
box-shadow: inset 5px 0 0 #ab2230;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav > li.disabled > a,
|
|
||||||
.navbar-default .navbar-nav > .disabled > a {
|
|
||||||
color: #a7a7a7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-copyright {
|
|
||||||
color: #a7a7a7;
|
|
||||||
font-size: 11px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-copyright > li > a {
|
|
||||||
padding-top: 0 !important;
|
|
||||||
padding-bottom: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-default .navbar-nav > .open > a {
|
|
||||||
background-color: #d6d6d6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu > li > a:hover,
|
|
||||||
.dropdown-menu > li > a:focus {
|
|
||||||
background-color: #e5e5e5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.well {
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
padding-right: 25px;
|
|
||||||
padding-left: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: middle !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-button {
|
.responsive-button {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discrete-link {
|
.timeago-contextual {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.discrete-link {
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
transition: all 0.3s !important;
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
@@ -120,65 +24,182 @@ td {
|
|||||||
a.discrete-link:hover {
|
a.discrete-link:hover {
|
||||||
color: #337ab7 !important;
|
color: #337ab7 !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
transition: all 0.3s !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.discrete-link:focus {
|
a.discrete-link:focus {
|
||||||
color: #ab2230 !important;
|
color: #ab2230 !important;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 2px solid;
|
||||||
|
border-color: #d6d6d6;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-text .invalid-feedback {
|
||||||
|
font-size: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen {
|
||||||
|
z-index: 9999;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input.is-valid ~ .form-check-label,
|
||||||
|
.was-validated .form-check-input:valid ~ .form-check-label {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-strike-through {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the default up/down arrow buttons for number inputs because we use our own buttons in numberpicker */
|
||||||
|
input[type='number'] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation style customizations */
|
||||||
|
#mainNav {
|
||||||
|
background-color: #e5e5e5 !important;
|
||||||
|
border-bottom: 2px solid !important;
|
||||||
|
border-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-sidenav {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-top: 2px solid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-sidenav,
|
||||||
|
.sidenav-second-level {
|
||||||
|
background-color: #e5e5e5 !important;
|
||||||
|
border-right: 2px solid !important;
|
||||||
|
border-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-menu {
|
||||||
|
background-color: #e5e5e5 !important;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .dropdown-divider {
|
||||||
|
border-top: 2px solid !important;
|
||||||
|
border-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav-toggler {
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
border-right: 2px solid !important;
|
||||||
|
border-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-sidenav > li,
|
||||||
|
.sidenav-second-level > li {
|
||||||
transition: all 0.3s !important;
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-sidenav > li:hover,
|
||||||
|
.sidenav-second-level > li:hover,
|
||||||
|
.navbar-nav .dropdown-item:hover {
|
||||||
|
box-shadow: inset 5px 0 0 #337ab7 !important;
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-sidenav > li > a:focus,
|
||||||
|
.sidenav-second-level > li > a:focus,
|
||||||
|
.navbar-nav .dropdown-item:focus {
|
||||||
|
box-shadow: inset 5px 0 0 #ab2230 !important;
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-page {
|
||||||
|
box-shadow: inset 5px 0 0 #ab2230 !important;
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - DataTables */
|
||||||
|
td {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
.table td.fit-content,
|
.table td.fit-content,
|
||||||
.table th.fit-content {
|
.table th.fit-content {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 1%;
|
width: 1%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dataTables_info,
|
.dataTables_filter,
|
||||||
.dataTables_length,
|
.dataTables_info {
|
||||||
.dataTables_filter {
|
display: none;
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeago-contextual {
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled,
|
|
||||||
.no-real-button {
|
|
||||||
pointer-events: none;
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discrete-content-separator-2x {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-bg {
|
|
||||||
background-color: #fcf8e3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-bg {
|
|
||||||
background-color: #f2dede !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-bg {
|
|
||||||
background-color: #afd9ee !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - toastr */
|
||||||
#toast-container > div {
|
#toast-container > div {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
filter: alpha(opacity=100);
|
filter: alpha(opacity=100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-success {
|
.toast-success {
|
||||||
background-color: #4c994c;
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-error {
|
||||||
|
background-color: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toast-container > div {
|
#toast-container > div {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - SB Admin 2 */
|
||||||
|
#mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after,
|
||||||
|
#mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse:after {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:992px) {
|
||||||
|
#mainNav .navbar-collapse .navbar-sidenav > .nav-item > .nav-link {
|
||||||
|
padding: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - Tempus Dominus */
|
||||||
|
.date-only-datetimepicker .bootstrap-datetimepicker-widget.dropdown-menu {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - Bootstrap Combobox */
|
||||||
|
.typeahead .active {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - Popper.js */
|
||||||
|
.tooltip {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
221
public/css/grocy_night_mode.css
Normal file
221
public/css/grocy_night_mode.css
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .note-editor.note-frame .note-editing-area .note-editable {
|
||||||
|
color: #c1c1c1;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bootstrap-datetimepicker-widget table td.day {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bootstrap-datetimepicker-widget table td {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bootstrap-datetimepicker-widget table td,
|
||||||
|
.night-mode .bootstrap-datetimepicker-widget table th {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .dropdown-menu {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB |
20
public/img/grocy_icon.svg
Normal file
20
public/img/grocy_icon.svg
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="60.000000pt" height="93.000000pt" viewBox="0 0 60.000000 93.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,93.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#0b024c" stroke="none">
|
||||||
|
<path d="M165 905 c-52 -18 -109 -82 -132 -148 -14 -39 -18 -82 -18 -172 0
|
||||||
|
-104 3 -127 23 -170 43 -94 114 -144 205 -145 59 0 112 21 156 63 l33 32 -7
|
||||||
|
-75 c-10 -96 -17 -116 -57 -140 -43 -26 -130 -26 -233 0 -43 11 -80 20 -82 20
|
||||||
|
-2 0 -3 -30 -1 -67 l3 -68 50 -13 c28 -8 100 -14 160 -15 172 -1 255 38 304
|
||||||
|
142 l26 56 3 353 4 352 -75 0 -75 0 -7 -40 -7 -40 -36 31 c-67 59 -150 75
|
||||||
|
-237 44z m206 -141 c45 -23 62 -72 62 -180 1 -112 -18 -152 -79 -172 -125 -42
|
||||||
|
-201 80 -163 260 21 96 97 135 180 92z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1002 B |
33
public/img/grocy_logo.svg
Normal file
33
public/img/grocy_logo.svg
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="242.000000pt" height="93.000000pt" viewBox="0 0 242.000000 93.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.15, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,93.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#0b024c" stroke="none">
|
||||||
|
<path d="M165 905 c-52 -18 -109 -82 -132 -148 -14 -39 -18 -82 -18 -172 0
|
||||||
|
-104 3 -127 23 -170 43 -94 114 -144 205 -145 59 0 112 21 156 63 l33 32 -7
|
||||||
|
-75 c-10 -96 -17 -116 -57 -140 -43 -26 -130 -26 -233 0 -43 11 -80 20 -82 20
|
||||||
|
-2 0 -3 -30 -1 -67 l3 -68 50 -13 c28 -8 100 -14 160 -15 121 -1 183 14 244
|
||||||
|
60 36 28 81 112 81 155 0 54 6 58 90 58 l78 0 4 193 c3 180 4 194 25 223 20
|
||||||
|
28 99 72 110 61 2 -3 -1 -24 -6 -48 -6 -24 -11 -79 -11 -121 0 -137 53 -233
|
||||||
|
158 -285 50 -24 69 -28 147 -28 107 0 158 20 219 83 l40 41 23 -34 c13 -20 46
|
||||||
|
-45 80 -62 51 -25 69 -28 153 -28 68 0 108 5 143 18 l47 19 0 74 0 74 -42 -21
|
||||||
|
c-58 -30 -154 -37 -197 -15 -85 44 -98 250 -21 328 24 24 36 28 84 28 99 0 92
|
||||||
|
9 200 -260 l97 -242 -23 -46 c-32 -65 -67 -87 -134 -87 l-54 0 0 -66 0 -67 45
|
||||||
|
-6 c108 -17 215 34 269 128 20 33 296 754 296 771 0 3 -40 5 -89 5 l-90 0 -64
|
||||||
|
-197 c-35 -109 -69 -214 -75 -233 -10 -34 -10 -34 -11 -7 -1 16 -31 120 -68
|
||||||
|
232 l-66 202 -143 8 c-116 5 -154 4 -197 -9 -61 -18 -126 -61 -145 -98 l-14
|
||||||
|
-25 -52 53 c-40 39 -67 56 -106 68 -87 26 -181 19 -272 -19 -14 -5 -18 -2 -18
|
||||||
|
14 0 19 -6 21 -52 21 -64 0 -129 -30 -164 -76 -15 -19 -30 -34 -34 -34 -4 0
|
||||||
|
-13 23 -20 50 l-12 50 -133 0 -133 0 -7 -40 -7 -40 -36 31 c-67 59 -150 75
|
||||||
|
-237 44z m206 -141 c45 -23 62 -72 62 -180 1 -112 -18 -152 -79 -172 -125 -42
|
||||||
|
-201 80 -163 260 21 96 97 135 180 92z m888 -10 c40 -33 54 -89 49 -184 -7
|
||||||
|
-118 -41 -160 -131 -160 -87 0 -121 53 -121 185 0 135 34 185 125 185 36 0 55
|
||||||
|
-6 78 -26z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -31,3 +31,36 @@ GetUriParam = function(key)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IsTouchInputDevice = function()
|
||||||
|
{
|
||||||
|
if (("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoolVal = function(test)
|
||||||
|
{
|
||||||
|
var anything = test.toString().toLowerCase();
|
||||||
|
if (anything === true || anything === "true" || anything === "1" || anything === "on")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetFileNameFromPath = function(path)
|
||||||
|
{
|
||||||
|
return path.split("/").pop().split("\\").pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
GetFileExtension = function(pathOrFileName)
|
||||||
|
{
|
||||||
|
return pathOrFileName.split(".").pop();
|
||||||
|
}
|
||||||
|
@@ -3,6 +3,22 @@
|
|||||||
var localizedText = Grocy.LocalizationStrings[text];
|
var localizedText = Grocy.LocalizationStrings[text];
|
||||||
if (localizedText === undefined)
|
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;
|
localizedText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,16 +35,64 @@ U = function(relativePath)
|
|||||||
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pluralize = function(number, singularForm, pluralForm)
|
||||||
|
{
|
||||||
|
var text = singularForm;
|
||||||
|
if (number != 1 && pluralForm !== null && !pluralForm.isEmpty())
|
||||||
|
{
|
||||||
|
text = pluralForm;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Grocy.ActiveNav.isEmpty())
|
if (!Grocy.ActiveNav.isEmpty())
|
||||||
{
|
{
|
||||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
var menuItem = $('#sidebarResponsive').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||||
menuItem.addClass('active');
|
menuItem.addClass('active-page');
|
||||||
|
|
||||||
|
var parentMenuSelector = menuItem.data("sub-menu-of");
|
||||||
|
if (typeof parentMenuSelector !== "undefined")
|
||||||
|
{
|
||||||
|
$(parentMenuSelector).collapse("show");
|
||||||
|
$(parentMenuSelector).prev(".nav-link-collapse").addClass("active-page");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var observer = new MutationObserver(function(mutations)
|
||||||
|
{
|
||||||
|
mutations.forEach(function(mutation)
|
||||||
|
{
|
||||||
|
if (mutation.attributeName === "class")
|
||||||
|
{
|
||||||
|
var attributeValue = $(mutation.target).prop(mutation.attributeName);
|
||||||
|
if (attributeValue.contains("sidenav-toggled"))
|
||||||
|
{
|
||||||
|
window.localStorage.setItem("sidebar_state", "collapsed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.localStorage.setItem("sidebar_state", "expanded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe(document.body, {
|
||||||
|
attributes: true
|
||||||
|
});
|
||||||
|
if (window.localStorage.getItem("sidebar_state") === "collapsed")
|
||||||
|
{
|
||||||
|
$("#sidenavToggler").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
$.timeago.settings.allowFuture = true;
|
$.timeago.settings.allowFuture = true;
|
||||||
RefreshContextualTimeago = function()
|
RefreshContextualTimeago = function()
|
||||||
{
|
{
|
||||||
$('time.timeago').timeago();
|
$("time.timeago").each(function()
|
||||||
|
{
|
||||||
|
var element = $(this);
|
||||||
|
var timestamp = element.attr("datetime");
|
||||||
|
element.timeago("update", timestamp);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
RefreshContextualTimeago();
|
RefreshContextualTimeago();
|
||||||
|
|
||||||
@@ -39,6 +103,18 @@ toastr.options = {
|
|||||||
extendedTimeOut: 5000
|
extendedTimeOut: 5000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.FontAwesomeConfig = {
|
||||||
|
searchPseudoElements: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't show tooltips on touch input devices
|
||||||
|
if (IsTouchInputDevice())
|
||||||
|
{
|
||||||
|
var css = document.createElement("style");
|
||||||
|
css.innerHTML = ".tooltip { display: none; }";
|
||||||
|
document.body.appendChild(css);
|
||||||
|
}
|
||||||
|
|
||||||
Grocy.Api = { };
|
Grocy.Api = { };
|
||||||
Grocy.Api.Get = function(apiFunction, success, error)
|
Grocy.Api.Get = function(apiFunction, success, error)
|
||||||
{
|
{
|
||||||
@@ -100,3 +176,172 @@ Grocy.Api.Post = function(apiFunction, jsonData, success, error)
|
|||||||
xhr.setRequestHeader('Content-type', 'application/json');
|
xhr.setRequestHeader('Content-type', 'application/json');
|
||||||
xhr.send(JSON.stringify(jsonData));
|
xhr.send(JSON.stringify(jsonData));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Grocy.Api.UploadFile = function(file, group, fileName, success, error)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/file/' + group + '?file_name=' + encodeURIComponent(fileName));
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE)
|
||||||
|
{
|
||||||
|
if (xhr.status === 200)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
success(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
error(xhr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('PUT', url, true);
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/octet-stream');
|
||||||
|
xhr.send(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
Grocy.Api.DeleteFile = function(fileName, group, success, error)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/file/' + group + '?file_name=' + encodeURIComponent(fileName));
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function()
|
||||||
|
{
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE)
|
||||||
|
{
|
||||||
|
if (xhr.status === 200)
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
success(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
error(xhr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('DELETE', url, true);
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/json');
|
||||||
|
xhr.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
Grocy.FrontendHelpers = { };
|
||||||
|
Grocy.FrontendHelpers.ValidateForm = function(formId)
|
||||||
|
{
|
||||||
|
var form = document.getElementById(formId);
|
||||||
|
if (form.checkValidity() === true)
|
||||||
|
{
|
||||||
|
$(form).find(':submit').removeClass('disabled');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$(form).find(':submit').addClass('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(form).addClass('was-validated');
|
||||||
|
}
|
||||||
|
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
|
||||||
|
{
|
||||||
|
toastr.error(L(message) + '<br><br>' + L('Click to show technical details'), '', {
|
||||||
|
onclick: function()
|
||||||
|
{
|
||||||
|
bootbox.alert({
|
||||||
|
title: L('Error details'),
|
||||||
|
message: JSON.stringify(exception, null, 4)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 settingKey = element.attr("data-setting-key");
|
||||||
|
|
||||||
|
var inputType = "unknown";
|
||||||
|
if (typeof element.attr("type") !== typeof undefined && element.attr("type") !== false)
|
||||||
|
{
|
||||||
|
inputType = element.attr("type").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show file name Bootstrap custom file input
|
||||||
|
$('input.custom-file-input').on('change', function()
|
||||||
|
{
|
||||||
|
$(this).next('.custom-file-label').html(GetFileNameFromPath($(this).val()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Translation of "Browse"-button of Bootstrap custom file input
|
||||||
|
if ($(".custom-file-label").length > 0)
|
||||||
|
{
|
||||||
|
$("<style>").html('.custom-file-label::after { content: "' + L("Select file") + '"; }').appendTo("head");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResizeResponsiveEmbeds = function(fillEntireViewport = false)
|
||||||
|
{
|
||||||
|
if (!fillEntireViewport)
|
||||||
|
{
|
||||||
|
var maxHeight = $("body").height() - $("#mainNav").outerHeight() - 62;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var maxHeight = $("body").height();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".embed-responsive").attr("height", maxHeight.toString() + "px");
|
||||||
|
}
|
||||||
|
$(window).on('resize', function()
|
||||||
|
{
|
||||||
|
ResizeResponsiveEmbeds($("body").hasClass("fullscreen-card"));
|
||||||
|
});
|
||||||
|
62
public/js/grocy_dbchangedhandling.js
Normal file
62
public/js/grocy_dbchangedhandling.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
Grocy.Api.Get('system/get-db-changed-time',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
Grocy.DatabaseChangedTime = moment(result.changed_time);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
// when there is no unsaved form data and when the user enabled auto reloading
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('system/get-db-changed-time',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var newDbChangedTime = moment(result.changed_time);
|
||||||
|
if (newDbChangedTime.isAfter(Grocy.DatabaseChangedTime))
|
||||||
|
{
|
||||||
|
if (Grocy.IdleTime >= 50)
|
||||||
|
{
|
||||||
|
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0 && !$("body").hasClass("fullscreen-card"))
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Grocy.DatabaseChangedTime = newDbChangedTime;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
Grocy.IdleTime = 0;
|
||||||
|
Grocy.ResetIdleTime = function()
|
||||||
|
{
|
||||||
|
Grocy.IdleTime = 0;
|
||||||
|
}
|
||||||
|
window.onmousemove = Grocy.ResetIdleTime;
|
||||||
|
window.onmousedown = Grocy.ResetIdleTime;
|
||||||
|
window.onclick = Grocy.ResetIdleTime;
|
||||||
|
window.onscroll = Grocy.ResetIdleTime;
|
||||||
|
window.onkeypress = Grocy.ResetIdleTime;
|
||||||
|
|
||||||
|
// Increase the idle time once every second
|
||||||
|
// On any interaction it will be reset to 0 (see above)
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
Grocy.IdleTime += 1;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change))
|
||||||
|
{
|
||||||
|
$("#auto-reload-enabled").prop("checked", true);
|
||||||
|
}
|
106
public/js/grocy_nightmode.js
Normal file
106
public/js/grocy_nightmode.js
Normal 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);
|
||||||
|
}
|
@@ -1,4 +1,36 @@
|
|||||||
$(document).on('click', '.battery-delete-button', function(e)
|
var batteriesTable = $('#batteries-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
batteriesTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.battery-delete-button', function (e)
|
||||||
{
|
{
|
||||||
var objectName = $(e.currentTarget).attr('data-battery-name');
|
var objectName = $(e.currentTarget).attr('data-battery-name');
|
||||||
var objectId = $(e.currentTarget).attr('data-battery-id');
|
var objectId = $(e.currentTarget).attr('data-battery-id');
|
||||||
@@ -33,12 +65,3 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#batteries-table').DataTable({
|
|
||||||
'bPaginate': false,
|
|
||||||
'order': [[1, 'asc']],
|
|
||||||
'columnDefs': [
|
|
||||||
{ 'orderable': false, 'targets': 0 }
|
|
||||||
],
|
|
||||||
'language': JSON.parse(L('datatables_localization'))
|
|
||||||
});
|
|
||||||
|
70
public/viewjs/batteriesjournal.js
Normal file
70
public/viewjs/batteriesjournal.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
var batteriesJournalTable = $('#batteries-journal-table').DataTable({
|
||||||
|
'paginate': true,
|
||||||
|
'order': [[1, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#battery-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
var text = $("#battery-filter option:selected").text();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
batteriesJournalTable.column(1).search(text).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
batteriesJournalTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof GetUriParam("battery") !== "undefined")
|
||||||
|
{
|
||||||
|
$("#battery-filter").val(GetUriParam("battery"));
|
||||||
|
$("#battery-filter").trigger("change");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.undo-battery-execution-button', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var element = $(e.currentTarget);
|
||||||
|
var chargeCycleId = $(e.currentTarget).attr('data-charge-cycle-id');
|
||||||
|
|
||||||
|
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
element.closest("tr").addClass("text-muted");
|
||||||
|
element.closest(".undo-battery-execution-button").addClass("disabled");
|
||||||
|
toastr.success(L("Charge cycle successfully undone"));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
@@ -1,29 +1,116 @@
|
|||||||
$('#batteries-overview-table').DataTable({
|
var batteriesOverviewTable = $('#batteries-overview-table').DataTable({
|
||||||
'bPaginate': false,
|
'paginate': false,
|
||||||
'order': [[2, 'desc']],
|
'order': [[2, 'desc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 }
|
{ 'orderable': false, 'targets': 0 }
|
||||||
],
|
],
|
||||||
'language': JSON.parse(L('datatables_localization'))
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
batteriesOverviewTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
batteriesOverviewTable.column(4).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.track-charge-cycle-button', function(e)
|
$(document).on('click', '.track-charge-cycle-button', function(e)
|
||||||
{
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
var batteryId = $(e.currentTarget).attr('data-battery-id');
|
var batteryId = $(e.currentTarget).attr('data-battery-id');
|
||||||
var batteryName = $(e.currentTarget).attr('data-battery-name');
|
var batteryName = $(e.currentTarget).attr('data-battery-name');
|
||||||
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
Grocy.Api.Get('batteries/track-charge-cycle/' + batteryId + '?tracked_time=' + trackedTime,
|
Grocy.Api.Get('batteries/track-charge-cycle/' + batteryId + '?tracked_time=' + trackedTime,
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('batteries/get-battery-details/' + batteryId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
$('#battery-' + batteryId + '-last-tracked-time').parent().effect('highlight', {}, 500);
|
var batteryRow = $('#battery-' + batteryId + '-row');
|
||||||
$('#battery-' + batteryId + '-last-tracked-time').fadeOut(500, function () {
|
var nextXDaysThreshold = moment().add($("#info-due-batteries").data("next-x-days"), "days");
|
||||||
|
var now = moment();
|
||||||
|
var nextExecutionTime = moment(result.next_estimated_charge_time);
|
||||||
|
|
||||||
|
batteryRow.removeClass("table-warning");
|
||||||
|
batteryRow.removeClass("table-danger");
|
||||||
|
if (nextExecutionTime.isBefore(now))
|
||||||
|
{
|
||||||
|
batteryRow.addClass("table-danger");
|
||||||
|
}
|
||||||
|
else if (nextExecutionTime.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
batteryRow.addClass("table-warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#battery-' + batteryId + '-last-tracked-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#battery-' + batteryId + '-last-tracked-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
$(this).text(trackedTime).fadeIn(500);
|
$(this).text(trackedTime).fadeIn(500);
|
||||||
});
|
});
|
||||||
$('#battery-' + batteryId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
$('#battery-' + batteryId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
||||||
RefreshContextualTimeago();
|
|
||||||
|
|
||||||
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryName, trackedTime));
|
if (result.battery.charge_interval_days != 0)
|
||||||
|
{
|
||||||
|
$('#battery-' + batteryId + '-next-charge-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#battery-' + batteryId + '-next-charge-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(result.next_estimated_charge_time).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#battery-' + batteryId + '-next-charge-time-timeago').attr('datetime', result.next_estimated_charge_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryName, trackedTime));
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
RefreshStatistics();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -31,3 +118,37 @@ $(document).on('click', '.track-charge-cycle-button', function(e)
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function RefreshStatistics()
|
||||||
|
{
|
||||||
|
var nextXDays = $("#info-due-batteries").data("next-x-days");
|
||||||
|
Grocy.Api.Get('batteries/get-current',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var dueCount = 0;
|
||||||
|
var overdueCount = 0;
|
||||||
|
var now = moment();
|
||||||
|
var nextXDaysThreshold = moment().add(nextXDays, "days");
|
||||||
|
result.forEach(element => {
|
||||||
|
var date = moment(element.next_estimated_charge_time);
|
||||||
|
if (date.isBefore(now))
|
||||||
|
{
|
||||||
|
overdueCount++;
|
||||||
|
}
|
||||||
|
else if (date.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
dueCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#info-due-batteries").text(Pluralize(dueCount, L('#1 battery is due to be charged within the next #2 days', dueCount, nextXDays), L('#1 batteries are due to be charged within the next #2 days', dueCount, nextXDays)));
|
||||||
|
$("#info-overdue-batteries").text(Pluralize(overdueCount, L('#1 battery is overdue to be charged', overdueCount), L('#1 batteries are overdue to be charged', overdueCount)));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshStatistics();
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,33 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#battery-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('battery-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#battery-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('battery-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-battery-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#name').focus();
|
$('#name').focus();
|
||||||
$('#battery-form').validator();
|
Grocy.FrontendHelpers.ValidateForm('battery-form');
|
||||||
$('#battery-form').validator('validate');
|
|
||||||
|
@@ -7,18 +7,18 @@
|
|||||||
Grocy.Api.Get('batteries/get-battery-details/' + jsonForm.battery_id,
|
Grocy.Api.Get('batteries/get-battery-details/' + jsonForm.battery_id,
|
||||||
function (batteryDetails)
|
function (batteryDetails)
|
||||||
{
|
{
|
||||||
Grocy.Api.Get('batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').val(),
|
Grocy.Api.Get('batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').find('input').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').val()));
|
toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').find('input').val()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChargeCycle(' + result.charge_cycle_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||||
|
|
||||||
$('#battery_id').val('');
|
$('#battery_id').val('');
|
||||||
$('#battery_id_text_input').focus();
|
$('#battery_id_text_input').focus();
|
||||||
$('#battery_id_text_input').val('');
|
$('#battery_id_text_input').val('');
|
||||||
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
$('#tracked_time').find('input').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
$('#tracked_time').trigger('change');
|
$('#tracked_time').find('input').trigger('change');
|
||||||
$('#battery_id_text_input').trigger('change');
|
$('#battery_id_text_input').trigger('change');
|
||||||
$('#batterytracking-form').validator('validate');
|
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -35,115 +35,67 @@
|
|||||||
|
|
||||||
$('#battery_id').on('change', function(e)
|
$('#battery_id').on('change', function(e)
|
||||||
{
|
{
|
||||||
var batteryId = $(e.target).val();
|
var input = $('#battery_id_text_input').val().toString();
|
||||||
|
$('#battery_id_text_input').val(input);
|
||||||
|
$('#battery_id').data('combobox').refresh();
|
||||||
|
|
||||||
|
var batteryId = $(e.target).val();
|
||||||
if (batteryId)
|
if (batteryId)
|
||||||
{
|
{
|
||||||
Grocy.Components.BatteryCard.Refresh(batteryId);
|
Grocy.Components.BatteryCard.Refresh(batteryId);
|
||||||
$('#tracked_time').focus();
|
$('#tracked_time').find('input').focus();
|
||||||
}
|
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||||
});
|
|
||||||
|
|
||||||
$('.datetimepicker').datetimepicker(
|
|
||||||
{
|
|
||||||
format: 'YYYY-MM-DD HH:mm:ss',
|
|
||||||
showTodayButton: true,
|
|
||||||
calendarWeeks: true,
|
|
||||||
maxDate: moment()
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#tracked_time').val(moment().format('YYYY-MM-DD HH:mm:ss'));
|
|
||||||
$('#tracked_time').trigger('change');
|
|
||||||
|
|
||||||
$('#tracked_time').on('focus', function(e)
|
|
||||||
{
|
|
||||||
if ($('#battery_id_text_input').val().length === 0)
|
|
||||||
{
|
|
||||||
$('#battery_id_text_input').focus();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.combobox').combobox({
|
$('.combobox').combobox({
|
||||||
appendId: '_text_input'
|
appendId: '_text_input',
|
||||||
|
bsVersion: '4'
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#battery_id').val('');
|
$('#battery_id').val('');
|
||||||
$('#battery_id_text_input').focus();
|
$('#battery_id_text_input').focus();
|
||||||
$('#battery_id_text_input').val('');
|
$('#battery_id_text_input').val('');
|
||||||
$('#battery_id_text_input').trigger('change');
|
$('#battery_id_text_input').trigger('change');
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||||
|
|
||||||
$('#batterytracking-form').validator();
|
$('#batterytracking-form input').keyup(function (event)
|
||||||
$('#batterytracking-form').validator('validate');
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||||
|
});
|
||||||
|
|
||||||
$('#batterytracking-form input').keydown(function(event)
|
$('#batterytracking-form input').keydown(function(event)
|
||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
|
||||||
if ($('#batterytracking-form').validator('validate').has('.has-error').length !== 0) //There is at least one validation error
|
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('batterytracking-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-batterytracking-button').click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#tracked_time').on('change', function(e)
|
$('#tracked_time').find('input').on('keypress', function (e)
|
||||||
{
|
{
|
||||||
var value = $('#tracked_time').val();
|
Grocy.FrontendHelpers.ValidateForm('batterytracking-form');
|
||||||
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');
|
|
||||||
$('#tracked_time').val(value);
|
|
||||||
$('#batterytracking-form').validator('validate');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#tracked_time').on('keypress', function(e)
|
function UndoChargeCycle(chargeCycleId)
|
||||||
{
|
{
|
||||||
var element = $(e.target);
|
Grocy.Api.Get('batteries/undo-charge-cycle/' + chargeCycleId.toString(),
|
||||||
var value = element.val();
|
function(result)
|
||||||
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);
|
toastr.success(L("Charge cycle successfully undone"));
|
||||||
}
|
},
|
||||||
|
function(xhr)
|
||||||
if (dateObj.isValid())
|
|
||||||
{
|
{
|
||||||
if (e.keyCode === 38) //Up
|
console.error(xhr);
|
||||||
{
|
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#batterytracking-form').validator('validate');
|
|
||||||
});
|
|
||||||
|
72
public/viewjs/choreform.js
Normal file
72
public/viewjs/choreform.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
$('#save-chore-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('add-object/chores', $('#chore-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/chores');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('edit-object/chores/' + Grocy.EditObjectId, $('#chore-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/chores');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('chore-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-chore-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
||||||
|
|
||||||
|
$('.input-group-chore-period-type').on('change', function(e)
|
||||||
|
{
|
||||||
|
var periodType = $('#period_type').val();
|
||||||
|
var periodDays = $('#period_days').val();
|
||||||
|
|
||||||
|
if (periodType === 'dynamic-regular')
|
||||||
|
{
|
||||||
|
$('#chore-period-type-info').text(L('This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', periodDays.toString()));
|
||||||
|
$('#chore-period-type-info').removeClass('d-none');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#chore-period-type-info').addClass('d-none');
|
||||||
|
}
|
||||||
|
});
|
67
public/viewjs/chores.js
Normal file
67
public/viewjs/chores.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
var choresTable = $('#chores-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.chore-delete-button', function (e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-chore-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-chore-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete chore "#1"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: L('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: L('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('delete-object/chores/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/chores');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
70
public/viewjs/choresjournal.js
Normal file
70
public/viewjs/choresjournal.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
var choresJournalTable = $('#chores-journal-table').DataTable({
|
||||||
|
'paginate': true,
|
||||||
|
'order': [[1, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#chore-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
var text = $("#chore-filter option:selected").text();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresJournalTable.column(1).search(text).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresJournalTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof GetUriParam("chore") !== "undefined")
|
||||||
|
{
|
||||||
|
$("#chore-filter").val(GetUriParam("chore"));
|
||||||
|
$("#chore-filter").trigger("change");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', '.undo-chore-execution-button', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var element = $(e.currentTarget);
|
||||||
|
var executionId = $(e.currentTarget).attr('data-execution-id');
|
||||||
|
|
||||||
|
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
element.closest("tr").addClass("text-muted");
|
||||||
|
element.closest(".undo-chore-execution-button").addClass("disabled");
|
||||||
|
toastr.success(L("Chore execution successfully undone"));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
154
public/viewjs/choresoverview.js
Normal file
154
public/viewjs/choresoverview.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
var choresOverviewTable = $('#chores-overview-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[2, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresOverviewTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
choresOverviewTable.column(4).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.track-chore-button', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
var choreId = $(e.currentTarget).attr('data-chore-id');
|
||||||
|
var choreName = $(e.currentTarget).attr('data-chore-name');
|
||||||
|
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
Grocy.Api.Get('chores/track-chore-execution/' + choreId + '?tracked_time=' + trackedTime,
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + choreId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var choreRow = $('#chore-' + choreId + '-row');
|
||||||
|
var nextXDaysThreshold = moment().add($("#info-due-chores").data("next-x-days"), "days");
|
||||||
|
var now = moment();
|
||||||
|
var nextExecutionTime = moment(result.next_estimated_execution_time);
|
||||||
|
|
||||||
|
choreRow.removeClass("table-warning");
|
||||||
|
choreRow.removeClass("table-danger");
|
||||||
|
if (nextExecutionTime.isBefore(now))
|
||||||
|
{
|
||||||
|
choreRow.addClass("table-danger");
|
||||||
|
}
|
||||||
|
else if (nextExecutionTime.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
choreRow.addClass("table-warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(trackedTime).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
||||||
|
|
||||||
|
if (result.chore.period_type == "dynamic-regular")
|
||||||
|
{
|
||||||
|
$('#chore-' + choreId + '-next-execution-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#chore-' + choreId + '-next-execution-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(result.next_estimated_execution_time).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#chore-' + choreId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(L('Tracked execution of chore #1 on #2', choreName, trackedTime));
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
RefreshStatistics();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function RefreshStatistics()
|
||||||
|
{
|
||||||
|
var nextXDays = $("#info-due-chores").data("next-x-days");
|
||||||
|
Grocy.Api.Get('chores/get-current',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var dueCount = 0;
|
||||||
|
var overdueCount = 0;
|
||||||
|
var now = moment();
|
||||||
|
var nextXDaysThreshold = moment().add(nextXDays, "days");
|
||||||
|
result.forEach(element => {
|
||||||
|
var date = moment(element.next_estimated_execution_time);
|
||||||
|
if (date.isBefore(now))
|
||||||
|
{
|
||||||
|
overdueCount++;
|
||||||
|
}
|
||||||
|
else if (date.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
dueCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#info-due-chores").text(Pluralize(dueCount, L('#1 chore is due to be done within the next #2 days', dueCount, nextXDays), L('#1 chores are due to be done within the next #2 days', dueCount, nextXDays)));
|
||||||
|
$("#info-overdue-chores").text(Pluralize(overdueCount, L('#1 chore is overdue to be done', overdueCount), L('#1 chores are overdue to be done', overdueCount)));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshStatistics();
|
98
public/viewjs/choretracking.js
Normal file
98
public/viewjs/choretracking.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
$('#save-choretracking-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonForm = $('#choretracking-form').serializeJSON();
|
||||||
|
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + jsonForm.chore_id,
|
||||||
|
function (choreDetails)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/track-chore-execution/' + jsonForm.chore_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoChoreExecution(' + result.chore_execution_id + ')"><i class="fas fa-undo"></i> ' + L("Undo") + '</a>');
|
||||||
|
|
||||||
|
$('#chore_id').val('');
|
||||||
|
$('#chore_id_text_input').focus();
|
||||||
|
$('#chore_id_text_input').val('');
|
||||||
|
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
$('#chore_id_text_input').trigger('change');
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore_id').on('change', function(e)
|
||||||
|
{
|
||||||
|
var input = $('#chore_id_text_input').val().toString();
|
||||||
|
$('#chore_id_text_input').val(input);
|
||||||
|
$('#chore_id').data('combobox').refresh();
|
||||||
|
|
||||||
|
var choreId = $(e.target).val();
|
||||||
|
if (choreId)
|
||||||
|
{
|
||||||
|
Grocy.Components.ChoreCard.Refresh(choreId);
|
||||||
|
Grocy.Components.DateTimePicker.GetInputElement().focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.combobox').combobox({
|
||||||
|
appendId: '_text_input',
|
||||||
|
bsVersion: '4'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore_id_text_input').focus();
|
||||||
|
$('#chore_id_text_input').trigger('change');
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
|
||||||
|
$('#choretracking-form input').keyup(function (event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#choretracking-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('choretracking-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-choretracking-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
function UndoChoreExecution(executionId)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/undo-chore-execution/' + executionId.toString(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
toastr.success(L("Chore execution successfully undone"));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
@@ -7,10 +7,13 @@ Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
|||||||
{
|
{
|
||||||
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
||||||
$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
|
$('#batterycard-battery-used_in').text(batteryDetails.battery.used_in);
|
||||||
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || 'never'));
|
$('#batterycard-battery-last-charged').text((batteryDetails.last_charged || L('never')));
|
||||||
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
$('#batterycard-battery-last-charged-timeago').text($.timeago(batteryDetails.last_charged || ''));
|
||||||
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
$('#batterycard-battery-charge-cycles-count').text((batteryDetails.charge_cycles_count || '0'));
|
||||||
|
|
||||||
|
$('#batterycard-battery-edit-button').attr("href", U("/battery/" + batteryDetails.battery.id.toString()));
|
||||||
|
$('#batterycard-battery-edit-button').removeClass("disabled");
|
||||||
|
|
||||||
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', L('timeago_nan'));
|
EmptyElementWhenMatches('#batterycard-battery-last-charged-timeago', L('timeago_nan'));
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
|
40
public/viewjs/components/calendarcard.js
Normal file
40
public/viewjs/components/calendarcard.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
$('#calendar').datetimepicker(
|
||||||
|
{
|
||||||
|
format: 'L',
|
||||||
|
buttons: {
|
||||||
|
showToday: true,
|
||||||
|
showClose: false
|
||||||
|
},
|
||||||
|
calendarWeeks: true,
|
||||||
|
locale: moment.locale(),
|
||||||
|
icons: {
|
||||||
|
time: 'far fa-clock',
|
||||||
|
date: 'far fa-calendar',
|
||||||
|
up: 'fas fa-arrow-up',
|
||||||
|
down: 'fas fa-arrow-down',
|
||||||
|
previous: 'fas fa-chevron-left',
|
||||||
|
next: 'fas fa-chevron-right',
|
||||||
|
today: 'fas fa-calendar-check',
|
||||||
|
clear: 'far fa-trash-alt',
|
||||||
|
close: 'far fa-times-circle'
|
||||||
|
},
|
||||||
|
keepOpen: true,
|
||||||
|
inline: true,
|
||||||
|
keyBinds: {
|
||||||
|
up: function(widget) { },
|
||||||
|
down: function(widget) { },
|
||||||
|
'control up': function(widget) { },
|
||||||
|
'control down': function(widget) { },
|
||||||
|
left: function(widget) { },
|
||||||
|
right: function(widget) { },
|
||||||
|
pageUp: function(widget) { },
|
||||||
|
pageDown: function(widget) { },
|
||||||
|
enter: function(widget) { },
|
||||||
|
escape: function(widget) { },
|
||||||
|
'control space': function(widget) { },
|
||||||
|
t: function(widget) { },
|
||||||
|
'delete': function(widget) { }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#calendar').datetimepicker('show');
|
24
public/viewjs/components/chorecard.js
Normal file
24
public/viewjs/components/chorecard.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Grocy.Components.ChoreCard = { };
|
||||||
|
|
||||||
|
Grocy.Components.ChoreCard.Refresh = function(choreId)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + choreId,
|
||||||
|
function(choreDetails)
|
||||||
|
{
|
||||||
|
$('#chorecard-chore-name').text(choreDetails.chore.name);
|
||||||
|
$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || L('never')));
|
||||||
|
$('#chorecard-chore-last-tracked-timeago').text($.timeago(choreDetails.last_tracked || ''));
|
||||||
|
$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
|
||||||
|
$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || L('Unknown')));
|
||||||
|
|
||||||
|
$('#chorecard-chore-edit-button').attr("href", U("/chore/" + choreDetails.chore.id.toString()));
|
||||||
|
$('#chorecard-chore-edit-button').removeClass("disabled");
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', L('timeago_nan'));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user