Compare commits

..

186 Commits

Author SHA1 Message Date
github-actions
ac3f6557de Auto commit for release 'develop' on 2024-07-20 2024-07-20 10:36:54 +02:00
github-actions
b0a909150c Auto commit for release 'v6.1.19' on 2024-07-20 2024-07-20 06:56:39 +02:00
James Cole
913f163fe4 Update changelog 2024-07-20 06:51:33 +02:00
github-actions
3126b07b33 Auto commit for release 'develop' on 2024-07-15 2024-07-15 06:18:29 +02:00
James Cole
08ca90cf75 Merge pull request #9049 from firefly-iii/dependabot/npm_and_yarn/develop/sass-1.77.8 2024-07-15 06:15:16 +02:00
James Cole
540ac2a277 Merge pull request #9050 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/core-32.0.2 2024-07-15 06:15:08 +02:00
dependabot[bot]
ed80bed066 Bump sass from 1.77.6 to 1.77.8
Bumps [sass](https://github.com/sass/dart-sass) from 1.77.6 to 1.77.8.
- [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.77.6...1.77.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 04:14:54 +00:00
dependabot[bot]
41d2541c6a Bump @ag-grid-community/core from 32.0.0 to 32.0.2
Bumps [@ag-grid-community/core](https://github.com/ag-grid/ag-grid) from 32.0.0 to 32.0.2.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v32.0.0...v32.0.2)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 04:14:44 +00:00
James Cole
5dedf63498 Merge pull request #9051 from firefly-iii/dependabot/npm_and_yarn/develop/laravel-vite-plugin-1.0.5 2024-07-15 06:14:12 +02:00
James Cole
09bc4f41d2 Merge pull request #9052 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/infinite-row-model-32.0.2 2024-07-15 06:14:05 +02:00
James Cole
cebf0b5c57 Merge pull request #9053 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/client-side-row-model-32.0.2 2024-07-15 06:13:57 +02:00
James Cole
1632a57e3e Merge pull request #9054 from firefly-iii/dependabot/composer/develop/barryvdh/laravel-ide-helper-3.1.0 2024-07-15 06:13:49 +02:00
James Cole
744c4be7d1 Merge pull request #9055 from firefly-iii/dependabot/composer/develop/phpunit/phpunit-10.5.27 2024-07-15 06:13:41 +02:00
James Cole
bd99ef3eff Merge pull request #9056 from firefly-iii/dependabot/composer/develop/laravel/framework-11.15.0 2024-07-15 06:13:33 +02:00
dependabot[bot]
8a86f13a5d Bump laravel/framework from 11.14.0 to 11.15.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.14.0 to 11.15.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.14.0...v11.15.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>
2024-07-15 03:28:11 +00:00
dependabot[bot]
7418b2f0ee Bump phpunit/phpunit from 10.5.25 to 10.5.27
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.25 to 10.5.27.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.27/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.25...10.5.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 03:27:58 +00:00
dependabot[bot]
a0e9de9312 Bump barryvdh/laravel-ide-helper from 3.0.0 to 3.1.0
Bumps [barryvdh/laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/barryvdh/laravel-ide-helper/releases)
- [Changelog](https://github.com/barryvdh/laravel-ide-helper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/barryvdh/laravel-ide-helper/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: barryvdh/laravel-ide-helper
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 03:27:52 +00:00
dependabot[bot]
7e23a6f5e8 Bump @ag-grid-community/client-side-row-model from 32.0.0 to 32.0.2
Bumps [@ag-grid-community/client-side-row-model](https://github.com/ag-grid/ag-grid) from 32.0.0 to 32.0.2.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v32.0.0...v32.0.2)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/client-side-row-model"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 03:11:06 +00:00
dependabot[bot]
44589f8744 Bump @ag-grid-community/infinite-row-model from 32.0.0 to 32.0.2
Bumps [@ag-grid-community/infinite-row-model](https://github.com/ag-grid/ag-grid) from 32.0.0 to 32.0.2.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v32.0.0...v32.0.2)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/infinite-row-model"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 03:10:52 +00:00
dependabot[bot]
d24531030f Bump laravel-vite-plugin from 1.0.4 to 1.0.5
Bumps [laravel-vite-plugin](https://github.com/laravel/vite-plugin) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/laravel/vite-plugin/releases)
- [Changelog](https://github.com/laravel/vite-plugin/blob/1.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/vite-plugin/compare/v1.0.4...v1.0.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 03:10:36 +00:00
James Cole
25bdab1346 Add some debug info. 2024-07-10 11:55:02 +02:00
James Cole
41af1c863a Fix https://github.com/firefly-iii/firefly-iii/issues/9009 2024-07-10 11:40:41 +02:00
James Cole
76b3b18cfb Merge pull request #9030 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/client-side-row-model-32.0.0
Bump @ag-grid-community/client-side-row-model from 31.3.2 to 32.0.0
2024-07-08 13:19:40 +02:00
dependabot[bot]
e6fb2958a9 Bump @ag-grid-community/client-side-row-model from 31.3.2 to 32.0.0
Bumps [@ag-grid-community/client-side-row-model](https://github.com/ag-grid/ag-grid) from 31.3.2 to 32.0.0.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v31.3.2...v32.0.0)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/client-side-row-model"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 11:19:16 +00:00
James Cole
15b75b322f Merge pull request #9029 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/infinite-row-model-32.0.0
Bump @ag-grid-community/infinite-row-model from 31.3.2 to 32.0.0
2024-07-08 13:18:28 +02:00
dependabot[bot]
86149d1032 Bump @ag-grid-community/infinite-row-model from 31.3.2 to 32.0.0
Bumps [@ag-grid-community/infinite-row-model](https://github.com/ag-grid/ag-grid) from 31.3.2 to 32.0.0.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v31.3.2...v32.0.0)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/infinite-row-model"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 11:18:02 +00:00
James Cole
ded142cd9e Merge pull request #9031 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/core-32.0.0
Bump @ag-grid-community/core from 31.3.2 to 32.0.0
2024-07-08 13:17:12 +02:00
dependabot[bot]
7923eb9ec9 Bump @ag-grid-community/core from 31.3.2 to 32.0.0
Bumps [@ag-grid-community/core](https://github.com/ag-grid/ag-grid) from 31.3.2 to 32.0.0.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v31.3.2...v32.0.0)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/core"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 10:59:19 +00:00
James Cole
132553c108 Merge pull request #9032 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/styles-32.0.0
Bump @ag-grid-community/styles from 31.3.2 to 32.0.0
2024-07-08 12:58:14 +02:00
github-actions
c2269fc9a4 Auto commit for release 'develop' on 2024-07-08 2024-07-08 05:07:00 +02:00
dependabot[bot]
aed30d1499 Bump @ag-grid-community/styles from 31.3.2 to 32.0.0
Bumps [@ag-grid-community/styles](https://github.com/ag-grid/ag-grid) from 31.3.2 to 32.0.0.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v31.3.2...v32.0.0)

---
updated-dependencies:
- dependency-name: "@ag-grid-community/styles"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 03:05:13 +00:00
James Cole
84a1a876e1 Fix https://github.com/firefly-iii/firefly-iii/issues/8844 2024-07-06 18:50:33 +02:00
James Cole
dc675707f9 Fix https://github.com/firefly-iii/firefly-iii/issues/9021 2024-07-06 15:50:20 +02:00
James Cole
d5667c7ef6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Support/Http/Api/ExchangeRateConverter.php
2024-07-06 15:43:27 +02:00
James Cole
cba1213dd1 Better cache 2024-07-06 15:42:50 +02:00
James Cole
7219c90957 Fix https://github.com/firefly-iii/firefly-iii/issues/9022 2024-07-06 15:42:33 +02:00
github-actions
af13bd991e Auto commit for release 'develop' on 2024-07-01 2024-07-01 05:06:49 +02:00
James Cole
48e548eb52 Better disable thing. 2024-06-30 18:20:52 +02:00
github-actions
1a19e27f0e Auto commit for release 'develop' on 2024-06-24 2024-06-24 05:07:31 +02:00
James Cole
0cbd22426d Fix https://github.com/firefly-iii/firefly-iii/issues/8986 2024-06-20 18:29:00 +02:00
James Cole
d5e52e99e0 Fix #8981 2024-06-18 19:44:22 +02:00
github-actions
f52978e71f Auto commit for release 'v6.1.18' on 2024-06-17 2024-06-17 20:47:03 +02:00
James Cole
3a3358124d Meta data for new release. 2024-06-17 20:43:02 +02:00
github-actions
929808c633 Auto commit for release 'develop' on 2024-06-17 2024-06-17 05:07:03 +02:00
github-actions
a78df574f3 Auto commit for release 'develop' on 2024-06-16 2024-06-16 20:26:04 +02:00
James Cole
875cad16b6 Possible fix for https://github.com/firefly-iii/firefly-iii/issues/8977 2024-06-16 20:21:39 +02:00
James Cole
7bc30192ca Fix https://github.com/firefly-iii/firefly-iii/issues/8978 2024-06-16 19:27:38 +02:00
github-actions
a1a8968e98 Auto commit for release 'develop' on 2024-06-15 2024-06-15 16:16:40 +02:00
James Cole
6abb74a038 Mondays only [skip ci] 2024-06-15 14:26:59 +02:00
github-actions
2d7d05e985 Auto commit for release 'v6.1.17' on 2024-06-15 2024-06-15 13:07:23 +02:00
github-actions
d426e09474 Merge branch 'develop' 2024-06-15 11:04:29 +00:00
James Cole
72d55cb953 Revert workflow change. 2024-06-15 13:02:53 +02:00
James Cole
73ad865581 Add changelog. 2024-06-15 12:56:41 +02:00
James Cole
fefb52beb7 Weekly develop 2024-06-15 12:50:15 +02:00
James Cole
abd503543b Fix regression error in cookie queue 2024-06-15 12:33:56 +02:00
James Cole
e3eb550581 Add currency because why not. 2024-06-15 09:42:09 +02:00
James Cole
46b780758e Fix https://github.com/firefly-iii/firefly-iii/issues/8927 2024-06-15 09:38:19 +02:00
James Cole
b2c3ee9779 Fix https://github.com/firefly-iii/firefly-iii/issues/8954 2024-06-15 09:29:40 +02:00
James Cole
dca899bcee Fix https://github.com/firefly-iii/firefly-iii/issues/8893 2024-06-15 09:25:41 +02:00
James Cole
9667b8a948 Fix https://github.com/firefly-iii/firefly-iii/issues/8958 2024-06-15 09:17:41 +02:00
James Cole
661f225fe7 Add custom routes for Laravel passport. 2024-06-15 09:15:48 +02:00
github-actions
4c6fe0c8de Auto commit for release 'develop' on 2024-06-13 2024-06-13 05:08:55 +02:00
James Cole
78f457950e Merge pull request #8957 from JeroenED/unlimited-nesting-level
Remove nesting level for markdown
2024-06-11 15:17:43 +02:00
Jeroen De Meerleer
d831cc8df2 Update max nesting level for Markdown converter
Increased the max nesting level to 5 for better parsing.
2024-06-11 15:04:15 +02:00
James Cole
7056406afc Merge pull request #8951 from stevewasiura/patch-3
add icon for delete action
2024-06-11 15:03:09 +02:00
github-actions
c85cfcf3e6 Auto commit for release 'develop' on 2024-06-10 2024-06-10 05:09:13 +02:00
Jeroen De Meerleer
db06d06789 Remove unnecessary option from Markdown converter
The 'max_nesting_level' option was removed from the Markdown converter configuration in order to simplify and optimize the code.
2024-06-07 09:05:38 +02:00
github-actions
a28b990cd1 Auto commit for release 'develop' on 2024-06-06 2024-06-06 05:09:02 +02:00
Steve Wasiura
dab4bfa7a6 add icon for delete action
in workflow of reconciling account, sometimes a record needs to be deleted (i.e. in event of a duplicated transaction from an import or error) and this change adds an icon to the delete route

Signed-off-by: Steve Wasiura <steve@waztech.com>
2024-06-03 22:09:22 -04:00
James Cole
6575236f2b Merge pull request #8949 from firefly-iii/dependabot/github_actions/crowdin/github-action-2
Bump crowdin/github-action from 1 to 2
2024-06-03 07:34:04 +02:00
dependabot[bot]
ad582c8806 Bump crowdin/github-action from 1 to 2
Bumps [crowdin/github-action](https://github.com/crowdin/github-action) from 1 to 2.
- [Release notes](https://github.com/crowdin/github-action/releases)
- [Commits](https://github.com/crowdin/github-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: crowdin/github-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 03:59:42 +00:00
github-actions
452e9cb953 Auto commit for release 'develop' on 2024-06-03 2024-06-03 05:09:54 +02:00
James Cole
a64f137b39 Fix missing locales files. 2024-06-02 07:29:41 +02:00
James Cole
c067d6aab0 Catch division by zero 2024-06-01 05:36:21 +02:00
github-actions
119b9920a6 Auto commit for release 'develop' on 2024-05-30 2024-05-30 05:48:54 +02:00
James Cole
99ed54fce8 Revert steps 2024-05-30 05:45:04 +02:00
James Cole
2ea57cdd38 Merge pull request #8930 from stevewasiura/patch-2
icon title chgd from Deposit to Transfer
2024-05-29 18:00:47 +02:00
James Cole
bb94bdfdaf Merge pull request #8929 from stevewasiura/patch-1
icon title chgd from Deposit to Transfer
2024-05-29 17:59:49 +02:00
Steve Wasiura
4de8398cc2 icon title chgd from Deposit to Transfer
Signed-off-by: Steve Wasiura <steve@waztech.com>
2024-05-29 09:20:39 -04:00
Steve Wasiura
e6e8cd5d8a icon title chgd from Deposit to Transfer
Signed-off-by: Steve Wasiura <steve@waztech.com>
2024-05-29 09:15:53 -04:00
James Cole
0b200309ba Catch empty strings 2024-05-28 05:53:17 +02:00
github-actions
a184548912 Auto commit for release 'develop' on 2024-05-27 2024-05-27 05:08:44 +02:00
James Cole
c987191212 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Console/Commands/Upgrade/CorrectAccountBalance.php
2024-05-26 15:37:20 +02:00
James Cole
7009b444d9 Skip creation using if statement. 2024-05-26 15:36:30 +02:00
github-actions
06551d5367 Auto commit for release 'develop' on 2024-05-25 2024-05-25 20:18:23 +02:00
James Cole
a20622ac0c Fix https://github.com/firefly-iii/firefly-iii/issues/8911 2024-05-25 11:28:32 +02:00
James Cole
ca38117fca Fix https://github.com/firefly-iii/firefly-iii/issues/8907 2024-05-25 08:10:23 +02:00
James Cole
9478f78d4f Merge branch 'main' into develop 2024-05-25 08:05:45 +02:00
James Cole
5c2397bbae Remove unused workflows. 2024-05-25 08:05:32 +02:00
James Cole
92fefef816 Merge branch 'main' into develop 2024-05-24 05:30:29 +02:00
James Cole
d3ced65524 Remove 2024-05-24 05:30:17 +02:00
James Cole
29eb748831 Merge branch 'main' into develop 2024-05-24 05:22:57 +02:00
github-actions
76df3d5f33 Auto commit for release 'develop' on 2024-05-23 2024-05-23 05:10:54 +02:00
James Cole
252076ec1f Add to cleanup 2024-05-22 06:12:19 +02:00
James Cole
bbec28591f Add workflow that responds to PRs 2024-05-22 06:02:49 +02:00
James Cole
075a360ba6 Add new workflow for issue thing. 2024-05-21 20:20:06 +02:00
James Cole
477524a8ae You could've just asked... 2024-05-21 17:48:54 +02:00
James Cole
dfe055732d Fix https://github.com/firefly-iii/firefly-iii/issues/8907 2024-05-21 17:36:05 +02:00
James Cole
78b611a18d Update .gitignore 2024-05-21 17:28:43 +02:00
James Cole
367bdf65e6 Remove auto-generated language files 2024-05-21 17:25:29 +02:00
James Cole
3fc9caa31a Add git ignore, remove .gitkeep. 2024-05-21 17:24:25 +02:00
James Cole
95a41fcab7 Remove auto-generated language files. 2024-05-21 17:23:07 +02:00
James Cole
58b409fc00 Restore confirmation for data removal. 2024-05-20 20:19:23 +02:00
James Cole
3eaaac09ad Clean up balance chart. 2024-05-20 07:30:41 +02:00
James Cole
bcb672920c Fix chart, uses new filter. 2024-05-20 06:55:36 +02:00
James Cole
79b91e25c2 Expand chart api 2024-05-20 06:49:42 +02:00
github-actions
7170931464 Auto commit for release 'develop' on 2024-05-20 2024-05-20 05:09:44 +02:00
github-actions
c1b5a1a13e Auto commit for release 'develop' on 2024-05-19 2024-05-19 10:37:08 +02:00
James Cole
a6265ce8ab Fix line in job. 2024-05-19 10:28:01 +02:00
github-actions
90109917df Auto commit for release 'v6.1.16' on 2024-05-19 2024-05-19 10:26:25 +02:00
James Cole
0acd54c2b7 Update changelog. 2024-05-19 10:21:03 +02:00
James Cole
c96226b9b4 Add final API changes 2024-05-19 10:17:21 +02:00
James Cole
6d143f1624 Clean up API 2024-05-19 06:36:31 +02:00
James Cole
93324d1154 Remove unused variables 2024-05-18 06:57:38 +02:00
github-actions
a39f0e1891 Auto commit for release 'develop' on 2024-05-18 2024-05-18 06:49:29 +02:00
James Cole
822f609a22 Fix ibans 2024-05-18 06:44:18 +02:00
James Cole
cd7ddd1c61 Add more IBAN filters 2024-05-18 06:42:09 +02:00
James Cole
0b63ba26bb Fix autocomplete entries. 2024-05-18 05:51:02 +02:00
James Cole
94d70cdb62 Fix missing parameters 2024-05-17 20:45:16 +02:00
James Cole
acb3831c8b Add parallel thing 2024-05-16 07:30:07 +02:00
github-actions
c9d9ecede4 Auto commit for release 'develop' on 2024-05-16 2024-05-16 07:22:12 +02:00
James Cole
4eb5873353 Merge branch 'main' into develop 2024-05-16 07:17:18 +02:00
James Cole
7ca39fdb21 Fix commands 2024-05-16 07:16:47 +02:00
James Cole
b8d1d7a8c0 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Support/Models/AccountBalanceCalculator.php
2024-05-16 07:15:36 +02:00
James Cole
1af79eab30 Clean up commands 2024-05-16 07:14:44 +02:00
github-actions
03be2704ce Auto commit for release 'develop' on 2024-05-16 2024-05-16 05:10:41 +02:00
James Cole
34baea66a7 Merge pull request #8880 from firefly-iii/dependabot/github_actions/github/command-1.2.0 2024-05-14 05:43:54 +02:00
James Cole
0638d109d0 Clean up code. 2024-05-13 20:31:52 +02:00
dependabot[bot]
561e228a2d Bump github/command from 1.1.1 to 1.2.0
Bumps [github/command](https://github.com/github/command) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/github/command/releases)
- [Commits](https://github.com/github/command/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: github/command
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 03:12:36 +00:00
github-actions
cb5d856769 Auto commit for release 'develop' on 2024-05-13 2024-05-13 05:10:16 +02:00
James Cole
04fe5d1fc4 Correct account balances better. 2024-05-12 18:24:38 +02:00
James Cole
45e9d4f8de Fix auto complete 2024-05-12 17:50:54 +02:00
James Cole
73fdbb6202 Update API endpoints and account autocomplete. 2024-05-12 13:31:33 +02:00
James Cole
e49dbefddd Make the combined combination unique. 2024-05-12 08:10:12 +02:00
James Cole
fc5143337a Add a title to the table, so multiple balances per account are possible. 2024-05-12 08:09:50 +02:00
James Cole
4b3eb6dace Rename command. 2024-05-12 06:26:50 +02:00
James Cole
c741b2a819 Add related models. 2024-05-12 06:25:13 +02:00
James Cole
cebfaa32bf Add routine that caches account balances. Add it to the /flush routine as well. 2024-05-12 06:24:11 +02:00
James Cole
d356d39d43 add account balances with some random data 2024-05-11 20:32:25 +02:00
James Cole
7d9f22d3f4 Merge branch 'api' into develop
# Conflicts:
#	app/JsonApi/V3/Accounts/AccountRepository.php
#	app/JsonApi/V3/Accounts/AccountResource.php
#	app/JsonApi/V3/Accounts/Capabilities/AccountQuery.php
#	app/JsonApi/V3/Users/UserSchema.php
2024-05-10 12:52:44 +02:00
James Cole
c6c8f282e2 Restore previous stuff 2024-05-10 12:51:02 +02:00
James Cole
6a64420721 Refactor resources 2024-05-10 09:42:09 +02:00
James Cole
fcde4e2488 no message 2024-05-10 09:41:53 +02:00
James Cole
aa5c4c20e9 Various messy code. 2024-05-10 09:17:09 +02:00
James Cole
794e31e487 Add some experimental account endpoints using a package 2024-05-10 06:43:18 +02:00
James Cole
16bf186312 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	composer.lock
2024-05-10 06:42:47 +02:00
James Cole
45c722e786 Add config, merge later. 2024-05-10 06:36:57 +02:00
github-actions
36d9e5c3fe Auto commit for release 'develop' on 2024-05-09 2024-05-09 05:10:13 +02:00
James Cole
8d614de67f Possible fix for https://github.com/firefly-iii/firefly-iii/issues/8867 2024-05-08 21:55:14 +02:00
James Cole
d17da670ab Fix connect exception, add some debug logs. 2024-05-06 06:33:12 +02:00
github-actions
5bf4df9ad8 Auto commit for release 'develop' on 2024-05-06 2024-05-06 05:10:30 +02:00
James Cole
07db6b59ce Fix webhooks overview. 2024-05-02 06:15:41 +02:00
github-actions
e16f1cf4ee Auto commit for release 'develop' on 2024-05-02 2024-05-02 05:10:23 +02:00
James Cole
4c80d929ca Possible division by zero? 2024-05-01 06:29:28 +02:00
github-actions
16364d9859 Auto commit for release 'develop' on 2024-04-30 2024-04-30 20:33:00 +02:00
James Cole
c24f6acb2c Fix styles 2024-04-30 20:26:05 +02:00
James Cole
4bd19e0627 Expand and fix sort columns 2024-04-30 20:22:31 +02:00
James Cole
69d839997a Fix sorting and order for account lists. 2024-04-29 20:20:11 +02:00
github-actions
c02c027f4f Auto commit for release 'develop' on 2024-04-29 2024-04-29 06:36:36 +02:00
James Cole
b14606625e Upload corrected file 2024-04-29 06:31:18 +02:00
James Cole
222d7b56c7 Merge pull request #8835 from firefly-iii/dependabot/npm_and_yarn/develop/chartjs-chart-sankey-0.12.1 2024-04-29 06:07:17 +02:00
James Cole
b7f7bf42b2 Merge pull request #8834 from firefly-iii/dependabot/composer/develop/ramsey/uuid-4.7.6 2024-04-29 06:07:09 +02:00
dependabot[bot]
0cbd64d31a Bump chartjs-chart-sankey from 0.12.0 to 0.12.1
Bumps [chartjs-chart-sankey](https://github.com/kurkle/chartjs-chart-sankey) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/kurkle/chartjs-chart-sankey/releases)
- [Commits](https://github.com/kurkle/chartjs-chart-sankey/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: chartjs-chart-sankey
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:55:28 +00:00
dependabot[bot]
60bdae47c4 Bump ramsey/uuid from 4.7.5 to 4.7.6
Bumps [ramsey/uuid](https://github.com/ramsey/uuid) from 4.7.5 to 4.7.6.
- [Release notes](https://github.com/ramsey/uuid/releases)
- [Changelog](https://github.com/ramsey/uuid/blob/4.x/CHANGELOG.md)
- [Commits](https://github.com/ramsey/uuid/compare/4.7.5...4.7.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:34:37 +00:00
James Cole
ca4b38d905 Add debug thing for tables. 2024-04-28 20:20:18 +02:00
James Cole
3674465f53 Translate stuff 2024-04-28 14:43:23 +02:00
James Cole
b951d4130c Allow account grouping 2024-04-28 14:40:22 +02:00
James Cole
7992b810fd Expand view with several new options. Move cache to api endpoints. 2024-04-28 13:30:42 +02:00
James Cole
c1c0afa40b Expand account index overview. 2024-04-28 09:54:28 +02:00
github-actions
56c9026299 Auto commit for release 'develop' on 2024-04-26 2024-04-26 06:18:27 +02:00
James Cole
021ddfc36b Clear out file to see if develop action picks it up correctly. 2024-04-26 06:13:03 +02:00
James Cole
5bd72f6428 Add command 2024-04-26 05:32:21 +02:00
James Cole
feabfe54f0 First attempt at comma based preference collector 2024-04-26 05:31:02 +02:00
James Cole
565409b486 Remove address 2024-04-25 19:51:15 +02:00
James Cole
f57366da5f Add thanks file. 2024-04-25 19:49:58 +02:00
github-actions
064217ccb0 Auto commit for release 'develop' on 2024-04-25 2024-04-25 05:10:20 +02:00
James Cole
fa3ccbda33 Fix phpstan issues. 2024-04-23 19:40:48 +02:00
github-actions
f43aadf02d Auto commit for release 'v6.1.15' on 2024-04-23 2024-04-23 16:34:26 +02:00
James Cole
3b8a4d3e9b Expand changelog. 2024-04-23 16:27:58 +02:00
James Cole
3d410556ef Fix https://github.com/firefly-iii/firefly-iii/issues/8812 2024-04-23 16:24:46 +02:00
github-actions
f15ca1d0a1 Auto commit for release 'v6.1.14' on 2024-04-23 2024-04-23 07:13:10 +02:00
James Cole
7002463c54 Update changelog for new release. 2024-04-23 07:06:37 +02:00
James Cole
649f876437 Better account index in v2. 2024-04-23 07:00:08 +02:00
James Cole
3cfd178cbd New translations. 2024-04-23 06:59:49 +02:00
github-actions
cefbaafa19 Auto commit for release 'develop' on 2024-04-22 2024-04-22 05:11:03 +02:00
James Cole
a8c88800c4 Fix column 2024-04-21 19:49:28 +02:00
787 changed files with 8371 additions and 155501 deletions

View File

@@ -19,6 +19,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
$current = __DIR__;
$paths = [
@@ -35,6 +37,7 @@ $finder = PhpCsFixer\Finder::create()
$config = new PhpCsFixer\Config();
$config->setParallelConfig(ParallelConfigFactory::detect());
return $config->setRules(
[
// rule sets

File diff suppressed because it is too large Load Diff

View File

@@ -299,27 +299,6 @@ DKR_BUILD_LOCALE=false
# Won't significantly speed up things.
DKR_CHECK_SQLITE=true
# Run database creation and migration commands. Disable this only if you're 100% sure the DB exists
# and is up to date.
DKR_RUN_MIGRATION=true
# Run database upgrade commands. Disable this only when you're 100% sure your DB is up-to-date
# with the latest fixes (outside of migrations!)
DKR_RUN_UPGRADE=true
# Verify database integrity. Includes all data checks and verifications.
# Disabling this makes Firefly III assume your DB is intact.
DKR_RUN_VERIFY=true
# Run database reporting commands. When disabled, Firefly III won't go over your data to report current state.
# Disabling this should have no impact on data integrity or safety but it won't warn you of possible issues.
DKR_RUN_REPORT=true
# Generate OAuth2 keys.
# When disabled, Firefly III won't attempt to generate OAuth2 Passport keys. This won't be an issue, IFF (if and only if)
# you had previously generated keys already and they're stored in your database for restoration.
DKR_RUN_PASSPORT_INSTALL=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII

View File

@@ -13,7 +13,7 @@ jobs:
close_duplicates:
runs-on: ubuntu-latest
steps:
- uses: github/command@v1.1.1
- uses: github/command@v1.2.0
id: command
with:
allowed_contexts: "issue"

View File

@@ -8,7 +8,7 @@ on:
required: true
default: 'develop'
schedule:
- cron: '0 3 * * MON,THU'
- cron: '0 3 * * MON'
jobs:
build:
@@ -39,7 +39,7 @@ jobs:
php-version: '8.3'
extensions: mbstring, intl, zip, bcmath
- name: crowdin action
uses: crowdin/github-action@v1
uses: crowdin/github-action@v2
with:
upload_sources: true
download_translations: true
@@ -67,6 +67,15 @@ jobs:
env:
FIREFLY_III_ROOT: /github/workspace
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
- name: "Create THANKS.md"
id: thank-you
uses: JC5/firefly-iii-dev@main
with:
action: 'ff3:thank-you'
output: ''
env:
FIREFLY_III_ROOT: /github/workspace
GH_TOKEN: ''
- name: Extract changelog
id: extract-changelog
uses: JC5/firefly-iii-dev@main
@@ -116,9 +125,9 @@ jobs:
- name: Build JS
run: |
npm install
npm update
npm run prod --workspace=v1
npm run build --workspace=v2
npm update
- name: Run CI
run: |
rm -rf vendor composer.lock
@@ -245,7 +254,7 @@ jobs:
echo '' >> output.txt
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)."
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo "Create default release."
git tag -a $releaseName -m "Here be changelog"

194
THANKS.md Executable file
View File

@@ -0,0 +1,194 @@
# Thank you! :tada: :heart: :tada:
Over time, many people have contributed to Firefly III. Their efforts are not always visible, but always remembered and appreciated.
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
## 2024
- Steve Wasiura
- imlonghao
- Rahman Yusuf
- Michael Thomas
- WardenJakx
- kuilin
- Stevie Robinson
- luzpaz
- Lemuel Roberto Bonifácio
- maureenferreira
## 2023
- tieu1991
- Maxco10
- zqye
- Mateus Pereira
- josephbadow
- Christian Desktop
- Edgars
- Hannah K
- noxonad
- Kaijia Feng
- Marc Ordinas i Llopis
- Kuba Turek
- Julien Stébenne
## 2022
- Johannes Zellner
- Janne Heß
- charlesteets
- Nathan PERIER
- Jan Willhaus
- canoine
- Rick Cuddy
- James
- Hugo Meyronneinc
- naveen
- neilnaveen
- naveensrinivasan
- Federico Micelli
- George Hahn
## 2021
- StillLoading
- Igor Rzegocki
- Lorenzo Breda
- Hosh
- Flightkick
- alex6480
- VREEdom
- Hamza FADIL
- Kasper Læssø Sørensen
- Alex
- Jeroen De Meerleer
- Ruben van Erk
- Fabian Zimmermann
- Mirko Berger
- KaihatsuOnline
- MihataBG
## 2020
- Hannes Körber
- Julien Cassagne
- bu4ak
- Viktor Yakovlev
- Oliver Kaufmann
- Arvind Chembarpu
- GrayStrider
- psychowood
- Hosh Sadiq
- emansih
- Aniruddha Maru
- johnny
- sephrat
- bpatath
- Florian Dupret
- Maxim Kurbatov
- Lucas Guima
- Sandro
- Ruben Verhoef
- Daniel Idzerda
- Calum Smith
- Agraphie
- Tomer Shvueli
- Tomer S
## 2019
- Pascal Jungblut
- Justyn Shull
- Timendum
- Nicolas Lœuillet
- Dominic Guhl
- Melroy van den Berg
- Henning Stein
- Jan Klepek
- Jonathan
- Geoffrey “Frogeye” Preud'homme
- Michael Fix
- Juraj Mlich
- Eddybrando Vásquez
- hulloanson
- Will Rouesnel
- lastlink
- Mr. Funk
- Simon Taddiken
- Joris
- Bastiaan Nijkamp
## 2018
- a1ex4
- Daniel Quah
- Marco Lourenço
- Dennis Enderink
- Luca Bognolo
- Mike Conway
- Ben
- Mathieu Post
- George Hertz
- HamuZ HamuZ
- David Meiseles
- Erik Gelderblom
- Luca Vallerini
- Clemens Wijnekus
- Jacob Weisz
- Mateusz Gozdek
- anmol26s
- Kevin Hellemun
- Shashank M Chakravarthy
- Nico Schreiner
- Paul Sohier
- Brenden Conte
- Ben Yanke
- Andrew Prokhorenkov
- devlearner
- Kelvin
- J'informatique
## 2017
- Victor Mosin
- Justin
- Hugo van Duijn
- Lukas Winkler
- Marcin Szymanski
- Jens Kat
- koziolek
- jleeong
- Simon Hanna
- richard & xeli.eu
- Sergey Besedin
- Welbert Serra
- Joris de Vries
- Patrick Kostjens
- Enrico Lamperti
- Christian Musa
- Enno Lohmeier
## 2016
- Sander
- Toon Schoenmakers
- Telyn
- Sander Kleykens
- Tom van der Werf
- Matthew Peck
- Sander Mulders
- Bonno Nachtegaal-Karels
- Niek Haarman
- Edwin
- Thijs Alkemade
- zjean
- Graham Miller
- Robert Horlings
- leander091
## 2015
- Antonio Spinelli
- Colin O'Dell
- RonaldvanMeer
- Richard Ebbers
- Balazs Varkonyi
- Niek van der Kooy
- Ilya Kil
## 2014
- Stewart Malik
- Graham Campbell
Thank you for all your support!

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\Preference;
use FireflyIII\Transformers\PreferenceTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
@@ -97,6 +98,32 @@ class PreferencesController extends Controller
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
/**
* TODO This endpoint is not documented.
*
* Return a single preference by name.
*/
public function showList(Collection $collection): JsonResponse
{
$manager = $this->getManager();
$count = $collection->count();
$pageSize = $this->parameters->get('limit');
$preferences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($preferences, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.preferences.show-list').$this->buildParams());
/** @var PreferenceTransformer $transformer */
$transformer = app(PreferenceTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($preferences, $transformer, self::RESOURCE_KEY);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
/**
* This endpoint is documented at:
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/preferences/storePreference

View File

@@ -63,7 +63,7 @@ class StoreRequest extends FormRequest
'order' => $this->convertInteger('order'),
'currency_code' => $this->convertString('currency_code'),
'virtual_balance' => $this->convertString('virtual_balance'),
'iban' => $this->convertString('iban'),
'iban' => $this->convertIban('iban'),
'BIC' => $this->convertString('bic'),
'account_number' => $this->convertString('account_number'),
'account_role' => $this->convertString('account_role'),

View File

@@ -51,7 +51,7 @@ class UpdateRequest extends FormRequest
'include_net_worth' => ['include_net_worth', 'boolean'],
'account_type_name' => ['type', 'convertString'],
'virtual_balance' => ['virtual_balance', 'convertString'],
'iban' => ['iban', 'convertString'],
'iban' => ['iban', 'convertIban'],
'BIC' => ['bic', 'convertString'],
'account_number' => ['account_number', 'convertString'],
'account_role' => ['account_role', 'convertString'],

View File

@@ -58,7 +58,7 @@ class StoreRequest extends FormRequest
$models = config('firefly.valid_attachment_models');
$models = array_map(
static function (string $className) {
return str_replace('FireflyIII\\Models\\', '', $className);
return str_replace('FireflyIII\Models\\', '', $className);
},
$models
);

View File

@@ -60,7 +60,7 @@ class UpdateRequest extends FormRequest
$models = config('firefly.valid_attachment_models');
$models = array_map(
static function (string $className) {
return str_replace('FireflyIII\\Models\\', '', $className);
return str_replace('FireflyIII\Models\\', '', $className);
},
$models
);

View File

@@ -103,14 +103,14 @@ class StoreRequest extends FormRequest
// source of transaction. If everything is null, assume cash account.
'source_id' => $this->integerFromValue((string)$object['source_id']),
'source_name' => $this->clearString((string)$object['source_name']),
'source_iban' => $this->clearString((string)$object['source_iban']),
'source_iban' => $this->clearIban((string)$object['source_iban']),
'source_number' => $this->clearString((string)$object['source_number']),
'source_bic' => $this->clearString((string)$object['source_bic']),
// destination of transaction. If everything is null, assume cash account.
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
'destination_name' => $this->clearString((string)$object['destination_name']),
'destination_iban' => $this->clearString((string)$object['destination_iban']),
'destination_iban' => $this->clearIban((string)$object['destination_iban']),
'destination_number' => $this->clearString((string)$object['destination_number']),
'destination_bic' => $this->clearString((string)$object['destination_bic']),

View File

@@ -28,22 +28,21 @@ use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/**
* Class AccountController
*/
class AccountController extends Controller
{
use AccountFilter;
private AdminAccountRepositoryInterface $adminRepository;
private array $balanceTypes;
private AccountRepositoryInterface $repository;
private AccountRepositoryInterface $repository;
private TransactionCurrency $default;
private ExchangeRateConverter $converter;
/**
* AccountController constructor.
@@ -53,83 +52,88 @@ class AccountController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->adminRepository = app(AdminAccountRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->adminRepository->setUserGroup($userGroup);
}
$userGroup = $this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUserGroup($userGroup);
$this->default = app('amount')->getDefaultCurrency();
$this->converter = app(ExchangeRateConverter::class);
return $next($request);
}
);
$this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
}
/**
* Documentation for this endpoint:
* TODO list of checks
* 1. use dates from ParameterBag
* 2. Request validates dates
* 3. Request includes user_group_id
* 4. Endpoint is documented.
* 5. Collector uses user_group_id
*
* @throws FireflyException
* @throws FireflyException
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getAccountsAC
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
$date = $this->parameters->get('date') ?? today(config('app.timezone'));
$result = $this->adminRepository->searchAccount((string)$query, $types, $data['limit']);
$defaultCurrency = app('amount')->getDefaultCurrency();
$groupedResult = [];
$allItems = [];
$queryParameters = $request->getParameters();
$result = $this->repository->searchAccount($queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
$return = [];
/** @var Account $account */
foreach ($result as $account) {
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = app('steam')->balance($account, $date);
$nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false));
}
$type = (string)trans(sprintf('firefly.%s', $account->accountType->type));
$groupedResult[$type] ??= [
'group ' => $type,
'items' => [],
];
$allItems[] = [
'id' => (string)$account->id,
'value' => (string)$account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'label' => $nameWithBalance,
'type' => $account->accountType->type,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$return[] = $this->parseAccount($account);
}
usort(
$allItems,
static function (array $left, array $right): int {
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
$posLeft = (int)array_search($left['type'], $order, true);
$posRight = (int)array_search($right['type'], $order, true);
return response()->json($return);
}
return $posLeft - $posRight;
private function parseAccount(Account $account): array
{
$currency = $this->repository->getAccountCurrency($account);
return [
'id' => (string) $account->id,
'title' => $account->name,
'meta' => [
'type' => $account->accountType->type,
'currency_id' => null === $currency ? null : (string) $currency->id,
'currency_code' => $currency?->code,
'currency_symbol' => $currency?->symbol,
'currency_decimal_places' => $currency?->decimal_places,
'account_balances' => $this->getAccountBalances($account),
],
];
}
private function getAccountBalances(Account $account): array
{
$return = [];
$balances = $this->repository->getAccountBalances($account);
/** @var AccountBalance $balance */
foreach ($balances as $balance) {
try {
$return[] = $this->parseAccountBalance($balance);
} catch (FireflyException $e) {
Log::error(sprintf('Could not parse convert account balance: %s', $e->getMessage()));
}
);
}
return response()->json($allItems);
return $return;
}
/**
* @throws FireflyException
*/
private function parseAccountBalance(AccountBalance $balance): array
{
$currency = $balance->transactionCurrency;
return [
'title' => $balance->title,
'native_amount' => $this->converter->convert($currency, $this->default, today(), $balance->balance),
'amount' => app('steam')->bcround($balance->balance, $currency->decimal_places),
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string) $this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal_places' => $this->default->decimal_places,
];
}
}

View File

@@ -45,11 +45,7 @@ class CategoryController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(CategoryRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}
@@ -57,23 +53,18 @@ class CategoryController extends Controller
}
/**
* Documentation for this endpoint:
* TODO list of checks
* 1. use dates from ParameterBag
* 2. Request validates dates
* 3. Request includes user_group_id
* 4. Endpoint is documented.
* 5. Collector uses user_group_id
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getCategoriesAC
*/
public function categories(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$result = $this->repository->searchCategory($data['query'], $this->parameters->get('limit'));
$filtered = $result->map(
$queryParameters = $request->getParameters();
$result = $this->repository->searchCategory($queryParameters['query'], $queryParameters['size']);
$filtered = $result->map(
static function (Category $item) {
return [
'id' => (string)$item->id,
'name' => $item->name,
'id' => (string)$item->id,
'title' => $item->name,
'meta' => [],
];
}
);

View File

@@ -45,11 +45,7 @@ class TagController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(TagRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}
@@ -57,25 +53,20 @@ class TagController extends Controller
}
/**
* Documentation for this endpoint:
* TODO list of checks
* 1. use dates from ParameterBag
* 2. Request validates dates
* 3. Request includes user_group_id
* 4. Endpoint is documented.
* 5. Collector uses user_group_id
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTagsAC
*/
public function tags(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$result = $this->repository->searchTag($data['query'], $data['limit']);
$filtered = $result->map(
$queryParameters = $request->getParameters();
$result = $this->repository->searchTag($queryParameters['query'], $queryParameters['size']);
$filtered = $result->map(
static function (Tag $item) {
return [
'id' => (string)$item->id,
'name' => $item->tag,
'value' => (string)$item->id,
'id' => (string) $item->id,
'title' => $item->tag,
'value' => (string) $item->id,
'label' => $item->tag,
'meta' => [],
];
}
);

View File

@@ -45,11 +45,7 @@ class TransactionController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(JournalRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}
@@ -57,30 +53,25 @@ class TransactionController extends Controller
}
/**
* Documentation for this endpoint:
* TODO list of checks
* 1. use dates from ParameterBag
* 2. Request validates dates
* 3. Request includes user_group_id
* 4. Endpoint is documented.
* 5. Collector uses user_group_id
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTransactionsAC
*/
public function transactionDescriptions(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$result = $this->repository->searchJournalDescriptions($data['query'], $data['limit']);
$queryParameters = $request->getParameters();
$result = $this->repository->searchJournalDescriptions($queryParameters['query'], $queryParameters['size']);
// limit and unique
$filtered = $result->unique('description');
$array = [];
$filtered = $result->unique('description');
$array = [];
/** @var TransactionJournal $journal */
foreach ($filtered as $journal) {
$array[] = [
'id' => (string)$journal->id,
'transaction_group_id' => (string)$journal->transaction_group_id,
'name' => $journal->description,
'description' => $journal->description,
'id' => (string) $journal->id,
'title' => $journal->description,
'meta' => [
'transaction_group_id' => (string) $journal->transaction_group_id,
],
];
}

View File

@@ -24,19 +24,17 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Chart\DashboardChartRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Api\V2\Request\Chart\ChartRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class AccountController
@@ -44,10 +42,12 @@ use Illuminate\Support\Collection;
class AccountController extends Controller
{
use CleansChartData;
use CollectsAccountsFromFilter;
use ValidatesUserGroupTrait;
private AccountRepositoryInterface $repository;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private ChartData $chartData;
private TransactionCurrency $default;
public function __construct()
{
@@ -55,8 +55,9 @@ class AccountController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
$this->repository->setUserGroup($userGroup);
$this->repository->setUserGroup($this->validateUserGroup($request));
$this->chartData = new ChartData();
$this->default = app('amount')->getDefaultCurrency();
return $next($request);
}
@@ -64,107 +65,76 @@ class AccountController extends Controller
}
/**
* This endpoint is documented at
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v2)#/charts/getChartAccountOverview
*
* 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 user_group_id from request
* TODO fix documentation
*
* @throws FireflyException
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function dashboard(DashboardChartRequest $request): JsonResponse
public function dashboard(ChartRequest $request): JsonResponse
{
/** @var Carbon $start */
$start = $this->parameters->get('start');
$queryParameters = $request->getParameters();
$accounts = $this->getAccountList($queryParameters);
/** @var Carbon $end */
$end = $this->parameters->get('end');
$end->endOfDay();
/** @var TransactionCurrency $default */
$default = app('amount')->getDefaultCurrency();
$params = $request->getAll();
/** @var Collection $accounts */
$accounts = $params['accounts'];
$chartData = [];
// user's preferences
if (0 === $accounts->count()) {
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
$frontpage->data = $defaultSet;
$frontpage->save();
}
$accounts = $this->repository->getAccountsById($frontpage->data);
}
// both options are overruled by "preselected"
if ('all' === $params['preselected']) {
$accounts = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
}
if ('assets' === $params['preselected']) {
$accounts = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
}
if ('liabilities' === $params['preselected']) {
$accounts = $this->repository->getAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
}
// move date to end of day
$queryParameters['start']->startOfDay();
$queryParameters['end']->endOfDay();
// loop each account, and collect info:
/** @var Account $account */
foreach ($accounts as $account) {
$currency = $this->repository->getAccountCurrency($account);
if (null === $currency) {
$currency = $default;
}
$currentSet = [
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string)$currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the default currency of the user (could be the same!)
'native_currency_id' => (string)$default->id,
'native_currency_code' => $default->code,
'native_currency_symbol' => $default->symbol,
'native_currency_decimal_places' => $default->decimal_places,
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'period' => '1D',
'entries' => [],
'native_entries' => [],
];
$currentStart = clone $start;
$range = app('steam')->balanceInRange($account, $start, clone $end, $currency);
$rangeConverted = app('steam')->balanceInRangeConverted($account, $start, clone $end, $default);
$previous = array_values($range)[0];
$previousConverted = array_values($rangeConverted)[0];
while ($currentStart <= $end) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format] : $previous;
$balanceConverted = array_key_exists($format, $rangeConverted) ? $rangeConverted[$format] : $previousConverted;
$previous = $balance;
$previousConverted = $balanceConverted;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
$currentSet['native_entries'][$label] = $balanceConverted;
}
$chartData[] = $currentSet;
$this->renderAccountData($queryParameters, $account);
}
return response()->json($this->clean($chartData));
return response()->json($this->chartData->render());
}
/**
* @throws FireflyException
*/
private function renderAccountData(array $params, Account $account): void
{
$currency = $this->repository->getAccountCurrency($account);
if (null === $currency) {
$currency = $this->default;
}
$currentSet = [
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the default currency of the user (could be the same!)
'native_currency_id' => (string) $this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal_places' => $this->default->decimal_places,
'date' => $params['start']->toAtomString(),
'start' => $params['start']->toAtomString(),
'end' => $params['end']->toAtomString(),
'period' => '1D',
'entries' => [],
'native_entries' => [],
];
$currentStart = clone $params['start'];
$range = app('steam')->balanceInRange($account, $params['start'], clone $params['end'], $currency);
$rangeConverted = app('steam')->balanceInRangeConverted($account, $params['start'], clone $params['end'], $this->default);
$previous = array_values($range)[0];
$previousConverted = array_values($rangeConverted)[0];
while ($currentStart <= $params['end']) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format] : $previous;
$balanceConverted = array_key_exists($format, $rangeConverted) ? $rangeConverted[$format] : $previousConverted;
$previous = $balance;
$previousConverted = $balanceConverted;
$currentStart->addDay();
$currentSet['entries'][$label] = $balance;
$currentSet['native_entries'][$label] = $balanceConverted;
}
$this->chartData->add($currentSet);
}
}

View File

@@ -24,18 +24,18 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Chart\BalanceChartRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Api\V2\Request\Chart\ChartRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
/**
* Class BalanceController
@@ -43,7 +43,30 @@ use Illuminate\Support\Collection;
class BalanceController extends Controller
{
use CleansChartData;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
use CollectsAccountsFromFilter;
private AccountRepositoryInterface $repository;
private GroupCollectorInterface $collector;
private ChartData $chartData;
private TransactionCurrency $default;
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$userGroup = $this->validateUserGroup($request);
$this->repository->setUserGroup($userGroup);
$this->collector->setUserGroup($userGroup);
$this->chartData = new ChartData();
$this->default = app('amount')->getDefaultCurrency();
return $next($request);
}
);
}
/**
* The code is practically a duplicate of ReportController::operations.
@@ -54,50 +77,43 @@ class BalanceController extends Controller
* 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 user_group_id
* TODO collector set group, not user
*
* @throws FireflyException
*/
public function balance(BalanceChartRequest $request): JsonResponse
public function balance(ChartRequest $request): JsonResponse
{
$params = $request->getAll();
$queryParameters = $request->getParameters();
$accounts = $this->getAccountList($queryParameters);
/** @var Carbon $start */
$start = $this->parameters->get('start');
/** @var Carbon $end */
$end = $this->parameters->get('end');
$end->endOfDay();
/** @var Collection $accounts */
$accounts = $params['accounts'];
/** @var string $preferredRange */
$preferredRange = $params['period'];
// move date to end of day
$queryParameters['start']->startOfDay();
$queryParameters['end']->endOfDay();
// prepare for currency conversion and data collection:
/** @var TransactionCurrency $default */
$default = app('amount')->getDefaultCurrency();
$default = app('amount')->getDefaultCurrency();
// 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();
$object = new AccountBalanceGrouped();
$object->setPreferredRange($preferredRange);
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
->withAccountInformation()
->setXorAccounts($accounts)
->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::RECONCILIATION, TransactionType::TRANSFER])
;
$journals = $this->collector->getExtractedJournals();
$object = new AccountBalanceGrouped();
$object->setPreferredRange($queryParameters['period']);
$object->setDefault($default);
$object->setAccounts($accounts);
$object->setJournals($journals);
$object->setStart($start);
$object->setEnd($end);
$object->setStart($queryParameters['start']);
$object->setEnd($queryParameters['end']);
$object->groupByCurrencyAndPeriod();
$chartData = $object->convertToChartData();
$data = $object->convertToChartData();
foreach ($data as $entry) {
$this->chartData->add($entry);
}
return response()->json($this->clean($chartData));
return response()->json($this->chartData->render());
}
}

View File

@@ -64,12 +64,9 @@ class BudgetController extends Controller
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->currency = app('amount')->getDefaultCurrency();
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
$this->opsRepository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($userGroup);
$this->opsRepository->setUserGroup($userGroup);
return $next($request);
}
@@ -124,11 +121,11 @@ class BudgetController extends Controller
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
'currency_id' => (string)$row['currency_id'],
'currency_id' => (string) $row['currency_id'],
'currency_code' => $row['currency_code'],
'currency_name' => $row['currency_name'],
'currency_decimal_places' => $row['currency_decimal_places'],
'native_currency_id' => (string)$row['native_currency_id'],
'native_currency_id' => (string) $row['native_currency_id'],
'native_currency_code' => $row['native_currency_code'],
'native_currency_name' => $row['native_currency_name'],
'native_currency_decimal_places' => $row['native_currency_decimal_places'],
@@ -189,12 +186,12 @@ class BudgetController extends Controller
foreach ($array as $currencyId => $block) {
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
$return[$currencyId] ??= [
'currency_id' => (string)$currencyId,
'currency_id' => (string) $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_currency_id' => (string)$this->currency->id,
'currency_decimal_places' => (int) $block['currency_decimal_places'],
'native_currency_id' => (string) $this->currency->id,
'native_currency_code' => $this->currency->code,
'native_currency_name' => $this->currency->name,
'native_currency_symbol' => $this->currency->symbol,

View File

@@ -57,10 +57,7 @@ class CategoryController extends Controller
function ($request, $next) {
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->accountRepos->setUserGroup($userGroup);
}
$this->accountRepos->setUserGroup($this->validateUserGroup($request));
return $next($request);
}
@@ -100,25 +97,25 @@ class CategoryController extends Controller
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$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'];
$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'] === $default->id) {
if ((int) $journal['foreign_currency_id'] === $default->id) {
$nativeAmount = app('steam')->positive($journal['foreign_amount']);
}
// create arrays
$return[$key] ??= [
'label' => $categoryName,
'currency_id' => (string)$currency->id,
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$default->id,
'native_currency_id' => (string) $default->id,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
'native_currency_symbol' => $default->symbol,
@@ -138,7 +135,7 @@ class CategoryController extends Controller
// order by native amount
usort($return, static function (array $a, array $b) {
return (float)$a['native_amount'] < (float)$b['native_amount'] ? 1 : -1;
return (float) $a['native_amount'] < (float) $b['native_amount'] ? 1 : -1;
});
$converter->summarize();

View File

@@ -157,6 +157,9 @@ class Controller extends BaseController
{
$manager = new Manager();
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
// TODO add stuff to path?
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$objects = $paginator->getCollection();

View File

@@ -30,6 +30,7 @@ use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Transformers\V2\AccountTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Log;
class IndexController extends Controller
{
@@ -57,21 +58,35 @@ class IndexController extends Controller
}
/**
* TODO the sort instructions need proper repeatable documentation.
* TODO see autocomplete/account controller for list.
*/
public function index(IndexRequest $request): JsonResponse
{
$this->repository->resetAccountOrder();
$types = $request->getAccountTypes();
$instructions = $request->getSortInstructions('accounts');
$accounts = $this->repository->getAccountsByType($types, $instructions);
$pageSize = $this->parameters->get('limit');
$count = $accounts->count();
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
$transformer = new AccountTransformer();
$types = $request->getAccountTypes();
$sorting = $request->getSortInstructions('accounts');
$filters = $request->getFilterInstructions('accounts');
$accounts = $this->repository->getAccountsByType($types, $sorting, $filters);
$pageSize = $this->parameters->get('limit');
$count = $accounts->count();
$this->parameters->set('sort', $instructions);
// depending on the sort parameters, this list must not be split, because the
// order is calculated in the account transformer and by that time it's too late.
$first = array_key_first($sorting);
$disablePagination = in_array($first, ['last_activity', 'balance', 'balance_difference'], true);
Log::debug(sprintf('Will disable pagination in account index v2? %s', var_export($disablePagination, true)));
if (!$disablePagination) {
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
}
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
$transformer = new AccountTransformer();
$this->parameters->set('disablePagination', $disablePagination);
$this->parameters->set('pageSize', $pageSize);
$this->parameters->set('sort', $sorting);
$this->parameters->set('filters', $filters);
$transformer->setParameters($this->parameters); // give params to transformer
return response()

View File

@@ -45,11 +45,7 @@ class UpdateController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
// new way of user group validation
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

@@ -46,12 +46,7 @@ class IndexController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(BillRepositoryInterface::class);
// new way of user group validation
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

@@ -46,12 +46,7 @@ class ShowController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(BillRepositoryInterface::class);
// new way of user group validation
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

@@ -45,11 +45,7 @@ class SumController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(BillRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

@@ -46,11 +46,7 @@ class IndexController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(PiggyBankRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->repository->setUserGroup($userGroup);
}
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

@@ -52,10 +52,8 @@ class NetWorthController extends Controller
$this->repository = app(AccountRepositoryInterface::class);
// new way of user group validation
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->netWorth->setUserGroup($userGroup);
$this->repository->setUserGroup($userGroup);
}
$this->netWorth->setUserGroup($userGroup);
$this->repository->setUserGroup($userGroup);
return $next($request);
}

View File

@@ -23,47 +23,68 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Autocomplete;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\AccountType;
use FireflyIII\JsonApi\Rules\IsValidFilter;
use FireflyIII\JsonApi\Rules\IsValidPage;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use LaravelJsonApi\Core\Query\QueryParameters;
use LaravelJsonApi\Validation\Rule as JsonApiRule;
/**
* Class AutocompleteRequest
*/
class AutocompleteRequest extends FormRequest
{
use AccountFilter;
use ChecksLogin;
use ConvertsDataTypes;
use ParsesQueryFilters;
protected array $acceptedRoles = [UserRoleEnum::MANAGE_TRANSACTIONS];
public function getData(): array
/**
* Loops over all possible query parameters (these are shared over ALL auto complete requests)
* and returns a validated array of parameters.
*
* The advantage is a single class. But you may also submit "account types" to an endpoint that doesn't use these.
*/
public function getParameters(): array
{
$types = $this->convertString('types');
$array = [];
if ('' !== $types) {
$array = explode(',', $types);
}
$limit = $this->convertInteger('limit');
$limit = 0 === $limit ? 10 : $limit;
// remove 'initial balance' and another from allowed types. its internal
$array = array_diff($array, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION]);
$queryParameters = QueryParameters::cast($this->all());
return [
'types' => $array,
'query' => $this->convertString('query'),
'date' => $this->getCarbonDate('date'),
'limit' => $limit,
'date' => $this->dateOrToday($queryParameters, 'date'),
'query' => $this->arrayOfStrings($queryParameters, 'query'),
'size' => $this->integerFromQueryParams($queryParameters, 'size', 50),
'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
];
}
public function rules(): array
{
return [
'limit' => 'min:0|max:1337',
'fields' => JsonApiRule::notSupported(),
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
'include' => JsonApiRule::notSupported(),
'page' => ['nullable', 'array', new IsValidPage(['size'])],
'sort' => JsonApiRule::notSupported(),
];
}
private function getAccountTypeParameter(mixed $types): array
{
if (is_string($types) && str_contains($types, ',')) {
$types = explode(',', $types);
}
if (!is_iterable($types)) {
$types = [$types];
}
$return = [];
foreach ($types as $type) {
$return = array_merge($return, $this->mapAccountTypes($type));
}
return array_unique($return);
}
}

View File

@@ -0,0 +1,118 @@
<?php
/*
* DashboardChartRequest.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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Chart;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\JsonApi\Rules\IsValidFilter;
use FireflyIII\Rules\IsFilterValueIn;
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
use LaravelJsonApi\Core\Query\QueryParameters;
use LaravelJsonApi\Validation\Rule as JsonApiRule;
/**
* Class ChartRequest
*/
class ChartRequest extends FormRequest
{
use ChecksLogin;
use ConvertsDataTypes;
use ParsesQueryFilters;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
public function getParameters(): array
{
$queryParameters = QueryParameters::cast($this->all());
return [
'start' => $this->dateOrToday($queryParameters, 'start'),
'end' => $this->dateOrToday($queryParameters, 'end'),
'preselected' => $this->stringFromQueryParams($queryParameters, 'preselected', 'empty'),
'period' => $this->stringFromQueryParams($queryParameters, 'period', '1M'),
'accounts' => $this->arrayOfStrings($queryParameters, 'accounts'),
// preselected heeft maar een paar toegestane waardes, dat moet ook goed gaan.
// 'query' => $this->arrayOfStrings($queryParameters, 'query'),
// 'size' => $this->integerFromQueryParams($queryParameters,'size', 50),
// 'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
];
// collect accounts based on this list?
}
// return [
// 'accounts' => $this->getAccountList(),
// 'preselected' => $this->convertString('preselected'),
// ];
// }
/**
* The rules that the incoming request must be matched against.
*/
public function rules(): array
{
return [
'fields' => JsonApiRule::notSupported(),
'filter' => ['nullable', 'array',
new IsValidFilter(['start', 'end', 'preselected', 'accounts']),
new IsFilterValueIn('preselected', config('firefly.preselected_accounts')),
],
'include' => JsonApiRule::notSupported(),
'page' => JsonApiRule::notSupported(),
'sort' => JsonApiRule::notSupported(),
];
// 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',
// 'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
// 'accounts.*' => 'exists:accounts,id',
// ];
}
public function withValidator(Validator $validator): void
{
$validator->after(
static function (Validator $validator): void {
// 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']));
}
}
);
if ($validator->fails()) {
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
}
}
}

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\Support\Request\GetFilterInstructions;
use FireflyIII\Support\Request\GetSortInstructions;
use Illuminate\Foundation\Http\FormRequest;
@@ -40,6 +41,7 @@ class IndexRequest extends FormRequest
use AccountFilter;
use ChecksLogin;
use ConvertsDataTypes;
use GetFilterInstructions;
use GetSortInstructions;
public function getAccountTypes(): array

View File

@@ -60,15 +60,13 @@ class FixIbans extends Command
{
/** @var Account $account */
foreach ($accounts as $account) {
$iban = $account->iban;
if (str_contains($iban, ' ')) {
$iban = app('steam')->filterSpaces((string)$account->iban);
if ('' !== $iban) {
$account->iban = $iban;
$account->save();
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
++$this->count;
}
$iban = (string) $account->iban;
$newIban = app('steam')->filterSpaces($iban);
if ('' !== $iban && $iban !== $newIban) {
$account->iban = $newIban;
$account->save();
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
++$this->count;
}
}
}
@@ -81,7 +79,7 @@ class FixIbans extends Command
foreach ($accounts as $account) {
$userId = $account->user_id;
$set[$userId] ??= [];
$iban = (string)$account->iban;
$iban = (string) $account->iban;
if ('' === $iban) {
continue;
}

View File

@@ -80,7 +80,7 @@ class CreateGroupMemberships extends Command
// check if membership exists
$userGroup = UserGroup::where('title', $user->email)->first();
if (null === $userGroup) {
$userGroup = UserGroup::create(['title' => $user->email, 'default_administration' => true]);
$userGroup = UserGroup::create(['title' => $user->email]);
}
$userRole = UserRole::where('title', UserRoleEnum::OWNER->value)->first();

View File

@@ -115,7 +115,7 @@ class UpdateGroupInformation extends Command
return;
}
if (0 !== $result) {
$this->friendlyPositive(sprintf('User #%d: Moved %d %s objects to the correct group.', $user->id, $result, str_replace('FireflyIII\\Models\\', '', $className)));
$this->friendlyPositive(sprintf('User #%d: Moved %d %s objects to the correct group.', $user->id, $result, str_replace('FireflyIII\Models\\', '', $className)));
}
}
}

View File

@@ -111,7 +111,7 @@ class ForceDecimalSize extends Command
{
// switch stuff based on database connection:
$this->operator = 'REGEXP';
$this->regularExpression = '\'\\\\.[\\\\d]{%d}[1-9]+\'';
$this->regularExpression = '\'\\\.[\\\d]{%d}[1-9]+\'';
$this->cast = 'CHAR';
if ('pgsql' === config('database.default')) {
$this->operator = 'SIMILAR TO';
@@ -119,7 +119,7 @@ class ForceDecimalSize extends Command
$this->cast = 'TEXT';
}
if ('sqlite' === config('database.default')) {
$this->regularExpression = '"\\.[\d]{%d}[1-9]+"';
$this->regularExpression = '"\.[\d]{%d}[1-9]+"';
}
}
@@ -307,7 +307,7 @@ class ForceDecimalSize extends Command
/** @var string $field */
foreach ($fields as $field) {
$value = $item->{$field};
if (null === $value) {
if (null === $value || '' === $value) {
continue;
}
// fix $field by rounding it down correctly.

View File

@@ -0,0 +1,69 @@
<?php
/*
* CorrectAccountBalance.php
* Copyright (c) 2024 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\Console\Commands\Upgrade;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Console\Command;
/**
* Class CorrectionSkeleton
*/
class CorrectAccountBalance extends Command
{
use ShowsFriendlyMessages;
public const string CONFIG_NAME = '610_correct_balances';
protected $description = 'Recalculate all account balance amounts';
protected $signature = 'firefly-iii:correct-account-balance {--F|force : Force the execution of this command.}';
public function handle(): int
{
if ($this->isExecuted() && true !== $this->option('force')) {
$this->friendlyInfo('This command has already been executed.');
return 0;
}
$this->correctBalanceAmounts();
$this->markAsExecuted();
return 0;
}
private function correctBalanceAmounts(): void
{
AccountBalanceCalculator::recalculateAll();
}
private function isExecuted(): bool
{
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
return (bool)$configVar?->data;
}
private function markAsExecuted(): void
{
app('fireflyconfig')->set(self::CONFIG_NAME, true);
}
}

View File

@@ -65,6 +65,7 @@ class UpgradeDatabase extends Command
'firefly-iii:budget-limit-periods',
'firefly-iii:migrate-rule-actions',
'firefly-iii:restore-oauth-keys',
'firefly-iii:correct-account-balance',
// also just in case, some integrity commands:
'firefly-iii:create-group-memberships',
'firefly-iii:upgrade-group-information',

View File

@@ -0,0 +1,49 @@
<?php
/*
* AccountBalance.php
* Copyright (c) 2024 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\Entities;
use FireflyIII\Models\Account;
class AccountBalance
{
public string $id;
public string $amount;
public string $currencyId;
public static function fromArray(): self
{
$balance = new self();
$balance->id = (string) random_int(1, 1000);
$balance->name = (string) random_int(1, 1000);
$balance->amount = (string) random_int(1, 1000);
$balance->currencyId = '1';
return $balance;
}
public function getAccount(): Account
{
return Account::inRandomOrder()->first();
}
}

View File

@@ -36,6 +36,7 @@ use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException as LaravelValidationException;
use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException;
use LaravelJsonApi\Core\Exceptions\JsonApiException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Response;
@@ -63,6 +64,7 @@ class Handler extends ExceptionHandler
HttpException::class,
SuspiciousOperationException::class,
BadHttpHeaderException::class,
JsonApiException::class,
];
/**

View File

@@ -43,7 +43,7 @@ class AttachmentFactory
public function create(array $data): ?Attachment
{
// append if necessary.
$model = !str_contains($data['attachable_type'], 'FireflyIII') ? sprintf('FireflyIII\\Models\\%s', $data['attachable_type'])
$model = !str_contains($data['attachable_type'], 'FireflyIII') ? sprintf('FireflyIII\Models\%s', $data['attachable_type'])
: $data['attachable_type'];
// get journal instead of transaction.

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Transaction;
use FireflyIII\Support\Models\AccountBalanceCalculator;
/**
* Class TransactionObserver
@@ -35,4 +36,16 @@ class TransactionObserver
app('log')->debug('Observe "deleting" of a transaction.');
$transaction?->transactionJournal?->delete();
}
public function updated(Transaction $transaction): void
{
app('log')->debug('Observe "updated" of a transaction.');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
}
public function created(Transaction $transaction): void
{
app('log')->debug('Observe "created" of a transaction.');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
}
}

View File

@@ -717,7 +717,7 @@ trait MetaCollection
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
$this->query->where('journal_meta.data', '=', $internalReference);
return $this;
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Api\V3\Controllers;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\JsonApi\V3\AccountBalances\AccountBalanceSchema;
use FireflyIII\Models\Account;
use Illuminate\Contracts\Support\Responsable;
use LaravelJsonApi\Core\Facades\JsonApi;
use LaravelJsonApi\Core\Responses\DataResponse;
use LaravelJsonApi\Laravel\Http\Controllers\Actions;
use LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery;
class AccountController extends Controller
{
use Actions\AttachRelationship;
use Actions\Destroy;
use Actions\DetachRelationship;
use Actions\FetchMany;
use Actions\FetchOne;
use Actions\FetchRelated;
use Actions\FetchRelationship;
use Actions\Store;
use Actions\Update;
use Actions\UpdateRelationship;
public function readAccountBalances(AnonymousQuery $query, AccountBalanceSchema $schema, Account $account): Responsable
{
$schema = JsonApi::server()->schemas()->schemaFor('account-balances');
$models = $schema
->repository()
->queryAll()
->withRequest($query)
->withAccount($account)
->get()
;
return DataResponse::make($models);
}
}

View File

@@ -121,7 +121,7 @@ class LoginController extends Controller
// Copied directly from AuthenticatesUsers, but with logging added:
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// to log in and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
Log::channel('audit')->warning(sprintf('Login failed. Attempt for user "%s" failed.', $request->get($this->username())));
@@ -233,7 +233,7 @@ class LoginController extends Controller
$storeInCookie = config('google2fa.store_in_cookie', false);
if (false !== $storeInCookie) {
$cookieName = config('google2fa.cookie_name', 'google2fa_token');
request()->cookies->set($cookieName, 'invalid');
\Cookie::queue(\Cookie::make($cookieName, 'invalid-'.time()));
}
$usernameField = $this->username();

View File

@@ -185,7 +185,7 @@ class BudgetLimitController extends Controller
$array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']);
$array['days_left'] = (string)$this->activeDaysLeft($start, $end);
// left per day:
$array['left_per_day'] = bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
$array['left_per_day'] = 0 === bccomp('0', $array['days_left']) ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
// left per day formatted.
$array['left_per_day_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $array['left_per_day']);

View File

@@ -86,14 +86,14 @@ class DebugController extends Controller
{
app('preferences')->mark();
$request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range', 'temp-mfa-secret', 'temp-mfa-codes']);
app('log')->debug('Call cache:clear...');
Artisan::call('cache:clear');
app('log')->debug('Call config:clear...');
Artisan::call('config:clear');
app('log')->debug('Call route:clear...');
Artisan::call('route:clear');
app('log')->debug('Call twig:clean...');
Artisan::call('view:clear');
// also do some recalculations.
Artisan::call('firefly-iii:trigger-credit-recalculation');
try {
Artisan::call('twig:clean');
@@ -101,7 +101,6 @@ class DebugController extends Controller
throw new FireflyException($e->getMessage(), 0, $e);
}
app('log')->debug('Call view:clear...');
Artisan::call('view:clear');
return redirect(route('index'));

View File

@@ -65,7 +65,7 @@ class HomeController extends Controller
$stringEnd = '';
try {
$stringStart = e((string)$request->get('start'));
$stringStart = e((string) $request->get('start'));
$start = Carbon::createFromFormat('Y-m-d', $stringStart);
} catch (InvalidFormatException $e) {
app('log')->error(sprintf('Start: could not parse date string "%s" so ignore it.', $stringStart));
@@ -73,7 +73,7 @@ class HomeController extends Controller
}
try {
$stringEnd = e((string)$request->get('end'));
$stringEnd = e((string) $request->get('end'));
$end = Carbon::createFromFormat('Y-m-d', $stringEnd);
} catch (InvalidFormatException $e) {
app('log')->error(sprintf('End could not parse date string "%s" so ignore it.', $stringEnd));
@@ -92,7 +92,7 @@ class HomeController extends Controller
app('log')->debug('dateRange: Received dateRange', ['start' => $stringStart, 'end' => $stringEnd, 'label' => $request->get('label')]);
// check if the label is "everything" or "Custom range" which will betray
// a possible problem with the budgets.
if ($label === (string)trans('firefly.everything') || $label === (string)trans('firefly.customRange')) {
if ($label === (string) trans('firefly.everything') || $label === (string) trans('firefly.customRange')) {
$isCustomRange = true;
app('log')->debug('Range is now marked as "custom".');
}
@@ -100,7 +100,7 @@ class HomeController extends Controller
$diff = $start->diffInDays($end, true) + 1;
if ($diff > 366) {
$request->session()->flash('warning', (string)trans('firefly.warning_much_data', ['days' => (int)$diff]));
$request->session()->flash('warning', (string) trans('firefly.warning_much_data', ['days' => (int) $diff]));
}
$request->session()->put('is_custom_range', $isCustomRange);
@@ -128,10 +128,10 @@ class HomeController extends Controller
return redirect(route('new-user.index'));
}
if ('v1' === (string)config('view.layout')) {
if ('v1' === (string) config('view.layout')) {
return $this->indexV1($repository);
}
if ('v2' === (string)config('view.layout')) {
if ('v2' === (string) config('view.layout')) {
return $this->indexV2();
}
@@ -141,8 +141,9 @@ class HomeController extends Controller
private function indexV1(AccountRepositoryInterface $repository): mixed
{
$types = config('firefly.accountTypesByIdentifier.asset');
$pageTitle = (string) trans('firefly.main_dashboard_page_title');
$count = $repository->count($types);
$subTitle = (string)trans('firefly.welcome_back');
$subTitle = (string) trans('firefly.welcome_back');
$transactions = [];
$frontpage = app('preferences')->getFresh('frontpageAccounts', $repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray());
$frontpageArray = $frontpage->data;
@@ -151,8 +152,9 @@ class HomeController extends Controller
}
/** @var Carbon $start */
/** @var Carbon $end */
$start = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth());
$accounts = $repository->getAccountsById($frontpageArray);
$today = today(config('app.timezone'));
@@ -176,20 +178,21 @@ class HomeController extends Controller
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
return view('index', compact('count', 'subTitle', 'transactions', 'billCount', 'start', 'end', 'today'));
return view('index', compact('count', 'subTitle', 'transactions', 'billCount', 'start', 'end', 'today', 'pageTitle'));
}
private function indexV2(): mixed
{
$subTitle = (string)trans('firefly.welcome_back');
$subTitle = (string) trans('firefly.welcome_back');
$pageTitle = (string) trans('firefly.main_dashboard_page_title');
$start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth());
$start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth());
/** @var User $user */
$user = auth()->user();
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
return view('index', compact('subTitle', 'start', 'end'));
return view('index', compact('subTitle', 'start', 'end', 'pageTitle'));
}
}

View File

@@ -81,6 +81,8 @@ class ReconcileController extends Controller
if ($end->lt($start)) {
[$start, $end] = [$end, $start];
}
$end->endOfDay();
$start->startOfDay();
$route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]);
$selectedIds = $request->get('journals') ?? [];

View File

@@ -81,7 +81,12 @@ class RecurrenceController extends Controller
$skip = $skip < 0 || $skip > 31 ? 0 : $skip;
$weekend = $weekend < 1 || $weekend > 4 ? 1 : $weekend;
if (null === $start || null === $end || null === $firstDate || null === $endDate) {
if (null === $endDate) {
// safety catch:
$endDate = now()->addYear();
}
if (null === $start || null === $end || null === $firstDate) {
return response()->json();
}

View File

@@ -120,7 +120,7 @@ class PreferencesController extends Controller
// list of locales also has "equal" which makes it equal to whatever the language is.
try {
$locales = json_decode((string)file_get_contents(resource_path(sprintf('lang/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR);
$locales = json_decode((string)file_get_contents(resource_path(sprintf('locales/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
app('log')->error($e->getMessage());
$locales = [];

View File

@@ -84,6 +84,7 @@ class ShowController extends Controller
$transformer->setParameters(new ParameterBag());
$array = $transformer->transform($recurrence);
$groups = $this->recurring->getTransactions($recurrence);
$today = today(config('app.timezone'));
$array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null;

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use Exception;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
@@ -65,7 +64,7 @@ class ConvertController extends Controller
$this->middleware(
function ($request, $next) {
$this->accountRepository = app(AccountRepositoryInterface::class);
app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('title', (string) trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-exchange');
return $next($request);
@@ -95,7 +94,7 @@ class ConvertController extends Controller
$groupTitle = $group->title ?? $first->description;
$groupArray = $transformer->transformObject($group);
$subTitle = (string)trans('firefly.convert_to_'.$destinationType->type, ['description' => $groupTitle]);
$subTitle = (string) trans('firefly.convert_to_'.$destinationType->type, ['description' => $groupTitle]);
$subTitleIcon = 'fa-exchange';
// get a list of asset accounts and liabilities and stuff, in various combinations:
@@ -111,7 +110,7 @@ class ConvertController extends Controller
if ($sourceType->type === $destinationType->type) { // cannot convert to its own type.
app('log')->debug('This is already a transaction of the expected type..');
session()->flash('info', (string)trans('firefly.convert_is_already_type_'.$destinationType->type));
session()->flash('info', (string) trans('firefly.convert_is_already_type_'.$destinationType->type));
return redirect(route('transactions.show', [$group->id]));
}
@@ -147,7 +146,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$role = (string)$this->accountRepository->getMetaValue($account, 'account_role');
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
$name = $account->name;
if ('' === $role) {
$role = 'no_account_type';
@@ -165,7 +164,7 @@ class ConvertController extends Controller
$role = 'revenue_account';
}
$key = (string)trans('firefly.opt_group_'.$role);
$key = (string) trans('firefly.opt_group_'.$role);
$grouped[$key][$account->id] = $name;
}
@@ -184,7 +183,7 @@ class ConvertController extends Controller
// group accounts:
/** @var Account $account */
foreach ($accountList as $account) {
$role = (string)$this->accountRepository->getMetaValue($account, 'account_role');
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
$name = $account->name;
if ('' === $role) {
$role = 'no_account_type';
@@ -202,7 +201,7 @@ class ConvertController extends Controller
$role = 'expense_account';
}
$key = (string)trans('firefly.opt_group_'.$role);
$key = (string) trans('firefly.opt_group_'.$role);
$grouped[$key][$account->id] = $name;
}
@@ -225,7 +224,7 @@ class ConvertController extends Controller
$balance = app('steam')->balance($account, today());
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$role = 'l_'.$account->accountType->type;
$key = (string)trans('firefly.opt_group_'.$role);
$key = (string) trans('firefly.opt_group_'.$role);
$grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
}
@@ -247,12 +246,12 @@ class ConvertController extends Controller
foreach ($accountList as $account) {
$balance = app('steam')->balance($account, today());
$currency = $this->accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
$role = (string)$this->accountRepository->getMetaValue($account, 'account_role');
$role = (string) $this->accountRepository->getMetaValue($account, 'account_role');
if ('' === $role) {
$role = 'no_account_type';
}
$key = (string)trans('firefly.opt_group_'.$role);
$key = (string) trans('firefly.opt_group_'.$role);
$grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')';
}
@@ -285,7 +284,7 @@ class ConvertController extends Controller
// correct transfers:
$group->refresh();
session()->flash('success', (string)trans('firefly.converted_to_'.$destinationType->type));
session()->flash('success', (string) trans('firefly.converted_to_'.$destinationType->type));
event(new UpdatedTransactionGroup($group, true, true));
return redirect(route('transactions.show', [$group->id]));
@@ -306,11 +305,11 @@ class ConvertController extends Controller
$destinationId = $data['destination_id'][$journal->id] ?? null;
$destinationName = $data['destination_name'][$journal->id] ?? null;
// double check its not an empty string.
$sourceId = '' === $sourceId || null === $sourceId ? null : (int)$sourceId;
$sourceName = '' === $sourceName ? null : (string)$sourceName;
$destinationId = '' === $destinationId || null === $destinationId ? null : (int)$destinationId;
$destinationName = '' === $destinationName ? null : (string)$destinationName;
// double check it's not an empty string.
$sourceId = '' === $sourceId || null === $sourceId ? null : (int) $sourceId;
$sourceName = '' === $sourceName ? null : (string) $sourceName;
$destinationId = '' === $destinationId || null === $destinationId ? null : (int) $destinationId;
$destinationName = '' === $destinationName ? null : (string) $destinationName;
$validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName]);
$validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName]);
@@ -331,6 +330,19 @@ class ConvertController extends Controller
'type' => $transactionType->type,
];
// also set the currency to the currency of the source account, in case you're converting a deposit into a transfer.
if (TransactionType::TRANSFER === $transactionType->type && TransactionType::DEPOSIT === $journal->transactionType->type) {
$source = $this->accountRepository->find((int) $sourceId);
$sourceCurrency = $this->accountRepository->getAccountCurrency($source);
$dest = $this->accountRepository->find((int) $destinationId);
$destCurrency = $this->accountRepository->getAccountCurrency($dest);
if (null !== $sourceCurrency && null !== $destCurrency && $sourceCurrency->code !== $destCurrency->code) {
$update['currency_id'] = $sourceCurrency->id;
$update['foreign_currency_id'] = $destCurrency->id;
$update['foreign_amount'] = '1'; // not the best solution but at this point the amount is hard to get.
}
}
/** @var JournalUpdateService $service */
$service = app(JournalUpdateService::class);
$service->setTransactionJournal($journal);

View File

@@ -27,6 +27,7 @@ use Carbon\Carbon;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\Http\Controllers\RequestInformation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Class SessionFilter.
@@ -63,6 +64,7 @@ class Range
{
// ignore preference. set the range to be the current month:
if (!app('session')->has('start') && !app('session')->has('end')) {
Log::debug('setRange: Session has no start or end.');
$viewRange = app('preferences')->get('viewRange', '1M')->data;
if (is_array($viewRange)) {
$viewRange = '1M';
@@ -76,6 +78,8 @@ class Range
app('session')->put('end', $end);
}
if (!app('session')->has('first')) {
Log::debug('setRange: Session has no "first".');
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$journal = $repository->firstNull();

View File

@@ -28,4 +28,9 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
/**
* Class VerifyCsrfToken.
*/
class VerifyCsrfToken extends Middleware {}
class VerifyCsrfToken extends Middleware
{
protected $except = [
'oauth/token',
];
}

View File

@@ -57,7 +57,7 @@ class AccountFormRequest extends FormRequest
'account_type_name' => $this->convertString('objectType'),
'currency_id' => $this->convertInteger('currency_id'),
'virtual_balance' => $this->convertString('virtual_balance'),
'iban' => $this->convertString('iban'),
'iban' => $this->convertIban('iban'),
'BIC' => $this->convertString('BIC'),
'account_number' => $this->convertString('account_number'),
'account_role' => $this->convertString('account_role'),

View File

@@ -361,11 +361,17 @@ class CreateRecurringTransactions implements ShouldQueue
// create transaction array and send to factory.
$groupTitle = null;
$count = $recurrence->recurrenceTransactions->count();
if ($count > 1) {
// #8844, if there is one recurrence transaction, use the first title as the title.
if (1 === $count) {
/** @var RecurrenceTransaction $first */
$first = $recurrence->recurrenceTransactions()->first();
$groupTitle = $first->description;
}
// #8844, if there are more, use the recurrence transaction itself.
if ($count > 1) {
$groupTitle = $recurrence->title;
}
if (0 === $count) {
app('log')->error('No transactions to be created in this recurrence. Cannot continue.');
@@ -411,12 +417,12 @@ class CreateRecurringTransactions implements ShouldQueue
/** @var RecurrenceTransaction $transaction */
foreach ($transactions as $index => $transaction) {
$single = [
'type' => null === $first->transactionType ? strtolower($recurrence->transactionType->type) : strtolower($first->transactionType->type),
'type' => null === $transaction?->transactionType?->type ? strtolower($recurrence->transactionType->type) : strtolower($transaction->transactionType->type),
'date' => $date,
'user' => $recurrence->user_id,
'currency_id' => $transaction->transaction_currency_id,
'currency_code' => null,
'description' => $first->description,
'description' => $transaction->description,
'amount' => $transaction->amount,
'budget_id' => $this->repository->getBudget($transaction),
'budget_name' => null,

View File

@@ -29,6 +29,7 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Bus\Queueable;
@@ -100,7 +101,7 @@ class DownloadExchangeRates implements ShouldQueue
try {
$res = $client->get($url);
} catch (RequestException $e) {
} catch (ConnectException|RequestException $e) {
app('log')->warning(sprintf('Trying to grab "%s" resulted in error "%d".', $url, $e->getMessage()));
return;

View File

@@ -130,12 +130,12 @@ class MailError extends Job implements ShouldQueue
}
if (file_exists($file)) {
Log::debug(sprintf('Read file in "%s"', $file));
$limits = json_decode(file_get_contents($file), true);
$limits = json_decode((string)file_get_contents($file), true);
}
// limit reached?
foreach ($types as $type => $info) {
Log::debug(sprintf('Now checking limit "%s"', $type), $info);
if (!isset($limits[$type])) {
if (!array_key_exists($type, $limits)) {
Log::debug(sprintf('Limit "%s" reset to zero, did not exist yet.', $type));
$limits[$type] = [
'time' => time(),

View File

@@ -0,0 +1,53 @@
<?php
/*
* IsValidFilter.php
* Copyright (c) 2024 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\JsonApi\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
class IsValidFilter implements ValidationRule
{
private array $allowed;
public function __construct(array $keys)
{
$this->allowed = $keys;
$this->allowed[] = 'user_group_id';
}
#[\Override]
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if ('filter' !== $attribute) {
$fail('validation.bad_api_filter')->translate();
}
if (!is_array($value)) {
$value = explode(',', $value);
}
foreach ($value as $key => $val) {
if (!in_array($key, $this->allowed, true)) {
$fail('validation.bad_api_filter')->translate(['filter' => $key]);
}
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* IsValidFilter.php
* Copyright (c) 2024 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\JsonApi\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
class IsValidPage implements ValidationRule
{
private array $allowed;
public function __construct(array $keys)
{
$this->allowed = $keys;
}
#[\Override]
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if ('page' !== $attribute) {
$fail('validation.bad_api_filter')->translate();
}
if (!is_array($value)) {
$value = explode(',', $value);
}
foreach ($value as $key => $val) {
if (!in_array($key, $this->allowed, true)) {
$fail('validation.bad_api_page')->translate();
}
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* AccountBalanceRepository.php
* Copyright (c) 2024 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\JsonApi\V3\AccountBalances;
use FireflyIII\Entities\AccountBalance;
use LaravelJsonApi\Contracts\Store\QueriesAll;
use LaravelJsonApi\NonEloquent\AbstractRepository;
class AccountBalanceRepository extends AbstractRepository implements QueriesAll
{
#[\Override]
public function find(string $resourceId): ?object
{
return AccountBalance::fromArray();
}
public function queryAll(): Capabilities\AccountBalanceQuery
{
return Capabilities\AccountBalanceQuery::make()
->withServer($this->server)
->withSchema($this->schema)
;
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\AccountBalances;
use Illuminate\Http\Request;
use LaravelJsonApi\Core\Resources\JsonApiResource;
class AccountBalanceResource extends JsonApiResource
{
/**
* Get the resource id.
*/
public function id(): string
{
return $this->resource->id;
}
/**
* Get the resource's attributes.
*
* @param null|Request $request
*/
public function attributes($request): iterable
{
return [
'name' => $this->resource->amount,
'amount' => $this->resource->amount,
];
}
/**
* Get the resource's relationships.
*
* @param null|Request $request
*/
public function relationships($request): iterable
{
return [
$this->relation('account')->withData($this->resource->getAccount()),
];
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\AccountBalances;
use FireflyIII\Entities\AccountBalance;
use LaravelJsonApi\Core\Schema\Schema;
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
use LaravelJsonApi\NonEloquent\Fields\Attribute;
use LaravelJsonApi\NonEloquent\Fields\ID;
class AccountBalanceSchema extends Schema
{
/**
* The model the schema corresponds to.
*/
public static string $model = AccountBalance::class;
/**
* Get the resource fields.
*/
public function fields(): array
{
return [
ID::make(),
Attribute::make('name'),
Attribute::make('amount'),
HasOne::make('account'),
];
}
/**
* Get the resource filters.
*/
public function filters(): array
{
return [
// Filter::make('id'),
];
}
public function repository(): AccountBalanceRepository
{
return AccountBalanceRepository::make()
->withServer($this->server)
->withSchema($this)
;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* AccountBalanceQuery.php
* Copyright (c) 2024 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\JsonApi\V3\AccountBalances\Capabilities;
use FireflyIII\Entities\AccountBalance;
use FireflyIII\Models\Account;
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
class AccountBalanceQuery extends QueryAll
{
private Account $account;
/**
* QuerySites constructor.
*/
public function __construct()
{
parent::__construct();
}
public function get(): iterable
{
return [
AccountBalance::fromArray(),
AccountBalance::fromArray(),
AccountBalance::fromArray(),
AccountBalance::fromArray(),
];
}
public function withAccount(Account $account): self
{
$this->account = $account;
return $this;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* AccountRepository.php
* Copyright (c) 2024 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\JsonApi\V3\Accounts;
use FireflyIII\Models\Account;
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
use LaravelJsonApi\Contracts\Store\QueriesAll;
use LaravelJsonApi\NonEloquent\AbstractRepository;
class AccountRepository extends AbstractRepository implements QueriesAll
{
use UsergroupAware;
/**
* SiteRepository constructor.
*/
public function __construct() {}
public function find(string $resourceId): ?object
{
return Account::find((int) $resourceId);
}
public function queryAll(): Capabilities\AccountQuery
{
return Capabilities\AccountQuery::make()
->withUserGroup($this->userGroup)
->withServer($this->server)
->withSchema($this->schema)
;
}
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Accounts;
use FireflyIII\Models\Account;
use Illuminate\Http\Request;
use LaravelJsonApi\Core\Resources\JsonApiResource;
/**
* @property Account $resource
*/
class AccountResource extends JsonApiResource
{
/**
* Get the resource's attributes.
*
* @param null|Request $request
*/
public function attributes($request): iterable
{
return [
'created_at' => $this->resource->created_at,
'updated_at' => $this->resource->updated_at,
'name' => $this->resource->name,
'iban' => '' === $this->resource->iban ? null : $this->resource->iban,
'active' => $this->resource->active,
'last_activity' => $this->resource->last_activity,
'type' => $this->resource->type,
'account_role' => $this->resource->account_role,
// 'virtual_balance' => $this->resource->virtual_balance,
// 'native_balance' => $this->resource->native_balance,
// 'user' => $this->resource->user_array,
// 'balances' => []
//
// currency
// 'currency_id' => $this->resource->currency_id,
// 'currency_code' => $this->resource->currency_code,
// 'currency_symbol' => $this->resource->currency_symbol,
// 'currency_decimal_places' => $this->resource->currency_decimal_places,
// balance (in currency, on date)
// 'current_balance' => $this->resource->current_balance,
// 'current_balance' => app('steam')->bcround(app('steam')->balance($account, $date), $decimalPlaces),
// 'current_balance_date' => $date->toAtomString(),
// 'notes' => $this->repository->getNoteText($account),
// 'monthly_payment_date' => $monthlyPaymentDate,
// 'credit_card_type' => $creditCardType,
// 'account_number' => $this->repository->getMetaValue($account, 'account_number'),
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
// 'opening_balance' => $openingBalance,
// 'opening_balance_date' => $openingBalanceDate,
// 'liability_type' => $liabilityType,
// 'liability_direction' => $liabilityDirection,
// 'interest' => $interest,
// 'interest_period' => $interestPeriod,
// 'current_debt' => $this->repository->getMetaValue($account, 'current_debt'),
// 'include_net_worth' => $includeNetWorth,
// 'longitude' => $longitude,
// 'latitude' => $latitude,
// 'zoom_level' => $zoomLevel,
// 'order' => $order,
// 'currency_id' => (string) $currency->id,
// 'currency_code' => $currency->code,
// 'currency_symbol' => $currency->symbol,
// 'currency_decimal_places' => $currency->decimal_places,
//
// 'native_currency_id' => (string) $this->default->id,
// 'native_currency_code' => $this->default->code,
// 'native_currency_symbol' => $this->default->symbol,
// 'native_currency_decimal_places' => $this->default->decimal_places,
//
// // balance:
// 'current_balance' => $balance,
// 'native_current_balance' => $nativeBalance,
// 'current_balance_date' => $this->getDate()->endOfDay()->toAtomString(),
//
// // balance difference
// 'balance_difference' => $balanceDiff,
// 'native_balance_difference' => $nativeBalanceDiff,
// 'balance_difference_start' => $diffStart,
// 'balance_difference_end' => $diffEnd,
//
// // more meta
// 'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
//
// // liability stuff
// 'liability_type' => $liabilityType,
// 'liability_direction' => $liabilityDirection,
// 'interest' => $interest,
// 'interest_period' => $interestPeriod,
// 'current_debt' => $currentDebt,
//
// // object group
// 'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
// 'object_group_order' => $objectGroupOrder,
// 'object_group_title' => $objectGroupTitle,
// 'notes' => $this->repository->getNoteText($account),
// 'monthly_payment_date' => $monthlyPaymentDate,
// 'credit_card_type' => $creditCardType,
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
// 'virtual_balance' => number_format((float) $account->virtual_balance, $decimalPlaces, '.', ''),
// 'opening_balance' => $openingBalance,
// 'opening_balance_date' => $openingBalanceDate,
// 'include_net_worth' => $includeNetWorth,
// 'longitude' => $longitude,
// 'latitude' => $latitude,
// 'zoom_level' => $zoomLevel,
];
}
/**
* Get the resource's relationships.
*
* @param null|Request $request
*/
public function relationships($request): iterable
{
return [
$this->relation('user')->withData($this->resource->user),
$this->relation('account_balances')->withData($this->resource->balances),
];
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Accounts;
use FireflyIII\Models\Account;
use LaravelJsonApi\Eloquent\Contracts\Paginator;
use LaravelJsonApi\Eloquent\Fields\Boolean;
use LaravelJsonApi\Eloquent\Fields\DateTime;
use LaravelJsonApi\Eloquent\Fields\ID;
use LaravelJsonApi\Eloquent\Fields\Number;
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
use LaravelJsonApi\Eloquent\Fields\Str;
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
use LaravelJsonApi\Eloquent\Schema;
class AccountSchema extends Schema
{
/**
* The model the schema corresponds to.
*/
public static string $model = Account::class;
/**
* Get the resource fields.
*/
public function fields(): array
{
return [
ID::make(),
DateTime::make('created_at')->sortable()->readOnly(),
DateTime::make('updated_at')->sortable()->readOnly(),
Str::make('name')->sortable(),
Str::make('account_type'),
Str::make('virtual_balance'),
Str::make('iban'),
Boolean::make('active'),
Number::make('order'),
HasOne::make('user'),
HasMany::make('account_balances'),
];
}
/**
* Get the resource filters.
*/
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
/**
* Get the resource paginator.
*/
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* AccountQuery.php
* Copyright (c) 2024 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\JsonApi\V3\Accounts\Capabilities;
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\ExpandsQuery;
use FireflyIII\Support\JsonApi\FiltersPagination;
use FireflyIII\Support\JsonApi\SortsCollection;
use FireflyIII\Support\JsonApi\ValidateSortParameters;
use LaravelJsonApi\Contracts\Store\HasPagination;
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
class AccountQuery extends QueryAll implements HasPagination
{
use ExpandsQuery;
use FiltersPagination;
use PaginatesEnumerables;
use SortsCollection;
use UsergroupAware;
use ValidateSortParameters;
#[\Override]
public function get(): iterable
{
$filters = $this->queryParameters->filter();
$sort = $this->queryParameters->sortFields();
$pagination = $this->filtersPagination($this->queryParameters->page());
$needsAll = $this->validateParams('account', $sort);
$query = $this->userGroup->accounts();
if (!$needsAll) {
$query = $this->addPagination($query, $pagination);
}
$query = $this->addSortParams($query, $sort);
$query = $this->addFilterParams('account', $query, $filters);
$collection = $query->get(['accounts.*']);
// enrich data
$enrichment = new AccountEnrichment();
$collection = $enrichment->enrich($collection);
// add filters after the query
// add sort after the query
return $this->sortCollection($collection, $sort);
// var_dump($filters->value('name'));
// exit;
}
}

38
app/JsonApi/V3/Server.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3;
use FireflyIII\JsonApi\V3\Accounts\AccountSchema;
use FireflyIII\JsonApi\V3\AccountBalances\AccountBalanceSchema;
use FireflyIII\JsonApi\V3\Users\UserSchema;
use LaravelJsonApi\Core\Server\Server as BaseServer;
class Server extends BaseServer
{
/**
* The base URI namespace for this server.
*/
protected string $baseUri = '/api/v3';
/**
* Bootstrap the server when it is handling an HTTP request.
*/
public function serving(): void
{
// no-op
}
/**
* Get the server's list of schemas.
*/
protected function allSchemas(): array
{
return [
AccountSchema::class,
UserSchema::class,
AccountBalanceSchema::class,
];
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Users;
use FireflyIII\Models\User;
use Illuminate\Http\Request;
use LaravelJsonApi\Core\Resources\JsonApiResource;
/**
* @property User $resource
*/
class UserResource extends JsonApiResource
{
/**
* Get the resource's attributes.
*
* @param null|Request $request
*/
public function attributes($request): iterable
{
return [
'created_at' => $this->resource->created_at,
'updated_at' => $this->resource->updated_at,
'email' => $this->resource->email,
];
}
/**
* Get the resource's relationships.
*
* @param null|Request $request
*/
public function relationships($request): iterable
{
return [
// @TODO
];
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Users;
use FireflyIII\User;
use LaravelJsonApi\Eloquent\Contracts\Paginator;
use LaravelJsonApi\Eloquent\Fields\DateTime;
use LaravelJsonApi\Eloquent\Fields\ID;
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;
use LaravelJsonApi\Eloquent\Fields\Str;
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
use LaravelJsonApi\Eloquent\Schema;
class UserSchema extends Schema
{
/**
* The model the schema corresponds to.
*/
public static string $model = User::class;
/**
* Get the resource fields.
*/
public function fields(): array
{
return [
ID::make(),
DateTime::make('created_at')->sortable()->readOnly(),
DateTime::make('updated_at')->sortable()->readOnly(),
Str::make('email'),
HasMany::make('accounts'),
];
}
/**
* Get the resource filters.
*/
public function filters(): array
{
return [
WhereIdIn::make($this),
];
}
/**
* Get the resource paginator.
*/
public function pagination(): ?Paginator
{
return PagePagination::make();
}
}

View File

@@ -193,6 +193,11 @@ class Account extends Model
return $this->hasMany(AccountMeta::class);
}
public function accountBalances(): HasMany
{
return $this->hasMany(AccountBalance::class);
}
public function getEditNameAttribute(): string
{
$name = $this->name;

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class AccountBalance extends Model
{
use HasFactory;
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
public function account(): BelongsTo
{
return $this->belongsTo(Account::class);
}
public function transactionCurrency(): BelongsTo
{
return $this->belongsTo(TransactionCurrency::class);
}
}

View File

@@ -98,7 +98,7 @@ class UserGroup extends Model
{
use ReturnsIntegerIdTrait;
protected $fillable = ['title', 'default_administration'];
protected $fillable = ['title'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -1,8 +1,7 @@
<?php
/**
* auth.php
* Copyright (c) 2019 james@firefly-iii.org
/*
* AccountPolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,23 +16,33 @@
* 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/>.
*/
/*
* PLEASE DO NOT EDIT THIS FILE DIRECTLY.
* YOUR CHANGES WILL BE OVERWRITTEN!
* YOUR PR WITH CHANGES TO THIS FILE WILL BE REJECTED!
*
* GO TO CROWDIN TO FIX OR CHANGE TRANSLATIONS!
*
* https://crowdin.com/project/firefly-iii
*
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
return [
'failed' => 'These credentials do not match our records.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];
namespace FireflyIII\Policies;
use FireflyIII\Entities\AccountBalance;
use FireflyIII\User;
class AccountBalancePolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, AccountBalance $accountBalance): bool
{
return true;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* AccountPolicy.php
* Copyright (c) 2024 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\Policies;
use FireflyIII\Models\Account;
use FireflyIII\User;
class AccountPolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, Account $account): bool
{
return true;
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
return auth()->check();
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewUser(User $user, Account $account): bool
{
return $this->view($user, $account);
}
public function viewAccountBalances(User $user, Account $account): bool
{
return $this->view($user, $account);
}
}

View File

@@ -1,8 +1,7 @@
<?php
/**
* components.php
* Copyright (c) 2019 james@firefly-iii.org
/*
* BalancePolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,21 +16,33 @@
* 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/>.
*/
/*
* PLEASE DO NOT EDIT THIS FILE DIRECTLY.
* YOUR CHANGES WILL BE OVERWRITTEN!
* YOUR PR WITH CHANGES TO THIS FILE WILL BE REJECTED!
*
* GO TO CROWDIN TO FIX OR CHANGE TRANSLATIONS!
*
* https://crowdin.com/project/firefly-iii
*
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
return [
];
namespace FireflyIII\Policies;
use FireflyIII\Models\Account;
use FireflyIII\User;
class BalancePolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, Account $account): bool
{
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return auth()->check();
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* UserPolicy.php
* Copyright (c) 2024 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\Policies;
use FireflyIII\User;
class UserPolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, User $user1): bool
{
return true;
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
return auth()->check();
}
public function viewAccounts(User $user): bool
{
return true;
return auth()->check();
}
}

View File

@@ -28,6 +28,7 @@ use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;
/**
* Class AppServiceProvider
@@ -87,6 +88,7 @@ class AppServiceProvider extends ServiceProvider
*/
public function register(): void
{
Passport::ignoreRoutes();
// Passport::ignoreMigrations();
// Sanctum::ignoreMigrations();
}

View File

@@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
use FireflyIII\Services\Internal\Update\AccountUpdateService;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
@@ -123,6 +124,7 @@ class AccountRepository implements AccountRepositoryInterface
public function findByIbanNull(string $iban, array $types): ?Account
{
$iban = Steam::filterSpaces($iban);
$query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
if (0 !== count($types)) {

View File

@@ -146,7 +146,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface
// should be validated already:
if (array_key_exists('attachable_type', $data) && array_key_exists('attachable_id', $data)) {
$attachment->attachable_id = (int)$data['attachable_id'];
$attachment->attachable_type = sprintf('FireflyIII\\Models\\%s', $data['attachable_type']);
$attachment->attachable_type = sprintf('FireflyIII\Models\%s', $data['attachable_type']);
}
$attachment->save();

View File

@@ -408,10 +408,16 @@ class RecurringRepository implements RecurringRepositoryInterface
private function filterMaxDate(?Carbon $max, array $occurrences): array
{
if (null === $max) {
return $occurrences;
}
$filtered = [];
if (null === $max) {
foreach ($occurrences as $date) {
if ($date->gt(today())) {
$filtered[] = $date;
}
}
return $filtered;
}
foreach ($occurrences as $date) {
if ($date->lte($max) && $date->gt(today())) {
$filtered[] = $date;

View File

@@ -150,7 +150,6 @@ class RuleRepository implements RuleRepositoryInterface
$params[] = sprintf('%s:true', OperatorQuerySearch::getRootOperator($trigger->trigger_type));
}
if (true === $needsContext) {
var_dump('x');
$params[] = sprintf('%s:"%s"', OperatorQuerySearch::getRootOperator($trigger->trigger_type), $trigger->trigger_value);
}
}

View File

@@ -27,9 +27,11 @@ namespace FireflyIII\Repositories\UserGroups\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Services\Internal\Update\AccountUpdateService;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
@@ -79,6 +81,7 @@ class AccountRepository implements AccountRepositoryInterface
public function findByIbanNull(string $iban, array $types): ?Account
{
$iban = Steam::filterSpaces($iban);
$query = $this->userGroup->accounts()->where('iban', '!=', '')->whereNotNull('iban');
if (0 !== count($types)) {
@@ -240,7 +243,7 @@ class AccountRepository implements AccountRepositoryInterface
}
}
public function getAccountsByType(array $types, ?array $sort = []): Collection
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
{
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
@@ -249,6 +252,22 @@ class AccountRepository implements AccountRepositoryInterface
$query->accountTypeIn($types);
}
// process filters
// TODO this should be repeatable, it feels like a hack when you do it here.
// TODO some fields cannot be filtered using the query, and a second filter must be applied on the collection.
foreach ($filters as $column => $value) {
// filter on NULL values
if (null === $value) {
continue;
}
if ('active' === $column) {
$query->where('accounts.active', $value);
}
if ('name' === $column) {
$query->where('accounts.name', 'LIKE', sprintf('%%%s%%', $value));
}
}
// add sort parameters. At this point they're filtered to allowed fields to sort by:
$hasActiveColumn = array_key_exists('active', $sort);
if (count($sort) > 0) {
@@ -273,7 +292,7 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['accounts.*']);
}
public function searchAccount(string $query, array $types, int $limit): Collection
public function searchAccount(array $query, array $types, int $limit): Collection
{
// search by group, not by user
$dbQuery = $this->userGroup->accounts()
@@ -283,13 +302,17 @@ class AccountRepository implements AccountRepositoryInterface
->orderBy('accounts.name', 'ASC')
->with(['accountType'])
;
if ('' !== $query) {
if (count($query) > 0) {
// split query on spaces just in case:
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$dbQuery->where('name', 'LIKE', $search);
}
$dbQuery->where(function (EloquentBuilder $q) use ($query): void {
foreach ($query as $line) {
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('name', 'LIKE', $search);
}
}
});
}
if (0 !== count($types)) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -337,4 +360,40 @@ class AccountRepository implements AccountRepositoryInterface
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
}
#[\Override]
public function getObjectGroups(Collection $accounts): array
{
$groupIds = [];
$return = [];
$set = DB::table('object_groupables')->where('object_groupable_type', Account::class)
->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get()
;
/** @var \stdClass $row */
foreach ($set as $row) {
$groupIds[] = $row->object_group_id;
}
$groupIds = array_unique($groupIds);
$groups = ObjectGroup::whereIn('id', $groupIds)->get();
/** @var \stdClass $row */
foreach ($set as $row) {
if (!array_key_exists($row->object_groupable_id, $return)) {
/** @var null|ObjectGroup $group */
$group = $groups->firstWhere('id', '=', $row->object_group_id);
if (null !== $group) {
$return[$row->object_groupable_id] = ['title' => $group->title, 'order' => $group->order, 'id' => $group->id];
}
}
}
return $return;
}
#[\Override]
public function getAccountBalances(Account $account): Collection
{
return $account->accountBalances;
}
}

View File

@@ -53,9 +53,11 @@ interface AccountRepositoryInterface
public function getAccountCurrency(Account $account): ?TransactionCurrency;
public function getAccountBalances(Account $account): Collection;
public function getAccountsById(array $accountIds): Collection;
public function getAccountsByType(array $types, ?array $sort = []): Collection;
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection;
/**
* Used in the infinite accounts list.
@@ -69,6 +71,8 @@ interface AccountRepositoryInterface
*/
public function getMetaValue(Account $account, string $field): ?string;
public function getObjectGroups(Collection $accounts): array;
public function getUserGroup(): UserGroup;
/**
@@ -76,7 +80,7 @@ interface AccountRepositoryInterface
*/
public function resetAccountOrder(): void;
public function searchAccount(string $query, array $types, int $limit): Collection;
public function searchAccount(array $query, array $types, int $limit): Collection;
public function setUser(User $user): void;

View File

@@ -24,17 +24,27 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\UserGroups\Category;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
class CategoryRepository implements CategoryRepositoryInterface
{
use UserGroupTrait;
public function searchCategory(string $query, int $limit): Collection
public function searchCategory(array $query, int $limit): Collection
{
$search = $this->userGroup->categories();
if ('' !== $query) {
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
if (count($query) > 0) {
// split query on spaces just in case:
$search->where(function (EloquentBuilder $q) use ($query): void {
foreach ($query as $line) {
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('name', 'LIKE', $search);
}
}
});
}
return $search->take($limit)->get();

View File

@@ -30,5 +30,5 @@ interface CategoryRepositoryInterface
/**
* Search for a category using wild cards. Uses the database, so case sensitive.
*/
public function searchCategory(string $query, int $limit): Collection;
public function searchCategory(array $query, int $limit): Collection;
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\UserGroups\Journal;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
/**
@@ -33,15 +34,24 @@ class JournalRepository implements JournalRepositoryInterface
{
use UserGroupTrait;
public function searchJournalDescriptions(string $search, int $limit): Collection
public function searchJournalDescriptions(array $query, int $limit): Collection
{
$query = $this->userGroup->transactionJournals()
$search = $this->userGroup->transactionJournals()
->orderBy('date', 'DESC')
;
if ('' !== $search) {
$query->where('description', 'LIKE', sprintf('%%%s%%', $search));
if (count($query) > 0) {
// split query on spaces just in case:
$search->where(function (EloquentBuilder $q) use ($query): void {
foreach ($query as $line) {
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('description', 'LIKE', $search);
}
}
});
}
return $query->take($limit)->get();
return $search->take($limit)->get();
}
}

View File

@@ -34,7 +34,7 @@ interface JournalRepositoryInterface
/**
* Search in journal descriptions.
*/
public function searchJournalDescriptions(string $search, int $limit): Collection;
public function searchJournalDescriptions(array $query, int $limit): Collection;
public function setUser(User $user): void;
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\UserGroups\Tag;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
/**
@@ -33,11 +34,20 @@ class TagRepository implements TagRepositoryInterface
{
use UserGroupTrait;
public function searchTag(string $query, int $limit): Collection
public function searchTag(array $query, int $limit): Collection
{
$search = $this->user->tags();
if ('' !== $query) {
$search->where('tag', 'LIKE', sprintf('%%%s%%', $query));
$search = $this->userGroup->tags();
if (count($query) > 0) {
// split query on spaces just in case:
$search->where(function (EloquentBuilder $q) use ($query): void {
foreach ($query as $line) {
$parts = explode(' ', $line);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
$q->orWhere('tag', 'LIKE', $search);
}
}
});
}
return $search->take($limit)->get(['tags.*']);

View File

@@ -30,5 +30,5 @@ interface TagRepositoryInterface
/**
* Find one or more tags based on the query.
*/
public function searchTag(string $query, int $limit): Collection;
public function searchTag(array $query, int $limit): Collection;
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* IsFilterValueIn.php
* Copyright (c) 2024 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\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
class IsFilterValueIn implements ValidationRule
{
private string $key;
private array $values;
public function __construct(string $key, array $values)
{
$this->key = $key;
$this->values = $values;
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if (!is_array($value)) {
return;
}
if (!array_key_exists($this->key, $value)) {
return;
}
$value = $value[$this->key] ?? null;
if (!is_string($value) && null !== $value) {
$fail('validation.filter_not_string')->translate(['filter' => $this->key]);
}
if (!in_array($value, $this->values, true)) {
$fail('validation.filter_must_be_in')->translate(['filter' => $this->key, 'values' => implode(', ', $this->values)]);
}
// $fail('validation.filter_not_string')->translate(['filter' => $this->key]);
}
}

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Rules;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Contracts\Validation\ValidationRule;
/**
@@ -95,6 +96,9 @@ class UniqueIban implements ValidationRule
$maxCounts = $this->getMaxOccurrences();
foreach ($maxCounts as $type => $max) {
// make sure to trim the value of $value so all spaces are removed.
$value = Steam::filterSpaces($value);
$count = $this->countHits($type, $value);
app('log')->debug(sprintf('Count for "%s" and IBAN "%s" is %d', $type, $value, $count));
if ($count > $max) {

View File

@@ -216,7 +216,6 @@ class CreditRecalculateService
app('log')->debug(sprintf('Destination amount "%s" is now "%s"', $dest->amount, app('steam')->negative($dest->amount)));
$source->amount = app('steam')->positive($source->amount);
$dest->amount = app('steam')->negative($source->amount);
var_dump($source->foreign_amount);
if (null !== $source->foreign_amount && '' !== $source->foreign_amount) {
$source->foreign_amount = app('steam')->positive($source->foreign_amount);
app('log')->debug(sprintf('Source foreign amount "%s" is now "%s"', $source->foreign_amount, app('steam')->positive($source->foreign_amount)));

View File

@@ -80,9 +80,10 @@ class RemoteUserProvider implements UserProvider
$roleObject = Role::where('name', 'owner')->first();
$user->roles()->attach($roleObject);
}
// make sure the user gets an administration as well.
CreateGroupMemberships::createGroupMembership($user);
}
// make sure the user gets an administration as well.
CreateGroupMemberships::createGroupMembership($user);
app('log')->debug(sprintf('Going to return user #%d (%s)', $user->id, $user->email));
return $user;

View File

@@ -0,0 +1,66 @@
<?php
/*
* ChartData.php
* Copyright (c) 2024 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\Support\Chart;
use FireflyIII\Exceptions\FireflyException;
class ChartData
{
private array $series;
public function __construct()
{
$this->series = [];
}
public function render(): array
{
if (0 === count($this->series)) {
throw new FireflyException('No series added to chart');
}
return $this->series;
}
/**
* @throws FireflyException
*/
public function add(array $data): void
{
if (array_key_exists('currency_id', $data)) {
$data['currency_id'] = (string) $data['currency_id'];
}
if (array_key_exists('native_currency_id', $data)) {
$data['native_currency_id'] = (string) $data['native_currency_id'];
}
$required = ['start', 'date', 'end', 'entries', 'native_entries'];
foreach ($required as $field) {
if (!array_key_exists($field, $data)) {
throw new FireflyException(sprintf('Data-set is missing the "%s"-variable.', $field));
}
}
$this->series[] = $data;
}
}

View File

@@ -35,15 +35,22 @@ use Illuminate\Support\Facades\Log;
*/
class AccountBalanceGrouped
{
private array $accountIds;
private string $carbonFormat;
private array $currencies = [];
private array $data = [];
private TransactionCurrency $default;
private Carbon $end;
private array $journals = [];
private string $preferredRange;
private Carbon $start;
private array $accountIds;
private string $carbonFormat;
private array $currencies = [];
private array $data = [];
private TransactionCurrency $default;
private Carbon $end;
private array $journals = [];
private string $preferredRange;
private Carbon $start;
private ExchangeRateConverter $converter;
public function __construct()
{
$this->accountIds = [];
$this->converter = app(ExchangeRateConverter::class);
}
/**
* Convert the given input to a chart compatible array.
@@ -58,14 +65,15 @@ class AccountBalanceGrouped
// income and expense array prepped:
$income = [
'label' => 'earned',
'currency_id' => (string)$currency['currency_id'],
'currency_id' => (string) $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'native_currency_id' => (string)$currency['native_currency_id'],
'native_currency_id' => (string) $currency['native_currency_id'],
'native_currency_symbol' => $currency['native_currency_symbol'],
'native_currency_code' => $currency['native_currency_code'],
'native_currency_decimal_places' => $currency['native_currency_decimal_places'],
'date' => $this->start->toAtomString(),
'start' => $this->start->toAtomString(),
'end' => $this->end->toAtomString(),
'period' => $this->preferredRange,
@@ -74,14 +82,15 @@ class AccountBalanceGrouped
];
$expense = [
'label' => 'spent',
'currency_id' => (string)$currency['currency_id'],
'currency_id' => (string) $currency['currency_id'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
'native_currency_id' => (string)$currency['native_currency_id'],
'native_currency_id' => (string) $currency['native_currency_id'],
'native_currency_symbol' => $currency['native_currency_symbol'],
'native_currency_code' => $currency['native_currency_code'],
'native_currency_decimal_places' => $currency['native_currency_decimal_places'],
'date' => $this->start->toAtomString(),
'start' => $this->start->toAtomString(),
'end' => $this->end->toAtomString(),
'period' => $this->preferredRange,
@@ -124,76 +133,7 @@ class AccountBalanceGrouped
// loop. group by currency and by period.
/** @var array $journal */
foreach ($this->journals as $journal) {
// format the date according to the period
$period = $journal['date']->format($this->carbonFormat);
$currencyId = (int)$journal['currency_id'];
$currency = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
$this->currencies[$currencyId] = $currency; // may just re-assign itself, don't mind.
// set the array with monetary info, if it does not exist.
$this->data[$currencyId] ??= [
'currency_id' => (string)$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_currency_id' => (string)$this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal_places' => $this->default->decimal_places,
];
// set the array (in monetary info) with spent/earned in this $period, if it does not exist.
$this->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'], $this->accountIds, true)
)
) {
$key = 'earned';
$amount = app('steam')->positive($journal['amount']);
}
// get conversion rate
try {
$rate = $converter->getCurrencyRate($currency, $this->default, $journal['date']);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
$rate = '1';
}
$amountConverted = bcmul($amount, $rate);
// perhaps transaction already has the foreign amount in the native currency.
if ((int)$journal['foreign_currency_id'] === $this->default->id) {
$amountConverted = $journal['foreign_amount'] ?? '0';
$amountConverted = 'earned' === $key ? app('steam')->positive($amountConverted) : app('steam')->negative($amountConverted);
}
// add normal entry
$this->data[$currencyId][$period][$key] = bcadd($this->data[$currencyId][$period][$key], $amount);
// add converted entry
$convertedKey = sprintf('native_%s', $key);
$this->data[$currencyId][$period][$convertedKey] = bcadd($this->data[$currencyId][$period][$convertedKey], $amountConverted);
$this->processJournal($journal);
}
$converter->summarize();
}
@@ -209,12 +149,12 @@ class AccountBalanceGrouped
$defaultCurrencyId = $default->id;
$this->currencies = [$default->id => $default]; // currency cache
$this->data[$defaultCurrencyId] = [
'currency_id' => (string)$defaultCurrencyId,
'currency_id' => (string) $defaultCurrencyId,
'currency_symbol' => $default->symbol,
'currency_code' => $default->code,
'currency_name' => $default->name,
'currency_decimal_places' => $default->decimal_places,
'native_currency_id' => (string)$defaultCurrencyId,
'native_currency_id' => (string) $defaultCurrencyId,
'native_currency_symbol' => $default->symbol,
'native_currency_code' => $default->code,
'native_currency_name' => $default->name,
@@ -242,4 +182,113 @@ class AccountBalanceGrouped
{
$this->start = $start;
}
private function processJournal(array $journal): void
{
// format the date according to the period
$period = $journal['date']->format($this->carbonFormat);
$currencyId = (int) $journal['currency_id'];
$currency = $this->findCurrency($currencyId);
// set the array with monetary info, if it does not exist.
$this->createDefaultDataEntry($journal);
// set the array (in monetary info) with spent/earned in this $period, if it does not exist.
$this->createDefaultPeriodEntry($journal);
// is this journal's amount in- our outgoing?
$key = $this->getDataKey($journal);
$amount = 'spent' === $key ? app('steam')->negative($journal['amount']) : app('steam')->positive($journal['amount']);
// get conversion rate
$rate = $this->getRate($currency, $journal['date']);
$amountConverted = bcmul($amount, $rate);
// perhaps transaction already has the foreign amount in the native currency.
if ((int) $journal['foreign_currency_id'] === $this->default->id) {
$amountConverted = $journal['foreign_amount'] ?? '0';
$amountConverted = 'earned' === $key ? app('steam')->positive($amountConverted) : app('steam')->negative($amountConverted);
}
// add normal entry
$this->data[$currencyId][$period][$key] = bcadd($this->data[$currencyId][$period][$key], $amount);
// add converted entry
$convertedKey = sprintf('native_%s', $key);
$this->data[$currencyId][$period][$convertedKey] = bcadd($this->data[$currencyId][$period][$convertedKey], $amountConverted);
}
private function findCurrency(int $currencyId): TransactionCurrency
{
if (array_key_exists($currencyId, $this->currencies)) {
return $this->currencies[$currencyId];
}
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
return $this->currencies[$currencyId];
}
private function createDefaultDataEntry(array $journal): void
{
$currencyId = (int) $journal['currency_id'];
$this->data[$currencyId] ??= [
'currency_id' => (string) $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_currency_id' => (string) $this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal_places' => $this->default->decimal_places,
];
}
private function createDefaultPeriodEntry(array $journal): void
{
$currencyId = (int) $journal['currency_id'];
$period = $journal['date']->format($this->carbonFormat);
$this->data[$currencyId][$period] ??= [
'period' => $period,
'spent' => '0',
'earned' => '0',
'native_spent' => '0',
'native_earned' => '0',
];
}
private function getDataKey(array $journal): string
{
$key = 'spent';
// 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'], $this->accountIds, true)
)
) {
$key = 'earned';
}
return $key;
}
private function getRate(TransactionCurrency $currency, Carbon $date): string
{
try {
$rate = $this->converter->getCurrencyRate($currency, $this->default, $date);
} catch (FireflyException $e) {
app('log')->error($e->getMessage());
$rate = '1';
}
return $rate;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* CollectsAccountsFromFilter.php
* Copyright (c) 2024 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\Support\Http\Api;
use FireflyIII\Models\AccountType;
use Illuminate\Support\Collection;
trait CollectsAccountsFromFilter
{
/**
* TODO Duplicate function but I think it belongs here or in a separate trait
*/
private function getAccountList(array $queryParameters): Collection
{
$collection = new Collection();
// always collect from the query parameter, even when it's empty.
foreach ($queryParameters['accounts'] as $accountId) {
$account = $this->repository->find((int) $accountId);
if (null !== $account) {
$collection->push($account);
}
}
// if no "preselected", and found accounts
if ('empty' === $queryParameters['preselected'] && $collection->count() > 0) {
return $collection;
}
// if no preselected, but no accounts:
if ('empty' === $queryParameters['preselected'] && 0 === $collection->count()) {
$defaultSet = $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT])->pluck('id')->toArray();
$frontpage = app('preferences')->get('frontpageAccounts', $defaultSet);
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
$frontpage->data = $defaultSet;
$frontpage->save();
}
return $this->repository->getAccountsById($frontpage->data);
}
// both options are overruled by "preselected"
if ('all' === $queryParameters['preselected']) {
return $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
}
if ('assets' === $queryParameters['preselected']) {
return $this->repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]);
}
if ('liabilities' === $queryParameters['preselected']) {
return $this->repository->getAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
}
return $collection;
}
}

View File

@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
@@ -48,7 +49,11 @@ class ExchangeRateConverter
*/
public function convert(TransactionCurrency $from, TransactionCurrency $to, Carbon $date, string $amount): string
{
Log::debug('convert()');
if (false === config('cer.enabled')) {
Log::debug('ExchangeRateConverter: disabled, return amount as is.');
return $amount;
}
$rate = $this->getCurrencyRate($from, $to, $date);
return bcmul($amount, $rate);
@@ -59,7 +64,11 @@ class ExchangeRateConverter
*/
public function getCurrencyRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
Log::debug('getCurrencyRate()');
if (false === config('cer.enabled')) {
Log::debug('ExchangeRateConverter: disabled, return "1".');
return '1';
}
$rate = $this->getRate($from, $to, $date);
return '0' === $rate ? '1' : $rate;
@@ -70,25 +79,36 @@ class ExchangeRateConverter
*/
private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
Log::debug('getRate()');
if ($this->isPrepared && $this->noPreparedRates) {
$fallback = $this->fallback[$from->id][$to->id] ?? '0';
Log::debug(sprintf('Return fallback rate from #%d to #%d on %s: %s', $from->id, $to->id, $date->format('Y-m-d'), $fallback));
$key = $this->getCacheKey($from, $to, $date);
$res = Cache::get($key, null);
return $fallback;
// find in cache
if (null !== $res) {
Log::debug(sprintf('ExchangeRateConverter: Return cached rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $res;
}
// first attempt:
// find in database
$rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d'));
if (null !== $rate) {
Cache::forever($key, $rate);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $rate;
}
// no result. perhaps the other way around?
// find reverse in database
$rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d'));
if (null !== $rate) {
return bcdiv('1', $rate);
$rate = bcdiv('1', $rate);
Cache::forever($key, $rate);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
return $rate;
}
// if nothing in place, fall back on the rate for $from to EUR
// fallback scenario.
$first = $this->getEuroRate($from, $date);
$second = $this->getEuroRate($to, $date);
@@ -96,17 +116,19 @@ class ExchangeRateConverter
if (0 === bccomp('0', $first) || 0 === bccomp('0', $second)) {
Log::warning(sprintf('$first is "%s" and $second is "%s"', $first, $second));
return '0';
return '1';
}
$second = bcdiv('1', $second);
$rate = bcmul($first, $second);
Log::debug(sprintf('ExchangeRateConverter: Return DB rate from #%d to #%d on %s.', $from->id, $to->id, $date->format('Y-m-d')));
Cache::forever($key, $rate);
return bcmul($first, $second);
return $rate;
}
private function getFromDB(int $from, int $to, string $date): ?string
{
Log::debug('getFromDB()');
if ($from === $to) {
return '1';
}
@@ -115,7 +137,7 @@ class ExchangeRateConverter
// perhaps the rate has been cached during this particular run
$preparedRate = $this->prepared[$date][$from][$to] ?? null;
if (null !== $preparedRate && 0 !== bccomp('0', $preparedRate)) {
Log::debug(sprintf('Found prepared rate from #%d to #%d on %s.', $from, $to, $date));
Log::debug(sprintf('ExchangeRateConverter: Found prepared rate from #%d to #%d on %s.', $from, $to, $date));
return $preparedRate;
}
@@ -127,7 +149,7 @@ class ExchangeRateConverter
if ('' === $rate) {
return null;
}
Log::debug(sprintf('Found cached rate from #%d to #%d on %s.', $from, $to, $date));
Log::debug(sprintf('ExchangeRateConverter: Found !cached! rate from #%d to #%d on %s.', $from, $to, $date));
return $rate;
}
@@ -142,19 +164,19 @@ class ExchangeRateConverter
->first()
;
++$this->queryCount;
$rate = (string)$result?->rate;
$rate = (string) $result?->rate;
if ('' === $rate) {
app('log')->debug(sprintf('Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date));
app('log')->debug(sprintf('ExchangeRateConverter: Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date));
return null;
}
if (0 === bccomp('0', $rate)) {
app('log')->debug(sprintf('Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date));
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date));
return null;
}
app('log')->debug(sprintf('Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate));
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate));
$cache->store($rate);
// if the rate has not been cached during this particular run, save it
@@ -178,7 +200,6 @@ class ExchangeRateConverter
*/
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
{
Log::debug('getEuroRate()');
$euroId = $this->getEuroId();
if ($euroId === $currency->id) {
return '1';
@@ -198,7 +219,7 @@ class ExchangeRateConverter
// grab backup values from config file:
$backup = config(sprintf('cer.rates.%s', $currency->code));
if (null !== $backup) {
return bcdiv('1', (string)$backup);
return bcdiv('1', (string) $backup);
// app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
// return $backup;
}
@@ -216,7 +237,7 @@ class ExchangeRateConverter
$cache = new CacheProperties();
$cache->addProperty('cer-euro-id');
if ($cache->has()) {
return (int)$cache->get();
return (int) $cache->get();
}
$euro = TransactionCurrency::whereCode('EUR')->first();
++$this->queryCount;
@@ -233,6 +254,9 @@ class ExchangeRateConverter
*/
public function prepare(TransactionCurrency $from, TransactionCurrency $to, Carbon $start, Carbon $end): void
{
if (false === config('cer.enabled')) {
return;
}
Log::debug('prepare()');
$start->startOfDay();
$end->endOfDay();
@@ -305,6 +329,14 @@ class ExchangeRateConverter
public function summarize(): void
{
if (false === config('cer.enabled')) {
return;
}
Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
}
private function getCacheKey(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
return sprintf('cer-%d-%d-%s', $from->id, $to->id, $date->format('Y-m-d'));
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* ParsesQueryFilters.php
* Copyright (c) 2024 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\Support\Http\Api;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Support\Facades\Log;
use LaravelJsonApi\Core\Query\QueryParameters;
trait ParsesQueryFilters
{
private function dateOrToday(QueryParameters $parameters, string $field): Carbon
{
$date = today();
$value = $parameters->filter()?->value($field, date('Y-m-d'));
if (is_array($value)) {
Log::error(sprintf('Multiple values for date field "%s". Using first value.', $field));
$value = $value[0];
}
try {
$date = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone'));
} catch (InvalidFormatException $e) {
Log::debug(sprintf('Invalid date format in request. Using today: %s', $e->getMessage()));
}
return $date;
}
private function arrayOfStrings(QueryParameters $parameters, string $field): array
{
$array = $parameters->filter()?->value($field, []) ?? [];
return is_string($array) ? [$array] : $array;
}
private function integerFromQueryParams(QueryParameters $parameters, string $field, int $default): int
{
return (int) ($parameters->page()[$field] ?? $default);
}
private function stringFromQueryParams(QueryParameters $parameters, string $field, string $default): string
{
return (string) ($parameters->page()[$field] ?? $default);
}
}

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