Compare commits

..

198 Commits

Author SHA1 Message Date
James Cole
67d29b8416 Merge branch 'release/v6.0.23' 2023-09-03 18:09:17 +02:00
James Cole
f62e93b487 Add spans to stop parsing 2023-09-03 18:01:12 +02:00
James Cole
a7cc70b975 Remove whitespace 2023-09-03 18:00:07 +02:00
James Cole
60f0d8074a Meta files for new release. 2023-09-03 17:53:22 +02:00
James Cole
ed1fdf9382 Update files for new release 2023-09-03 17:38:54 +02:00
James Cole
9ea3c4224e Remove extra slashes 2023-09-02 10:47:10 +02:00
James Cole
71325de44e Merge branch 'release/v6.0.22' 2023-09-01 19:41:15 +02:00
James Cole
cdb041e647 Merge tag 'v6.0.22' into develop
v6.0.22
2023-09-01 19:41:15 +02:00
James Cole
b9d750bf59 Meta data for new release. 2023-09-01 19:40:17 +02:00
James Cole
461249737e Fix #7917, add missing return statement 2023-09-01 15:23:10 +02:00
James Cole
1aeaa8b77d Merge tag 'v6.0.21' into develop
v6.0.21
2023-09-01 05:33:52 +02:00
James Cole
a59d7ccdc2 Merge branch 'release/v6.0.21' 2023-09-01 05:33:50 +02:00
James Cole
2c1ca428db Update meta files for new release. 2023-09-01 05:33:02 +02:00
James Cole
224970f3bd New version 2023-09-01 05:16:58 +02:00
James Cole
742d934ddb Catch missing key. 2023-09-01 04:42:49 +02:00
James Cole
1699513023 Fix test 2023-08-31 19:18:16 +02:00
James Cole
488a8a7e86 Fix test 2023-08-31 19:13:06 +02:00
James Cole
dd571d6221 Fix test 2023-08-31 19:02:38 +02:00
James Cole
4cc5128b4c Use relative urls see what breaks. 2023-08-31 18:45:11 +02:00
James Cole
2636a6557a Less notice events. 2023-08-30 18:03:47 +02:00
James Cole
0bf97ccf22 Expand logging 2023-08-30 15:57:59 +02:00
James Cole
592fc71b4e Change some logging settings. 2023-08-30 15:38:15 +02:00
James Cole
0e7712d3b8 Add more tracing 2023-08-30 11:58:44 +02:00
James Cole
80f21d2a4f Fix test 2023-08-30 07:06:43 +02:00
James Cole
5513ec068e Fix test 2023-08-30 06:53:46 +02:00
James Cole
14a46a6197 Add more info to error message. 2023-08-30 06:40:12 +02:00
James Cole
b93ee5efd8 Sort accounts properly 2023-08-29 18:28:29 +02:00
James Cole
cdd4dc6065 Rebuild frontend 2023-08-29 07:34:41 +02:00
James Cole
0d8b2ae799 Fix https://github.com/firefly-iii/firefly-iii/issues/7910 2023-08-29 05:47:13 +02:00
James Cole
77dc79b638 Merge pull request #7898 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-1.10.32 2023-08-28 05:51:54 +02:00
dependabot[bot]
f48723db40 chore(deps-dev): bump phpstan/phpstan from 1.10.29 to 1.10.32
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.29 to 1.10.32.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.29...1.10.32)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:50:38 +00:00
James Cole
ebf91078c5 Merge pull request #7899 from firefly-iii/dependabot/composer/develop/guzzlehttp/guzzle-7.8.0 2023-08-28 05:50:17 +02:00
James Cole
90d0d85dd6 Merge pull request #7900 from firefly-iii/dependabot/composer/develop/laravel/framework-10.20.0 2023-08-28 05:50:06 +02:00
James Cole
0c5b4f7f64 Merge pull request #7901 from firefly-iii/dependabot/composer/develop/laravel/sanctum-3.2.6 2023-08-28 05:49:57 +02:00
James Cole
9c4bb08ed1 Merge pull request #7902 from firefly-iii/dependabot/composer/develop/spatie/laravel-ignition-2.3.0 2023-08-28 05:49:46 +02:00
James Cole
1d3bbde4b0 Merge pull request #7903 from firefly-iii/dependabot/npm_and_yarn/develop/axios-1.5.0 2023-08-28 05:49:36 +02:00
James Cole
b6faee033d Merge pull request #7904 from firefly-iii/dependabot/npm_and_yarn/develop/alpinejs-3.13.0 2023-08-28 05:49:27 +02:00
James Cole
05e0f88d11 Merge pull request #7905 from firefly-iii/dependabot/npm_and_yarn/develop/chart.js-4.4.0 2023-08-28 05:49:17 +02:00
dependabot[bot]
ae9a151140 chore(deps): bump chart.js from 4.3.3 to 4.4.0
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.3.3 to 4.4.0.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.3.3...v4.4.0)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:25:21 +00:00
dependabot[bot]
da633e3c62 chore(deps): bump alpinejs from 3.12.3 to 3.13.0
Bumps [alpinejs](https://github.com/alpinejs/alpine/tree/HEAD/packages/alpinejs) from 3.12.3 to 3.13.0.
- [Release notes](https://github.com/alpinejs/alpine/releases)
- [Commits](https://github.com/alpinejs/alpine/commits/v3.13.0/packages/alpinejs)

---
updated-dependencies:
- dependency-name: alpinejs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:25:10 +00:00
dependabot[bot]
8cb384d3cf chore(deps-dev): bump axios from 1.4.0 to 1.5.0
Bumps [axios](https://github.com/axios/axios) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:24:59 +00:00
dependabot[bot]
de2a34c3ec chore(deps): bump spatie/laravel-ignition from 2.2.0 to 2.3.0
Bumps [spatie/laravel-ignition](https://github.com/spatie/laravel-ignition) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/spatie/laravel-ignition/releases)
- [Changelog](https://github.com/spatie/laravel-ignition/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spatie/laravel-ignition/compare/2.2.0...2.3.0)

---
updated-dependencies:
- dependency-name: spatie/laravel-ignition
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:06:43 +00:00
dependabot[bot]
b7c65446a8 chore(deps): bump laravel/sanctum from 3.2.5 to 3.2.6
Bumps [laravel/sanctum](https://github.com/laravel/sanctum) from 3.2.5 to 3.2.6.
- [Release notes](https://github.com/laravel/sanctum/releases)
- [Changelog](https://github.com/laravel/sanctum/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/sanctum/compare/v3.2.5...v3.2.6)

---
updated-dependencies:
- dependency-name: laravel/sanctum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:06:31 +00:00
dependabot[bot]
bc648b187c chore(deps): bump laravel/framework from 10.19.0 to 10.20.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 10.19.0 to 10.20.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v10.19.0...v10.20.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:06:19 +00:00
dependabot[bot]
4de0828b5d chore(deps): bump guzzlehttp/guzzle from 7.7.0 to 7.8.0
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.7.0 to 7.8.0.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/7.8/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.7.0...7.8.0)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 03:06:01 +00:00
James Cole
b2364d26ec Various color fixes. 2023-08-27 18:45:06 +02:00
James Cole
83d94cb792 Expand cache 2023-08-27 07:45:09 +02:00
James Cole
66cc3f48bc Mild code cleanup. 2023-08-26 18:42:18 +02:00
James Cole
63297c43b7 Possible fix for https://github.com/firefly-iii/firefly-iii/issues/7891 2023-08-26 18:40:23 +02:00
James Cole
5b0637558f Better error for invalid date 2023-08-24 05:53:17 +02:00
James Cole
e9a8e104be Change URLs to relative URLs 2023-08-24 05:47:28 +02:00
James Cole
0f524e7800 Add relative route 2023-08-23 07:05:41 +02:00
James Cole
969e0bccc9 Remove slash 2023-08-23 07:04:47 +02:00
James Cole
ba3e026927 Add base href 2023-08-23 07:02:30 +02:00
James Cole
0c6868d477 Add relative paths to JS 2023-08-23 07:00:09 +02:00
James Cole
7f70cf47ec Fix https://github.com/firefly-iii/firefly-iii/issues/7883 2023-08-22 16:08:19 +02:00
James Cole
fa4492287d Rebuild frontend 2023-08-22 08:28:52 +02:00
James Cole
60809c688e Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2023-08-22 08:25:16 +02:00
James Cole
2404f5299c New code for transaction processing and frontend 2023-08-22 08:25:06 +02:00
James Cole
21a394eaf5 Merge pull request #7873 from firefly-iii/dependabot/composer/develop/doctrine/dbal-3.6.6
chore(deps): bump doctrine/dbal from 3.6.5 to 3.6.6
2023-08-21 05:41:31 +02:00
dependabot[bot]
419d7846c9 chore(deps): bump doctrine/dbal from 3.6.5 to 3.6.6
Bumps [doctrine/dbal](https://github.com/doctrine/dbal) from 3.6.5 to 3.6.6.
- [Release notes](https://github.com/doctrine/dbal/releases)
- [Commits](https://github.com/doctrine/dbal/compare/3.6.5...3.6.6)

---
updated-dependencies:
- dependency-name: doctrine/dbal
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:38:59 +00:00
James Cole
0827accc39 Merge pull request #7872 from firefly-iii/dependabot/npm_and_yarn/develop/sass-1.66.1 2023-08-21 05:38:48 +02:00
James Cole
2e53f7d0b7 Merge pull request #7874 from firefly-iii/dependabot/composer/develop/predis/predis-2.2.1 2023-08-21 05:38:27 +02:00
James Cole
bdfcf8ec95 Merge pull request #7875 from firefly-iii/dependabot/composer/develop/laravel/framework-10.19.0 2023-08-21 05:38:18 +02:00
James Cole
6a20170e00 Merge pull request #7876 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-1.10.29 2023-08-21 05:38:00 +02:00
James Cole
0c974f1ff7 Merge pull request #7877 from firefly-iii/dependabot/composer/develop/ergebnis/phpstan-rules-2.1.0 2023-08-21 05:37:48 +02:00
dependabot[bot]
9c098d45c5 chore(deps-dev): bump ergebnis/phpstan-rules from 2.0.0 to 2.1.0
Bumps [ergebnis/phpstan-rules](https://github.com/ergebnis/phpstan-rules) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/ergebnis/phpstan-rules/releases)
- [Changelog](https://github.com/ergebnis/phpstan-rules/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ergebnis/phpstan-rules/compare/2.0.0...2.1.0)

---
updated-dependencies:
- dependency-name: ergebnis/phpstan-rules
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:25:35 +00:00
dependabot[bot]
cfdf9aa8dc chore(deps-dev): bump phpstan/phpstan from 1.10.28 to 1.10.29
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.28 to 1.10.29.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.28...1.10.29)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:25:31 +00:00
dependabot[bot]
2ea74542e6 chore(deps): bump laravel/framework from 10.18.0 to 10.19.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 10.18.0 to 10.19.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v10.18.0...v10.19.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:25:26 +00:00
dependabot[bot]
7fcaa2b5fb chore(deps): bump predis/predis from 2.2.0 to 2.2.1
Bumps [predis/predis](https://github.com/predis/predis) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/predis/predis/releases)
- [Changelog](https://github.com/predis/predis/blob/v2.x/CHANGELOG.md)
- [Commits](https://github.com/predis/predis/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: predis/predis
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:25:12 +00:00
dependabot[bot]
e9e905e495 chore(deps-dev): bump sass from 1.65.1 to 1.66.1
Bumps [sass](https://github.com/sass/dart-sass) from 1.65.1 to 1.66.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.65.1...1.66.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 03:12:29 +00:00
James Cole
f67ff98d78 More exact search for available budgets. #7853 2023-08-15 18:37:47 +02:00
James Cole
e3c4dde4ff Add cleanup method for issue #7853 and some more debug info. 2023-08-15 17:57:02 +02:00
James Cole
9787561000 Potential solution for #7853 2023-08-15 17:19:50 +02:00
James Cole
cd041b4c75 Add debug info for #7853 2023-08-15 16:22:54 +02:00
James Cole
60ee70c926 Check for rule triggers in #7853 2023-08-15 16:17:54 +02:00
James Cole
00de78b6f1 Also support Discord. 2023-08-15 13:52:30 +02:00
James Cole
33a841b831 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2023-08-15 13:51:51 +02:00
James Cole
df66dcf102 Various UI fixes 2023-08-15 13:51:38 +02:00
James Cole
086a7a0b1e Merge pull request #7848 from firefly-iii/dependabot/npm_and_yarn/develop/laravel-vite-plugin-0.8.0 2023-08-14 06:58:21 +02:00
dependabot[bot]
ec0ba3d212 chore(deps-dev): bump laravel-vite-plugin from 0.7.8 to 0.8.0
Bumps [laravel-vite-plugin](https://github.com/laravel/vite-plugin) from 0.7.8 to 0.8.0.
- [Release notes](https://github.com/laravel/vite-plugin/releases)
- [Changelog](https://github.com/laravel/vite-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/laravel/vite-plugin/compare/v0.7.8...v0.8.0)

---
updated-dependencies:
- dependency-name: laravel-vite-plugin
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 03:58:10 +00:00
James Cole
b0ab06b7eb Add rule error reporting to all rules. 2023-08-13 15:01:12 +02:00
James Cole
965acd6d45 Finetune index. 2023-08-13 14:30:16 +02:00
James Cole
4d156870ef Add nonce to scripts 2023-08-13 07:30:19 +02:00
James Cole
499720df46 Fix math issue in summary controller. 2023-08-13 07:24:51 +02:00
James Cole
610bc108e7 Add some slack notifications and a todo to fix the rest 2023-08-12 20:57:51 +02:00
James Cole
53f1b0218c Merge tag 'v6.0.20' into develop
v6.0.20
2023-08-12 19:56:11 +02:00
James Cole
0a7a099796 Merge branch 'release/v6.0.20' 2023-08-12 19:56:07 +02:00
James Cole
ed90ebc896 Update final meta 2023-08-12 19:55:31 +02:00
James Cole
87fe518d45 Rebuild JS and CSS 2023-08-12 19:41:59 +02:00
James Cole
5cb0891a27 Forgot to add a date range 2023-08-12 19:21:43 +02:00
James Cole
b4bd66bd58 Various fixes. 2023-08-12 18:21:29 +02:00
James Cole
64d95fe845 Add integrity things to JS things. 2023-08-12 18:12:20 +02:00
James Cole
a2d6d7a92c Add nonce. 2023-08-12 18:09:23 +02:00
James Cole
1d8a784586 Update meta files for new release. 2023-08-12 18:01:51 +02:00
James Cole
3c8bfae8ff Update API version 2023-08-12 17:47:13 +02:00
James Cole
f65eccfe4c Fix https://github.com/firefly-iii/firefly-iii/issues/7835 2023-08-12 17:46:56 +02:00
James Cole
7ce6dc9f16 Merge branch 'adminlte4' into develop 2023-08-12 17:44:08 +02:00
James Cole
4a73caf4c3 Rebuild frontend 2023-08-12 17:44:00 +02:00
James Cole
1b9d8dd3c3 Fix #7845 among other things 2023-08-12 17:43:24 +02:00
James Cole
b11bfb0aae update frontend 2023-08-12 17:41:56 +02:00
James Cole
024e16bf4b Cleanup charts 2023-08-12 07:53:11 +02:00
James Cole
daf753d76e Merge branch 'develop' into adminlte4 2023-08-11 19:43:18 +02:00
James Cole
4c90f66578 Minor code cleanup 2023-08-11 19:40:44 +02:00
James Cole
db94f18d46 Clean up migrations 2023-08-11 19:37:52 +02:00
James Cole
a9bd0f551d Make sure all objects also add user group 2023-08-11 19:37:28 +02:00
James Cole
e1ba2d9ad9 New translations 2023-08-11 06:24:50 +02:00
James Cole
939c636a74 Add new action that can switch the source and destination account of a transfer 2023-08-11 06:16:40 +02:00
James Cole
2b829cb645 Code cleanup 2023-08-11 06:05:02 +02:00
James Cole
4bbc898639 Expand bill API and v2 account API 2023-08-11 06:04:03 +02:00
James Cole
27d07d5807 Expand views 2023-08-11 06:03:31 +02:00
James Cole
f010ffefc1 Expand with list of piggy banks 2023-08-10 19:51:36 +02:00
James Cole
fc0fee161e Merge branch 'develop' into adminlte4 2023-08-09 14:14:57 +02:00
James Cole
72c99d3834 New API endpoints + transformer 2023-08-09 14:14:44 +02:00
James Cole
a2984f299b Extend API 2023-08-09 14:14:33 +02:00
James Cole
3925077223 Expand views 2023-08-09 14:13:32 +02:00
James Cole
deb51bd8de Merge branch 'develop' into adminlte4 2023-08-08 16:45:23 +02:00
James Cole
03f39f53d8 Fix nullpointer 2023-08-08 15:55:48 +02:00
James Cole
e72314778c Fix sankey chart 2023-08-08 15:55:26 +02:00
James Cole
ced3e9387a Merge branch 'develop' into adminlte4 2023-08-08 15:04:10 +02:00
James Cole
02c906afe6 Expand API to return transaction groups. 2023-08-08 15:03:58 +02:00
James Cole
7a2d4c9bd2 Log as error, not debug 2023-08-08 14:11:25 +02:00
James Cole
dffddfda18 Expand layout 2023-08-08 14:11:04 +02:00
James Cole
9bb62c865a Merge branch 'develop' into adminlte4 2023-08-07 19:05:41 +02:00
James Cole
a97ebf2b97 Move to chartJS 2023-08-07 19:05:23 +02:00
James Cole
9a62a6c514 Merge pull request #7832 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-deprecation-rules-1.1.4
chore(deps-dev): bump phpstan/phpstan-deprecation-rules from 1.1.3 to 1.1.4
2023-08-07 05:52:09 +02:00
James Cole
04dc162270 Merge pull request #7831 from firefly-iii/dependabot/composer/develop/laravel/framework-10.17.1
chore(deps): bump laravel/framework from 10.16.1 to 10.17.1
2023-08-07 05:51:59 +02:00
James Cole
75d5a50328 Merge pull request #7830 from firefly-iii/dependabot/composer/develop/phpstan/phpstan-1.10.27
chore(deps-dev): bump phpstan/phpstan from 1.10.26 to 1.10.27
2023-08-07 05:51:51 +02:00
James Cole
eadb7d5dcb Merge pull request #7829 from firefly-iii/dependabot/composer/develop/mockery/mockery-1.6.5
chore(deps-dev): bump mockery/mockery from 1.6.4 to 1.6.5
2023-08-07 05:51:42 +02:00
James Cole
8dc07f1148 Merge pull request #7828 from firefly-iii/dependabot/composer/develop/league/csv-9.10.0
chore(deps): bump league/csv from 9.9.0 to 9.10.0
2023-08-07 05:51:32 +02:00
dependabot[bot]
217ce85a9b chore(deps-dev): bump phpstan/phpstan-deprecation-rules
Bumps [phpstan/phpstan-deprecation-rules](https://github.com/phpstan/phpstan-deprecation-rules) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/phpstan/phpstan-deprecation-rules/releases)
- [Commits](https://github.com/phpstan/phpstan-deprecation-rules/compare/1.1.3...1.1.4)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-deprecation-rules
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 03:26:49 +00:00
dependabot[bot]
ef88b25dbd chore(deps): bump laravel/framework from 10.16.1 to 10.17.1
Bumps [laravel/framework](https://github.com/laravel/framework) from 10.16.1 to 10.17.1.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v10.16.1...v10.17.1)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 03:26:44 +00:00
dependabot[bot]
4c2e2a3a5a chore(deps-dev): bump phpstan/phpstan from 1.10.26 to 1.10.27
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.26 to 1.10.27.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.26...1.10.27)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 03:26:29 +00:00
dependabot[bot]
1e8e1c6e51 chore(deps-dev): bump mockery/mockery from 1.6.4 to 1.6.5
Bumps [mockery/mockery](https://github.com/mockery/mockery) from 1.6.4 to 1.6.5.
- [Release notes](https://github.com/mockery/mockery/releases)
- [Changelog](https://github.com/mockery/mockery/blob/1.6.x/CHANGELOG.md)
- [Commits](https://github.com/mockery/mockery/compare/1.6.4...1.6.5)

---
updated-dependencies:
- dependency-name: mockery/mockery
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 03:26:22 +00:00
dependabot[bot]
a92944786c chore(deps): bump league/csv from 9.9.0 to 9.10.0
Bumps [league/csv](https://github.com/thephpleague/csv) from 9.9.0 to 9.10.0.
- [Release notes](https://github.com/thephpleague/csv/releases)
- [Changelog](https://github.com/thephpleague/csv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/csv/compare/9.9.0...9.10.0)

---
updated-dependencies:
- dependency-name: league/csv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 03:26:17 +00:00
James Cole
8ce760a0bf Merge branch 'develop' into adminlte4
# Conflicts:
#	app/Api/V2/Controllers/Chart/CategoryController.php
2023-08-06 19:40:09 +02:00
James Cole
8d8e047d2c New charts 2023-08-06 19:37:51 +02:00
James Cole
06a923db94 Expand view 2023-08-06 19:36:55 +02:00
James Cole
551408b801 Expand layout 2023-08-06 18:33:29 +02:00
James Cole
e1915e365a Merge branch 'develop' into adminlte4
# Conflicts:
#	config/cors.php
#	config/hashing.php
2023-08-06 11:25:20 +02:00
James Cole
e8c4eec536 New API code for boxes. 2023-08-06 11:22:36 +02:00
James Cole
e3f4e75561 Fix boxes api 2023-08-06 11:21:20 +02:00
James Cole
fd640f9698 Merge branch 'develop' into adminlte4 2023-08-06 07:08:33 +02:00
James Cole
ffd8aef35f Expand API with v2 summary endpoint. 2023-08-06 07:04:09 +02:00
James Cole
46412bdc66 Fix #7810 2023-08-06 07:03:39 +02:00
James Cole
d73c3476c8 Expand layout 2023-08-06 07:02:55 +02:00
James Cole
ff737ae05e Clean up views, add top bar 2023-08-05 06:34:28 +02:00
James Cole
171ec0c630 Merge branch 'develop' into adminlte4 2023-08-05 06:19:53 +02:00
James Cole
b4a6c6fcbe Fix https://github.com/firefly-iii/firefly-iii/issues/7818 2023-08-04 19:11:01 +02:00
James Cole
4886084296 Add stuff 2023-08-04 19:10:49 +02:00
James Cole
1a3dfbdef6 Merge branch 'develop' into adminlte4 2023-08-01 19:55:08 +02:00
James Cole
0c087f33c2 Build administration-compatible budget chart. 2023-08-01 19:38:53 +02:00
James Cole
5f9f621fa6 Small chart fixes 2023-08-01 12:28:11 +02:00
James Cole
cd3de7b545 Title fix 2023-08-01 12:27:55 +02:00
James Cole
8aeb513d54 Merge branch 'develop' into adminlte4 2023-08-01 12:11:45 +02:00
James Cole
bfc5c5d154 Fix API version and match API 2023-08-01 11:15:19 +02:00
James Cole
7c9f7f04b7 Chart field name refactor 2023-08-01 10:26:26 +02:00
James Cole
3352c2cf3c JS rebuild 2023-08-01 10:26:09 +02:00
James Cole
e61a433999 Merge branch 'develop' into adminlte4 2023-08-01 09:54:40 +02:00
James Cole
3517452ea1 Expand upgrade command 2023-08-01 09:54:09 +02:00
James Cole
8a1190b9ee Update packages 2023-08-01 09:53:51 +02:00
James Cole
fc78c9a1d6 Merge branch 'develop' into adminlte4
# Conflicts:
#	yarn.lock
2023-08-01 09:47:30 +02:00
James Cole
8d55f7f2e5 Clean up code 2023-08-01 09:47:06 +02:00
James Cole
8162b22d43 Fix chart 2023-08-01 09:42:59 +02:00
James Cole
8504d55f17 First code for new balance chart 2023-08-01 09:27:39 +02:00
James Cole
75ae1bbde1 Show expanded transactions in all account overviews. 2023-08-01 06:24:26 +02:00
James Cole
a20668e91b Fix #7771 2023-07-31 20:25:47 +02:00
James Cole
eb559bbb03 Merge pull request #7803 from zqye/main 2023-07-31 19:11:46 +02:00
zqye
2eae96a895 exchangeRatesCronJob should only be fired when ENABLE_EXTERNAL_RATES is true. 2023-07-31 10:04:16 -07:00
James Cole
4bbf35bca4 Merge pull request #7801 from firefly-iii/dependabot/composer/develop/symfony/http-client-6.3.2 2023-07-31 06:40:19 +02:00
dependabot[bot]
f1b5e45488 chore(deps): bump symfony/http-client from 6.3.1 to 6.3.2
Bumps [symfony/http-client](https://github.com/symfony/http-client) from 6.3.1 to 6.3.2.
- [Release notes](https://github.com/symfony/http-client/releases)
- [Changelog](https://github.com/symfony/http-client/blob/6.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-client/compare/v6.3.1...v6.3.2)

---
updated-dependencies:
- dependency-name: symfony/http-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 04:39:38 +00:00
James Cole
57ed1581ec Merge pull request #7800 from firefly-iii/dependabot/composer/develop/nunomaduro/larastan-2.6.4 2023-07-31 06:39:04 +02:00
James Cole
c668381ab0 Merge pull request #7802 from firefly-iii/dependabot/composer/develop/symfony/mailgun-mailer-6.3.2 2023-07-31 06:38:50 +02:00
dependabot[bot]
44f99991fc chore(deps): bump symfony/mailgun-mailer from 6.3.0 to 6.3.2
Bumps [symfony/mailgun-mailer](https://github.com/symfony/mailgun-mailer) from 6.3.0 to 6.3.2.
- [Release notes](https://github.com/symfony/mailgun-mailer/releases)
- [Changelog](https://github.com/symfony/mailgun-mailer/blob/6.3/CHANGELOG.md)
- [Commits](https://github.com/symfony/mailgun-mailer/compare/v6.3.0...v6.3.2)

---
updated-dependencies:
- dependency-name: symfony/mailgun-mailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 03:42:42 +00:00
dependabot[bot]
696c76102b chore(deps-dev): bump nunomaduro/larastan from 2.6.3 to 2.6.4
Bumps [nunomaduro/larastan](https://github.com/nunomaduro/larastan) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/nunomaduro/larastan/releases)
- [Changelog](https://github.com/nunomaduro/larastan/blob/master/RELEASE.md)
- [Commits](https://github.com/nunomaduro/larastan/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: nunomaduro/larastan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 03:42:28 +00:00
James Cole
fe4e00dc5c Fix #7792 2023-07-29 06:52:21 +02:00
James Cole
1a1b0ee27d Fix https://github.com/firefly-iii/firefly-iii/issues/7787 2023-07-29 06:17:09 +02:00
James Cole
298c4ec654 Merge tag 'v6.0.19' into develop
v6.0.19
2023-07-28 16:17:25 +02:00
James Cole
b2084a94e5 Expand layout 2023-07-26 07:08:00 +02:00
James Cole
ddcb246955 Add options 2023-07-26 07:07:53 +02:00
James Cole
8796168580 Merge branch 'develop' into adminlte4
# Conflicts:
#	yarn.lock
2023-07-25 16:12:58 +02:00
James Cole
a6503fda39 Lots of new code. 2023-07-24 18:58:35 +02:00
James Cole
f1173263b6 Fall back on default layout if no page is present (yet) 2023-07-23 08:33:37 +02:00
James Cole
68da1a7039 Rebuild css and js 2023-07-23 08:31:41 +02:00
James Cole
64414edf58 Rename and move files 2023-07-23 08:28:57 +02:00
James Cole
6b136d2c8c Remove unused assets 2023-07-23 08:26:41 +02:00
James Cole
752a877b91 Move more to v2 2023-07-23 08:26:12 +02:00
James Cole
d11c36e476 Merge branch 'develop' into adminlte4 2023-07-23 08:20:39 +02:00
James Cole
b13232f06b Rename some things from v4 to v2 2023-07-23 08:18:42 +02:00
James Cole
be531d777e Continued work on v4 2023-07-23 07:10:31 +02:00
James Cole
190508fa54 Basic set of stuff working! 2023-07-22 16:42:33 +02:00
James Cole
2c2dddc071 It works! 2023-07-22 07:51:02 +02:00
James Cole
c60858de5c Merge branch 'develop' into adminlte4
# Conflicts:
#	composer.lock
#	yarn.lock
2023-07-21 18:26:19 +02:00
James Cole
e58da3c41d More work on v4 2023-07-15 15:25:04 +02:00
James Cole
ab3dbf9218 Various fixes 2023-07-14 06:04:48 +02:00
James Cole
d943a5ae9b A little further ahead with the app in Alpine 2023-07-12 07:07:06 +02:00
James Cole
449058dad7 Get the basics working 2023-07-11 14:42:57 +02:00
James Cole
b9cf8b3ef2 Initial resources kit. 2023-07-11 11:45:55 +02:00
505 changed files with 35017 additions and 10505 deletions

View File

@@ -79,16 +79,16 @@
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
"version": "3.3.2", "version": "3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/semver.git", "url": "https://github.com/composer/semver.git",
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -138,9 +138,9 @@
"versioning" "versioning"
], ],
"support": { "support": {
"irc": "irc://irc.freenode.org/composer", "irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues", "issues": "https://github.com/composer/semver/issues",
"source": "https://github.com/composer/semver/tree/3.3.2" "source": "https://github.com/composer/semver/tree/3.4.0"
}, },
"funding": [ "funding": [
{ {
@@ -156,7 +156,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-04-01T19:23:25+00:00" "time": "2023-08-31T09:50:34+00:00"
}, },
{ {
"name": "composer/xdebug-handler", "name": "composer/xdebug-handler",
@@ -224,178 +224,23 @@
], ],
"time": "2022-02-25T21:32:43+00:00" "time": "2022-02-25T21:32:43+00:00"
}, },
{
"name": "doctrine/annotations",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/annotations.git",
"reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f",
"reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f",
"shasum": ""
},
"require": {
"doctrine/lexer": "^2 || ^3",
"ext-tokenizer": "*",
"php": "^7.2 || ^8.0",
"psr/cache": "^1 || ^2 || ^3"
},
"require-dev": {
"doctrine/cache": "^2.0",
"doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.8.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"symfony/cache": "^5.4 || ^6",
"vimeo/psalm": "^4.10"
},
"suggest": {
"php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "Docblock Annotations Parser",
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"keywords": [
"annotations",
"docblock",
"parser"
],
"support": {
"issues": "https://github.com/doctrine/annotations/issues",
"source": "https://github.com/doctrine/annotations/tree/2.0.1"
},
"time": "2023-02-02T22:02:53+00:00"
},
{
"name": "doctrine/lexer",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "84a527db05647743d50373e0ec53a152f2cde568"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568",
"reference": "84a527db05647743d50373e0ec53a152f2cde568",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^9.5",
"psalm/plugin-phpunit": "^0.18.3",
"vimeo/psalm": "^5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\Lexer\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"keywords": [
"annotations",
"docblock",
"lexer",
"parser",
"php"
],
"support": {
"issues": "https://github.com/doctrine/lexer/issues",
"source": "https://github.com/doctrine/lexer/tree/3.0.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
"type": "tidelift"
}
],
"time": "2022-12-15T16:57:16+00:00"
},
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.22.0", "version": "v3.25.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "92b019f6c8d79aa26349d0db7671d37440dc0ff3" "reference": "9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/92b019f6c8d79aa26349d0db7671d37440dc0ff3", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d",
"reference": "92b019f6c8d79aa26349d0db7671d37440dc0ff3", "reference": "9025b7d2b6e1d90a63d0ac0905018ce5d03ec88d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer/semver": "^3.3", "composer/semver": "^3.3",
"composer/xdebug-handler": "^3.0.3", "composer/xdebug-handler": "^3.0.3",
"doctrine/annotations": "^2",
"doctrine/lexer": "^2 || ^3",
"ext-json": "*", "ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
@@ -464,7 +309,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.22.0" "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.25.0"
}, },
"funding": [ "funding": [
{ {
@@ -472,56 +317,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-07-16T23:08:06+00:00" "time": "2023-08-31T21:27:18+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for caching libraries",
"keywords": [
"cache",
"psr",
"psr-6"
],
"support": {
"source": "https://github.com/php-fig/cache/tree/3.0.0"
},
"time": "2021-02-03T23:26:27+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",
@@ -745,16 +541,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v6.3.0", "version": "v6.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6",
"reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -815,7 +611,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v6.3.0" "source": "https://github.com/symfony/console/tree/v6.3.4"
}, },
"funding": [ "funding": [
{ {
@@ -831,7 +627,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-29T12:49:39+00:00" "time": "2023-08-16T10:10:12+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@@ -902,16 +698,16 @@
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v6.3.0", "version": "v6.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -962,7 +758,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -978,7 +774,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-04-21T14:41:17+00:00" "time": "2023-07-06T06:56:43+00:00"
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
@@ -1121,16 +917,16 @@
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v6.3.0", "version": "v6.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e",
"reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1165,7 +961,7 @@
"description": "Finds files and directories via an intuitive fluent interface", "description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/finder/tree/v6.3.0" "source": "https://github.com/symfony/finder/tree/v6.3.3"
}, },
"funding": [ "funding": [
{ {
@@ -1181,7 +977,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-04-02T01:25:41+00:00" "time": "2023-07-31T08:31:44+00:00"
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
@@ -1252,16 +1048,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a" "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1276,7 +1072,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1314,7 +1110,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1330,20 +1126,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-grapheme", "name": "symfony/polyfill-intl-grapheme",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "511a08c03c1960e08a883f4cffcacd219b758354" "reference": "875e90aeea2777b6f135677f618529449334a612"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
"reference": "511a08c03c1960e08a883f4cffcacd219b758354", "reference": "875e90aeea2777b6f135677f618529449334a612",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1355,7 +1151,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1395,7 +1191,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1411,20 +1207,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{ {
"name": "symfony/polyfill-intl-normalizer", "name": "symfony/polyfill-intl-normalizer",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1436,7 +1232,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1479,7 +1275,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1495,20 +1291,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" "reference": "42292d99c55abe617799667f454222c54c60e229"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1523,7 +1319,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1562,7 +1358,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1578,20 +1374,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-07-28T09:04:16+00:00"
}, },
{ {
"name": "symfony/polyfill-php80", "name": "symfony/polyfill-php80",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php80.git", "url": "https://github.com/symfony/polyfill-php80.git",
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1600,7 +1396,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1645,7 +1441,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1661,20 +1457,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{ {
"name": "symfony/polyfill-php81", "name": "symfony/polyfill-php81",
"version": "v1.27.0", "version": "v1.28.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-php81.git", "url": "https://github.com/symfony/polyfill-php81.git",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b",
"reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1683,7 +1479,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "1.27-dev" "dev-main": "1.28-dev"
}, },
"thanks": { "thanks": {
"name": "symfony/polyfill", "name": "symfony/polyfill",
@@ -1724,7 +1520,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0"
}, },
"funding": [ "funding": [
{ {
@@ -1740,20 +1536,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-11-03T14:55:06+00:00" "time": "2023-01-26T09:26:14+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v6.3.0", "version": "v6.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54",
"reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1785,7 +1581,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v6.3.0" "source": "https://github.com/symfony/process/tree/v6.3.4"
}, },
"funding": [ "funding": [
{ {
@@ -1801,7 +1597,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-19T08:06:44+00:00" "time": "2023-08-07T10:39:22+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
@@ -1949,16 +1745,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v6.3.0", "version": "v6.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" "reference": "53d1a83225002635bca3482fcbf963001313fb68"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", "reference": "53d1a83225002635bca3482fcbf963001313fb68",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2015,7 +1811,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v6.3.0" "source": "https://github.com/symfony/string/tree/v6.3.2"
}, },
"funding": [ "funding": [
{ {
@@ -2031,7 +1827,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-03-21T21:06:29+00:00" "time": "2023-07-05T08:41:27+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

View File

@@ -45,12 +45,6 @@ TRUSTED_PROXIES=
# Default setting 'stack' will log to 'daily' and to 'stdout' at the same time. # Default setting 'stack' will log to 'daily' and to 'stdout' at the same time.
LOG_CHANNEL=stack LOG_CHANNEL=stack
#
# Used when logging to papertrail:
#
PAPERTRAIL_HOST=
PAPERTRAIL_PORT=
# Log level. You can set this from least severe to most severe: # Log level. You can set this from least severe to most severe:
# debug, info, notice, warning, error, critical, alert, emergency # 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 # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably
@@ -58,8 +52,30 @@ PAPERTRAIL_PORT=
APP_LOG_LEVEL=notice APP_LOG_LEVEL=notice
# Audit log level. # Audit log level.
# Set this to "emergency" if you dont want to store audit logs, leave on info otherwise. # The audit log is used to log notable Firefly III events on a separate channel.
AUDIT_LOG_LEVEL=info # These log entries may contain sensitive financial information.
# The audit log is disabled by default.
#
# To enable it, set AUDIT_LOG_LEVEL to "info"
# To disable it, set AUDIT_LOG_LEVEL to "emergency"
AUDIT_LOG_LEVEL=emergency
#
# If you want, you can redirect the audit logs to another channel.
# Set 'audit_stdout', 'audit_syslog', 'audit_errorlog' to log to the system itself.
# Use audit_daily to log to a rotating file.
# Use audit_papertrail to log to papertrail.
#
# If you do this, the audit logs may be mixed with normal logs because the settings for these channels
# are often the same as the settings for the normal logs.
AUDIT_LOG_CHANNEL=
#
# Used when logging to papertrail:
# Also used when audit logs log to papertrail:
#
PAPERTRAIL_HOST=
PAPERTRAIL_PORT=
# Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III
# For other database types, please see the FAQ: https://docs.firefly-iii.org/firefly-iii/faq/self-hosted/#i-want-to-use-sqlite # For other database types, please see the FAQ: https://docs.firefly-iii.org/firefly-iii/faq/self-hosted/#i-want-to-use-sqlite

17
.gitattributes vendored
View File

@@ -1,8 +1,11 @@
* text=auto * text=auto eol=lf
*.css linguist-vendored
*.scss linguist-vendored *.blade.php diff=html
*.js linguist-vendored *.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore CHANGELOG.md export-ignore
/tests export-ignore .styleci.yml export-ignore
/phpunit.xml export-ignore
/.ci export-ignore

View File

@@ -102,6 +102,7 @@ abstract class Controller extends BaseController
} catch (BadRequestException $e) { } catch (BadRequestException $e) {
Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $field)); Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $field));
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$value = null; $value = null;
} }
$obj = null; $obj = null;
@@ -130,6 +131,7 @@ abstract class Controller extends BaseController
} catch (BadRequestException $e) { } catch (BadRequestException $e) {
Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $integer)); Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $integer));
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$value = null; $value = null;
} }
if (null !== $value) { if (null !== $value) {
@@ -154,6 +156,7 @@ abstract class Controller extends BaseController
} catch (BadRequestException $e) { } catch (BadRequestException $e) {
Log::error('Request field "sort" contains a non-scalar value. Value set to NULL.'); Log::error('Request field "sort" contains a non-scalar value. Value set to NULL.');
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$param = ''; $param = '';
} }
if ('' === $param) { if ('' === $param) {

View File

@@ -78,7 +78,8 @@ class UpdateController extends Controller
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
{ {
Log::debug('Now in update routine for transaction group!'); Log::debug('Now in update routine for transaction group!');
$data = $request->getAll(); $data = $request->getAll();
$transactionGroup = $this->groupRepository->update($transactionGroup, $data); $transactionGroup = $this->groupRepository->update($transactionGroup, $data);
$manager = $this->getManager(); $manager = $this->getManager();

View File

@@ -322,16 +322,11 @@ class BasicController extends Controller
'currency_decimal_places' => $row['currency_decimal_places'], 'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false), 'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'local_icon' => 'money', 'local_icon' => 'money',
'sub_title' => (string)trans( 'sub_title' => app('amount')->formatFlat(
'firefly.box_spend_per_day', $row['currency_symbol'],
[ $row['currency_decimal_places'],
'amount' => app('amount')->formatFlat( $perDay,
$row['currency_symbol'], false
$row['currency_decimal_places'],
$perDay,
false
),
]
), ),
]; ];
} }

View File

@@ -58,7 +58,7 @@ class CronController extends Controller
$return = []; $return = [];
$return['recurring_transactions'] = $this->runRecurring($config['force'], $config['date']); $return['recurring_transactions'] = $this->runRecurring($config['force'], $config['date']);
$return['auto_budgets'] = $this->runAutoBudget($config['force'], $config['date']); $return['auto_budgets'] = $this->runAutoBudget($config['force'], $config['date']);
if (true === config('cer.enabled')) { if (true === config('cer.download_enabled')) {
$return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']); $return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']);
} }
$return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']); $return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']);

View File

@@ -33,7 +33,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use JsonException;
/** /**
* Class AccountController * Class AccountController
@@ -65,12 +64,16 @@ class AccountController extends Controller
/** /**
* Documentation for this endpoint: * Documentation for this endpoint:
* TODO endpoint is not documented. * TODO list of checks
* 1. use dates from ParameterBag
* 2. Request validates dates
* 3. Request includes user_group_id as administration_id
* 4. Endpoint is documented.
* 5. Collector uses administration_id
* *
* @param AutocompleteRequest $request * @param AutocompleteRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
* @throws FireflyException * @throws FireflyException
* @throws FireflyException * @throws FireflyException
*/ */
@@ -79,7 +82,7 @@ class AccountController extends Controller
$data = $request->getData(); $data = $request->getData();
$types = $data['types']; $types = $data['types'];
$query = $data['query']; $query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone')); $date = $this->parameters->get('date') ?? today(config('app.timezone'));
$this->adminRepository->setAdministrationId($data['administration_id']); $this->adminRepository->setAdministrationId($data['administration_id']);
$return = []; $return = [];

View File

@@ -27,11 +27,12 @@ namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller; use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest; use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\ConvertsExchangeRates; use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface; use Psr\Container\NotFoundExceptionInterface;
@@ -41,7 +42,7 @@ use Psr\Container\NotFoundExceptionInterface;
*/ */
class AccountController extends Controller class AccountController extends Controller
{ {
use ConvertsExchangeRates; use CleansChartData;
private AccountRepositoryInterface $repository; private AccountRepositoryInterface $repository;
@@ -54,7 +55,7 @@ class AccountController extends Controller
$this->middleware( $this->middleware(
function ($request, $next) { function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class); $this->repository = app(AccountRepositoryInterface::class);
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request); return $next($request);
} }
); );
@@ -66,39 +67,38 @@ class AccountController extends Controller
* *
* The native currency is the preferred currency on the page /currencies. * The native currency is the preferred currency on the page /currencies.
* *
* If a transaction has foreign currency = native currency, the foreign amount will be used, no conversion
* will take place.
*
* TODO validate and set administration_id from request
*
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws ContainerExceptionInterface * @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface * @throws NotFoundExceptionInterface
* @throws FireflyException
*/ */
public function dashboard(DateRequest $request): JsonResponse public function dashboard(DateRequest $request): JsonResponse
{ {
// parameters for chart:
$dates = $request->getAll();
/** @var Carbon $start */ /** @var Carbon $start */
$start = $dates['start']; $start = $this->parameters->get('start');
/** @var Carbon $end */ /** @var Carbon $end */
$end = $dates['end']; $end = $this->parameters->get('end');
/** @var User $user */ $end->endOfDay();
$user = auth()->user();
// group ID
$administrationId = $user->getAdministrationId();
$this->repository->setAdministrationId($administrationId);
// user's preferences // user's preferences
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray(); $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
$default = app('amount')->getDefaultCurrency(); /** @var TransactionCurrency $default */
$accounts = $this->repository->getAccountsById($frontPage->data); $default = app('amount')->getDefaultCurrency();
$chartData = []; $accounts = $this->repository->getAccountsById($frontPage->data);
$chartData = [];
if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) { if (!(is_array($frontPage->data) && count($frontPage->data) > 0)) {
$frontPage->data = $defaultSet; $frontPage->data = $defaultSet;
$frontPage->save(); $frontPage->save();
} }
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
$currency = $this->repository->getAccountCurrency($account); $currency = $this->repository->getAccountCurrency($account);
@@ -113,15 +113,16 @@ class AccountController extends Controller
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
// the default currency of the user (may be the same!) // the default currency of the user (could be the same!)
'native_id' => $default->id, 'native_id' => (int)$default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => $default->decimal_places, 'native_decimal_places' => (int)$default->decimal_places,
'start_date' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end_date' => $end->toAtomString(), 'end' => $end->toAtomString(),
'period' => '1D',
'entries' => [], 'entries' => [],
'converted_entries' => [], 'native_entries' => [],
]; ];
$currentStart = clone $start; $currentStart = clone $start;
$range = app('steam')->balanceInRange($account, $start, clone $end, $currency); $range = app('steam')->balanceInRange($account, $start, clone $end, $currency);
@@ -138,13 +139,13 @@ class AccountController extends Controller
$previousConverted = $balanceConverted; $previousConverted = $balanceConverted;
$currentStart->addDay(); $currentStart->addDay();
$currentSet['entries'][$label] = $balance; $currentSet['entries'][$label] = $balance;
$currentSet['converted_entries'][$label] = $balanceConverted; $currentSet['native_entries'][$label] = $balanceConverted;
} }
$currentSet = $this->cerChartSet($currentSet);
$chartData[] = $currentSet; $chartData[] = $currentSet;
} }
return response()->json($chartData); return response()->json($this->clean($chartData));
} }
} }

View File

@@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
/*
* BalanceController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Chart\BalanceChartRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class BalanceController
*/
class BalanceController extends Controller
{
use CleansChartData;
private AccountRepositoryInterface $repository;
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
return $next($request);
}
);
}
/**
* The code is practically a duplicate of ReportController::operations.
*
* Currency is up to the account/transactions in question, but conversion to the default
* currency is possible.
*
* If the transaction being processed is already in native currency OR if the
* foreign amount is in the native currency, the amount will not be converted.
*
* TODO validate and set administration_id
* TODO collector set group, not user
*
* @param BalanceChartRequest $request
*
* @return JsonResponse
* @throws FireflyException
*/
public function balance(BalanceChartRequest $request): JsonResponse
{
$params = $request->getAll();
/** @var Carbon $start */
$start = $this->parameters->get('start');
/** @var Carbon $end */
$end = $this->parameters->get('end');
$end->endOfDay();
/** @var Collection $accounts */
$accounts = $params['accounts'];
$preferredRange = $params['period'];
// set some formats, based on input parameters.
$format = app('navigation')->preferredCarbonFormatByPeriod($preferredRange);
// prepare for currency conversion and data collection:
$ids = $accounts->pluck('id')->toArray();
/** @var TransactionCurrency $default */
$default = app('amount')->getDefaultCurrency();
$converter = new ExchangeRateConverter();
$currencies = [(int)$default->id => $default,]; // currency cache
$data = [];
$chartData = [];
// get journals for entire period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withAccountInformation();
$collector->setXorAccounts($accounts);
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER]);
$journals = $collector->getExtractedJournals();
// set array for default currency (even if unused later on)
$defaultCurrencyId = (int)$default->id;
$data[$defaultCurrencyId] = [
'currency_id' => $defaultCurrencyId,
'currency_symbol' => $default->symbol,
'currency_code' => $default->code,
'currency_name' => $default->name,
'currency_decimal_places' => (int)$default->decimal_places,
'native_id' => $defaultCurrencyId,
'native_symbol' => $default->symbol,
'native_code' => $default->code,
'native_name' => $default->name,
'native_decimal_places' => (int)$default->decimal_places,
];
// loop. group by currency and by period.
/** @var array $journal */
foreach ($journals as $journal) {
// format the date according to the period
$period = $journal['date']->format($format);
// collect (and cache) currency information for this journal.
$currencyId = (int)$journal['currency_id'];
$currency = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$currencies[$currencyId] = $currency; // may just re-assign itself, don't mind.
// set the array with monetary info, if it does not exist.
$data[$currencyId] = $data[$currencyId] ?? [
'currency_id' => $currencyId,
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_name' => $journal['currency_name'],
'currency_decimal_places' => $journal['currency_decimal_places'],
// native currency info (could be the same)
'native_id' => (int)$default->id,
'native_code' => $default->code,
'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places,
];
// set the array (in monetary info) with spent/earned in this $period, if it does not exist.
$data[$currencyId][$period] = $data[$currencyId][$period] ?? [
'period' => $period,
'spent' => '0',
'earned' => '0',
'native_spent' => '0',
'native_earned' => '0',
];
// is this journal's amount in- our outgoing?
$key = 'spent';
$amount = app('steam')->negative($journal['amount']);
// deposit = incoming
// transfer or reconcile or opening balance, and these accounts are the destination.
if (
TransactionType::DEPOSIT === $journal['transaction_type_type']
||
(
(
TransactionType::TRANSFER === $journal['transaction_type_type']
|| TransactionType::RECONCILIATION === $journal['transaction_type_type']
|| TransactionType::OPENING_BALANCE === $journal['transaction_type_type']
)
&& in_array($journal['destination_account_id'], $ids, true)
)
) {
$key = 'earned';
$amount = app('steam')->positive($journal['amount']);
}
// get conversion rate
$rate = $converter->getCurrencyRate($currency, $default, $journal['date']);
$amountConverted = bcmul($amount, $rate);
// perhaps transaction already has the foreign amount in the native currency.
if ((int)$journal['foreign_currency_id'] === (int)$default->id) {
$amountConverted = $journal['foreign_amount'] ?? '0';
$amountConverted = 'earned' === $key ? app('steam')->positive($amountConverted) : app('steam')->negative($amountConverted);
}
// add normal entry
$data[$currencyId][$period][$key] = bcadd($data[$currencyId][$period][$key], $amount);
// add converted entry
$convertedKey = sprintf('native_%s', $key);
$data[$currencyId][$period][$convertedKey] = bcadd($data[$currencyId][$period][$convertedKey], $amountConverted);
}
// loop this data, make chart bars for each currency:
/** @var array $currency */
foreach ($data as $currency) {
// income and expense array prepped:
$income = [
'label' => 'earned',
'currency_id' => $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'native_id' => $currency['native_id'],
'native_symbol' => $currency['native_symbol'],
'native_code' => $currency['native_code'],
'native_decimal_places' => $currency['native_decimal_places'],
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'period' => $preferredRange,
'entries' => [],
'native_entries' => [],
];
$expense = [
'label' => 'spent',
'currency_id' => $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'native_id' => $currency['native_id'],
'native_symbol' => $currency['native_symbol'],
'native_code' => $currency['native_code'],
'native_decimal_places' => $currency['native_decimal_places'],
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'period' => $preferredRange,
'entries' => [],
'native_entries' => [],
];
// loop all possible periods between $start and $end, and add them to the correct dataset.
$currentStart = clone $start;
while ($currentStart <= $end) {
$key = $currentStart->format($format);
$label = $currentStart->toAtomString();
// normal entries
$income['entries'][$label] = app('steam')->bcround(($currency[$key]['earned'] ?? '0'), $currency['currency_decimal_places']);
$expense['entries'][$label] = app('steam')->bcround(($currency[$key]['spent'] ?? '0'), $currency['currency_decimal_places']);
// converted entries
$income['native_entries'][$label] = app('steam')->bcround(($currency[$key]['native_earned'] ?? '0'), $currency['native_decimal_places']);
$expense['native_entries'][$label] = app('steam')->bcround(($currency[$key]['native_spent'] ?? '0'), $currency['native_decimal_places']);
// next loop
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
}
$chartData[] = $income;
$chartData[] = $expense;
}
return response()->json($this->clean($chartData));
}
}

View File

@@ -0,0 +1,313 @@
<?php
declare(strict_types=1);
/*
* BudgetController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class BudgetController
*/
class BudgetController extends Controller
{
use CleansChartData;
protected OperationsRepositoryInterface $opsRepository;
private BudgetLimitRepositoryInterface $blRepository;
private array $currencies = [];
private TransactionCurrency $currency;
private BudgetRepositoryInterface $repository;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(BudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->currency = app('amount')->getDefaultCurrency();
return $next($request);
}
);
}
/**
* @param DateRequest $request
*
* TODO see autocomplete/accountcontroller
*
* @return JsonResponse
* @throws FireflyException
*/
public function dashboard(DateRequest $request): JsonResponse
{
// get user.
/** @var User $user */
$user = auth()->user();
// group ID
$administrationId = $user->getAdministrationId();
$this->repository->setAdministrationId($administrationId);
$this->opsRepository->setAdministrationId($administrationId);
$params = $request->getAll();
/** @var Carbon $start */
$start = $params['start'];
/** @var Carbon $end */
$end = $params['end'];
// code from FrontpageChartGenerator, but not in separate class
$budgets = $this->repository->getActiveBudgets();
$data = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
// could return multiple arrays, so merge.
$data = array_merge($data, $this->processBudget($budget, $start, $end));
}
return response()->json($this->clean($data));
}
/**
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @throws FireflyException
*/
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
{
// get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
$rows = [];
// if no limits
if (0 === $limits->count()) {
// return as a single item in an array
$rows = $this->noBudgetLimits($budget, $start, $end);
}
if ($limits->count() > 0) {
$rows = $this->budgetLimits($budget, $limits);
}
// is always an array
$return = [];
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
'currency_id' => $row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_name' => $row['currency_name'],
'currency_decimal_places' => $row['currency_decimal_places'],
'native_id' => $row['native_id'],
'native_code' => $row['native_code'],
'native_name' => $row['native_name'],
'native_decimal_places' => $row['native_decimal_places'],
'period' => null,
'start' => $row['start'],
'end' => $row['end'],
'entries' => [
'spent' => $row['spent'],
'left' => $row['left'],
'overspent' => $row['overspent'],
],
'native_entries' => [
'spent' => $row['native_spent'],
'left' => $row['native_left'],
'overspent' => $row['native_overspent'],
],
];
$return[] = $current;
}
return $return;
}
/**
* When no budget limits are present, the expenses of the whole period are collected and grouped.
* This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
*
* @param Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @throws FireflyException
*/
private function noBudgetLimits(Budget $budget, Carbon $start, Carbon $end): array
{
$budgetId = (int)$budget->id;
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
return $this->processExpenses($budgetId, $spent, $start, $end);
}
/**
* Shared between the "noBudgetLimits" function and "processLimit".
*
* Will take a single set of expenses and return its info.
*
* @param int $budgetId
* @param array $array
*
* @return array
* @throws FireflyException
*/
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array
{
$converter = new ExchangeRateConverter();
$return = [];
/**
* This array contains the expenses in this budget. Grouped per currency.
* The grouping is on the main currency only.
*
* @var int $currencyId
* @var array $block
*/
foreach ($array as $currencyId => $block) {
$this->currencies[$currencyId] = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$return[$currencyId] = $return[$currencyId] ?? [
'currency_id' => $currencyId,
'currency_code' => $block['currency_code'],
'currency_name' => $block['currency_name'],
'currency_symbol' => $block['currency_symbol'],
'currency_decimal_places' => (int)$block['currency_decimal_places'],
'native_id' => (int)$this->currency->id,
'native_code' => $this->currency->code,
'native_name' => $this->currency->name,
'native_symbol' => $this->currency->symbol,
'native_decimal_places' => (int)$this->currency->decimal_places,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'spent' => '0',
'native_spent' => '0',
'left' => '0',
'native_left' => '0',
'overspent' => '0',
'native_overspent' => '0',
];
$currentBudgetArray = $block['budgets'][$budgetId];
//var_dump($return);
/** @var array $journal */
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
// convert the amount to the native currency.
$rate = $converter->getCurrencyRate($this->currencies[$currencyId], $this->currency, $journal['date']);
$convertedAmount = bcmul($journal['amount'], $rate);
if ($journal['foreign_currency_id'] === $this->currency->id) {
$convertedAmount = $journal['foreign_amount'];
}
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $journal['amount']);
$return[$currencyId]['native_spent'] = bcadd($return[$currencyId]['native_spent'], $convertedAmount);
}
}
return $return;
}
/**
* Function that processes each budget limit (per budget).
*
* If you have a budget limit in EUR, only transactions in EUR will be considered.
* If you have a budget limit in GBP, only transactions in GBP will be considered.
*
* If you have a budget limit in EUR, and a transaction in GBP, it will not be considered for the EUR budget limit.
*
* @param Budget $budget
* @param Collection $limits
*
* @return array
* @throws FireflyException
*/
private function budgetLimits(Budget $budget, Collection $limits): array
{
app('log')->debug(sprintf('Now in budgetLimits(#%d)', $budget->id));
$data = [];
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$data = array_merge($data, $this->processLimit($budget, $limit));
}
return $data;
}
/**
* @param Budget $budget
* @param BudgetLimit $limit
*
* @return array
* @throws FireflyException
*/
private function processLimit(Budget $budget, BudgetLimit $limit): array
{
$budgetId = (int)$budget->id;
$end = clone $limit->end_date;
$end->endOfDay();
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
$limitCurrencyId = (int)$limit->transaction_currency_id;
$limitCurrency = $limit->transactionCurrency;
$converter = new ExchangeRateConverter();
$filtered = [];
$rate = $converter->getCurrencyRate($limitCurrency, $this->currency, $limit->start_date);
$convertedLimitAmount = bcmul($limit->amount, $rate);
/** @var array $entry */
foreach ($spent as $currencyId => $entry) {
// only spent the entry where the entry's currency matches the budget limit's currency
// so $filtered will only have 1 or 0 entries
if ($entry['currency_id'] === $limitCurrencyId) {
$filtered[$currencyId] = $entry;
}
}
$result = $this->processExpenses($budgetId, $filtered, $limit->start_date, $end);
if (1 === count($result)) {
$compare = bccomp((string)$limit->amount, app('steam')->positive($result[$limitCurrencyId]['spent']));
if (1 === $compare) {
// convert this amount into the native currency:
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, $result[$limitCurrencyId]['spent']);
$result[$limitCurrencyId]['native_left'] = bcadd($convertedLimitAmount, $result[$limitCurrencyId]['native_spent']);
}
if ($compare <= 0) {
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, $result[$limitCurrencyId]['spent']));
$result[$limitCurrencyId]['native_overspent'] = app('steam')->positive(bcadd($convertedLimitAmount, $result[$limitCurrencyId]['native_spent']));
}
}
return $result;
}
}

View File

@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
/*
* BudgetController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Http\JsonResponse;
/**
* Class BudgetController
*/
class CategoryController extends Controller
{
use CleansChartData;
private AccountRepositoryInterface $accountRepos;
private CurrencyRepositoryInterface $currencyRepos;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setAdministrationId(auth()->user()->user_group_id);
return $next($request);
}
);
}
/**
* TODO may be worth to move to a handler but the data is simple enough.
* TODO see autoComplete/account controller
*
* @param DateRequest $request
*
* @return JsonResponse
* @throws FireflyException
*/
public function dashboard(DateRequest $request): JsonResponse
{
/** @var Carbon $start */
$start = $this->parameters->get('start');
/** @var Carbon $end */
$end = $this->parameters->get('end');
$accounts = $this->accountRepos->getAccountsByType([AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::ASSET, AccountType::DEFAULT]);
$default = app('amount')->getDefaultCurrency();
$converter = new ExchangeRateConverter();
$currencies = [];
$return = [];
// get journals for entire period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withAccountInformation();
$collector->setXorAccounts($accounts)->withCategoryInformation();
$collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::RECONCILIATION]);
$journals = $collector->getExtractedJournals();
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
$currencies[$currencyId] = $currency;
$categoryName = null === $journal['category_name'] ? (string)trans('firefly.no_category') : $journal['category_name'];
$amount = app('steam')->positive($journal['amount']);
$nativeAmount = $converter->convert($default, $currency, $journal['date'], $amount);
$key = sprintf('%s-%s', $categoryName, $currency->code);
if ((int)$journal['foreign_currency_id'] === (int)$default->id) {
$nativeAmount = app('steam')->positive($journal['foreign_amount']);
}
// create arrays
$return[$key] = $return[$key] ?? [
'label' => $categoryName,
'currency_id' => (int)$currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places,
'native_id' => (int)$default->id,
'native_code' => $default->code,
'native_name' => $default->name,
'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places,
'period' => null,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'amount' => '0',
'native_amount' => '0',
];
// add monies
$return[$key]['amount'] = bcadd($return[$key]['amount'], $amount);
$return[$key]['native_amount'] = bcadd($return[$key]['native_amount'], $nativeAmount);
}
$return = array_values($return);
// order by native amount
usort($return, function (array $a, array $b) {
return (float)$a['native_amount'] < (float)$b['native_amount'] ? 1 : -1;
});
return response()->json($this->clean($return));
}
}

View File

@@ -93,21 +93,26 @@ class Controller extends BaseController
// some date fields: // some date fields:
foreach ($dates as $field) { foreach ($dates as $field) {
$date = null; $date = null;
$obj = null;
try { try {
$date = request()->query->get($field); $date = request()->query->get($field);
} catch (BadRequestException $e) { } catch (BadRequestException $e) {
Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $field)); Log::error(sprintf('Request field "%s" contains a non-scalar value. Value set to NULL.', $field));
Log::error($e->getMessage()); Log::error($e->getMessage());
$value = null; Log::error($e->getTraceAsString());
} }
$obj = null;
if (null !== $date) { if (null !== $date) {
try { try {
$obj = Carbon::parse($date); $obj = Carbon::parse($date, config('app.timezone'));
} catch (InvalidDateException | InvalidFormatException $e) { } catch (InvalidDateException | InvalidFormatException $e) {
// don't care // don't care
app('log')->warning(sprintf('Ignored invalid date "%s" in API v2 controller parameter check: %s', substr($date, 0, 20), $e->getMessage())); app('log')->warning(sprintf('Ignored invalid date "%s" in API v2 controller parameter check: %s', substr($date, 0, 20), $e->getMessage()));
} }
// out of range? set to null.
if (null !== $obj && ($obj->year <= 1900 || $obj->year > 2099)) {
app('log')->warning(sprintf('Refuse to use date "%s" in API v2 controller parameter check: %s', $field, $obj->toAtomString()));
$obj = null;
}
} }
$bag->set($field, $obj); $bag->set($field, $obj);
} }
@@ -148,7 +153,7 @@ class Controller extends BaseController
$objects = $paginator->getCollection(); $objects = $paginator->getCollection();
// the transformer, at this point, needs to collect information that ALL items in the collection // the transformer, at this point, needs to collect information that ALL items in the collection
// require, like meta data and stuff like that, and save it for later. // require, like meta-data and stuff like that, and save it for later.
$transformer->collectMetaData($objects); $transformer->collectMetaData($objects);
$resource = new FractalCollection($objects, $transformer, $key); $resource = new FractalCollection($objects, $transformer, $key);

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/*
* ShowController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Model\Bill;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Transformers\V2\AccountTransformer;
use FireflyIII\Transformers\V2\BillTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Class ShowController
*/
class ShowController extends Controller
{
private BillRepositoryInterface $repository;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request);
}
);
}
/**
* @param Request $request
*
* TODO see autocomplete/accountcontroller for list.
*
* @return JsonResponse
*/
public function index(Request $request): JsonResponse
{
$this->repository->correctOrder();
$bills = $this->repository->getBills();
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
$count = $bills->count();
$bills = $bills->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
$transformer = new BillTransformer();
$transformer->setParameters($this->parameters); // give params to transformer
return response()
->json($this->jsonApiList('subscriptions', $paginator, $transformer))
->header('Content-Type', self::CONTENT_TYPE);
}
/**
* TODO this endpoint is not documented
*/
public function show(Request $request, Bill $bill): JsonResponse
{
$transformer = new BillTransformer();
$transformer->setParameters($this->parameters);
return response()
->api($this->jsonApiObject('subscriptions', $bill, $transformer))
->header('Content-Type', self::CONTENT_TYPE);
}
}

