Compare commits

...

384 Commits

Author SHA1 Message Date
James Cole
0e84ca1df5 Merge branch 'release/4.7.1.3' 2018-03-04 20:57:12 +01:00
James Cole
b28bdda510 Move base href to top of page. 2018-03-04 20:40:49 +01:00
James Cole
72314e2d9f Expand tests for updated triggers / actions. 2018-03-04 17:10:36 +01:00
James Cole
d22fb9f438 Improve tests for transaction rules. 2018-03-04 16:42:29 +01:00
James Cole
04b8552d27 Implement tests. 2018-03-04 16:30:20 +01:00
James Cole
5d6f44cd91 Fix test that could select a deposit or transfer. 2018-03-04 16:07:03 +01:00
James Cole
afc8ad7ff5 Clean test. 2018-03-04 15:57:19 +01:00
James Cole
ae039bf1c7 Update composer file for failed passport thing and update version. 2018-03-04 15:36:06 +01:00
James Cole
8fa25e9d37 Update read me file. 2018-03-04 15:25:43 +01:00
James Cole
0daab491ec Remove unused file [skip ci] 2018-03-04 15:16:37 +01:00
James Cole
4f825bac1a Add newlines to files [skip ci] 2018-03-04 15:16:18 +01:00
James Cole
39c8b79ebb Add newlines to files [skip ci] 2018-03-04 15:14:29 +01:00
James Cole
7d66c90beb Make sure that the API returns all entries of a split transaction. 2018-03-04 14:20:23 +01:00
James Cole
1b8b65582a Merge branch 'release/4.7.1.2' 2018-03-04 13:27:21 +01:00
James Cole
d25971cb44 Actually upgrade version. 2018-03-04 13:26:35 +01:00
James Cole
89a56e661d Merge branch 'hotfix/4.7.1.1' into develop 2018-03-04 13:24:07 +01:00
James Cole
3482746d7c Merge branch 'hotfix/4.7.1.1' 2018-03-04 13:24:06 +01:00
James Cole
d5aeca6222 Fix import problem in 4.7.1 2018-03-04 13:23:44 +01:00
James Cole
03f46638e1 Merge branch 'release/4.7.1' 2018-03-04 10:52:17 +01:00
James Cole
c2b000d910 Update change log. 2018-03-04 10:39:40 +01:00
James Cole
88eb702d7b Update Sandstorm files. 2018-03-04 10:39:31 +01:00
James Cole
b1d98f026f Update docker file. 2018-03-04 10:39:00 +01:00
James Cole
28dfe7b02c Update test config. 2018-03-04 10:38:50 +01:00
James Cole
84f0ee183c Fix tests. 2018-03-04 09:49:15 +01:00
James Cole
7eedd6c2fc Update changelog and increment version. 2018-03-04 09:13:15 +01:00
James Cole
e7b80c6d10 Fix parameter error in test script. 2018-03-04 09:12:58 +01:00
James Cole
d165609476 Expand debug view with API version. 2018-03-04 09:12:47 +01:00
James Cole
2f17521c06 Fix small errors in bulk and mass controller 2018-03-04 09:12:33 +01:00
James Cole
8b52006959 Updated all language strings. 2018-03-04 08:54:01 +01:00
James Cole
8eb3d43123 Fix tag auto select. 2018-03-04 08:52:06 +01:00
James Cole
a511368229 Fix missing index. 2018-03-04 08:51:46 +01:00
James Cole
f0006a0743 Update composer lock file. 2018-03-04 08:51:20 +01:00
James Cole
7171e69715 All API routes seem to work. 2018-03-04 08:22:32 +01:00
James Cole
2ab44fb33a Improve test coverage. 2018-03-04 07:56:30 +01:00
James Cole
7542175258 Improve test coverage. 2018-03-03 17:16:47 +01:00
James Cole
9dc4c50527 Expand test coverage. 2018-03-03 14:24:06 +01:00
James Cole
99d116f4ce Improve test coverage. 2018-03-03 10:15:39 +01:00
James Cole
9475fef8f6 Implement user API and first tests. 2018-03-03 08:12:18 +01:00
James Cole
60339a0f6a make sure randomly selected journals match prerequisites. 2018-03-02 17:29:47 +01:00
James Cole
36113f84be make sure randomly selected journals match prerequisites. 2018-03-02 17:07:32 +01:00
James Cole
139c2284b8 Various code cleanup. 2018-03-02 16:31:02 +01:00
James Cole
91909a70d7 Fix #1209 2018-03-02 03:14:36 +01:00
James Cole
a23d97563f Fix tests that selected split journals. 2018-03-01 21:39:31 +01:00
James Cole
bf538e2514 Merge pull request #1211 from m0nhawk/patch-1
3 new european currencies in database seed
2018-03-01 21:28:36 +01:00
Andrew Prokhorenkov
149b62f486 3 new european currencies in database seed 2018-03-01 22:20:04 +02:00
James Cole
06dc8a499b Expand factory tests. 2018-03-01 20:54:50 +01:00
James Cole
5b8479f3a4 Remove PHP 7.2 support. 2018-03-01 17:40:06 +01:00
James Cole
e803a5e26e Fix test coverage. 2018-03-01 17:20:06 +01:00
James Cole
959f798a7f Update English strings. 2018-02-28 21:33:14 +01:00
James Cole
5b8adbfd0c Repository and test clean up. 2018-02-28 21:32:59 +01:00
James Cole
54ba18975a Use different method for finding objects. 2018-02-28 20:23:45 +01:00
James Cole
fdd2dedfc6 Fix test cases. 2018-02-28 20:18:47 +01:00
James Cole
46f4fa1a7d Expand tests. 2018-02-28 15:50:00 +01:00
James Cole
28debb46be Needs to return bill to work. 2018-02-28 14:57:58 +01:00
James Cole
5f132be94d Add catalan (still in comments, because its incomplete [skip ci] 2018-02-28 07:22:57 +01:00
James Cole
9f9feea159 Code to fix #1185 2018-02-28 07:22:11 +01:00
James Cole
3bd9e0bcd4 Remove not existing method 2018-02-27 07:39:28 +01:00
James Cole
c80a76f8c0 Remove not existing method. 2018-02-27 07:37:40 +01:00
James Cole
c71f498587 Merge branches 'develop' and 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Update validation.php
  Update config.php

* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Update validation.php
  Update config.php
2018-02-26 21:09:51 +01:00
James Cole
e658d447ca Update validation. 2018-02-26 21:09:33 +01:00
James Cole
33def5b45d Update validation.php 2018-02-26 13:18:34 +01:00
James Cole
9fc26a8ee0 Update config.php 2018-02-26 13:15:54 +01:00
James Cole
1b304bf85e use journal repository instead of direct calls. 2018-02-25 19:09:05 +01:00
James Cole
99983a5c8f Match default values #1191 2018-02-25 17:39:19 +01:00
James Cole
d01b370cd7 Change docker vars #1191 2018-02-25 17:39:09 +01:00
James Cole
1a643e2042 Expand tests 2018-02-25 17:38:24 +01:00
James Cole
1aaf5fd288 Improve split controller code. 2018-02-25 16:04:25 +01:00
James Cole
8a758b8df0 Fix #1192 2018-02-25 15:09:57 +01:00
James Cole
211caa07dc Update edit and submit routines for transactions. 2018-02-24 14:31:20 +01:00
James Cole
ac66e89edb Expand some tests. 2018-02-24 09:18:01 +01:00
James Cole
6fe5b50410 Expand view + JS for view to cope with new factory 2018-02-24 09:17:48 +01:00
James Cole
166cdad58b Some intermittent changes to storing journals. 2018-02-24 09:17:15 +01:00
James Cole
1a721ac6b5 Fix transactions. 2018-02-23 16:59:21 +01:00
James Cole
6591fa9fb4 Small adjustments to fix tests. 2018-02-23 16:21:28 +01:00
James Cole
d804093f8b Expand destroy routine. 2018-02-23 15:13:30 +01:00
James Cole
5261b784b0 New validation 2018-02-23 15:13:23 +01:00
James Cole
5a188ceca3 Remove triggers 2018-02-23 15:13:16 +01:00
James Cole
ce56cc538d Remove bad method. 2018-02-23 15:13:09 +01:00
James Cole
269433bf00 Refactor reconciliation routine 2018-02-23 15:13:01 +01:00
James Cole
dae3371c69 Move common methods to traits 2018-02-23 15:12:47 +01:00
James Cole
38c1d332e2 Removed a lot of old spaghetti code. Now have to rewrite it 2018-02-22 20:13:00 +01:00
James Cole
b627d42160 Code removal. The code removed from these classes must move to respective services. 2018-02-22 20:07:14 +01:00
James Cole
4e923057ae Clean up repository. 2018-02-21 21:11:44 +01:00
James Cole
35d0bd1985 Factory seems to work for update and create 2018-02-21 21:06:59 +01:00
James Cole
085eb650e7 Code cleanup. 2018-02-21 20:34:48 +01:00
James Cole
b4157e8ce0 Build account update service. 2018-02-21 20:34:24 +01:00
James Cole
81221038f0 Expand services. 2018-02-21 18:42:15 +01:00
James Cole
9f37bf5875 Fix budget controller tests. 2018-02-21 09:23:20 +01:00
James Cole
140a5b20db Update transactions, delete splits. 2018-02-21 08:58:06 +01:00
James Cole
e9b6b45fc4 Expand code to be able to handle updates. 2018-02-21 08:51:30 +01:00
James Cole
f16760d607 Expand API validation. 2018-02-20 18:03:02 +01:00
James Cole
9d457787f7 Specify times for SQLite database. #1192 2018-02-20 17:17:14 +01:00
James Cole
4e6afd5afc Remove todo items [skip ci] 2018-02-19 20:32:44 +01:00
James Cole
36354c3846 Fix for #1111 2018-02-19 20:32:33 +01:00
James Cole
9f63dfb9cb Fix #1178 [skip ci] 2018-02-19 20:17:37 +01:00
James Cole
cae4faad0a Expand tests. 2018-02-19 20:02:27 +01:00
James Cole
e389d0f7fa Expand tests 2018-02-19 19:45:13 +01:00
James Cole
6b32213735 make findByName nullable. 2018-02-19 19:44:58 +01:00
James Cole
b3fe24b713 Expand and refactor factories. 2018-02-19 19:44:46 +01:00
James Cole
5bb7530642 Expand tests. 2018-02-18 20:40:32 +01:00
James Cole
0b61c16eb0 Expand test cases for transaction creation through the API. 2018-02-18 19:55:35 +01:00
James Cole
77aced6734 Test every happy path for journal creation. 2018-02-18 16:35:26 +01:00
James Cole
94a7b6b9bd First create basic objects. Then, enhance. 2018-02-18 10:52:56 +01:00
James Cole
f8bf6c163f Add some factory stuff before another refactoring. 2018-02-18 10:49:42 +01:00
James Cole
eb0da038fb Expand tests and API code. 2018-02-18 10:31:15 +01:00
James Cole
6cda9f2900 Expand tests for account API. 2018-02-17 19:56:45 +01:00
James Cole
ecd4a862ff Tests for API controllers 2018-02-17 14:46:12 +01:00
James Cole
632d50a0d0 Fix all tests. 2018-02-17 14:14:26 +01:00
James Cole
0f1cc46b71 Fix JSON tests 2018-02-17 12:33:42 +01:00
James Cole
60b225d61c Fix use of transformer. 2018-02-17 12:24:29 +01:00
James Cole
23e540a57a Fix missing methods in account controller test 2018-02-17 10:50:47 +01:00
James Cole
1998412a3c Remove API tests for the time being. 2018-02-17 10:48:31 +01:00
James Cole
7bbfb692de Move code to repository. 2018-02-17 10:47:32 +01:00
James Cole
c6da990748 Expand decryption routine. 2018-02-17 10:47:18 +01:00
James Cole
049e57d578 New tests for object transformers. 2018-02-17 10:47:06 +01:00
James Cole
78ba0f749c tests for bill and attachment transformers. 2018-02-16 22:47:08 +01:00
James Cole
9cc1bfb4b5 Improve code for test coverage 2018-02-16 22:14:53 +01:00
James Cole
278b7ac52b First tests for transformers. 2018-02-16 22:14:34 +01:00
James Cole
645a29e22b Add API test suite 2018-02-16 22:14:08 +01:00
James Cole
b22d30bc65 Add method to mark journals as completed. 2018-02-16 16:58:08 +01:00
James Cole
2ee0490141 Remove debug info. 2018-02-16 16:57:54 +01:00
James Cole
1fd783de69 Remove debug info. 2018-02-16 16:57:46 +01:00
James Cole
8073896965 Add request data for tags. 2018-02-16 16:57:35 +01:00
James Cole
8a26e43c40 Fix display for new transaction store. 2018-02-16 16:57:27 +01:00
James Cole
a302aba3ab Expand journal repos 2018-02-16 16:45:03 +01:00
James Cole
0458058cb1 Update piggy bank transformer 2018-02-16 16:44:52 +01:00
James Cole
999bb5ed49 Add new transaction type repository 2018-02-16 16:44:21 +01:00
James Cole
c9f4a1eb7b Add route binder to transaction 2018-02-16 16:43:57 +01:00
James Cole
45aa76afce Expand collector to return single journals. 2018-02-16 16:43:48 +01:00
James Cole
e89a77efb1 New factories for the creation of journals and associated meta data. 2018-02-16 16:43:25 +01:00
James Cole
8f930d6dd5 Transaction request with full validation 2018-02-16 16:43:00 +01:00
James Cole
9d62b4c70d Add return types 2018-02-16 16:42:23 +01:00
James Cole
834032f58e Updated transaction controller 2018-02-16 16:42:13 +01:00
James Cole
33db99ffd3 Update find methods to return null 2018-02-16 15:19:19 +01:00
James Cole
28b00f6507 New routes for transaction 2018-02-16 15:18:07 +01:00
James Cole
6559076c48 New strings for validation 2018-02-16 15:17:55 +01:00
James Cole
60f6311e00 Rule to validate if object belongs to submitting user. 2018-02-16 15:17:36 +01:00
James Cole
574a5630e0 Add binder for transactions 2018-02-16 14:52:16 +01:00
James Cole
d3294be1bc Add method that makes sure that URL's are expanded for page navigation/ 2018-02-16 14:51:59 +01:00
James Cole
22fdc81de2 Refactor transactions. 2018-02-13 21:04:15 +01:00
James Cole
370e9b25d1 Expand API. 2018-02-13 18:24:06 +01:00
James Cole
30f821af3e About controller for basic site info 2018-02-13 18:23:26 +01:00
James Cole
7a5aa1c39b Update and restructure YAML file for Docker. [skip ci] 2018-02-11 20:53:30 +01:00
James Cole
f674df4422 Fix empty title. 2018-02-11 20:45:48 +01:00
James Cole
c2da5931ec Expanded API code, wrote a bunch new transformers as well. 2018-02-11 20:45:33 +01:00
James Cole
94f6bd34c7 Fix some issues with semi-colon delimiters, see #1172 2018-02-11 15:52:24 +01:00
James Cole
e066a6421c Fix #1172 2018-02-11 15:36:16 +01:00
James Cole
ef338e2515 Fix #1174 2018-02-11 15:27:28 +01:00
James Cole
dcf549261c Fix for #1175 2018-02-11 15:24:19 +01:00
James Cole
8b868b426a First API routes for accounts. 2018-02-11 08:08:08 +01:00
James Cole
2ef1022c92 Make sure bills API is consistent. 2018-02-11 07:46:34 +01:00
James Cole
9b3abd3b19 Expand transformers to include other objects. 2018-02-10 10:58:06 +01:00
James Cole
db02fefcf4 Update composer. 2018-02-10 09:58:06 +01:00
James Cole
523ae83811 Show proper 404 page for JSON. 2018-02-10 09:57:56 +01:00
James Cole
4eb010f807 Use correct CSS in 404 page. 2018-02-10 09:57:47 +01:00
James Cole
4958f28052 Allow API to work with bills. 2018-02-10 09:57:31 +01:00
James Cole
7e727b63ed Use built-in PHPUnit in tests. 2018-02-10 09:57:05 +01:00
James Cole
138c38fbb5 Clean up code in validator. 2018-02-10 09:22:13 +01:00
James Cole
2e61bb7375 Fix tests. 2018-02-10 09:22:04 +01:00
James Cole
fce4c9174d Fix for #1154 2018-02-10 08:21:35 +01:00
James Cole
2220963899 Remove guard from user model. 2018-02-10 08:21:20 +01:00
James Cole
e69e6c1ce8 Would be nice to remove the references as well... 2018-02-09 19:28:16 +01:00
James Cole
0f09a9db4d Remove reference to guard from other bind support classes. 2018-02-09 19:24:30 +01:00
James Cole
53a6c10ada Remove reference to guard from models. 2018-02-09 19:24:15 +01:00
James Cole
14772469ed Remove reference to guard from binder 2018-02-09 19:23:31 +01:00
James Cole
55f13ef121 Code cleanup in 2FA middleware. 2018-02-09 19:12:46 +01:00
James Cole
95648c37b3 Various code cleanup. 2018-02-09 19:11:55 +01:00
James Cole
ac98822a55 Fix for issue #1167 2018-02-09 16:47:01 +01:00
James Cole
c460419166 Final fixes for API binder. 2018-02-09 15:01:22 +01:00
James Cole
d2a8819dd4 Merge branch 'apifix' into develop
* apifix:
  Fix issues with API authentication.

# Conflicts:
#	app/Api/V1/Controllers/BillController.php
#	app/Http/Middleware/HttpBinder.php
#	app/Transformers/AttachmentTransformer.php
#	app/Transformers/BillTransformer.php
#	app/Transformers/NoteTransformer.php
#	routes/api.php
2018-02-09 14:57:39 +01:00
James Cole
d393c693de Fix issues with API authentication. 2018-02-09 14:47:37 +01:00
James Cole
d4a84ed198 Update tests. 2018-02-07 16:49:11 +01:00
James Cole
e8c7986a58 Rename binder test 2018-02-07 16:20:40 +01:00
James Cole
809e40c5ce Remove double middleware from routes. 2018-02-07 11:20:37 +01:00
James Cole
f445a95c26 Consistent use of links in transformers. 2018-02-07 11:20:24 +01:00
James Cole
909dc212fb make sure all route binders use guard. 2018-02-07 11:15:36 +01:00
James Cole
eacc1da157 Implement multi purpose binder 2018-02-07 11:13:04 +01:00
James Cole
587ad1298d Make sure transformer accepts null dates. 2018-02-07 10:49:24 +01:00
James Cole
fae7dabbc2 Split binder in api and http binder 2018-02-07 10:49:06 +01:00
James Cole
3a813c30b4 Clean up js file. 2018-02-06 19:52:46 +01:00
James Cole
3de46f55fa Use transformer in view. 2018-02-06 19:49:53 +01:00
James Cole
3aa922341c Remove unused package from config 2018-02-06 19:49:38 +01:00
James Cole
e94043edc2 Expand transformers. 2018-02-06 19:49:29 +01:00
James Cole
da91645ec0 Clean up code. 2018-02-06 19:49:16 +01:00
James Cole
178072d3af Remove unused markdown method 2018-02-06 19:49:09 +01:00
James Cole
811d8e330f Refer to correct location for bill transformer. 2018-02-06 19:48:56 +01:00
James Cole
d3c8d06114 Update lock files 2018-02-06 19:48:43 +01:00
James Cole
82dc0045ba Move bill transformer to previous location 2018-02-06 19:48:32 +01:00
James Cole
3d06f0ac14 Remove unused dependencies 2018-02-06 18:15:26 +01:00
James Cole
b5c0ef01d9 Clean up app.js 2018-02-06 18:13:54 +01:00
James Cole
3a5d3016c7 Refer to correct bill route 2018-02-06 18:12:43 +01:00
James Cole
20690b4d5b Clean up API routes 2018-02-06 18:12:31 +01:00
James Cole
2816a4a325 Make bill views use transformer object. 2018-02-06 18:12:09 +01:00
James Cole
2f4f37778c Fix error page CSS 2018-02-06 18:11:46 +01:00
James Cole
c4507a7f75 Make sure the "classic" page uses the transformer as well. 2018-02-06 18:11:33 +01:00
James Cole
9a0672e359 Update previous view to use new strings. 2018-02-06 10:57:23 +01:00
James Cole
f128db35c6 Update view to use localized strings. 2018-02-06 10:57:07 +01:00
James Cole
a2cfaa0867 Update language strings. 2018-02-06 10:56:50 +01:00
James Cole
5850c5e20a Add code to enable localisation. 2018-02-06 10:56:37 +01:00
James Cole
e77a1e403f Expand config for localisation 2018-02-06 10:56:17 +01:00
James Cole
b72e8db7b1 Add localisation package. 2018-02-06 10:56:01 +01:00
James Cole
07506784f4 Add localisation package. 2018-02-06 10:55:40 +01:00
James Cole
0435e42b3d Update package files. 2018-02-06 07:52:04 +01:00
James Cole
31884bbba6 Add generated js / css to all views. 2018-02-06 07:51:49 +01:00
James Cole
6b38faf84e Expand views for bills. 2018-02-06 07:51:28 +01:00
James Cole
2f95f99890 Update exceptions thrown for better IDE support. 2018-02-06 07:50:19 +01:00
James Cole
9b78069f41 Expand API for bills. 2018-02-06 07:49:56 +01:00
James Cole
559c2042ac Remove library now included in npm build. 2018-02-06 07:49:19 +01:00
James Cole
ae3b369e9a Match layout to Firefly III 2018-02-04 15:58:03 +01:00
James Cole
31a6565e17 Add package fractal. 2018-02-04 15:57:48 +01:00
James Cole
f488bbde02 First basic routes and code for bills. 2018-02-04 15:57:35 +01:00
James Cole
e668b88fb5 Make sure authorise view is translatable and matches Firefly III 2018-02-04 14:04:52 +01:00
James Cole
2d0aa4af96 Give all web routes full namespace. 2018-02-04 14:04:29 +01:00
James Cole
6e67416c83 Blank namespace for route namespace prefix. 2018-02-04 13:55:36 +01:00
James Cole
1ef28cbc02 Changes to repair API auth 2018-02-04 13:41:59 +01:00
James Cole
58bdf14f6b First empty controllers for API. 2018-02-04 13:41:42 +01:00
James Cole
9f4ecb0963 Make authorise view a twig file. 2018-02-04 11:21:57 +01:00
James Cole
b1259a014f Add support for Spanish [skip ci] 2018-02-04 09:52:42 +01:00
James Cole
142d0b5af2 First set of JS/CSS built by npm. 2018-02-04 09:23:46 +01:00
James Cole
450e2bad1c New composer.lock after installing Laravel Passport. 2018-02-04 09:23:20 +01:00
James Cole
089300d57e Update date related code to fix several issues with SQLite 2018-02-04 09:22:52 +01:00
James Cole
36f67793cb Include Vue components. 2018-02-04 08:17:22 +01:00
James Cole
c335a9bbc8 Rename access token variables 2018-02-04 08:17:05 +01:00
James Cole
029688a594 Passport auth view. 2018-02-04 08:16:37 +01:00
James Cole
6f2eb33fd0 Future strings for JS translations 2018-02-04 08:16:06 +01:00
James Cole
8351020913 Add passport components to app.js 2018-02-04 08:15:20 +01:00
James Cole
220efca8d7 Add passport migrations 2018-02-04 08:14:36 +01:00
James Cole
28579f7b80 Add debug information to import routine. 2018-02-04 08:14:22 +01:00
James Cole
f1d77bdb50 Expand code to support laravel passport 2018-02-04 08:14:03 +01:00
James Cole
ca8b4cb11a Add laravel passport 2018-02-04 08:13:31 +01:00
James Cole
31dbb7b111 Update view to use new assets 2018-02-03 09:14:58 +01:00
James Cole
352cdf75c8 Include font awesome in assets. 2018-02-03 09:14:39 +01:00
James Cole
d81c99bcda Other name for CSRF token in html and JS. 2018-02-01 19:39:55 +01:00
James Cole
1e2c979341 Fix #1155 2018-02-01 19:39:41 +01:00
James Cole
d8664096f9 Fix view issues when user has multiple pages of budgets #1111 2018-02-01 17:55:18 +01:00
James Cole
0d9a221b00 Extend debug page with session fields. 2018-02-01 16:58:47 +01:00
James Cole
de85f17cac Improve login form autocomplete values [skip ci] 2018-02-01 16:54:42 +01:00
James Cole
e3d6f4f00f Add some Vue related components. Prep for inclusion of passport and other tools. 2018-01-31 17:55:49 +01:00
James Cole
d0e0054b00 Correct errors in English sentences. [skip ci] 2018-01-31 14:02:04 +01:00
James Cole
735222a8ed Merge branch 'release/4.7.0' 2018-01-31 07:14:15 +01:00
James Cole
66f299cd06 Update composer file. 2018-01-31 06:42:44 +01:00
James Cole
e3c5268143 Update changelog for Sandstorm. 2018-01-31 06:35:41 +01:00
James Cole
df32493d77 Final update for some translations. 2018-01-30 21:07:14 +01:00
James Cole
86faf44153 Small fixes in change log [skip ci] 2018-01-29 20:17:43 +01:00
James Cole
e2f3e4b555 Update sandstorm file list. 2018-01-29 19:56:57 +01:00
James Cole
e57ed6015c Updated composer lock file. 2018-01-29 19:31:36 +01:00
James Cole
9c34ca7fc4 Changelog for Sandstorm. 2018-01-29 19:14:58 +01:00
James Cole
7b94f4a441 Update change log. 2018-01-29 19:13:51 +01:00
James Cole
693f8d0738 Update language files. 2018-01-29 19:13:43 +01:00
James Cole
d6ecbc06bf Add Portuguese (Brazil) 2018-01-29 19:12:58 +01:00
James Cole
c31674fffc New version in Sandstorm file. 2018-01-29 19:10:29 +01:00
James Cole
b08de8cc00 Clean up config. 2018-01-29 19:09:52 +01:00
James Cole
4c503e4c7c Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Workaround IE tab order issue w/ js initial select
2018-01-29 19:09:13 +01:00
James Cole
dd4158c6b4 Expand tests 2018-01-29 19:09:00 +01:00
James Cole
5fd7ea2b96 Add code coverage. 2018-01-29 19:08:49 +01:00
James Cole
b9ff80eb5a Update changelog. 2018-01-29 19:08:26 +01:00
James Cole
afc725bbc8 Merge pull request #1148 from devlearner/login-tab-order
Workaround IE tab order issue on js initial select
2018-01-28 21:13:56 +01:00
James Cole
0342c371cc Fix debug issue caught by @devlearner. 2018-01-28 20:59:26 +01:00
devlearner
2da4e6b048 Workaround IE tab order issue w/ js initial select 2018-01-28 14:23:06 +00:00
James Cole
f50550d79c Fix unit tests 2018-01-25 20:38:50 +01:00
James Cole
3fa39a6805 Clean up view and route. 2018-01-25 19:21:58 +01:00
James Cole
3dbe6d4870 Clean up config. 2018-01-25 19:21:46 +01:00
James Cole
59c48268ab Support more icons. 2018-01-25 19:21:40 +01:00
James Cole
1f83c5195d Add view method. Clean up repository links 2018-01-25 19:21:31 +01:00
James Cole
49a95a08fe More friendly demo user message. 2018-01-25 19:02:14 +01:00
James Cole
c86c5ccfe9 Add forgotten migration [skip ci] 2018-01-25 18:55:31 +01:00
James Cole
8a2497fc67 Expand mime type and upgrade version [skip ci] 2018-01-25 18:41:51 +01:00
James Cole
7c70732247 Some light refactoring. No changes. 2018-01-25 18:41:27 +01:00
James Cole
53fc4f2740 Better link to Spectre docs. [skip ci] 2018-01-24 15:23:27 +01:00
James Cole
f3ade5621e Merge pull request #1145 from devlearner/woff2
Include Woff fonts as well
2018-01-24 14:30:57 +01:00
devlearner
ec2e08e33a Update fonts for Source Sans Pro
(Bold Italic)
2018-01-24 21:04:03 +08:00
devlearner
b9a26faa4d Update css for Source Sans Pro
(Bold Italic)
2018-01-24 12:52:48 +00:00
James Cole
1a434d0c83 Expand readme with links to contribution pages. [skip ci] 2018-01-24 12:00:41 +01:00
James Cole
9a2c6c2967 Expand test coverage. 2018-01-24 11:09:21 +01:00
James Cole
602b35d589 Breadcrumb shows the correct title. 2018-01-24 11:09:05 +01:00
James Cole
f42cd0c7c3 Give correct info about import jobs. 2018-01-24 11:08:50 +01:00
James Cole
cb81855a17 Fix #1143 2018-01-24 11:05:00 +01:00
James Cole
89cf351ad0 add some fixes for #1111 2018-01-24 05:17:26 +01:00
devlearner
3f70a3f06c Added woff fonts 2018-01-24 01:10:04 +08:00
devlearner
9df360f010 Updated woff2 fonts
Roboto: Last modified 2017-10-16 (v18)
Lato: Last modified 2017-10-11 (v14)
2018-01-24 00:59:27 +08:00
James Cole
9a26d6d49f Fix #1140 2018-01-22 18:37:59 +01:00
James Cole
bc4d801c12 Fix #1141 2018-01-22 18:16:50 +01:00
James Cole
f2d8e13576 Fix #1142 2018-01-22 18:14:30 +01:00
James Cole
ec0b5db973 Update change log. 2018-01-21 20:04:53 +01:00
James Cole
46a0d1ce35 Update change log. 2018-01-21 20:03:29 +01:00
James Cole
cb81446ca6 Clean up debug code 2018-01-21 20:03:13 +01:00
James Cole
9350b4939c Make some file names lowercase. 2018-01-21 19:47:19 +01:00
James Cole
33e8c8c415 Make some file names lowercase. 2018-01-21 19:46:56 +01:00
James Cole
48fa86cc54 Improve some test coverage. 2018-01-21 18:06:57 +01:00
James Cole
d5e6d1c578 Remove reference to website from read me. 2018-01-21 11:10:08 +01:00
James Cole
0bc688795a Small update in update routine. 2018-01-21 11:09:55 +01:00
James Cole
eb76ed5591 New text in contributing [skip ci] 2018-01-21 09:10:36 +01:00
James Cole
839cbaf37a Updated read me file [skip ci] 2018-01-21 00:08:14 +01:00
James Cole
788fc9204d Update readme [skip ci] 2018-01-21 00:06:40 +01:00
James Cole
3e3e304ef3 Updated read me file [skip ci] 2018-01-20 23:59:10 +01:00
James Cole
447d453fdc Update composer lock file [skip ci] 2018-01-20 07:15:39 +01:00
James Cole
36fd7884f3 Update demo pages. 2018-01-20 07:15:26 +01:00
James Cole
54da08b2f2 Change settings so demo user can use Spectre. 2018-01-20 06:40:23 +01:00
James Cole
3f02072ae9 Update read me[skip ci] 2018-01-20 06:40:05 +01:00
James Cole
a9c117703b Update composer.json 2018-01-19 08:35:25 +01:00
James Cole
c137255155 Update .scrutinizer.yml 2018-01-19 08:23:31 +01:00
James Cole
e7829ecc38 Committed bad help JS. [skip ci] 2018-01-17 14:26:31 +01:00
James Cole
529bdafa31 Code climate file. 2018-01-17 13:04:43 +01:00
James Cole
e2af0caa41 Fix tests 2018-01-17 12:29:00 +01:00
James Cole
80f96abf08 Fix notes in link types. 2018-01-17 10:40:44 +01:00
James Cole
70da38193f Fix issue with budget chart. 2018-01-17 10:17:49 +01:00
James Cole
13df973873 Fix query cache. 2018-01-17 10:03:47 +01:00
James Cole
3ccb791674 Various code cleanup. [skip ci] 2018-01-17 09:32:18 +01:00
James Cole
ccf1a6c182 Fix #1134 2018-01-17 09:22:45 +01:00
James Cole
493543c1f5 Update language files [skip ci] 2018-01-17 06:43:04 +01:00
James Cole
5f5725e0e3 Explicit language tag in layout 2018-01-17 06:25:32 +01:00
James Cole
107dd42957 Update English language files [skip ci] 2018-01-17 06:24:50 +01:00
James Cole
a9f3fe4d3a Remove memcached experiment. 2018-01-16 22:01:55 +01:00
James Cole
3e62e17b9e Add some echo to Sandstorm scripts. 2018-01-16 21:34:36 +01:00
James Cole
57855b1930 Remove references to unused cache thing. 2018-01-16 21:09:27 +01:00
James Cole
aa9e8227bb Smal changes in Sandstorm configuration. [skip ci] #1130 2018-01-15 17:48:20 +01:00
James Cole
a80f083b6e Catch errors in DB seeds. 2018-01-15 17:13:23 +01:00
James Cole
474e066d4a Expand debug message [skip ci] 2018-01-14 19:59:05 +01:00
James Cole
4428ccefbf Expand debug message [skip ci] 2018-01-14 19:58:39 +01:00
James Cole
d568a6c8a9 First version of actual update check. 2018-01-14 19:56:18 +01:00
James Cole
97e9ad6cb2 Small updates in read me. 2018-01-14 19:49:29 +01:00
James Cole
00607d2a6d Code for #1040 2018-01-14 19:36:24 +01:00
James Cole
c2a425121d Code for #1040 2018-01-14 16:32:26 +01:00
James Cole
435694e9ea Code for #989 2018-01-14 10:57:27 +01:00
James Cole
f59135a9ca Code for #989 2018-01-14 10:48:17 +01:00
James Cole
102b106402 Different “drop up” menu. 2018-01-13 18:52:06 +01:00
James Cole
5c27c8e633 Multi currency net worth box. 2018-01-13 18:40:28 +01:00
James Cole
edd5215b21 Different icon for view. [skip ci] 2018-01-13 18:07:25 +01:00
James Cole
94b173ae6b New language strings. 2018-01-13 18:02:41 +01:00
James Cole
7d96b281b6 Add buttons to views 2018-01-13 18:01:53 +01:00
James Cole
a5515ac89f Update tests. 2018-01-13 10:36:49 +01:00
James Cole
fb863b0bf2 Improve step count for spectre imports. 2018-01-13 07:52:35 +01:00
James Cole
50882f309b Make sure number of steps is always correct. 2018-01-13 07:36:44 +01:00
James Cole
ce854fbb43 Catch error when trying to read (non-existent) logs. 2018-01-12 21:43:04 +01:00
James Cole
6799268ec4 Add intval just in case. 2018-01-12 21:31:39 +01:00
James Cole
6fe5ce0485 Expand budget report #1106 2018-01-12 21:08:59 +01:00
James Cole
cbeaf8e16a Expand tag report #1106 2018-01-12 21:02:27 +01:00
James Cole
04de4c9b36 Expand category report #1106 2018-01-12 20:53:18 +01:00
James Cole
517731cb59 Extra buttons 2018-01-12 20:37:56 +01:00
James Cole
e34e43173c Fix #1132 2018-01-12 20:37:39 +01:00
James Cole
79d6055a78 Fix #1131 2018-01-12 20:32:09 +01:00
James Cole
7ac4d2a2f4 Various new strings [skip ci] 2018-01-12 18:44:59 +01:00
James Cole
4984eda320 Clean up view HTML 2018-01-12 18:42:48 +01:00
James Cole
89e0791e2f Add button to create transaction. 2018-01-12 18:42:25 +01:00
James Cole
922d487821 Update icon [skip ci] 2018-01-11 21:27:24 +01:00
James Cole
4b789979ac Fix 2FA check #1125 2018-01-11 20:56:42 +01:00
James Cole
554b38ccff Code for #1126 2018-01-11 20:49:55 +01:00
James Cole
9614310208 Extra button for #1124 2018-01-11 19:08:01 +01:00
James Cole
d9ec3ac354 Code for #1124 2018-01-11 19:06:46 +01:00
James Cole
f326f08f7b Fix #1088 2018-01-11 18:58:33 +01:00
James Cole
0ae8418f32 Fix tests. 2018-01-10 20:07:47 +01:00
James Cole
309f9cd076 Add new roles 2018-01-10 19:59:40 +01:00
James Cole
61f5ed3874 Fix check for column roles. 2018-01-10 19:06:27 +01:00
James Cole
91178d2604 Various cleanup in import. 2018-01-10 18:18:49 +01:00
James Cole
87dae6ea18 Expand some code for Spectre import. 2018-01-10 16:49:32 +01:00
James Cole
2e495c38d1 Make env files more readable. 2018-01-10 14:37:40 +01:00
James Cole
c2987aaf4c Update docker config #1081 2018-01-10 13:15:12 +01:00
James Cole
f4f4eecb7b Add routes to ignore. [skip ci] 2018-01-10 12:42:32 +01:00
James Cole
84d9287251 Remove unused route [skip ci] 2018-01-10 12:42:17 +01:00
James Cole
b71f334744 Small rewrite in readme [skip ci] 2018-01-10 12:42:01 +01:00
James Cole
ad306e4d01 Strings for #1116 (help text) 2018-01-10 08:03:37 +01:00
James Cole
e40d4ef829 Add strings for #1116 to intro page. 2018-01-10 07:57:36 +01:00
James Cole
48c16c3dcc Clean up binders. 2018-01-10 07:51:47 +01:00
James Cole
c045193246 Remove unnecessary routes. 2018-01-10 07:29:55 +01:00
James Cole
a816e59a97 Small update in readme [skip ci] 2018-01-10 07:19:28 +01:00
James Cole
892074eaf2 Final touches on readme [skipci] 2018-01-09 20:24:45 +01:00
James Cole
3e501e429d Fix link to softalucous [skip ci] 2018-01-09 20:23:31 +01:00
James Cole
0f40accb4c Expand links. [skipci] 2018-01-09 20:22:46 +01:00
James Cole
3dae6c99a4 Fix badges. [skipci] 2018-01-09 20:17:58 +01:00
James Cole
b185c83597 Update read me file. [skip ci] 2018-01-09 20:16:17 +01:00
James Cole
ef6b4120f1 Lower case some file names. 2018-01-09 19:27:12 +01:00
James Cole
a43eef01fc Lower case some file names. 2018-01-09 19:26:49 +01:00
James Cole
2cb9aa537f Experimental support for CodeCov. 2018-01-09 17:52:18 +01:00
James Cole
2edd49a8b4 First version that supports Spectre. 2018-01-08 20:20:45 +01:00
James Cole
a57554d380 Merge branch 'develop' of https://github.com/firefly-iii/firefly-iii into develop
* 'develop' of https://github.com/firefly-iii/firefly-iii:
  Fix charts in IE
2018-01-08 20:20:14 +01:00
James Cole
c89486b6d9 Expand Spectre import code. 2018-01-08 19:21:00 +01:00
James Cole
f737cb7235 Update language files for #1109 2018-01-08 19:20:41 +01:00
James Cole
f1fe169553 Disable Spectre again. 2018-01-08 19:19:17 +01:00
James Cole
2fc760780e Add support for Russian. 2018-01-08 19:19:03 +01:00
James Cole
8c3290bf6f Merge pull request #1107 from devlearner/patch-1
Fix charts in Internet Explorer
2018-01-08 10:21:09 +01:00
devlearner
495158b9c9 Fix charts in IE
since IE apparently doesn't support arrow function expression (and throws a syntax error)
2018-01-08 07:49:32 +00:00
James Cole
f9fc9b1889 Spectre should not be enabled. [skip ci] 2018-01-07 16:49:49 +01:00
James Cole
11ff2ab9d1 Debug controller cannot be accessed by demo user. 2018-01-07 12:53:20 +01:00
James Cole
52b138e6b2 Fix error in read me [skip ci] 2018-01-07 12:43:02 +01:00
741 changed files with 51551 additions and 11116 deletions

12
.codeclimate.yml Normal file
View 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/

View File

@@ -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=${APP_DEBUG}
# This should be your email address
SITE_OWNER=${SITE_OWNER}
# 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,47 +26,65 @@ 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=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
# If you want Firefly III to mail you, update these settings
MAIL_DRIVER=${MAIL_DRIVER}
MAIL_HOST=${MAIL_HOST}
MAIL_PORT=${MAIL_PORT}
MAIL_FROM=${MAIL_FROM}
MAIL_USERNAME=${MAIL_USERNAME}
MAIL_PASSWORD=${MAIL_PASSWORD}
MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
# Firefly III can send you the following messages
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=false
# Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places.
MAPBOX_API_KEY=${MAPBOX_API_KEY}
# If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here.
ANALYTICS_ID=${ANALYTICS_ID}
# 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
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
CACHE_PREFIX=firefly
SEARCH_RESULT_LIMIT=50
EXCHANGE_RATE_SERVICE=fixerio
MAPBOX_API_KEY=
ANALYTICS_ID=
SITE_OWNER=mail@example.com
USE_ENCRYPTION=true
PUSHER_KEY=
PUSHER_SECRET=
PUSHER_ID=
DEMO_USERNAME=
DEMO_PASSWORD=
IS_DOCKER=true
IS_SANDSTORM=false
IS_HEROKU=false

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -27,7 +27,7 @@ REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_DRIVER=log
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_FROM=changeme@example.com
@@ -36,7 +36,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
SEND_REGISTRATION_MAIL=true
SEND_ERROR_MESSAGE=true
SEND_ERROR_MESSAGE=false
CACHE_PREFIX=firefly

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +1,48 @@
# 4.7.1
- A brand new API. Read about it in the [documentation](http://firefly-iii.readthedocs.io/en/latest/).
- Add support for Spanish. [issue 1194](https://github.com/firefly-iii/firefly-iii/issues/1194)
- Some custom preferences are selected by default for a better user experience.
- Some new currencies [issue 1211](https://github.com/firefly-iii/firefly-iii/issues/1211)
- Fixed [issue 1155](https://github.com/firefly-iii/firefly-iii/issues/1155) (reported by [ndandanov](https://github.com/ndandanov))
- [Issue 1156](https://github.com/firefly-iii/firefly-iii/issues/1156) [issue 1182](https://github.com/firefly-iii/firefly-iii/issues/1182) and other issues related to SQLite databases.
- Multi-page budget overview was broken (reported by [jinformatique](https://github.com/jinformatique))
- Importing CSV files with semi-colons in them did not work [issue 1172](https://github.com/firefly-iii/firefly-iii/issues/1172) [issue 1183](https://github.com/firefly-iii/firefly-iii/issues/1183) [issue 1210](https://github.com/firefly-iii/firefly-iii/issues/1210)
- Could not use account number that was in use by a deleted account [issue 1174](https://github.com/firefly-iii/firefly-iii/issues/1174)
- Fixed spelling error that lead to 404's [issue 1175](https://github.com/firefly-iii/firefly-iii/issues/1175) [issue 1190](https://github.com/firefly-iii/firefly-iii/issues/1190)
- Fixed tag autocomplete [issue 1178](https://github.com/firefly-iii/firefly-iii/issues/1178)
- Better links for "new transaction" buttons [issue 1185](https://github.com/firefly-iii/firefly-iii/issues/1185)
- Cache errors in budget charts [issue 1192](https://github.com/firefly-iii/firefly-iii/issues/1192)
- Deleting transactions that are linked to other other transactions would lead to errors [issue 1209](https://github.com/firefly-iii/firefly-iii/issues/1209)
# 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)

View File

@@ -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;"

File diff suppressed because it is too large Load Diff

View File

@@ -15,8 +15,8 @@ const pkgdef :Spk.PackageDefinition = (
manifest = (
appTitle = (defaultText = "Firefly III"),
appVersion = 7,
appMarketingVersion = (defaultText = "4.6.13"),
appVersion = 9,
appMarketingVersion = (defaultText = "4.7.1"),
actions = [
# Define your "new document" handlers here.

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
language: php
php:
- 7.1
- 7.2
cache:
directories:
@@ -24,6 +23,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:

View File

@@ -1,89 +0,0 @@
# Firefly III: A personal finances manager
[![Requires PHP7.1](https://img.shields.io/badge/php-7.1-red.svg)](https://secure.php.net/downloads.php) [![Latest Stable Version](https://poser.pugx.org/grumpydictator/firefly-iii/v/stable)](https://packagist.org/packages/grumpydictator/firefly-iii) [![License](https://img.shields.io/badge/license-GPL-lightgrey.svg)](https://www.gnu.org/licenses/gpl.html) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA)
[![The index of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/index.png)](https://firefly-iii.org/static/screenshots/4.6.12/index.png) [![The account overview of Firefly III](https://firefly-iii.org/static/screenshots/4.6.12/tiny/account.png)](https://firefly-iii.org/static/screenshots/4.6.12/account.png)
[![Overview of all budgets](https://firefly-iii.org/static/screenshots/4.6.12/tiny/budget.png)](https://firefly-iii.org/static/screenshots/4.6.12/budget.png) [![Overview of a category](https://firefly-iii.org/static/screenshots/4.6.12/tiny/category.png)](https://firefly-iii.org/static/screenshots/4.6.12/category.png)
[![View of a report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report1.png)](https://firefly-iii.org/static/screenshots/4.6.12/report1.png) [![View of another report](https://firefly-iii.org/static/screenshots/4.6.12/tiny/report2.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
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](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.
### Other options
Firefly III is also available as a package on [https://softaculous.com/](Softaculous) and [AMPPS](https://www.ampps.com/).
## 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", its just somebody elses 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).
[![Build Status](https://travis-ci.org/firefly-iii/firefly-iii.svg?branch=master)](https://travis-ci.org/firefly-iii/firefly-iii) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/firefly-iii/firefly-iii/?branch=master) [![Coverage Status](https://coveralls.io/repos/github/firefly-iii/firefly-iii/badge.svg?branch=master)](https://coveralls.io/github/firefly-iii/firefly-iii?branch=master)

View File

@@ -0,0 +1,88 @@
<?php
/**
* AboutController.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use DB;
use FireflyIII\Transformers\UserTransformer;
use Illuminate\Http\Request;
use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class AboutController
*/
class AboutController extends Controller
{
/**
* AccountController constructor.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function __construct()
{
parent::__construct();
}
/**
* @return \Illuminate\Http\JsonResponse
*/
public function about()
{
$search = ['~', '#'];
$replace = ['\~', '# '];
$phpVersion = str_replace($search, $replace, PHP_VERSION);
$phpOs = str_replace($search, $replace, php_uname());
$currentDriver = DB::getDriverName();
$data
= [
'version' => config('firefly.version'),
'api_version' => config('firefly.api_version'),
'php_version' => $phpVersion,
'os' => $phpOs,
'driver' => $currentDriver,
];
return response()->json(['data' => $data], 200)->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function user(Request $request)
{
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item(auth()->user(), new UserTransformer($this->parameters), 'users');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}

View File

@@ -0,0 +1,263 @@
<?php
/**
* AccountController.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\AccountRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Transformers\AccountTransformer;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Preferences;
/**
* Class AccountController
*/
class AccountController extends Controller
{
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** @var AccountRepositoryInterface */
private $repository;
/**
* AccountController constructor.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var AccountRepositoryInterface repository */
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser(auth()->user());
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->currencyRepository->setUser(auth()->user());
return $next($request);
}
);
}
/**
* Remove the specified resource from storage.
*
* @param \FireflyIII\Models\Account $account
*
* @return \Illuminate\Http\Response
*/
public function delete(Account $account)
{
$this->repository->destroy($account, null);
return response()->json([], 204);
}
/**
* Display a listing of the resource.
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
// create some objects:
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// read type from URI
$type = $request->get('type') ?? 'all';
$this->parameters->set('type', $type);
// types to get, page size:
$types = $this->mapTypes($this->parameters->get('type'));
$pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data);
// get list of accounts. Count it and split it.
$collection = $this->repository->getAccountsByType($types);
$count = $collection->count();
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.accounts.index') . $this->buildParams());
// present to user.
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new FractalCollection($accounts, new AccountTransformer($this->parameters), 'accounts');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
* @param Account $account
*
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, Account $account)
{
$manager = new Manager();
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($account, new AccountTransformer($this->parameters), 'accounts');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param AccountRequest $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(AccountRequest $request)
{
$data = $request->getAll();
// if currency ID is 0, find the currency by the code:
if ($data['currency_id'] === 0) {
$currency = $this->currencyRepository->findByCodeNull($data['currency_code']);
$data['currency_id'] = is_null($currency) ? 0 : $currency->id;
}
$account = $this->repository->store($data);
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($account, new AccountTransformer($this->parameters), 'accounts');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* Update account.
*
* @param AccountRequest $request
* @param Account $account
*
* @return \Illuminate\Http\JsonResponse
*/
public function update(AccountRequest $request, Account $account)
{
$data = $request->getAll();
// if currency ID is 0, find the currency by the code:
if ($data['currency_id'] === 0) {
$currency = $this->currencyRepository->findByCodeNull($data['currency_code']);
$data['currency_id'] = is_null($currency) ? 0 : $currency->id;
}
// set correct type:
$data['type'] = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$this->repository->update($account, $data);
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($account, new AccountTransformer($this->parameters), 'accounts');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param string $type
*
* @return array
*/
private function mapTypes(string $type): array
{
$types = [
'all' => [
AccountType::DEFAULT,
AccountType::CASH,
AccountType::ASSET,
AccountType::EXPENSE,
AccountType::REVENUE,
AccountType::INITIAL_BALANCE,
AccountType::BENEFICIARY,
AccountType::IMPORT,
AccountType::RECONCILIATION,
AccountType::LOAN,
],
'asset' => [
AccountType::DEFAULT,
AccountType::ASSET,
],
'cash' => [
AccountType::CASH,
],
'expense' => [
AccountType::EXPENSE,
AccountType::BENEFICIARY,
],
'revenue' => [
AccountType::REVENUE,
],
'special' => [
AccountType::CASH,
AccountType::INITIAL_BALANCE,
AccountType::IMPORT,
AccountType::RECONCILIATION,
AccountType::LOAN,
],
'hidden' => [
AccountType::INITIAL_BALANCE,
AccountType::IMPORT,
AccountType::RECONCILIATION,
AccountType::LOAN,
],
AccountType::DEFAULT => [AccountType::DEFAULT],
AccountType::CASH => [AccountType::CASH],
AccountType::ASSET => [AccountType::ASSET],
AccountType::EXPENSE => [AccountType::EXPENSE],
AccountType::REVENUE => [AccountType::REVENUE],
AccountType::INITIAL_BALANCE => [AccountType::INITIAL_BALANCE],
AccountType::BENEFICIARY => [AccountType::BENEFICIARY],
AccountType::IMPORT => [AccountType::IMPORT],
AccountType::RECONCILIATION => [AccountType::RECONCILIATION],
AccountType::LOAN => [AccountType::LOAN],
];
if (isset($types[$type])) {
return $types[$type];
}
return $types['all']; // @codeCoverageIgnore
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* BillController.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\BillRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Transformers\BillTransformer;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Preferences;
/**
* Class BillController
*/
class BillController extends Controller
{
/** @var BillRepositoryInterface */
private $repository;
/**
* BillController constructor.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var BillRepositoryInterface repository */
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setUser(auth()->user());
return $next($request);
}
);
}
/**
* Remove the specified resource from storage.
*
* @param \FireflyIII\Models\Bill $bill
*
* @return \Illuminate\Http\Response
*/
public function delete(Bill $bill)
{
$this->repository->destroy($bill);
return response()->json([], 204);
}
/**
* Display a listing of the resource.
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
$pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data);
$paginator = $this->repository->getPaginator($pageSize);
/** @var Collection $bills */
$bills = $paginator->getCollection();
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new FractalCollection($bills, new BillTransformer($this->parameters), 'bills');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
* @param Bill $bill
*
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, Bill $bill)
{
$manager = new Manager();
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($bill, new BillTransformer($this->parameters), 'bills');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param BillRequest $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(BillRequest $request)
{
$bill = $this->repository->store($request->getAll());
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($bill, new BillTransformer($this->parameters), 'bills');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param BillRequest $request
* @param Bill $bill
*
* @return \Illuminate\Http\JsonResponse
*/
public function update(BillRequest $request, Bill $bill)
{
$data = $request->getAll();
$bill = $this->repository->update($bill, $data);
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($bill, new BillTransformer($this->parameters), 'bills');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}

View File

@@ -0,0 +1,121 @@
<?php
/**
* Controller.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use FireflyConfig;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class Controller.
* @codeCoverageIgnore
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/** @var ParameterBag */
protected $parameters;
/**
* Controller constructor.
*
* @throws FireflyException
*/
public function __construct()
{
// is site a demo site?
$isDemoSite = FireflyConfig::get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
// do not expose API on demo site:
if (true === $isDemoSite) {
throw new FireflyException('The API is not available on the demo site.');
}
// get global parameters
$this->parameters = $this->getParameters();
}
/**
* @return string
*/
protected function buildParams(): string
{
$return = '?';
$params = [];
foreach ($this->parameters as $key => $value) {
if($key === 'page') {
continue;
}
if ($value instanceof Carbon) {
$params[$key] = $value->format('Y-m-d');
}
if (!$value instanceof Carbon) {
$params[$key] = $value;
}
}
$return .= http_build_query($params);
if (strlen($return) === 1) {
return '';
}
return $return;
}
/**
* @return ParameterBag
*/
private function getParameters(): ParameterBag
{
$bag = new ParameterBag;
$page = (int)request()->get('page');
if ($page === 0) {
$page = 1;
}
$bag->set('page', $page);
// some date fields:
$dates = ['start', 'end', 'date'];
foreach ($dates as $field) {
$date = request()->get($field);
$obj = null;
if (!is_null($date)) {
try {
$obj = new Carbon($date);
} catch (InvalidDateException $e) {
// don't care
}
}
$bag->set($field, $obj);
}
return $bag;
}
}

View File

@@ -0,0 +1,333 @@
<?php
/**
* TransactionController.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\TransactionRequest;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Transformers\TransactionTransformer;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Log;
use Preferences;
/**
* Class TransactionController
*/
class TransactionController extends Controller
{
/** @var JournalRepositoryInterface */
private $repository;
/**
* TransactionController constructor.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var JournalRepositoryInterface repository */
$this->repository = app(JournalRepositoryInterface::class);
$this->repository->setUser(auth()->user());
return $next($request);
}
);
}
/**
* Remove the specified resource from storage.
*
* @param \FireflyIII\Models\Transaction $transaction
*
* @return \Illuminate\Http\Response
*/
public function delete(Transaction $transaction)
{
$journal = $transaction->transactionJournal;
$this->repository->destroy($journal);
return response()->json([], 204);
}
/**
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
$pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data);
// read type from URI
$type = $request->get('type') ?? 'default';
$this->parameters->set('type', $type);
// types to get, page size:
$types = $this->mapTypes($this->parameters->get('type'));
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// collect transactions using the journal collector
$collector = app(JournalCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
$collector->setAllAssetAccounts();
// remove internal transfer filter:
if (in_array(TransactionType::TRANSFER, $types)) {
$collector->removeFilter(InternalTransferFilter::class);
}
if (!is_null($this->parameters->get('start')) && !is_null($this->parameters->get('end'))) {
$collector->setRange($this->parameters->get('start'), $this->parameters->get('end'));
}
$collector->setLimit($pageSize)->setPage($this->parameters->get('page'));
$collector->setTypes($types);
$paginator = $collector->getPaginatedJournals();
$paginator->setPath(route('api.v1.transactions.index') . $this->buildParams());
$transactions = $paginator->getCollection();
$resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters), 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
* @param Transaction $transaction
*
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, Transaction $transaction)
{
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// collect transactions using the journal collector
$collector = app(JournalCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
// filter on specific journals.
$collector->setJournals(new Collection([$transaction->transactionJournal]));
// add filter to remove transactions:
$transactionType = $transaction->transactionJournal->transactionType->type;
if ($transactionType === TransactionType::WITHDRAWAL) {
$collector->addFilter(PositiveAmountFilter::class);
}
if (!($transactionType === TransactionType::WITHDRAWAL)) {
$collector->addFilter(NegativeAmountFilter::class);
}
$transactions = $collector->getJournals();
$resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters), 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param TransactionRequest $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(TransactionRequest $request, JournalRepositoryInterface $repository)
{
$data = $request->getAll();
$data['user'] = auth()->user()->id;
$journal = $repository->store($data);
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// collect transactions using the journal collector
$collector = app(JournalCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
// filter on specific journals.
$collector->setJournals(new Collection([$journal]));
// add filter to remove transactions:
$transactionType = $journal->transactionType->type;
if ($transactionType === TransactionType::WITHDRAWAL) {
$collector->addFilter(PositiveAmountFilter::class);
}
if (!($transactionType === TransactionType::WITHDRAWAL)) {
$collector->addFilter(NegativeAmountFilter::class);
}
$transactions = $collector->getJournals();
$resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters), 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param TransactionRequest $request
* @param JournalRepositoryInterface $repository
* @param Transaction $transaction
*
* @return \Illuminate\Http\JsonResponse
*/
public function update(TransactionRequest $request, JournalRepositoryInterface $repository, Transaction $transaction)
{
$data = $request->getAll();
$data['user'] = auth()->user()->id;
Log::debug('Inside transaction update');
$journal = $repository->update($transaction->transactionJournal, $data);
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// needs a lot of extra data to match the journal collector. Or just expand that one.
// collect transactions using the journal collector
$collector = app(JournalCollectorInterface::class);
$collector->setUser(auth()->user());
$collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation();
// filter on specific journals.
$collector->setJournals(new Collection([$journal]));
// add filter to remove transactions:
$transactionType = $journal->transactionType->type;
if ($transactionType === TransactionType::WITHDRAWAL) {
$collector->addFilter(PositiveAmountFilter::class);
}
if (!($transactionType === TransactionType::WITHDRAWAL)) {
$collector->addFilter(NegativeAmountFilter::class);
}
$transactions = $collector->getJournals();
$resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters), 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param string $type
*
* @return array
*/
private function mapTypes(string $type): array
{
$types = [
'all' => [
TransactionType::WITHDRAWAL,
TransactionType::DEPOSIT,
TransactionType::TRANSFER,
TransactionType::OPENING_BALANCE,
TransactionType::RECONCILIATION,
],
'withdrawal' => [
TransactionType::WITHDRAWAL,
],
'withdrawals' => [
TransactionType::WITHDRAWAL,
],
'expense' => [
TransactionType::WITHDRAWAL,
],
'income' => [
TransactionType::DEPOSIT,
],
'deposit' => [
TransactionType::DEPOSIT,
],
'deposits' => [
TransactionType::DEPOSIT,
],
'transfer' => [
TransactionType::TRANSFER,
],
'transfers' => [
TransactionType::TRANSFER,
],
'opening_balance' => [
TransactionType::OPENING_BALANCE,
],
'reconciliation' => [
TransactionType::RECONCILIATION,
],
'reconciliations' => [
TransactionType::RECONCILIATION,
],
'special' => [
TransactionType::OPENING_BALANCE,
TransactionType::RECONCILIATION,
],
'specials' => [
TransactionType::OPENING_BALANCE,
TransactionType::RECONCILIATION,
],
'default' => [
TransactionType::WITHDRAWAL,
TransactionType::DEPOSIT,
TransactionType::TRANSFER,
],
];
if (isset($types[$type])) {
return $types[$type];
}
return $types['default']; // @codeCoverageIgnore
}
}

View File

@@ -0,0 +1,192 @@
<?php
/**
* UserController.php
* Copyright (c) 2018 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\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\UserRequest;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Transformers\UserTransformer;
use FireflyIII\User;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
use Preferences;
use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
/**
* Class UserController
*/
class UserController extends Controller
{
/** @var UserRepositoryInterface */
private $repository;
/**
* UserController constructor.
*
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var UserRepositoryInterface repository */
$this->repository = app(UserRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Remove the specified resource from storage.
*
* @param \FireflyIII\User $user
*
* @return \Illuminate\Http\Response
*/
public function delete(User $user)
{
if (auth()->user()->hasRole('owner')) {
$this->repository->destroy($user);
return response()->json([], 204);
}
throw new AccessDeniedException(''); // @codeCoverageIgnore
}
/**
* Display a listing of the resource.
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
// user preferences
$pageSize = intval(Preferences::getForUser(auth()->user(), 'listPageSize', 50)->data);
// make manager
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// build collection
$collection = $this->repository->all();
$count = $collection->count();
$users = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($users, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.users.index') . $this->buildParams());
// make resource
$resource = new FractalCollection($users, new UserTransformer($this->parameters), 'users');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
* @param User $user
*
* @return \Illuminate\Http\JsonResponse
*/
public function show(Request $request, User $user)
{
// make manager
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// make resource
$resource = new Item($user, new UserTransformer($this->parameters), 'users');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param UserRequest $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(UserRequest $request)
{
$data = $request->getAll();
$user = $this->repository->store($data);
// make manager
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// make resource
$resource = new Item($user, new UserTransformer($this->parameters), 'users');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param UserRequest $request
* @param User $user
*
* @return \Illuminate\Http\JsonResponse
*/
public function update(UserRequest $request, User $user)
{
$data = $request->getAll();
$user = $this->repository->update($user, $data);
// make manager
$manager = new Manager();
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
// make resource
$resource = new Item($user, new UserTransformer($this->parameters), 'users');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* AccountRequest.php
* Copyright (c) 2018 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\Api\V1\Requests;
/**
* Class AccountRequest
*/
class AccountRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* @return array
*/
public function getAll(): array
{
$data = [
'name' => $this->string('name'),
'active' => $this->boolean('active'),
'accountType' => $this->string('type'),
'account_type_id' => null,
'currency_id' => $this->integer('currency_id'),
'currency_code' => $this->string('currency_code'),
'virtualBalance' => $this->string('virtual_balance'),
'iban' => $this->string('iban'),
'BIC' => $this->string('bic'),
'accountNumber' => $this->string('account_number'),
'accountRole' => $this->string('account_role'),
'openingBalance' => $this->string('opening_balance'),
'openingBalanceDate' => $this->date('opening_balance_date'),
'ccType' => $this->string('cc_type'),
'ccMonthlyPaymentDate' => $this->string('cc_monthly_payment_date'),
'notes' => $this->string('notes'),
];
return $data;
}
/**
* @return array
*/
public function rules(): array
{
$accountRoles = join(',', config('firefly.accountRoles'));
$types = join(',', array_keys(config('firefly.subTitlesByIdentifier')));
$ccPaymentTypes = join(',', array_keys(config('firefly.ccTypes')));
$rules = [
'name' => 'required|min:1|uniqueAccountForUser',
'opening_balance' => 'numeric|required_with:opening_balance_date|nullable',
'opening_balance_date' => 'date|required_with:opening_balance|nullable',
'iban' => 'iban|nullable',
'bic' => 'bic|nullable',
'virtual_balance' => 'numeric|nullable',
'currency_id' => 'numeric|exists:transaction_currencies,id|required_without:currency_code',
'currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:currency_id',
'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser',
'account_role' => 'in:' . $accountRoles . '|required_if:type,asset',
'active' => 'required|boolean',
'cc_type' => 'in:' . $ccPaymentTypes . '|required_if:account_role,ccAsset',
'cc_monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:cc_type,monthlyFull',
'type' => 'required|in:' . $types,
'notes' => 'min:0|max:65536',
];
switch ($this->method()) {
default:
break;
case 'PUT':
case 'PATCH':
$account = $this->route()->parameter('account');
$rules['name'] .= ':' . $account->id;
$rules['account_number'] .= ':' . $account->id;
$rules['type'] = 'in:' . $types;
break;
}
return $rules;
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* BillRequest.php
* Copyright (c) 2018 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\Api\V1\Requests;
use Illuminate\Validation\Validator;
/**
* Class BillRequest
*/
class BillRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* @return array
*/
public function getAll(): array
{
$data = [
'name' => $this->string('name'),
'match' => $this->string('match'),
'amount_min' => $this->string('amount_min'),
'amount_max' => $this->string('amount_max'),
//'currency_id' => $this->integer('currency_id'),
//'currency_code' => $this->string('currency_code'),
'date' => $this->date('date'),
'repeat_freq' => $this->string('repeat_freq'),
'skip' => $this->integer('skip'),
'automatch' => $this->boolean('automatch'),
'active' => $this->boolean('active'),
'notes' => $this->string('notes'),
];
return $data;
}
/**
* @return array
*/
public function rules(): array
{
$rules = [
'name' => 'required|between:1,255|uniqueObjectForUser:bills,name',
'match' => 'required|between:1,255|uniqueObjectForUser:bills,match',
'amount_min' => 'required|numeric|more:0',
'amount_max' => 'required|numeric|more:0',
//'currency_id' => 'numeric|exists:transaction_currencies,id|required_without:currency_code',
//'currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:currency_id',
'date' => 'required|date',
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
'skip' => 'required|between:0,31',
'automatch' => 'required|boolean',
'active' => 'required|boolean',
'notes' => 'between:1,65536',
];
switch ($this->method()) {
default:
break;
case 'PUT':
case 'PATCH':
$bill = $this->route()->parameter('bill');
$rules['name'] .= ',' . $bill->id;
$rules['match'] .= ',' . $bill->id;
break;
}
return $rules;
}
/**
* Configure the validator instance.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
$data = $validator->getData();
$min = floatval($data['amount_min'] ?? 0);
$max = floatval($data['amount_max'] ?? 0);
if ($min > $max) {
$validator->errors()->add('amount_min', trans('validation.amount_min_over_max'));
}
}
);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* Request.php
* Copyright (c) 2018 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\Api\V1\Requests;
use FireflyIII\Http\Requests\Request as FireflyIIIRequest;
/**
* Class Request.
*/
class Request extends FireflyIIIRequest
{
}

View File

@@ -0,0 +1,500 @@
<?php
/**
* TransactionRequest.php
* Copyright (c) 2018 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\Api\V1\Requests;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\BelongsUser;
use Illuminate\Validation\Validator;
/**
* Class TransactionRequest
*/
class TransactionRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* @return array
*/
public function getAll(): array
{
$data = [
// basic fields for journal:
'type' => $this->string('type'),
'date' => $this->date('date'),
'description' => $this->string('description'),
'piggy_bank_id' => $this->integer('piggy_bank_id'),
'piggy_bank_name' => $this->string('piggy_bank_name'),
'bill_id' => $this->integer('bill_id'),
'bill_name' => $this->string('bill_name'),
'tags' => explode(',', $this->string('tags')),
// then, custom fields for journal
'interest_date' => $this->date('interest_date'),
'book_date' => $this->date('book_date'),
'process_date' => $this->date('process_date'),
'due_date' => $this->date('due_date'),
'payment_date' => $this->date('payment_date'),
'invoice_date' => $this->date('invoice_date'),
'internal_reference' => $this->string('internal_reference'),
'notes' => $this->string('notes'),
// then, transactions (see below).
'transactions' => [],
];
foreach ($this->get('transactions') as $index => $transaction) {
$array = [
'description' => $transaction['description'] ?? null,
'amount' => $transaction['amount'],
'currency_id' => isset($transaction['currency_id']) ? intval($transaction['currency_id']) : null,
'currency_code' => isset($transaction['currency_code']) ? $transaction['currency_code'] : null,
'foreign_amount' => $transaction['foreign_amount'] ?? null,
'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? intval($transaction['foreign_currency_id']) : null,
'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null,
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : null,
'budget_name' => $transaction['budget_name'] ?? null,
'category_id' => isset($transaction['category_id']) ? intval($transaction['category_id']) : null,
'category_name' => $transaction['category_name'] ?? null,
'source_id' => isset($transaction['source_id']) ? intval($transaction['source_id']) : null,
'source_name' => isset($transaction['source_name']) ? strval($transaction['source_name']) : null,
'destination_id' => isset($transaction['destination_id']) ? intval($transaction['destination_id']) : null,
'destination_name' => isset($transaction['destination_name']) ? strval($transaction['destination_name']) : null,
'reconciled' => $transaction['reconciled'] ?? false,
'identifier' => $index,
];
$data['transactions'][] = $array;
}
return $data;
}
/**
* @return array
*/
public function rules(): array
{
$rules = [
// basic fields for journal:
'type' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',
'description' => 'between:1,255',
'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser],
'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser],
'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser],
'bill_name' => ['between:1,255', 'nullable', new BelongsUser],
'tags' => 'between:1,255',
// then, custom fields for journal
'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',
// transaction rules (in array for splits):
'transactions.*.description' => 'nullable|between:1,255',
'transactions.*.amount' => 'required|numeric|more:0',
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code',
'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id',
'transactions.*.foreign_amount' => 'numeric|more:0',
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id',
'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code',
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser],
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser],
'transactions.*.category_name' => 'between:1,255|nullable',
'transactions.*.reconciled' => 'boolean|nullable',
// basic rules will be expanded later.
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser],
'transactions.*.source_name' => 'between:1,255|nullable',
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser],
'transactions.*.destination_name' => 'between:1,255|nullable',
];
switch ($this->method()) {
default:
break;
case 'PUT':
case 'PATCH':
unset($rules['type'], $rules['piggy_bank_id'], $rules['piggy_bank_name']);
break;
}
return $rules;
}
/**
* Configure the validator instance.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
$this->atLeastOneTransaction($validator);
$this->checkValidDescriptions($validator);
$this->equalToJournalDescription($validator);
$this->emptySplitDescriptions($validator);
$this->foreignCurrencyInformation($validator);
$this->validateAccountInformation($validator);
$this->validateSplitAccounts($validator);
}
);
}
/**
* Throws an error when this asset account is invalid.
*
* @param Validator $validator
* @param int|null $accountId
* @param null|string $accountName
* @param string $idField
* @param string $nameField
*/
protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): void
{
$accountId = intval($accountId);
$accountName = strval($accountName);
// both empty? hard exit.
if ($accountId < 1 && strlen($accountName) === 0) {
$validator->errors()->add($idField, trans('validation.filled', ['attribute' => $idField]));
return;
}
// ID belongs to user and is asset account:
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser(auth()->user());
$set = $repository->getAccountsById([$accountId]);
if ($set->count() === 1) {
/** @var Account $first */
$first = $set->first();
if ($first->accountType->type !== AccountType::ASSET) {
$validator->errors()->add($idField, trans('validation.belongs_user'));
return;
}
// we ignore the account name at this point.
return;
}
$account = $repository->findByName($accountName, [AccountType::ASSET]);
if (is_null($account)) {
$validator->errors()->add($nameField, trans('validation.belongs_user'));
}
return;
}
/**
* Adds an error to the validator when there are no transactions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneTransaction(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
// need at least one transaction
if (count($transactions) === 0) {
$validator->errors()->add('description', trans('validation.at_least_one_transaction'));
}
}
/**
* Adds an error to the "description" field when the user has submitted no descriptions and no
* journal description.
*
* @param Validator $validator
*/
protected function checkValidDescriptions(Validator $validator)
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
$journalDescription = strval($data['description'] ?? '');
$validDescriptions = 0;
foreach ($transactions as $index => $transaction) {
if (strlen(strval($transaction['description'] ?? '')) > 0) {
$validDescriptions++;
}
}
// no valid descriptions and empty journal description? error.
if ($validDescriptions === 0 && strlen($journalDescription) === 0) {
$validator->errors()->add('description', trans('validation.filled', ['attribute' => trans('validation.attributes.description')]));
}
}
/**
* Adds an error to the validator when the user submits a split transaction (more than 1 transactions)
* but does not give them a description.
*
* @param Validator $validator
*/
protected function emptySplitDescriptions(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
foreach ($transactions as $index => $transaction) {
$description = strval($transaction['description'] ?? '');
// filled description is mandatory for split transactions.
if (count($transactions) > 1 && strlen($description) === 0) {
$validator->errors()->add(
'transactions.' . $index . '.description',
trans('validation.filled', ['attribute' => trans('validation.attributes.transaction_description')])
);
}
}
}
/**
* Adds an error to the validator when any transaction descriptions are equal to the journal description.
*
* @param Validator $validator
*/
protected function equalToJournalDescription(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
$journalDescription = strval($data['description'] ?? '');
foreach ($transactions as $index => $transaction) {
$description = strval($transaction['description'] ?? '');
// description cannot be equal to journal description.
if ($description === $journalDescription) {
$validator->errors()->add('transactions.' . $index . '.description', trans('validation.equal_description'));
}
}
}
/**
* If the transactions contain foreign amounts, there must also be foreign currency information.
*
* @param Validator $validator
*/
protected function foreignCurrencyInformation(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
foreach ($transactions as $index => $transaction) {
// must have currency info.
if (isset($transaction['foreign_amount'])
&& !(isset($transaction['foreign_currency_id'])
|| isset($transaction['foreign_currency_code']))) {
$validator->errors()->add(
'transactions.' . $index . '.foreign_amount',
trans('validation.require_currency_info')
);
}
}
}
/**
* Throws an error when the given opposing account (of type $type) is invalid.
* Empty data is allowed, system will default to cash.
*
* @param Validator $validator
* @param string $type
* @param int|null $accountId
* @param null|string $accountName
* @param string $idField
*/
protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): void {
$accountId = intval($accountId);
$accountName = strval($accountName);
// both empty? done!
if ($accountId < 1 && strlen($accountName) === 0) {
return;
}
if ($accountId !== 0) {
// ID belongs to user and is $type account:
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$repository->setUser(auth()->user());
$set = $repository->getAccountsById([$accountId]);
if ($set->count() === 1) {
/** @var Account $first */
$first = $set->first();
if ($first->accountType->type !== $type) {
$validator->errors()->add($idField, trans('validation.belongs_user'));
return;
}
// we ignore the account name at this point.
return;
}
}
// not having an opposing account by this name is NOT a problem.
return;
}
/**
* Validates the given account information. Switches on given transaction type.
*
* @param Validator $validator
*
* @throws FireflyException
*/
protected function validateAccountInformation(Validator $validator): void
{
$data = $validator->getData();
$transactions = $data['transactions'] ?? [];
if (!isset($data['type'])) {
// the journal may exist in the request:
/** @var Transaction $transaction */
$transaction = $this->route()->parameter('transaction');
if (is_null($transaction)) {
return; // @codeCoverageIgnore
}
$data['type'] = strtolower($transaction->transactionJournal->transactionType->type);
}
foreach ($transactions as $index => $transaction) {
$sourceId = isset($transaction['source_id']) ? intval($transaction['source_id']) : null;
$sourceName = $transaction['source_name'] ?? null;
$destinationId = isset($transaction['destination_id']) ? intval($transaction['destination_id']) : null;
$destinationName = $transaction['destination_name'] ?? null;
switch ($data['type']) {
case 'withdrawal':
$idField = 'transactions.' . $index . '.source_id';
$nameField = 'transactions.' . $index . '.source_name';
$this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField);
$idField = 'transactions.' . $index . '.destination_id';
$this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField);
break;
case 'deposit':
$idField = 'transactions.' . $index . '.source_id';
$this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField);
$idField = 'transactions.' . $index . '.destination_id';
$nameField = 'transactions.' . $index . '.destination_name';
$this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField);
break;
case 'transfer':
$idField = 'transactions.' . $index . '.source_id';
$nameField = 'transactions.' . $index . '.source_name';
$this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField);
$idField = 'transactions.' . $index . '.destination_id';
$nameField = 'transactions.' . $index . '.destination_name';
$this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField);
break;
default:
throw new FireflyException(sprintf('The validator cannot handle transaction type "%s" in validateAccountInformation().', $data['type']));
}
}
}
/**
* @param Validator $validator
*
* @throws FireflyException
*/
protected function validateSplitAccounts(Validator $validator)
{
$data = $validator->getData();
$count = isset($data['transactions']) ? count($data['transactions']) : 0;
if ($count < 2) {
return;
}
// this is pretty much impossible:
// @codeCoverageIgnoreStart
if (!isset($data['type'])) {
// the journal may exist in the request:
/** @var Transaction $transaction */
$transaction = $this->route()->parameter('transaction');
if (is_null($transaction)) {
return;
}
$data['type'] = strtolower($transaction->transactionJournal->transactionType->type);
}
// @codeCoverageIgnoreEnd
// collect all source ID's and destination ID's, if present:
$sources = [];
$destinations = [];
foreach ($data['transactions'] as $transaction) {
$sources[] = isset($transaction['source_id']) ? intval($transaction['source_id']) : 0;
$destinations[] = isset($transaction['destination_id']) ? intval($transaction['destination_id']) : 0;
}
$destinations = array_unique($destinations);
$sources = array_unique($sources);
// switch on type:
switch ($data['type']) {
case 'withdrawal':
if (count($sources) > 1) {
$validator->errors()->add('transactions.0.source_id', trans('validation.all_accounts_equal'));
}
break;
case 'deposit':
if (count($destinations) > 1) {
$validator->errors()->add('transactions.0.destination_id', trans('validation.all_accounts_equal'));
}
break;
case 'transfer':
if (count($sources) > 1 || count($destinations) > 1) {
$validator->errors()->add('transactions.0.source_id', trans('validation.all_accounts_equal'));
$validator->errors()->add('transactions.0.destination_id', trans('validation.all_accounts_equal'));
}
break;
default:
// @codeCoverageIgnoreStart
throw new FireflyException(
sprintf('The validator cannot handle transaction type "%s" in validateSplitAccounts().', $data['type'])
);
// @codeCoverageIgnoreEnd
}
return;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* UserRequest.php
* Copyright (c) 2018 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\Api\V1\Requests;
use FireflyIII\User;
/**
* Class UserRequest
*/
class UserRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
if (!auth()->check()) {
return false; // @codeCoverageIgnore
}
/** @var User $user */
$user = auth()->user();
if (!$user->hasRole('owner')) {
return false; // @codeCoverageIgnore
}
return true;
}
/**
* @return array
*/
public function getAll(): array
{
$data = [
'email' => $this->string('email'),
'blocked' => $this->boolean('blocked'),
'blocked_code' => $this->string('blocked_code'),
];
return $data;
}
/**
* @return array
*/
public function rules(): array
{
$rules = [
'email' => 'required|email|unique:users,email,',
'blocked' => 'required|boolean',
'blocked_code' => 'in:email_changed',
];
switch ($this->method()) {
default:
break;
case 'PUT':
case 'PATCH':
$user = $this->route()->parameter('user');
$rules['email'] = 'required|email|unique:users,email,' . $user->id;
break;
}
return $rules;
}
}

View File

@@ -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;

View File

@@ -85,8 +85,8 @@ class Import extends Command
$monolog->pushHandler($handler);
// actually start job:
$type = 'csv' === $job->file_type ? 'file' : $job->file_type;
$key = sprintf('import.routine.%s', $type);
$type = 'csv' === $job->file_type ? 'file' : $job->file_type;
$key = sprintf('import.routine.%s', $type);
$className = config($key);
if (null === $className || !class_exists($className)) {
throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore

View File

@@ -220,7 +220,7 @@ class UpgradeDatabase extends Command
// when mismatch in transaction:
if (!(intval($transaction->transaction_currency_id) === intval($currency->id))) {
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
$transaction->foreign_currency_id = intval($transaction->transaction_currency_id);
$transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id;
$transaction->save();
@@ -412,12 +412,10 @@ class UpgradeDatabase extends Command
if (!(intval($transaction->transaction_currency_id) === intval($currency->id)) && null === $transaction->foreign_amount) {
Log::debug(
sprintf(
'Transaction #%d has a currency setting (#%d) (%s) that should be #%d (%s). 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,
$this->var_dump_ret(intval($transaction->transaction_currency_id)),
$currency->id,
$this->var_dump_ret(intval($currency->id)),
$transaction->amount
)
);
@@ -504,19 +502,4 @@ class UpgradeDatabase extends Command
return;
}
/**
* @param null $mixed
*
* @return string
*/
private function var_dump_ret($mixed = null): string
{
ob_start();
var_dump($mixed);
$content = ob_get_contents();
ob_end_clean();
return trim($content);
}
}

View File

@@ -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;

View File

@@ -25,8 +25,11 @@ namespace FireflyIII\Exceptions;
use ErrorException;
use Exception;
use FireflyIII\Jobs\MailError;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class Handler
@@ -62,6 +65,36 @@ class Handler extends ExceptionHandler
*/
public function render($request, Exception $exception)
{
if ($exception instanceof ValidationException && $request->expectsJson()) {
// ignore it: controller will handle it.
return parent::render($request, $exception);
}
if ($exception instanceof NotFoundHttpException && $request->expectsJson()) {
return response()->json(['message' => 'Resource not found', 'exception' => 'NotFoundHttpException'], 404);
}
if ($exception instanceof AuthenticationException && $request->expectsJson()) {
return response()->json(['message' => 'Unauthenticated', 'exception' => 'AuthenticationException'], 401);
}
if ($request->expectsJson()) {
$isDebug = config('app.debug', false);
if ($isDebug) {
return response()->json(
[
'message' => $exception->getMessage(),
'exception' => get_class($exception),
'line' => $exception->getLine(),
'file' => $exception->getFile(),
'trace' => $exception->getTrace(),
], 500
);
}
return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => get_class($exception)], 500);
}
if ($exception instanceof FireflyException || $exception instanceof ErrorException) {
$isDebug = env('APP_DEBUG', false);

View File

@@ -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');

View File

@@ -0,0 +1,171 @@
<?php
/**
* AccountFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Services\Internal\Support\AccountServiceTrait;
use FireflyIII\User;
/**
* Factory to create or return accounts.
*
* Class AccountFactory
*/
class AccountFactory
{
use AccountServiceTrait;
/** @var User */
private $user;
/**
* @param array $data
*
* @return Account
*/
public function create(array $data): Account
{
$type = $this->getAccountType($data['account_type_id'], $data['accountType']);
$data['iban'] = $this->filterIban($data['iban']);
// account may exist already:
$existingAccount = $this->find($data['name'], $type->type);
if (null !== $existingAccount) {
return $existingAccount;
}
// create it:
$databaseData
= [
'user_id' => $this->user->id,
'account_type_id' => $type->id,
'name' => $data['name'],
'virtual_balance' => strlen(strval($data['virtualBalance'])) === 0 ? '0' : $data['virtualBalance'],
'active' => true === $data['active'] ? true : false,
'iban' => $data['iban'],
];
// remove virtual balance when not an asset account:
if ($type->type !== AccountType::ASSET) {
$databaseData['virtual_balance'] = '0';
}
$newAccount = Account::create($databaseData);
$this->updateMetadata($newAccount, $data);
if ($this->validIBData($data) && $type->type === AccountType::ASSET) {
$this->updateIB($newAccount, $data);
}
if (!$this->validIBData($data) && $type->type === AccountType::ASSET) {
$this->deleteIB($newAccount);
}
// update note:
if (isset($data['notes'])) {
$this->updateNote($newAccount, $data['notes']);
}
return $newAccount;
}
/**
* @param string $accountName
* @param string $accountType
*
* @return Account|null
*/
public function find(string $accountName, string $accountType): ?Account
{
$type = AccountType::whereType($accountType)->first();
$accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(['accounts.*']);
/** @var Account $object */
foreach ($accounts as $object) {
if ($object->name === $accountName) {
return $object;
}
}
return null;
}
/**
* @param string $accountName
* @param string $accountType
*
* @return Account
*/
public function findOrCreate(string $accountName, string $accountType): Account
{
$type = AccountType::whereType($accountType)->first();
$accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(['accounts.*']);
/** @var Account $object */
foreach ($accounts as $object) {
if ($object->name === $accountName) {
return $object;
}
}
return $this->create(
[
'user_id' => $this->user->id,
'name' => $accountName,
'account_type_id' => $type->id,
'accountType' => null,
'virtualBalance' => '0',
'iban' => null,
'active' => true,
]
);
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @param int|null $accountTypeId
* @param null|string $accountType
*
* @return AccountType|null
*/
protected function getAccountType(?int $accountTypeId, ?string $accountType): ?AccountType
{
$accountTypeId = intval($accountTypeId);
if ($accountTypeId > 0) {
return AccountType::find($accountTypeId);
}
$type = config('firefly.accountTypeByIdentifier.' . strval($accountType));
return AccountType::whereType($type)->first();
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* AccountMetaFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\AccountMeta;
/**
* Class AccountMetaFactory
*/
class AccountMetaFactory
{
/**
* @param array $data
*
* @return AccountMeta|null
*/
public function create(array $data): ?AccountMeta
{
return AccountMeta::create($data);
}
}

137
app/Factory/BillFactory.php Normal file
View File

@@ -0,0 +1,137 @@
<?php
/**
* BillFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Bill;
use FireflyIII\Services\Internal\Support\BillServiceTrait;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Log;
/**
* Class BillFactory
*/
class BillFactory
{
use BillServiceTrait;
/** @var User */
private $user;
/**
* @param array $data
*
* @return Bill|null
*/
public function create(array $data): ?Bill
{
$matchArray = explode(',', $data['match']);
$matchArray = array_unique($matchArray);
$match = join(',', $matchArray);
/** @var Bill $bill */
$bill = Bill::create(
[
'name' => $data['name'],
'match' => $match,
'amount_min' => $data['amount_min'],
'user_id' => $this->user->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'repeat_freq' => $data['repeat_freq'],
'skip' => $data['skip'],
'automatch' => $data['automatch'],
'active' => $data['active'],
]
);
// update note:
if (isset($data['notes'])) {
$this->updateNote($bill, $data['notes']);
}
return $bill;
}
/**
* @param int|null $billId
* @param null|string $billName
*
* @return Bill|null
*/
public function find(?int $billId, ?string $billName): ?Bill
{
$billId = intval($billId);
$billName = strval($billName);
// first find by ID:
if ($billId > 0) {
/** @var Bill $bill */
$bill = $this->user->bills()->find($billId);
if (!is_null($bill)) {
return $bill;
}
}
// then find by name:
if (strlen($billName) > 0) {
$bill = $this->findByName($billName);
if (!is_null($bill)) {
return $bill;
}
}
return null;
}
/**
* @param string $name
*
* @return Bill|null
*/
public function findByName(string $name): ?Bill
{
/** @var Collection $collection */
$collection = $this->user->bills()->get();
/** @var Bill $bill */
foreach ($collection as $bill) {
Log::debug(sprintf('"%s" vs. "%s"', $bill->name, $name));
if ($bill->name === $name) {
return $bill;
}
}
Log::debug(sprintf('Bill::Find by name returns NULL based on "%s"', $name));
return null;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* BudgetFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Budget;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
* Class BudgetFactory
*/
class BudgetFactory
{
/** @var User */
private $user;
/**
* @param int|null $budgetId
* @param null|string $budgetName
*
* @return Budget|null
*/
public function find(?int $budgetId, ?string $budgetName): ?Budget
{
$budgetId = intval($budgetId);
$budgetName = strval($budgetName);
if (strlen($budgetName) === 0 && $budgetId === 0) {
return null;
}
// first by ID:
if ($budgetId > 0) {
/** @var Budget $budget */
$budget = $this->user->budgets()->find($budgetId);
if (!is_null($budget)) {
return $budget;
}
}
if (strlen($budgetName) > 0) {
$budget = $this->findByName($budgetName);
if (!is_null($budget)) {
return $budget;
}
}
return null;
}
/**
* @param string $name
*
* @return Budget|null
*/
public function findByName(string $name): ?Budget
{
/** @var Collection $collection */
$collection = $this->user->budgets()->get();
/** @var Budget $budget */
foreach ($collection as $budget) {
if ($budget->name === $name) {
return $budget;
}
}
return null;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* CategoryFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Category;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Log;
/**
* Class CategoryFactory
*/
class CategoryFactory
{
/** @var User */
private $user;
/**
* @param string $name
*
* @return Category|null
*/
public function findByName(string $name): ?Category
{
/** @var Collection $collection */
$collection = $this->user->categories()->get();
/** @var Category $category */
foreach ($collection as $category) {
if ($category->name === $name) {
return $category;
}
}
return null;
}
/**
* @param int|null $categoryId
* @param null|string $categoryName
*
* @return Category|null
*/
public function findOrCreate(?int $categoryId, ?string $categoryName): ?Category
{
$categoryId = intval($categoryId);
$categoryName = strval($categoryName);
Log::debug(sprintf('Going to find category with ID %d and name "%s"', $categoryId, $categoryName));
if (strlen($categoryName) === 0 && $categoryId === 0) {
return null;
}
// first by ID:
if ($categoryId > 0) {
/** @var Category $category */
$category = $this->user->categories()->find($categoryId);
if (!is_null($category)) {
return $category;
}
}
if (strlen($categoryName) > 0) {
$category = $this->findByName($categoryName);
if (!is_null($category)) {
return $category;
}
return Category::create(
[
'user_id' => $this->user->id,
'name' => $categoryName,
]
);
}
return null;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* PiggyBankEventFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Log;
/**
* Create piggy bank events.
*
* Class PiggyBankEventFactory
*/
class PiggyBankEventFactory
{
/**
* @param TransactionJournal $journal
* @param PiggyBank|null $piggyBank
*
* @return PiggyBankEvent|null
*/
public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): ?PiggyBankEvent
{
Log::debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type));
if (is_null($piggyBank)) {
return null;
}
// is a transfer?
if (!(TransactionType::TRANSFER === $journal->transactionType->type)) {
Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id));
return null;
}
/** @var PiggyBankRepositoryInterface $piggyRepos */
$piggyRepos = app(PiggyBankRepositoryInterface::class);
$piggyRepos->setUser($journal->user);
// repetition exists?
$repetition = $piggyRepos->getRepetition($piggyBank, $journal->date);
if (null === $repetition->id) {
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
return null;
}
// get the amount
$amount = $piggyRepos->getExactAmount($piggyBank, $repetition, $journal);
if (0 === bccomp($amount, '0')) {
Log::debug('Amount is zero, will not create event.');
return null;
}
// update amount
$piggyRepos->addAmountToRepetition($repetition, $amount);
$event = $piggyRepos->createEventWithJournal($piggyBank, $amount, $journal);
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
return $event;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* PiggyBankFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\PiggyBank;
use FireflyIII\User;
/**
* Class PiggyBankFactory
*/
class PiggyBankFactory
{
/** @var User */
private $user;
/**
* @param int|null $piggyBankId
* @param null|string $piggyBankName
*
* @return PiggyBank|null
*/
public function find(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank
{
$piggyBankId = intval($piggyBankId);
$piggyBankName = strval($piggyBankName);
if (strlen($piggyBankName) === 0 && $piggyBankId === 0) {
return null;
}
// first find by ID:
if ($piggyBankId > 0) {
/** @var PiggyBank $piggyBank */
$piggyBank = $this->user->piggyBanks()->find($piggyBankId);
if (!is_null($piggyBank)) {
return $piggyBank;
}
}
// then find by name:
if (strlen($piggyBankName) > 0) {
/** @var PiggyBank $piggyBank */
$piggyBank = $this->findByName($piggyBankName);
if (!is_null($piggyBank)) {
return $piggyBank;
}
}
return null;
}
/**
* @param string $name
*
* @return PiggyBank|null
*/
public function findByName(string $name): ?PiggyBank
{
$set = $this->user->piggyBanks()->get();
/** @var PiggyBank $piggy */
foreach ($set as $piggy) {
if ($piggy->name === $name) {
return $piggy;
}
}
return null;
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

102
app/Factory/TagFactory.php Normal file
View File

@@ -0,0 +1,102 @@
<?php
/**
* TagFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Tag;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
* Class TagFactory
*/
class TagFactory
{
/** @var Collection */
private $tags;
/** @var User */
private $user;
/**
* @param array $data
*
* @return Tag|null
*/
public function create(array $data): ?Tag
{
return Tag::create(
[
'user_id' => $this->user->id,
'tag' => $data['tag'],
'tagMode' => 'nothing',
'date' => $data['date'],
'description' => $data['description'],
'latitude' => $data['latitude'],
'longitude ' => $data['longitude'],
'zoomLevel' => $data['zoom_level'],
]
);
}
/**
* @param string $tag
*
* @return Tag|null
*/
public function findOrCreate(string $tag): ?Tag
{
if (is_null($this->tags)) {
$this->tags = $this->user->tags()->get();
}
/** @var Tag $object */
foreach ($this->tags as $object) {
if ($object->tag === $tag) {
return $object;
}
}
$newTag = $this->create(
[
'tag' => $tag,
'date' => null,
'description' => null,
'latitude' => null,
'longitude' => null,
'zoom_level' => null,
]
);
$this->tags->push($newTag);
return $newTag;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* TransactionCurrencyFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\TransactionCurrency;
/**
* Class TransactionCurrencyFactory
*/
class TransactionCurrencyFactory
{
/**
* @param int|null $currencyId
* @param null|string $currencyCode
*
* @return TransactionCurrency|null
*/
public function find(?int $currencyId, ?string $currencyCode): ?TransactionCurrency
{
$currencyCode = strval($currencyCode);
$currencyId = intval($currencyId);
if (strlen($currencyCode) === 0 && intval($currencyId) === 0) {
return null;
}
// first by ID:
if ($currencyId > 0) {
$currency = TransactionCurrency::find($currencyId);
if (!is_null($currency)) {
return $currency;
}
}
// then by code:
if (strlen($currencyCode) > 0) {
$currency = TransactionCurrency::whereCode($currencyCode)->first();
if (!is_null($currency)) {
return $currency;
}
}
return null;
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* TransactionFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Services\Internal\Support\TransactionServiceTrait;
use FireflyIII\User;
use Illuminate\Support\Collection;
/**
* Class TransactionFactory
*/
class TransactionFactory
{
use TransactionServiceTrait;
/** @var User */
private $user;
/**
* @param array $data
*
* @return Transaction
*/
public function create(array $data): Transaction
{
$currencyId = isset($data['currency']) ? $data['currency']->id : $data['currency_id'];
return Transaction::create(
[
'reconciled' => $data['reconciled'],
'account_id' => $data['account']->id,
'transaction_journal_id' => $data['transaction_journal']->id,
'description' => $data['description'],
'transaction_currency_id' => $currencyId,
'amount' => $data['amount'],
'foreign_amount' => $data['foreign_amount'],
'foreign_currency_id' => null,
'identifier' => $data['identifier'],
]
);
}
/**
* Create a pair of transactions based on the data given in the array.
*
* @param TransactionJournal $journal
* @param array $data
*
* @return Collection
*/
public function createPair(TransactionJournal $journal, array $data): Collection
{
// all this data is the same for both transactions:
$currency = $this->findCurrency($data['currency_id'], $data['currency_code']);
$description = $journal->description === $data['description'] ? null : $data['description'];
// type of source account depends on journal type:
$sourceType = $this->accountType($journal, 'source');
$sourceAccount = $this->findAccount($sourceType, $data['source_id'], $data['source_name']);
// same for destination account:
$destinationType = $this->accountType($journal, 'destination');
$destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']);
// first make a "negative" (source) transaction based on the data in the array.
$source = $this->create(
[
'description' => $description,
'amount' => app('steam')->negative(strval($data['amount'])),
'foreign_amount' => null,
'currency' => $currency,
'account' => $sourceAccount,
'transaction_journal' => $journal,
'reconciled' => $data['reconciled'],
'identifier' => $data['identifier'],
]
);
// then make a "positive" transaction based on the data in the array.
$dest = $this->create(
[
'description' => $description,
'amount' => app('steam')->positive(strval($data['amount'])),
'foreign_amount' => null,
'currency' => $currency,
'account' => $destinationAccount,
'transaction_journal' => $journal,
'reconciled' => $data['reconciled'],
'identifier' => $data['identifier'],
]
);
// set foreign currency
$foreign = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']);
$this->setForeignCurrency($source, $foreign);
$this->setForeignCurrency($dest, $foreign);
// set foreign amount:
if (!is_null($data['foreign_amount'])) {
$this->setForeignAmount($source, app('steam')->negative(strval($data['foreign_amount'])));
$this->setForeignAmount($dest, app('steam')->positive(strval($data['foreign_amount'])));
}
// set budget:
$budget = $this->findBudget($data['budget_id'], $data['budget_name']);
$this->setBudget($source, $budget);
$this->setBudget($dest, $budget);
// set category
$category = $this->findCategory($data['category_id'], $data['category_name']);
$this->setCategory($source, $category);
$this->setCategory($dest, $category);
return new Collection([$source, $dest]);
}
/**
* @param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* TransactionJournalFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\User;
use Log;
/**
* Class TransactionJournalFactory
*/
class TransactionJournalFactory
{
use JournalServiceTrait;
/** @var User */
private $user;
/**
* Create a new transaction journal and associated transactions.
*
* @param array $data
*
* @return TransactionJournal
* @throws FireflyException
*/
public function create(array $data): TransactionJournal
{
Log::debug('Start of TransactionJournalFactory::create()');
// store basic journal first.
$type = $this->findTransactionType($data['type']);
$defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user);
$journal = TransactionJournal::create(
[
'user_id' => $data['user'],
'transaction_type_id' => $type->id,
'bill_id' => null,
'transaction_currency_id' => $defaultCurrency->id,
'description' => $data['description'],
'date' => $data['date']->format('Y-m-d'),
'order' => 0,
'tag_count' => 0,
'completed' => 0,
]
);
// store basic transactions:
$factory = app(TransactionFactory::class);
$factory->setUser($this->user);
/** @var array $trData */
foreach ($data['transactions'] as $trData) {
$factory->createPair($journal, $trData);
}
$journal->completed = true;
$journal->save();
// link bill:
$this->connectBill($journal, $data);
// link piggy bank (if transfer)
$this->connectPiggyBank($journal, $data);
// link tags:
$this->connectTags($journal, $data);
// store note:
$this->storeNote($journal, strval($data['notes']));
// store date meta fields (if present):
$this->storeMeta($journal, $data, 'interest_date');
$this->storeMeta($journal, $data, 'book_date');
$this->storeMeta($journal, $data, 'process_date');
$this->storeMeta($journal, $data, 'due_date');
$this->storeMeta($journal, $data, 'payment_date');
$this->storeMeta($journal, $data, 'invoice_date');
$this->storeMeta($journal, $data, 'internal_reference');
Log::debug('End of TransactionJournalFactory::create()');
return $journal;
}
/**
* Set the user.
*
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @param TransactionJournal $journal
* @param array $data
*/
protected function connectPiggyBank(TransactionJournal $journal, array $data): void
{
/** @var PiggyBankFactory $factory */
$factory = app(PiggyBankFactory::class);
$factory->setUser($this->user);
$piggyBank = $factory->find($data['piggy_bank_id'], $data['piggy_bank_name']);
if (!is_null($piggyBank)) {
/** @var PiggyBankEventFactory $factory */
$factory = app(PiggyBankEventFactory::class);
$factory->create($journal, $piggyBank);
}
}
/**
* Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always
* use TransactionType repository.
*
* @param string $type
*
* @return TransactionType
* @throws FireflyException
*/
protected function findTransactionType(string $type): TransactionType
{
$factory = app(TransactionTypeFactory::class);
$transactionType = $factory->find($type);
if (is_null($transactionType)) {
throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore
}
return $transactionType;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* TransactionJournalMetaFactory.php
* Copyright (c) 2018 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\Factory;
use Carbon\Carbon;
use Exception;
use FireflyIII\Models\TransactionJournalMeta;
use Log;
/**
* Class TransactionJournalMetaFactory
*/
class TransactionJournalMetaFactory
{
/**
* @param array $data
*
* @return TransactionJournalMeta|null
*/
public function updateOrCreate(array $data): ?TransactionJournalMeta
{
$value = $data['data'];
/** @var TransactionJournalMeta $entry */
$entry = $data['journal']->transactionJournalMeta()->where('name', $data['name'])->first();
if (is_null($value) && !is_null($entry)) {
try {
$entry->delete();
} catch (Exception $e) { // @codeCoverageIgnore
Log::error(sprintf('Could not delete transaction journal meta: %s', $e->getMessage())); // @codeCoverageIgnore
}
return null;
}
if ($data['data'] instanceof Carbon) {
$value = $data['data']->toW3cString();
}
if (null === $entry) {
$entry = new TransactionJournalMeta();
$entry->transactionJournal()->associate($data['journal']);
$entry->name = $data['name'];
}
$entry->data = $value;
$entry->save();
return $entry;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* TransactionTypeFactory.php
* Copyright (c) 2018 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\Factory;
use FireflyIII\Models\TransactionType;
/**
* @codeCoverageIgnore
* Class TransactionTypeFactory
*/
class TransactionTypeFactory
{
/**
* @param string $type
*
* @return TransactionType|null
*/
public function find(string $type): ?TransactionType
{
return TransactionType::whereType(ucfirst($type))->first();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -61,61 +61,6 @@ class StoredJournalEventHandler
$this->ruleGroupRepository = $ruleGroupRepository;
}
/**
* This method connects a new transfer to a piggy bank.
*
* @param StoredTransactionJournal $event
*
* @return bool
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function connectToPiggyBank(StoredTransactionJournal $event): bool
{
$journal = $event->journal;
$piggyBankId = $event->piggyBankId;
$piggyBank = $this->repository->find($piggyBankId);
// is a transfer?
if (!$this->journalRepository->isTransfer($journal)) {
Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id));
return true;
}
// piggy exists?
if (null === $piggyBank->id) {
Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId));
return true;
}
// repetition exists?
$repetition = $this->repository->getRepetition($piggyBank, $journal->date);
if (null === $repetition->id) {
Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d')));
return true;
}
// get the amount
$amount = $this->repository->getExactAmount($piggyBank, $repetition, $journal);
if (0 === bccomp($amount, '0')) {
Log::debug('Amount is zero, will not create event.');
return true;
}
// update amount
$this->repository->addAmountToRepetition($repetition, $amount);
$event = $this->repository->createEventWithJournal($piggyBank, $amount, $journal);
Log::debug(sprintf('Created piggy bank event #%d', $event->id));
return true;
}
/**
* This method grabs all the users rules and processes them.
*

View File

@@ -51,6 +51,7 @@ class UpdatedJournalEventHandler
/**
* This method will check all the rules when a journal is updated.
* TODO move to factory.
*
* @param UpdatedTransactionJournal $updatedJournalEvent
*
@@ -81,6 +82,7 @@ class UpdatedJournalEventHandler
/**
* This method calls a special bill scanner that will check if the updated journal is part of a bill.
* TODO move to factory.
*
* @param UpdatedTransactionJournal $updatedJournalEvent
*

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -50,8 +50,9 @@ class AttachmentHelper implements AttachmentHelperInterface
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
protected $uploadDisk;
/**
*
* AttachmentHelper constructor.
*/
public function __construct()
{

View File

@@ -79,6 +79,9 @@ class MetaPieChart implements MetaPieChartInterface
/** @var User */
protected $user;
/**
* MetaPieChart constructor.
*/
public function __construct()
{
$this->accounts = new Collection;
@@ -273,11 +276,13 @@ class MetaPieChart implements MetaPieChartInterface
$collector->setBudgets($this->budgets);
$collector->setCategories($this->categories);
// @codeCoverageIgnoreStart
if ($this->tags->count() > 0) {
$collector->setTags($this->tags);
$collector->withCategoryInformation();
$collector->withBudgetInformation();
}
// @codeCoverageIgnoreEnd
return $collector->getJournals();
}
@@ -294,7 +299,7 @@ class MetaPieChart implements MetaPieChartInterface
{
if (0 === count($fields) && $this->tags->count() > 0) {
// do a special group on tags:
return $this->groupByTag($set);
return $this->groupByTag($set); // @codeCoverageIgnore
}
$grouped = [];
@@ -338,6 +343,8 @@ class MetaPieChart implements MetaPieChartInterface
}
/**
* @codeCoverageIgnore
*
* @param Collection $set
*
* @return array

View File

@@ -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;
@@ -68,6 +69,8 @@ class JournalCollector implements JournalCollectorInterface
'transaction_journals.description',
'transaction_journals.date',
'transaction_journals.encrypted',
'transaction_journals.created_at',
'transaction_journals.updated_at',
'transaction_types.type as transaction_type_type',
'transaction_journals.bill_id',
'transaction_journals.updated_at',
@@ -232,7 +235,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 +246,18 @@ 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));
@@ -258,11 +273,22 @@ class JournalCollector implements JournalCollectorInterface
if (null !== $transaction->bill_name) {
$transaction->bill_name = Steam::decrypt(intval($transaction->bill_name_encrypted), $transaction->bill_name);
}
$transaction->account_name = app('steam')->tryDecrypt($transaction->account_name);
$transaction->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
$transaction->account_iban = app('steam')->tryDecrypt($transaction->account_iban);
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
// budget name
$transaction->transaction_journal_budget_name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name);
$transaction->transaction_budget_name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
// category name:
$transaction->transaction_journal_category_name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name);
$transaction->transaction_category_name = app('steam')->tryDecrypt($transaction->transaction_category_name);
}
);
Log::debug(sprintf('Cached query with ID "%s".', $key));
$cache->store($set);
return $set;
}
@@ -328,7 +354,7 @@ class JournalCollector implements JournalCollectorInterface
*/
public function setAfter(Carbon $after): JournalCollectorInterface
{
$afterStr = $after->format('Y-m-d');
$afterStr = $after->format('Y-m-d 00:00:00');
$this->query->where('transaction_journals.date', '>=', $afterStr);
Log::debug(sprintf('JournalCollector range is now after %s (inclusive)', $afterStr));
@@ -364,7 +390,7 @@ class JournalCollector implements JournalCollectorInterface
*/
public function setBefore(Carbon $before): JournalCollectorInterface
{
$beforeStr = $before->format('Y-m-d');
$beforeStr = $before->format('Y-m-d 00:00:00');
$this->query->where('transaction_journals.date', '<=', $beforeStr);
Log::debug(sprintf('JournalCollector range is now before %s (inclusive)', $beforeStr));
@@ -471,6 +497,23 @@ class JournalCollector implements JournalCollectorInterface
return $this;
}
/**
* @param Collection $journals
*
* @return JournalCollectorInterface
*/
public function setJournals(Collection $journals): JournalCollectorInterface
{
$ids = $journals->pluck('id')->toArray();
$this->query->where(
function (EloquentBuilder $q) use ($ids) {
$q->whereIn('transaction_journals.id', $ids);
}
);
return $this;
}
/**
* @param int $limit
*
@@ -551,8 +594,8 @@ class JournalCollector implements JournalCollectorInterface
public function setRange(Carbon $start, Carbon $end): JournalCollectorInterface
{
if ($start <= $end) {
$startStr = $start->format('Y-m-d');
$endStr = $end->format('Y-m-d');
$startStr = $start->format('Y-m-d 00:00:00');
$endStr = $end->format('Y-m-d 23:59:59');
$this->query->where('transaction_journals.date', '>=', $startStr);
$this->query->where('transaction_journals.date', '<=', $endStr);
Log::debug(sprintf('JournalCollector range is now %s - %s (inclusive)', $startStr, $endStr));

View File

@@ -42,6 +42,13 @@ interface JournalCollectorInterface
*/
public function addFilter(string $filter): JournalCollectorInterface;
/**
* @param Collection $journals
*
* @return JournalCollectorInterface
*/
public function setJournals(Collection $journals): JournalCollectorInterface;
/**
* @param string $amount
*

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Support\Collection;
/**
@@ -136,7 +137,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();
@@ -182,6 +183,9 @@ class PopupReport implements PopupReportInterface
*/
public function byIncome(Account $account, array $attributes): Collection
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$repository->setUser($account->user);
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])
@@ -191,9 +195,10 @@ class PopupReport implements PopupReportInterface
// filter the set so the destinations outside of $attributes['accounts'] are not included.
$journals = $journals->filter(
function (Transaction $transaction) use ($report) {
function (Transaction $transaction) use ($report, $repository) {
// get the destinations:
$destinations = $transaction->transactionJournal->destinationAccountList()->pluck('id')->toArray();
$journal = $transaction->transactionJournal;
$destinations = $repository->getJournalDestinationAccounts($journal)->pluck('id')->toArray();
// do these intersect with the current list?
return !empty(array_intersect($report, $destinations));

View File

@@ -24,9 +24,11 @@ namespace FireflyIII\Http\Controllers\Account;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionJournalFactory;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\ReconciliationFormRequest;
use FireflyIII\Http\Requests\ReconciliationStoreRequest;
use FireflyIII\Http\Requests\ReconciliationUpdateRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
@@ -35,8 +37,10 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Services\Internal\Update\TransactionUpdateService;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Response;
use Session;
@@ -48,6 +52,9 @@ use Session;
*/
class ReconcileController extends Controller
{
/** @var JournalRepositoryInterface */
private $repository;
/**
*
*/
@@ -60,6 +67,7 @@ class ReconcileController extends Controller
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', trans('firefly.accounts'));
$this->repository = app(JournalRepositoryInterface::class);
return $next($request);
}
@@ -80,10 +88,10 @@ class ReconcileController extends Controller
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
// journal related code
$pTransaction = $journal->positiveTransaction();
$pTransaction = $this->repository->getFirstPosTransaction($journal);
$preFilled = [
'date' => $journal->dateAsString(),
'category' => $journal->categoryAsString(),
'date' => $this->repository->getJournalDate($journal, null),
'category' => $this->repository->getJournalCategoryName($journal),
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
'amount' => $pTransaction->amount,
];
@@ -126,10 +134,8 @@ class ReconcileController extends Controller
$clearedAmount = '0';
$route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]);
// get sum of transaction amounts:
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$transactions = $repository->getTransactionsById($transactionIds);
$cleared = $repository->getTransactionsById($clearedIds);
$transactions = $this->repository->getTransactionsById($transactionIds);
$cleared = $this->repository->getTransactionsById($clearedIds);
$countCleared = 0;
/** @var Transaction $transaction */
@@ -182,7 +188,7 @@ class ReconcileController extends Controller
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
$currency = $currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
@@ -220,71 +226,105 @@ class ReconcileController extends Controller
}
/**
* @param JournalRepositoryInterface $repository
* @param TransactionJournal $journal
* @param TransactionJournal $journal
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
*/
public function show(JournalRepositoryInterface $repository, TransactionJournal $journal)
public function show(TransactionJournal $journal)
{
if (TransactionType::RECONCILIATION !== $journal->transactionType->type) {
return redirect(route('transactions.show', [$journal->id]));
}
$subTitle = trans('firefly.reconciliation') . ' "' . $journal->description . '"';
// get main transaction:
$transaction = $repository->getAssetTransaction($journal);
$transaction = $this->repository->getAssetTransaction($journal);
$account = $transaction->account;
return view('accounts.reconcile.show', compact('journal', 'subTitle', 'transaction', 'account'));
}
/**
* @param Request $request
* @param Account $account
* @param Carbon $start
* @param Carbon $end
* @param ReconciliationStoreRequest $request
* @param Account $account
* @param Carbon $start
* @param Carbon $end
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
public function submit(Request $request, Account $account, Carbon $start, Carbon $end)
public function submit(ReconciliationStoreRequest $request, JournalRepositoryInterface $repository, Account $account, Carbon $start, Carbon $end)
{
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$transactions = $repository->getTransactionsById($request->get('transactions') ?? []);
Log::debug('In ReconcileController::submit()');
$data = $request->getAll();
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$repository->reconcile($transaction); // mark as reconciled.
foreach ($data['transactions'] as $transactionId) {
$repository->reconcileById(intval($transactionId));
}
Log::debug('Reconciled all transactions.');
// create reconciliation transaction (if necessary):
if ('create' === $request->get('reconcile')) {
if ('create' === $data['reconcile']) {
// get "opposing" account.
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$reconciliation = $accountRepos->getReconciliation($account);
$difference = $request->get('difference');
// store journal between these two.
$data = [
'what' => 'Reconciliation',
'source' => $account,
'destination' => $reconciliation,
'category' => '',
'budget_id' => 0,
'amount' => $difference,
'currency_id' => $account->getMeta('currency_id'),
'description' => trans(
'firefly.reconcilliation_transaction_title',
['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)]
),
'date' => $request->get('end'),
'notes' => join(',', $transactions->pluck('id')->toArray()),
$difference = $data['difference'];
$source = $reconciliation;
$destination = $account;
if (bccomp($difference, '0') === 1) {
// amount is positive. Add it to reconciliation?
$source = $account;
$destination = $reconciliation;
}
// data for journal
$description = trans(
'firefly.reconcilliation_transaction_title',
['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)]
);
$journalData = [
'type' => 'Reconciliation',
'description' => $description,
'user' => auth()->user()->id,
'date' => $data['end'],
'bill_id' => null,
'bill_name' => null,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'tags' => null,
'interest_date' => null,
'transactions' => [[
'currency_id' => intval($account->getMeta('currency_id')),
'currency_code' => null,
'description' => null,
'amount' => app('steam')->positive($difference),
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'reconciled' => true,
'identifier' => 0,
'foreign_currency_id' => null,
'foreign_currency_code' => null,
'foreign_amount' => null,
'budget_id' => null,
'budget_name' => null,
'category_id' => null,
'category_name' => null,
],
],
'notes' => join(', ', $data['transactions']),
];
$journal = $repository->store($data);
// reconcile this transaction too:
$transaction = $journal->transactions()->first();
$repository->reconcile($transaction);
$journal = $repository->store($journalData);
}
Log::debug('End of routine.');
Session::flash('success', trans('firefly.reconciliation_stored'));
@@ -313,7 +353,7 @@ class ReconcileController extends Controller
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencyId = intval($account->getMeta('currency_id'));
$currency = $currencyRepos->find($currencyId);
$currency = $currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
@@ -339,13 +379,12 @@ class ReconcileController extends Controller
}
/**
* @param ReconciliationFormRequest $request
* @param AccountRepositoryInterface $repository
* @param TransactionJournal $journal
* @param ReconciliationUpdateRequest $request
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(ReconciliationFormRequest $request, AccountRepositoryInterface $repository, TransactionJournal $journal)
public function update(ReconciliationUpdateRequest $request, TransactionJournal $journal)
{
if (TransactionType::RECONCILIATION !== $journal->transactionType->type) {
return redirect(route('transactions.show', [$journal->id]));
@@ -356,8 +395,53 @@ class ReconcileController extends Controller
return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput();
}
// update journal using account repository. Keep it consistent.
$data = $request->getJournalData();
$repository->updateReconciliation($journal, $data);
$submitted = $request->getJournalData();
// amount pos neg influences the accounts:
$source = $this->repository->getJournalSourceAccounts($journal)->first();
$destination = $this->repository->getJournalDestinationAccounts($journal)->first();
if (bccomp($submitted['amount'], '0') === 1) {
// amount is positive, switch accounts:
list($source, $destination) = [$destination, $source];
}
// expand data with journal data:
$data = [
'type' => $journal->transactionType->type,
'description' => $journal->description,
'user' => $journal->user_id,
'date' => $journal->date,
'bill_id' => null,
'bill_name' => null,
'piggy_bank_id' => null,
'piggy_bank_name' => null,
'tags' => $submitted['tags'],
'interest_date' => null,
'book_date' => null,
'transactions' => [[
'currency_id' => intval($journal->transaction_currency_id),
'currency_code' => null,
'description' => null,
'amount' => app('steam')->positive($submitted['amount']),
'source_id' => $source->id,
'source_name' => null,
'destination_id' => $destination->id,
'destination_name' => null,
'reconciled' => true,
'identifier' => 0,
'foreign_currency_id' => null,
'foreign_currency_code' => null,
'foreign_amount' => null,
'budget_id' => null,
'budget_name' => null,
'category_id' => null,
'category_name' => $submitted['category'],
],
],
'notes' => $this->repository->getNoteText($journal),
];
$this->repository->update($journal, $data);
// @codeCoverageIgnoreStart
if (1 === intval($request->get('return_to_edit'))) {

View File

@@ -39,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;
@@ -51,6 +50,13 @@ use View;
*/
class AccountController extends Controller
{
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
/** @var AccountRepositoryInterface */
private $repository;
/**
*
*/
@@ -64,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);
}
);
@@ -77,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);
@@ -102,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
@@ -127,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->findNull(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();
@@ -154,17 +161,13 @@ class AccountController extends Controller
* @return View
*
* @throws FireflyException
* @throws FireflyException
* @throws FireflyException
*/
public function edit(Request $request, Account $account, AccountRepositoryInterface $repository)
{
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = 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 = $currencyRepos->get();
$allCurrencies = $this->currencyRepos->get();
$currencySelectList = ExpandedForm::makeSelectList($allCurrencies);
$roles = [];
foreach (config('firefly.accountRoles') as $role) {
@@ -180,11 +183,9 @@ class AccountController extends Controller
// pre fill some useful values.
// the opening balance is tricky:
$openingBalanceAmount = $account->getOpeningBalanceAmount();
$openingBalanceAmount = '0' === $account->getOpeningBalanceAmount() ? '' : $openingBalanceAmount;
$openingBalanceDate = $account->getOpeningBalanceDate();
$openingBalanceDate = 1900 === $openingBalanceDate->year ? null : $openingBalanceDate->format('Y-m-d');
$currency = $currencyRepos->find(intval($account->getMeta('currency_id')));
$openingBalanceAmount = strval($repository->getOpeningBalanceAmount($account));
$openingBalanceDate = $repository->getOpeningBalanceDate($account);
$currency = $this->currencyRepos->findNull(intval($account->getMeta('currency_id')));
$preFilled = [
'accountNumber' => $account->getMeta('accountNumber'),
@@ -197,9 +198,10 @@ class AccountController extends Controller
'virtualBalance' => $account->virtual_balance,
'currency_id' => $currency->id,
'notes' => '',
'active' => $account->active,
];
/** @var Note $note */
$note = $repository->getNote($account);
$note = $this->repository->getNote($account);
if (null !== $note) {
$preFilled['notes'] = $note->text;
}
@@ -224,19 +226,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);
@@ -272,10 +273,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
*
@@ -284,82 +284,58 @@ class AccountController extends Controller
*
* @throws FireflyException
*/
public function show(Request $request, JournalRepositoryInterface $repository, Account $account, string $moment = '')
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
{
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;
if (null === $start) {
$start = session('start');
}
if (null === $end) {
$end = app('navigation')->endOfPeriod($start, $range);
}
if ($end < $start) {
throw new FireflyException('End is after start!'); // @codeCoverageIgnore
}
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$currencyId = intval($account->getMeta('currency_id'));
$currency = $this->currencyRepos->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore
}
// prep for "all" view.
if ('all' === $moment) {
$subTitle = trans('firefly.all_journals_for_account', ['name' => $account->name]);
$chartUri = route('chart.account.all', [$account->id]);
$first = $repository->first();
$start = $first->date ?? new Carbon;
$end = new Carbon;
}
// prep for "specific date" view.
if (strlen($moment) > 0 && 'all' !== $moment) {
$start = new Carbon($moment);
$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);
}
// prep for current period view
if (0 === strlen($moment)) {
$start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range));
$end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $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]);
$periods = $this->getPeriodOverview($account);
}
// grab journals:
$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'), $end->format('Y-m-d')]);
$periods = $this->getPeriodOverview($account, $end);
$collector = app(JournalCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page);
if (null !== $start) {
$collector->setRange($start, $end);
}
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('accounts.show', [$account->id, $moment]));
$transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
return view(
'accounts.show',
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri')
compact('account', 'currency', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri')
);
}
/**
* @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();
@@ -390,10 +366,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();
@@ -435,56 +411,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;
if ($end < $start) {
list($start, $end) = [$end, $start]; // @codeCoverageIgnore
}
// 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'));
$dateName = app('navigation')->periodShow($date['start'], $date['period']);
$entries->push(
[
'string' => $dateStr,
'name' => $dateName,
'spent' => $spent,
'earned' => $earned,
'date' => clone $end,]
'start' => $date['start']->format('Y-m-d'),
'end' => $date['end']->format('Y-m-d'),
]
);
$end = app('navigation')->subtractPeriod($end, $range, 1);
++$count;
}
$cache->store($entries);
return $entries;

View File

@@ -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;

View File

@@ -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.');
}
}

View File

@@ -75,6 +75,7 @@ class ResetPasswordController extends Controller
if (true === $singleUserMode && $userCount > 0) {
$allowRegistration = false;
}
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email, 'allowRegistration' => $allowRegistration]
);

View File

@@ -30,10 +30,14 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Transformers\BillTransformer;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use League\Fractal\Manager;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\DataArraySerializer;
use Preferences;
use Symfony\Component\HttpFoundation\ParameterBag;
use URL;
use View;
@@ -167,36 +171,26 @@ class BillController extends Controller
*
* @return View
*/
public function index(Request $request, BillRepositoryInterface $repository)
public function index(BillRepositoryInterface $repository)
{
/** @var Carbon $start */
$start = session('start');
/** @var Carbon $end */
$start = session('start');
$end = session('end');
$page = 0 === intval($request->get('page')) ? 1 : intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$collection = $repository->getBills();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$collection->each(
function (Bill $bill) use ($repository, $start, $end) {
// paid in this period?
$bill->paidDates = $repository->getPaidDatesInRange($bill, $start, $end);
$bill->payDates = $repository->getPayDatesInRange($bill, $start, $end);
$lastPaidDate = $this->lastPaidDate($repository->getPaidDatesInRange($bill, $start, $end), $start);
if ($bill->paidDates->count() >= $bill->payDates->count()) {
// if all bills have been been paid, jump to next period.
$lastPaidDate = $end;
}
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
$paginator = $repository->getPaginator($pageSize);
$parameters = new ParameterBag();
$parameters->set('start', $start);
$parameters->set('end', $end);
$transformer = new BillTransformer($parameters);
/** @var Collection $bills */
$bills = $paginator->getCollection()->map(
function (Bill $bill) use ($transformer) {
return $transformer->transform($bill);
}
);
// paginate bills
$bills = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$bills->setPath(route('bills.index'));
return view('bills.index', compact('bills'));
$paginator->setPath(route('bills.index'));
return view('bills.index', compact('bills', 'paginator'));
}
/**
@@ -235,15 +229,24 @@ class BillController extends Controller
*/
public function show(Request $request, BillRepositoryInterface $repository, Bill $bill)
{
/** @var Carbon $date */
$date = session('start');
/** @var Carbon $end */
$subTitle = $bill->name;
$start = session('start');
$end = session('end');
$year = $date->year;
$year = $start->year;
$page = intval($request->get('page'));
$pageSize = intval(Preferences::get('listPageSize', 50)->data);
$yearAverage = $repository->getYearAverage($bill, $date);
$yearAverage = $repository->getYearAverage($bill, $start);
$overallAverage = $repository->getOverallAverage($bill);
$manager = new Manager();
$manager->setSerializer(new DataArraySerializer());
$manager->parseIncludes(['attachments']);
// Make a resource out of the data and
$parameters = new ParameterBag();
$parameters->set('start', $start);
$parameters->set('end', $end);
$resource = new Item($bill, new BillTransformer($parameters), 'bill');
$object = $manager->createData($resource)->toArray();
// use collector:
/** @var JournalCollectorInterface $collector */
@@ -253,18 +256,8 @@ class BillController extends Controller
$transactions = $collector->getPaginatedJournals();
$transactions->setPath(route('bills.show', [$bill->id]));
$bill->paidDates = $repository->getPaidDatesInRange($bill, $date, $end);
$bill->payDates = $repository->getPayDatesInRange($bill, $date, $end);
$lastPaidDate = $this->lastPaidDate($repository->getPaidDatesInRange($bill, $date, $end), $date);
if ($bill->paidDates->count() >= $bill->payDates->count()) {
// if all bills have been been paid, jump to next period.
$lastPaidDate = $end;
}
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastPaidDate);
$hideBill = true;
$subTitle = e($bill->name);
return view('bills.show', compact('transactions', 'yearAverage', 'overallAverage', 'year', 'hideBill', 'bill', 'subTitle'));
return view('bills.show', compact('transactions', 'yearAverage', 'overallAverage', 'year', 'object', 'bill', 'subTitle'));
}
/**
@@ -335,28 +328,4 @@ class BillController extends Controller
return redirect($this->getPreviousUri('bills.edit.uri'));
}
/**
* Returns the latest date in the set, or start when set is empty.
*
* @param Collection $dates
* @param Carbon $default
*
* @return Carbon
*/
private function lastPaidDate(Collection $dates, Carbon $default): Carbon
{
if (0 === $dates->count()) {
return $default; // @codeCoverageIgnore
}
$latest = $dates->first();
/** @var Carbon $date */
foreach ($dates as $date) {
if ($date->gte($latest)) {
$latest = $date;
}
}
return $latest;
}
}

View File

@@ -82,9 +82,9 @@ class BudgetController extends Controller
*/
public function amount(Request $request, BudgetRepositoryInterface $repository, Budget $budget)
{
$amount = strval($request->get('amount'));
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$amount = strval($request->get('amount'));
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
if (0 === bccomp($amount, '0')) {
$budgetLimit = null;
@@ -198,13 +198,13 @@ class BudgetController extends Controller
$prev->subDay();
$prev = app('navigation')->startOfPeriod($prev, $range);
$this->repository->cleanupBudgets();
$budgets = $this->repository->getActiveBudgets();
$total = $budgets->count();
$budgets = $budgets->slice(($page - 1) * $pageSize, $pageSize);
$allBudgets = $this->repository->getActiveBudgets();
$total = $allBudgets->count();
$budgets = $allBudgets->slice(($page - 1) * $pageSize, $pageSize);
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->repository->collectBudgetInformation($budgets, $start, $end);
$budgetInformation = $this->repository->collectBudgetInformation($allBudgets, $start, $end);
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
@@ -252,7 +252,7 @@ class BudgetController extends Controller
'currentMonth',
'next',
'nextText',
'prev',
'prev', 'allBudgets',
'prevText',
'periodStart',
'periodEnd',
@@ -420,11 +420,12 @@ class BudgetController extends Controller
$end = Carbon::createFromFormat('Y-m-d', $request->string('end'));
$defaultCurrency = app('amount')->getDefaultCurrency();
$amount = $request->get('amount');
$page = $request->integer('page') === 0 ? 1 : $request->integer('page');
$this->repository->cleanupBudgets();
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
Preferences::mark();
return redirect(route('budgets.index', [$start->format('Y-m-d')]));
return redirect(route('budgets.index', [$start->format('Y-m-d')]) . '?page=' . $page);
}
/**
@@ -503,7 +504,7 @@ class BudgetController extends Controller
{
$data = $request->getBudgetData();
$budget = $this->repository->store($data);
$this->repository->cleanupBudgets();
$request->session()->flash('success', strval(trans('firefly.stored_new_budget', ['name' => $budget->name])));
Preferences::mark();
@@ -530,6 +531,7 @@ class BudgetController extends Controller
$this->repository->update($budget, $data);
$request->session()->flash('success', strval(trans('firefly.updated_budget', ['name' => $budget->name])));
$this->repository->cleanupBudgets();
Preferences::mark();
if (1 === intval($request->get('return_to_edit'))) {
@@ -549,13 +551,14 @@ class BudgetController extends Controller
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function updateIncome(Carbon $start, Carbon $end)
public function updateIncome(Request $request, Carbon $start, Carbon $end)
{
$defaultCurrency = app('amount')->getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$available = round($available, $defaultCurrency->decimal_places);
$page = intval($request->get('page'));
return view('budgets.income', compact('available', 'start', 'end'));
return view('budgets.income', compact('available', 'start', 'end', 'page'));
}
/**
@@ -611,21 +614,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);

View File

@@ -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);

View File

@@ -39,7 +39,6 @@ use Illuminate\Support\Collection;
use Log;
use Preferences;
use Response;
use Steam;
/** checked
* Class AccountController.
@@ -58,44 +57,6 @@ class AccountController extends Controller
$this->generator = app(GeneratorInterface::class);
}
/**
* @param Account $account
*
* @return \Illuminate\Http\JsonResponse
*/
public function all(Account $account)
{
$cache = new CacheProperties;
$cache->addProperty('chart.account.all');
$cache->addProperty($account->id);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/** @var AccountRepositoryInterface $repository */
$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();
}
$data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);
return Response::json($data);
}
/**
* Shows the balances for all the user's expense accounts.
@@ -118,8 +79,8 @@ class AccountController extends Controller
$start->subDay();
$accounts = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
$startBalances = Steam::balancesByAccounts($accounts, $start);
$endBalances = Steam::balancesByAccounts($accounts, $end);
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
$chartData = [];
foreach ($accounts as $account) {
@@ -341,34 +302,57 @@ class AccountController extends Controller
*
* @return \Illuminate\Http\JsonResponse
*/
public function period(Account $account, Carbon $start)
public function period(Account $account, Carbon $start, Carbon $end)
{
$range = Preferences::get('viewRange', '1M')->data;
$end = app('navigation')->endOfPeriod($start, $range);
$cache = new CacheProperties();
$cache = new CacheProperties;
$cache->addProperty('chart.account.period');
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('chart.account.period');
$cache->addProperty($account->id);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$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'; // @codeCoverageIgnore
}
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 = app('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;
// @codeCoverageIgnoreStart
case '1W':
case '1M':
case '1Y':
while ($end >= $current) {
$balance = floatval(app('steam')->balance($account, $current));
$label = app('navigation')->periodShow($current, $step);
$chartData[$label] = $balance;
$current = app('navigation')->addPeriod($current, $step, 1);
}
break;
// @codeCoverageIgnoreEnd
}
$data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);
@@ -411,8 +395,8 @@ class AccountController extends Controller
$accounts = $repository->getAccountsByType([AccountType::REVENUE]);
$start->subDay();
$startBalances = Steam::balancesByAccounts($accounts, $start);
$endBalances = Steam::balancesByAccounts($accounts, $end);
$startBalances = app('steam')->balancesByAccounts($accounts, $start);
$endBalances = app('steam')->balancesByAccounts($accounts, $end);
foreach ($accounts as $account) {
$id = $account->id;
@@ -432,49 +416,6 @@ class AccountController extends Controller
return Response::json($data);
}
/**
* Shows an account's balance for a single month.
*
* @param Account $account
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function single(Account $account)
{
$start = clone session('start', Carbon::now()->startOfMonth());
$end = clone session('end', Carbon::now()->endOfMonth());
// chart properties for cache:
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('chart.account.single');
$cache->addProperty($account->id);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$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();
}
$data = $this->generator->singleSet($account->name, $chartData);
$cache->store($data);
return Response::json($data);
}
/**
* @param Collection $accounts
* @param Carbon $start
@@ -500,14 +441,14 @@ class AccountController extends Controller
$chartData = [];
foreach ($accounts as $account) {
$currency = $repository->find(intval($account->getMeta('currency_id')));
$currency = $repository->findNull(intval($account->getMeta('currency_id')));
$currentSet = [
'label' => $account->name,
'currency_symbol' => $currency->symbol,
'entries' => [],
];
$currentStart = clone $start;
$range = Steam::balanceInRange($account, $start, clone $end);
$range = app('steam')->balanceInRange($account, $start, clone $end);
$previous = array_values($range)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');

View File

@@ -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,48 @@ 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');
$cache->addProperty($budget->id);
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);
@@ -140,6 +150,7 @@ class BudgetController extends Controller
$cache->addProperty($end);
$cache->addProperty('chart.budget.budget.limit');
$cache->addProperty($budgetLimit->id);
$cache->addProperty($budget->id);
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore

View File

@@ -197,7 +197,7 @@ class ExpenseReportController extends Controller
$collection->push($expenseAccount);
$revenue = $this->accountRepository->findByName($expenseAccount->name, [AccountType::REVENUE]);
if (!is_null($revenue->id)) {
if (!is_null($revenue)) {
$collection->push($revenue);
}
$combined[$expenseAccount->name] = $collection;

View File

@@ -25,6 +25,8 @@ 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;
@@ -34,6 +36,15 @@ use Monolog\Handler\RotatingFileHandler;
*/
class DebugController extends Controller
{
/**
* HomeController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(IsDemoUser::class);
}
/**
* @param Request $request
@@ -74,7 +85,11 @@ class DebugController extends Controller
if ($handler instanceof RotatingFileHandler) {
$logFile = $handler->getUrl();
if (null !== $logFile) {
$logContent = file_get_contents($logFile);
try {
$logContent = file_get_contents($logFile);
} catch (Exception $e) {
// don't care
}
}
}
}

View File

@@ -200,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 = '&nbsp;';
/** @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;';
}
}

View File

@@ -55,7 +55,7 @@ class ConfigurationController extends Controller
return $next($request);
}
);
$this->middleware(IsDemoUser::class)->except(['index']);
$this->middleware(IsDemoUser::class);
}
/**

View File

@@ -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']);
}
/**
@@ -100,6 +100,7 @@ class IndexController extends Controller
$config['initial-config-complete'] = false;
$config['has-file-upload'] = false;
$config['delimiter'] = "\t" === $config['delimiter'] ? 'tab' : $config['delimiter'];
unset($config['stage']);
$result = json_encode($config, JSON_PRETTY_PRINT);
$name = sprintf('"%s"', addcslashes('import-configuration-' . date('Y-m-d') . '.json', '"\\'));

View File

@@ -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);
}

View File

@@ -47,7 +47,7 @@ class JavascriptController extends Controller
{
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
$preference = Preferences::get('currencyPreference', config('firefly.default_currency', 'EUR'));
$default = $currencyRepository->findByCode($preference->data);
$default = $currencyRepository->findByCodeNull($preference->data);
$data = ['accounts' => []];
@@ -95,13 +95,13 @@ class JavascriptController extends Controller
*/
public function variables(Request $request, AccountRepositoryInterface $repository, CurrencyRepositoryInterface $currencyRepository)
{
$account = $repository->find(intval($request->get('account')));
$account = $repository->findNull(intval($request->get('account')));
$currencyId = 0;
if (null !== $account) {
$currencyId = intval($account->getMeta('currency_id'));
}
/** @var TransactionCurrency $currency */
$currency = $currencyRepository->find($currencyId);
$currency = $currencyRepository->findNull($currencyId);
if (0 === $currencyId) {
$currency = app('amount')->getDefaultCurrency();
}

View File

@@ -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->findNull($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);

View File

@@ -45,9 +45,8 @@ class FrontpageController extends Controller
$info = [];
/** @var PiggyBank $piggyBank */
foreach ($set as $piggyBank) {
$rep = $piggyBank->currentRelevantRep();
$amount = strval($rep->currentamount);
if (null !== $rep->id && 1 === bccomp($amount, '0')) {
$amount = $repository->getCurrentAmount($piggyBank);
if (1 === bccomp($amount, '0')) {
// percentage!
$pct = round(($amount / $piggyBank->targetamount) * 100);

View File

@@ -85,14 +85,28 @@ class NewUserController extends Controller
$this->createSavingsAccount($request, $repository);
// also store currency preference from input:
$currency = $currencyRepository->find(intval($request->input('amount_currency_id_bank_balance')));
$currency = $currencyRepository->findNull(intval($request->input('amount_currency_id_bank_balance')));
if (null !== $currency->id) {
if (null !== $currency) {
// store currency preference:
Preferences::set('currencyPreference', $currency->code);
Preferences::mark();
}
// set default optional fields:
$visibleFields = [
'interest_date' => true,
'book_date' => false,
'process_date' => false,
'due_date' => false,
'payment_date' => false,
'invoice_date' => false,
'internal_reference' => false,
'notes' => true,
'attachments' => true,
];
Preferences::set('transaction_journal_optional_fields', $visibleFields);
Session::flash('success', strval(trans('firefly.stored_new_accounts_new_user')));
Preferences::mark();
@@ -112,6 +126,7 @@ class NewUserController extends Controller
'iban' => null,
'accountType' => 'asset',
'virtualBalance' => 0,
'account_type_id' => null,
'active' => true,
'accountRole' => 'defaultAsset',
'openingBalance' => $request->input('bank_balance'),
@@ -136,6 +151,7 @@ class NewUserController extends Controller
'name' => $request->get('bank_name') . ' savings account',
'iban' => null,
'accountType' => 'asset',
'account_type_id' => null,
'virtualBalance' => 0,
'active' => true,
'accountRole' => 'savingAsset',

View File

@@ -67,12 +67,12 @@ class PiggyBankController extends Controller
*
* @return View
*/
public function add(PiggyBank $piggyBank)
public function add(PiggyBank $piggyBank, PiggyBankRepositoryInterface $repository)
{
/** @var Carbon $date */
$date = session('end', Carbon::now()->endOfMonth());
$leftOnAccount = $piggyBank->leftOnAccount($date);
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
$savedSoFar = $repository->getCurrentAmount($piggyBank);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = min($leftOnAccount, $leftToSave);
@@ -86,12 +86,12 @@ class PiggyBankController extends Controller
*
* @return View
*/
public function addMobile(PiggyBank $piggyBank)
public function addMobile(PiggyBank $piggyBank, PiggyBankRepositoryInterface $repository)
{
/** @var Carbon $date */
$date = session('end', Carbon::now()->endOfMonth());
$leftOnAccount = $piggyBank->leftOnAccount($date);
$savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
$savedSoFar = $repository->getCurrentAmount($piggyBank);
$leftToSave = bcsub($piggyBank->targetamount, $savedSoFar);
$maxAmount = min($leftOnAccount, $leftToSave);
@@ -212,7 +212,8 @@ class PiggyBankController extends Controller
Log::debug('Looping piggues');
/** @var PiggyBank $piggyBank */
foreach ($collection as $piggyBank) {
$piggyBank->savedSoFar = $piggyBank->currentRelevantRep()->currentamount ?? '0';
$piggyBank->savedSoFar = $piggyRepository->getCurrentAmount($piggyBank);
$piggyBank->percentage = 0 !== bccomp('0', $piggyBank->savedSoFar) ? intval($piggyBank->savedSoFar / $piggyBank->targetamount * 100) : 0;
$piggyBank->leftToSave = bcsub($piggyBank->targetamount, strval($piggyBank->savedSoFar));
$piggyBank->percentage = $piggyBank->percentage > 100 ? 100 : $piggyBank->percentage;
@@ -383,7 +384,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'));
}

View File

@@ -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);

View File

@@ -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'));

View File

@@ -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 = [];

View File

@@ -313,7 +313,7 @@ class ExpenseController extends Controller
$collection->push($expenseAccount);
$revenue = $this->accountRepository->findByName($expenseAccount->name, [AccountType::REVENUE]);
if (!is_null($revenue->id)) {
if (!is_null($revenue)) {
$collection->push($revenue);
}
$combined[$expenseAccount->name] = $collection;
@@ -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])) {

View File

@@ -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',

View File

@@ -31,11 +31,11 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Services\Internal\Update\JournalUpdateService;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Log;
use Preferences;
use Session;
use View;
/**
@@ -43,6 +43,8 @@ use View;
*/
class BulkController extends Controller
{
/** @var JournalRepositoryInterface */
private $repository;
/**
@@ -54,6 +56,7 @@ class BulkController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(JournalRepositoryInterface::class);
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
@@ -77,8 +80,8 @@ class BulkController extends Controller
$messages = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
$sources = $this->repository->getJournalSourceAccounts($journal);
$destinations = $this->repository->getJournalDestinationAccounts($journal);
if ($sources->count() > 1) {
$messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
continue;
@@ -88,13 +91,13 @@ class BulkController extends Controller
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
if (TransactionType::OPENING_BALANCE === $this->repository->getTransactionType($journal)) {
$messages[] = trans('firefly.cannot_edit_opening_balance');
continue;
}
// cannot edit reconciled transactions / journals:
if ($journal->transactions->first()->reconciled) {
if ($this->repository->isJournalReconciled($journal)) {
$messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
@@ -138,11 +141,14 @@ class BulkController extends Controller
*/
public function update(BulkEditJournalRequest $request, JournalRepositoryInterface $repository)
{
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
$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;
$count = 0;
if (is_array($journalIds)) {
foreach ($journalIds as $journalId) {
$journal = $repository->find(intval($journalId));
@@ -152,6 +158,7 @@ class BulkController extends Controller
// 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)
@@ -161,7 +168,7 @@ class BulkController extends Controller
}
if ($ignoreTags === false) {
Log::debug(sprintf('Set tags to %s', $request->string('budget_id')));
$repository->updateTags($journal, explode(',', $request->string('tags')));
$repository->updateTags($journal,['tags' => explode(',', $request->string('tags'))]);
}
// update tags if not told to ignore (and is withdrawal)
}

View File

@@ -43,6 +43,9 @@ class ConvertController extends Controller
/** @var AccountRepositoryInterface */
private $accounts;
/** @var JournalRepositoryInterface */
private $repository;
/**
* ConvertController constructor.
*/
@@ -53,7 +56,8 @@ class ConvertController extends Controller
// some useful repositories:
$this->middleware(
function ($request, $next) {
$this->accounts = app(AccountRepositoryInterface::class);
$this->accounts = app(AccountRepositoryInterface::class);
$this->repository = app(JournalRepositoryInterface::class);
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-exchange');
@@ -76,8 +80,7 @@ class ConvertController extends Controller
return $this->redirectToAccount($journal);
}
// @codeCoverageIgnoreEnd
$positiveAmount = $journal->amountPositive();
$positiveAmount = $this->repository->getJournalTotal($journal);
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$sourceType = $journal->transactionType;
$subTitle = trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]);
@@ -98,8 +101,8 @@ class ConvertController extends Controller
}
// get source and destination account:
$sourceAccount = $journal->sourceAccountList()->first();
$destinationAccount = $journal->destinationAccountList()->first();
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
return view(
'transactions.convert',
@@ -183,8 +186,8 @@ class ConvertController extends Controller
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = $journal->sourceAccountList()->first();
$destinationAccount = $journal->destinationAccountList()->first();
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
@@ -196,7 +199,7 @@ class ConvertController extends Controller
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER:
// two
$destination = $accountRepository->find(intval($data['destination_account_asset']));
$destination = $accountRepository->findNull(intval($data['destination_account_asset']));
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL:
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL:
@@ -239,8 +242,8 @@ class ConvertController extends Controller
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = $journal->sourceAccountList()->first();
$destinationAccount = $journal->destinationAccountList()->first();
$sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first();
$destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first();
$sourceType = $journal->transactionType;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
@@ -273,7 +276,7 @@ class ConvertController extends Controller
$source = $destinationAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER:
$source = $accountRepository->find(intval($data['source_account_asset']));
$source = $accountRepository->findNull(intval($data['source_account_asset']));
break;
}

View File

@@ -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
* @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();
@@ -86,19 +93,14 @@ class LinkController extends Controller
}
/**
* @param JournalLinkRequest $request
* @param LinkTypeRepositoryInterface $repository
* @param JournalRepositoryInterface $journalRepository
* @param TransactionJournal $journal
* @param JournalLinkRequest $request
* @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
* @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());
}

View File

@@ -25,8 +25,8 @@ namespace FireflyIII\Http\Controllers\Transaction;
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\Http\Requests\MassEditJournalRequest;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -43,6 +43,9 @@ use View;
*/
class MassController extends Controller
{
/** @var JournalRepositoryInterface */
private $repository;
/**
*
*/
@@ -54,6 +57,7 @@ class MassController extends Controller
function ($request, $next) {
app('view')->share('title', trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat');
$this->repository = app(JournalRepositoryInterface::class);
return $next($request);
}
@@ -76,12 +80,11 @@ class MassController extends Controller
}
/**
* @param MassDeleteJournalRequest $request
* @param JournalRepositoryInterface $repository
* @param MassDeleteJournalRequest $request
*
* @return mixed
*/
public function destroy(MassDeleteJournalRequest $request, JournalRepositoryInterface $repository)
public function destroy(MassDeleteJournalRequest $request)
{
$ids = $request->get('confirm_mass_delete');
$set = new Collection;
@@ -89,7 +92,7 @@ class MassController extends Controller
/** @var int $journalId */
foreach ($ids as $journalId) {
/** @var TransactionJournal $journal */
$journal = $repository->find(intval($journalId));
$journal = $this->repository->find(intval($journalId));
if (null !== $journal->id && intval($journalId) === $journal->id) {
$set->push($journal);
}
@@ -100,7 +103,7 @@ class MassController extends Controller
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$repository->delete($journal);
$this->repository->delete($journal);
++$count;
}
@@ -112,6 +115,8 @@ class MassController extends Controller
}
/**
* TODO this code is a mess.
*
* @param Collection $journals
*
* @return View
@@ -134,8 +139,8 @@ class MassController extends Controller
$messages = [];
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
$sources = $this->repository->getJournalSourceAccounts($journal);
$destinations = $this->repository->getJournalDestinationAccounts($journal);
if ($sources->count() > 1) {
$messages[] = trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]);
continue;
@@ -145,13 +150,13 @@ class MassController extends Controller
$messages[] = trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) {
if (TransactionType::OPENING_BALANCE === $this->repository->getTransactionType($journal)) {
$messages[] = trans('firefly.cannot_edit_opening_balance');
continue;
}
// cannot edit reconciled transactions / journals:
if ($journal->transactions->first()->reconciled) {
if ($this->repository->isJournalReconciled($journal)) {
$messages[] = trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]);
continue;
}
@@ -169,11 +174,11 @@ class MassController extends Controller
// collect some useful meta data for the mass edit:
$filtered->each(
function (TransactionJournal $journal) {
$transaction = $journal->positiveTransaction();
$transaction = $this->repository->getFirstPosTransaction($journal);
$currency = $transaction->transactionCurrency;
$journal->amount = floatval($transaction->amount);
$sources = $journal->sourceAccountList();
$destinations = $journal->destinationAccountList();
$sources = $this->repository->getJournalSourceAccounts($journal);
$destinations = $this->repository->getJournalDestinationAccounts($journal);
$journal->transaction_count = $journal->transactions()->count();
$journal->currency_symbol = $currency->symbol;
$journal->transaction_type_type = $journal->transactionType->type;
@@ -202,6 +207,8 @@ class MassController extends Controller
}
/**
* TODO this cannot work with new update service.
*
* @param MassEditJournalRequest $request
* @param JournalRepositoryInterface $repository
*
@@ -216,11 +223,12 @@ class MassController extends Controller
$journal = $repository->find(intval($journalId));
if (!is_null($journal)) {
// get optional fields:
$what = strtolower($journal->transactionTypeStr());
$sourceAccountId = $request->get('source_account_id')[$journal->id] ?? 0;
$sourceAccountName = $request->get('source_account_name')[$journal->id] ?? '';
$destAccountId = $request->get('destination_account_id')[$journal->id] ?? 0;
$destAccountName = $request->get('destination_account_name')[$journal->id] ?? '';
$what = strtolower($this->repository->getTransactionType($journal));
$sourceAccountId = $request->get('source_account_id')[$journal->id] ?? null;
$currencyId = $request->get('transaction_currency_id')[$journal->id] ?? 1;
$sourceAccountName = $request->get('source_account_name')[$journal->id] ?? null;
$destAccountId = $request->get('destination_account_id')[$journal->id] ?? null;
$destAccountName = $request->get('destination_account_name')[$journal->id] ?? null;
$budgetId = $request->get('budget_id')[$journal->id] ?? 0;
$category = $request->get('category')[$journal->id];
$tags = $journal->tags->pluck('tag')->toArray();
@@ -228,29 +236,47 @@ class MassController extends Controller
$foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null;
$foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ?
intval($request->get('foreign_currency_id')[$journal->id]) : null;
$notes = $repository->getNoteText($journal);
// build data array
$data = [
'id' => $journal->id,
'what' => $what,
'description' => $request->get('description')[$journal->id],
'source_account_id' => intval($sourceAccountId),
'source_account_name' => $sourceAccountName,
'destination_account_id' => intval($destAccountId),
'destination_account_name' => $destAccountName,
'amount' => $foreignAmount,
'native_amount' => $amount,
'source_amount' => $amount,
'date' => new Carbon($request->get('date')[$journal->id]),
'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date,
'process_date' => $journal->process_date,
'budget_id' => intval($budgetId),
'currency_id' => $foreignCurrencyId,
'foreign_amount' => $foreignAmount,
'destination_amount' => $foreignAmount,
'category' => $category,
'tags' => $tags,
'id' => $journal->id,
'what' => $what,
'description' => $request->get('description')[$journal->id],
'date' => new Carbon($request->get('date')[$journal->id]),
'bill_id' => null,
'bill_name' => null,
'notes' => $notes,
'transactions' => [[
'category_id' => null,
'category_name' => $category,
'budget_id' => intval($budgetId),
'budget_name' => null,
'source_id' => intval($sourceAccountId),
'source_name' => $sourceAccountName,
'destination_id' => intval($destAccountId),
'destination_name' => $destAccountName,
'amount' => $amount,
'identifier' => 0,
'reconciled' => false,
'currency_id' => intval($currencyId),
'currency_code' => null,
'description' => null,
'foreign_amount' => null,
'foreign_currency_id' => $foreignCurrencyId,
'foreign_currency_code' => null,
//'native_amount' => $amount,
//'source_amount' => $amount,
//'foreign_amount' => $foreignAmount,
//'destination_amount' => $foreignAmount,
//'amount' => $foreignAmount,
]],
'currency_id' => $foreignCurrencyId,
'tags' => $tags,
'interest_date' => $journal->interest_date,
'book_date' => $journal->book_date,
'process_date' => $journal->process_date,
];
// call repository update function.
$repository->update($journal, $data);

View File

@@ -104,13 +104,13 @@ class SingleController extends Controller
*/
public function cloneTransaction(TransactionJournal $journal)
{
$source = $journal->sourceAccountList()->first();
$destination = $journal->destinationAccountList()->first();
$budget = $journal->budgets()->first();
$budgetId = null === $budget ? 0 : $budget->id;
$category = $journal->categories()->first();
$categoryName = null === $category ? '' : $category->name;
$tags = join(',', $journal->tags()->get()->pluck('tag')->toArray());
$source = $this->repository->getJournalSourceAccounts($journal)->first();
$destination = $this->repository->getJournalDestinationAccounts($journal)->first();
$budgetId = $this->repository->getJournalBudgetId($journal);
$categoryName = $this->repository->getJournalCategoryName($journal);
$tags = join(',', $this->repository->getTags($journal));
// todo less direct database access. Use collector?
/** @var Transaction $transaction */
$transaction = $journal->transactions()->first();
$amount = app('steam')->positive($transaction->amount);
@@ -171,6 +171,14 @@ class SingleController extends Controller
$subTitle = trans('form.add_new_' . $what);
$subTitleIcon = 'fa-plus';
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$source = intval($request->get('source'));
if (($what === 'withdrawal' || $what === 'transfer') && $source > 0) {
$preFilled['source_account_id'] = $source;
}
if ($what === 'deposit' && $source > 0) {
$preFilled['destination_account_id'] = $source;
}
Session::put('preFilled', $preFilled);
@@ -230,7 +238,7 @@ class SingleController extends Controller
$type = $transactionJournal->transactionTypeStr();
Session::flash('success', strval(trans('firefly.deleted_' . strtolower($type), ['description' => $transactionJournal->description])));
$this->repository->delete($transactionJournal);
$this->repository->destroy($transactionJournal);
Preferences::mark();
@@ -242,53 +250,56 @@ class SingleController extends Controller
*
* @return mixed
*/
public function edit(TransactionJournal $journal)
public function edit(TransactionJournal $journal, JournalRepositoryInterface $repository)
{
// @codeCoverageIgnoreStart
if ($this->isOpeningBalance($journal)) {
$transactionType = $repository->getTransactionType($journal);
// redirect to account:
if ($transactionType === TransactionType::OPENING_BALANCE) {
return $this->redirectToAccount($journal);
}
// @codeCoverageIgnoreEnd
// redirect to reconcile edit:
if ($transactionType === TransactionType::RECONCILIATION) {
return redirect(route('accounts.reconcile.edit', [$journal->id]));
}
// redirect to split edit:
if ($this->isSplitJournal($journal)) {
return redirect(route('transactions.split.edit', [$journal->id]));
}
$what = strtolower($journal->transactionTypeStr());
$what = strtolower($transactionType);
$assetAccounts = $this->groupedAccountList();
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets());
if (TransactionType::RECONCILIATION === $journal->transactionType->type) {
return redirect(route('accounts.reconcile.edit', [$journal->id]));
}
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
// journal related code
$sourceAccounts = $journal->sourceAccountList();
$destinationAccounts = $journal->destinationAccountList();
$sourceAccounts = $repository->getJournalSourceAccounts($journal);
$destinationAccounts = $repository->getJournalDestinationAccounts($journal);
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$pTransaction = $journal->positiveTransaction();
$pTransaction = $repository->getFirstPosTransaction($journal);
$foreignCurrency = null !== $pTransaction->foreignCurrency ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency;
$preFilled = [
'date' => $journal->dateAsString(),
'interest_date' => $journal->dateAsString('interest_date'),
'book_date' => $journal->dateAsString('book_date'),
'process_date' => $journal->dateAsString('process_date'),
'category' => $journal->categoryAsString(),
'budget_id' => $journal->budgetId(),
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString()
'interest_date' => $repository->getJournalDate($journal, 'interest_date'),
'book_date' => $repository->getJournalDate($journal, 'book_date'),
'process_date' => $repository->getJournalDate($journal, 'process_date'),
'category' => $repository->getJournalCategoryName($journal),
'budget_id' => $repository->getJournalBudgetId($journal),
'tags' => join(',', $repository->getTags($journal)),
'source_account_id' => $sourceAccounts->first()->id,
'source_account_name' => $sourceAccounts->first()->edit_name,
'destination_account_id' => $destinationAccounts->first()->id,
'destination_account_name' => $destinationAccounts->first()->edit_name,
// new custom fields:
'due_date' => $journal->dateAsString('due_date'),
'payment_date' => $journal->dateAsString('payment_date'),
'invoice_date' => $journal->dateAsString('invoice_date'),
'interal_reference' => $journal->getMeta('internal_reference'),
'notes' => '',
'due_date' => $repository->getJournalDate($journal, 'due_date'),
'payment_date' => $repository->getJournalDate($journal, 'payment_date'),
'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'),
'interal_reference' => $repository->getMetaField($journal, 'internal_reference'),
'notes' => $repository->getNoteText($journal),
// amount fields
'amount' => $pTransaction->amount,
@@ -301,11 +312,6 @@ class SingleController extends Controller
'foreign_currency' => $foreignCurrency,
'destination_currency' => $foreignCurrency,
];
/** @var Note $note */
$note = $this->repository->getNote($journal);
if (null !== $note) {
$preFilled['notes'] = $note->text;
}
// amounts for withdrawals and deposits:
// amount, native_amount, source_amount, destination_amount
@@ -340,6 +346,8 @@ class SingleController extends Controller
$createAnother = 1 === intval($request->get('create_another'));
$data = $request->getJournalData();
$journal = $repository->store($data);
if (null === $journal->id) {
// error!
Log::error('Could not store transaction journal: ', $journal->getErrors()->toArray());
@@ -416,7 +424,7 @@ class SingleController extends Controller
event(new UpdatedTransactionJournal($journal));
// update, get events by date and sort DESC
$type = strtolower($journal->transactionTypeStr());
$type = strtolower($this->repository->getTransactionType($journal));
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => $data['description']])));
Preferences::mark();

View File

@@ -29,7 +29,6 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\SplitJournalFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
@@ -37,7 +36,6 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Http\Request;
use Log;
use Preferences;
use Session;
use Steam;
@@ -97,7 +95,7 @@ class SplitController extends Controller
public function edit(Request $request, TransactionJournal $journal)
{
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal);
return $this->redirectToAccount($journal); // @codeCoverageIgnore
}
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
@@ -125,20 +123,11 @@ class SplitController extends Controller
Session::forget('transactions.edit-split.fromUpdate');
return view(
'transactions.split.edit',
compact(
'subTitleIcon',
'currencies',
'optionalFields',
'preFilled',
'subTitle',
'uploadSize',
'assetAccounts',
'budgets',
'journal',
'accountArray',
'previous'
)
'transactions.split.edit', compact(
'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'assetAccounts', 'budgets',
'journal', 'accountArray',
'previous'
)
);
}
@@ -151,10 +140,11 @@ class SplitController extends Controller
public function update(SplitJournalFormRequest $request, TransactionJournal $journal)
{
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal);
return $this->redirectToAccount($journal); // @codeCoverageIgnore
}
$data = $this->arrayFromInput($request);
$journal = $this->repository->updateSplitJournal($journal, $data);
$data = $request->getAll();
$journal = $this->repository->update($journal, $data);
/** @var array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
// save attachments:
@@ -168,8 +158,8 @@ class SplitController extends Controller
}
// @codeCoverageIgnoreEnd
$type = strtolower($journal->transactionTypeStr());
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => $data['journal_description']])));
$type = strtolower($this->repository->getTransactionType($journal));
Session::flash('success', strval(trans('firefly.updated_' . $type, ['description' => $data['description']])));
Preferences::mark();
// @codeCoverageIgnoreStart
@@ -185,39 +175,6 @@ class SplitController extends Controller
return redirect($this->getPreviousUri('transactions.edit-split.uri'));
}
/**
* @param SplitJournalFormRequest $request
*
* @return array
*/
private function arrayFromInput(SplitJournalFormRequest $request): array
{
$tags = null === $request->get('tags') ? '' : $request->get('tags');
$array = [
'journal_description' => $request->get('journal_description'),
'journal_source_account_id' => $request->get('journal_source_account_id'),
'journal_source_account_name' => $request->get('journal_source_account_name'),
'journal_destination_account_id' => $request->get('journal_destination_account_id'),
'what' => $request->get('what'),
'date' => $request->get('date'),
// all custom fields:
'interest_date' => $request->get('interest_date'),
'book_date' => $request->get('book_date'),
'process_date' => $request->get('process_date'),
'due_date' => $request->get('due_date'),
'payment_date' => $request->get('payment_date'),
'invoice_date' => $request->get('invoice_date'),
'internal_reference' => $request->get('internal_reference'),
'notes' => $request->get('notes'),
'tags' => explode(',', $tags),
// transactions.
'transactions' => $this->getTransactionDataFromRequest($request),
];
return $array;
}
/**
* @param SplitJournalFormRequest|Request $request
* @param TransactionJournal $journal
@@ -226,36 +183,29 @@ class SplitController extends Controller
*/
private function arrayFromJournal(Request $request, TransactionJournal $journal): array
{
$sourceAccounts = $journal->sourceAccountList();
$destinationAccounts = $journal->destinationAccountList();
$notes = '';
/** @var Note $note */
$note = $this->repository->getNote($journal);
if (null !== $note) {
$notes = $note->text;
}
$array = [
$sourceAccounts = $this->repository->getJournalSourceAccounts($journal);
$destinationAccounts = $this->repository->getJournalDestinationAccounts($journal);
$array = [
'journal_description' => $request->old('journal_description', $journal->description),
'journal_amount' => $journal->amountPositive(),
'journal_amount' => $this->repository->getJournalTotal($journal),
'sourceAccounts' => $sourceAccounts,
'journal_source_account_id' => $request->old('journal_source_account_id', $sourceAccounts->first()->id),
'journal_source_account_name' => $request->old('journal_source_account_name', $sourceAccounts->first()->name),
'journal_destination_account_id' => $request->old('journal_destination_account_id', $destinationAccounts->first()->id),
'destinationAccounts' => $destinationAccounts,
'what' => strtolower($journal->transactionTypeStr()),
'date' => $request->old('date', $journal->date->format('Y-m-d')),
'what' => strtolower($this->repository->getTransactionType($journal)),
'date' => $request->old('date', $this->repository->getJournalDate($journal, null)),
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
// all custom fields:
'interest_date' => $request->old('interest_date', $journal->getMeta('interest_date')),
'book_date' => $request->old('book_date', $journal->getMeta('book_date')),
'process_date' => $request->old('process_date', $journal->getMeta('process_date')),
'due_date' => $request->old('due_date', $journal->getMeta('due_date')),
'payment_date' => $request->old('payment_date', $journal->getMeta('payment_date')),
'invoice_date' => $request->old('invoice_date', $journal->getMeta('invoice_date')),
'internal_reference' => $request->old('internal_reference', $journal->getMeta('internal_reference')),
'notes' => $request->old('notes', $notes),
'interest_date' => $request->old('interest_date', $this->repository->getMetaField($journal, 'interest_date')),
'book_date' => $request->old('book_date', $this->repository->getMetaField($journal, 'book_date')),
'process_date' => $request->old('process_date', $this->repository->getMetaField($journal, 'process_date')),
'due_date' => $request->old('due_date', $this->repository->getMetaField($journal, 'due_date')),
'payment_date' => $request->old('payment_date', $this->repository->getMetaField($journal, 'payment_date')),
'invoice_date' => $request->old('invoice_date', $this->repository->getMetaField($journal, 'invoice_date')),
'internal_reference' => $request->old('internal_reference', $this->repository->getMetaField($journal, 'internal_reference')),
'notes' => $request->old('notes', $this->repository->getNoteText($journal)),
// transactions.
'transactions' => $this->getTransactionDataFromJournal($journal),
@@ -295,12 +245,14 @@ class SplitController extends Controller
'foreign_currency_code' => $transaction['foreign_currency_code'],
'foreign_currency_symbol' => $transaction['foreign_currency_symbol'],
];
// set initial category and/or budget:
if (1 === count($transactions) && 0 === $index) {
$set['budget_id'] = $journal->budgetId();
$set['category'] = $journal->categoryAsString();
if ($set['budget_id'] === 0) {
$set['budget_id'] = $this->repository->getJournalBudgetId($journal);
}
if (strlen($set['category']) === 0) {
$set['category'] = $this->repository->getJournalCategoryName($journal);
}
$return[] = $set;
}
@@ -308,35 +260,6 @@ class SplitController extends Controller
return $return;
}
/**
* @param SplitJournalFormRequest|Request $request
*
* @return array
*/
private function getTransactionDataFromRequest(SplitJournalFormRequest $request): array
{
$return = [];
$transactions = $request->get('transactions');
foreach ($transactions as $transaction) {
$return[] = [
'description' => $transaction['description'],
'source_account_id' => $transaction['source_account_id'] ?? 0,
'source_account_name' => $transaction['source_account_name'] ?? '',
'destination_account_id' => $transaction['destination_account_id'] ?? 0,
'destination_account_name' => $transaction['destination_account_name'] ?? '',
'amount' => round($transaction['amount'] ?? 0, 12),
'foreign_amount' => !isset($transaction['foreign_amount']) ? null : round($transaction['foreign_amount'] ?? 0, 12),
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
'category' => $transaction['category'] ?? '',
'transaction_currency_id' => intval($transaction['transaction_currency_id']),
'foreign_currency_id' => $transaction['foreign_currency_id'] ?? null,
];
}
Log::debug(sprintf('Found %d splits in request data.', count($return)));
return $return;
}
/**
* @param $array
* @param $old

View File

@@ -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])) {

View File

@@ -44,6 +44,7 @@ use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
/**
* @codeCoverageIgnore
@@ -84,7 +85,7 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
CreateFreshApiToken::class,
],
// MUST NOT be logged in. Does not care about 2FA or confirmation.
@@ -95,7 +96,6 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
Binder::class,
RedirectIfAuthenticated::class,
],
@@ -109,7 +109,6 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
Binder::class,
Authenticate::class,
RedirectIfTwoFactorAuthenticated::class,
@@ -125,7 +124,6 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
Binder::class,
Authenticate::class,
],
@@ -141,11 +139,11 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
Authenticate::class,
AuthenticateTwoFactor::class,
Range::class,
Binder::class,
CreateFreshApiToken::class,
],
// MUST be logged in
// MUST have 2fa
@@ -159,12 +157,12 @@ class Kernel extends HttpKernel
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
//SubstituteBindings::class,
Authenticate::class,
AuthenticateTwoFactor::class,
IsAdmin::class,
Range::class,
Binder::class,
CreateFreshApiToken::class,
],
'api' => [

View File

@@ -1,7 +1,7 @@
<?php
/**
* Authenticate.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
@@ -18,50 +18,94 @@
* 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\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Session;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
/**
* Class Authenticate.
* Class Authenticate
*/
class Authenticate
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
*
* @return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
*
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle(Request $request, Closure $next, $guard = null)
public function handle($request, Closure $next, ...$guards)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('login');
}
if (1 === intval(auth()->user()->blocked)) {
$message = strval(trans('firefly.block_account_logout'));
if ('email_changed' === auth()->user()->blocked_code) {
$message = strval(trans('firefly.email_changed_logout'));
}
Session::flash('logoutMessage', $message);
Auth::guard($guard)->logout();
return redirect()->guest('login');
}
$this->authenticate($guards);
return $next($request);
}
/**
* Determine if the user is logged in to any of the given guards.
*
* @param array $guards
*
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
// go for default guard:
if ($this->auth->check()) {
// do an extra check on user object.
$user = $this->auth->authenticate();
if (1 === intval($user->blocked)) {
$message = strval(trans('firefly.block_account_logout'));
if ('email_changed' === $user->blocked_code) {
$message = strval(trans('firefly.email_changed_logout'));
}
app('session')->flash('logoutMessage', $message);
$this->auth->logout();
throw new AuthenticationException('Blocked account.', $guards);
}
}
return $this->auth->authenticate();
}
// @codeCoverageIgnoreStart
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
// @codeCoverageIgnoreEnd
}
}

View File

@@ -23,11 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Contracts\Auth\Factory as Auth;
use Log;
use Preferences;
use Session;
/**
* Class AuthenticateTwoFactor.
@@ -35,36 +32,41 @@ use Session;
class AuthenticateTwoFactor
{
/**
* Handle an incoming request.
* The authentication factory instance.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
*
* @return mixed
* @var \Illuminate\Contracts\Auth\Factory
*/
public function handle(Request $request, Closure $next, $guard = null)
protected $auth;
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
*
* @return void
*/
public function __construct(Auth $auth)
{
// do the usual auth, again:
if (Auth::guard($guard)->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
}
$this->auth = $auth;
}
/**
* @param $request
* @param Closure $next
* @param array ...$guards
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed
* @throws \Illuminate\Container\EntryNotFoundException
*/
public function handle($request, Closure $next, ...$guards)
{
if ($this->auth->guest()) {
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');
$is2faEnabled = app('preferences')->get('twoFactorAuthEnabled', false)->data;
$has2faSecret = null !== app('preferences')->get('twoFactorAuthSecret');
$is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated');
if ($is2faEnabled && $has2faSecret && !$is2faAuthed) {
Log::debug('Does not seem to be 2 factor authed, redirect.');
@@ -74,4 +76,5 @@ class AuthenticateTwoFactor
return $next($request);
}
}

View File

@@ -1,7 +1,7 @@
<?php
/**
* Binder.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
@@ -24,13 +24,20 @@ namespace FireflyIII\Http\Middleware;
use Closure;
use FireflyIII\Support\Domain;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Routing\Route;
/**
* Class Binder.
* Class HttpBinder
*/
class Binder
{
/**
* The authentication factory instance.
*
* @var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* @var array
*/
@@ -38,21 +45,27 @@ class Binder
/**
* Binder constructor.
*
* @param \Illuminate\Contracts\Auth\Factory $auth
*/
public function __construct()
public function __construct(Auth $auth)
{
$this->binders = Domain::getBindables();
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
*
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle(Request $request, Closure $next)
public function handle($request, Closure $next, ...$guards)
{
foreach ($request->route()->parameters() as $key => $value) {
if (isset($this->binders[$key])) {
@@ -71,7 +84,7 @@ class Binder
*
* @return mixed
*/
private function performBinding($key, $value, $route)
private function performBinding(string $key, string $value, Route $route)
{
$class = $this->binders[$key];

Some files were not shown because too many files have changed in this diff Show More