mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-15 16:57:09 +00:00
Compare commits
229 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
735222a8ed | ||
|
66f299cd06 | ||
|
e3c5268143 | ||
|
df32493d77 | ||
|
86faf44153 | ||
|
e2f3e4b555 | ||
|
e57ed6015c | ||
|
9c34ca7fc4 | ||
|
7b94f4a441 | ||
|
693f8d0738 | ||
|
d6ecbc06bf | ||
|
c31674fffc | ||
|
b08de8cc00 | ||
|
4c503e4c7c | ||
|
dd4158c6b4 | ||
|
5fd7ea2b96 | ||
|
b9ff80eb5a | ||
|
afc725bbc8 | ||
|
0342c371cc | ||
|
2da4e6b048 | ||
|
f50550d79c | ||
|
3fa39a6805 | ||
|
3dbe6d4870 | ||
|
59c48268ab | ||
|
1f83c5195d | ||
|
49a95a08fe | ||
|
c86c5ccfe9 | ||
|
8a2497fc67 | ||
|
7c70732247 | ||
|
53fc4f2740 | ||
|
f3ade5621e | ||
|
ec2e08e33a | ||
|
b9a26faa4d | ||
|
1a434d0c83 | ||
|
9a2c6c2967 | ||
|
602b35d589 | ||
|
f42cd0c7c3 | ||
|
cb81855a17 | ||
|
89cf351ad0 | ||
|
3f70a3f06c | ||
|
9df360f010 | ||
|
9a26d6d49f | ||
|
bc4d801c12 | ||
|
f2d8e13576 | ||
|
ec0b5db973 | ||
|
46a0d1ce35 | ||
|
cb81446ca6 | ||
|
9350b4939c | ||
|
33e8c8c415 | ||
|
48fa86cc54 | ||
|
d5e6d1c578 | ||
|
0bc688795a | ||
|
eb76ed5591 | ||
|
839cbaf37a | ||
|
788fc9204d | ||
|
3e3e304ef3 | ||
|
447d453fdc | ||
|
36fd7884f3 | ||
|
54da08b2f2 | ||
|
3f02072ae9 | ||
|
a9c117703b | ||
|
c137255155 | ||
|
e7829ecc38 | ||
|
529bdafa31 | ||
|
e2af0caa41 | ||
|
80f96abf08 | ||
|
70da38193f | ||
|
13df973873 | ||
|
3ccb791674 | ||
|
ccf1a6c182 | ||
|
493543c1f5 | ||
|
5f5725e0e3 | ||
|
107dd42957 | ||
|
a9f3fe4d3a | ||
|
3e62e17b9e | ||
|
57855b1930 | ||
|
aa9e8227bb | ||
|
a80f083b6e | ||
|
474e066d4a | ||
|
4428ccefbf | ||
|
d568a6c8a9 | ||
|
97e9ad6cb2 | ||
|
00607d2a6d | ||
|
c2a425121d | ||
|
435694e9ea | ||
|
f59135a9ca | ||
|
102b106402 | ||
|
5c27c8e633 | ||
|
edd5215b21 | ||
|
94b173ae6b | ||
|
7d96b281b6 | ||
|
a5515ac89f | ||
|
fb863b0bf2 | ||
|
50882f309b | ||
|
ce854fbb43 | ||
|
6799268ec4 | ||
|
6fe5ce0485 | ||
|
cbeaf8e16a | ||
|
04de4c9b36 | ||
|
517731cb59 | ||
|
e34e43173c | ||
|
79d6055a78 | ||
|
7ac4d2a2f4 | ||
|
4984eda320 | ||
|
89e0791e2f | ||
|
922d487821 | ||
|
4b789979ac | ||
|
554b38ccff | ||
|
9614310208 | ||
|
d9ec3ac354 | ||
|
f326f08f7b | ||
|
0ae8418f32 | ||
|
309f9cd076 | ||
|
61f5ed3874 | ||
|
91178d2604 | ||
|
87dae6ea18 | ||
|
2e495c38d1 | ||
|
c2987aaf4c | ||
|
f4f4eecb7b | ||
|
84d9287251 | ||
|
b71f334744 | ||
|
ad306e4d01 | ||
|
e40d4ef829 | ||
|
48c16c3dcc | ||
|
c045193246 | ||
|
a816e59a97 | ||
|
892074eaf2 | ||
|
3e501e429d | ||
|
0f40accb4c | ||
|
3dae6c99a4 | ||
|
b185c83597 | ||
|
ef6b4120f1 | ||
|
a43eef01fc | ||
|
2cb9aa537f | ||
|
2edd49a8b4 | ||
|
a57554d380 | ||
|
c89486b6d9 | ||
|
f737cb7235 | ||
|
f1fe169553 | ||
|
2fc760780e | ||
|
8c3290bf6f | ||
|
495158b9c9 | ||
|
f9fc9b1889 | ||
|
11ff2ab9d1 | ||
|
52b138e6b2 | ||
|
3562ec1f79 | ||
|
943b4de6dc | ||
|
4097f51f69 | ||
|
cad5b2cae9 | ||
|
8bb4b0b9b2 | ||
|
9bcd1ed807 | ||
|
d7b1892bda | ||
|
ea0980ba83 | ||
|
b479a3ef99 | ||
|
600e15d42f | ||
|
824959e71a | ||
|
cfd8d231fb | ||
|
87c3dc2ecc | ||
|
590f0a83ea | ||
|
65e518856b | ||
|
469313eca7 | ||
|
f13f378d7f | ||
|
76d8017be5 | ||
|
af1bdc0c2c | ||
|
6ac1e05e60 | ||
|
7fa937e80b | ||
|
ed0e14aee5 | ||
|
e300c1aab4 | ||
|
9e1586878b | ||
|
5d54939bd4 | ||
|
3e9f98b43e | ||
|
c329ffa545 | ||
|
d3cad66b69 | ||
|
246af608d3 | ||
|
e7debc5466 | ||
|
5866300ac1 | ||
|
c1221aef4b | ||
|
bc879a3872 | ||
|
2b4a3d463b | ||
|
6b59b6de6e | ||
|
3ce5ccb98a | ||
|
a1b83def4f | ||
|
5177619301 | ||
|
4e0319bacc | ||
|
30f64af55f | ||
|
cdeabaaf9a | ||
|
fa676f60fb | ||
|
4ec123a19a | ||
|
de35bde048 | ||
|
3f786856f3 | ||
|
d49aecdf49 | ||
|
6713597621 | ||
|
09bd55675d | ||
|
d87dda40ee | ||
|
06cfc391a1 | ||
|
bea06a06a1 | ||
|
06fa2ab7a1 | ||
|
4926011f3f | ||
|
0455f8658d | ||
|
4773021ff0 | ||
|
e2bd1f6544 | ||
|
b73884160a | ||
|
8bcfa729b6 | ||
|
f8dda5b4a6 | ||
|
c0961d438d | ||
|
d9dd00eb39 | ||
|
6d9baaa499 | ||
|
b37ed5ab23 | ||
|
3fba741f1b | ||
|
73051d7d42 | ||
|
717e101b80 | ||
|
08f28d48af | ||
|
ec2f693575 | ||
|
3cc1bf52cb | ||
|
5da9f0cfb6 | ||
|
64624bea1d | ||
|
dc95395f42 | ||
|
273f188101 | ||
|
a6b22b5156 | ||
|
9d8a4cd934 | ||
|
c73dd5f664 | ||
|
7ff8c5e966 | ||
|
1c11aa13b8 | ||
|
0c2ec58366 | ||
![]() |
2955d7148f | ||
|
f9d87cb9f6 | ||
|
50fcc42e99 | ||
|
805456d032 | ||
|
a9df7906eb |
12
.codeclimate.yml
Normal file
12
.codeclimate.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
exclude_patterns:
|
||||
- public/lib/
|
||||
- public/js/lib/
|
||||
- public/fonts/
|
||||
- public/css/jquery-ui/
|
||||
- public/css/bootstrap-multiselect.css
|
||||
- public/css/bootstrap-sortable.css
|
||||
- public/css/bootstrap-tagsinput.css
|
||||
- public/css/daterangepicker.css
|
||||
- public/css/google-fonts.css
|
||||
- .sandstorm/
|
72
.env.docker
72
.env.docker
@@ -1,12 +1,24 @@
|
||||
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
|
||||
# Never set it to "testing".
|
||||
APP_ENV=${FF_APP_ENV}
|
||||
APP_DEBUG=false
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=${FF_APP_KEY}
|
||||
APP_LOG=daily
|
||||
APP_LOG_LEVEL=warning
|
||||
APP_URL=http://localhost
|
||||
TRUSTED_PROXIES=
|
||||
|
||||
# Set to true if you want to see debug information in error screens.
|
||||
APP_DEBUG=false
|
||||
|
||||
# This should be your email address
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
# The encryption key for your database and sessions. Keep this very secure.
|
||||
# If you generate a new one all existing data must be considered LOST.
|
||||
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
|
||||
APP_KEY=${FF_APP_KEY}
|
||||
|
||||
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
|
||||
APP_URL=${APP_URL}
|
||||
TRUSTED_PROXIES=${TRUSTED_PROXIES}
|
||||
|
||||
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
|
||||
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=${FF_DB_HOST}
|
||||
DB_PORT=3306
|
||||
@@ -14,19 +26,27 @@ DB_DATABASE=${FF_DB_NAME}
|
||||
DB_USERNAME=${FF_DB_USER}
|
||||
DB_PASSWORD=${FF_DB_PASSWORD}
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
|
||||
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
|
||||
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
|
||||
APP_LOG=daily
|
||||
|
||||
# Log level. You can set this from least severe to most severe:
|
||||
# debug, info, notice, warning, error, critical, alert, emergency
|
||||
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
|
||||
# nothing will get logged, ever.
|
||||
APP_LOG_LEVEL=warning
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached.
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
COOKIE_SECURE=false
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
# If you want Firefly III to mail you, update these settings
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
# Firefly III can send you the following messages
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
CACHE_PREFIX=firefly
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
IS_DOCKER=true
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=false
|
||||
|
64
.env.example
64
.env.example
@@ -1,12 +1,24 @@
|
||||
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
|
||||
# Never set it to "testing".
|
||||
APP_ENV=local
|
||||
|
||||
# Set to true if you want to see debug information in error screens.
|
||||
APP_DEBUG=false
|
||||
APP_NAME=FireflyIII
|
||||
|
||||
# This should be your email address
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
# The encryption key for your database and sessions. Keep this very secure.
|
||||
# If you generate a new one all existing data must be considered LOST.
|
||||
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
APP_LOG=daily
|
||||
APP_LOG_LEVEL=notice
|
||||
|
||||
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
|
||||
APP_URL=http://localhost
|
||||
TRUSTED_PROXIES=
|
||||
|
||||
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
|
||||
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
@@ -14,19 +26,27 @@ DB_DATABASE=homestead
|
||||
DB_USERNAME=homestead
|
||||
DB_PASSWORD=secret
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
|
||||
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
|
||||
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
|
||||
APP_LOG=daily
|
||||
|
||||
# Log level. You can set this from least severe to most severe:
|
||||
# debug, info, notice, warning, error, critical, alert, emergency
|
||||
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
|
||||
# nothing will get logged, ever.
|
||||
APP_LOG_LEVEL=notice
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached.
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
COOKIE_SECURE=false
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
# If you want Firefly III to mail you, update these settings
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
# Firefly III can send you the following messages
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
CACHE_PREFIX=firefly
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
IS_DOCKER=false
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=false
|
||||
|
72
.env.heroku
72
.env.heroku
@@ -1,12 +1,24 @@
|
||||
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
|
||||
# Never set it to "testing".
|
||||
APP_ENV=heroku
|
||||
APP_DEBUG=true
|
||||
APP_NAME=FireflyIII
|
||||
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
|
||||
APP_LOG=errorlog
|
||||
APP_LOG_LEVEL=debug
|
||||
APP_URL=http://localhost
|
||||
TRUSTED_PROXIES=*
|
||||
|
||||
# Set to true if you want to see debug information in error screens.
|
||||
APP_DEBUG=false
|
||||
|
||||
# This should be your email address
|
||||
SITE_OWNER=heroku@example.com
|
||||
|
||||
# The encryption key for your database and sessions. Keep this very secure.
|
||||
# If you generate a new one all existing data must be considered LOST.
|
||||
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
|
||||
APP_KEY=7ahyYVPVsmxjdhsweWCauGeJfwc92NP2
|
||||
|
||||
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
|
||||
APP_URL=http://localhost
|
||||
TRUSTED_PROXIES=
|
||||
|
||||
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
|
||||
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
|
||||
DB_CONNECTION=pgsql
|
||||
|
||||
|
||||
@@ -14,19 +26,27 @@ DB_CONNECTION=pgsql
|
||||
|
||||
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
|
||||
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
|
||||
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
|
||||
APP_LOG=errorlog
|
||||
|
||||
# Log level. You can set this from least severe to most severe:
|
||||
# debug, info, notice, warning, error, critical, alert, emergency
|
||||
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
|
||||
# nothing will get logged, ever.
|
||||
APP_LOG_LEVEL=debug
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached.
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
COOKIE_SECURE=false
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
# If you want Firefly III to mail you, update these settings
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
# Firefly III can send you the following messages
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=heroku@example.com
|
||||
|
||||
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
CACHE_PREFIX=firefly
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
IS_DOCKER=false
|
||||
IS_SANDSTORM=false
|
||||
IS_HEROKU=true
|
||||
|
@@ -1,12 +1,24 @@
|
||||
# You can leave this on "local". If you change it to production most console commands will ask for extra confirmation.
|
||||
# Never set it to "testing".
|
||||
APP_ENV=local
|
||||
|
||||
# Set to true if you want to see debug information in error screens.
|
||||
APP_DEBUG=false
|
||||
APP_NAME=FireflyIII
|
||||
|
||||
# This should be your email address
|
||||
SITE_OWNER=sandstorm@example.com
|
||||
|
||||
# The encryption key for your database and sessions. Keep this very secure.
|
||||
# If you generate a new one all existing data must be considered LOST.
|
||||
# Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it
|
||||
APP_KEY=SomeRandomStringOf32CharsExactly
|
||||
APP_LOG=syslog
|
||||
APP_LOG_LEVEL=info
|
||||
|
||||
# APP_URL and TRUSTED_PROXIES are useful when using Docker and/or a reverse proxy.
|
||||
APP_URL=http://localhost
|
||||
TRUSTED_PROXIES=
|
||||
|
||||
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
|
||||
# If you use SQLite, set connection to `sqlite` and remove the database, username and password settings.
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
@@ -14,19 +26,27 @@ DB_DATABASE=firefly
|
||||
DB_USERNAME=firefly
|
||||
DB_PASSWORD=firefly
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
# 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/.
|
||||
# Several other options exist. You can use 'single' for one big fat error log (not recommended).
|
||||
# Also available are 'syslog' and 'errorlog' which will log to the system itself.
|
||||
APP_LOG=syslog
|
||||
|
||||
# Log level. You can set this from least severe to most severe:
|
||||
# debug, info, notice, warning, error, critical, alert, emergency
|
||||
# If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
|
||||
# nothing will get logged, ever.
|
||||
APP_LOG_LEVEL=info
|
||||
|
||||
# If you're looking for performance improvements, you could install memcached.
|
||||
CACHE_DRIVER=file
|
||||
SESSION_DRIVER=file
|
||||
QUEUE_DRIVER=sync
|
||||
|
||||
# Cookie settings. Should not be necessary to change these.
|
||||
COOKIE_PATH="/"
|
||||
COOKIE_DOMAIN=
|
||||
COOKIE_SECURE=false
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
# If you want Firefly III to mail you, update these settings
|
||||
MAIL_DRIVER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
@@ -35,26 +55,36 @@ MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
|
||||
# Firefly III can send you the following messages
|
||||
SEND_REGISTRATION_MAIL=true
|
||||
SEND_ERROR_MESSAGE=true
|
||||
|
||||
CACHE_PREFIX=firefly
|
||||
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
|
||||
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
|
||||
MAPBOX_API_KEY=
|
||||
|
||||
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
|
||||
ANALYTICS_ID=
|
||||
SITE_OWNER=mail@example.com
|
||||
|
||||
# Most parts of the database are encrypted by default, but you can turn this off if you want to.
|
||||
# This makes it easier to migrate your database. Not that some fields will never be decrypted.
|
||||
USE_ENCRYPTION=true
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
CACHE_PREFIX=firefly
|
||||
SEARCH_RESULT_LIMIT=50
|
||||
EXCHANGE_RATE_SERVICE=fixerio
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
PUSHER_ID=
|
||||
|
||||
DEMO_USERNAME=
|
||||
DEMO_PASSWORD=
|
||||
|
||||
IS_DOCKER=false
|
||||
IS_SANDSTORM=true
|
||||
IS_HEROKU=false
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
## Feature requests
|
||||
|
||||
I am always interested in expanding Firefly III's many features. If you are requesting a new feature, please check out the list of [often requested features](https://firefly-iii.org/requested-features/).
|
||||
I am always interested in expanding Firefly III's many features. Just open a ticket or [drop me a line](mailto:thegrumpydictator@gmail.com).
|
||||
|
||||
## Pull requests
|
||||
|
0
.github/SUPPORT.md → .github/support.md
vendored
0
.github/SUPPORT.md → .github/support.md
vendored
14
.htaccess
Normal file
14
.htaccess
Normal file
@@ -0,0 +1,14 @@
|
||||
# Optional: force HTTPS:
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
|
||||
# </IfModule>
|
||||
|
||||
# To hide directory listing
|
||||
Options All -Indexes
|
||||
|
||||
# To prevent access to .env and other files
|
||||
<Files .*>
|
||||
Deny from all
|
||||
</Files>
|
@@ -3,10 +3,9 @@
|
||||
# This script only runs once, when the app connects to sandstorm.
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
echo "In build.sh"
|
||||
|
||||
cd /opt/app
|
||||
|
||||
cp .env.sandstorm .env
|
||||
|
||||
if [ -f /opt/app/composer.json ] ; then
|
||||
|
@@ -1,3 +1,50 @@
|
||||
# 4.7.0
|
||||
- Support for Russian and Portuguese (Brazil)
|
||||
- Support for the Spectre API (Salt Edge)
|
||||
- Many strings now translatable thanks to [Nik-vr](https://github.com/Nik-vr) ([issue 1118](https://github.com/firefly-iii/firefly-iii/issues/1118), [issue 1116](https://github.com/firefly-iii/firefly-iii/issues/1116), [issue 1109](https://github.com/firefly-iii/firefly-iii/issues/1109), )
|
||||
- Many buttons to quickly create stuff
|
||||
- Sum of tables in reports, requested by [MacPaille](https://github.com/MacPaille) ([issue 1106](https://github.com/firefly-iii/firefly-iii/issues/1106))
|
||||
- Future versions of Firefly III will notify you there is a new version, as suggested by [8bitgentleman](https://github.com/8bitgentleman) in [issue 1050](https://github.com/firefly-iii/firefly-iii/issues/1050)
|
||||
- Improved net worth box [issue 1101](https://github.com/firefly-iii/firefly-iii/issues/1101) ([Nik-vr](https://github.com/Nik-vr))
|
||||
- Nice dropdown in transaction list [issue 1082](https://github.com/firefly-iii/firefly-iii/issues/1082)
|
||||
- Better support for local fonts thanks to [devlearner](https://github.com/devlearner) ([issue 1145](https://github.com/firefly-iii/firefly-iii/issues/1145))
|
||||
- Improve attachment support and view capabilities (suggested by [trinhit](https://github.com/trinhit) in [issue 1146](https://github.com/firefly-iii/firefly-iii/issues/1146))
|
||||
- Whole new [read me file](https://github.com/firefly-iii/firefly-iii/blob/master/readme.md), [new end user documentation](https://firefly-iii.readthedocs.io/en/latest/) and an [updated website](https://www.firefly-iii.org/)!
|
||||
- Many charts and info-blocks now scale property ([issue 989](https://github.com/firefly-iii/firefly-iii/issues/989) and [issue 1040](https://github.com/firefly-iii/firefly-iii/issues/1040))
|
||||
- Charts work in IE thanks to [devlearner](https://github.com/devlearner) ([issue 1107](https://github.com/firefly-iii/firefly-iii/issues/1107))
|
||||
- Various fixes in import routine
|
||||
- Bug that left charts empty ([issue 1088](https://github.com/firefly-iii/firefly-iii/issues/1088)), reported by various users amongst which [jinformatique](https://github.com/jinformatique)
|
||||
- [Issue 1124](https://github.com/firefly-iii/firefly-iii/issues/1124), as reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1125](https://github.com/firefly-iii/firefly-iii/issues/1125), as reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1126](https://github.com/firefly-iii/firefly-iii/issues/1126), as reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1131](https://github.com/firefly-iii/firefly-iii/issues/1131), as reported by [dp87](https://github.com/dp87)
|
||||
- [Issue 1129](https://github.com/firefly-iii/firefly-iii/issues/1129), as reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1132](https://github.com/firefly-iii/firefly-iii/issues/1132), as reported by [gavu](https://github.com/gavu)
|
||||
- Issue with cache in Sandstorm ([issue 1130](https://github.com/firefly-iii/firefly-iii/issues/1130))
|
||||
- [Issue 1134](https://github.com/firefly-iii/firefly-iii/issues/1134)
|
||||
- [Issue 1140](https://github.com/firefly-iii/firefly-iii/issues/1140)
|
||||
- [Issue 1141](https://github.com/firefly-iii/firefly-iii/issues/1141), reported by [ErikFontanel](https://github.com/ErikFontanel)
|
||||
- [Issue 1142](https://github.com/firefly-iii/firefly-iii/issues/1142)
|
||||
- Removed many access rights from the demo user
|
||||
|
||||
# 4.6.13
|
||||
- [Issue 1074](https://github.com/firefly-iii/firefly-iii/issues/1074), suggested by [MacPaille](https://github.com/MacPaille)
|
||||
- [Issue 1077](https://github.com/firefly-iii/firefly-iii/issues/1077), suggested by [wtercato](https://github.com/wtercato)
|
||||
- Bulk edit of transactions thanks to [vicmosin](https://github.com/vicmosin) ([issue 1078](https://github.com/firefly-iii/firefly-iii/issues/1078))
|
||||
- Support for Turkish.
|
||||
- [Issue 1090](https://github.com/firefly-iii/firefly-iii/issues/1090), suggested by [Findus23](https://github.com/Findus23)
|
||||
- [Issue 1097](https://github.com/firefly-iii/firefly-iii/issues/1097), suggested by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1093](https://github.com/firefly-iii/firefly-iii/issues/1093), suggested by [jinformatique](https://github.com/jinformatique)
|
||||
- [Issue 1098](https://github.com/firefly-iii/firefly-iii/issues/1098), suggested by [Nik-vr](https://github.com/Nik-vr)
|
||||
- [Issue 972](https://github.com/firefly-iii/firefly-iii/issues/972), reported by [pjotrvdh](https://github.com/pjotrvdh)
|
||||
- [Issue 1079](https://github.com/firefly-iii/firefly-iii/issues/1079), reported by [gavu](https://github.com/gavu)
|
||||
- [Issue 1080](https://github.com/firefly-iii/firefly-iii/issues/1080), reported by [zjean](https://github.com/zjean)
|
||||
- [Issue 1083](https://github.com/firefly-iii/firefly-iii/issues/1083), reported by [skuzzle](https://github.com/skuzzle)
|
||||
- [Issue 1085](https://github.com/firefly-iii/firefly-iii/issues/1085), reported by [nicoschreiner](https://github.com/nicoschreiner)
|
||||
- [Issue 1087](https://github.com/firefly-iii/firefly-iii/issues/1087), reported by [4oo4](https://github.com/4oo4)
|
||||
- [Issue 1089](https://github.com/firefly-iii/firefly-iii/issues/1089), reported by [robin5210](https://github.com/robin5210)
|
||||
- [Issue 1092](https://github.com/firefly-iii/firefly-iii/issues/1092), reported by [kelvinhammond](https://github.com/kelvinhammond)
|
||||
- [Issue 1096](https://github.com/firefly-iii/firefly-iii/issues/1096), reported by [wtercato](https://github.com/wtercato)
|
||||
|
||||
# 4.6.12
|
||||
- Support for Indonesian.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Runs every time we create a new grain!
|
||||
echo "Now in launcher.sh"
|
||||
|
||||
# Create a bunch of folders under the clean /var that php, nginx, and mysql expect to exist
|
||||
mkdir -p /var/lib/mysql
|
||||
@@ -30,7 +31,6 @@ mkdir -p /var/storage/framework/views
|
||||
mkdir -p /var/storage/logs
|
||||
mkdir -p /var/storage/upload
|
||||
|
||||
|
||||
# Ensure mysql tables created
|
||||
HOME=/etc/mysql /usr/bin/mysql_install_db --force
|
||||
|
||||
@@ -58,5 +58,9 @@ echo "Migrating..."
|
||||
php /opt/app/artisan migrate --seed --force
|
||||
echo "Done!"
|
||||
|
||||
echo "Clear cache.."
|
||||
php /opt/app/artisan cache:clear
|
||||
echo "Done"
|
||||
|
||||
# Start nginx.
|
||||
/usr/sbin/nginx -c /opt/app/.sandstorm/service-config/nginx.conf -g "daemon off;"
|
||||
|
@@ -200,14 +200,23 @@ lib/x86_64-linux-gnu/libwrap.so.0.7.6
|
||||
lib/x86_64-linux-gnu/libz.so.1
|
||||
lib/x86_64-linux-gnu/libz.so.1.2.8
|
||||
lib64/ld-linux-x86-64.so.2
|
||||
opt/app/.dockerignore
|
||||
opt/app/.codeclimate.yml
|
||||
opt/app/.env
|
||||
opt/app/.env.docker
|
||||
opt/app/.env.example
|
||||
opt/app/.env.heroku
|
||||
opt/app/.env.sandstorm
|
||||
opt/app/.gitattributes
|
||||
opt/app/.htaccess
|
||||
opt/app/.sandstorm/.gitattributes
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_provision
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/action_set_name
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/creator_uid
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/id
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/index_uuid
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/private_key
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/synced_folders
|
||||
opt/app/.sandstorm/.vagrant/machines/default/virtualbox/vagrant_cwd
|
||||
opt/app/.sandstorm/Vagrantfile
|
||||
opt/app/.sandstorm/app-graphics/firefly-iii-128.png
|
||||
opt/app/.sandstorm/app-graphics/firefly-iii-150.png
|
||||
@@ -229,12 +238,7 @@ opt/app/.sandstorm/service-config/mime.types
|
||||
opt/app/.sandstorm/service-config/nginx.conf
|
||||
opt/app/.sandstorm/setup.sh
|
||||
opt/app/.sandstorm/stack
|
||||
opt/app/CHANGELOG.md
|
||||
opt/app/CODE_OF_CONDUCT.md
|
||||
opt/app/Dockerfile
|
||||
opt/app/LICENSE
|
||||
opt/app/Procfile
|
||||
opt/app/README.md
|
||||
opt/app/app.json
|
||||
opt/app/app/Console/Commands/CreateExport.php
|
||||
opt/app/app/Console/Commands/CreateImport.php
|
||||
@@ -359,6 +363,7 @@ opt/app/app/Http/Controllers/Chart/ReportController.php
|
||||
opt/app/app/Http/Controllers/Chart/TagReportController.php
|
||||
opt/app/app/Http/Controllers/Controller.php
|
||||
opt/app/app/Http/Controllers/CurrencyController.php
|
||||
opt/app/app/Http/Controllers/DebugController.php
|
||||
opt/app/app/Http/Controllers/ExportController.php
|
||||
opt/app/app/Http/Controllers/HelpController.php
|
||||
opt/app/app/Http/Controllers/HomeController.php
|
||||
@@ -389,6 +394,7 @@ opt/app/app/Http/Controllers/RuleController.php
|
||||
opt/app/app/Http/Controllers/RuleGroupController.php
|
||||
opt/app/app/Http/Controllers/SearchController.php
|
||||
opt/app/app/Http/Controllers/TagController.php
|
||||
opt/app/app/Http/Controllers/Transaction/BulkController.php
|
||||
opt/app/app/Http/Controllers/Transaction/ConvertController.php
|
||||
opt/app/app/Http/Controllers/Transaction/LinkController.php
|
||||
opt/app/app/Http/Controllers/Transaction/MassController.php
|
||||
@@ -416,6 +422,7 @@ opt/app/app/Http/Requests/AttachmentFormRequest.php
|
||||
opt/app/app/Http/Requests/BillFormRequest.php
|
||||
opt/app/app/Http/Requests/BudgetFormRequest.php
|
||||
opt/app/app/Http/Requests/BudgetIncomeRequest.php
|
||||
opt/app/app/Http/Requests/BulkEditJournalRequest.php
|
||||
opt/app/app/Http/Requests/CategoryFormRequest.php
|
||||
opt/app/app/Http/Requests/ConfigurationRequest.php
|
||||
opt/app/app/Http/Requests/CurrencyFormRequest.php
|
||||
@@ -625,10 +632,22 @@ opt/app/app/Services/Github/Request/GithubRequest.php
|
||||
opt/app/app/Services/Github/Request/UpdateRequest.php
|
||||
opt/app/app/Services/Password/PwndVerifier.php
|
||||
opt/app/app/Services/Password/Verifier.php
|
||||
opt/app/app/Services/Spectre/Exception/DuplicatedCustomerException.php
|
||||
opt/app/app/Services/Spectre/Exception/SpectreException.php
|
||||
opt/app/app/Services/Spectre/Object/Account.php
|
||||
opt/app/app/Services/Spectre/Object/Attempt.php
|
||||
opt/app/app/Services/Spectre/Object/Customer.php
|
||||
opt/app/app/Services/Spectre/Object/Holder.php
|
||||
opt/app/app/Services/Spectre/Object/Login.php
|
||||
opt/app/app/Services/Spectre/Object/SpectreObject.php
|
||||
opt/app/app/Services/Spectre/Object/Token.php
|
||||
opt/app/app/Services/Spectre/Object/Transaction.php
|
||||
opt/app/app/Services/Spectre/Object/TransactionExtra.php
|
||||
opt/app/app/Services/Spectre/Request/CreateTokenRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListAccountsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListCustomersRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListLoginsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/ListTransactionsRequest.php
|
||||
opt/app/app/Services/Spectre/Request/NewCustomerRequest.php
|
||||
opt/app/app/Services/Spectre/Request/SpectreRequest.php
|
||||
opt/app/app/Support/Amount.php
|
||||
@@ -657,7 +676,8 @@ opt/app/app/Support/Import/Configuration/ConfigurationInterface.php
|
||||
opt/app/app/Support/Import/Configuration/File/Initial.php
|
||||
opt/app/app/Support/Import/Configuration/File/Map.php
|
||||
opt/app/app/Support/Import/Configuration/File/Roles.php
|
||||
opt/app/app/Support/Import/Configuration/File/Upload.php
|
||||
opt/app/app/Support/Import/Configuration/File/UploadConfig.php
|
||||
opt/app/app/Support/Import/Configuration/Spectre/HaveAccounts.php
|
||||
opt/app/app/Support/Import/Information/BunqInformation.php
|
||||
opt/app/app/Support/Import/Information/InformationInterface.php
|
||||
opt/app/app/Support/Models/TransactionJournalTrait.php
|
||||
@@ -666,7 +686,6 @@ opt/app/app/Support/Preferences.php
|
||||
opt/app/app/Support/Search/Modifier.php
|
||||
opt/app/app/Support/Search/Search.php
|
||||
opt/app/app/Support/Search/SearchInterface.php
|
||||
opt/app/app/Support/SingleCacheProperties.php
|
||||
opt/app/app/Support/Steam.php
|
||||
opt/app/app/Support/Twig/AmountFormat.php
|
||||
opt/app/app/Support/Twig/Extension/Transaction.php
|
||||
@@ -741,6 +760,7 @@ opt/app/artisan
|
||||
opt/app/bootstrap/app.php
|
||||
opt/app/bootstrap/cache/packages.php
|
||||
opt/app/bootstrap/cache/services.php
|
||||
opt/app/changelog.md
|
||||
opt/app/composer.json
|
||||
opt/app/composer.lock
|
||||
opt/app/composer.phar
|
||||
@@ -763,7 +783,6 @@ opt/app/config/session.php
|
||||
opt/app/config/twigbridge.php
|
||||
opt/app/config/upgrade.php
|
||||
opt/app/config/view.php
|
||||
opt/app/crowdin.yml
|
||||
opt/app/database/factories/ModelFactory.php
|
||||
opt/app/database/migrations/2016_06_16_000000_create_support_tables.php
|
||||
opt/app/database/migrations/2016_06_16_000001_create_users_table.php
|
||||
@@ -786,8 +805,7 @@ opt/app/database/seeds/PermissionSeeder.php
|
||||
opt/app/database/seeds/TransactionCurrencySeeder.php
|
||||
opt/app/database/seeds/TransactionTypeSeeder.php
|
||||
opt/app/docker-compose.yml
|
||||
opt/app/nginx_app.conf
|
||||
opt/app/phpunit.coverage.xml
|
||||
opt/app/index.php
|
||||
opt/app/public/.htaccess
|
||||
opt/app/public/android-chrome-192x192.png
|
||||
opt/app/public/android-chrome-512x512.png
|
||||
@@ -810,56 +828,121 @@ opt/app/public/css/jquery-ui/jquery-ui.theme.min.css
|
||||
opt/app/public/favicon-16x16.png
|
||||
opt/app/public/favicon-32x32.png
|
||||
opt/app/public/favicon.ico
|
||||
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-Bold-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-BoldItalic-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-Italic-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-Light-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-LightItalic-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-Regular-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBold-vietnamese.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-cyrillic.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-greek.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin-ext.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-latin.woff2
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff
|
||||
opt/app/public/fonts/SourceSansPro-SemiBoldItalic-vietnamese.woff2
|
||||
opt/app/public/fonts/lato-100.woff
|
||||
opt/app/public/fonts/lato-100.woff2
|
||||
opt/app/public/fonts/roboto-light-300.woff
|
||||
opt/app/public/fonts/roboto-light-300.woff2
|
||||
opt/app/public/images/error.png
|
||||
opt/app/public/images/image.png
|
||||
@@ -920,6 +1003,7 @@ opt/app/public/js/ff/tags/create-edit.js
|
||||
opt/app/public/js/ff/tags/index.js
|
||||
opt/app/public/js/ff/tags/show.js
|
||||
opt/app/public/js/ff/transactions/list.js
|
||||
opt/app/public/js/ff/transactions/mass/edit-bulk.js
|
||||
opt/app/public/js/ff/transactions/mass/edit.js
|
||||
opt/app/public/js/ff/transactions/show.js
|
||||
opt/app/public/js/ff/transactions/single/common.js
|
||||
@@ -935,8 +1019,6 @@ opt/app/public/js/lib/bootstrap-tagsinput.min.js.map
|
||||
opt/app/public/js/lib/bootstrap3-typeahead.min.js
|
||||
opt/app/public/js/lib/daterangepicker.js
|
||||
opt/app/public/js/lib/html5shiv.min.js
|
||||
opt/app/public/js/lib/jquery-3.1.1.min.js
|
||||
opt/app/public/js/lib/jquery-3.1.1.min.map
|
||||
opt/app/public/js/lib/jquery-3.2.1.min.js
|
||||
opt/app/public/js/lib/jquery-3.2.1.min.map
|
||||
opt/app/public/js/lib/jquery-ui.min.js
|
||||
@@ -986,6 +1068,7 @@ opt/app/public/report.html
|
||||
opt/app/public/robots.txt
|
||||
opt/app/public/safari-pinned-tab.svg
|
||||
opt/app/public/web.config
|
||||
opt/app/readme.md
|
||||
opt/app/resources/lang/de_DE/auth.php
|
||||
opt/app/resources/lang/de_DE/bank.php
|
||||
opt/app/resources/lang/de_DE/breadcrumbs.php
|
||||
@@ -1074,6 +1157,48 @@ opt/app/resources/lang/pl_PL/list.php
|
||||
opt/app/resources/lang/pl_PL/pagination.php
|
||||
opt/app/resources/lang/pl_PL/passwords.php
|
||||
opt/app/resources/lang/pl_PL/validation.php
|
||||
opt/app/resources/lang/pt_BR/auth.php
|
||||
opt/app/resources/lang/pt_BR/bank.php
|
||||
opt/app/resources/lang/pt_BR/breadcrumbs.php
|
||||
opt/app/resources/lang/pt_BR/config.php
|
||||
opt/app/resources/lang/pt_BR/csv.php
|
||||
opt/app/resources/lang/pt_BR/demo.php
|
||||
opt/app/resources/lang/pt_BR/firefly.php
|
||||
opt/app/resources/lang/pt_BR/form.php
|
||||
opt/app/resources/lang/pt_BR/import.php
|
||||
opt/app/resources/lang/pt_BR/intro.php
|
||||
opt/app/resources/lang/pt_BR/list.php
|
||||
opt/app/resources/lang/pt_BR/pagination.php
|
||||
opt/app/resources/lang/pt_BR/passwords.php
|
||||
opt/app/resources/lang/pt_BR/validation.php
|
||||
opt/app/resources/lang/ru_RU/auth.php
|
||||
opt/app/resources/lang/ru_RU/bank.php
|
||||
opt/app/resources/lang/ru_RU/breadcrumbs.php
|
||||
opt/app/resources/lang/ru_RU/config.php
|
||||
opt/app/resources/lang/ru_RU/csv.php
|
||||
opt/app/resources/lang/ru_RU/demo.php
|
||||
opt/app/resources/lang/ru_RU/firefly.php
|
||||
opt/app/resources/lang/ru_RU/form.php
|
||||
opt/app/resources/lang/ru_RU/import.php
|
||||
opt/app/resources/lang/ru_RU/intro.php
|
||||
opt/app/resources/lang/ru_RU/list.php
|
||||
opt/app/resources/lang/ru_RU/pagination.php
|
||||
opt/app/resources/lang/ru_RU/passwords.php
|
||||
opt/app/resources/lang/ru_RU/validation.php
|
||||
opt/app/resources/lang/tr_TR/auth.php
|
||||
opt/app/resources/lang/tr_TR/bank.php
|
||||
opt/app/resources/lang/tr_TR/breadcrumbs.php
|
||||
opt/app/resources/lang/tr_TR/config.php
|
||||
opt/app/resources/lang/tr_TR/csv.php
|
||||
opt/app/resources/lang/tr_TR/demo.php
|
||||
opt/app/resources/lang/tr_TR/firefly.php
|
||||
opt/app/resources/lang/tr_TR/form.php
|
||||
opt/app/resources/lang/tr_TR/import.php
|
||||
opt/app/resources/lang/tr_TR/intro.php
|
||||
opt/app/resources/lang/tr_TR/list.php
|
||||
opt/app/resources/lang/tr_TR/pagination.php
|
||||
opt/app/resources/lang/tr_TR/passwords.php
|
||||
opt/app/resources/lang/tr_TR/validation.php
|
||||
opt/app/resources/stubs/binary.bin
|
||||
opt/app/resources/stubs/csv.csv
|
||||
opt/app/resources/stubs/demo-configuration.json
|
||||
@@ -1137,7 +1262,6 @@ opt/app/resources/views/demo/accounts/index.twig
|
||||
opt/app/resources/views/demo/budgets/index.twig
|
||||
opt/app/resources/views/demo/currencies/index.twig
|
||||
opt/app/resources/views/demo/home.twig
|
||||
opt/app/resources/views/demo/import/configure.twig
|
||||
opt/app/resources/views/demo/import/index.twig
|
||||
opt/app/resources/views/demo/index.twig
|
||||
opt/app/resources/views/demo/no-demo-text.twig
|
||||
@@ -1194,13 +1318,11 @@ opt/app/resources/views/import/bunq/prerequisites.twig
|
||||
opt/app/resources/views/import/file/initial.twig
|
||||
opt/app/resources/views/import/file/map.twig
|
||||
opt/app/resources/views/import/file/roles.twig
|
||||
opt/app/resources/views/import/file/upload.twig
|
||||
opt/app/resources/views/import/file/upload-config.twig
|
||||
opt/app/resources/views/import/index.twig
|
||||
opt/app/resources/views/import/spectre/input-fields.twig
|
||||
opt/app/resources/views/import/spectre/accounts.twig
|
||||
opt/app/resources/views/import/spectre/prerequisites.twig
|
||||
opt/app/resources/views/import/spectre/redirect.twig
|
||||
opt/app/resources/views/import/spectre/select-country.twig
|
||||
opt/app/resources/views/import/spectre/select-provider.twig
|
||||
opt/app/resources/views/import/status.twig
|
||||
opt/app/resources/views/index.twig
|
||||
opt/app/resources/views/javascript/accounts.twig
|
||||
@@ -1219,6 +1341,7 @@ opt/app/resources/views/list/piggy-bank-events.twig
|
||||
opt/app/resources/views/list/piggy-banks.twig
|
||||
opt/app/resources/views/new-user/index.twig
|
||||
opt/app/resources/views/partials/boxes.twig
|
||||
opt/app/resources/views/partials/breadcrumbs.twig
|
||||
opt/app/resources/views/partials/control-bar.twig
|
||||
opt/app/resources/views/partials/empty.twig
|
||||
opt/app/resources/views/partials/favicons.twig
|
||||
@@ -1297,10 +1420,11 @@ opt/app/resources/views/tags/edit.twig
|
||||
opt/app/resources/views/tags/index.twig
|
||||
opt/app/resources/views/tags/show.twig
|
||||
opt/app/resources/views/test/test.twig
|
||||
opt/app/resources/views/transactions/bulk/edit.twig
|
||||
opt/app/resources/views/transactions/convert.twig
|
||||
opt/app/resources/views/transactions/index.twig
|
||||
opt/app/resources/views/transactions/links/delete.twig
|
||||
opt/app/resources/views/transactions/mass-delete.twig
|
||||
opt/app/resources/views/transactions/mass/delete.twig
|
||||
opt/app/resources/views/transactions/mass/edit.twig
|
||||
opt/app/resources/views/transactions/show.twig
|
||||
opt/app/resources/views/transactions/single/create.twig
|
||||
@@ -2280,10 +2404,8 @@ opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/boots
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/none-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/Example.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/react-stubs/webpack.mix.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/ExampleComponent.vue
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/app.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/Presets/vue-stubs/webpack.mix.js
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/ProviderMakeCommand.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/QueuedCommand.php
|
||||
opt/app/vendor/laravel/framework/src/Illuminate/Foundation/Console/RequestMakeCommand.php
|
||||
@@ -2801,6 +2923,7 @@ opt/app/vendor/league/commonmark/src/Block/Element/Heading.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/HtmlBlock.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/IndentedCode.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainer.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/InlineContainerInterface.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListBlock.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListData.php
|
||||
opt/app/vendor/league/commonmark/src/Block/Element/ListItem.php
|
||||
@@ -2833,7 +2956,6 @@ opt/app/vendor/league/commonmark/src/Context.php
|
||||
opt/app/vendor/league/commonmark/src/ContextInterface.php
|
||||
opt/app/vendor/league/commonmark/src/Converter.php
|
||||
opt/app/vendor/league/commonmark/src/Cursor.php
|
||||
opt/app/vendor/league/commonmark/src/CursorState.php
|
||||
opt/app/vendor/league/commonmark/src/Delimiter/Delimiter.php
|
||||
opt/app/vendor/league/commonmark/src/Delimiter/DelimiterStack.php
|
||||
opt/app/vendor/league/commonmark/src/DocParser.php
|
||||
@@ -2932,6 +3054,7 @@ opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v2.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/aws-s3-v3.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/azure.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/copy.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/digitalocean-spaces.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/dropbox.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/ftp.md
|
||||
opt/app/vendor/league/flysystem/docs/adapter/gridfs.md
|
||||
@@ -2951,10 +3074,15 @@ opt/app/vendor/league/flysystem/docs/creating-an-adapter.md
|
||||
opt/app/vendor/league/flysystem/docs/index.md
|
||||
opt/app/vendor/league/flysystem/docs/installation.md
|
||||
opt/app/vendor/league/flysystem/docs/integrations.md
|
||||
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button.png
|
||||
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@2x.png
|
||||
opt/app/vendor/league/flysystem/docs/logo/become_a_patron_button@3x.png
|
||||
opt/app/vendor/league/flysystem/docs/logo/laravel.svg
|
||||
opt/app/vendor/league/flysystem/docs/mount-manager.md
|
||||
opt/app/vendor/league/flysystem/docs/performance.md
|
||||
opt/app/vendor/league/flysystem/docs/plugins.md
|
||||
opt/app/vendor/league/flysystem/docs/recipes.md
|
||||
opt/app/vendor/league/flysystem/docs/sponsors.md
|
||||
opt/app/vendor/league/flysystem/docs/upgrade-to-1.0.0.md
|
||||
opt/app/vendor/league/flysystem/src/Adapter/AbstractAdapter.php
|
||||
opt/app/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php
|
||||
@@ -3002,6 +3130,7 @@ opt/app/vendor/league/flysystem/src/Util.php
|
||||
opt/app/vendor/league/flysystem/src/Util/ContentListingFormatter.php
|
||||
opt/app/vendor/league/flysystem/src/Util/MimeType.php
|
||||
opt/app/vendor/league/flysystem/src/Util/StreamHasher.php
|
||||
opt/app/vendor/league/flysystem/wait_for_ftp_service.php
|
||||
opt/app/vendor/monolog/monolog/.php_cs
|
||||
opt/app/vendor/monolog/monolog/CHANGELOG.md
|
||||
opt/app/vendor/monolog/monolog/LICENSE
|
||||
@@ -3336,10 +3465,10 @@ opt/app/vendor/pragmarx/google2fa-laravel/src/config/config.php
|
||||
opt/app/vendor/pragmarx/google2fa-laravel/tests/spec/Support/AuthenticatorSpec.php
|
||||
opt/app/vendor/pragmarx/google2fa-laravel/upgrading.md
|
||||
opt/app/vendor/pragmarx/google2fa/LICENSE
|
||||
opt/app/vendor/pragmarx/google2fa/README.md
|
||||
opt/app/vendor/pragmarx/google2fa/changelog.md
|
||||
opt/app/vendor/pragmarx/google2fa/composer.json
|
||||
opt/app/vendor/pragmarx/google2fa/docs/playground.jpg
|
||||
opt/app/vendor/pragmarx/google2fa/readme.md
|
||||
opt/app/vendor/pragmarx/google2fa/src/Exceptions/IncompatibleWithGoogleAuthenticatorException.php
|
||||
opt/app/vendor/pragmarx/google2fa/src/Exceptions/InvalidCharactersException.php
|
||||
opt/app/vendor/pragmarx/google2fa/src/Exceptions/SecretKeyTooShortException.php
|
||||
@@ -3348,6 +3477,7 @@ opt/app/vendor/pragmarx/google2fa/src/Support/Base32.php
|
||||
opt/app/vendor/pragmarx/google2fa/src/Support/Constants.php
|
||||
opt/app/vendor/pragmarx/google2fa/src/Support/QRCode.php
|
||||
opt/app/vendor/pragmarx/google2fa/src/Support/Url.php
|
||||
opt/app/vendor/pragmarx/google2fa/tests/Constants.php
|
||||
opt/app/vendor/pragmarx/google2fa/tests/Google2FATest.php
|
||||
opt/app/vendor/pragmarx/google2fa/tests/bootstrap.php
|
||||
opt/app/vendor/pragmarx/google2fa/upgrading.md
|
||||
@@ -3382,6 +3512,9 @@ opt/app/vendor/ramsey/uuid/CONTRIBUTING.md
|
||||
opt/app/vendor/ramsey/uuid/LICENSE
|
||||
opt/app/vendor/ramsey/uuid/README.md
|
||||
opt/app/vendor/ramsey/uuid/composer.json
|
||||
opt/app/vendor/ramsey/uuid/docs/Makefile
|
||||
opt/app/vendor/ramsey/uuid/docs/conf.py
|
||||
opt/app/vendor/ramsey/uuid/docs/index.rst
|
||||
opt/app/vendor/ramsey/uuid/src/BinaryUtils.php
|
||||
opt/app/vendor/ramsey/uuid/src/Builder/DefaultUuidBuilder.php
|
||||
opt/app/vendor/ramsey/uuid/src/Builder/DegradedUuidBuilder.php
|
||||
@@ -4049,6 +4182,8 @@ opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_7
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_8.txt
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/Style/SymfonyStyle/output/output_9.txt
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestCommand.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestTiti.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/TestToto.php
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.json
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.md
|
||||
opt/app/vendor/symfony/console/Tests/Fixtures/application_1.txt
|
||||
@@ -4335,6 +4470,8 @@ opt/app/vendor/symfony/debug/Tests/Fixtures/reallyNotPsr0.php
|
||||
opt/app/vendor/symfony/debug/Tests/Fixtures2/RequiredTwice.php
|
||||
opt/app/vendor/symfony/debug/Tests/HeaderMock.php
|
||||
opt/app/vendor/symfony/debug/Tests/MockExceptionHandler.php
|
||||
opt/app/vendor/symfony/debug/Tests/phpt/debug_class_loader.phpt
|
||||
opt/app/vendor/symfony/debug/Tests/phpt/decorate_exception_hander.phpt
|
||||
opt/app/vendor/symfony/debug/Tests/phpt/exception_rethrown.phpt
|
||||
opt/app/vendor/symfony/debug/Tests/phpt/fatal_with_nested_handlers.phpt
|
||||
opt/app/vendor/symfony/debug/composer.json
|
||||
@@ -4751,6 +4888,8 @@ opt/app/vendor/symfony/http-kernel/Tests/EventListener/LocaleListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ProfilerListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/ResponseListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/RouterListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SaveSessionListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SessionListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/SurrogateListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TestSessionListenerTest.php
|
||||
opt/app/vendor/symfony/http-kernel/Tests/EventListener/TranslatorListenerTest.php
|
||||
@@ -4962,6 +5101,7 @@ opt/app/vendor/symfony/routing/Tests/Fixtures/AnnotatedClasses/FooTrait.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomCompiledRoute.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomRouteCompiler.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/CustomXmlFileLoader.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/AnonymousClassInTrait.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/NoStartTagClass.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/OtherAnnotatedClasses/VariadicClass.php
|
||||
opt/app/vendor/symfony/routing/Tests/Fixtures/RedirectableUrlMatcher.php
|
||||
@@ -6161,7 +6301,6 @@ opt/app/vendor/watson/validating/src/ValidatingModel.php
|
||||
opt/app/vendor/watson/validating/src/ValidatingObserver.php
|
||||
opt/app/vendor/watson/validating/src/ValidatingTrait.php
|
||||
opt/app/vendor/watson/validating/src/ValidationException.php
|
||||
opt/app/webpack.mix.js
|
||||
proc/cpuinfo
|
||||
sandstorm-http-bridge
|
||||
sandstorm-http-bridge-config
|
||||
|
@@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
|
||||
|
||||
manifest = (
|
||||
appTitle = (defaultText = "Firefly III"),
|
||||
appVersion = 6,
|
||||
appMarketingVersion = (defaultText = "4.6.12"),
|
||||
appVersion = 8,
|
||||
appMarketingVersion = (defaultText = "4.7.0"),
|
||||
|
||||
actions = [
|
||||
# Define your "new document" handlers here.
|
||||
|
@@ -57,6 +57,7 @@ http {
|
||||
fastcgi_index index.php;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_read_timeout 900;
|
||||
|
||||
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
# When you change this file, you must take manual action. Read this doc:
|
||||
# - https://docs.sandstorm.io/en/latest/vagrant-spk/customizing/#setupsh
|
||||
echo "Now in setup.sh"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -14,8 +15,11 @@ apt-get install -y python-software-properties software-properties-common
|
||||
# install all languages
|
||||
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# id_ID.UTF-8 UTF-8/id_ID.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# pl_PL.UTF-8 UTF-8/pl_PL.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
sed -i 's/# tr_TR.UTF-8 UTF-8/tr_TR.UTF-8 UTF-8/g' /etc/locale.gen
|
||||
dpkg-reconfigure --frontend=noninteractive locales
|
||||
|
||||
|
||||
|
105
.scrutinizer.yml
105
.scrutinizer.yml
@@ -1,51 +1,58 @@
|
||||
# .scrutinizer.yml
|
||||
tools:
|
||||
external_code_coverage: false
|
||||
filter:
|
||||
paths:
|
||||
---
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
checks:
|
||||
javascript: true
|
||||
php:
|
||||
align_assignments: true
|
||||
avoid_fixme_comments: true
|
||||
avoid_multiple_statements_on_same_line: true
|
||||
avoid_perl_style_comments: true
|
||||
avoid_todo_comments: true
|
||||
duplication: false
|
||||
encourage_single_quotes: true
|
||||
newline_at_end_of_file: true
|
||||
no_goto: true
|
||||
no_long_variable_names:
|
||||
maximum: "20"
|
||||
no_short_method_names:
|
||||
minimum: "3"
|
||||
no_short_variable_names:
|
||||
minimum: "3"
|
||||
optional_parameters_at_the_end: true
|
||||
parameter_doc_comments: true
|
||||
remove_extra_empty_lines: true
|
||||
return_doc_comment_if_not_inferrable: true
|
||||
return_doc_comments: true
|
||||
uppercase_constants: true
|
||||
use_self_instead_of_fqcn: true
|
||||
coding_style:
|
||||
php:
|
||||
spaces:
|
||||
around_operators:
|
||||
concatenation: true
|
||||
other:
|
||||
after_type_cast: false
|
||||
filter:
|
||||
excluded_paths:
|
||||
- database/migrations/*
|
||||
- bootstrap/*
|
||||
- config/*
|
||||
- docker/*
|
||||
- public/js/lib/*
|
||||
- public/lib/adminlte/js/*
|
||||
- public/lib/bootstrap/js/*
|
||||
- resources/*
|
||||
- routes/*
|
||||
- storage/*
|
||||
paths:
|
||||
- app/*
|
||||
- public/js/ff/*
|
||||
excluded_paths:
|
||||
- "database/migrations/*"
|
||||
- "bootstrap/*"
|
||||
- "config/*"
|
||||
- "docker/*"
|
||||
- "public/js/lib/*"
|
||||
- "public/lib/adminlte/js/*"
|
||||
- "public/lib/bootstrap/js/*"
|
||||
- "resources/*"
|
||||
- "routes/*"
|
||||
- "storage/*"
|
||||
checks:
|
||||
php:
|
||||
use_self_instead_of_fqcn: true
|
||||
uppercase_constants: true
|
||||
return_doc_comments: true
|
||||
return_doc_comment_if_not_inferrable: true
|
||||
remove_extra_empty_lines: true
|
||||
parameter_doc_comments: true
|
||||
optional_parameters_at_the_end: true
|
||||
no_short_variable_names:
|
||||
minimum: '3'
|
||||
no_short_method_names:
|
||||
minimum: '3'
|
||||
no_long_variable_names:
|
||||
maximum: '20'
|
||||
no_goto: true
|
||||
newline_at_end_of_file: true
|
||||
encourage_single_quotes: true
|
||||
avoid_todo_comments: true
|
||||
avoid_perl_style_comments: true
|
||||
avoid_fixme_comments: true
|
||||
avoid_multiple_statements_on_same_line: true
|
||||
align_assignments: true
|
||||
duplication: false
|
||||
javascript: true
|
||||
|
||||
coding_style:
|
||||
php:
|
||||
spaces:
|
||||
around_operators:
|
||||
concatenation: true
|
||||
other:
|
||||
after_type_cast: false
|
||||
tools:
|
||||
external_code_coverage: false
|
||||
|
@@ -24,6 +24,7 @@ script:
|
||||
|
||||
after_success:
|
||||
- travis_retry php vendor/bin/php-coveralls -x storage/build/clover-all.xml
|
||||
- bash <(curl -s https://codecov.io/bash) -f storage/build/clover-all.xml
|
||||
|
||||
# safelist
|
||||
branches:
|
||||
|
85
README.md
85
README.md
@@ -1,85 +0,0 @@
|
||||
# Firefly III: A personal finances manager
|
||||
|
||||
[](https://secure.php.net/downloads.php) [](https://packagist.org/packages/grumpydictator/firefly-iii) [](https://www.gnu.org/licenses/gpl.html) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
|
||||
|
||||
[](https://firefly-iii.org/static/screenshots/4.6.12/index.png) [](https://firefly-iii.org/static/screenshots/4.6.12/account.png)
|
||||
|
||||
[](https://firefly-iii.org/static/screenshots/4.6.12/budget.png) [](https://firefly-iii.org/static/screenshots/4.6.12/category.png)
|
||||
|
||||
[](https://firefly-iii.org/static/screenshots/4.6.12/report1.png) [](https://firefly-iii.org/static/screenshots/4.6.12/report2.png)
|
||||
|
||||
"Firefly III" is a financial manager for your personal finances. It can help you keep track of your expenses and income.
|
||||
Firefly III supports the use of budgets. You can categorize and tag your transactions.
|
||||
It also supports credit cards, shared household accounts and savings accounts.
|
||||
There are many financial reports available.
|
||||
|
||||
## Want to try Firefly III?
|
||||
|
||||
There is a **[demo site](https://demo.firefly-iii.org)** with an example financial administration already present. You can use Docker, Heroku or Sandstorm.io (see below) to quickly setup your own instance.
|
||||
|
||||
## Install Firefly III
|
||||
|
||||
### Using docker
|
||||
|
||||
You can use docker-compose to [set up your personal secure](https://firefly-iii.org/using-docker.html) Firefly III environment. Advanced users can use the Dockerfile directly.
|
||||
|
||||
### Using vagrant (or other VMs)
|
||||
|
||||
You can install Firefly III on any Linux or Windows machine. You'll need a web server (preferrably on Linux) and access to the command line. Please read the [installation guide](https://firefly-iii.org/using-installing.html).
|
||||
|
||||
### Using Heroku
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/firefly-iii/firefly-iii/tree/master)
|
||||
|
||||
Register for a free Heroku account and instantly run Firefly III on your very own cloud instance.
|
||||
|
||||
### Using Sandstorm.io
|
||||
|
||||
You can find Firefly III in [the Sandstorm.io marketplace](https://apps.sandstorm.io/app/uws252ya9mep4t77tevn85333xzsgrpgth8q4y1rhknn1hammw70). You can run it on your own installation or on Oasis.
|
||||
|
||||
## More about Firefly III
|
||||
|
||||
Personal financial management is pretty difficult, and everybody has their own approach to it.
|
||||
Some people make budgets, other people limit their cashflow by throwing away their credit cards,
|
||||
others try to increase their current cashflow. There are tons of ways to save and earn money.
|
||||
|
||||
Firefly works on the principle that if you know where you're money is going, you can stop it from going there.
|
||||
|
||||
### Some advantages of using Firefly
|
||||
|
||||
- Firefly can import any CSV file, so migrating from other systems is easy.
|
||||
- Firefly runs on your own server, so you are fully in control of your data. Remember, there is no such thing as "the cloud", it’s just somebody else’s computer!
|
||||
- Firefly has lots of features without being fancy or bloated.
|
||||
- If you feel you're missing something you can just ask me and I'll add it!
|
||||
|
||||
Firefly III has become pretty awesome over the years! (but please excuse me for bragging, it's just that I'm proud of it).
|
||||
[You can read more about Firefly III, and its features, on the website](https://firefly-iii.org/).
|
||||
|
||||
### Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](https://github.com/firefly-iii/firefly-iii/blob/master/.github/CONTRIBUTING.md) for details on contributing, and the process for submitting pull requests. Please check out the [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/master/CODE_OF_CONDUCT.md) as well.
|
||||
|
||||
### Versioning
|
||||
|
||||
We use [SemVer](http://semver.org/) for versioning. For the versions available, see [the tags](https://github.com/firefly-iii/firefly-iii/tags) on this repository.
|
||||
|
||||
### Authors
|
||||
|
||||
* James Cole
|
||||
* Over time, [many people have contributed to Firefly III](https://github.com/firefly-iii/firefly-iii/graphs/contributors).
|
||||
|
||||
### License
|
||||
|
||||
This work [is licensed](https://github.com/firefly-iii/firefly-iii/blob/master/LICENSE) under the [GPL v3](https://www.gnu.org/licenses/gpl.html).
|
||||
|
||||
### Other stuff
|
||||
|
||||
If you like Firefly III and if it helps you save lots of money, why not send me [a dime for every dollar saved](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA) (this is a joke, although the Paypal form works just fine, try it!)
|
||||
|
||||
If you want to contact me, please open an issue or [email me](mailto:thegrumpydictator@gmail.com).
|
||||
|
||||
### Alternatives
|
||||
|
||||
If you are looking for alternatives, check out [Kickball's Awesome-Selfhosted list](https://github.com/Kickball/awesome-selfhosted) which features not only Firefly III but also noteworthy alternatives such as [Silverstrike](https://github.com/agstrike/silverstrike).
|
||||
|
||||
[](https://travis-ci.org/firefly-iii/firefly-iii) [](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)
|
@@ -178,7 +178,7 @@ class CreateImport extends Command
|
||||
$cwd = getcwd();
|
||||
$validTypes = config('import.options.file.import_formats');
|
||||
$type = strtolower($this->option('type'));
|
||||
if (null === $user->id) {
|
||||
if (null === $user) {
|
||||
$this->error(sprintf('There is no user with ID %d.', $this->option('user')));
|
||||
|
||||
return false;
|
||||
|
@@ -146,6 +146,7 @@ class UpgradeDatabase extends Command
|
||||
|
||||
// both 0? set to default currency:
|
||||
if (0 === $accountCurrency && 0 === $obCurrency) {
|
||||
AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
@@ -218,8 +219,8 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
if ($transaction->transaction_currency_id !== $currency->id) {
|
||||
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
|
||||
if (!(intval($transaction->transaction_currency_id) === intval($currency->id))) {
|
||||
$transaction->foreign_currency_id = intval($transaction->transaction_currency_id);
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
@@ -401,24 +402,24 @@ class UpgradeDatabase extends Command
|
||||
|
||||
// has no currency ID? Must have, so fill in using account preference:
|
||||
if (null === $transaction->transaction_currency_id) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->transaction_currency_id = intval($currency->id);
|
||||
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code));
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// does not match the source account (see above)? Can be fixed
|
||||
// when mismatch in transaction and NO foreign amount is set:
|
||||
if ($transaction->transaction_currency_id !== $currency->id && null === $transaction->foreign_amount) {
|
||||
if (!(intval($transaction->transaction_currency_id) === intval($currency->id)) && null === $transaction->foreign_amount) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Transaction #%d has a currency setting (#%d) that should be #%d. Amount remains %s, currency is changed.',
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$transaction->id,
|
||||
$transaction->transaction_currency_id,
|
||||
$currency->id,
|
||||
$transaction->amount
|
||||
)
|
||||
);
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->transaction_currency_id = intval($currency->id);
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
@@ -436,7 +437,7 @@ class UpgradeDatabase extends Command
|
||||
}
|
||||
|
||||
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
|
||||
if ($opposingCurrency->id === $currency->id) {
|
||||
if (intval($opposingCurrency->id) === intval($currency->id)) {
|
||||
// update both transactions to match:
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->foreign_currency_id = null;
|
||||
@@ -450,7 +451,7 @@ class UpgradeDatabase extends Command
|
||||
return;
|
||||
}
|
||||
// if destination account currency is different, both transactions must have this currency as foreign currency id.
|
||||
if ($opposingCurrency->id !== $currency->id) {
|
||||
if (!(intval($opposingCurrency->id) === intval($currency->id))) {
|
||||
$transaction->foreign_currency_id = $opposingCurrency->id;
|
||||
$opposing->foreign_currency_id = $opposingCurrency->id;
|
||||
$transaction->save();
|
||||
@@ -500,4 +501,5 @@ class UpgradeDatabase extends Command
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ trait VerifiesAccessToken
|
||||
/**
|
||||
* Abstract method to make sure trait knows about method "option".
|
||||
*
|
||||
* @param null $key
|
||||
* @param string|null $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -55,7 +55,7 @@ trait VerifiesAccessToken
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$user = $repository->find($userId);
|
||||
|
||||
if (null === $user->id) {
|
||||
if (null === $user) {
|
||||
Log::error(sprintf('verifyAccessToken(): no such user for input "%d"', $userId));
|
||||
|
||||
return false;
|
||||
|
@@ -52,7 +52,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// @var AttachmentRepositoryInterface repository
|
||||
/** @var AttachmentRepositoryInterface repository */
|
||||
$this->repository = app(AttachmentRepositoryInterface::class);
|
||||
// make storage:
|
||||
$this->uploadDisk = Storage::disk('upload');
|
||||
|
@@ -26,6 +26,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Generator\Report\ReportGeneratorInterface;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Steam;
|
||||
@@ -173,7 +174,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
$startBalance = $dayBeforeBalance;
|
||||
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||
|
||||
// @var Transaction $journal
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journals as $transaction) {
|
||||
$transaction->before = $startBalance;
|
||||
$transactionAmount = $transaction->transaction_amount;
|
||||
|
@@ -194,7 +194,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||
*/
|
||||
private function summarizeByBudget(Collection $collection): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [
|
||||
'sum' => '0',
|
||||
];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($collection as $transaction) {
|
||||
$jrnlBudId = intval($transaction->transaction_journal_budget_id);
|
||||
@@ -202,6 +204,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface
|
||||
$budgetId = max($jrnlBudId, $transBudId);
|
||||
$result[$budgetId] = $result[$budgetId] ?? '0';
|
||||
$result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]);
|
||||
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@@ -102,7 +102,12 @@ class Support
|
||||
*/
|
||||
protected function getObjectSummary(array $spent, array $earned): array
|
||||
{
|
||||
$return = [];
|
||||
$return = [
|
||||
'sum' => [
|
||||
'spent' => '0',
|
||||
'earned' => '0',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
@@ -110,10 +115,11 @@ class Support
|
||||
*/
|
||||
foreach ($spent as $objectId => $entry) {
|
||||
if (!isset($return[$objectId])) {
|
||||
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
|
||||
}
|
||||
|
||||
$return[$objectId]['spent'] = $entry;
|
||||
$return['sum']['spent'] = bcadd($return['sum']['spent'], $entry);
|
||||
}
|
||||
unset($entry);
|
||||
|
||||
@@ -123,10 +129,11 @@ class Support
|
||||
*/
|
||||
foreach ($earned as $objectId => $entry) {
|
||||
if (!isset($return[$objectId])) {
|
||||
$return[$objectId] = ['spent' => 0, 'earned' => 0];
|
||||
$return[$objectId] = ['spent' => '0', 'earned' => '0'];
|
||||
}
|
||||
|
||||
$return[$objectId]['earned'] = $entry;
|
||||
$return['sum']['earned'] = bcadd($return['sum']['earned'], $entry);
|
||||
}
|
||||
|
||||
return $return;
|
||||
@@ -139,12 +146,15 @@ class Support
|
||||
*/
|
||||
protected function summarizeByAccount(Collection $collection): array
|
||||
{
|
||||
$result = [];
|
||||
$result = [
|
||||
'sum' => '0',
|
||||
];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($collection as $transaction) {
|
||||
$accountId = $transaction->account_id;
|
||||
$result[$accountId] = $result[$accountId] ?? '0';
|
||||
$result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]);
|
||||
$result['sum'] = bcadd($result['sum'], $transaction->transaction_amount);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@@ -25,11 +25,11 @@ namespace FireflyIII\Handlers\Events;
|
||||
use FireflyIII\Events\RegisteredUser;
|
||||
use FireflyIII\Events\RequestedNewPassword;
|
||||
use FireflyIII\Events\UserChangedEmail;
|
||||
use FireflyIII\Factories\RoleFactory;
|
||||
use FireflyIII\Mail\ConfirmEmailChangeMail;
|
||||
use FireflyIII\Mail\RegisteredUser as RegisteredUserMail;
|
||||
use FireflyIII\Mail\RequestedNewPassword as RequestedNewPasswordMail;
|
||||
use FireflyIII\Mail\UndoEmailChangeMail;
|
||||
use FireflyIII\Models\Role;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
@@ -74,11 +74,12 @@ class UserEventHandler
|
||||
*/
|
||||
public function checkSingleUserIsAdmin(Login $event): bool
|
||||
{
|
||||
Log::debug('In checkSingleUserIsAdmin');
|
||||
/** @var UserRepositoryInterface $repository */
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
$count = User::count();
|
||||
$count = $repository->count();
|
||||
|
||||
if ($count > 1) {
|
||||
// if more than one user, do nothing.
|
||||
@@ -93,17 +94,16 @@ class UserEventHandler
|
||||
return true;
|
||||
}
|
||||
// user is the only user but does not have role "owner".
|
||||
$role = Role::where('name', 'owner')->first();
|
||||
$role = $repository->getRole('owner');
|
||||
if (is_null($role)) {
|
||||
// create role, does not exist. Very strange situation so let's raise a big fuss about it.
|
||||
$role = Role::create(['name' => 'owner', 'display_name' => 'Site Owner', 'description' => 'User runs this instance of FF3']);
|
||||
$role = $repository->createRole('owner', 'Site Owner', 'User runs this instance of FF3');
|
||||
Log::error('Could not find role "owner". This is weird.');
|
||||
}
|
||||
|
||||
Log::info(sprintf('Gave user #%d role #%d ("%s")', $user->id, $role->id, $role->name));
|
||||
// give user the role
|
||||
$user->attachRole($role);
|
||||
$user->save();
|
||||
$repository->attachRole($user, 'owner');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -25,6 +25,9 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Events\RequestedVersionCheckStatus;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Services\Github\Object\Release;
|
||||
use FireflyIII\Services\Github\Request\UpdateRequest;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
|
||||
@@ -45,6 +48,7 @@ class VersionCheckEventHandler
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
if (!$user->hasRole('owner')) {
|
||||
@@ -55,7 +59,7 @@ class VersionCheckEventHandler
|
||||
$lastCheckTime = FireflyConfig::get('last_update_check', time());
|
||||
$now = time();
|
||||
if ($now - $lastCheckTime->data < 604800) {
|
||||
Log::debug('Checked for updates less than a week ago.');
|
||||
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
|
||||
|
||||
return;
|
||||
|
||||
@@ -71,8 +75,42 @@ class VersionCheckEventHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// actually check for update and inform the user.
|
||||
$current = config('firefly.version');
|
||||
/** @var UpdateRequest $request */
|
||||
$request = app(UpdateRequest::class);
|
||||
$check = -2;
|
||||
$first = new Release(['id' => '0', 'title' => '0', 'updated' => '2017-01-01', 'content' => '']);
|
||||
try {
|
||||
$request->call();
|
||||
$releases = $request->getReleases();
|
||||
// first entry should be the latest entry:
|
||||
/** @var Release $first */
|
||||
$first = reset($releases);
|
||||
$check = version_compare($current, $first->getTitle());
|
||||
FireflyConfig::set('last_update_check', time());
|
||||
} catch (FireflyException $e) {
|
||||
Log::error(sprintf('Could not check for updates: %s', $e->getMessage()));
|
||||
}
|
||||
$string = 'no result: ' . $check;
|
||||
if ($check === -2) {
|
||||
$string = strval(trans('firefly.update_check_error'));
|
||||
}
|
||||
if ($check === -1) {
|
||||
// there is a new FF version!
|
||||
$monthAndDayFormat = (string)trans('config.month_and_day');
|
||||
$string = strval(
|
||||
trans(
|
||||
'firefly.update_new_version_alert',
|
||||
['your_version' => $current, 'new_version' => $first->getTitle(), 'date' => $first->getUpdated()->formatLocalized($monthAndDayFormat)]
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($check !== 0) {
|
||||
// flash info
|
||||
session()->flash('info', $string);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
@@ -37,6 +37,7 @@ use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
@@ -232,7 +233,7 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$countQuery->getQuery()->groups = null;
|
||||
$countQuery->getQuery()->orders = null;
|
||||
$countQuery->groupBy('accounts.user_id');
|
||||
$this->count = $countQuery->count();
|
||||
$this->count = intval($countQuery->count());
|
||||
|
||||
return $this->count;
|
||||
}
|
||||
@@ -243,6 +244,17 @@ class JournalCollector implements JournalCollectorInterface
|
||||
public function getJournals(): Collection
|
||||
{
|
||||
$this->run = true;
|
||||
|
||||
// find query set in cache.
|
||||
$hash = hash('sha256', $this->query->toSql() . serialize($this->query->getBindings()));
|
||||
$key = 'query-' . substr($hash, -8);
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
Log::debug(sprintf('Return cache of query with ID "%s".', $key));
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/** @var Collection $set */
|
||||
$set = $this->query->get(array_values($this->fields));
|
||||
|
||||
@@ -263,6 +275,8 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
|
||||
}
|
||||
);
|
||||
Log::debug(sprintf('Cached query with ID "%s".', $key));
|
||||
$cache->store($set);
|
||||
|
||||
return $set;
|
||||
}
|
||||
@@ -773,7 +787,8 @@ class JournalCollector implements JournalCollectorInterface
|
||||
|
||||
$this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
|
||||
$this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id');
|
||||
|
||||
$this->query->whereNull('transaction_journal_categories.deleted_at');
|
||||
$this->query->whereNull('transaction_categories.deleted_at');
|
||||
$this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id';
|
||||
$this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted';
|
||||
$this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name';
|
||||
|
@@ -27,6 +27,7 @@ use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
@@ -136,7 +136,7 @@ class PopupReport implements PopupReportInterface
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER])
|
||||
->setRange($attributes['startDate'], $attributes['endDate'])
|
||||
->setRange($attributes['startDate'], $attributes['endDate'])->withOpposingAccount()
|
||||
->setCategory($category);
|
||||
$journals = $collector->getJournals();
|
||||
|
||||
|
@@ -29,6 +29,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\AccountFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@@ -38,7 +39,6 @@ use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Steam;
|
||||
use View;
|
||||
@@ -50,6 +50,13 @@ use View;
|
||||
*/
|
||||
class AccountController extends Controller
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface */
|
||||
private $currencyRepos;
|
||||
/** @var JournalRepositoryInterface */
|
||||
private $journalRepos;
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -63,6 +70,10 @@ class AccountController extends Controller
|
||||
app('view')->share('mainTitleIcon', 'fa-credit-card');
|
||||
app('view')->share('title', trans('firefly.accounts'));
|
||||
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$this->journalRepos = app(JournalRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
@@ -76,9 +87,7 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function create(Request $request, string $what = 'asset')
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$allCurrencies = $repository->get();
|
||||
$allCurrencies = $this->currencyRepos->get();
|
||||
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
@@ -101,16 +110,15 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
* @param Account $account
|
||||
* @param Account $account
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function delete(AccountRepositoryInterface $repository, Account $account)
|
||||
public function delete(Account $account)
|
||||
{
|
||||
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
|
||||
$subTitle = trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
|
||||
$accountList = ExpandedForm::makeSelectListWithEmpty($repository->getAccountsByType([$account->accountType->type]));
|
||||
$accountList = ExpandedForm::makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
|
||||
unset($accountList[$account->id]);
|
||||
|
||||
// put previous url in session
|
||||
@@ -126,14 +134,14 @@ class AccountController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, AccountRepositoryInterface $repository, Account $account)
|
||||
public function destroy(Request $request, Account $account)
|
||||
{
|
||||
$type = $account->accountType->type;
|
||||
$typeName = config('firefly.shortNamesByFullName.' . $type);
|
||||
$name = $account->name;
|
||||
$moveTo = $repository->find(intval($request->get('move_account_before_delete')));
|
||||
$moveTo = $this->repository->find(intval($request->get('move_account_before_delete')));
|
||||
|
||||
$repository->destroy($account, $moveTo);
|
||||
$this->repository->destroy($account, $moveTo);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.' . $typeName . '_deleted', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
@@ -153,17 +161,13 @@ class AccountController extends Controller
|
||||
* @return View
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws FireflyException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function edit(Request $request, Account $account)
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$what = config('firefly.shortNamesByFullName')[$account->accountType->type];
|
||||
$subTitle = trans('firefly.edit_' . $what . '_account', ['name' => $account->name]);
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$allCurrencies = $repository->get();
|
||||
$allCurrencies = $this->currencyRepos->get();
|
||||
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
|
||||
$roles = [];
|
||||
foreach (config('firefly.accountRoles') as $role) {
|
||||
@@ -183,7 +187,7 @@ class AccountController extends Controller
|
||||
$openingBalanceAmount = '0' === $account->getOpeningBalanceAmount() ? '' : $openingBalanceAmount;
|
||||
$openingBalanceDate = $account->getOpeningBalanceDate();
|
||||
$openingBalanceDate = 1900 === $openingBalanceDate->year ? null : $openingBalanceDate->format('Y-m-d');
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
$currency = $this->currencyRepos->find(intval($account->getMeta('currency_id')));
|
||||
|
||||
$preFilled = [
|
||||
'accountNumber' => $account->getMeta('accountNumber'),
|
||||
@@ -195,7 +199,15 @@ class AccountController extends Controller
|
||||
'openingBalance' => $openingBalanceAmount,
|
||||
'virtualBalance' => $account->virtual_balance,
|
||||
'currency_id' => $currency->id,
|
||||
'notes' => '',
|
||||
];
|
||||
/** @var Note $note */
|
||||
$note = $this->repository->getNote($account);
|
||||
if (null !== $note) {
|
||||
$preFilled['notes'] = $note->text;
|
||||
}
|
||||
|
||||
|
||||
$request->session()->flash('preFilled', $preFilled);
|
||||
|
||||
return view(
|
||||
@@ -215,19 +227,18 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
* @param string $what
|
||||
* @param Request $request
|
||||
* @param string $what
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function index(Request $request, AccountRepositoryInterface $repository, string $what)
|
||||
public function index(Request $request, string $what)
|
||||
{
|
||||
$what = $what ?? 'asset';
|
||||
$subTitle = trans('firefly.' . $what . '_accounts');
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $what);
|
||||
$types = config('firefly.accountTypesByIdentifier.' . $what);
|
||||
$collection = $repository->getAccountsByType($types);
|
||||
$collection = $this->repository->getAccountsByType($types);
|
||||
$total = $collection->count();
|
||||
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
|
||||
@@ -263,10 +274,9 @@ class AccountController extends Controller
|
||||
/**
|
||||
* Show an account.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param Account $account
|
||||
* @param string $moment
|
||||
* @param Request $request
|
||||
* @param Account $account
|
||||
* @param string $moment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*
|
||||
@@ -275,23 +285,21 @@ class AccountController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
|
||||
public function show(Request $request, Account $account, string $moment = '')
|
||||
{
|
||||
if (AccountType::INITIAL_BALANCE === $account->accountType->type) {
|
||||
return $this->redirectToOriginalAccount($account);
|
||||
}
|
||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.single', [$account->id]);
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$currencyId = intval($account->getMeta('currency_id'));
|
||||
$currency = $currencyRepos->find($currencyId);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||
$page = intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
|
||||
$chartUri = route('chart.account.single', [$account->id]);
|
||||
$start = null;
|
||||
$end = null;
|
||||
$periods = new Collection;
|
||||
$currencyId = intval($account->getMeta('currency_id'));
|
||||
$currency = $this->currencyRepos->find($currencyId);
|
||||
if (0 === $currencyId) {
|
||||
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
|
||||
}
|
||||
@@ -300,7 +308,7 @@ class AccountController extends Controller
|
||||
if ('all' === $moment) {
|
||||
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
|
||||
$chartUri = route('chart.account.all', [$account->id]);
|
||||
$first = $repository->first();
|
||||
$first = $this->journalRepos->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = new Carbon;
|
||||
}
|
||||
@@ -308,12 +316,13 @@ class AccountController extends Controller
|
||||
// prep for "specific date" view.
|
||||
if (strlen($moment) > 0 && 'all' !== $moment) {
|
||||
$start = new Carbon($moment);
|
||||
$start = app('navigation')->startOfPeriod($start, $range);
|
||||
$end = app('navigation')->endOfPeriod($start, $range);
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d')]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
$periods = $this->getPeriodOverview($account, $start);
|
||||
}
|
||||
|
||||
// prep for current period view
|
||||
@@ -323,7 +332,7 @@ class AccountController extends Controller
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$subTitle = trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
|
||||
$periods = $this->getPeriodOverview($account);
|
||||
$periods = $this->getPeriodOverview($account, null);
|
||||
}
|
||||
|
||||
// grab journals:
|
||||
@@ -342,15 +351,14 @@ class AccountController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountFormRequest $request
|
||||
* @param AccountRepositoryInterface $repository
|
||||
* @param AccountFormRequest $request
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(AccountFormRequest $request, AccountRepositoryInterface $repository)
|
||||
public function store(AccountFormRequest $request)
|
||||
{
|
||||
$data = $request->getAccountData();
|
||||
$account = $repository->store($data);
|
||||
$account = $this->repository->store($data);
|
||||
$request->session()->flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
|
||||
Preferences::mark();
|
||||
|
||||
@@ -381,10 +389,10 @@ class AccountController extends Controller
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(AccountFormRequest $request, AccountRepositoryInterface $repository, Account $account)
|
||||
public function update(AccountFormRequest $request, Account $account)
|
||||
{
|
||||
$data = $request->getAccountData();
|
||||
$repository->update($account, $data);
|
||||
$this->repository->update($account, $data);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
|
||||
Preferences::mark();
|
||||
@@ -426,56 +434,55 @@ class AccountController extends Controller
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
private function getPeriodOverview(Account $account): Collection
|
||||
private function getPeriodOverview(Account $account, ?Carbon $date): Collection
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = app('navigation')->startOfPeriod($start, $range);
|
||||
$end = app('navigation')->endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$count = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = $this->repository->oldestJournalDate($account);
|
||||
$end = $date ?? new Carbon;
|
||||
|
||||
// properties for cache
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('account-show-period-entries');
|
||||
$cache->addProperty($account->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
Log::debug('Going to get period expenses and incomes.');
|
||||
while ($end >= $start && $count < 90) {
|
||||
$end = app('navigation')->startOfPeriod($end, $range);
|
||||
$currentEnd = app('navigation')->endOfPeriod($end, $range);
|
||||
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
|
||||
// loop dates
|
||||
foreach ($dates as $date) {
|
||||
|
||||
// try a collector for income:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount();
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::DEPOSIT])
|
||||
->withOpposingAccount();
|
||||
$earned = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
// try a collector for expenses:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($end, $currentEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount();
|
||||
$spent = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($end, $range);
|
||||
$collector->setAccounts(new Collection([$account]))->setRange($date['start'], $date['end'])->setTypes([TransactionType::WITHDRAWAL])
|
||||
->withOpposingAccount();
|
||||
$spent = strval($collector->getJournals()->sum('transaction_amount'));
|
||||
|
||||
$dateStr = $date['end']->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($date['start'], $date['period']);
|
||||
$entries->push(
|
||||
[
|
||||
'string' => $dateStr,
|
||||
'name' => $dateName,
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'date' => clone $end,]
|
||||
'date' => clone $date['end'],]
|
||||
);
|
||||
$end = app('navigation')->subtractPeriod($end, $range, 1);
|
||||
++$count;
|
||||
}
|
||||
|
||||
$cache->store($entries);
|
||||
|
||||
return $entries;
|
||||
|
@@ -125,7 +125,7 @@ class UserController extends Controller
|
||||
$list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret'];
|
||||
$preferences = Preferences::getArrayForUser($user, $list);
|
||||
$user->isAdmin = $user->hasRole('owner');
|
||||
$is2faEnabled = true === $preferences['twoFactorAuthEnabled'];
|
||||
$is2faEnabled = 1 === $preferences['twoFactorAuthEnabled'];
|
||||
$has2faSecret = null !== $preferences['twoFactorAuthSecret'];
|
||||
$user->has2FA = ($is2faEnabled && $has2faSecret) ? true : false;
|
||||
$user->prefs = $preferences;
|
||||
|
@@ -22,7 +22,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use File;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Requests\AttachmentFormRequest;
|
||||
use FireflyIII\Models\Attachment;
|
||||
@@ -40,6 +39,9 @@ use View;
|
||||
*/
|
||||
class AttachmentController extends Controller
|
||||
{
|
||||
/** @var AttachmentRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -52,6 +54,7 @@ class AttachmentController extends Controller
|
||||
function ($request, $next) {
|
||||
app('view')->share('mainTitleIcon', 'fa-paperclip');
|
||||
app('view')->share('title', trans('firefly.attachments'));
|
||||
$this->repository = app(AttachmentRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -74,17 +77,16 @@ class AttachmentController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
* @param Request $request
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
public function destroy(Request $request, Attachment $attachment)
|
||||
{
|
||||
$name = $attachment->filename;
|
||||
|
||||
$repository->destroy($attachment);
|
||||
$this->repository->destroy($attachment);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.attachment_deleted', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
@@ -93,17 +95,16 @@ class AttachmentController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function download(AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
public function download(Attachment $attachment)
|
||||
{
|
||||
if ($repository->exists($attachment)) {
|
||||
$content = $repository->getContent($attachment);
|
||||
if ($this->repository->exists($attachment)) {
|
||||
$content = $this->repository->getContent($attachment);
|
||||
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
@@ -145,37 +146,15 @@ class AttachmentController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
public function preview(Attachment $attachment)
|
||||
{
|
||||
$image = 'images/page_green.png';
|
||||
|
||||
if ('application/pdf' === $attachment->mime) {
|
||||
$image = 'images/page_white_acrobat.png';
|
||||
}
|
||||
$file = public_path($image);
|
||||
$response = Response::make(File::get($file));
|
||||
$response->header('Content-Type', 'image/png');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttachmentFormRequest $request
|
||||
* @param AttachmentRepositoryInterface $repository
|
||||
* @param Attachment $attachment
|
||||
* @param AttachmentFormRequest $request
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
|
||||
public function update(AttachmentFormRequest $request, Attachment $attachment)
|
||||
{
|
||||
$data = $request->getAttachmentData();
|
||||
$repository->update($attachment, $data);
|
||||
$this->repository->update($attachment, $data);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
|
||||
Preferences::mark();
|
||||
@@ -191,4 +170,25 @@ class AttachmentController extends Controller
|
||||
// redirect to previous URL.
|
||||
return redirect($this->getPreviousUri('attachments.edit.uri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function view(Attachment $attachment)
|
||||
{
|
||||
if ($this->repository->exists($attachment)) {
|
||||
$content = $this->repository->getContent($attachment);
|
||||
|
||||
return Response::make(
|
||||
$content, 200, [
|
||||
'Content-Type' => $attachment->mime,
|
||||
'Content-Disposition' => 'inline; filename="' . $attachment->filename . '"',
|
||||
]
|
||||
);
|
||||
}
|
||||
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
|
||||
}
|
||||
}
|
||||
|
@@ -22,8 +22,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
/**
|
||||
* Class ForgotPasswordController
|
||||
@@ -51,4 +56,57 @@ class ForgotPasswordController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset link to the given user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @param UserRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function sendResetLinkEmail(Request $request, UserRepositoryInterface $repository)
|
||||
{
|
||||
$this->validateEmail($request);
|
||||
|
||||
// verify if the user is not a demo user. If so, we give him back an error.
|
||||
$user = User::where('email', $request->get('email'))->first();
|
||||
|
||||
if (!is_null($user) && $repository->hasRole($user, 'demo')) {
|
||||
return back()->withErrors(['email' => trans('firefly.cannot_reset_demo_user')]);
|
||||
}
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$response = $this->broker()->sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($response == Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', trans($response));
|
||||
}
|
||||
|
||||
return back()->withErrors(['email' => trans($response)]); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Display the form to request a password reset link.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showLinkRequestForm()
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
return view('auth.passwords.email')->with(compact('allowRegistration'));
|
||||
}
|
||||
}
|
||||
|
@@ -22,8 +22,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use FireflyConfig;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
@@ -52,4 +55,28 @@ class ResetPasswordController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the password reset view for the given token.
|
||||
*
|
||||
* If no token is present, display the link request form.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string|null $token
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
// is allowed to?
|
||||
$singleUserMode = FireflyConfig::get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$userCount = User::count();
|
||||
$allowRegistration = true;
|
||||
if (true === $singleUserMode && $userCount > 0) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
return view('auth.passwords.reset')->with(
|
||||
['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -262,7 +262,7 @@ class BillController extends Controller
|
||||
}
|
||||
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
|
||||
$hideBill = true;
|
||||
$subTitle = e($bill->name);
|
||||
$subTitle = $bill->name;
|
||||
|
||||
return view('bills.show', compact('transactions', 'yearAverage', 'overallAverage', 'year', 'hideBill', 'bill', 'subTitle'));
|
||||
}
|
||||
|
@@ -611,21 +611,19 @@ class BudgetController extends Controller
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
Log::debug('Going to get period expenses and incomes.');
|
||||
while ($end >= $start) {
|
||||
$end = app('navigation')->startOfPeriod($end, $range);
|
||||
$currentEnd = app('navigation')->endOfPeriod($end, $range);
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
foreach ($dates as $date) {
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutBudget()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutBudget()->withOpposingAccount()->setTypes(
|
||||
[TransactionType::WITHDRAWAL]
|
||||
);
|
||||
$set = $collector->getJournals();
|
||||
$sum = strval($set->sum('transaction_amount') ?? '0');
|
||||
$journals = $set->count();
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($end, $range);
|
||||
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $end]);
|
||||
$end = app('navigation')->subtractPeriod($end, $range, 1);
|
||||
$dateStr = $date['end']->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
|
||||
$entries->push(['string' => $dateStr, 'name' => $dateName, 'count' => $journals, 'sum' => $sum, 'date' => clone $date['end']]);
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
|
@@ -46,6 +46,13 @@ use View;
|
||||
*/
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
/** @var AccountRepositoryInterface */
|
||||
private $accountRepos;
|
||||
/** @var JournalRepositoryInterface */
|
||||
private $journalRepos;
|
||||
/** @var CategoryRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -57,6 +64,9 @@ class CategoryController extends Controller
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', trans('firefly.categories'));
|
||||
app('view')->share('mainTitleIcon', 'fa-bar-chart');
|
||||
$this->journalRepos = app(JournalRepositoryInterface::class);
|
||||
$this->repository = app(CategoryRepositoryInterface::class);
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -95,16 +105,15 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param CategoryRepositoryInterface $repository
|
||||
* @param Category $category
|
||||
* @param Request $request
|
||||
* @param Category $category
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, CategoryRepositoryInterface $repository, Category $category)
|
||||
public function destroy(Request $request, Category $category)
|
||||
{
|
||||
$name = $category->name;
|
||||
$repository->destroy($category);
|
||||
$this->repository->destroy($category);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.deleted_category', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
@@ -132,21 +141,21 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CategoryRepositoryInterface $repository
|
||||
* @param Request $request
|
||||
*
|
||||
* @return View
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index(Request $request, CategoryRepositoryInterface $repository)
|
||||
public function index(Request $request)
|
||||
{
|
||||
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
|
||||
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
|
||||
$collection = $repository->getCategories();
|
||||
$collection = $this->repository->getCategories();
|
||||
$total = $collection->count();
|
||||
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
|
||||
|
||||
$collection->each(
|
||||
function (Category $category) use ($repository) {
|
||||
$category->lastActivity = $repository->lastUseDate($category, new Collection);
|
||||
function (Category $category) {
|
||||
$category->lastActivity = $this->repository->lastUseDate($category, new Collection);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -158,13 +167,12 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param string $moment
|
||||
* @param Request $request
|
||||
* @param string $moment
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function noCategory(Request $request, JournalRepositoryInterface $repository, string $moment = '')
|
||||
public function noCategory(Request $request, string $moment = '')
|
||||
{
|
||||
// default values:
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
@@ -177,27 +185,27 @@ class CategoryController extends Controller
|
||||
// prep for "all" view.
|
||||
if ('all' === $moment) {
|
||||
$subTitle = trans('firefly.all_journals_without_category');
|
||||
$first = $repository->first();
|
||||
$first = $this->journalRepos->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = new Carbon;
|
||||
}
|
||||
|
||||
// prep for "specific date" view.
|
||||
if (strlen($moment) > 0 && 'all' !== $moment) {
|
||||
$start = new Carbon($moment);
|
||||
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
|
||||
$end = app('navigation')->endOfPeriod($start, $range);
|
||||
$subTitle = trans(
|
||||
'firefly.without_category_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
);
|
||||
$periods = $this->getNoCategoryPeriodOverview();
|
||||
$periods = $this->getNoCategoryPeriodOverview($start);
|
||||
}
|
||||
|
||||
// prep for current period
|
||||
if (0 === strlen($moment)) {
|
||||
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
|
||||
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
|
||||
$periods = $this->getNoCategoryPeriodOverview();
|
||||
$periods = $this->getNoCategoryPeriodOverview($start);
|
||||
$subTitle = trans(
|
||||
'firefly.without_category_between',
|
||||
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
@@ -248,14 +256,14 @@ class CategoryController extends Controller
|
||||
|
||||
// prep for "specific date" view.
|
||||
if (strlen($moment) > 0 && 'all' !== $moment) {
|
||||
$start = new Carbon($moment);
|
||||
$start = app('navigation')->startOfPeriod(new Carbon($moment), $range);
|
||||
$end = app('navigation')->endOfPeriod($start, $range);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_category',
|
||||
['name' => $category->name,
|
||||
'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),]
|
||||
);
|
||||
$periods = $this->getPeriodOverview($category);
|
||||
$periods = $this->getPeriodOverview($category, $start);
|
||||
$path = route('categories.show', [$category->id, $moment]);
|
||||
}
|
||||
|
||||
@@ -265,7 +273,7 @@ class CategoryController extends Controller
|
||||
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range));
|
||||
$periods = $this->getPeriodOverview($category);
|
||||
$periods = $this->getPeriodOverview($category, $start);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_category',
|
||||
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
@@ -336,38 +344,36 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $theDate
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function getNoCategoryPeriodOverview(): Collection
|
||||
private function getNoCategoryPeriodOverview(Carbon $theDate): Collection
|
||||
{
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$first = $repository->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$start = app('navigation')->startOfPeriod($start, $range);
|
||||
$end = app('navigation')->endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$first = $this->journalRepos->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = $theDate ?? new Carbon;
|
||||
|
||||
// properties for cache
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('no-budget-period-entries');
|
||||
$cache->addProperty('no-category-period-entries');
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Going to get period expenses and incomes between %s and %s.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
while ($end >= $start) {
|
||||
Log::debug('Loop!');
|
||||
$end = app('navigation')->startOfPeriod($end, $range);
|
||||
$currentEnd = app('navigation')->endOfPeriod($end, $range);
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
|
||||
foreach ($dates as $date) {
|
||||
|
||||
// count journals without category in this period:
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
|
||||
->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$count = $collector->getJournals()->count();
|
||||
@@ -375,7 +381,7 @@ class CategoryController extends Controller
|
||||
// amount transferred
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()
|
||||
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
|
||||
@@ -383,17 +389,20 @@ class CategoryController extends Controller
|
||||
// amount spent
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL]);
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
|
||||
[TransactionType::WITHDRAWAL]
|
||||
);
|
||||
$spent = $collector->getJournals()->sum('transaction_amount');
|
||||
|
||||
// amount earned
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withoutCategory()->withOpposingAccount()->setTypes([TransactionType::DEPOSIT]);
|
||||
$earned = $collector->getJournals()->sum('transaction_amount');
|
||||
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($end, $range);
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes(
|
||||
[TransactionType::DEPOSIT]
|
||||
);
|
||||
$earned = $collector->getJournals()->sum('transaction_amount');
|
||||
$dateStr = $date['end']->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
|
||||
$entries->push(
|
||||
[
|
||||
'string' => $dateStr,
|
||||
@@ -402,10 +411,9 @@ class CategoryController extends Controller
|
||||
'spent' => $spent,
|
||||
'earned' => $earned,
|
||||
'transferred' => $transferred,
|
||||
'date' => clone $end,
|
||||
'date' => clone $date['end'],
|
||||
]
|
||||
);
|
||||
$end = app('navigation')->subtractPeriod($end, $range, 1);
|
||||
}
|
||||
Log::debug('End of loops');
|
||||
$cache->store($entries);
|
||||
@@ -418,45 +426,39 @@ class CategoryController extends Controller
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function getPeriodOverview(Category $category): Collection
|
||||
private function getPeriodOverview(Category $category, Carbon $date): Collection
|
||||
{
|
||||
/** @var CategoryRepositoryInterface $repository */
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$first = $repository->firstUseDate($category);
|
||||
if (null === $first) {
|
||||
$first = new Carbon; // @codeCoverageIgnore
|
||||
}
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$first = app('navigation')->startOfPeriod($first, $range);
|
||||
$end = app('navigation')->endOfX(new Carbon, $range, null);
|
||||
$entries = new Collection;
|
||||
$count = 0;
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$first = $this->journalRepos->first();
|
||||
$start = $first->date ?? new Carbon;
|
||||
$end = $date ?? new Carbon;
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($first);
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($range);
|
||||
$cache->addProperty('categories.entries');
|
||||
$cache->addProperty($category->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
while ($end >= $first && $count < 90) {
|
||||
$end = app('navigation')->startOfPeriod($end, $range);
|
||||
$currentEnd = app('navigation')->endOfPeriod($end, $range);
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $end, $currentEnd);
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($end, $range);
|
||||
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = new Collection;
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$spent = $this->repository->spentInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
|
||||
$earned = $this->repository->earnedInPeriod(new Collection([$category]), $accounts, $date['start'], $date['end']);
|
||||
$dateStr = $date['end']->format('Y-m-d');
|
||||
$dateName = app('navigation')->periodShow($date['end'], $date['period']);
|
||||
|
||||
// amount transferred
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->setCategory($category)
|
||||
$collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->setCategory($category)
|
||||
->withOpposingAccount()->setTypes([TransactionType::TRANSFER]);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$transferred = Steam::positive($collector->getJournals()->sum('transaction_amount'));
|
||||
@@ -469,11 +471,9 @@ class CategoryController extends Controller
|
||||
'earned' => $earned,
|
||||
'sum' => bcadd($earned, $spent),
|
||||
'transferred' => $transferred,
|
||||
'date' => clone $end,
|
||||
'date' => clone $date['end'],
|
||||
]
|
||||
);
|
||||
$end = app('navigation')->subtractPeriod($end, $range, 1);
|
||||
++$count;
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
|
@@ -76,21 +76,46 @@ class AccountController extends Controller
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$start = $repository->oldestJournalDate($account);
|
||||
$end = new Carbon;
|
||||
$format = (string)trans('config.month_and_day');
|
||||
$range = Steam::balanceInRange($account, $start, $end);
|
||||
$current = clone $start;
|
||||
$previous = array_values($range)[0];
|
||||
$chartData = [];
|
||||
|
||||
while ($end >= $current) {
|
||||
$theDate = $current->format('Y-m-d');
|
||||
$balance = $range[$theDate] ?? $previous;
|
||||
$label = $current->formatLocalized($format);
|
||||
$chartData[$label] = $balance;
|
||||
$previous = $balance;
|
||||
$current->addDay();
|
||||
// depending on diff, do something with range of chart.
|
||||
$step = '1D';
|
||||
$months = $start->diffInMonths($end);
|
||||
if ($months > 3) {
|
||||
$step = '1W';
|
||||
}
|
||||
if ($months > 24) {
|
||||
$step = '1M'; // @codeCoverageIgnore
|
||||
}
|
||||
if ($months > 100) {
|
||||
$step = '1Y'; // @codeCoverageIgnore
|
||||
}
|
||||
$chartData = [];
|
||||
$current = clone $start;
|
||||
switch ($step) {
|
||||
case '1D':
|
||||
$format = (string)trans('config.month_and_day');
|
||||
$range = Steam::balanceInRange($account, $start, $end);
|
||||
$previous = array_values($range)[0];
|
||||
while ($end >= $current) {
|
||||
$theDate = $current->format('Y-m-d');
|
||||
$balance = $range[$theDate] ?? $previous;
|
||||
$label = $current->formatLocalized($format);
|
||||
$chartData[$label] = floatval($balance);
|
||||
$previous = $balance;
|
||||
$current->addDay();
|
||||
}
|
||||
break;
|
||||
case '1W':
|
||||
case '1M': // @codeCoverageIgnore
|
||||
case '1Y': // @codeCoverageIgnore
|
||||
while ($end >= $current) {
|
||||
$balance = floatval(Steam::balance($account, $current));
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$chartData[$label] = $balance;
|
||||
$current = app('navigation')->addPeriod($current, $step, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $this->generator->singleSet($account->name, $chartData);
|
||||
$cache->store($data);
|
||||
|
||||
|
@@ -37,7 +37,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Support\Collection;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Steam;
|
||||
|
||||
@@ -78,37 +77,47 @@ class BudgetController extends Controller
|
||||
*/
|
||||
public function budget(Budget $budget)
|
||||
{
|
||||
$first = $this->repository->firstUseDate($budget);
|
||||
$range = Preferences::get('viewRange', '1M')->data;
|
||||
$currentStart = app('navigation')->startOfPeriod($first, $range);
|
||||
$last = session('end', new Carbon);
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($first);
|
||||
$cache->addProperty($last);
|
||||
$start = $this->repository->firstUseDate($budget);
|
||||
$end = session('end', new Carbon);
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('chart.budget.budget');
|
||||
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$final = clone $last;
|
||||
$final->addYears(2);
|
||||
// depending on diff, do something with range of chart.
|
||||
$step = '1D';
|
||||
$months = $start->diffInMonths($end);
|
||||
if ($months > 3) {
|
||||
$step = '1W';
|
||||
}
|
||||
if ($months > 24) {
|
||||
$step = '1M';
|
||||
}
|
||||
if ($months > 60) {
|
||||
$step = '1Y'; // @codeCoverageIgnore
|
||||
}
|
||||
$budgetCollection = new Collection([$budget]);
|
||||
$last = app('navigation')->endOfX($last, $range, $final); // not to overshoot.
|
||||
$entries = [];
|
||||
while ($currentStart < $last) {
|
||||
// periodspecific dates:
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $range);
|
||||
// sub another day because reasons.
|
||||
$currentEnd->subDay();
|
||||
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $currentStart, $currentEnd);
|
||||
$format = app('navigation')->periodShow($currentStart, $range);
|
||||
$entries[$format] = bcmul($spent, '-1');
|
||||
$currentStart = clone $currentEnd;
|
||||
$currentStart->addDays(2);
|
||||
$chartData = [];
|
||||
$current = clone $start;
|
||||
$current = app('navigation')->startOfPeriod($current, $step);
|
||||
|
||||
while ($end >= $current) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($current, $step);
|
||||
if ($step === '1Y') {
|
||||
$currentEnd->subDay(); // @codeCoverageIgnore
|
||||
}
|
||||
$spent = $this->repository->spentInPeriod($budgetCollection, new Collection, $current, $currentEnd);
|
||||
$label = app('navigation')->periodShow($current, $step);
|
||||
$chartData[$label] = floatval(bcmul($spent, '-1'));
|
||||
$current = clone $currentEnd;
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $entries);
|
||||
$data = $this->generator->singleSet(strval(trans('firefly.spent')), $chartData);
|
||||
|
||||
$cache->store($data);
|
||||
|
||||
|
174
app/Http/Controllers/DebugController.php
Normal file
174
app/Http/Controllers/DebugController.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* DebugController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
|
||||
/**
|
||||
* Class DebugController
|
||||
*/
|
||||
class DebugController extends Controller
|
||||
{
|
||||
/**
|
||||
* HomeController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(IsDemoUser::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$search = ['~', '#'];
|
||||
$replace = ['\~', '# '];
|
||||
|
||||
$phpVersion = str_replace($search, $replace, PHP_VERSION);
|
||||
$phpOs = str_replace($search, $replace, php_uname());
|
||||
$interface = PHP_SAPI;
|
||||
$now = Carbon::create()->format('Y-m-d H:i:s e');
|
||||
$extensions = join(', ', get_loaded_extensions());
|
||||
$drivers = join(', ', DB::availableDrivers());
|
||||
$currentDriver = DB::getDriverName();
|
||||
$userAgent = $request->header('user-agent');
|
||||
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
|
||||
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
|
||||
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
|
||||
$displayErrors = ini_get('display_errors');
|
||||
$errorReporting = $this->errorReporting(intval(ini_get('error_reporting')));
|
||||
$appEnv = env('APP_ENV', '');
|
||||
$appDebug = var_export(env('APP_DEBUG', false), true);
|
||||
$appLog = env('APP_LOG', '');
|
||||
$appLogLevel = env('APP_LOG_LEVEL', '');
|
||||
$packages = $this->collectPackages();
|
||||
$cacheDriver = env('CACHE_DRIVER', 'unknown');
|
||||
|
||||
|
||||
// get latest log file:
|
||||
$logger = Log::getMonolog();
|
||||
$handlers = $logger->getHandlers();
|
||||
$logContent = '';
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof RotatingFileHandler) {
|
||||
$logFile = $handler->getUrl();
|
||||
if (null !== $logFile) {
|
||||
try {
|
||||
$logContent = file_get_contents($logFile);
|
||||
} catch (Exception $e) {
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// last few lines
|
||||
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
|
||||
|
||||
return view(
|
||||
'debug',
|
||||
compact(
|
||||
'phpVersion',
|
||||
'extensions',
|
||||
'carbon',
|
||||
'appEnv',
|
||||
'appDebug',
|
||||
'appLog',
|
||||
'appLogLevel',
|
||||
'now',
|
||||
'packages',
|
||||
'drivers',
|
||||
'currentDriver',
|
||||
'userAgent',
|
||||
'displayErrors',
|
||||
'errorReporting',
|
||||
'phpOs',
|
||||
'interface',
|
||||
'logContent',
|
||||
'cacheDriver',
|
||||
'isDocker',
|
||||
'isSandstorm',
|
||||
'trustedProxies'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some common combinations.
|
||||
*
|
||||
* @param int $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function errorReporting(int $value): string
|
||||
{
|
||||
$array = [
|
||||
-1 => 'ALL errors',
|
||||
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED => 'E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED',
|
||||
E_ALL => 'E_ALL',
|
||||
E_ALL & ~E_DEPRECATED & ~E_STRICT => 'E_ALL & ~E_DEPRECATED & ~E_STRICT',
|
||||
E_ALL & ~E_NOTICE => 'E_ALL & ~E_NOTICE',
|
||||
E_ALL & ~E_NOTICE & ~E_STRICT => 'E_ALL & ~E_NOTICE & ~E_STRICT',
|
||||
E_COMPILE_ERROR | E_RECOVERABLE_ERROR | E_ERROR | E_CORE_ERROR => 'E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR',
|
||||
];
|
||||
if (isset($array[$value])) {
|
||||
return $array[$value];
|
||||
}
|
||||
|
||||
return strval($value); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function collectPackages(): array
|
||||
{
|
||||
$packages = [];
|
||||
$file = realpath(__DIR__ . '/../../../vendor/composer/installed.json');
|
||||
if (!($file === false) && file_exists($file)) {
|
||||
// file exists!
|
||||
$content = file_get_contents($file);
|
||||
$json = json_decode($content, true);
|
||||
foreach ($json as $package) {
|
||||
$packages[]
|
||||
= [
|
||||
'name' => $package['name'],
|
||||
'version' => $package['version'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
}
|
@@ -24,7 +24,6 @@ namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Artisan;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Events\RequestedVersionCheckStatus;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@@ -38,7 +37,6 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Route as RouteFacade;
|
||||
@@ -98,59 +96,6 @@ class HomeController extends Controller
|
||||
return Response::json(['ok' => 'ok']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function displayDebug(Request $request)
|
||||
{
|
||||
$phpVersion = str_replace('~', '\~', PHP_VERSION);
|
||||
$phpOs = php_uname();
|
||||
$interface = PHP_SAPI;
|
||||
$now = Carbon::create()->format('Y-m-d H:i:s e');
|
||||
$extensions = join(', ', get_loaded_extensions());
|
||||
$drivers = join(', ', DB::availableDrivers());
|
||||
$currentDriver = DB::getDriverName();
|
||||
$userAgent = $request->header('user-agent');
|
||||
$isSandstorm = var_export(env('IS_SANDSTORM', 'unknown'), true);
|
||||
$isDocker = var_export(env('IS_DOCKER', 'unknown'), true);
|
||||
$trustedProxies = env('TRUSTED_PROXIES', '(none)');
|
||||
|
||||
// get latest log file:
|
||||
$logger = Log::getMonolog();
|
||||
$handlers = $logger->getHandlers();
|
||||
$logContent = '';
|
||||
foreach ($handlers as $handler) {
|
||||
if ($handler instanceof RotatingFileHandler) {
|
||||
$logFile = $handler->getUrl();
|
||||
if (null !== $logFile) {
|
||||
$logContent = file_get_contents($logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
// last few lines
|
||||
$logContent = 'Truncated from this point <----|' . substr($logContent, -4096);
|
||||
|
||||
return view(
|
||||
'debug',
|
||||
compact(
|
||||
'phpVersion',
|
||||
'extensions',
|
||||
'carbon',
|
||||
'now',
|
||||
'drivers',
|
||||
'currentDriver',
|
||||
'userAgent',
|
||||
'phpOs',
|
||||
'interface',
|
||||
'logContent',
|
||||
'isDocker',
|
||||
'isSandstorm',
|
||||
'trustedProxies'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
@@ -255,20 +200,25 @@ class HomeController extends Controller
|
||||
'login', 'logout', 'password.reset', 'profile.confirm-email-change', 'profile.undo-email-change',
|
||||
'register', 'report.options', 'routes', 'rule-groups.down', 'rule-groups.up', 'rules.up', 'rules.down',
|
||||
'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch',
|
||||
'two-factor.lost', 'report.options',
|
||||
'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json',
|
||||
'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money',
|
||||
'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download',
|
||||
'transactions.clone', 'two-factor.index',
|
||||
];
|
||||
$return = ' ';
|
||||
/** @var Route $route */
|
||||
foreach ($set as $route) {
|
||||
$name = $route->getName();
|
||||
if (null !== $name && in_array('GET', $route->methods()) && strlen($name) > 0) {
|
||||
|
||||
$found = false;
|
||||
foreach ($ignore as $string) {
|
||||
if (false !== strpos($name, $string)) {
|
||||
if (!(false === stripos($name, $string))) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
if ($found === false) {
|
||||
$return .= 'touch ' . $route->getName() . '.md;';
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@ class ConfigurationController extends Controller
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
$this->middleware(IsDemoUser::class)->except(['index']);
|
||||
$this->middleware(IsDemoUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,9 @@ class ConfigurationController extends Controller
|
||||
|
||||
return redirect(route('import.status', [$job->key]));
|
||||
}
|
||||
|
||||
$this->repository->updateStatus($job, 'configuring');
|
||||
|
||||
$view = $configurator->getNextView();
|
||||
$data = $configurator->getNextData();
|
||||
$subTitle = trans('firefly.import_config_bread_crumb');
|
||||
@@ -135,6 +137,7 @@ class ConfigurationController extends Controller
|
||||
if (null === $className || !class_exists($className)) {
|
||||
throw new FireflyException(sprintf('Cannot find configurator class for job of type "%s".', $type)); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Going to create class "%s"', $className));
|
||||
/** @var ConfiguratorInterface $configurator */
|
||||
$configurator = app($className);
|
||||
$configurator->setJob($job);
|
||||
|
@@ -33,6 +33,7 @@ use Log;
|
||||
use Response;
|
||||
use View;
|
||||
|
||||
|
||||
/**
|
||||
* Class FileController.
|
||||
*/
|
||||
@@ -57,8 +58,7 @@ class IndexController extends Controller
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
|
||||
$this->middleware(IsDemoUser::class)->except(['create', 'index']);
|
||||
$this->middleware(IsDemoUser::class)->except(['index']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Import;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Response;
|
||||
@@ -47,6 +48,7 @@ class StatusController extends Controller
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
$this->middleware(IsDemoUser::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,20 +95,29 @@ class StatusController extends Controller
|
||||
$result['percentage'] = round(($job->extended_status['done'] / $job->extended_status['steps']) * 100, 0);
|
||||
$result['show_percentage'] = true;
|
||||
}
|
||||
|
||||
if ('finished' === $job->status) {
|
||||
$tagId = $job->extended_status['tag'];
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$result['finished'] = true;
|
||||
$result['finishedText'] = trans('import.status_finished_job', ['link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]);
|
||||
$result['finished'] = true;
|
||||
$tagId = intval($job->extended_status['tag']);
|
||||
if ($tagId !== 0) {
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$tag = $repository->find($tagId);
|
||||
$count = $tag->transactionJournals()->count();
|
||||
$result['finishedText'] = trans(
|
||||
'import.status_finished_job', ['count' => $count, 'link' => route('tags.show', [$tag->id, 'all']), 'tag' => $tag->tag]
|
||||
);
|
||||
}
|
||||
|
||||
if ($tagId === 0) {
|
||||
$result['finishedText'] = trans('import.status_finished_no_tag'); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
if ('running' === $job->status) {
|
||||
$result['started'] = true;
|
||||
$result['running'] = true;
|
||||
}
|
||||
$result['percentage'] = $result['percentage'] > 100 ? 100 : $result['percentage'];
|
||||
|
||||
return Response::json($result);
|
||||
}
|
||||
|
@@ -25,11 +25,13 @@ namespace FireflyIII\Http\Controllers\Json;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Response;
|
||||
|
||||
@@ -170,7 +172,7 @@ class BoxController extends Controller
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function netWorth(AccountRepositoryInterface $repository)
|
||||
public function netWorth(AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepos)
|
||||
{
|
||||
$date = new Carbon(date('Y-m-d')); // needed so its per day.
|
||||
/** @var Carbon $start */
|
||||
@@ -193,16 +195,32 @@ class BoxController extends Controller
|
||||
if ($cache->has()) {
|
||||
return Response::json($cache->get()); // @codeCoverageIgnore
|
||||
}
|
||||
$netWorth = [];
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$currency = app('amount')->getDefaultCurrency();
|
||||
$balances = app('steam')->balancesByAccounts($accounts, $date);
|
||||
$sum = '0';
|
||||
foreach ($balances as $entry) {
|
||||
$sum = bcadd($sum, $entry);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$accountCurrency = $currency;
|
||||
$balance = $balances[$account->id] ?? '0';
|
||||
$currencyId = intval($account->getMeta('currency_id'));
|
||||
if ($currencyId !== 0) {
|
||||
$accountCurrency = $currencyRepos->find($currencyId);
|
||||
}
|
||||
if (!isset($netWorth[$accountCurrency->id])) {
|
||||
$netWorth[$accountCurrency->id]['currency'] = $accountCurrency;
|
||||
$netWorth[$accountCurrency->id]['sum'] = '0';
|
||||
}
|
||||
$netWorth[$accountCurrency->id]['sum'] = bcadd($netWorth[$accountCurrency->id]['sum'], $balance);
|
||||
}
|
||||
|
||||
$return = [];
|
||||
foreach ($netWorth as $currencyId => $data) {
|
||||
$return[$currencyId] = app('amount')->formatAnything($data['currency'], $data['sum'], false);
|
||||
}
|
||||
$return = [
|
||||
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
|
||||
'net_worths' => array_values($return),
|
||||
];
|
||||
|
||||
$cache->store($return);
|
||||
|
@@ -383,7 +383,7 @@ class PiggyBankController extends Controller
|
||||
{
|
||||
$note = $piggyBank->notes()->first();
|
||||
$events = $repository->getEvents($piggyBank);
|
||||
$subTitle = e($piggyBank->name);
|
||||
$subTitle = $piggyBank->name;
|
||||
|
||||
return view('piggy-banks.show', compact('piggyBank', 'events', 'subTitle', 'note'));
|
||||
}
|
||||
|
@@ -59,16 +59,16 @@ class ReportController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
// @var AccountRepositoryInterface $repository
|
||||
/** @var AccountRepositoryInterface accountRepository */
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
|
||||
// @var BudgetRepositoryInterface $repository
|
||||
/** @var BudgetRepositoryInterface budgetRepository */
|
||||
$this->budgetRepository = app(BudgetRepositoryInterface::class);
|
||||
|
||||
// @var CategoryRepositoryInterface categoryRepository
|
||||
/** @var CategoryRepositoryInterface categoryRepository */
|
||||
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
|
||||
// @var PopupReportInterface popupHelper
|
||||
/** @var PopupReportInterface popupHelper */
|
||||
$this->popupHelper = app(PopupReportInterface::class);
|
||||
|
||||
return $next($request);
|
||||
|
@@ -128,7 +128,7 @@ class PreferencesController extends Controller
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter) // it's unused but the class does some validation.
|
||||
*/
|
||||
public function postCode(TokenFormRequest $request)
|
||||
public function postCode(/** @scrutinizer ignore-unused */ TokenFormRequest $request)
|
||||
{
|
||||
Preferences::set('twoFactorAuthEnabled', 1);
|
||||
Preferences::set('twoFactorAuthSecret', Session::get('two-factor-secret'));
|
||||
|
@@ -129,12 +129,12 @@ class CategoryController extends Controller
|
||||
$report = [];
|
||||
/** @var Category $category */
|
||||
foreach ($categories as $category) {
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
|
||||
if (0 !== bccomp($spent, '0')) {
|
||||
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'id' => $category->id];
|
||||
$spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $start, $end);
|
||||
$earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $start, $end);
|
||||
if (0 !== bccomp($spent, '0') || 0 !== bccomp($earned, '0')) {
|
||||
$report[$category->id] = ['name' => $category->name, 'spent' => $spent, 'earned' => $earned, 'id' => $category->id];
|
||||
}
|
||||
}
|
||||
|
||||
// sort the result
|
||||
// Obtain a list of columns
|
||||
$sum = [];
|
||||
|
@@ -568,7 +568,7 @@ class ExpenseController extends Controller
|
||||
];
|
||||
// loop to support multi currency
|
||||
foreach ($set as $transaction) {
|
||||
$currencyId = $transaction->transaction_currency_id;
|
||||
$currencyId = intval($transaction->transaction_currency_id);
|
||||
|
||||
// if not set, set to zero:
|
||||
if (!isset($sum['per_currency'][$currencyId])) {
|
||||
|
@@ -48,6 +48,9 @@ class ReportController extends Controller
|
||||
/** @var ReportHelperInterface */
|
||||
protected $helper;
|
||||
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -55,13 +58,13 @@ class ReportController extends Controller
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->helper = app(ReportHelperInterface::class);
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', trans('firefly.reports'));
|
||||
app('view')->share('mainTitleIcon', 'fa-line-chart');
|
||||
View::share('subTitleIcon', 'fa-calendar');
|
||||
$this->helper = app(ReportHelperInterface::class);
|
||||
$this->repository = app(BudgetRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -87,6 +90,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle', trans(
|
||||
@@ -120,6 +124,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle',
|
||||
@@ -157,6 +162,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle',
|
||||
@@ -195,6 +201,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle',
|
||||
@@ -233,6 +240,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle',
|
||||
@@ -265,6 +273,7 @@ class ReportController extends Controller
|
||||
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
|
||||
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$accountList = join(',', $accounts->pluck('id')->toArray());
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
return view('reports.index', compact('months', 'accounts', 'start', 'accountList', 'customFiscalYear'));
|
||||
}
|
||||
@@ -391,6 +400,7 @@ class ReportController extends Controller
|
||||
if ($start < session('first')) {
|
||||
$start = session('first');
|
||||
}
|
||||
$this->repository->cleanupBudgets();
|
||||
|
||||
View::share(
|
||||
'subTitle',
|
||||
|
178
app/Http/Controllers/Transaction/BulkController.php
Normal file
178
app/Http/Controllers/Transaction/BulkController.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* BulkController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\BulkEditJournalRequest;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class BulkController
|
||||
*/
|
||||
class BulkController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
app('view')->share('title', trans('firefly.transactions'));
|
||||
app('view')->share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $journals
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function edit(Request $request, Collection $journals)
|
||||
{
|
||||
|
||||
$subTitle = trans('firefly.mass_bulk_journals');
|
||||
|
||||
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
if ($sources->count() > 1) {
|
||||
$messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($destinations->count() > 1) {
|
||||
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
|
||||
$messages[] = trans('firefly.cannot_edit_opening_balance');
|
||||
continue;
|
||||
}
|
||||
|
||||
// cannot edit reconciled transactions / journals:
|
||||
if ($journal->transactions->first()->reconciled) {
|
||||
$messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered->push($journal);
|
||||
}
|
||||
|
||||
if (count($messages) > 0) {
|
||||
$request->session()->flash('info', $messages);
|
||||
}
|
||||
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('transactions.bulk-edit.uri');
|
||||
|
||||
// get list of budgets:
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($repository->getActiveBudgets());
|
||||
// collect some useful meta data for the mass edit:
|
||||
$filtered->each(
|
||||
function (TransactionJournal $journal) {
|
||||
$journal->transaction_count = $journal->transactions()->count();
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === $filtered->count()) {
|
||||
$request->session()->flash('error', trans('firefly.no_edit_multiple_left'));
|
||||
}
|
||||
|
||||
$journals = $filtered;
|
||||
|
||||
return view('transactions.bulk.edit', compact('journals', 'subTitle', 'budgetList'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BulkEditJournalRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(BulkEditJournalRequest $request, JournalRepositoryInterface $repository)
|
||||
{
|
||||
$journalIds = $request->get('journals');
|
||||
$ignoreCategory = intval($request->get('ignore_category')) === 1;
|
||||
$ignoreBudget = intval($request->get('ignore_budget')) === 1;
|
||||
$ignoreTags = intval($request->get('ignore_tags')) === 1;
|
||||
$count = 0;
|
||||
if (is_array($journalIds)) {
|
||||
foreach ($journalIds as $journalId) {
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if (!is_null($journal)) {
|
||||
$count++;
|
||||
Log::debug(sprintf('Found journal #%d', $journal->id));
|
||||
// update category if not told to ignore
|
||||
if ($ignoreCategory === false) {
|
||||
Log::debug(sprintf('Set category to %s', $request->string('category')));
|
||||
$repository->updateCategory($journal, $request->string('category'));
|
||||
}
|
||||
// update budget if not told to ignore (and is withdrawal)
|
||||
if ($ignoreBudget === false) {
|
||||
Log::debug(sprintf('Set budget to %d', $request->integer('budget_id')));
|
||||
$repository->updateBudget($journal, $request->integer('budget_id'));
|
||||
}
|
||||
if ($ignoreTags === false) {
|
||||
Log::debug(sprintf('Set tags to %s', $request->string('budget_id')));
|
||||
$repository->updateTags($journal, explode(',', $request->string('tags')));
|
||||
}
|
||||
// update tags if not told to ignore (and is withdrawal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Preferences::mark();
|
||||
$request->session()->flash('success', trans('firefly.mass_edited_transactions_success', ['amount' => $count]));
|
||||
|
||||
// redirect to previous URL:
|
||||
return redirect($this->getPreviousUri('transactions.bulk-edit.uri'));
|
||||
}
|
||||
|
||||
}
|
@@ -38,6 +38,11 @@ use URL;
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
/** @var JournalRepositoryInterface */
|
||||
private $journalRepository;
|
||||
/** @var LinkTypeRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -50,6 +55,9 @@ class LinkController extends Controller
|
||||
app('view')->share('title', trans('firefly.transactions'));
|
||||
app('view')->share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
$this->journalRepository = app(JournalRepositoryInterface::class);
|
||||
$this->repository = app(LinkTypeRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
@@ -70,14 +78,13 @@ class LinkController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
public function destroy(TransactionJournalLink $link)
|
||||
{
|
||||
$repository->destroyLink($link);
|
||||
$this->repository->destroyLink($link);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.deleted_link')));
|
||||
Preferences::mark();
|
||||
@@ -87,18 +94,13 @@ class LinkController extends Controller
|
||||
|
||||
/**
|
||||
* @param JournalLinkRequest $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param JournalRepositoryInterface $journalRepository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(
|
||||
JournalLinkRequest $request,
|
||||
LinkTypeRepositoryInterface $repository,
|
||||
JournalRepositoryInterface $journalRepository,
|
||||
TransactionJournal $journal
|
||||
) {
|
||||
public function store(JournalLinkRequest $request, TransactionJournal $journal)
|
||||
{
|
||||
|
||||
Log::debug('We are here (store)');
|
||||
$linkInfo = $request->getLinkInfo();
|
||||
if (0 === $linkInfo['transaction_journal_id']) {
|
||||
@@ -106,30 +108,28 @@ class LinkController extends Controller
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
|
||||
$alreadyLinked = $repository->findLink($journal, $other);
|
||||
$other = $this->journalRepository->find($linkInfo['transaction_journal_id']);
|
||||
$alreadyLinked = $this->repository->findLink($journal, $other);
|
||||
if ($alreadyLinked) {
|
||||
Session::flash('error', trans('firefly.journals_error_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
Log::debug(sprintf('Journal is %d, opposing is %d', $journal->id, $other->id));
|
||||
|
||||
$repository->storeLink($linkInfo, $other, $journal);
|
||||
$this->repository->storeLink($linkInfo, $other, $journal);
|
||||
Session::flash('success', trans('firefly.journals_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function switchLink(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
public function switchLink(TransactionJournalLink $link)
|
||||
{
|
||||
$repository->switchLink($link);
|
||||
$this->repository->switchLink($link);
|
||||
|
||||
return redirect(URL::previous());
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\MassDeleteJournalRequest;
|
||||
use FireflyIII\Http\Requests\MassEditJournalRequest;
|
||||
use FireflyIII\Http\Requests\MassEditBulkJournalRequest;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
@@ -71,7 +72,7 @@ class MassController extends Controller
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('transactions.mass-delete.uri');
|
||||
|
||||
return view('transactions.mass-delete', compact('journals', 'subTitle'));
|
||||
return view('transactions.mass.delete', compact('journals', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +132,7 @@ class MassController extends Controller
|
||||
// skip transactions that have multiple destinations, multiple sources or are an opening balance.
|
||||
$filtered = new Collection;
|
||||
$messages = [];
|
||||
// @var TransactionJournal
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$sources = $journal->sourceAccountList();
|
||||
$destinations = $journal->destinationAccountList();
|
||||
@@ -213,7 +214,7 @@ class MassController extends Controller
|
||||
if (is_array($journalIds)) {
|
||||
foreach ($journalIds as $journalId) {
|
||||
$journal = $repository->find(intval($journalId));
|
||||
if ($journal) {
|
||||
if (!is_null($journal)) {
|
||||
// get optional fields:
|
||||
$what = strtolower($journal->transactionTypeStr());
|
||||
$sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
|
||||
@@ -264,4 +265,5 @@ class MassController extends Controller
|
||||
// redirect to previous URL:
|
||||
return redirect($this->getPreviousUri('transactions.mass-edit.uri'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -272,7 +272,7 @@ class TransactionController extends Controller
|
||||
$return = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($collection as $transaction) {
|
||||
$currencyId = $transaction->transaction_currency_id;
|
||||
$currencyId = intval($transaction->transaction_currency_id);
|
||||
|
||||
// save currency information:
|
||||
if (!isset($return[$currencyId])) {
|
||||
|
@@ -24,11 +24,10 @@ namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Auth;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* Class AuthenticateTwoFactor.
|
||||
*/
|
||||
@@ -45,26 +44,14 @@ class AuthenticateTwoFactor
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, $guard = null)
|
||||
{
|
||||
// do the usual auth, again:
|
||||
if (Auth::guard($guard)->guest()) {
|
||||
if ($request->ajax()) {
|
||||
return response('Unauthorized.', 401);
|
||||
}
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
|
||||
if (1 === intval(auth()->user()->blocked)) {
|
||||
Auth::guard($guard)->logout();
|
||||
Session::flash('logoutMessage', trans('firefly.block_account_logout'));
|
||||
|
||||
return redirect()->guest('login');
|
||||
}
|
||||
$is2faEnabled = Preferences::get('twoFactorAuthEnabled', false)->data;
|
||||
$has2faSecret = null !== Preferences::get('twoFactorAuthSecret');
|
||||
|
||||
// grab 2auth information from session.
|
||||
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
|
||||
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
|
||||
|
||||
if ($is2faEnabled && $has2faSecret && !$is2faAuthed) {
|
||||
Log::debug('Does not seem to be 2 factor authed, redirect.');
|
||||
|
@@ -51,9 +51,9 @@ class IsDemoUser
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
if ($user->hasRole('demo')) {
|
||||
Session::flash('warning', strval(trans('firefly.not_available_demo_user')));
|
||||
Session::flash('info', strval(trans('firefly.not_available_demo_user')));
|
||||
|
||||
return redirect(route('index'));
|
||||
return redirect($request->session()->previousUrl());
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
@@ -60,8 +60,8 @@ class TrustProxies extends Middleware
|
||||
public function __construct(Repository $config)
|
||||
{
|
||||
$trustedProxies = env('TRUSTED_PROXIES', null);
|
||||
if (null !== $trustedProxies && strlen($trustedProxies) > 0) {
|
||||
$this->proxies = $trustedProxies;
|
||||
if (false !== $trustedProxies && null !== $trustedProxies && strlen($trustedProxies) > 0) {
|
||||
$this->proxies = strval($trustedProxies);
|
||||
}
|
||||
|
||||
parent::__construct($config);
|
||||
|
@@ -57,6 +57,7 @@ class AccountFormRequest extends Request
|
||||
'openingBalanceDate' => $this->date('openingBalanceDate'),
|
||||
'ccType' => $this->string('ccType'),
|
||||
'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'),
|
||||
'notes' => $this->string('notes'),
|
||||
];
|
||||
}
|
||||
|
||||
|
50
app/Http/Requests/BulkEditJournalRequest.php
Normal file
50
app/Http/Requests/BulkEditJournalRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* BulkEditJournalRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
/**
|
||||
* Class MassEditBulkJournalRequest.
|
||||
*/
|
||||
class BulkEditJournalRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// Only allow logged in users
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
|
||||
// fixed
|
||||
return [
|
||||
'journals.*' => 'required|belongsToUser:transaction_journals,id',
|
||||
];
|
||||
}
|
||||
}
|
@@ -91,33 +91,33 @@ class JournalFormRequest extends Request
|
||||
{
|
||||
$what = $this->get('what');
|
||||
$rules = [
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'date' => 'required|date',
|
||||
'amount_currency_id_amount' => 'exists:transaction_currencies,id|required',
|
||||
// then, custom fields:
|
||||
'interest_date' => 'date|nullable',
|
||||
'book_date' => 'date|nullable',
|
||||
'process_date' => 'date|nullable',
|
||||
'due_date' => 'date|nullable',
|
||||
'payment_date' => 'date|nullable',
|
||||
'invoice_date' => 'date|nullable',
|
||||
'internal_reference' => 'min:1,max:255|nullable',
|
||||
'notes' => 'min:1,max:50000|nullable',
|
||||
'interest_date' => 'date|nullable',
|
||||
'book_date' => 'date|nullable',
|
||||
'process_date' => 'date|nullable',
|
||||
'due_date' => 'date|nullable',
|
||||
'payment_date' => 'date|nullable',
|
||||
'invoice_date' => 'date|nullable',
|
||||
'internal_reference' => 'min:1,max:255|nullable',
|
||||
'notes' => 'min:1,max:50000|nullable',
|
||||
// and then transaction rules:
|
||||
'description' => 'required|between:1,255',
|
||||
'amount' => 'numeric|required|more:0',
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
|
||||
'category' => 'between:1,255|nullable',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'source_account_name' => 'between:1,255|nullable',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'destination_account_name' => 'between:1,255|nullable',
|
||||
'piggy_bank_id' => 'between:1,255|nullable',
|
||||
'description' => 'required|between:1,255',
|
||||
'amount' => 'numeric|required|more:0',
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
|
||||
'category' => 'between:1,255|nullable',
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'source_account_name' => 'between:1,255|nullable',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'destination_account_name' => 'between:1,255|nullable',
|
||||
'piggy_bank_id' => 'between:1,255|nullable',
|
||||
|
||||
// foreign currency amounts
|
||||
'native_amount' => 'numeric|more:0|nullable',
|
||||
'source_amount' => 'numeric|more:0|nullable',
|
||||
'destination_amount' => 'numeric|more:0|nullable',
|
||||
'native_amount' => 'numeric|more:0|nullable',
|
||||
'source_amount' => 'numeric|more:0|nullable',
|
||||
'destination_amount' => 'numeric|more:0|nullable',
|
||||
];
|
||||
|
||||
// some rules get an upgrade depending on the type of data:
|
||||
|
@@ -48,7 +48,7 @@ class JournalLinkRequest extends Request
|
||||
$parts = explode('_', $linkType);
|
||||
$return['link_type_id'] = intval($parts[0]);
|
||||
$return['transaction_journal_id'] = $this->integer('link_journal_id');
|
||||
$return['comments'] = strlen($this->string('comments')) > 0 ? $this->string('comments') : null;
|
||||
$return['notes'] = strlen($this->string('notes')) > 0 ? $this->string('notes') : '';
|
||||
$return['direction'] = $parts[1];
|
||||
if (0 === $return['transaction_journal_id'] && ctype_digit($this->string('link_other'))) {
|
||||
$return['transaction_journal_id'] = $this->integer('link_other');
|
||||
|
@@ -143,7 +143,7 @@ class Request extends FormRequest
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function integer(string $field): int
|
||||
public function integer(string $field): int
|
||||
{
|
||||
return intval($this->get($field));
|
||||
}
|
||||
|
@@ -24,11 +24,12 @@ namespace FireflyIII\Import\Configuration;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\ConfigurationInterface;
|
||||
use FireflyIII\Support\Import\Configuration\File\Initial;
|
||||
use FireflyIII\Support\Import\Configuration\File\Map;
|
||||
use FireflyIII\Support\Import\Configuration\File\Roles;
|
||||
use FireflyIII\Support\Import\Configuration\File\Upload;
|
||||
use FireflyIII\Support\Import\Configuration\File\UploadConfig;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
@@ -36,17 +37,41 @@ use Log;
|
||||
*/
|
||||
class FileConfigurator implements ConfiguratorInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $defaultConfig
|
||||
= [
|
||||
'stage' => 'initial',
|
||||
'has-headers' => false, // assume
|
||||
'date-format' => 'Ymd', // assume
|
||||
'delimiter' => ',', // assume
|
||||
'import-account' => 0, // none,
|
||||
'specifics' => [], // none
|
||||
'column-count' => 0, // unknown
|
||||
'column-roles' => [], // unknown
|
||||
'column-do-mapping' => [], // not yet set which columns must be mapped
|
||||
'column-mapping-config' => [], // no mapping made yet.
|
||||
'file-type' => 'csv', // assume
|
||||
'has-config-file' => true,
|
||||
'apply-rules' => true,
|
||||
'match-bills' => false,
|
||||
'auto-start' => false,
|
||||
];
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
|
||||
// give job default config:
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
/**
|
||||
* ConfiguratorInterface constructor.
|
||||
* FileConfigurator constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug('Created FileConfigurator');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,11 +85,12 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function configureJob(array $data): bool
|
||||
{
|
||||
$class = $this->getConfigurationClass();
|
||||
$job = $this->job;
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
/** @var ConfigurationInterface $object */
|
||||
$object = new $class($this->job);
|
||||
$object->setJob($job);
|
||||
$object = app($this->getConfigurationClass());
|
||||
$object->setJob($this->job);
|
||||
$result = $object->storeConfiguration($data);
|
||||
$this->warning = $object->getWarningMessage();
|
||||
|
||||
@@ -80,11 +106,12 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
$class = $this->getConfigurationClass();
|
||||
$job = $this->job;
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getNextData() without a job.');
|
||||
}
|
||||
/** @var ConfigurationInterface $object */
|
||||
$object = app($class);
|
||||
$object->setJob($job);
|
||||
$object = app($this->getConfigurationClass());
|
||||
$object->setJob($this->job);
|
||||
|
||||
return $object->getData();
|
||||
}
|
||||
@@ -96,52 +123,56 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function getNextView(): string
|
||||
{
|
||||
if (!$this->job->configuration['has-file-upload']) {
|
||||
return 'import.file.upload';
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getNextView() without a job.');
|
||||
}
|
||||
if (!$this->job->configuration['initial-config-complete']) {
|
||||
return 'import.file.initial';
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
switch ($stage) {
|
||||
case 'initial': // has nothing, no file upload or anything.
|
||||
return 'import.file.initial';
|
||||
case 'upload-config': // has file, needs file config.
|
||||
return 'import.file.upload-config';
|
||||
case 'roles': // has configured file, needs roles.
|
||||
return 'import.file.roles';
|
||||
case 'map': // has roles, needs mapping.
|
||||
return 'import.file.map';
|
||||
}
|
||||
if (!$this->job->configuration['column-roles-complete']) {
|
||||
return 'import.file.roles';
|
||||
}
|
||||
if (!$this->job->configuration['column-mapping-complete']) {
|
||||
return 'import.file.map';
|
||||
}
|
||||
|
||||
throw new FireflyException('No view for state');
|
||||
throw new FireflyException(sprintf('No view for stage "%s"', $stage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return possible warning to user.
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getWarningMessage(): string
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getWarningMessage() without a job.');
|
||||
}
|
||||
|
||||
return $this->warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function isJobConfigured(): bool
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config['has-file-upload'] = $config['has-file-upload'] ?? false;
|
||||
$config['initial-config-complete'] = $config['initial-config-complete'] ?? false;
|
||||
$config['column-roles-complete'] = $config['column-roles-complete'] ?? false;
|
||||
$config['column-mapping-complete'] = $config['column-mapping-complete'] ?? false;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->save();
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call isJobConfigured() without a job.');
|
||||
}
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
if ($stage === 'ready') {
|
||||
Log::debug('isJobConfigured returns true');
|
||||
|
||||
if ($config['initial-config-complete']
|
||||
&& $config['column-roles-complete']
|
||||
&& $config['column-mapping-complete']
|
||||
&& $config['has-file-upload']
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
Log::debug('isJobConfigured returns false');
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -151,12 +182,30 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
if (null === $this->job->configuration || 0 === count($this->job->configuration)) {
|
||||
Log::debug(sprintf('Gave import job %s initial configuration.', $this->job->key));
|
||||
$this->job->configuration = config('csv.default_config');
|
||||
$this->job->save();
|
||||
}
|
||||
Log::debug(sprintf('FileConfigurator::setJob(#%d: %s)', $job->id, $job->key));
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
// set number of steps to 100:
|
||||
$extendedStatus = $this->getExtendedStatus();
|
||||
$extendedStatus['steps'] = 6;
|
||||
$extendedStatus['done'] = 0;
|
||||
$this->setExtendedStatus($extendedStatus);
|
||||
|
||||
$config = $this->getConfig();
|
||||
$newConfig = array_merge($this->defaultConfig, $config);
|
||||
$this->repository->setConfiguration($job, $newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short hand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,18 +215,22 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
*/
|
||||
private function getConfigurationClass(): string
|
||||
{
|
||||
$class = false;
|
||||
switch (true) {
|
||||
case !$this->job->configuration['has-file-upload']:
|
||||
$class = Upload::class;
|
||||
break;
|
||||
case !$this->job->configuration['initial-config-complete']:
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
$class = false;
|
||||
Log::debug(sprintf('Now in getConfigurationClass() for stage "%s"', $stage));
|
||||
|
||||
switch ($stage) {
|
||||
case 'initial': // has nothing, no file upload or anything.
|
||||
$class = Initial::class;
|
||||
break;
|
||||
case !$this->job->configuration['column-roles-complete']:
|
||||
case 'upload-config': // has file, needs file config.
|
||||
$class = UploadConfig::class;
|
||||
break;
|
||||
case 'roles': // has configured file, needs roles.
|
||||
$class = Roles::class;
|
||||
break;
|
||||
case !$this->job->configuration['column-mapping-complete']:
|
||||
case 'map': // has roles, needs mapping.
|
||||
$class = Map::class;
|
||||
break;
|
||||
default:
|
||||
@@ -185,12 +238,37 @@ class FileConfigurator implements ConfiguratorInterface
|
||||
}
|
||||
|
||||
if (false === $class || 0 === strlen($class)) {
|
||||
throw new FireflyException('Cannot handle current job state in getConfigurationClass().');
|
||||
throw new FireflyException(sprintf('Cannot handle job stage "%s" in getConfigurationClass().', $stage));
|
||||
}
|
||||
if (!class_exists($class)) {
|
||||
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class));
|
||||
throw new FireflyException(sprintf('Class %s does not exist in getConfigurationClass().', $class)); // @codeCoverageIgnore
|
||||
}
|
||||
Log::debug(sprintf('Configuration class is "%s"', $class));
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to return the extended status.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return array
|
||||
*/
|
||||
private function getExtendedStatus(): array
|
||||
{
|
||||
return $this->repository->getExtendedStatus($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to set the extended status.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param array $extended
|
||||
*/
|
||||
private function setExtendedStatus(array $extended): void
|
||||
{
|
||||
$this->repository->setExtendedStatus($this->job, $extended);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Configuration;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Support\Import\Configuration\Spectre\HaveAccounts;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class SpectreConfigurator.
|
||||
@@ -32,6 +36,9 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/** @var string */
|
||||
private $warning = '';
|
||||
|
||||
@@ -48,35 +55,96 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function configureJob(array $data): bool
|
||||
{
|
||||
die('cannot store config');
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
$stage = $this->getConfig()['stage'] ?? 'initial';
|
||||
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'have-accounts':
|
||||
/** @var HaveAccounts $class */
|
||||
$class = app(HaveAccounts::class);
|
||||
$class->setJob($this->job);
|
||||
$class->storeConfiguration($data);
|
||||
|
||||
// update job for next step and set to "configured".
|
||||
$config = $this->getConfig();
|
||||
$config['stage'] = 'have-account-mapping';
|
||||
$this->repository->setConfiguration($this->job, $config);
|
||||
|
||||
return true;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot store configuration when job is in state "%s"', $stage));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data required for the next step in the job configuration.
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getNextData(): array
|
||||
{
|
||||
// update config to tell Firefly we've redirected the user.
|
||||
$config = $this->job->configuration;
|
||||
$config['is-redirected'] = true;
|
||||
$this->job->configuration = $config;
|
||||
$this->job->status = 'configured';
|
||||
$this->job->save();
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
$config = $this->getConfig();
|
||||
$stage = $config['stage'] ?? 'initial';
|
||||
|
||||
return $this->job->configuration;
|
||||
Log::debug(sprintf('in getNextData(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
// simply redirect to Spectre.
|
||||
$config['is-redirected'] = true;
|
||||
$config['stage'] = 'user-logged-in';
|
||||
$status = 'configured';
|
||||
|
||||
// update config and status:
|
||||
$this->repository->setConfiguration($this->job, $config);
|
||||
$this->repository->setStatus($this->job, $status);
|
||||
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
case 'have-accounts':
|
||||
/** @var HaveAccounts $class */
|
||||
$class = app(HaveAccounts::class);
|
||||
$class->setJob($this->job);
|
||||
$data = $class->getData();
|
||||
|
||||
return $data;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getNextView(): string
|
||||
{
|
||||
return 'import.spectre.redirect';
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
$stage = $this->getConfig()['stage'] ?? 'initial';
|
||||
Log::debug(sprintf('in getNextView(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
// redirect to Spectre.
|
||||
Log::info('User is being redirected to Spectre.');
|
||||
|
||||
return 'import.spectre.redirect';
|
||||
case 'have-accounts':
|
||||
return 'import.spectre.accounts';
|
||||
default:
|
||||
return '';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,36 +159,75 @@ class SpectreConfigurator implements ConfiguratorInterface
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function isJobConfigured(): bool
|
||||
{
|
||||
// job is configured (and can start) when token is empty:
|
||||
$config = $this->job->configuration;
|
||||
if ($config['has-token'] === false) {
|
||||
return true;
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call configureJob() without a job.');
|
||||
}
|
||||
$stage = $this->getConfig()['stage'] ?? 'initial';
|
||||
Log::debug(sprintf('in isJobConfigured(), for stage "%s".', $stage));
|
||||
switch ($stage) {
|
||||
case 'has-token':
|
||||
case 'have-accounts':
|
||||
Log::debug('isJobConfigured returns false');
|
||||
|
||||
return false;
|
||||
return false;
|
||||
default:
|
||||
Log::debug('isJobConfigured returns true');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportJob $job
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
public function setJob(ImportJob $job): void
|
||||
{
|
||||
// make repository
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
// set default config:
|
||||
$defaultConfig = [
|
||||
'has-token' => false,
|
||||
'token' => '',
|
||||
'token-expires' => 0,
|
||||
'token-url' => '',
|
||||
'is-redirected' => false,
|
||||
|
||||
'has-token' => false,
|
||||
'token' => '',
|
||||
'token-expires' => 0,
|
||||
'token-url' => '',
|
||||
'is-redirected' => false,
|
||||
'customer' => null,
|
||||
'login' => null,
|
||||
'stage' => 'initial',
|
||||
'accounts' => '',
|
||||
'accounts-mapped' => '',
|
||||
'auto-start' => true,
|
||||
'apply-rules' => true,
|
||||
'match-bills' => false,
|
||||
];
|
||||
$currentConfig = $this->repository->getConfiguration($job);
|
||||
$finalConfig = array_merge($defaultConfig, $currentConfig);
|
||||
|
||||
$config = $job->configuration;
|
||||
$finalConfig = array_merge($defaultConfig, $config);
|
||||
$job->configuration = $finalConfig;
|
||||
$job->save();
|
||||
// set default extended status:
|
||||
$extendedStatus = $this->repository->getExtendedStatus($job);
|
||||
$extendedStatus['steps'] = 6;
|
||||
|
||||
// save to job:
|
||||
$job = $this->repository->setConfiguration($job, $finalConfig);
|
||||
$job = $this->repository->setExtendedStatus($job, $extendedStatus);
|
||||
$this->job = $job;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
}
|
||||
|
@@ -45,8 +45,10 @@ class Amount implements ConverterInterface
|
||||
if (null === $value) {
|
||||
return '0';
|
||||
}
|
||||
$value = strval($value);
|
||||
Log::debug(sprintf('Start with amount "%s"', $value));
|
||||
$original = $value;
|
||||
$value = strval($value);
|
||||
$value = $this->stripAmount($value);
|
||||
$len = strlen($value);
|
||||
$decimalPosition = $len - 3;
|
||||
$altPosition = $len - 2;
|
||||
@@ -75,33 +77,49 @@ class Amount implements ConverterInterface
|
||||
Log::debug(sprintf('Searched from the left for "." in amount "%s", assume this is the decimal sign.', $value));
|
||||
$decimal = '.';
|
||||
}
|
||||
unset($options, $res);
|
||||
unset($res);
|
||||
}
|
||||
|
||||
// if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos)
|
||||
if ('.' === $decimal) {
|
||||
$search = [',', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
$search = [',', ' '];
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
if (',' === $decimal) {
|
||||
$search = ['.', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
$search = ['.', ' '];
|
||||
$value = str_replace($search, '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
if (null === $decimal) {
|
||||
// replace all:
|
||||
$search = ['.', ' ', ','];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
$search = ['.', ' ', ','];
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $original, $value));
|
||||
}
|
||||
|
||||
$number = strval(number_format(round(floatval($value), 12), 12, '.', ''));
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function stripAmount(string $value): string
|
||||
{
|
||||
$str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value);
|
||||
$len = strlen($str);
|
||||
if ($str{0} === '(' && $str{$len - 1} === ')') {
|
||||
$str = '-' . substr($str, 1, ($len - 2));
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Stripped "%s" away to "%s"', $value, $str));
|
||||
|
||||
return $str;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Import\Specifics\SpecificInterface;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Iterator;
|
||||
use League\Csv\Reader;
|
||||
@@ -43,6 +43,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
private $job;
|
||||
/** @var Collection */
|
||||
private $objects;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
/** @var array */
|
||||
private $validConverters = [];
|
||||
/** @var array */
|
||||
@@ -60,9 +62,14 @@ class CsvProcessor implements FileProcessorInterface
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getObjects(): Collection
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call getObjects() without a job.');
|
||||
}
|
||||
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
@@ -73,12 +80,17 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*
|
||||
* @throws \League\Csv\Exception
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
if (is_null($this->job)) {
|
||||
throw new FireflyException('Cannot call run() without a job.');
|
||||
}
|
||||
Log::debug('Now in CsvProcessor run(). Job is now running...');
|
||||
|
||||
$entries = new Collection($this->getImportArray());
|
||||
$this->addStep();
|
||||
Log::notice('Building importable objects from CSV file.');
|
||||
Log::debug(sprintf('Number of entries: %d', $entries->count()));
|
||||
$notImported = $entries->filter(
|
||||
@@ -86,8 +98,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
$row = array_values($row);
|
||||
if ($this->rowAlreadyImported($row)) {
|
||||
$message = sprintf('Row #%d has already been imported.', $index);
|
||||
$this->job->addError($index, $message);
|
||||
$this->job->addStepsDone(5); // all steps.
|
||||
$this->repository->addError($this->job, $index, $message);
|
||||
Log::info($message);
|
||||
|
||||
return null;
|
||||
@@ -96,26 +107,31 @@ class CsvProcessor implements FileProcessorInterface
|
||||
return $row;
|
||||
}
|
||||
);
|
||||
$this->addStep();
|
||||
Log::debug(sprintf('Number of entries left: %d', $notImported->count()));
|
||||
|
||||
// set (new) number of steps:
|
||||
$status = $this->job->extended_status;
|
||||
$status['steps'] = $notImported->count() * 5;
|
||||
$this->job->extended_status = $status;
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Number of steps: %d', $notImported->count() * 5));
|
||||
|
||||
$notImported->each(
|
||||
function (array $row, int $index) {
|
||||
$journal = $this->importRow($index, $row);
|
||||
$this->objects->push($journal);
|
||||
$this->job->addStepsDone(1);
|
||||
}
|
||||
);
|
||||
$this->addStep();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to set the extended status.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @param array $array
|
||||
*/
|
||||
public function setExtendedStatus(array $array)
|
||||
{
|
||||
$this->repository->setExtendedStatus($this->job, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set import job for this processor.
|
||||
*
|
||||
@@ -125,11 +141,23 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job): FileProcessorInterface
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to add a step.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function addStep()
|
||||
{
|
||||
$this->repository->addStepsDone($this->job, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta data to the individual value and verify that it can be handled in a later stage.
|
||||
*
|
||||
@@ -142,7 +170,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function annotateValue(int $index, string $value)
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$role = $config['column-roles'][$index] ?? '_ignore';
|
||||
$mapped = $config['column-mapping-config'][$index][$value] ?? null;
|
||||
|
||||
@@ -160,25 +188,36 @@ class CsvProcessor implements FileProcessorInterface
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method to return configuration.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Iterator
|
||||
*
|
||||
* @throws \League\Csv\Exception
|
||||
* @throws \League\Csv\Exception
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||
*/
|
||||
private function getImportArray(): Iterator
|
||||
{
|
||||
$content = $this->job->uploadFileContents();
|
||||
$config = $this->job->configuration;
|
||||
$reader = Reader::createFromString($content);
|
||||
$delimiter = $config['delimiter'];
|
||||
$content = $this->repository->uploadFileContents($this->job);
|
||||
$config = $this->getConfig();
|
||||
$reader = Reader::createFromString($content);
|
||||
$delimiter = $config['delimiter'] ?? ',';
|
||||
$hasHeaders = isset($config['has-headers']) ? $config['has-headers'] : false;
|
||||
if ('tab' === $delimiter) {
|
||||
$delimiter = "\t";
|
||||
$delimiter = "\t"; // @codeCoverageIgnore
|
||||
}
|
||||
$reader->setDelimiter($delimiter);
|
||||
if ($config['has-headers']) {
|
||||
$reader->setHeaderOffset(0);
|
||||
if ($hasHeaders) {
|
||||
$reader->setHeaderOffset(0); // @codeCoverageIgnore
|
||||
}
|
||||
$results = $reader->getRecords();
|
||||
Log::debug('Created a CSV reader.');
|
||||
@@ -191,6 +230,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*
|
||||
* @param int $jsonError
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @return string
|
||||
*/
|
||||
private function getJsonError(int $jsonError): string
|
||||
@@ -230,7 +270,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
$jsonError = json_last_error();
|
||||
|
||||
if (false === $json) {
|
||||
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError)));
|
||||
throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError))); // @codeCoverageIgnore
|
||||
}
|
||||
$hash = hash('sha256', $json);
|
||||
|
||||
@@ -251,8 +291,9 @@ class CsvProcessor implements FileProcessorInterface
|
||||
{
|
||||
$row = array_values($row);
|
||||
Log::debug(sprintf('Now at row %d', $index));
|
||||
$row = $this->specifics($row);
|
||||
$hash = $this->getRowHash($row);
|
||||
$row = $this->specifics($row);
|
||||
$hash = $this->getRowHash($row);
|
||||
$config = $this->getConfig();
|
||||
|
||||
$journal = new ImportJournal;
|
||||
$journal->setUser($this->job->user);
|
||||
@@ -271,7 +312,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
}
|
||||
}
|
||||
// set some extra info:
|
||||
$journal->asset->setDefaultAccountId($this->job->configuration['import-account']);
|
||||
$importAccount = intval($config['import-account'] ?? 0);
|
||||
$journal->asset->setDefaultAccountId($importAccount);
|
||||
|
||||
return $journal;
|
||||
}
|
||||
@@ -288,12 +330,8 @@ class CsvProcessor implements FileProcessorInterface
|
||||
private function rowAlreadyImported(array $array): bool
|
||||
{
|
||||
$hash = $this->getRowHash($array);
|
||||
$json = json_encode($hash);
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
if (null !== $entry) {
|
||||
$count = $this->repository->countByHash($hash);
|
||||
if ($count > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -311,7 +349,7 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function specifics(array $row): array
|
||||
{
|
||||
$config = $this->job->configuration;
|
||||
$config = $this->getConfig();
|
||||
$names = array_keys($config['specifics'] ?? []);
|
||||
foreach ($names as $name) {
|
||||
if (!in_array($name, $this->validSpecifics)) {
|
||||
|
@@ -26,6 +26,7 @@ use Illuminate\Console\Command;
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Class CommandHandler.
|
||||
*/
|
||||
class CommandHandler extends AbstractProcessingHandler
|
||||
|
@@ -44,18 +44,17 @@ class AssetAccountIbans implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$iban = $account->iban ?? '';
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
if (strlen($iban) > 0) {
|
||||
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
|
||||
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
|
||||
}
|
||||
if (0 === strlen($iban)) {
|
||||
$list[$account->id] = $account->name;
|
||||
$list[$accountId] = $account->name;
|
||||
}
|
||||
}
|
||||
asort($topList);
|
||||
asort($list);
|
||||
|
||||
$list = $topList + $list;
|
||||
asort($list);
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -43,16 +43,15 @@ class AssetAccounts implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
if (strlen($iban) > 0) {
|
||||
$name .= ' (' . $account->iban . ')';
|
||||
$name .= ' (' . $iban . ')';
|
||||
}
|
||||
$list[$account->id] = $name;
|
||||
$list[$accountId] = $name;
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -42,10 +42,10 @@ class Bills implements MapperInterface
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($result as $bill) {
|
||||
$list[$bill->id] = $bill->name . ' [' . $bill->match . ']';
|
||||
$billId = intval($bill->id);
|
||||
$list[$billId] = $bill->name . ' [' . $bill->match . ']';
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -37,15 +37,15 @@ class Budgets implements MapperInterface
|
||||
{
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$result = $repository->getBudgets();
|
||||
$result = $repository->getActiveBudgets();
|
||||
$list = [];
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($result as $budget) {
|
||||
$list[$budget->id] = $budget->name;
|
||||
$budgetId = intval($budget->id);
|
||||
$list[$budgetId] = $budget->name;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -42,10 +42,10 @@ class Categories implements MapperInterface
|
||||
|
||||
/** @var Category $category */
|
||||
foreach ($result as $category) {
|
||||
$list[$category->id] = $category->name;
|
||||
$categoryId = intval($category->id);
|
||||
$list[$categoryId] = $category->name;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -50,18 +50,17 @@ class OpposingAccountIbans implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$iban = $account->iban ?? '';
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
if (strlen($iban) > 0) {
|
||||
$topList[$account->id] = $account->iban . ' (' . $account->name . ')';
|
||||
$topList[$accountId] = $account->iban . ' (' . $account->name . ')';
|
||||
}
|
||||
if (0 === strlen($iban)) {
|
||||
$list[$account->id] = $account->name;
|
||||
$list[$accountId] = $account->name;
|
||||
}
|
||||
}
|
||||
asort($topList);
|
||||
asort($list);
|
||||
|
||||
$list = $topList + $list;
|
||||
asort($list);
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -49,16 +49,15 @@ class OpposingAccounts implements MapperInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
$accountId = intval($account->id);
|
||||
$name = $account->name;
|
||||
$iban = $account->iban ?? '';
|
||||
if (strlen($iban) > 0) {
|
||||
$name .= ' (' . $account->iban . ')';
|
||||
$name .= ' (' . $iban . ')';
|
||||
}
|
||||
$list[$account->id] = $name;
|
||||
$list[$accountId] = $name;
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -42,10 +42,10 @@ class Tags implements MapperInterface
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($result as $tag) {
|
||||
$list[$tag->id] = $tag->tag;
|
||||
$tagId = intval($tag->id);
|
||||
$list[$tagId] = $tag->tag;
|
||||
}
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
||||
return $list;
|
||||
|
@@ -22,7 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Mapper;
|
||||
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class TransactionCurrencies.
|
||||
@@ -34,12 +34,14 @@ class TransactionCurrencies implements MapperInterface
|
||||
*/
|
||||
public function getMap(): array
|
||||
{
|
||||
$currencies = TransactionCurrency::get();
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currencies = $repository->get();
|
||||
$list = [];
|
||||
foreach ($currencies as $currency) {
|
||||
$list[$currency->id] = $currency->name . ' (' . $currency->code . ')';
|
||||
$currencyId = intval($currency->id);
|
||||
$list[$currencyId] = $currency->name . ' (' . $currency->code . ')';
|
||||
}
|
||||
|
||||
asort($list);
|
||||
|
||||
$list = [0 => trans('import.map_do_not_map')] + $list;
|
||||
|
@@ -34,6 +34,11 @@ class TagsComma implements PreProcessorInterface
|
||||
*/
|
||||
public function run(string $value): array
|
||||
{
|
||||
return explode(',', $value);
|
||||
$set = explode(',', $value);
|
||||
$set = array_map('trim', $set);
|
||||
$set = array_filter($set, 'strlen');
|
||||
$return = array_values($set);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,11 @@ class TagsSpace implements PreProcessorInterface
|
||||
*/
|
||||
public function run(string $value): array
|
||||
{
|
||||
return explode(' ', $value);
|
||||
$set = explode(' ', $value);
|
||||
$set = array_map('trim', $set);
|
||||
$set = array_filter($set, 'strlen');
|
||||
$return = array_values($set);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Object;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@@ -74,6 +75,7 @@ class ImportAccount
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAccount(): Account
|
||||
{
|
||||
@@ -85,6 +87,7 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* @return string
|
||||
*/
|
||||
public function getExpectedType(): string
|
||||
@@ -93,6 +96,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $expectedType
|
||||
*/
|
||||
public function setExpectedType(string $expectedType)
|
||||
@@ -101,6 +106,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountIban
|
||||
*/
|
||||
public function setAccountIban(array $accountIban)
|
||||
@@ -109,6 +116,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $value
|
||||
*/
|
||||
public function setAccountId(array $value)
|
||||
@@ -117,6 +126,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountName
|
||||
*/
|
||||
public function setAccountName(array $accountName)
|
||||
@@ -125,6 +136,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param array $accountNumber
|
||||
*/
|
||||
public function setAccountNumber(array $accountNumber)
|
||||
@@ -133,6 +146,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $defaultAccountId
|
||||
*/
|
||||
public function setDefaultAccountId(int $defaultAccountId)
|
||||
@@ -141,6 +156,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param int $forbiddenAccountId
|
||||
*/
|
||||
public function setForbiddenAccountId(int $forbiddenAccountId)
|
||||
@@ -149,6 +166,8 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
@@ -158,20 +177,21 @@ class ImportAccount
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
private function findExistingObject(): Account
|
||||
private function findExistingObject(): ?Account
|
||||
{
|
||||
Log::debug('In findExistingObject() for Account');
|
||||
// 0: determin account type:
|
||||
/** @var AccountType $accountType */
|
||||
$accountType = AccountType::whereType($this->expectedType)->first();
|
||||
$accountType = $this->repository->getAccountType($this->expectedType);
|
||||
|
||||
// 1: find by ID, iban or name (and type)
|
||||
if (3 === count($this->accountId)) {
|
||||
Log::debug(sprintf('Finding account of type %d and ID %d', $accountType->id, $this->accountId['value']));
|
||||
/** @var Account $account */
|
||||
|
||||
$account = $this->user->accounts()->where('id', '!=', $this->forbiddenAccountId)->where('account_type_id', $accountType->id)->where(
|
||||
'id',
|
||||
$this->accountId['value']
|
||||
@@ -233,13 +253,13 @@ class ImportAccount
|
||||
// 4: do not search by account number.
|
||||
Log::debug('Found NO existing accounts.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
*/
|
||||
private function findMappedObject(): Account
|
||||
private function findMappedObject(): ?Account
|
||||
{
|
||||
Log::debug('In findMappedObject() for Account');
|
||||
$fields = ['accountId', 'accountIban', 'accountNumber', 'accountName'];
|
||||
@@ -248,7 +268,7 @@ class ImportAccount
|
||||
Log::debug(sprintf('Find mapped account based on field "%s" with value', $field), $array);
|
||||
// check if a pre-mapped object exists.
|
||||
$mapped = $this->getMappedObject($array);
|
||||
if (null !== $mapped->id) {
|
||||
if (null !== $mapped) {
|
||||
Log::debug(sprintf('Found account #%d!', $mapped->id));
|
||||
|
||||
return $mapped;
|
||||
@@ -256,38 +276,38 @@ class ImportAccount
|
||||
}
|
||||
Log::debug('Found no account on mapped data or no map present.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*
|
||||
* @return Account
|
||||
* @return Account|null
|
||||
*/
|
||||
private function getMappedObject(array $array): Account
|
||||
private function getMappedObject(array $array): ?Account
|
||||
{
|
||||
Log::debug('In getMappedObject() for Account');
|
||||
if (0 === count($array)) {
|
||||
Log::debug('Array is empty, nothing will come of this.');
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (array_key_exists('mapped', $array) && null === $array['mapped']) {
|
||||
Log::debug(sprintf('No map present for value "%s". Return NULL.', $array['value']));
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::debug('Finding a mapped account based on', $array);
|
||||
|
||||
$search = intval($array['mapped']);
|
||||
$search = intval($array['mapped'] ?? 0);
|
||||
$account = $this->repository->find($search);
|
||||
|
||||
if (null === $account->id) {
|
||||
Log::error(sprintf('There is no account with id #%d. Invalid mapping will be ignored!', $search));
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
// must be of the same type
|
||||
// except when mapped is an asset, then it's fair game.
|
||||
@@ -302,7 +322,7 @@ class ImportAccount
|
||||
)
|
||||
);
|
||||
|
||||
return new Account;
|
||||
return null;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Found account! #%d ("%s"). Return it', $account->id, $account->name));
|
||||
@@ -312,19 +332,26 @@ class ImportAccount
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function store(): bool
|
||||
{
|
||||
if (is_null($this->user)) {
|
||||
throw new FireflyException('ImportAccount cannot continue without user.');
|
||||
}
|
||||
if ((is_null($this->defaultAccountId) || intval($this->defaultAccountId) === 0) && AccountType::ASSET === $this->expectedType) {
|
||||
throw new FireflyException('ImportAccount cannot continue without a default account to fall back on.');
|
||||
}
|
||||
// 1: find mapped object:
|
||||
$mapped = $this->findMappedObject();
|
||||
if (null !== $mapped->id) {
|
||||
if (null !== $mapped) {
|
||||
$this->account = $mapped;
|
||||
|
||||
return true;
|
||||
}
|
||||
// 2: find existing by given values:
|
||||
$found = $this->findExistingObject();
|
||||
if (null !== $found->id) {
|
||||
if (null !== $found) {
|
||||
$this->account = $found;
|
||||
|
||||
return true;
|
||||
@@ -335,7 +362,7 @@ class ImportAccount
|
||||
$oldExpectedType = $this->expectedType;
|
||||
$this->expectedType = AccountType::ASSET;
|
||||
$found = $this->findExistingObject();
|
||||
if (null !== $found->id) {
|
||||
if (null !== $found) {
|
||||
Log::debug('Found asset account!');
|
||||
$this->account = $found;
|
||||
|
||||
|
@@ -48,6 +48,8 @@ class ImportJournal
|
||||
public $currency;
|
||||
/** @var string */
|
||||
public $description = '';
|
||||
/** @var ImportCurrency */
|
||||
public $foreignCurrency;
|
||||
/** @var string */
|
||||
public $hash;
|
||||
/** @var array */
|
||||
@@ -71,6 +73,8 @@ class ImportJournal
|
||||
/** @var string */
|
||||
private $externalId = '';
|
||||
/** @var array */
|
||||
private $foreignAmount;
|
||||
/** @var array */
|
||||
private $modifiers = [];
|
||||
/** @var User */
|
||||
private $user;
|
||||
@@ -80,12 +84,13 @@ class ImportJournal
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->asset = new ImportAccount;
|
||||
$this->opposing = new ImportAccount;
|
||||
$this->bill = new ImportBill;
|
||||
$this->category = new ImportCategory;
|
||||
$this->budget = new ImportBudget;
|
||||
$this->currency = new ImportCurrency;
|
||||
$this->asset = new ImportAccount;
|
||||
$this->opposing = new ImportAccount;
|
||||
$this->bill = new ImportBill;
|
||||
$this->category = new ImportCategory;
|
||||
$this->budget = new ImportBudget;
|
||||
$this->currency = new ImportCurrency;
|
||||
$this->foreignCurrency = new ImportCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,6 +184,8 @@ class ImportJournal
|
||||
*/
|
||||
public function setValue(array $array)
|
||||
{
|
||||
$array['mapped'] = $array['mapped'] ?? null;
|
||||
$array['value'] = $array['value'] ?? null;
|
||||
switch ($array['role']) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('ImportJournal cannot handle "%s" with value "%s".', $array['role'], $array['value']));
|
||||
@@ -188,6 +195,12 @@ class ImportJournal
|
||||
case 'amount':
|
||||
$this->amount = $array;
|
||||
break;
|
||||
case 'amount_foreign':
|
||||
$this->foreignAmount = $array;
|
||||
break;
|
||||
case 'foreign-currency-code':
|
||||
$this->foreignCurrency->setId($array);
|
||||
break;
|
||||
case 'amount_debit':
|
||||
$this->amountDebit = $array;
|
||||
break;
|
||||
@@ -302,6 +315,10 @@ class ImportJournal
|
||||
if (0 === count($info)) {
|
||||
throw new FireflyException('No amount information for this row.');
|
||||
}
|
||||
$class = $info['class'] ?? '';
|
||||
if (strlen($class) === 0) {
|
||||
throw new FireflyException('No amount information (conversion class) for this row.');
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Converter class is %s', $info['class']));
|
||||
/** @var ConverterInterface $amountConverter */
|
||||
|
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Prerequisites;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\MessageBag;
|
||||
@@ -62,9 +63,13 @@ class FilePrerequisites implements PrerequisitesInterface
|
||||
* True if prerequisites. False if not.
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function hasPrerequisites(): bool
|
||||
{
|
||||
if($this->user->hasRole('demo')) {
|
||||
throw new FireflyException('Apologies, the demo user cannot import files.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Import\FileProcessor\FileProcessorInterface;
|
||||
use FireflyIII\Import\Storage\ImportStorage;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
@@ -46,6 +47,9 @@ class FileRoutine implements RoutineInterface
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* ImportRoutine constructor.
|
||||
*/
|
||||
@@ -84,26 +88,32 @@ class FileRoutine implements RoutineInterface
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
if ('configured' !== $this->job->status) {
|
||||
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
|
||||
if ('configured' !== $this->getStatus()) {
|
||||
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->getStatus()));
|
||||
|
||||
return false;
|
||||
}
|
||||
set_time_limit(0);
|
||||
Log::info(sprintf('Start with import job %s', $this->job->key));
|
||||
|
||||
// total steps: 6
|
||||
$this->setTotalSteps(6);
|
||||
|
||||
$importObjects = $this->getImportObjects();
|
||||
$this->lines = $importObjects->count();
|
||||
$this->addStep();
|
||||
|
||||
// total steps can now be extended. File has been scanned. 7 steps per line:
|
||||
$this->addTotalSteps(7 * $this->lines);
|
||||
|
||||
// once done, use storage thing to actually store them:
|
||||
Log::info(sprintf('Returned %d valid objects from file processor', $this->lines));
|
||||
|
||||
$storage = $this->storeObjects($importObjects);
|
||||
$this->addStep();
|
||||
Log::debug('Back in run()');
|
||||
|
||||
// update job:
|
||||
$this->job->status = 'finished';
|
||||
$this->job->save();
|
||||
|
||||
|
||||
Log::debug('Updated job...');
|
||||
Log::debug(sprintf('%d journals in $storage->journals', $storage->journals->count()));
|
||||
@@ -114,6 +124,10 @@ class FileRoutine implements RoutineInterface
|
||||
|
||||
// create tag, link tag to all journals:
|
||||
$this->createImportTag();
|
||||
$this->addStep();
|
||||
|
||||
// update job:
|
||||
$this->setStatus('finished');
|
||||
|
||||
Log::info(sprintf('Done with import job %s', $this->job->key));
|
||||
|
||||
@@ -125,7 +139,9 @@ class FileRoutine implements RoutineInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,18 +150,16 @@ class FileRoutine implements RoutineInterface
|
||||
protected function getImportObjects(): Collection
|
||||
{
|
||||
$objects = new Collection;
|
||||
$config = $this->job->configuration;
|
||||
$fileType = $config['file-type'] ?? 'csv';
|
||||
$fileType = $this->getConfig()['file-type'] ?? 'csv';
|
||||
// will only respond to "file"
|
||||
$class = config(sprintf('import.options.file.processors.%s', $fileType));
|
||||
/** @var FileProcessorInterface $processor */
|
||||
$processor = app($class);
|
||||
$processor->setJob($this->job);
|
||||
|
||||
if ('configured' === $this->job->status) {
|
||||
if ('configured' === $this->getStatus()) {
|
||||
// set job as "running"...
|
||||
$this->job->status = 'running';
|
||||
$this->job->save();
|
||||
$this->setStatus('running');
|
||||
|
||||
Log::debug('Job is configured, start with run()');
|
||||
$processor->run();
|
||||
@@ -155,6 +169,14 @@ class FileRoutine implements RoutineInterface
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*/
|
||||
private function addStep()
|
||||
{
|
||||
$this->repository->addStepsDone($this->job, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -167,11 +189,12 @@ class FileRoutine implements RoutineInterface
|
||||
|
||||
return new Tag;
|
||||
}
|
||||
$this->addTotalSteps($this->journals->count() + 2);
|
||||
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$repository->setUser($this->job->user);
|
||||
$data = [
|
||||
$data = [
|
||||
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
|
||||
'date' => new Carbon,
|
||||
'description' => null,
|
||||
@@ -180,11 +203,11 @@ class FileRoutine implements RoutineInterface
|
||||
'zoomLevel' => null,
|
||||
'tagMode' => 'nothing',
|
||||
];
|
||||
$tag = $repository->store($data);
|
||||
$extended = $this->job->extended_status;
|
||||
$extended['tag'] = $tag->id;
|
||||
$this->job->extended_status = $extended;
|
||||
$this->job->save();
|
||||
$tag = $repository->store($data);
|
||||
$this->addStep();
|
||||
$extended = $this->getExtendedStatus();
|
||||
$extended['tag'] = $tag->id;
|
||||
$this->setExtendedStatus($extended);
|
||||
|
||||
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
|
||||
Log::debug('Looping journals...');
|
||||
@@ -193,12 +216,81 @@ class FileRoutine implements RoutineInterface
|
||||
foreach ($journalIds as $journalId) {
|
||||
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
|
||||
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
|
||||
$this->addStep();
|
||||
}
|
||||
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $this->journals->count(), $tag->id, $tag->tag));
|
||||
|
||||
$this->addStep();
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getExtendedStatus(): array
|
||||
{
|
||||
return $this->repository->getExtendedStatus($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getStatus(): string
|
||||
{
|
||||
return $this->repository->getStatus($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $extended
|
||||
*/
|
||||
private function setExtendedStatus(array $extended): void
|
||||
{
|
||||
$this->repository->setExtendedStatus($this->job, $extended);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand
|
||||
*
|
||||
* @param string $status
|
||||
*/
|
||||
private function setStatus(string $status): void
|
||||
{
|
||||
$this->repository->setStatus($this->job, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand
|
||||
*
|
||||
* @param int $steps
|
||||
*/
|
||||
private function setTotalSteps(int $steps)
|
||||
{
|
||||
$this->repository->setTotalSteps($this->job, $steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand
|
||||
*
|
||||
* @param int $steps
|
||||
*/
|
||||
private function addTotalSteps(int $steps)
|
||||
{
|
||||
$this->repository->addTotalSteps($this->job, $steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $objects
|
||||
*
|
||||
@@ -206,9 +298,10 @@ class FileRoutine implements RoutineInterface
|
||||
*/
|
||||
private function storeObjects(Collection $objects): ImportStorage
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$storage = new ImportStorage;
|
||||
$storage->setJob($this->job);
|
||||
$storage->setDateFormat($this->job->configuration['date-format']);
|
||||
$storage->setDateFormat($config['date-format']);
|
||||
$storage->setObjects($objects);
|
||||
$storage->store();
|
||||
Log::info('Back in storeObjects()');
|
||||
|
@@ -22,11 +22,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Routine;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Import\Storage\ImportStorage;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Services\Spectre\Exception\DuplicatedCustomerException;
|
||||
use FireflyIII\Services\Spectre\Exception\SpectreException;
|
||||
use FireflyIII\Services\Spectre\Object\Account;
|
||||
use FireflyIII\Services\Spectre\Object\Customer;
|
||||
use FireflyIII\Services\Spectre\Object\Login;
|
||||
use FireflyIII\Services\Spectre\Object\Token;
|
||||
use FireflyIII\Services\Spectre\Object\Transaction;
|
||||
use FireflyIII\Services\Spectre\Request\CreateTokenRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListAccountsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListCustomersRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListLoginsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\ListTransactionsRequest;
|
||||
use FireflyIII\Services\Spectre\Request\NewCustomerRequest;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
@@ -46,6 +61,9 @@ class SpectreRoutine implements RoutineInterface
|
||||
/** @var ImportJob */
|
||||
private $job;
|
||||
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* ImportRoutine constructor.
|
||||
*/
|
||||
@@ -80,58 +98,58 @@ class SpectreRoutine implements RoutineInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spectre job that ends up here is either "configured" or "running", and will be set to "running"
|
||||
* when it is "configured".
|
||||
*
|
||||
* Job has several stages, stored in extended status key 'stage'
|
||||
*
|
||||
* initial: just begun, nothing happened. action: get a customer and a token. Next status: has-token
|
||||
* has-token: redirect user to sandstorm, make user login. set job to: user-logged-in
|
||||
* user-logged-in: customer has an attempt. action: analyse/get attempt and go for next status.
|
||||
* if attempt failed: job status is error, save a warning somewhere?
|
||||
* if success, try to get accounts. Save in config key 'accounts'. set status: have-accounts and "configuring"
|
||||
*
|
||||
* have-accounts: make user link accounts and select accounts to import from.
|
||||
*
|
||||
* If job is "configuring" and stage "have-accounts" then present the accounts and make user link them to
|
||||
* own asset accounts. Store this mapping, set config to "have-account-mapping" and job status configured".
|
||||
*
|
||||
* have-account-mapping: start downloading transactions?
|
||||
*
|
||||
*
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
public function run(): bool
|
||||
{
|
||||
if ('configured' !== $this->job->status) {
|
||||
Log::error(sprintf('Job %s is in state "%s" so it cannot be started.', $this->job->key, $this->job->status));
|
||||
|
||||
return false;
|
||||
if ('configured' === $this->getStatus()) {
|
||||
$this->repository->updateStatus($this->job, 'running');
|
||||
}
|
||||
Log::info(sprintf('Start with import job %s using Spectre.', $this->job->key));
|
||||
set_time_limit(0);
|
||||
|
||||
// check if job has token first!
|
||||
$config = $this->job->configuration;
|
||||
$hasToken = $config['has-token'] ?? false;
|
||||
if ($hasToken === false) {
|
||||
Log::debug('Job has no token');
|
||||
// create customer if user does not have one:
|
||||
$customer = $this->getCustomer();
|
||||
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
|
||||
// use customer to request a token:
|
||||
$uri = route('import.status', [$this->job->key]);
|
||||
$token = $this->getToken($customer, $uri);
|
||||
Log::debug(sprintf('Token is %s', $token->getToken()));
|
||||
$stage = $this->getConfig()['stage'] ?? 'unknown';
|
||||
|
||||
// update job, give it the token:
|
||||
$config = $this->job->configuration;
|
||||
$config['has-token'] = true;
|
||||
$config['token'] = $token->getToken();
|
||||
$config['token-expires'] = $token->getExpiresAt()->format('U');
|
||||
$config['token-url'] = $token->getConnectUrl();
|
||||
$this->job->configuration = $config;
|
||||
|
||||
Log::debug('Job config is now', $config);
|
||||
|
||||
// update job, set status to "configuring".
|
||||
$this->job->status = 'configuring';
|
||||
$this->job->save();
|
||||
Log::debug(sprintf('Job status is now %s', $this->job->status));
|
||||
|
||||
return true;
|
||||
}
|
||||
$isRedirected = $config['is-redirected'] ?? false;
|
||||
if ($isRedirected === true) {
|
||||
// assume user has "used" the token.
|
||||
// ...
|
||||
// now what?
|
||||
throw new FireflyException('Application cannot handle this.');
|
||||
switch ($stage) {
|
||||
case 'initial':
|
||||
// get customer and token:
|
||||
$this->runStageInitial();
|
||||
break;
|
||||
case 'has-token':
|
||||
// import routine does nothing at this point:
|
||||
break;
|
||||
case 'user-logged-in':
|
||||
$this->runStageLoggedIn();
|
||||
break;
|
||||
case 'have-account-mapping':
|
||||
$this->runStageHaveMapping();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle stage %s', $stage));
|
||||
}
|
||||
|
||||
throw new FireflyException('Application cannot handle this.');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,18 +157,40 @@ class SpectreRoutine implements RoutineInterface
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->job = $job;
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function createCustomer(): Customer
|
||||
{
|
||||
$newCustomerRequest = new NewCustomerRequest($this->job->user);
|
||||
$newCustomerRequest->call();
|
||||
$customer = $newCustomerRequest->getCustomer();
|
||||
$customer = null;
|
||||
try {
|
||||
$newCustomerRequest->call();
|
||||
$customer = $newCustomerRequest->getCustomer();
|
||||
} catch (DuplicatedCustomerException $e) {
|
||||
// already exists, must fetch customer instead.
|
||||
Log::warning('Customer exists already for user, fetch it.');
|
||||
}
|
||||
if (is_null($customer)) {
|
||||
$getCustomerRequest = new ListCustomersRequest($this->job->user);
|
||||
$getCustomerRequest->call();
|
||||
$customers = $getCustomerRequest->getCustomers();
|
||||
/** @var Customer $current */
|
||||
foreach ($customers as $current) {
|
||||
if ($current->getIdentifier() === 'default_ff3_customer') {
|
||||
$customer = $current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Preferences::setForUser($this->job->user, 'spectre_customer', $customer->toArray());
|
||||
|
||||
@@ -160,15 +200,25 @@ class SpectreRoutine implements RoutineInterface
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function getCustomer(): Customer
|
||||
{
|
||||
$preference = Preferences::getForUser($this->job->user, 'spectre_customer', null);
|
||||
if (is_null($preference)) {
|
||||
return $this->createCustomer();
|
||||
$config = $this->getConfig();
|
||||
if (!is_null($config['customer'])) {
|
||||
$customer = new Customer($config['customer']);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
$customer = new Customer($preference->data);
|
||||
|
||||
$customer = $this->createCustomer();
|
||||
$config['customer'] = [
|
||||
'id' => $customer->getId(),
|
||||
'identifier' => $customer->getIdentifier(),
|
||||
'secret' => $customer->getSecret(),
|
||||
];
|
||||
$this->setConfig($config);
|
||||
|
||||
return $customer;
|
||||
}
|
||||
@@ -179,6 +229,7 @@ class SpectreRoutine implements RoutineInterface
|
||||
*
|
||||
* @return Token
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \FireflyIII\Services\Spectre\Exception\SpectreException
|
||||
*/
|
||||
protected function getToken(Customer $customer, string $returnUri): Token
|
||||
{
|
||||
@@ -191,4 +242,351 @@ class SpectreRoutine implements RoutineInterface
|
||||
return $request->getToken();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function runStageInitial(): void
|
||||
{
|
||||
Log::debug('In runStageInitial()');
|
||||
|
||||
// create customer if user does not have one:
|
||||
$customer = $this->getCustomer();
|
||||
Log::debug(sprintf('Customer ID is %s', $customer->getId()));
|
||||
|
||||
// use customer to request a token:
|
||||
$uri = route('import.status', [$this->job->key]);
|
||||
$token = $this->getToken($customer, $uri);
|
||||
Log::debug(sprintf('Token is %s', $token->getToken()));
|
||||
|
||||
// update job, give it the token:
|
||||
$config = $this->getConfig();
|
||||
$config['has-token'] = true;
|
||||
$config['token'] = $token->getToken();
|
||||
$config['token-expires'] = $token->getExpiresAt()->format('U');
|
||||
$config['token-url'] = $token->getConnectUrl();
|
||||
$config['stage'] = 'has-token';
|
||||
$this->setConfig($config);
|
||||
|
||||
Log::debug('Job config is now', $config);
|
||||
|
||||
// update job, set status to "configuring".
|
||||
$this->setStatus('configuring');
|
||||
Log::debug(sprintf('Job status is now %s', $this->job->status));
|
||||
$this->addStep();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
protected function runStageLoggedIn(): void
|
||||
{
|
||||
Log::debug('In runStageLoggedIn');
|
||||
// list all logins:
|
||||
$customer = $this->getCustomer();
|
||||
$request = new ListLoginsRequest($this->job->user);
|
||||
$request->setCustomer($customer);
|
||||
$request->call();
|
||||
|
||||
$logins = $request->getLogins();
|
||||
/** @var Login $final */
|
||||
$final = null;
|
||||
// loop logins, find the latest with no error in it:
|
||||
$time = 0;
|
||||
/** @var Login $login */
|
||||
foreach ($logins as $login) {
|
||||
$attempt = $login->getLastAttempt();
|
||||
$attemptTime = intval($attempt->getCreatedAt()->format('U'));
|
||||
if ($attemptTime > $time && is_null($attempt->getFailErrorClass())) {
|
||||
$time = $attemptTime;
|
||||
$final = $login;
|
||||
}
|
||||
}
|
||||
if (is_null($final)) {
|
||||
Log::error('Could not find a valid login for this user.');
|
||||
$this->repository->addError($this->job, 0, 'Spectre connection failed. Did you use invalid credentials, press Cancel or failed the 2FA challenge?');
|
||||
$this->repository->setStatus($this->job, 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->addStep();
|
||||
|
||||
// list the users accounts using this login.
|
||||
$accountRequest = new ListAccountsRequest($this->job->user);
|
||||
$accountRequest->setLogin($login);
|
||||
$accountRequest->call();
|
||||
$accounts = $accountRequest->getAccounts();
|
||||
|
||||
// store accounts in job:
|
||||
$all = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$all[] = $account->toArray();
|
||||
}
|
||||
|
||||
// update job:
|
||||
$config = $this->getConfig();
|
||||
$config['accounts'] = $all;
|
||||
$config['login'] = $login->toArray();
|
||||
$config['stage'] = 'have-accounts';
|
||||
|
||||
$this->setConfig($config);
|
||||
$this->setStatus('configuring');
|
||||
$this->addStep();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*/
|
||||
private function addStep()
|
||||
{
|
||||
$this->repository->addStepsDone($this->job, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand
|
||||
*
|
||||
* @param int $steps
|
||||
*/
|
||||
private function addTotalSteps(int $steps)
|
||||
{
|
||||
$this->repository->addTotalSteps($this->job, $steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getConfig(): array
|
||||
{
|
||||
return $this->repository->getConfiguration($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getExtendedStatus(): array
|
||||
{
|
||||
return $this->repository->getExtendedStatus($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getStatus(): string
|
||||
{
|
||||
return $this->repository->getStatus($this->job);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $all
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function importTransactions(array $all)
|
||||
{
|
||||
Log::debug('Going to import transactions');
|
||||
$collection = new Collection;
|
||||
// create import objects?
|
||||
foreach ($all as $accountId => $data) {
|
||||
Log::debug(sprintf('Now at account #%d', $accountId));
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($data['transactions'] as $transaction) {
|
||||
Log::debug(sprintf('Now at transaction #%d', $transaction->getId()));
|
||||
/** @var Account $account */
|
||||
$account = $data['account'];
|
||||
$importJournal = new ImportJournal;
|
||||
$importJournal->setUser($this->job->user);
|
||||
$importJournal->asset->setDefaultAccountId($data['import_id']);
|
||||
// call set value a bunch of times for various data entries:
|
||||
$tags = [];
|
||||
$tags[] = $transaction->getMode();
|
||||
$tags[] = $transaction->getStatus();
|
||||
if ($transaction->isDuplicated()) {
|
||||
$tags[] = 'possibly-duplicated';
|
||||
}
|
||||
$extra = $transaction->getExtra()->toArray();
|
||||
$notes = '';
|
||||
$notes .= strval(trans('import.imported_from_account', ['account' => $account->getName()])) . ' '
|
||||
. "\n"; // double space for newline in Markdown.
|
||||
|
||||
foreach ($extra as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'account_number':
|
||||
$importJournal->setValue(['role' => 'account-number', 'value' => $value]);
|
||||
break;
|
||||
case 'original_category':
|
||||
case 'original_subcategory':
|
||||
case 'customer_category_code':
|
||||
case 'customer_category_name':
|
||||
$tags[] = $value;
|
||||
break;
|
||||
case 'payee':
|
||||
$importJournal->setValue(['role' => 'opposing-name', 'value' => $value]);
|
||||
break;
|
||||
case 'original_amount':
|
||||
$importJournal->setValue(['role' => 'amount_foreign', 'value' => $value]);
|
||||
break;
|
||||
case 'original_currency_code':
|
||||
$importJournal->setValue(['role' => 'foreign-currency-code', 'value' => $value]);
|
||||
break;
|
||||
default:
|
||||
$notes .= $key . ': ' . $value . ' '; // for newline in Markdown.
|
||||
}
|
||||
}
|
||||
// hash
|
||||
$importJournal->setHash($transaction->getHash());
|
||||
|
||||
// account ID (Firefly III account):
|
||||
$importJournal->setValue(['role' => 'account-id', 'value' => $data['import_id'], 'mapped' => $data['import_id']]);
|
||||
|
||||
// description:
|
||||
$importJournal->setValue(['role' => 'description', 'value' => $transaction->getDescription()]);
|
||||
|
||||
// date:
|
||||
$importJournal->setValue(['role' => 'date-transaction', 'value' => $transaction->getMadeOn()->toIso8601String()]);
|
||||
|
||||
|
||||
// amount
|
||||
$importJournal->setValue(['role' => 'amount', 'value' => $transaction->getAmount()]);
|
||||
$importJournal->setValue(['role' => 'currency-code', 'value' => $transaction->getCurrencyCode()]);
|
||||
|
||||
|
||||
// various meta fields:
|
||||
$importJournal->setValue(['role' => 'category-name', 'value' => $transaction->getCategory()]);
|
||||
$importJournal->setValue(['role' => 'note', 'value' => $notes]);
|
||||
$importJournal->setValue(['role' => 'tags-comma', 'value' => join(',', $tags)]);
|
||||
$collection->push($importJournal);
|
||||
}
|
||||
}
|
||||
$this->addStep();
|
||||
Log::debug(sprintf('Going to try and store all %d them.', $collection->count()));
|
||||
|
||||
$this->addTotalSteps(7 * $collection->count());
|
||||
// try to store them (seven steps per transaction)
|
||||
$storage = new ImportStorage;
|
||||
|
||||
$storage->setJob($this->job);
|
||||
$storage->setDateFormat('Y-m-d\TH:i:sO');
|
||||
$storage->setObjects($collection);
|
||||
$storage->store();
|
||||
Log::info('Back in importTransactions()');
|
||||
|
||||
// link to tag
|
||||
/** @var TagRepositoryInterface $repository */
|
||||
$repository = app(TagRepositoryInterface::class);
|
||||
$repository->setUser($this->job->user);
|
||||
$data = [
|
||||
'tag' => trans('import.import_with_key', ['key' => $this->job->key]),
|
||||
'date' => new Carbon,
|
||||
'description' => null,
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoomLevel' => null,
|
||||
'tagMode' => 'nothing',
|
||||
];
|
||||
$tag = $repository->store($data);
|
||||
$extended = $this->getExtendedStatus();
|
||||
$extended['tag'] = $tag->id;
|
||||
$this->setExtendedStatus($extended);
|
||||
|
||||
Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag));
|
||||
Log::debug('Looping journals...');
|
||||
$journalIds = $storage->journals->pluck('id')->toArray();
|
||||
$tagId = $tag->id;
|
||||
$this->addTotalSteps(count($journalIds));
|
||||
|
||||
foreach ($journalIds as $journalId) {
|
||||
Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId));
|
||||
DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]);
|
||||
$this->addStep();
|
||||
}
|
||||
Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $storage->journals->count(), $tag->id, $tag->tag));
|
||||
|
||||
// set status to "finished"?
|
||||
// update job:
|
||||
$this->setStatus('finished');
|
||||
$this->addStep();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws SpectreException
|
||||
*/
|
||||
private function runStageHaveMapping()
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
$accounts = $config['accounts'] ?? [];
|
||||
$all = [];
|
||||
$count = 0;
|
||||
/** @var array $accountArray */
|
||||
foreach ($accounts as $accountArray) {
|
||||
$account = new Account($accountArray);
|
||||
$importId = intval($config['accounts-mapped'][$account->getid()] ?? 0);
|
||||
$doImport = $importId !== 0 ? true : false;
|
||||
if (!$doImport) {
|
||||
Log::debug(sprintf('Will NOT import from Spectre account #%d ("%s")', $account->getId(), $account->getName()));
|
||||
continue;
|
||||
}
|
||||
// grab all transactions
|
||||
$listTransactionsRequest = new ListTransactionsRequest($this->job->user);
|
||||
$listTransactionsRequest->setAccount($account);
|
||||
$listTransactionsRequest->call();
|
||||
$transactions = $listTransactionsRequest->getTransactions();
|
||||
$all[$account->getId()] = [
|
||||
'account' => $account,
|
||||
'import_id' => $importId,
|
||||
'transactions' => $transactions,
|
||||
];
|
||||
$count += count($transactions);
|
||||
}
|
||||
Log::debug(sprintf('Total number of transactions: %d', $count));
|
||||
$this->addStep();
|
||||
|
||||
|
||||
$this->importTransactions($all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand.
|
||||
*
|
||||
* @param array $config
|
||||
*/
|
||||
private function setConfig(array $config): void
|
||||
{
|
||||
$this->repository->setConfiguration($this->job, $config);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*
|
||||
* @param array $extended
|
||||
*/
|
||||
private function setExtendedStatus(array $extended): void
|
||||
{
|
||||
$this->repository->setExtendedStatus($this->job, $extended);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand.
|
||||
*
|
||||
* @param string $status
|
||||
*/
|
||||
private function setStatus(string $status): void
|
||||
{
|
||||
$this->repository->setStatus($this->job, $status);
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,10 @@ class SnsDescription implements SpecificInterface
|
||||
*/
|
||||
public function run(array $row): array
|
||||
{
|
||||
$row = array_values($row);
|
||||
$row = array_values($row);
|
||||
if (!isset($row[17])) {
|
||||
return $row;
|
||||
}
|
||||
$row[17] = ltrim($row[17], "'");
|
||||
$row[17] = rtrim($row[17], "'");
|
||||
|
||||
|
@@ -30,11 +30,22 @@ use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
|
||||
/**
|
||||
* Is capable of storing individual ImportJournal objects.
|
||||
* Adds 7 steps per object stored:
|
||||
* 1. get all import data from import journal
|
||||
* 2. is not a duplicate
|
||||
* 3. create the journal
|
||||
* 4. store journal
|
||||
* 5. run rules
|
||||
* 6. run bills
|
||||
* 7. finished storing object
|
||||
*
|
||||
* Class ImportStorage.
|
||||
*/
|
||||
class ImportStorage
|
||||
@@ -53,6 +64,8 @@ class ImportStorage
|
||||
protected $defaultCurrencyId = 1;
|
||||
/** @var ImportJob */
|
||||
protected $job;
|
||||
/** @var ImportJobRepositoryInterface */
|
||||
protected $repository;
|
||||
/** @var Collection */
|
||||
protected $rules;
|
||||
/** @var bool */
|
||||
@@ -63,6 +76,8 @@ class ImportStorage
|
||||
private $matchBills = false;
|
||||
/** @var Collection */
|
||||
private $objects;
|
||||
/** @var int */
|
||||
private $total = 0;
|
||||
/** @var array */
|
||||
private $transfers = [];
|
||||
|
||||
@@ -89,13 +104,17 @@ class ImportStorage
|
||||
*/
|
||||
public function setJob(ImportJob $job)
|
||||
{
|
||||
$this->job = $job;
|
||||
$currency = app('amount')->getDefaultCurrencyByUser($this->job->user);
|
||||
$this->repository = app(ImportJobRepositoryInterface::class);
|
||||
$this->repository->setUser($job->user);
|
||||
|
||||
$config = $this->repository->getConfiguration($job);
|
||||
$currency = app('amount')->getDefaultCurrencyByUser($job->user);
|
||||
$this->defaultCurrencyId = $currency->id;
|
||||
$this->job = $job;
|
||||
$this->transfers = $this->getTransfers();
|
||||
$config = $job->configuration;
|
||||
$this->applyRules = $config['apply_rules'] ?? false;
|
||||
$this->matchBills = $config['match_bills'] ?? false;
|
||||
$this->applyRules = $config['apply-rules'] ?? false;
|
||||
$this->matchBills = $config['match-bills'] ?? false;
|
||||
|
||||
if (true === $this->applyRules) {
|
||||
Log::debug('applyRules seems to be true, get the rules.');
|
||||
$this->rules = $this->getRules();
|
||||
@@ -108,6 +127,8 @@ class ImportStorage
|
||||
}
|
||||
Log::debug(sprintf('Value of apply rules is %s', var_export($this->applyRules, true)));
|
||||
Log::debug(sprintf('Value of match bills is %s', var_export($this->matchBills, true)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +137,7 @@ class ImportStorage
|
||||
public function setObjects(Collection $objects)
|
||||
{
|
||||
$this->objects = $objects;
|
||||
$this->total = $objects->count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,6 +151,7 @@ class ImportStorage
|
||||
function (ImportJournal $importJournal, int $index) {
|
||||
try {
|
||||
$this->storeImportJournal($index, $importJournal);
|
||||
$this->addStep();
|
||||
} catch (FireflyException | ErrorException | Exception $e) {
|
||||
$this->errors->push($e->getMessage());
|
||||
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
|
||||
@@ -150,7 +173,7 @@ class ImportStorage
|
||||
*/
|
||||
protected function storeImportJournal(int $index, ImportJournal $importJournal): bool
|
||||
{
|
||||
Log::debug(sprintf('Going to store object #%d with description "%s"', $index, $importJournal->getDescription()));
|
||||
Log::debug(sprintf('Going to store object #%d/%d with description "%s"', ($index + 1), $this->total, $importJournal->getDescription()));
|
||||
$assetAccount = $importJournal->asset->getAccount();
|
||||
$amount = $importJournal->getAmount();
|
||||
$currencyId = $this->getCurrencyId($importJournal);
|
||||
@@ -159,9 +182,7 @@ class ImportStorage
|
||||
$opposingAccount = $this->getOpposingAccount($importJournal->opposing, $assetAccount->id, $amount);
|
||||
$transactionType = $this->getTransactionType($amount, $opposingAccount);
|
||||
$description = $importJournal->getDescription();
|
||||
|
||||
// First step done!
|
||||
$this->job->addStepsDone(1);
|
||||
$this->addStep();
|
||||
|
||||
/**
|
||||
* Check for double transfer.
|
||||
@@ -175,13 +196,17 @@ class ImportStorage
|
||||
'opposing' => $opposingAccount->name,
|
||||
];
|
||||
if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) {
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
$message = sprintf('Detected a possible duplicate, skip this one (hash: %s).', $importJournal->hash);
|
||||
Log::error($message, $parameters);
|
||||
|
||||
// add five steps to keep the pace:
|
||||
$this->addSteps(5);
|
||||
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
unset($parameters);
|
||||
$this->addStep();
|
||||
|
||||
// store journal and create transactions:
|
||||
$parameters = [
|
||||
@@ -197,9 +222,7 @@ class ImportStorage
|
||||
];
|
||||
$journal = $this->storeJournal($parameters);
|
||||
unset($parameters);
|
||||
|
||||
// Another step done!
|
||||
$this->job->addStepsDone(1);
|
||||
$this->addStep();
|
||||
|
||||
// store meta object things:
|
||||
$this->storeCategory($journal, $importJournal->category->getCategory());
|
||||
@@ -221,31 +244,31 @@ class ImportStorage
|
||||
// set journal completed:
|
||||
$journal->completed = true;
|
||||
$journal->save();
|
||||
|
||||
// Another step done!
|
||||
$this->job->addStepsDone(1);
|
||||
$this->addStep();
|
||||
|
||||
// run rules if config calls for it:
|
||||
if (true === $this->applyRules) {
|
||||
Log::info('Will apply rules to this journal.');
|
||||
$this->applyRules($journal);
|
||||
}
|
||||
Preferences::setForUser($this->job->user, 'lastActivity', microtime());
|
||||
|
||||
if (!(true === $this->applyRules)) {
|
||||
Log::info('Will NOT apply rules to this journal.');
|
||||
}
|
||||
$this->addStep();
|
||||
|
||||
// match bills if config calls for it.
|
||||
if (true === $this->matchBills) {
|
||||
Log::info('Cannot match bills (yet).');
|
||||
Log::info('Will match bills.');
|
||||
$this->matchBills($journal);
|
||||
}
|
||||
|
||||
if (!(true === $this->matchBills)) {
|
||||
Log::info('Cannot match bills (yet), but do not have to.');
|
||||
}
|
||||
$this->addStep();
|
||||
|
||||
// Another step done!
|
||||
$this->job->addStepsDone(1);
|
||||
$this->journals->push($journal);
|
||||
|
||||
Log::info(sprintf('Imported new journal #%d: "%s", amount %s %s.', $journal->id, $journal->description, $journal->transactionCurrency->code, $amount));
|
||||
@@ -253,6 +276,24 @@ class ImportStorage
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method.
|
||||
*/
|
||||
private function addStep()
|
||||
{
|
||||
$this->repository->addStepsDone($this->job, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand method
|
||||
*
|
||||
* @param int $steps
|
||||
*/
|
||||
private function addSteps(int $steps)
|
||||
{
|
||||
$this->repository->addStepsDone($this->job, $steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
*
|
||||
@@ -269,7 +310,6 @@ class ImportStorage
|
||||
|
||||
$amount = app('steam')->positive($parameters['amount']);
|
||||
$names = [$parameters['asset'], $parameters['opposing']];
|
||||
$transfer = [];
|
||||
|
||||
sort($names);
|
||||
|
||||
|
@@ -121,11 +121,11 @@ trait ImportSupport
|
||||
{
|
||||
$transaction = new Transaction;
|
||||
$transaction->account_id = $parameters['account'];
|
||||
$transaction->transaction_journal_id = $parameters['id'];
|
||||
$transaction->transaction_currency_id = $parameters['currency'];
|
||||
$transaction->transaction_journal_id = intval($parameters['id']);
|
||||
$transaction->transaction_currency_id = intval($parameters['currency']);
|
||||
$transaction->amount = $parameters['amount'];
|
||||
$transaction->foreign_currency_id = $parameters['foreign_currency'];
|
||||
$transaction->foreign_amount = $parameters['foreign_amount'];
|
||||
$transaction->foreign_currency_id = intval($parameters['foreign_currency']) === 0 ? null : intval($parameters['foreign_currency']);
|
||||
$transaction->foreign_amount = null === $transaction->foreign_currency_id ? null : $parameters['foreign_amount'];
|
||||
$transaction->save();
|
||||
if (null === $transaction->id) {
|
||||
$errorText = join(', ', $transaction->getErrors()->all());
|
||||
@@ -155,6 +155,7 @@ trait ImportSupport
|
||||
* @param ImportJournal $importJournal
|
||||
*
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getCurrencyId(ImportJournal $importJournal): int
|
||||
{
|
||||
@@ -192,7 +193,7 @@ trait ImportSupport
|
||||
{
|
||||
// use given currency by import journal.
|
||||
$currency = $importJournal->currency->getTransactionCurrency();
|
||||
if (null !== $currency->id && $currency->id !== $currencyId) {
|
||||
if (null !== $currency->id && intval($currency->id) !== intval($currencyId)) {
|
||||
return $currency->id;
|
||||
}
|
||||
|
||||
@@ -216,6 +217,7 @@ trait ImportSupport
|
||||
* @see ImportSupport::getTransactionType
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getOpposingAccount(ImportAccount $account, int $forbiddenAccount, string $amount): Account
|
||||
{
|
||||
@@ -435,8 +437,6 @@ trait ImportSupport
|
||||
|
||||
if (!$journal->save()) {
|
||||
$errorText = join(', ', $journal->getErrors()->all());
|
||||
// add three steps:
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
throw new FireflyException($errorText);
|
||||
}
|
||||
|
@@ -212,7 +212,7 @@ class Account extends Model
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNameAttribute($value): string
|
||||
public function getNameAttribute($value): ?string
|
||||
{
|
||||
if ($this->encrypted) {
|
||||
return Crypt::decrypt($value);
|
||||
@@ -345,6 +345,15 @@ class Account extends Model
|
||||
$this->attributes['iban'] = Crypt::encrypt($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Get all of the notes.
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->morphMany(Note::class, 'noteable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
|
@@ -132,7 +132,7 @@ class Bill extends Model
|
||||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->morphMany('FireflyIII\Models\Note', 'noteable');
|
||||
return $this->morphMany(Note::class, 'noteable');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -30,6 +30,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* Class ExportJob.
|
||||
*
|
||||
* @property User $user
|
||||
* @property string $key
|
||||
*/
|
||||
class ExportJob extends Model
|
||||
{
|
||||
|
@@ -82,32 +82,6 @@ class ImportJob extends Model
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
* @param string $message
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function addError(int $index, string $message): bool
|
||||
{
|
||||
$extended = $this->extended_status;
|
||||
$extended['errors'][$index][] = $message;
|
||||
$this->extended_status = $extended;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*/
|
||||
public function addStepsDone(int $count)
|
||||
{
|
||||
$status = $this->extended_status;
|
||||
$status['done'] += $count;
|
||||
$this->extended_status = $status;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*/
|
||||
@@ -117,6 +91,7 @@ class ImportJob extends Model
|
||||
$status['steps'] += $count;
|
||||
$this->extended_status = $status;
|
||||
$this->save();
|
||||
Log::debug(sprintf('Add %d to total steps for job "%s" making total steps %d', $count, $this->key, $status['steps']));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +136,7 @@ class ImportJob extends Model
|
||||
*/
|
||||
public function getExtendedStatusAttribute($value)
|
||||
{
|
||||
if (0 === strlen($value)) {
|
||||
if (0 === strlen(strval($value))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -209,6 +184,7 @@ class ImportJob extends Model
|
||||
$disk = Storage::disk('upload');
|
||||
$encryptedContent = $disk->get($fileName);
|
||||
$content = Crypt::decrypt($encryptedContent);
|
||||
$content = trim($content);
|
||||
Log::debug(sprintf('Content size is %d bytes.', strlen($content)));
|
||||
|
||||
return $content;
|
||||
|
@@ -57,8 +57,8 @@ class Note extends Model
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* Get all of the owning noteable models. Currently piggy bank and
|
||||
* transaction journal.
|
||||
*
|
||||
* Get all of the owning noteable models.
|
||||
*/
|
||||
public function noteable()
|
||||
{
|
||||
|
@@ -158,7 +158,7 @@ class PiggyBank extends Model
|
||||
public function leftOnAccount(Carbon $date): string
|
||||
{
|
||||
$balance = Steam::balanceIgnoreVirtual($this->account, $date);
|
||||
// @var PiggyBank $p
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($this->account->piggyBanks as $piggyBank) {
|
||||
$currentAmount = $piggyBank->currentRelevantRep()->currentamount ?? '0';
|
||||
|
||||
|
@@ -66,6 +66,7 @@ use Watson\Validating\ValidatingTrait;
|
||||
* @property string $transaction_currency_symbol
|
||||
* @property int $transaction_currency_dp
|
||||
* @property string $transaction_currency_code
|
||||
* @property string $description
|
||||
*/
|
||||
class Transaction extends Model
|
||||
{
|
||||
|
@@ -28,6 +28,9 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class TransactionCurrency.
|
||||
*
|
||||
* @property string $code
|
||||
*
|
||||
*/
|
||||
class TransactionCurrency extends Model
|
||||
{
|
||||
|
@@ -92,8 +92,6 @@ class TransactionJournal extends Model
|
||||
if (auth()->check()) {
|
||||
$journalId = intval($value);
|
||||
$journal = auth()->user()->transactionJournals()->where('transaction_journals.id', $journalId)
|
||||
->with('transactionType')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->first(['transaction_journals.*']);
|
||||
if (!is_null($journal)) {
|
||||
return $journal;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user