View File

@@ -26,8 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Bill;
use FireflyIII\Api\V2\Controllers\Controller; use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest; use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Support\Http\Api\ConvertsExchangeRates;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**
@@ -35,8 +34,6 @@ use Illuminate\Http\JsonResponse;
*/ */
class SumController extends Controller class SumController extends Controller
{ {
use ConvertsExchangeRates;
private BillRepositoryInterface $repository; private BillRepositoryInterface $repository;
/** /**
@@ -58,35 +55,37 @@ class SumController extends Controller
* This endpoint is documented at: * This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsPaidTrSum * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsPaidTrSum
* *
* TODO see autocomplete/accountcontroller for list.
*
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
*/ */
public function paid(DateRequest $request): JsonResponse public function paid(DateRequest $request): JsonResponse
{ {
$dates = $request->getAll(); $this->repository->setAdministrationId(auth()->user()->user_group_id);
$result = $this->repository->sumPaidInRange($dates['start'], $dates['end']); $result = $this->repository->sumPaidInRange($this->parameters->get('start'), $this->parameters->get('end'));
$converted = $this->cerSum($result);
// convert to JSON response: // convert to JSON response:
return response()->api($converted); return response()->api(array_values($result));
} }
/** /**
* This endpoint is documented at: * This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsUnpaidTrSum * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/transactions-sum/getBillsUnpaidTrSum
* *
* TODO see autocomplete/accountcontroller for list.
*
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
*/ */
public function unpaid(DateRequest $request): JsonResponse public function unpaid(DateRequest $request): JsonResponse
{ {
$dates = $request->getAll(); $this->repository->setAdministrationId(auth()->user()->user_group_id);
$result = $this->repository->sumUnpaidInRange($dates['start'], $dates['end']); $result = $this->repository->sumUnpaidInRange($this->parameters->get('start'), $this->parameters->get('end'));
$converted = $this->cerSum($result);
// convert to JSON response: // convert to JSON response:
return response()->api($converted); return response()->api(array_values($result));
} }
} }

View File

@@ -60,6 +60,8 @@ class ListController extends Controller
*/ */
public function index(Request $request): JsonResponse public function index(Request $request): JsonResponse
{ {
echo 'this needs move to Administration';
exit;
$collection = $this->repository->getActiveBudgets(); $collection = $this->repository->getActiveBudgets();
$total = $collection->count(); $total = $collection->count();
$collection->slice($this->pageSize * $this->parameters->get('page'), $this->pageSize); $collection->slice($this->pageSize * $this->parameters->get('page'), $this->pageSize);

View File

@@ -29,7 +29,6 @@ use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest; use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Api\ConvertsExchangeRates;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**
@@ -37,8 +36,6 @@ use Illuminate\Http\JsonResponse;
*/ */
class ShowController extends Controller class ShowController extends Controller
{ {
use ConvertsExchangeRates;
private BudgetRepositoryInterface $repository; private BudgetRepositoryInterface $repository;
/** /**
@@ -63,6 +60,7 @@ class ShowController extends Controller
*/ */
public function budgeted(DateRequest $request, Budget $budget): JsonResponse public function budgeted(DateRequest $request, Budget $budget): JsonResponse
{ {
die('deprecated use of thing.');
$data = $request->getAll(); $data = $request->getAll();
$result = $this->repository->budgetedInPeriodForBudget($budget, $data['start'], $data['end']); $result = $this->repository->budgetedInPeriodForBudget($budget, $data['start'], $data['end']);
$converted = $this->cerSum(array_values($result)); $converted = $this->cerSum(array_values($result));
@@ -77,6 +75,7 @@ class ShowController extends Controller
*/ */
public function spent(DateRequest $request, Budget $budget): JsonResponse public function spent(DateRequest $request, Budget $budget): JsonResponse
{ {
die('deprecated use of thing.');
$data = $request->getAll(); $data = $request->getAll();
$result = $this->repository->spentInPeriodForBudget($budget, $data['start'], $data['end']); $result = $this->repository->spentInPeriodForBudget($budget, $data['start'], $data['end']);
$converted = $this->cerSum(array_values($result)); $converted = $this->cerSum(array_values($result));

View File

@@ -27,7 +27,6 @@ namespace FireflyIII\Api\V2\Controllers\Model\Budget;
use FireflyIII\Api\V2\Controllers\Controller; use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest; use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\Http\Api\ConvertsExchangeRates;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**
@@ -35,8 +34,6 @@ use Illuminate\Http\JsonResponse;
*/ */
class SumController extends Controller class SumController extends Controller
{ {
use ConvertsExchangeRates;
private BudgetRepositoryInterface $repository; private BudgetRepositoryInterface $repository;
/** /**
@@ -64,6 +61,7 @@ class SumController extends Controller
*/ */
public function budgeted(DateRequest $request): JsonResponse public function budgeted(DateRequest $request): JsonResponse
{ {
die('deprecated use of thing.');
$data = $request->getAll(); $data = $request->getAll();
$result = $this->repository->budgetedInPeriod($data['start'], $data['end']); $result = $this->repository->budgetedInPeriod($data['start'], $data['end']);
$converted = $this->cerSum(array_values($result)); $converted = $this->cerSum(array_values($result));
@@ -81,6 +79,7 @@ class SumController extends Controller
*/ */
public function spent(DateRequest $request): JsonResponse public function spent(DateRequest $request): JsonResponse
{ {
die('deprecated use of thing.');
$data = $request->getAll(); $data = $request->getAll();
$result = $this->repository->spentInPeriod($data['start'], $data['end']); $result = $this->repository->spentInPeriod($data['start'], $data['end']);
$converted = $this->cerSum(array_values($result)); $converted = $this->cerSum(array_values($result));

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/*
* ShowController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Model\PiggyBank;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Repositories\Administration\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Transformers\V2\PiggyBankTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
/**
* Class ShowController
*/
class ShowController extends Controller
{
private PiggyBankRepositoryInterface $repository;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(PiggyBankRepositoryInterface::class);
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request);
}
);
}
/**
* @param Request $request
*
* TODO see autocomplete/accountcontroller for list.
*
* @return JsonResponse
*/
public function index(Request $request): JsonResponse
{
$piggies = $this->repository->getPiggyBanks();
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
$count = $piggies->count();
$piggies = $piggies->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($piggies, $count, $pageSize, $this->parameters->get('page'));
$transformer = new PiggyBankTransformer();
$transformer->setParameters($this->parameters); // give params to transformer
return response()
->json($this->jsonApiList('piggy-banks', $paginator, $transformer))
->header('Content-Type', self::CONTENT_TYPE);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* StoreController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
use FireflyIII\Api\V2\Controllers\Controller;
use Illuminate\Http\JsonResponse;
/**
* Class StoreController
*/
class StoreController extends Controller
{
/**
* @return JsonResponse
*/
public function post(): JsonResponse
{
return response()->json([]);
}
}

View File

@@ -26,7 +26,6 @@ namespace FireflyIII\Api\V2\Controllers;
use FireflyIII\Api\V2\Request\Generic\SingleDateRequest; use FireflyIII\Api\V2\Request\Generic\SingleDateRequest;
use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Support\Http\Api\ConvertsExchangeRates;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**
@@ -34,8 +33,6 @@ use Illuminate\Http\JsonResponse;
*/ */
class NetWorthController extends Controller class NetWorthController extends Controller
{ {
use ConvertsExchangeRates;
private NetWorthInterface $netWorth; private NetWorthInterface $netWorth;
/** /**
@@ -64,6 +61,7 @@ class NetWorthController extends Controller
*/ */
public function get(SingleDateRequest $request): JsonResponse public function get(SingleDateRequest $request): JsonResponse
{ {
die('deprecated use of thing.');
$date = $request->getDate(); $date = $request->getDate();
$result = $this->netWorth->sumNetWorthByCurrency($date); $result = $this->netWorth->sumNetWorthByCurrency($date);
$converted = $this->cerSum($result); $converted = $this->cerSum($result);

View File

@@ -0,0 +1,552 @@
<?php
/*
* SummaryController.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Summary;
use Carbon\Carbon;
use Exception;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
/**
* Class BasicController
*/
class BasicController extends Controller
{
private AvailableBudgetRepositoryInterface $abRepository;
private AccountRepositoryInterface $accountRepository;
private BillRepositoryInterface $billRepository;
private BudgetRepositoryInterface $budgetRepository;
private CurrencyRepositoryInterface $currencyRepos;
private OperationsRepositoryInterface $opsRepository;
/**
* BasicController constructor.
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->abRepository = app(AvailableBudgetRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->billRepository = app(BillRepositoryInterface::class);
$this->budgetRepository = app(BudgetRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->abRepository->setAdministrationId($user->user_group_id);
$this->accountRepository->setAdministrationId($user->user_group_id);
$this->billRepository->setAdministrationId($user->user_group_id);
$this->budgetRepository->setAdministrationId($user->user_group_id);
$this->currencyRepos->setUser($user);
$this->opsRepository->setAdministrationId($user->user_group_id);
return $next($request);
}
);
}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/summary/getBasicSummary
*
* @param DateRequest $request
*
* @return JsonResponse
* @throws Exception
*/
public function basic(DateRequest $request): JsonResponse
{
// parameters for boxes:
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
// balance information:
$balanceData = $this->getBalanceInformation($start, $end);
$billData = $this->getBillInformation($start, $end);
$spentData = $this->getLeftToSpendInfo($start, $end);
$netWorthData = $this->getNetWorthInfo($start, $end);
$total = array_merge($balanceData, $billData, $spentData, $netWorthData);
return response()->json($total);
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @throws FireflyException
*/
private function getBalanceInformation(Carbon $start, Carbon $end): array
{
// prep some arrays:
$incomes = [
'native' => '0',
];
$expenses = [
'native' => '0',
];
$sums = [
'native' => '0',
];
$return = [];
$currencies = [];
$converter = new ExchangeRateConverter();
$default = app('amount')->getDefaultCurrency();
/** @var User $user */
$user = auth()->user();
// collect income of user using the new group collector.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector
->setRange($start, $end)
->setUserGroup($user->userGroup)
// set page to retrieve
->setPage($this->parameters->get('page'))
// set types of transactions to return.
->setTypes([TransactionType::DEPOSIT])
->setRange($start, $end);
$set = $collector->getExtractedJournals();
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
// transaction info:
$currencyId = (int)$transactionJournal['currency_id'];
$amount = bcmul($transactionJournal['amount'], '-1');
$currency = $currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$currencies[$currencyId] = $currency;
$nativeAmount = $converter->convert($currency, $default, $transactionJournal['date'], $amount);
if ((int)$transactionJournal['foreign_currency_id'] === (int)$default->id) {
// use foreign amount instead
$nativeAmount = $transactionJournal['foreign_amount'];
}
// prep the arrays
$incomes[$currencyId] = $incomes[$currencyId] ?? '0';
$incomes['native'] = $incomes['native'] ?? '0';
$sums[$currencyId] = $sums[$currencyId] ?? '0';
$sums['native'] = $sums['native'] ?? '0';
// add values:
$incomes[$currencyId] = bcadd($incomes[$currencyId], $amount);
$sums[$currencyId] = bcadd($sums[$currencyId], $amount);
$incomes['native'] = bcadd($incomes['native'], $nativeAmount);
$sums['native'] = bcadd($sums['native'], $nativeAmount);
}
// collect expenses of user using the new group collector.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector
->setRange($start, $end)
->setUserGroup($user->userGroup)
// set page to retrieve
->setPage($this->parameters->get('page'))
// set types of transactions to return.
->setTypes([TransactionType::WITHDRAWAL])
->setRange($start, $end);
$set = $collector->getExtractedJournals();
/** @var array $transactionJournal */
foreach ($set as $transactionJournal) {
// transaction info
$currencyId = (int)$transactionJournal['currency_id'];
$amount = $transactionJournal['amount'];
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
$currencies[$currencyId] = $currency;
$nativeAmount = $converter->convert($currency, $default, $transactionJournal['date'], $amount);
if ((int)$transactionJournal['foreign_currency_id'] === (int)$default->id) {
// use foreign amount instead
$nativeAmount = $transactionJournal['foreign_amount'];
}
// prep arrays
$expenses[$currencyId] = $expenses[$currencyId] ?? '0';
$expenses['native'] = $expenses['native'] ?? '0';
$sums[$currencyId] = $sums[$currencyId] ?? '0';
$sums['native'] = $sums['native'] ?? '0';
// add values
$expenses[$currencyId] = bcadd($expenses[$currencyId], $amount);
$sums[$currencyId] = bcadd($sums[$currencyId], $amount);
$expenses['native'] = bcadd($expenses['native'], $nativeAmount);
$sums['native'] = bcadd($sums['native'], $nativeAmount);
}
// create special array for native currency:
$return[] = [
'key' => 'balance-in-native',
'value' => $sums['native'],
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
];
$return[] = [
'key' => 'spent-in-native',
'value' => $expenses['native'],
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
];
$return[] = [
'key' => 'earned-in-native',
'value' => $incomes['native'],
'currency_id' => $default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => $default->decimal_places,
];
// format amounts:
$keys = array_keys($sums);
foreach ($keys as $currencyId) {
if ('native' === $currencyId) {
// skip native entries.
continue;
}
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
$currencies[$currencyId] = $currency;
// create objects for big array.
$return[] = [
'key' => sprintf('balance-in-%s', $currency->code),
'value' => $sums[$currencyId] ?? '0',
'currency_id' => $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),
'value' => $expenses[$currencyId] ?? '0',
'currency_id' => $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$return[] = [
'key' => sprintf('earned-in-%s', $currency->code),
'value' => $incomes[$currencyId] ?? '0',
'currency_id' => $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
}
return $return;
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function getBillInformation(Carbon $start, Carbon $end): array
{
/*
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
$paidAmount = $this->billRepository->sumPaidInRange($start, $end);
$unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end);
$return = [];
/**
* @var array $info
*/
foreach ($paidAmount as $info) {
$amount = bcmul($info['sum'], '-1');
$nativeAmount = bcmul($info['native_sum'], '-1');
$return[] = [
'key' => sprintf('bills-paid-in-%s', $info['currency_code']),
'value' => $amount,
'currency_id' => $info['currency_id'],
'currency_code' => $info['currency_code'],
'currency_symbol' => $info['currency_symbol'],
'currency_decimal_places' => $info['currency_decimal_places'],
];
$return[] = [
'key' => 'bills-paid-in-native',
'value' => $nativeAmount,
'currency_id' => $info['native_id'],
'currency_code' => $info['native_code'],
'currency_symbol' => $info['native_symbol'],
'currency_decimal_places' => $info['native_decimal_places'],
];
}
/**
* @var array $info
*/
foreach ($unpaidAmount as $info) {
$amount = bcmul($info['sum'], '-1');
$nativeAmount = bcmul($info['native_sum'], '-1');
$return[] = [
'key' => sprintf('bills-unpaid-in-%s', $info['currency_code']),
'value' => $amount,
'currency_id' => $info['currency_id'],
'currency_code' => $info['currency_code'],
'currency_symbol' => $info['currency_symbol'],
'currency_decimal_places' => $info['currency_decimal_places'],
];
$return[] = [
'key' => 'bills-unpaid-in-native',
'value' => $nativeAmount,
'currency_id' => $info['native_id'],
'currency_code' => $info['native_code'],
'currency_symbol' => $info['native_symbol'],
'currency_decimal_places' => $info['native_decimal_places'],
];
}
return $return;
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @throws Exception
*/
private function getLeftToSpendInfo(Carbon $start, Carbon $end): array
{
app('log')->debug('Now in getLeftToSpendInfo');
$return = [];
$today = today(config('app.timezone'));
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
$budgets = $this->budgetRepository->getActiveBudgets();
$spent = $this->opsRepository->listExpenses($start, $end, null, $budgets);
$default = app('amount')->getDefaultCurrency();
$currencies = [];
$converter = new ExchangeRateConverter();
// native info:
$nativeLeft = [
'key' => 'left-to-spend-in-native',
'value' => '0',
'currency_id' => (string)$default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places,
];
$nativePerDay = [
'key' => 'left-per-day-to-spend-in-native',
'value' => '0',
'currency_id' => (string)$default->id,
'currency_code' => $default->code,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places,
];
/**
* @var int $currencyId
* @var array $row
*/
foreach ($spent as $currencyId => $row) {
app('log')->debug(sprintf('Processing spent array in currency #%d', $currencyId));
$currencyId = (int)$currencyId;
$spent = '0';
$spentNative = '0';
// get the sum from the array of transactions (double loop but who cares)
/** @var array $budget */
foreach ($row['budgets'] as $budget) {
app('log')->debug(sprintf('Processing expenses in budget "%s".', $budget['name']));
/** @var array $journal */
foreach ($budget['transaction_journals'] as $journal) {
$journalCurrencyId = $journal['currency_id'];
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
$currencies[$currencyId] = $currency;
$amount = app('steam')->negative($journal['amount']);
$amountNative = $converter->convert($default, $currency, $start, $amount);
if ((int)$journal['foreign_currency_id'] === (int)$default->id) {
$amountNative = $journal['foreign_amount'];
}
$spent = bcadd($spent, $amount);
$spentNative = bcadd($spentNative, $amountNative);
}
app('log')->debug(sprintf('Total spent in budget "%s" is %s', $budget['name'], $spent));
}
// either an amount was budgeted or 0 is available.
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
$currencies[$currencyId] = $currency;
$amount = $available[$currencyId]['amount'] ?? '0';
$amountNative = $available[$currencyId]['native_amount'] ?? '0';
$left = bcadd($amount, $spent);
$leftNative = bcadd($amountNative, $spentNative);
app('log')->debug(sprintf('Available amount is %s', $amount));
app('log')->debug(sprintf('Amount left is %s', $left));
// how much left per day?
$days = $today->diffInDays($end) + 1;
$perDay = '0';
$perDayNative = '0';
if (0 !== $days && bccomp($left, '0') > -1) {
$perDay = bcdiv($left, (string)$days);
}
if (0 !== $days && bccomp($leftNative, '0') > -1) {
$perDayNative = bcdiv($leftNative, (string)$days);
}
// left
$return[] = [
'key' => sprintf('left-to-spend-in-%s', $row['currency_code']),
'value' => $left,
'currency_id' => (string)$row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => (int)$row['currency_decimal_places'],
];
// left (native)
$nativeLeft['value'] = $leftNative;
// left per day:
$return[] = [
'key' => sprintf('left-per-day-to-spend-in-%s', $row['currency_code']),
'value' => $perDay,
'currency_id' => (string)$row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => (int)$row['currency_decimal_places'],
];
// left per day (native)
$nativePerDay['value'] = $perDayNative;
}
$return[] = $nativeLeft;
$return[] = $nativePerDay;
return $return;
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
private function getNetWorthInfo(Carbon $start, Carbon $end): array
{
/** @var UserGroup $userGroup */
$userGroup = auth()->user()->userGroup;
$date = today(config('app.timezone'))->startOfDay();
// start and end in the future? use $end
if ($this->notInDateRange($date, $start, $end)) {
/** @var Carbon $date */
$date = session('end', today(config('app.timezone'))->endOfMonth());
}
/** @var NetWorthInterface $netWorthHelper */
$netWorthHelper = app(NetWorthInterface::class);
$netWorthHelper->setUserGroup($userGroup);
$allAccounts = $this->accountRepository->getActiveAccountsByType(
[AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT]
);
// filter list on preference of being included.
$filtered = $allAccounts->filter(
function (Account $account) {
$includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth');
return null === $includeNetWorth || '1' === $includeNetWorth;
}
);
$netWorthSet = $netWorthHelper->byAccounts($filtered, $date);
$return = [];
// in native amount
$return[] = [
'key' => 'net-worth-in-native',
'value' => $netWorthSet['native']['balance'],
'currency_id' => $netWorthSet['native']['currency_id'],
'currency_code' => $netWorthSet['native']['currency_code'],
'currency_symbol' => $netWorthSet['native']['currency_symbol'],
'currency_decimal_places' => $netWorthSet['native']['currency_decimal_places'],
];
foreach ($netWorthSet as $key => $data) {
if ('native' === $key) {
continue;
}
$return[] = [
'key' => sprintf('net-worth-in-%s', $data['currency_code']),
'value' => $data['balance'],
'currency_id' => $data['currency_id'],
'currency_code' => $data['currency_code'],
'currency_symbol' => $data['currency_symbol'],
'currency_decimal_places' => $data['currency_decimal_places'],
];
}
return $return;
}
/**
* Check if date is outside session range.
*
* @param Carbon $date
*
* @param Carbon $start
* @param Carbon $end
*
* @return bool
*/
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference
{
$result = false;
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
$result = true;
}
// start and end in the past? use $end
if ($start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date)) {
$result = true;
}
return $result;
}
}

View File

@@ -49,19 +49,17 @@ class AccountController extends Controller
* *
* @return JsonResponse * @return JsonResponse
*/ */
public function listTransactions(ListRequest $request, Account $account): JsonResponse public function list(ListRequest $request, Account $account): JsonResponse
{ {
// collect transactions: // collect transactions:
$type = $request->get('type') ?? 'default'; $limit = $request->getLimit();
$limit = (int)$request->get('limit'); $page = $request->getPage();
$page = (int)$request->get('page');
$page = max($page, 1); $page = max($page, 1);
if ($limit > 0 && $limit <= $this->pageSize) { if ($limit > 0 && $limit <= $this->pageSize) {
$this->pageSize = $limit; $this->pageSize = $limit;
} }
$types = $this->mapTransactionTypes($type);
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
@@ -69,15 +67,25 @@ class AccountController extends Controller
->withAPIInformation() ->withAPIInformation()
->setLimit($this->pageSize) ->setLimit($this->pageSize)
->setPage($page) ->setPage($page)
->setTypes($types); ->setTypes($request->getTransactionTypes());
// TODO date filter $start = $request->getStartDate();
//if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $end = $request->getEndDate();
// $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); if (null !== $start) {
//} $collector->setStart($start);
}
if (null !== $end) {
$collector->setEnd($start);
}
$paginator = $collector->getPaginatedGroups(); $paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v2.accounts.transactions', [$account->id])); // TODO . $this->buildParams() $paginator->setPath(
sprintf(
'%s?%s',
route('api.v2.accounts.transactions', [$account->id]),
$request->buildParams()
)
);
return response() return response()
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer())) ->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/*
* TransactionController.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Controllers\Transaction\List;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Transaction\ListRequest;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
use Illuminate\Http\JsonResponse;
/**
* Class TransactionController
*/
class TransactionController extends Controller
{
/**
* @param ListRequest $request
*
* @return JsonResponse
*/
public function list(ListRequest $request): JsonResponse
{
// collect transactions:
$limit = $request->getLimit();
$page = $request->getPage();
$page = max($page, 1);
if ($limit > 0 && $limit <= $this->pageSize) {
$this->pageSize = $limit;
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUserGroup(auth()->user()->userGroup)
->withAPIInformation()
->setLimit($this->pageSize)
->setPage($page)
->setTypes($request->getTransactionTypes());
$start = $this->parameters->get('start');
$end = $this->parameters->get('end');
if (null !== $start) {
$collector->setStart($start);
}
if (null !== $end) {
$collector->setEnd($end);
}
// $collector->dumpQuery();
// exit;
$paginator = $collector->getPaginatedGroups();
$paginator->setPath(
sprintf(
'%s?%s',
route('api.v2.transactions.list'),
$request->buildParams()
)
);
return response()
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))
->header('Content-Type', self::CONTENT_TYPE);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/*
* BalanceChartRequest.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Api\V2\Request\Chart;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
class BalanceChartRequest extends FormRequest
{
use ConvertsDataTypes;
use ChecksLogin;
/**
* Get all data from the request.
*
* @return array
*/
public function getAll(): array
{
return [
'accounts' => $this->getAccountList(),
'period' => $this->convertString('period'),
];
}
/**
* The rules that the incoming request must be matched against.
*
* @return array
*/
public function rules(): array
{
return [
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
'accounts.*' => 'required|exists:accounts,id',
'period' => sprintf('required|in:%s', join(',', config('firefly.valid_view_ranges'))),
];
}
/**
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
// validate transaction query data.
$data = $validator->getData();
if (!array_key_exists('accounts', $data)) {
$validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
return;
}
if (!is_array($data['accounts'])) {
$validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
}
}
);
}
}

View File

@@ -47,7 +47,7 @@ class DateRequest extends FormRequest
{ {
return [ return [
'start' => $this->getCarbonDate('start'), 'start' => $this->getCarbonDate('start'),
'end' => $this->getCarbonDate('end'), 'end' => $this->getCarbonDate('end')->endOfDay(),
]; ];
} }

View File

@@ -24,15 +24,84 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Transaction; namespace FireflyIII\Api\V2\Request\Transaction;
use Carbon\Carbon;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
/** /**
* Class ListRequest * Class ListRequest
* Used specifically to list transactions.
*/ */
class ListRequest extends FormRequest class ListRequest extends FormRequest
{ {
use ChecksLogin; use ChecksLogin;
use ConvertsDataTypes;
use TransactionFilter;
/**
* @return string
*/
public function buildParams(): string
{
$array = [
'page' => $this->getPage(),
];
$start = $this->getStartDate();
$end = $this->getEndDate();
if (null !== $start && null !== $end) {
$array['start'] = $start->format('Y-m-d');
$array['end'] = $end->format('Y-m-d');
}
if (0 !== $this->getLimit()) {
$array['limit'] = $this->getLimit();
}
return http_build_query($array);
}
/**
* @return int
*/
public function getPage(): int
{
$page = $this->convertInteger('page');
return 0 === $page || $page > 65536 ? 1 : $page;
}
/**
* @return Carbon|null
*/
public function getStartDate(): ?Carbon
{
return $this->getCarbonDate('start');
}
/**
* @return Carbon|null
*/
public function getEndDate(): ?Carbon
{
return $this->getCarbonDate('end');
}
/**
* @return int
*/
public function getLimit(): int
{
return $this->convertInteger('limit');
}
/**
* @return array
*/
public function getTransactionTypes(): array
{
$type = (string)$this->get('type', 'default');
return $this->mapTransactionTypes($type);
}
/** /**
* @return array * @return array

View File

@@ -34,6 +34,7 @@ use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\RuleTrigger; use FireflyIII\Models\RuleTrigger;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use ValueError;
/** /**
* Class ReportSkeleton * Class ReportSkeleton
@@ -247,7 +248,16 @@ class CorrectAmounts extends Command
/** @var RuleTrigger $item */ /** @var RuleTrigger $item */
foreach ($set as $item) { foreach ($set as $item) {
// basic check: // basic check:
if (-1 === bccomp((string)$item->trigger_value, '0')) { $check = 0;
try {
$check = bccomp((string)$item->trigger_value, '0');
} catch (ValueError $e) {
$this->friendlyError(sprintf('Rule #%d contained invalid %s-trigger "%s". The trigger has been removed, and the rule is disabled.', $item->rule_id, $item->trigger_type, $item->trigger_value));
$item->rule->active = false;
$item->rule->save();
$item->forceDelete();
}
if (-1 === $check) {
$fixed++; $fixed++;
$item->trigger_value = app('steam')->positive((string)$item->trigger_value); $item->trigger_value = app('steam')->positive((string)$item->trigger_value);
$item->save(); $item->save();

View File

@@ -82,6 +82,7 @@ class DeleteEmptyJournals extends Command
TransactionJournal::find((int)$row->transaction_journal_id)->delete(); TransactionJournal::find((int)$row->transaction_journal_id)->delete();
} catch (QueryException $e) { } catch (QueryException $e) {
Log::info(sprintf('Could not delete journal: %s', $e->getMessage())); Log::info(sprintf('Could not delete journal: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
} }
@@ -113,6 +114,7 @@ class DeleteEmptyJournals extends Command
TransactionJournal::find($entry->id)->delete(); TransactionJournal::find($entry->id)->delete();
} catch (QueryException $e) { } catch (QueryException $e) {
Log::info(sprintf('Could not delete entry: %s', $e->getMessage())); Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
} }

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\Recurrence; use FireflyIII\Models\Recurrence;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
@@ -93,6 +94,7 @@ class UpdateGroupInformation extends Command
Bill::class, Bill::class,
Budget::class, Budget::class,
Category::class, Category::class,
ObjectGroup::class,
CurrencyExchangeRate::class, CurrencyExchangeRate::class,
Recurrence::class, Recurrence::class,
RuleGroup::class, RuleGroup::class,

View File

@@ -69,6 +69,9 @@ class UpgradeDatabase extends Command
'firefly-iii:liabilities-600', 'firefly-iii:liabilities-600',
'firefly-iii:budget-limit-periods', 'firefly-iii:budget-limit-periods',
'firefly-iii:restore-oauth-keys', 'firefly-iii:restore-oauth-keys',
// also just in case, some integrity commands:
'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information',
]; ];
$args = []; $args = [];
if ($this->option('force')) { if ($this->option('force')) {

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* RuleActionFailedOnArray.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\Rule;
use FireflyIII\Models\RuleAction;
use Illuminate\Queue\SerializesModels;
/**
* Class RuleActionFailedOnArray
*/
class RuleActionFailedOnArray
{
use SerializesModels;
public string $error;
public array $journal;
public RuleAction $ruleAction;
/**
* @param RuleAction $ruleAction
* @param array $journal
* @param string $error
*/
public function __construct(RuleAction $ruleAction, array $journal, string $error)
{
app('log')->debug('Created new RuleActionFailedOnArray');
$this->ruleAction = $ruleAction;
$this->journal = $journal;
$this->error = $error;
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* RuleActionFailedOnArray.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Queue\SerializesModels;
/**
* Class RuleActionFailedOnObject
*/
class RuleActionFailedOnObject
{
use SerializesModels;
public string $error;
public TransactionJournal $journal;
public RuleAction $ruleAction;
/**
* @param RuleAction $ruleAction
* @param TransactionJournal $journal
* @param string $error
*/
public function __construct(RuleAction $ruleAction, TransactionJournal $journal, string $error)
{
app('log')->debug('Created new RuleActionFailedOnObject');
$this->ruleAction = $ruleAction;
$this->journal = $journal;
$this->error = $error;
}
}

View File

@@ -92,6 +92,7 @@ class AccountFactory
$return = $this->create( $return = $this->create(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'name' => $accountName, 'name' => $accountName,
'account_type_id' => $type->id, 'account_type_id' => $type->id,
'account_type_name' => null, 'account_type_name' => null,
@@ -199,6 +200,7 @@ class AccountFactory
$active = array_key_exists('active', $data) ? $data['active'] : true; $active = array_key_exists('active', $data) ? $data['active'] : true;
$databaseData = [ $databaseData = [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'account_type_id' => $type->id, 'account_type_id' => $type->id,
'name' => $data['name'], 'name' => $data['name'],
'order' => 25000, 'order' => 25000,
@@ -216,6 +218,7 @@ class AccountFactory
} }
// create account! // create account!
$account = Account::create($databaseData); $account = Account::create($databaseData);
Log::channel('audit')->info(sprintf('Account #%d ("%s") has been created.', $account->id, $account->name));
// update meta data: // update meta data:
$data = $this->cleanMetaDataArray($account, $data); $data = $this->cleanMetaDataArray($account, $data);
@@ -226,6 +229,7 @@ class AccountFactory
$this->storeOpeningBalance($account, $data); $this->storeOpeningBalance($account, $data);
} catch (FireflyException $e) { } catch (FireflyException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
} }
// create credit liability data (only liabilities) // create credit liability data (only liabilities)
@@ -233,6 +237,7 @@ class AccountFactory
$this->storeCreditLiability($account, $data); $this->storeCreditLiability($account, $data);
} catch (FireflyException $e) { } catch (FireflyException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
} }
// create notes // create notes

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Factory;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountMeta;
use Illuminate\Support\Facades\Log;
/** /**
* Class AccountMetaFactory * Class AccountMetaFactory

View File

@@ -67,6 +67,7 @@ class BillFactory
'match' => 'MIGRATED_TO_RULES', 'match' => 'MIGRATED_TO_RULES',
'amount_min' => $data['amount_min'], 'amount_min' => $data['amount_min'],
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_currency_id' => $currency->id, 'transaction_currency_id' => $currency->id,
'amount_max' => $data['amount_max'], 'amount_max' => $data['amount_max'],
'date' => $data['date'], 'date' => $data['date'],

View File

@@ -70,12 +70,14 @@ class CategoryFactory
try { try {
return Category::create( return Category::create(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'name' => $categoryName, 'user_group_id' => $this->user->user_group_id,
'name' => $categoryName,
] ]
); );
} catch (QueryException $e) { } catch (QueryException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException('400003: Could not store new category.', 0, $e); throw new FireflyException('400003: Could not store new category.', 0, $e);
} }
} }

View File

@@ -107,6 +107,7 @@ class RecurrenceFactory
$recurrence = new Recurrence( $recurrence = new Recurrence(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_type_id' => $type->id, 'transaction_type_id' => $type->id,
'title' => $title, 'title' => $title,
'description' => $description, 'description' => $description,
@@ -129,6 +130,7 @@ class RecurrenceFactory
$this->createTransactions($recurrence, $data['transactions'] ?? []); $this->createTransactions($recurrence, $data['transactions'] ?? []);
} catch (FireflyException $e) { } catch (FireflyException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$recurrence->forceDelete(); $recurrence->forceDelete();
$message = sprintf('Could not create recurring transaction: %s', $e->getMessage()); $message = sprintf('Could not create recurring transaction: %s', $e->getMessage());
$this->errors->add('store', $message); $this->errors->add('store', $message);

View File

@@ -83,14 +83,15 @@ class TagFactory
$latitude = 0.0 === (float)$data['latitude'] ? null : (float)$data['latitude']; // intentional float $latitude = 0.0 === (float)$data['latitude'] ? null : (float)$data['latitude']; // intentional float
$longitude = 0.0 === (float)$data['longitude'] ? null : (float)$data['longitude']; // intentional float $longitude = 0.0 === (float)$data['longitude'] ? null : (float)$data['longitude']; // intentional float
$array = [ $array = [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'tag' => trim($data['tag']), 'user_group_id' => $this->user->user_group_id,
'tagMode' => 'nothing', 'tag' => trim($data['tag']),
'date' => $data['date'], 'tagMode' => 'nothing',
'description' => $data['description'], 'date' => $data['date'],
'latitude' => null, 'description' => $data['description'],
'longitude' => null, 'latitude' => null,
'zoomLevel' => null, 'longitude' => null,
'zoomLevel' => null,
]; ];
$tag = Tag::create($array); $tag = Tag::create($array);
if (null !== $tag && null !== $latitude && null !== $longitude) { if (null !== $tag && null !== $latitude && null !== $longitude) {

View File

@@ -69,6 +69,7 @@ class TransactionCurrencyFactory
} catch (QueryException $e) { } catch (QueryException $e) {
$result = null; $result = null;
Log::error(sprintf('Could not create new currency: %s', $e->getMessage())); Log::error(sprintf('Could not create new currency: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
throw new FireflyException('400004: Could not store new currency.', 0, $e); throw new FireflyException('400004: Could not store new currency.', 0, $e);
} }

View File

@@ -110,7 +110,7 @@ class TransactionFactory
Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data); Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data);
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString()); Log::error($e->getTraceAsString());
throw new FireflyException('Query exception when creating transaction.', 0, $e); throw new FireflyException(sprintf('Query exception when creating transaction: %s', $e->getMessage()), 0, $e);
} }
if (null === $result) { if (null === $result) {
throw new FireflyException('Transaction is NULL.'); throw new FireflyException('Transaction is NULL.');

View File

@@ -81,6 +81,7 @@ class TransactionGroupFactory
$group = new TransactionGroup(); $group = new TransactionGroup();
$group->user()->associate($this->user); $group->user()->associate($this->user);
$group->userGroup()->associate($this->user->userGroup);
$group->title = $title; $group->title = $title;
$group->save(); $group->save();

View File

@@ -225,6 +225,7 @@ class TransactionJournalFactory
$journal = TransactionJournal::create( $journal = TransactionJournal::create(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'user_group_id' => $this->user->user_group_id,
'transaction_type_id' => $type->id, 'transaction_type_id' => $type->id,
'bill_id' => $billId, 'bill_id' => $billId,
'transaction_currency_id' => $currency->id, 'transaction_currency_id' => $currency->id,
@@ -533,7 +534,7 @@ class TransactionJournalFactory
{ {
$description = '' === $description ? '(empty description)' : $description; $description = '' === $description ? '(empty description)' : $description;
return substr($description, 0, 255); return substr($description, 0, 1024);
} }
/** /**

View File

@@ -75,6 +75,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
->render(); ->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = sprintf('Could not render report view: %s', $e->getMessage()); $result = sprintf('Could not render report view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -77,6 +77,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
->render(); ->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.category.month: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.category.month: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = sprintf('Could not render report view: %s', $e->getMessage()); $result = sprintf('Could not render report view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -59,6 +59,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
return view('reports.default.month', compact('accountIds', 'reportType'))->with('start', $this->start)->with('end', $this->end)->render(); return view('reports.default.month', compact('accountIds', 'reportType'))->with('start', $this->start)->with('end', $this->end)->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.default.month: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.default.month: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render report view.'; $result = 'Could not render report view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -63,6 +63,7 @@ class MultiYearReportGenerator implements ReportGeneratorInterface
)->with('start', $this->start)->with('end', $this->end)->render(); )->with('start', $this->start)->with('end', $this->end)->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.default.multi-year: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.default.multi-year: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = sprintf('Could not render report view: %s', $e->getMessage()); $result = sprintf('Could not render report view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -63,6 +63,7 @@ class YearReportGenerator implements ReportGeneratorInterface
)->with('start', $this->start)->with('end', $this->end)->render(); )->with('start', $this->start)->with('end', $this->end)->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.account.report: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render report view.'; $result = 'Could not render report view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -71,6 +71,7 @@ class MonthReportGenerator implements ReportGeneratorInterface
)->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render(); )->with('start', $this->start)->with('end', $this->end)->with('tags', $this->tags)->with('accounts', $this->accounts)->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.tag.month: %s', $e->getMessage())); Log::error(sprintf('Cannot render reports.tag.month: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = sprintf('Could not render report view: %s', $e->getMessage()); $result = sprintf('Could not render report view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -174,6 +174,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
Log::error( Log::error(
sprintf('The transformer could not include the requested transaction group for webhook #%d: %s', $webhook->id, $e->getMessage()) sprintf('The transformer could not include the requested transaction group for webhook #%d: %s', $webhook->id, $e->getMessage())
); );
Log::error($e->getTraceAsString());
return; return;
} }

View File

@@ -131,6 +131,7 @@ class BudgetLimitHandler
$availableBudget = new AvailableBudget( $availableBudget = new AvailableBudget(
[ [
'user_id' => $budgetLimit->budget->user->id, 'user_id' => $budgetLimit->budget->user->id,
'user_group_id' => $budgetLimit->budget->user->user_group_id,
'transaction_currency_id' => $budgetLimit->transaction_currency_id, 'transaction_currency_id' => $budgetLimit->transaction_currency_id,
'start_date' => $current, 'start_date' => $current,
'end_date' => $currentEnd, 'end_date' => $currentEnd,
@@ -165,7 +166,7 @@ class BudgetLimitHandler
$availableBudget->end_date->format('Y-m-d') $availableBudget->end_date->format('Y-m-d')
) )
); );
// have to recalc everything just in case. // have to recalculate everything just in case.
$set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date); $set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date);
Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count())); Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count()));
/** @var BudgetLimit $budgetLimit */ /** @var BudgetLimit $budgetLimit */
@@ -185,15 +186,18 @@ class BudgetLimitHandler
precision : Precision::DAY(), precision : Precision::DAY(),
boundaries: Boundaries::EXCLUDE_NONE() boundaries: Boundaries::EXCLUDE_NONE()
); );
// if both equal eachother, amount from this BL must be added to the AB // if both equal each other, amount from this BL must be added to the AB
if ($limitPeriod->equals($abPeriod)) { if ($limitPeriod->equals($abPeriod)) {
app('log')->debug('This budget limit is equal to the available budget period.');
$newAmount = bcadd($newAmount, $budgetLimit->amount); $newAmount = bcadd($newAmount, $budgetLimit->amount);
} }
// if budget limit period inside AB period, can be added in full. // if budget limit period is inside AB period, it can be added in full.
if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) { if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) {
app('log')->debug('This budget limit is smaller than the available budget period.');
$newAmount = bcadd($newAmount, $budgetLimit->amount); $newAmount = bcadd($newAmount, $budgetLimit->amount);
} }
if (!$limitPeriod->equals($abPeriod) && $abPeriod->overlapsWith($limitPeriod)) { if (!$limitPeriod->equals($abPeriod) && !$abPeriod->contains($limitPeriod) && $abPeriod->overlapsWith($limitPeriod)) {
app('log')->debug('This budget limit is something else entirely!');
$overlap = $abPeriod->overlap($limitPeriod); $overlap = $abPeriod->overlap($limitPeriod);
if (null !== $overlap) { if (null !== $overlap) {
$length = $overlap->length(); $length = $overlap->length();
@@ -208,7 +212,7 @@ class BudgetLimitHandler
return; return;
} }
Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount)); Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount));
$availableBudget->amount = $newAmount; $availableBudget->amount = app('steam')->bcround($newAmount, $availableBudget->transactionCurrency->decimal_places);
$availableBudget->save(); $availableBudget->save();
} }

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/*
* RuleHandler.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Handlers\Events\Model;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Notifications\User\RuleActionFailed;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Support\Facades\Notification;
/**
* Class RuleHandler
*/
class RuleHandler
{
/**
* @param RuleActionFailedOnArray $event
*
* @return void
*/
public function ruleActionFailedOnArray(RuleActionFailedOnArray $event): void
{
$ruleAction = $event->ruleAction;
$rule = $ruleAction->rule;
$preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data;
if (false === $preference) {
return;
}
app('log')->debug('Now in ruleActionFailedOnArray');
$journal = $event->journal;
$error = $event->error;
$user = $ruleAction->rule->user;
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal['transaction_group_id'], 'error' => $error]);
$groupTitle = $journal['description'] ?? '';
$groupLink = route('transactions.show', [$journal['transaction_group_id']]);
$ruleTitle = $rule->title;
$ruleLink = route('rules.edit', [$rule->id]);
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
Notification::send($user, new RuleActionFailed($params));
}
/**
* @param RuleActionFailedOnObject $event
*
* @return void
*/
public function ruleActionFailedOnObject(RuleActionFailedOnObject $event): void
{
$ruleAction = $event->ruleAction;
$rule = $ruleAction->rule;
$preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data;
if (false === $preference) {
return;
}
app('log')->debug('Now in ruleActionFailedOnObject');
$journal = $event->journal;
$error = $event->error;
$user = $ruleAction->rule->user;
$mainMessage = trans('rules.main_message', ['rule' => $rule->title, 'action' => $ruleAction->action_type, 'group' => $journal->transaction_group_id, 'error' => $error]);
$groupTitle = $journal->description ?? '';
$groupLink = route('transactions.show', [$journal->transaction_group_id]);
$ruleTitle = $rule->title;
$ruleLink = route('rules.edit', [$rule->id]);
$params = [$mainMessage, $groupTitle, $groupLink, $ruleTitle, $ruleLink];
Notification::send($user, new RuleActionFailed($params));
}
}

View File

@@ -275,6 +275,7 @@ class UserEventHandler
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $url)); Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $url));
} catch (Exception $e) { // intentional generic exception } catch (Exception $e) { // intentional generic exception
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
} }
@@ -299,6 +300,7 @@ class UserEventHandler
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $url)); Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $url));
} catch (Exception $e) { // intentional generic exception } catch (Exception $e) { // intentional generic exception
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
} }
@@ -342,6 +344,7 @@ class UserEventHandler
Mail::to($invitee)->send(new InvitationMail($invitee, $admin, $url)); Mail::to($invitee)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) { // intentional generic exception } catch (Exception $e) { // intentional generic exception
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
} }

View File

@@ -57,7 +57,7 @@ class VersionCheckEventHandler
$permission = app('fireflyconfig')->get('permission_update_check', -1); $permission = app('fireflyconfig')->get('permission_update_check', -1);
$value = (int)$permission->data; $value = (int)$permission->data;
if (1 !== $value) { if (1 !== $value) {
Log::info('Update check is not enabled.'); Log::debug('Update check is not enabled.');
$this->warnToCheckForUpdates($event); $this->warnToCheckForUpdates($event);
return; return;

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Collector\Extensions; namespace FireflyIII\Helpers\Collector\Extensions;
use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -33,22 +34,29 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
trait CollectorProperties trait CollectorProperties
{ {
public const TEST = 'Test'; public const TEST = 'Test';
private bool $expandGroupSearch; private bool $expandGroupSearch;
private array $fields; private array $fields;
private bool $hasAccountInfo; private bool $hasAccountInfo;
private bool $hasBillInformation; private bool $hasBillInformation;
private bool $hasBudgetInformation; private bool $hasBudgetInformation;
private bool $hasCatInformation; private bool $hasCatInformation;
private bool $hasJoinedAttTables; private bool $hasJoinedAttTables;
private bool $hasJoinedMetaTables; private bool $hasJoinedMetaTables;
private bool $hasJoinedTagTables; private bool $hasJoinedTagTables;
private bool $hasNotesInformation; private bool $hasNotesInformation;
private array $integerFields; private array $integerFields;
private ?int $limit; private ?int $limit;
private ?int $page; private ?int $page;
private array $postFilters; private array $postFilters;
private HasMany $query; private HasMany $query;
private array $stringFields; private array $stringFields;
private int $total; /*
private ?User $user; * This array is used to collect ALL tags the user may search for (using 'setTags').
* This way the user can call 'setTags' multiple times and get a joined result.
*
*/
private array $tags;
private int $total;
private ?User $user;
private ?UserGroup $userGroup;
} }

View File

@@ -64,8 +64,8 @@ trait MetaCollection
// join bill table // join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields // add fields
$this->fields[] = 'bills.id as bill_id'; $this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name'; $this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true; $this->hasBillInformation = true;
} }
@@ -104,8 +104,8 @@ trait MetaCollection
// join cat table // join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields // add fields
$this->fields[] = 'budgets.id as budget_id'; $this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name'; $this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true; $this->hasBudgetInformation = true;
} }
@@ -157,8 +157,8 @@ trait MetaCollection
// join cat table // join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields // add fields
$this->fields[] = 'categories.id as category_id'; $this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name'; $this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true; $this->hasCatInformation = true;
} }
@@ -603,7 +603,7 @@ trait MetaCollection
} }
); );
// add fields // add fields
$this->fields[] = 'notes.text as notes'; $this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true; $this->hasNotesInformation = true;
} }
@@ -896,7 +896,8 @@ trait MetaCollection
public function setTags(Collection $tags): GroupCollectorInterface public function setTags(Collection $tags): GroupCollectorInterface
{ {
$this->withTagInformation(); $this->withTagInformation();
$this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray()); $this->tags = array_merge($this->tags, $tags->pluck('id')->toArray());
$this->query->whereIn('tag_transaction_journal.tag_id', $this->tags);
return $this; return $this;
} }
@@ -913,8 +914,8 @@ trait MetaCollection
$this->withTagInformation(); $this->withTagInformation();
// this method adds a "postFilter" to the collector. // this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray(); $list = $tags->pluck('tag')->toArray();
$filter = function (int $index, array $object) use ($list): bool { $filter = function (int $index, array $object) use ($list): bool {
foreach ($object['transactions'] as $transaction) { foreach ($object['transactions'] as $transaction) {
foreach ($transaction['tags'] as $tag) { foreach ($transaction['tags'] as $tag) {
if (in_array($tag['name'], $list, true)) { if (in_array($tag['name'], $list, true)) {

View File

@@ -675,6 +675,23 @@ trait TimeCollection
return $this; return $this;
} }
/**
* Set the end time of the results to return.
*
* @param Carbon $end
*
* @return GroupCollectorInterface
*/
public function setEnd(Carbon $end): GroupCollectorInterface
{
// always got to end of day / start of day for ranges.
$endStr = $end->format('Y-m-d 23:59:59');
$this->query->where('transaction_journals.date', '<=', $endStr);
return $this;
}
/** /**
* @param Carbon $date * @param Carbon $date
* @param string $field * @param string $field
@@ -822,6 +839,22 @@ trait TimeCollection
return $this; return $this;
} }
/**
* Set the start time of the results to return.
*
* @param Carbon $start
*
* @return GroupCollectorInterface
*/
public function setStart(Carbon $start): GroupCollectorInterface
{
$startStr = $start->format('Y-m-d 00:00:00');
$this->query->where('transaction_journals.date', '>=', $startStr);
return $this;
}
/** /**
* Collect transactions updated on a specific date. * Collect transactions updated on a specific date.
* *

View File

@@ -38,6 +38,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
@@ -65,7 +66,9 @@ class GroupCollector implements GroupCollectorInterface
public function __construct() public function __construct()
{ {
$this->postFilters = []; $this->postFilters = [];
$this->tags = [];
$this->user = null; $this->user = null;
$this->userGroup = null;
$this->limit = null; $this->limit = null;
$this->page = null; $this->page = null;
@@ -81,6 +84,7 @@ class GroupCollector implements GroupCollectorInterface
$this->integerFields = [ $this->integerFields = [
'transaction_group_id', 'transaction_group_id',
'user_id', 'user_id',
'user_group_id',
'transaction_journal_id', 'transaction_journal_id',
'transaction_type_id', 'transaction_type_id',
'order', 'order',
@@ -101,6 +105,7 @@ class GroupCollector implements GroupCollectorInterface
# group # group
'transaction_groups.id as transaction_group_id', 'transaction_groups.id as transaction_group_id',
'transaction_groups.user_id as user_id', 'transaction_groups.user_id as user_id',
'transaction_groups.user_group_id as user_group_id',
'transaction_groups.created_at as created_at', 'transaction_groups.created_at as created_at',
'transaction_groups.updated_at as updated_at', 'transaction_groups.updated_at as updated_at',
'transaction_groups.title as transaction_group_title', 'transaction_groups.title as transaction_group_title',
@@ -299,7 +304,20 @@ class GroupCollector implements GroupCollectorInterface
*/ */
public function dumpQuery(): void public function dumpQuery(): void
{ {
echo $this->query->select($this->fields)->toSql(); $query = $this->query->select($this->fields)->toSql();
$params = $this->query->getBindings();
foreach ($params as $param) {
$replace = sprintf('"%s"', $param);
if (is_int($param)) {
$replace = (string)$param;
}
$pos = strpos($query, '?');
if ($pos !== false) {
$query = substr_replace($query, $replace, $pos, 1);
}
}
echo $query;
echo '<pre>'; echo '<pre>';
print_r($this->query->getBindings()); print_r($this->query->getBindings());
echo '</pre>'; echo '</pre>';
@@ -547,6 +565,7 @@ class GroupCollector implements GroupCollectorInterface
$groupArray = [ $groupArray = [
'id' => (int)$augumentedJournal->transaction_group_id, 'id' => (int)$augumentedJournal->transaction_group_id,
'user_id' => (int)$augumentedJournal->user_id, 'user_id' => (int)$augumentedJournal->user_id,
'user_group_id' => (int)$augumentedJournal->user_group_id,
// Field transaction_group_title was added by the query. // Field transaction_group_title was added by the query.
'title' => $augumentedJournal->transaction_group_title, // @phpstan-ignore-line 'title' => $augumentedJournal->transaction_group_title, // @phpstan-ignore-line
'transaction_type' => $parsedGroup['transaction_type_type'], 'transaction_type' => $parsedGroup['transaction_type_type'],
@@ -852,7 +871,7 @@ class GroupCollector implements GroupCollectorInterface
public function setLimit(int $limit): GroupCollectorInterface public function setLimit(int $limit): GroupCollectorInterface
{ {
$this->limit = $limit; $this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); //app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this; return $this;
} }
@@ -957,7 +976,7 @@ class GroupCollector implements GroupCollectorInterface
{ {
$page = 0 === $page ? 1 : $page; $page = 0 === $page ? 1 : $page;
$this->page = $page; $this->page = $page;
app('log')->debug(sprintf('GroupCollector: page is now %d', $page)); //app('log')->debug(sprintf('GroupCollector: page is now %d', $page));
return $this; return $this;
} }
@@ -1086,6 +1105,64 @@ class GroupCollector implements GroupCollectorInterface
->orderBy('source.amount', 'DESC'); ->orderBy('source.amount', 'DESC');
} }
/**
* Set the user object and start the query.
*
* @param User $user
*
* @return GroupCollectorInterface
*/
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface
{
if (null === $this->userGroup) {
$this->userGroup = $userGroup;
$this->startQueryForGroup();
}
return $this;
}
/**
* Build the query.
*/
private function startQueryForGroup(): void
{
//app('log')->debug('GroupCollector::startQuery');
$this->query = $this->userGroup
->transactionJournals()
->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
// join source transaction.
->leftJoin(
'transactions as source',
function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
->where('source.amount', '<', 0);
}
)
// join destination transaction
->leftJoin(
'transactions as destination',
function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
->where('destination.amount', '>', 0);
}
)
// left join transaction type.
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')
->orderBy('transaction_journals.id', 'DESC')
->orderBy('transaction_journals.description', 'DESC')
->orderBy('source.amount', 'DESC');
}
/** /**
* Automatically include all stuff required to make API calls work. * Automatically include all stuff required to make API calls work.
* *

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\Category;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -1077,6 +1078,15 @@ interface GroupCollectorInterface
*/ */
public function setDestinationAccounts(Collection $accounts): GroupCollectorInterface; public function setDestinationAccounts(Collection $accounts): GroupCollectorInterface;
/**
* Set the end time of the results to return.
*
* @param Carbon $end
*
* @return GroupCollectorInterface
*/
public function setEnd(Carbon $end): GroupCollectorInterface;
/** /**
* @param bool $expandGroupSearch * @param bool $expandGroupSearch
*/ */
@@ -1261,6 +1271,15 @@ interface GroupCollectorInterface
*/ */
public function setSourceAccounts(Collection $accounts): GroupCollectorInterface; public function setSourceAccounts(Collection $accounts): GroupCollectorInterface;
/**
* Set the start time of the results to return.
*
* @param Carbon $start
*
* @return GroupCollectorInterface
*/
public function setStart(Carbon $start): GroupCollectorInterface;
/** /**
* Limit results to a specific tag. * Limit results to a specific tag.
* *
@@ -1315,6 +1334,15 @@ interface GroupCollectorInterface
*/ */
public function setUser(User $user): GroupCollectorInterface; public function setUser(User $user): GroupCollectorInterface;
/**
* Set the user group object and start the query.
*
* @param UserGroup $userGroup
*
* @return GroupCollectorInterface
*/
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface;
/** /**
* Only when does not have these tags * Only when does not have these tags
* *

View File

@@ -27,9 +27,12 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -41,10 +44,98 @@ use JsonException;
*/ */
class NetWorth implements NetWorthInterface class NetWorth implements NetWorthInterface
{ {
private AccountRepositoryInterface $accountRepository; private AccountRepositoryInterface $accountRepository;
private AdminAccountRepositoryInterface $adminAccountRepository;
private CurrencyRepositoryInterface $currencyRepos; private CurrencyRepositoryInterface $currencyRepos;
private User $user; private User $user;
private UserGroup $userGroup;
/**
* @param Collection $accounts
* @param Carbon $date
*
* @return array
* @throws FireflyException
*/
public function byAccounts(Collection $accounts, Carbon $date): array
{
// start in the past, end in the future? use $date
$ids = implode(',', $accounts->pluck('id')->toArray());
$cache = new CacheProperties();
$cache->addProperty($date);
$cache->addProperty('net-worth-by-accounts');
$cache->addProperty($ids);
if ($cache->has()) {
//return $cache->get();
}
app('log')->debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d')));
$default = app('amount')->getDefaultCurrency();
$converter = new ExchangeRateConverter();
// default "native" currency has everything twice, for consistency.
$netWorth = [
'native' => [
'balance' => '0',
'native_balance' => '0',
'currency_id' => (int)$default->id,
'currency_code' => $default->code,
'currency_name' => $default->name,
'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places,
'native_id' => (int)$default->id,
'native_code' => $default->code,
'native_name' => $default->name,
'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places,
],
];
$balances = app('steam')->balancesByAccountsConverted($accounts, $date);
/** @var Account $account */
foreach ($accounts as $account) {
app('log')->debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->adminAccountRepository->getAccountCurrency($account);
$currencyId = (int)$currency->id;
$balance = '0';
$nativeBalance = '0';
if (array_key_exists((int)$account->id, $balances)) {
$balance = $balances[(int)$account->id]['balance'] ?? '0';
$nativeBalance = $balances[(int)$account->id]['native_balance'] ?? '0';
}
app('log')->debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance
$virtualBalance = (string)$account->virtual_balance;
if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance);
$nativeVirtualBalance = $converter->convert($default, $currency, $account->created_at, $virtualBalance);
$nativeBalance = bcsub($nativeBalance, $nativeVirtualBalance);
}
$netWorth[$currencyId] = $netWorth[$currencyId] ?? [
'balance' => '0',
'native_balance' => '0',
'currency_id' => $currencyId,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places,
'native_id' => (int)$default->id,
'native_code' => $default->code,
'native_name' => $default->name,
'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places,
];
$netWorth[$currencyId]['balance'] = bcadd($balance, $netWorth[$currencyId]['balance']);
$netWorth[$currencyId]['native_balance'] = bcadd($nativeBalance, $netWorth[$currencyId]['native_balance']);
$netWorth['native']['balance'] = bcadd($nativeBalance, $netWorth['native']['balance']);
$netWorth['native']['native_balance'] = bcadd($nativeBalance, $netWorth['native']['native_balance']);
}
$cache->store($netWorth);
return $netWorth;
}
/** /**
* Returns the user's net worth in an array with the following layout: * Returns the user's net worth in an array with the following layout:
@@ -146,6 +237,17 @@ class NetWorth implements NetWorthInterface
$this->currencyRepos->setUser($this->user); $this->currencyRepos->setUser($this->user);
} }
/**
* @inheritDoc
* @throws FireflyException
*/
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
$this->adminAccountRepository = app(AdminAccountRepositoryInterface::class);
$this->adminAccountRepository->setAdministrationId($userGroup->id);
}
/** /**
* @inheritDoc * @inheritDoc
*/ */

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Report; namespace FireflyIII\Helpers\Report;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\UserGroup;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -34,6 +35,21 @@ use Illuminate\Support\Collection;
*/ */
interface NetWorthInterface interface NetWorthInterface
{ {
/**
* Collect net worth based on the given set of accounts.
*
* Returns X arrays with the net worth in each given currency, and the net worth in
* of that amount in the native currency.
*
* Includes extra array with the total(!) net worth in the native currency.
*
* @param Collection $accounts
* @param Carbon $date
*
* @return array
*/
public function byAccounts(Collection $accounts, Carbon $date): array;
/** /**
* TODO unsure why this is deprecated. * TODO unsure why this is deprecated.
* *
@@ -60,6 +76,11 @@ interface NetWorthInterface
*/ */
public function setUser(User | Authenticatable | null $user): void; public function setUser(User | Authenticatable | null $user): void;
/**
* @param UserGroup $userGroup
*/
public function setUserGroup(UserGroup $userGroup): void;
/** /**
* TODO move to repository * TODO move to repository
* *

View File

@@ -198,6 +198,11 @@ class ShowController extends Controller
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation(); $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation();
// this search will not include transaction groups where this asset account (or liability)
// is just part of ONE of the journals. To force this:
$collector->setExpandGroupSearch(true);
$groups = $collector->getPaginatedGroups(); $groups = $collector->getPaginatedGroups();
$groups->setPath(route('accounts.show.all', [$account->id])); $groups->setPath(route('accounts.show.all', [$account->id]));
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);

View File

@@ -27,6 +27,7 @@ use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Support\Facades\FireflyConfig; use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
@@ -99,7 +100,7 @@ class HomeController extends Controller
if ('' === $url) { if ('' === $url) {
FireflyConfig::delete('slack_webhook_url'); FireflyConfig::delete('slack_webhook_url');
} }
if (str_starts_with($url, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($url)) {
FireflyConfig::set('slack_webhook_url', $url); FireflyConfig::set('slack_webhook_url', $url);
} }

View File

@@ -90,7 +90,7 @@ class LoginController extends Controller
Log::info('User is trying to login.'); Log::info('User is trying to login.');
$this->validateLogin($request); $this->validateLogin($request);
Log::debug('Login data is valid.'); Log::debug('Login data is present.');
/** Copied directly from AuthenticatesUsers, but with logging added: */ /** Copied directly from AuthenticatesUsers, but with logging added: */
// If the class is using the ThrottlesLogins trait, we can automatically throttle // If the class is using the ThrottlesLogins trait, we can automatically throttle

View File

@@ -146,7 +146,7 @@ class IndexController extends Controller
private function getSums(array $bills): array private function getSums(array $bills): array
{ {
$sums = []; $sums = [];
$range = app('navigation')->getViewRange(false); $range = app('navigation')->getViewRange(true);
/** @var array $group */ /** @var array $group */
foreach ($bills as $groupOrder => $group) { foreach ($bills as $groupOrder => $group) {
@@ -177,7 +177,6 @@ class IndexController extends Controller
$sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range)); $sums[$groupOrder][$currencyId]['per_period'] = bcadd($sums[$groupOrder][$currencyId]['per_period'], $this->amountPerPeriod($bill, $range));
} }
} }
return $sums; return $sums;
} }

View File

@@ -101,6 +101,7 @@ class IndexController extends Controller
*/ */
public function index(Request $request, Carbon $start = null, Carbon $end = null) public function index(Request $request, Carbon $start = null, Carbon $end = null)
{ {
$this->abRepository->cleanup();
Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d'))); Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
// collect some basic vars: // collect some basic vars:
@@ -226,6 +227,7 @@ class IndexController extends Controller
Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name)); Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name));
$array = $current->toArray(); $array = $current->toArray();
$array['spent'] = []; $array['spent'] = [];
$array['spent_total'] = [];
$array['budgeted'] = []; $array['budgeted'] = [];
$array['attachments'] = $this->repository->getAttachments($current); $array['attachments'] = $this->repository->getAttachments($current);
$array['auto_budget'] = $this->repository->getAutoBudget($current); $array['auto_budget'] = $this->repository->getAutoBudget($current);
@@ -234,9 +236,10 @@ class IndexController extends Controller
foreach ($budgetLimits as $limit) { foreach ($budgetLimits as $limit) {
Log::debug(sprintf('Working on budget limit #%d', $limit->id)); Log::debug(sprintf('Working on budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency ?? $defaultCurrency; $currency = $limit->transactionCurrency ?? $defaultCurrency;
$amount = app('steam')->bcround($limit->amount, $currency->decimal_places);
$array['budgeted'][] = [ $array['budgeted'][] = [
'id' => $limit->id, 'id' => $limit->id,
'amount' => app('steam')->bcround($limit->amount, $currency->decimal_places), 'amount' => $amount,
'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat), 'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat), 'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
@@ -245,6 +248,7 @@ class IndexController extends Controller
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
]; ];
Log::debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount));
} }
/** @var TransactionCurrency $currency */ /** @var TransactionCurrency $currency */
@@ -337,6 +341,7 @@ class IndexController extends Controller
*/ */
public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
{ {
$this->abRepository->cleanup();
$budgetIds = $request->get('budgetIds'); $budgetIds = $request->get('budgetIds');
foreach ($budgetIds as $index => $budgetId) { foreach ($budgetIds as $index => $budgetId) {

View File

@@ -28,7 +28,10 @@ use Exception;
use FireflyConfig; use FireflyConfig;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Http\Controllers\GetConfigurationData; use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -37,6 +40,8 @@ use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\View\View; use Illuminate\View\View;
use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\RotatingFileHandler;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* Class DebugController * Class DebugController
@@ -117,86 +122,9 @@ class DebugController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
// basic scope information: $table = $this->generateTable();
$now = now(config('app.timezone'))->format('Y-m-d H:i:s e'); $table = str_replace(["\n", "\t", ' '], '', $table);
$buildNr = '(unknown)'; $now = now(config('app.timezone'))->format('Y-m-d H:i:s');
$buildDate = '(unknown)';
$baseBuildNr = '(unknown)';
$baseBuildDate = '(unknown)';
$expectedDBversion = config('firefly.db_version');
$foundDBversion = FireflyConfig::get('db_version', 1)->data;
try {
if (file_exists('/var/www/counter-main.txt')) {
$buildNr = trim(file_get_contents('/var/www/counter-main.txt'));
}
} catch (Exception $e) { // generic catch for open basedir.
Log::debug('Could not check counter.');
Log::warning($e->getMessage());
}
try {
if (file_exists('/var/www/build-date-main.txt')) {
$buildDate = trim(file_get_contents('/var/www/build-date-main.txt'));
}
} catch (Exception $e) { // generic catch for open basedir.
Log::debug('Could not check date.');
Log::warning($e->getMessage());
}
if ('' !== (string)env('BASE_IMAGE_BUILD')) {
$baseBuildNr = env('BASE_IMAGE_BUILD');
}
if ('' !== (string)env('BASE_IMAGE_DATE')) {
$baseBuildDate = env('BASE_IMAGE_DATE');
}
$phpVersion = PHP_VERSION;
$phpOs = PHP_OS;
// system information
$tz = env('TZ');
$appEnv = config('app.env');
$appDebug = var_export(config('app.debug'), true);
$cacheDriver = config('cache.default');
$logChannel = config('logging.default');
$appLogLevel = config('logging.level');
$maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes(ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize);
$displayErrors = ini_get('display_errors');
$errorReporting = $this->errorReporting((int)ini_get('error_reporting'));
$interface = PHP_SAPI;
$defaultLanguage = (string)config('firefly.default_language');
$defaultLocale = (string)config('firefly.default_locale');
$bcscale = bcscale();
$drivers = implode(', ', DB::availableDrivers());
$currentDriver = DB::getDriverName();
$trustedProxies = config('firefly.trusted_proxies');
// user info
$loginProvider = config('auth.providers.users.driver');
$userGuard = config('auth.defaults.guard');
$remoteHeader = $userGuard === 'remote_user_guard' ? config('auth.guard_header') : 'N/A';
$remoteMailHeader = $userGuard === 'remote_user_guard' ? config('auth.guard_email') : 'N/A';
$userLanguage = app('steam')->getLanguage();
$userLocale = app('steam')->getLocale();
$userAgent = $request->header('user-agent');
$stateful = join(', ', config('sanctum.stateful'));
// expected + found DB version:
// some new vars.
$isDocker = env('IS_DOCKER', false);
// set languages, see what happens:
$original = setlocale(LC_ALL, 0);
$localeAttempts = [];
$parts = app('steam')->getLocaleArray(app('steam')->getLocale());
foreach ($parts as $code) {
$code = trim($code);
Log::debug(sprintf('Trying to set %s', $code));
$localeAttempts[$code] = var_export(setlocale(LC_ALL, $code), true);
}
setlocale(LC_ALL, $original);
// get latest log file: // get latest log file:
$logger = Log::driver(); $logger = Log::driver();
@@ -213,50 +141,190 @@ class DebugController extends Controller
} }
if ('' !== $logContent) { if ('' !== $logContent) {
// last few lines // last few lines
$logContent = 'Truncated from this point <----|' . substr($logContent, -8192); $logContent = 'Truncated from this point <----|' . substr($logContent, -16384);
} }
return view( return view('debug', compact('table', 'now', 'logContent'));
'debug', }
compact(
'phpVersion', /**
'localeAttempts', * @return string
'expectedDBversion', */
'foundDBversion', private function generateTable(): string
'appEnv', {
'appDebug', // system information:
'logChannel', $system = $this->getSystemInformation();
'stateful', $docker = $this->getBuildInfo();
'tz', $app = $this->getAppInfo();
'uploadSize', $user = $this->getuserInfo();
'appLogLevel',
'remoteHeader', return (string)view('partials.debug-table', compact('system', 'docker', 'app', 'user'));
'remoteMailHeader', }
'now',
'drivers', /**
'currentDriver', * @return array
'userGuard', */
'loginProvider', private function getSystemInformation(): array
'buildNr', {
'buildDate', $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize'));
'baseBuildNr', $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size'));
'baseBuildDate', $drivers = DB::availableDrivers();
'bcscale', $currentDriver = DB::getDriverName();
'userAgent', return [
'displayErrors', 'db_version' => app('fireflyconfig')->get('db_version', 1)->data,
'errorReporting', 'php_version' => PHP_VERSION,
'phpOs', 'php_os' => PHP_OS,
'interface', 'interface' => PHP_SAPI,
'logContent', 'bcscale' => bcscale(),
'cacheDriver', 'display_errors' => ini_get('display_errors'),
'trustedProxies', 'error_reporting' => $this->errorReporting((int)ini_get('error_reporting')),
'userLanguage', 'upload_size' => min($maxFileSize, $maxPostSize),
'userLocale', 'all_drivers' => $drivers,
'defaultLanguage', 'current_driver' => $currentDriver,
'defaultLocale', ];
'isDocker' }
)
); /**
* @return array
*/
private function getBuildInfo(): array
{
$return = [
'is_docker' => env('IS_DOCKER', false),
'build' => '(unknown)',
'build_date' => '(unknown)',
'base_build' => '(unknown)',
'base_build_date' => '(unknown)',
];
try {
if (file_exists('/var/www/counter-main.txt')) {
$return['build'] = trim(file_get_contents('/var/www/counter-main.txt'));
}
} catch (Exception $e) { // generic catch for open basedir.
Log::debug('Could not check build counter, but thats ok.');
Log::warning($e->getMessage());
}
try {
if (file_exists('/var/www/build-date-main.txt')) {
$return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt'));
}
} catch (Exception $e) { // generic catch for open basedir.
Log::debug('Could not check build date, but thats ok.');
Log::warning($e->getMessage());
}
if ('' !== (string)env('BASE_IMAGE_BUILD')) {
$return['base_build'] = env('BASE_IMAGE_BUILD');
}
if ('' !== (string)env('BASE_IMAGE_DATE')) {
$return['base_build_date'] = env('BASE_IMAGE_DATE');
}
return $return;
}
/**
* @return array
*/
private function getAppInfo(): array
{
$userGuard = config('auth.defaults.guard');
return [
'tz' => env('TZ'),
'debug' => var_export(config('app.debug'), true),
'log_channel' => env('LOG_CHANNEL'),
'audit_log_channel' => envNonEmpty('AUDIT_LOG_CHANNEL', '(empty)'),
'default_language' => (string)config('firefly.default_language'),
'default_locale' => (string)config('firefly.default_locale'),
'remote_header' => $userGuard === 'remote_user_guard' ? config('auth.guard_header') : 'N/A',
'remote_mail_header' => $userGuard === 'remote_user_guard' ? config('auth.guard_email') : 'N/A',
'stateful_domains' => join(', ', config('sanctum.stateful')),
];
}
/**
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function getuserInfo(): array
{
$userFlags = $this->getUserFlags();
// user info
$userAgent = request()->header('user-agent');
// set languages, see what happens:
$original = setlocale(LC_ALL, 0);
$localeAttempts = [];
$parts = app('steam')->getLocaleArray(app('steam')->getLocale());
foreach ($parts as $code) {
$code = trim($code);
Log::debug(sprintf('Trying to set %s', $code));
$result = setlocale(LC_ALL, $code);
$localeAttempts[$code] = $result === $code;
}
setlocale(LC_ALL, $original);
return [
'user_id' => auth()->user()->id,
'user_count' => User::count(),
'user_flags' => $userFlags,
'user_agent' => $userAgent,
'locale_attempts' => $localeAttempts,
'locale' => app('steam')->getLocale(),
'language' => app('steam')->getLanguage(),
'view_range' => app('preferences')->get('viewRange', '1M')->data,
];
}
/**
* @return string
*/
private function getUserFlags(): string
{
$flags = [];
/** @var User $user */
$user = auth()->user();
// has liabilities
if ($user->accounts()->accountTypeIn([AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE])->count() > 0) {
$flags[] = ':credit_card:';
}
// has piggies
if ($user->piggyBanks()->count() > 0) {
$flags[] = ':pig:';
}
// has stored reconciliations
$type = TransactionType::whereType(TransactionType::RECONCILIATION)->first();
if ($user->transactionJournals()->where('transaction_type_id', $type->id)->count()) {
$flags[] = ':ledger:';
}
// has used importer?
// has rules
if ($user->rules()->count() > 0) {
$flags[] = ':wrench:';
}
// has recurring transactions
if ($user->recurrences()->count() > 0) {
$flags[] = ':clock130:';
}
// has groups
if ($user->objectGroups()->count() > 0) {
$flags[] = ':bookmark_tabs:';
}
// uses bills
if ($user->bills()->count() > 0) {
$flags[] = ':email:';
}
return join(' ', $flags);
} }
/** /**
@@ -275,4 +343,5 @@ class DebugController extends Controller
return redirect(route('home')); return redirect(route('home'));
} }
} }

View File

@@ -138,6 +138,9 @@ class HomeController extends Controller
$accounts = $repository->getAccountsById($frontPage->data); $accounts = $repository->getAccountsById($frontPage->data);
$today = today(config('app.timezone')); $today = today(config('app.timezone'));
// sort frontpage accounts by order
$accounts = $accounts->sortBy('order');
Log::debug('Frontpage accounts are ', $frontPage->data); Log::debug('Frontpage accounts are ', $frontPage->data);
/** @var BillRepositoryInterface $billRepository */ /** @var BillRepositoryInterface $billRepository */

View File

@@ -55,10 +55,12 @@ class BoxController extends Controller
*/ */
public function available(): JsonResponse public function available(): JsonResponse
{ {
app('log')->debug('Now in available()');
/** @var OperationsRepositoryInterface $opsRepository */ /** @var OperationsRepositoryInterface $opsRepository */
$opsRepository = app(OperationsRepositoryInterface::class); $opsRepository = app(OperationsRepositoryInterface::class);
/** @var AvailableBudgetRepositoryInterface $abRepository */ /** @var AvailableBudgetRepositoryInterface $abRepository */
$abRepository = app(AvailableBudgetRepositoryInterface::class); $abRepository = app(AvailableBudgetRepositoryInterface::class);
$abRepository->cleanup();
/** @var Carbon $start */ /** @var Carbon $start */
$start = session('start', today(config('app.timezone'))->startOfMonth()); $start = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */ /** @var Carbon $end */
@@ -73,40 +75,57 @@ class BoxController extends Controller
$cache->addProperty($today); $cache->addProperty($today);
$cache->addProperty('box-available'); $cache->addProperty('box-available');
if ($cache->has()) { if ($cache->has()) {
return response()->json($cache->get()); //return response()->json($cache->get());
} }
$leftPerDayAmount = '0'; $leftPerDayAmount = '0';
$leftToSpendAmount = '0'; $leftToSpendAmount = '0';
$currency = app('amount')->getDefaultCurrency(); $currency = app('amount')->getDefaultCurrency();
$availableBudgets = $abRepository->getAvailableBudgetsByDate($start, $end); app('log')->debug(sprintf('Default currency is %s', $currency->code));
$availableBudgets = $abRepository->getAvailableBudgetsByExactDate($start, $end);
app('log')->debug(sprintf('Found %d available budget(s)', $availableBudgets->count()));
$availableBudgets = $availableBudgets->filter( $availableBudgets = $availableBudgets->filter(
static function (AvailableBudget $availableBudget) use ($currency) { static function (AvailableBudget $availableBudget) use ($currency) {
if ($availableBudget->transaction_currency_id === $currency->id) { if ($availableBudget->transaction_currency_id === $currency->id) {
app('log')->debug(sprintf(
'Will include AB #%d: from %s-%s amount %s',
$availableBudget->id,
$availableBudget->start_date->format('Y-m-d'),
$availableBudget->end_date->format('Y-m-d'),
$availableBudget->amount
));
return $availableBudget; return $availableBudget;
} }
return null; return null;
} }
); );
app('log')->debug(sprintf('Filtered back to %d available budgets', $availableBudgets->count()));
// spent in this period, in budgets, for default currency. // spent in this period, in budgets, for default currency.
// also calculate spent per day. // also calculate spent per day.
$spent = $opsRepository->sumExpenses($start, $end, null, null, $currency); $spent = $opsRepository->sumExpenses($start, $end, null, null, $currency);
$spentAmount = $spent[(int)$currency->id]['sum'] ?? '0'; $spentAmount = $spent[(int)$currency->id]['sum'] ?? '0';
app('log')->debug(sprintf('Spent for default currency for all budgets in this period: %s', $spentAmount));
$days = $today->between($start, $end) ? $today->diffInDays($start) + 1 : $end->diffInDays($start) + 1; $days = $today->between($start, $end) ? $today->diffInDays($start) + 1 : $end->diffInDays($start) + 1;
app('log')->debug(sprintf('Number of days left: %d', $days));
$spentPerDay = bcdiv($spentAmount, (string)$days); $spentPerDay = bcdiv($spentAmount, (string)$days);
app('log')->debug(sprintf('Available to spend per day: %s', $spentPerDay));
if ($availableBudgets->count() > 0) { if ($availableBudgets->count() > 0) {
$display = 0; // assume user overspent $display = 0; // assume user overspent
$boxTitle = (string)trans('firefly.overspent'); $boxTitle = (string)trans('firefly.overspent');
$totalAvailableSum = (string)$availableBudgets->sum('amount'); $totalAvailableSum = (string)$availableBudgets->sum('amount');
app('log')->debug(sprintf('Total available sum is %s', $totalAvailableSum));
// calculate with available budget. // calculate with available budget.
$leftToSpendAmount = bcadd($totalAvailableSum, $spentAmount); $leftToSpendAmount = bcadd($totalAvailableSum, $spentAmount);
app('log')->debug(sprintf('So left to spend is %s', $leftToSpendAmount));
if (1 === bccomp($leftToSpendAmount, '0')) { if (1 === bccomp($leftToSpendAmount, '0')) {
app('log')->debug(sprintf('Left to spend is positive!'));
$boxTitle = (string)trans('firefly.left_to_spend'); $boxTitle = (string)trans('firefly.left_to_spend');
$days = $today->diffInDays($end) + 1; $days = $today->diffInDays($end) + 1;
$display = 1; // not overspent $display = 1; // not overspent
$leftPerDayAmount = bcdiv($leftToSpendAmount, (string)$days); $leftPerDayAmount = bcdiv($leftToSpendAmount, (string)$days);
app('log')->debug(sprintf('Left to spend per day is %s', $leftPerDayAmount));
} }
} }
@@ -118,9 +137,10 @@ class BoxController extends Controller
'left_per_day' => app('amount')->formatAnything($currency, $leftPerDayAmount, false), 'left_per_day' => app('amount')->formatAnything($currency, $leftPerDayAmount, false),
'title' => $boxTitle, 'title' => $boxTitle,
]; ];
app('log')->debug('Final output', $return);
$cache->store($return); $cache->store($return);
app('log')->debug('Now done with available()');
return response()->json($return); return response()->json($return);
} }

View File

@@ -75,6 +75,7 @@ class FrontpageController extends Controller
$html = view('json.piggy-banks', compact('info'))->render(); $html = view('json.piggy-banks', compact('info'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render json.piggy-banks: %s', $e->getMessage())); Log::error(sprintf('Cannot render json.piggy-banks: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$html = 'Could not render view.'; $html = 'Could not render view.';
throw new FireflyException($html, 0, $e); throw new FireflyException($html, 0, $e);
} }

View File

@@ -157,6 +157,7 @@ class ReconcileController extends Controller
)->render(); )->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('View error: %s', $e->getMessage())); Log::debug(sprintf('View error: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$view = sprintf('Could not render accounts.reconcile.overview: %s', $e->getMessage()); $view = sprintf('Could not render accounts.reconcile.overview: %s', $e->getMessage());
throw new FireflyException($view, 0, $e); throw new FireflyException($view, 0, $e);
} }
@@ -258,6 +259,7 @@ class ReconcileController extends Controller
)->render(); )->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render: %s', $e->getMessage())); Log::debug(sprintf('Could not render: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$html = sprintf('Could not render accounts.reconcile.transactions: %s', $e->getMessage()); $html = sprintf('Could not render accounts.reconcile.transactions: %s', $e->getMessage());
throw new FireflyException($html, 0, $e); throw new FireflyException($html, 0, $e);
} }

View File

@@ -55,6 +55,7 @@ class RuleController extends Controller
$view = view('rules.partials.action', compact('actions', 'count'))->render(); $view = view('rules.partials.action', compact('actions', 'count'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render rules.partials.action: %s', $e->getMessage())); Log::error(sprintf('Cannot render rules.partials.action: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$view = 'Could not render view.'; $view = 'Could not render view.';
throw new FireflyException($view, 0, $e); throw new FireflyException($view, 0, $e);
} }
@@ -86,6 +87,7 @@ class RuleController extends Controller
$view = view('rules.partials.trigger', compact('triggers', 'count'))->render(); $view = view('rules.partials.trigger', compact('triggers', 'count'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render rules.partials.trigger: %s', $e->getMessage())); Log::error(sprintf('Cannot render rules.partials.trigger: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$view = 'Could not render view.'; $view = 'Could not render view.';
throw new FireflyException($view, 0, $e); throw new FireflyException($view, 0, $e);
} }

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Preference; use FireflyIII\Models\Preference;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Notifications\UrlValidator;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -207,7 +208,7 @@ class PreferencesController extends Controller
// slack URL: // slack URL:
if (!auth()->user()->hasRole('demo')) { if (!auth()->user()->hasRole('demo')) {
$url = (string)$request->get('slackUrl'); $url = (string)$request->get('slackUrl');
if (str_starts_with($url, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($url)) {
app('preferences')->set('slack_webhook_url', $url); app('preferences')->set('slack_webhook_url', $url);
} }
if ('' === $url) { if ('' === $url) {

View File

@@ -65,7 +65,8 @@ class AccountController extends Controller
try { try {
$result = view('reports.partials.accounts', compact('accountReport'))->render(); $result = view('reports.partials.accounts', compact('accountReport'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.accounts: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.accounts: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -141,7 +141,8 @@ class BalanceController extends Controller
try { try {
$result = view('reports.partials.balance', compact('report'))->render(); $result = view('reports.partials.balance', compact('report'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.balance: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.balance: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -61,7 +61,8 @@ class BillController extends Controller
try { try {
$result = view('reports.partials.bills', compact('report'))->render(); $result = view('reports.partials.bills', compact('report'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budgets: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budgets: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -195,8 +195,9 @@ class BudgetController extends Controller
try { try {
$result = view('reports.budget.partials.avg-expenses', compact('result'))->render(); $result = view('reports.budget.partials.avg-expenses', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }
@@ -353,7 +354,8 @@ class BudgetController extends Controller
try { try {
$result = view('reports.partials.budget-period', compact('report', 'periods'))->render(); $result = view('reports.partials.budget-period', compact('report', 'periods'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }
@@ -406,7 +408,7 @@ class BudgetController extends Controller
try { try {
$result = view('reports.budget.partials.top-expenses', compact('result'))->render(); $result = view('reports.budget.partials.top-expenses', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -315,7 +315,7 @@ class CategoryController extends Controller
try { try {
$result = view('reports.category.partials.avg-expenses', compact('result'))->render(); $result = view('reports.category.partials.avg-expenses', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }
@@ -368,7 +368,7 @@ class CategoryController extends Controller
try { try {
$result = view('reports.category.partials.avg-income', compact('result'))->render(); $result = view('reports.category.partials.avg-income', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -115,7 +115,7 @@ class DoubleController extends Controller
try { try {
$result = view('reports.double.partials.avg-expenses', compact('result'))->render(); $result = view('reports.double.partials.avg-expenses', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
@@ -168,7 +168,7 @@ class DoubleController extends Controller
try { try {
$result = view('reports.double.partials.avg-income', compact('result'))->render(); $result = view('reports.double.partials.avg-income', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
@@ -463,7 +463,7 @@ class DoubleController extends Controller
try { try {
$result = view('reports.double.partials.top-expenses', compact('result'))->render(); $result = view('reports.double.partials.top-expenses', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }
@@ -514,7 +514,7 @@ class DoubleController extends Controller
try { try {
$result = view('reports.double.partials.top-income', compact('result'))->render(); $result = view('reports.double.partials.top-income', compact('result'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.budget-period: %s', $e->getMessage()));
$result = sprintf('Could not render view: %s', $e->getMessage()); $result = sprintf('Could not render view: %s', $e->getMessage());
throw new FireflyException($e->getMessage(), 0, $e); throw new FireflyException($e->getMessage(), 0, $e);
} }

View File

@@ -85,7 +85,8 @@ class OperationsController extends Controller
try { try {
$result = view('reports.partials.income-expenses', compact('report', 'type'))->render(); $result = view('reports.partials.income-expenses', compact('report', 'type'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.income-expense: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.income-expense: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }
@@ -121,7 +122,8 @@ class OperationsController extends Controller
try { try {
$result = view('reports.partials.income-expenses', compact('report', 'type'))->render(); $result = view('reports.partials.income-expenses', compact('report', 'type'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.income-expenses: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.income-expenses: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }
@@ -177,7 +179,8 @@ class OperationsController extends Controller
try { try {
$result = view('reports.partials.operations', compact('sums'))->render(); $result = view('reports.partials.operations', compact('sums'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::debug(sprintf('Could not render reports.partials.operations: %s', $e->getMessage())); Log::error(sprintf('Could not render reports.partials.operations: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$result = 'Could not render view.'; $result = 'Could not render view.';
throw new FireflyException($result, 0, $e); throw new FireflyException($result, 0, $e);
} }

View File

@@ -128,6 +128,7 @@ class SearchController extends Controller
$html = view('search.search', compact('groups', 'hasPages', 'searchTime'))->render(); $html = view('search.search', compact('groups', 'hasPages', 'searchTime'))->render();
} catch (Throwable $e) { } catch (Throwable $e) {
Log::error(sprintf('Cannot render search.search: %s', $e->getMessage())); Log::error(sprintf('Cannot render search.search: %s', $e->getMessage()));
Log::error($e->getTraceAsString());
$html = 'Could not render view.'; $html = 'Could not render view.';
throw new FireflyException($html, 0, $e); throw new FireflyException($html, 0, $e);
} }

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Middleware;
use Closure; use Closure;
use Exception; use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
/** /**
* *
@@ -46,6 +47,7 @@ class SecureHeaders
{ {
// generate and share nonce. // generate and share nonce.
$nonce = base64_encode(random_bytes(16)); $nonce = base64_encode(random_bytes(16));
Vite::useCspNonce($nonce);
app('view')->share('JS_NONCE', $nonce); app('view')->share('JS_NONCE', $nonce);
$response = $next($request); $response = $next($request);

View File

@@ -158,6 +158,7 @@ class ReportFormRequest extends FormRequest
} catch (Exception $e) { // intentional generic exception } catch (Exception $e) { // intentional generic exception
$error = sprintf('"%s" is not a valid date range: %s', $range, $e->getMessage()); $error = sprintf('"%s" is not a valid date range: %s', $range, $e->getMessage());
Log::error($error); Log::error($error);
Log::error($e->getTraceAsString());
throw new FireflyException($error, 0, $e); throw new FireflyException($error, 0, $e);
} }
return $date; return $date;
@@ -192,6 +193,7 @@ class ReportFormRequest extends FormRequest
} catch (Exception $e) { // intentional generic exception } catch (Exception $e) { // intentional generic exception
$error = sprintf('"%s" is not a valid date range: %s', $range, $e->getMessage()); $error = sprintf('"%s" is not a valid date range: %s', $range, $e->getMessage());
Log::error($error); Log::error($error);
Log::error($e->getTraceAsString());
throw new FireflyException($error, 0, $e); throw new FireflyException($error, 0, $e);
} }
return $date; return $date;
@@ -220,19 +222,18 @@ class ReportFormRequest extends FormRequest
} }
if (!is_array($set)) { if (!is_array($set)) {
Log::error(sprintf('Set is not an array! "%s"', $set)); Log::error(sprintf('Set is not an array! "%s"', $set));
return $collection;
} }
if (is_array($set)) { foreach ($set as $tagTag) {
foreach ($set as $tagTag) { Log::debug(sprintf('Now searching for "%s"', $tagTag));
Log::debug(sprintf('Now searching for "%s"', $tagTag)); $tag = $repository->findByTag($tagTag);
$tag = $repository->findByTag($tagTag); if (null !== $tag) {
if (null !== $tag) { $collection->push($tag);
$collection->push($tag); continue;
continue; }
} $tag = $repository->find((int)$tagTag);
$tag = $repository->find((int)$tagTag); if (null !== $tag) {
if (null !== $tag) { $collection->push($tag);
$collection->push($tag);
}
} }
} }

View File

@@ -125,7 +125,7 @@ class Account extends Model
'encrypted' => 'boolean', 'encrypted' => 'boolean',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban']; protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban'];
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];
private bool $joinedAccountTypes = false; private bool $joinedAccountTypes = false;

View File

@@ -85,7 +85,7 @@ class AvailableBudget extends Model
'transaction_currency_id' => 'int', 'transaction_currency_id' => 'int',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; protected $fillable = ['user_id', 'user_group_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -130,6 +130,7 @@ class Bill extends Model
'match', 'match',
'amount_min', 'amount_min',
'user_id', 'user_id',
'user_group_id',
'amount_max', 'amount_max',
'date', 'date',
'repeat_freq', 'repeat_freq',

View File

@@ -99,7 +99,7 @@ class Budget extends Model
'encrypted' => 'boolean', 'encrypted' => 'boolean',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'name', 'active', 'order']; protected $fillable = ['user_id', 'name', 'active', 'order', 'user_group_id'];
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];

View File

@@ -89,7 +89,7 @@ class Category extends Model
'encrypted' => 'boolean', 'encrypted' => 'boolean',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'name']; protected $fillable = ['user_id', 'user_group_id', 'name'];
/** @var array Hidden from view */ /** @var array Hidden from view */
protected $hidden = ['encrypted']; protected $hidden = ['encrypted'];

View File

@@ -61,6 +61,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|ObjectGroup whereTitle($value) * @method static Builder|ObjectGroup whereTitle($value)
* @method static Builder|ObjectGroup whereUpdatedAt($value) * @method static Builder|ObjectGroup whereUpdatedAt($value)
* @method static Builder|ObjectGroup whereUserId($value) * @method static Builder|ObjectGroup whereUserId($value)
* @property int|null $user_group_id
* @method static Builder|ObjectGroup whereUserGroupId($value)
* @mixin Eloquent * @mixin Eloquent
*/ */
class ObjectGroup extends Model class ObjectGroup extends Model
@@ -77,7 +79,7 @@ class ObjectGroup extends Model
'user_id' => 'integer', 'user_id' => 'integer',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
]; ];
protected $fillable = ['title', 'order', 'user_id']; protected $fillable = ['title', 'order', 'user_id', 'user_group_id'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -76,6 +76,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|Rule withoutTrashed() * @method static Builder|Rule withoutTrashed()
* @property int|null $user_group_id * @property int|null $user_group_id
* @method static \Illuminate\Database\Eloquent\Builder|Rule whereUserGroupId($value) * @method static \Illuminate\Database\Eloquent\Builder|Rule whereUserGroupId($value)
* @property-read \FireflyIII\Models\UserGroup|null $userGroup
* @mixin Eloquent * @mixin Eloquent
*/ */
class Rule extends Model class Rule extends Model
@@ -165,4 +166,12 @@ class Rule extends Model
{ {
$this->attributes['description'] = e($value); $this->attributes['description'] = e($value);
} }
/**
* @return BelongsTo
*/
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
} }

View File

@@ -99,7 +99,7 @@ class Tag extends Model
'longitude' => 'float', 'longitude' => 'float',
]; ];
/** @var array Fields that can be filled */ /** @var array Fields that can be filled */
protected $fillable = ['user_id', 'tag', 'date', 'description', 'tagMode']; protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'description', 'tagMode'];
protected $hidden = ['zoomLevel', 'latitude', 'longitude']; protected $hidden = ['zoomLevel', 'latitude', 'longitude'];

View File

@@ -61,6 +61,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @method static Builder|TransactionGroup withoutTrashed() * @method static Builder|TransactionGroup withoutTrashed()
* @property int|null $user_group_id * @property int|null $user_group_id
* @method static \Illuminate\Database\Eloquent\Builder|TransactionGroup whereUserGroupId($value) * @method static \Illuminate\Database\Eloquent\Builder|TransactionGroup whereUserGroupId($value)
* @property-read \FireflyIII\Models\UserGroup|null $userGroup
* @mixin Eloquent * @mixin Eloquent
*/ */
class TransactionGroup extends Model class TransactionGroup extends Model
@@ -130,4 +131,12 @@ class TransactionGroup extends Model
{ {
return $this->hasMany(TransactionJournal::class); return $this->hasMany(TransactionJournal::class);
} }
/**
* @return BelongsTo
*/
public function userGroup(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
} }

View File

@@ -148,6 +148,7 @@ class TransactionJournal extends Model
protected $fillable protected $fillable
= [ = [
'user_id', 'user_id',
'user_group_id',
'transaction_type_id', 'transaction_type_id',
'bill_id', 'bill_id',
'tag_count', 'tag_count',

View File

@@ -29,6 +29,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
/** /**
@@ -51,6 +52,16 @@ use Illuminate\Support\Carbon;
* @method static Builder|UserGroup whereUpdatedAt($value) * @method static Builder|UserGroup whereUpdatedAt($value)
* @property-read Collection<int, Account> $accounts * @property-read Collection<int, Account> $accounts
* @property-read int|null $accounts_count * @property-read int|null $accounts_count
* @property-read Collection<int, \FireflyIII\Models\AvailableBudget> $availableBudgets
* @property-read int|null $available_budgets_count
* @property-read Collection<int, \FireflyIII\Models\Bill> $bills
* @property-read int|null $bills_count
* @property-read Collection<int, \FireflyIII\Models\Budget> $budgets
* @property-read int|null $budgets_count
* @property-read Collection<int, \FireflyIII\Models\PiggyBank> $piggyBanks
* @property-read int|null $piggy_banks_count
* @property-read Collection<int, \FireflyIII\Models\TransactionJournal> $transactionJournals
* @property-read int|null $transaction_journals_count
* @mixin Eloquent * @mixin Eloquent
*/ */
class UserGroup extends Model class UserGroup extends Model
@@ -67,6 +78,36 @@ class UserGroup extends Model
return $this->hasMany(Account::class); return $this->hasMany(Account::class);
} }
/**
* Link to bills.
*
* @return HasMany
*/
public function availableBudgets(): HasMany
{
return $this->hasMany(AvailableBudget::class);
}
/**
* Link to bills.
*
* @return HasMany
*/
public function bills(): HasMany
{
return $this->hasMany(Bill::class);
}
/**
* Link to budgets.
*
* @return HasMany
*/
public function budgets(): HasMany
{
return $this->hasMany(Budget::class);
}
/** /**
* *
* @return HasMany * @return HasMany
@@ -75,4 +116,24 @@ class UserGroup extends Model
{ {
return $this->hasMany(GroupMembership::class); return $this->hasMany(GroupMembership::class);
} }
/**
* Link to piggy banks.
*
* @return HasManyThrough
*/
public function piggyBanks(): HasManyThrough
{
return $this->hasManyThrough(PiggyBank::class, Account::class);
}
/**
* Link to transaction journals.
*
* @return HasMany
*/
public function transactionJournals(): HasMany
{
return $this->hasMany(TransactionJournal::class);
}
} }

View File

@@ -88,7 +88,7 @@ class Webhook extends Model
'response' => 'integer', 'response' => 'integer',
'delivery' => 'integer', 'delivery' => 'integer',
]; ];
protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'url', 'title', 'secret']; protected $fillable = ['active', 'trigger', 'response', 'delivery', 'user_id', 'user_group_id', 'url', 'title', 'secret'];
/** /**
* @return array * @return array

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Admin; namespace FireflyIII\Notifications\Admin;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -99,9 +100,9 @@ class TestNotification extends Notification
public function via($notifiable) public function via($notifiable)
{ {
/** @var User|null $user */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
$slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data; $slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (str_starts_with($slackUrl, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($slackUrl)) {
return ['mail', 'slack']; return ['mail', 'slack'];
} }
return ['mail']; return ['mail'];

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Admin; namespace FireflyIII\Notifications\Admin;
use FireflyIII\Models\InvitedUser; use FireflyIII\Models\InvitedUser;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -102,9 +103,9 @@ class UserInvitation extends Notification
public function via($notifiable) public function via($notifiable)
{ {
/** @var User|null $user */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
$slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data; $slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (str_starts_with($slackUrl, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($slackUrl)) {
return ['mail', 'slack']; return ['mail', 'slack'];
} }
return ['mail']; return ['mail'];

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Admin; namespace FireflyIII\Notifications\Admin;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -99,9 +100,9 @@ class UserRegistration extends Notification
public function via($notifiable) public function via($notifiable)
{ {
/** @var User|null $user */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
$slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data; $slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (str_starts_with($slackUrl, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($slackUrl)) {
return ['mail', 'slack']; return ['mail', 'slack'];
} }
return ['mail']; return ['mail'];

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Admin; namespace FireflyIII\Notifications\Admin;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -87,21 +88,10 @@ class VersionCheckResult extends Notification
*/ */
public function toSlack($notifiable) public function toSlack($notifiable)
{ {
// return (new SlackMessage())->text($this->message)
// ->sectionBlock(function (SectionBlock $block) {
// $button = new ButtonElement('Button');
// $button->url('https://github.com/firefly-iii/firefly-iii/releases');
// $block->accessory($button);
// });
//// ->attachment(function ($attachment) {
//// $attachment->title('Firefly III @ GitHub', 'https://github.com/firefly-iii/firefly-iii/releases');
//// });
return (new SlackMessage())->content($this->message) return (new SlackMessage())->content($this->message)
->attachment(function ($attachment) { ->attachment(function ($attachment) {
$attachment->title('Firefly III @ GitHub', 'https://github.com/firefly-iii/firefly-iii/releases'); $attachment->title('Firefly III @ GitHub', 'https://github.com/firefly-iii/firefly-iii/releases');
}); });
} }
/** /**
@@ -114,9 +104,9 @@ class VersionCheckResult extends Notification
public function via($notifiable) public function via($notifiable)
{ {
/** @var User|null $user */ /** @var User|null $user */
$user = auth()->user(); $user = auth()->user();
$slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data; $slackUrl = null === $user ? '' : (string)app('preferences')->getForUser(auth()->user(), 'slack_webhook_url', '')->data;
if (str_starts_with($slackUrl, 'https://hooks.slack.com/services/')) { if (UrlValidator::isValidWebhookURL($slackUrl)) {
return ['mail', 'slack']; return ['mail', 'slack'];
} }
return ['mail']; return ['mail'];

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