mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-25 04:31:24 +00:00
Compare commits
73 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac8a43bb37 | ||
|
|
2df4b40a28 | ||
|
|
e06736c254 | ||
|
|
ec367e94ce | ||
|
|
1515dea9fa | ||
|
|
adedf9c17d | ||
|
|
0b52fb84f1 | ||
|
|
16e742ae73 | ||
|
|
1b4471dfae | ||
|
|
ae152ce0a4 | ||
|
|
2aa023f140 | ||
|
|
6e2e4c6f08 | ||
|
|
1c8c038735 | ||
|
|
4d339a6da8 | ||
|
|
b7edd4407a | ||
|
|
a679a1e94a | ||
|
|
180451d32f | ||
|
|
7396f22bca | ||
|
|
058019aa84 | ||
|
|
695f83d1d8 | ||
|
|
ac4dfb3baf | ||
|
|
427001b223 | ||
|
|
3117d8b30d | ||
|
|
d19dd2a8b2 | ||
|
|
de3dcc3fc2 | ||
|
|
077f3e095b | ||
|
|
ad3b0bb320 | ||
|
|
8538741341 | ||
|
|
a0aef5d579 | ||
|
|
fdd93427aa | ||
|
|
ac3f6557de | ||
|
|
b0a909150c | ||
|
|
913f163fe4 | ||
|
|
3126b07b33 | ||
|
|
08ca90cf75 | ||
|
|
540ac2a277 | ||
|
|
ed80bed066 | ||
|
|
41d2541c6a | ||
|
|
5dedf63498 | ||
|
|
09bc4f41d2 | ||
|
|
cebf0b5c57 | ||
|
|
1632a57e3e | ||
|
|
744c4be7d1 | ||
|
|
bd99ef3eff | ||
|
|
8a86f13a5d | ||
|
|
7418b2f0ee | ||
|
|
a0e9de9312 | ||
|
|
7e23a6f5e8 | ||
|
|
44589f8744 | ||
|
|
d24531030f | ||
|
|
25bdab1346 | ||
|
|
41af1c863a | ||
|
|
76b3b18cfb | ||
|
|
e6fb2958a9 | ||
|
|
15b75b322f | ||
|
|
86149d1032 | ||
|
|
ded142cd9e | ||
|
|
7923eb9ec9 | ||
|
|
132553c108 | ||
|
|
c2269fc9a4 | ||
|
|
aed30d1499 | ||
|
|
84a1a876e1 | ||
|
|
dc675707f9 | ||
|
|
d5667c7ef6 | ||
|
|
cba1213dd1 | ||
|
|
7219c90957 | ||
|
|
af13bd991e | ||
|
|
48e548eb52 | ||
|
|
1a19e27f0e | ||
|
|
0cbd22426d | ||
|
|
d5e52e99e0 | ||
|
|
f52978e71f | ||
|
|
3a3358124d |
214
.ci/php-cs-fixer/composer.lock
generated
214
.ci/php-cs-fixer/composer.lock
generated
@@ -72,30 +72,38 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.4",
|
||||
"version": "3.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "04229f163664973f68f38f6f73d917799168ef24"
|
||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24",
|
||||
"reference": "04229f163664973f68f38f6f73d917799168ef24",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<1.11.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.3",
|
||||
"phpstan/phpstan": "^1.11.8",
|
||||
"phpstan/phpstan-strict-rules": "^1.1",
|
||||
"symfony/phpunit-bridge": "^5"
|
||||
"phpunit/phpunit": "^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
},
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -123,7 +131,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.4"
|
||||
"source": "https://github.com/composer/pcre/tree/3.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -139,20 +147,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-27T13:40:54+00:00"
|
||||
"time": "2024-07-25T09:36:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32"
|
||||
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32",
|
||||
"reference": "35e8d0af4486141bc745f23a29cc2091eb624a32",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6",
|
||||
"reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -204,7 +212,7 @@
|
||||
"support": {
|
||||
"irc": "ircs://irc.libera.chat:6697/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.4.0"
|
||||
"source": "https://github.com/composer/semver/tree/3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -220,7 +228,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-08-31T09:50:34+00:00"
|
||||
"time": "2024-07-12T11:35:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
@@ -398,16 +406,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.59.3",
|
||||
"version": "v3.60.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29"
|
||||
"reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29",
|
||||
"reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
|
||||
"reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -489,7 +497,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.60.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -497,7 +505,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-16T14:17:03+00:00"
|
||||
"time": "2024-07-25T09:26:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1026,31 +1034,31 @@
|
||||
},
|
||||
{
|
||||
"name": "react/socket",
|
||||
"version": "v1.15.0",
|
||||
"version": "v1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/socket.git",
|
||||
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038"
|
||||
"reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038",
|
||||
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038",
|
||||
"url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
|
||||
"reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"php": ">=5.3.0",
|
||||
"react/dns": "^1.11",
|
||||
"react/dns": "^1.13",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3 || ^2.6 || ^1.2.1",
|
||||
"react/stream": "^1.2"
|
||||
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
|
||||
"react/stream": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4 || ^3 || ^2",
|
||||
"react/async": "^4.3 || ^3.3 || ^2",
|
||||
"react/promise-stream": "^1.4",
|
||||
"react/promise-timer": "^1.10"
|
||||
"react/promise-timer": "^1.11"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1094,7 +1102,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/socket/issues",
|
||||
"source": "https://github.com/reactphp/socket/tree/v1.15.0"
|
||||
"source": "https://github.com/reactphp/socket/tree/v1.16.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1102,7 +1110,7 @@
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-15T11:02:10+00:00"
|
||||
"time": "2024-07-26T10:38:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/stream",
|
||||
@@ -1184,16 +1192,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/diff",
|
||||
"version": "6.0.1",
|
||||
"version": "6.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||
"reference": "ab83243ecc233de5655b76f577711de9f842e712"
|
||||
"reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712",
|
||||
"reference": "ab83243ecc233de5655b76f577711de9f842e712",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
|
||||
"reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1239,7 +1247,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||
"security": "https://github.com/sebastianbergmann/diff/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/6.0.1"
|
||||
"source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1247,20 +1255,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-02T07:30:33+00:00"
|
||||
"time": "2024-07-03T04:53:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3"
|
||||
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/9b008f2d7b21c74ef4d0c3de6077a642bc55ece3",
|
||||
"reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
|
||||
"reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1324,7 +1332,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/console/tree/v7.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1340,7 +1348,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-07-26T12:41:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1567,16 +1575,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "802e87002f919296c9f606457d9fa327a0b3d6b2"
|
||||
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/802e87002f919296c9f606457d9fa327a0b3d6b2",
|
||||
"reference": "802e87002f919296c9f606457d9fa327a0b3d6b2",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c",
|
||||
"reference": "92a91985250c251de9b947a14bb2c9390b1a562c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1613,7 +1621,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1629,20 +1637,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-06-28T10:03:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6"
|
||||
"reference": "717c6329886f32dc65e27461f80f2a465412fdca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6",
|
||||
"reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca",
|
||||
"reference": "717c6329886f32dc65e27461f80f2a465412fdca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1677,7 +1685,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/finder/tree/v7.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1693,7 +1701,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-07-24T07:08:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@@ -1764,16 +1772,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1823,7 +1831,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1839,20 +1847,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
|
||||
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
|
||||
"reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a",
|
||||
"reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1901,7 +1909,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1917,20 +1925,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
|
||||
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb",
|
||||
"reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1982,7 +1990,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1998,20 +2006,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2062,7 +2070,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2078,20 +2086,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-06-19T12:30:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2142,7 +2150,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2158,20 +2166,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.29.0",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d"
|
||||
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||
"reference": "c565ad1e63f30e7477fc40738343c62b40bc672d",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af",
|
||||
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2218,7 +2226,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2234,20 +2242,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
"time": "2024-06-19T12:30:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "febf90124323a093c7ee06fdb30e765ca3c20028"
|
||||
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028",
|
||||
"reference": "febf90124323a093c7ee06fdb30e765ca3c20028",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
|
||||
"reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2279,7 +2287,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/process/tree/v7.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2295,7 +2303,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T14:57:53+00:00"
|
||||
"time": "2024-07-26T12:44:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -2444,16 +2452,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.1.1",
|
||||
"version": "v7.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "60bc311c74e0af215101235aa6f471bcbc032df2"
|
||||
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/60bc311c74e0af215101235aa6f471bcbc032df2",
|
||||
"reference": "60bc311c74e0af215101235aa6f471bcbc032df2",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07",
|
||||
"reference": "ea272a882be7f20cad58d5d78c215001617b7f07",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2511,7 +2519,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.1"
|
||||
"source": "https://github.com/symfony/string/tree/v7.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2527,7 +2535,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-04T06:40:14+00:00"
|
||||
"time": "2024-07-22T10:25:37+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -4,6 +4,7 @@ updates:
|
||||
# Check for updates to GitHub Actions every week
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
labels: []
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
@@ -11,6 +12,7 @@ updates:
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/" # Location of package manifests
|
||||
target-branch: develop
|
||||
labels: []
|
||||
versioning-strategy: increase
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -18,6 +20,7 @@ updates:
|
||||
# yarn / JS updates
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
labels: []
|
||||
target-branch: develop
|
||||
versioning-strategy: increase
|
||||
schedule:
|
||||
|
||||
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v1.2.0
|
||||
- uses: github/command@v1.2.1
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
||||
110
app/Api/V2/Controllers/JsonApi/AccountController.php
Normal file
110
app/Api/V2/Controllers/JsonApi/AccountController.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountController.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\Api\V2\Controllers\JsonApi;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\JsonApi\V2\Accounts\AccountCollectionQuery;
|
||||
use FireflyIII\JsonApi\V2\Accounts\AccountSchema;
|
||||
use FireflyIII\JsonApi\V2\Accounts\AccountSingleQuery;
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Contracts\Support\Responsable;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Responses\DataResponse;
|
||||
use LaravelJsonApi\Laravel\Http\Controllers\Actions;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
*
|
||||
* This class handles api/v2 requests for accounts.
|
||||
* Most stuff is default stuff.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fetch zero to many JSON API resources.
|
||||
*
|
||||
* @return Responsable|Response
|
||||
*/
|
||||
public function index(AccountSchema $schema, AccountCollectionQuery $request)
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$models = $schema
|
||||
->repository()
|
||||
->queryAll()
|
||||
->withRequest($request)
|
||||
->get()
|
||||
;
|
||||
|
||||
// do something custom...
|
||||
|
||||
return new DataResponse($models);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch zero to one JSON API resource by id.
|
||||
*
|
||||
* @return Responsable|Response
|
||||
*/
|
||||
public function show(AccountSchema $schema, AccountSingleQuery $request, Account $account)
|
||||
{
|
||||
$model = $schema
|
||||
->repository()
|
||||
->queryOne($account)
|
||||
->withRequest($request)
|
||||
->first()
|
||||
;
|
||||
|
||||
// do something custom...
|
||||
|
||||
return new DataResponse($model);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// }
|
||||
}
|
||||
@@ -44,55 +44,8 @@ class FixUnevenAmount extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$count = 0;
|
||||
$journals = \DB::table('transactions')
|
||||
->groupBy('transaction_journal_id')
|
||||
->whereNull('deleted_at')
|
||||
->get(['transaction_journal_id', \DB::raw('SUM(amount) AS the_sum')])
|
||||
;
|
||||
|
||||
/** @var \stdClass $entry */
|
||||
foreach ($journals as $entry) {
|
||||
$sum = (string)$entry->the_sum;
|
||||
if (!is_numeric($sum)
|
||||
|| '' === $sum // @phpstan-ignore-line
|
||||
|| str_contains($sum, 'e')
|
||||
|| str_contains($sum, ',')) {
|
||||
$message = sprintf(
|
||||
'Journal #%d has an invalid sum ("%s"). No sure what to do.',
|
||||
$entry->transaction_journal_id,
|
||||
$entry->the_sum
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
++$count;
|
||||
|
||||
continue;
|
||||
}
|
||||
$res = -1;
|
||||
|
||||
try {
|
||||
$res = bccomp($sum, '0');
|
||||
} catch (\ValueError $e) {
|
||||
$this->friendlyError(sprintf('Could not bccomp("%s", "0").', $sum));
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
if (0 !== $res) {
|
||||
$message = sprintf(
|
||||
'Sum of journal #%d is %s instead of zero.',
|
||||
$entry->transaction_journal_id,
|
||||
$entry->the_sum
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
$this->fixJournal($entry->transaction_journal_id);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyPositive('Database amount integrity is OK');
|
||||
}
|
||||
$this->fixUnevenAmounts();
|
||||
$this->matchCurrencies();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -149,4 +102,76 @@ class FixUnevenAmount extends Command
|
||||
$message = sprintf('Corrected amount in transaction journal #%d', $param);
|
||||
$this->friendlyInfo($message);
|
||||
}
|
||||
|
||||
private function fixUnevenAmounts(): void
|
||||
{
|
||||
$count = 0;
|
||||
$journals = \DB::table('transactions')
|
||||
->groupBy('transaction_journal_id')
|
||||
->whereNull('deleted_at')
|
||||
->get(['transaction_journal_id', \DB::raw('SUM(amount) AS the_sum')])
|
||||
;
|
||||
|
||||
/** @var \stdClass $entry */
|
||||
foreach ($journals as $entry) {
|
||||
$sum = (string) $entry->the_sum;
|
||||
if (!is_numeric($sum)
|
||||
|| '' === $sum // @phpstan-ignore-line
|
||||
|| str_contains($sum, 'e')
|
||||
|| str_contains($sum, ',')) {
|
||||
$message = sprintf(
|
||||
'Journal #%d has an invalid sum ("%s"). No sure what to do.',
|
||||
$entry->transaction_journal_id,
|
||||
$entry->the_sum
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
++$count;
|
||||
|
||||
continue;
|
||||
}
|
||||
$res = -1;
|
||||
|
||||
try {
|
||||
$res = bccomp($sum, '0');
|
||||
} catch (\ValueError $e) {
|
||||
$this->friendlyError(sprintf('Could not bccomp("%s", "0").', $sum));
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
if (0 !== $res) {
|
||||
$message = sprintf(
|
||||
'Sum of journal #%d is %s instead of zero.',
|
||||
$entry->transaction_journal_id,
|
||||
$entry->the_sum
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
app('log')->warning($message);
|
||||
$this->fixJournal($entry->transaction_journal_id);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
$this->friendlyPositive('Database amount integrity is OK');
|
||||
}
|
||||
}
|
||||
|
||||
private function matchCurrencies(): void
|
||||
{
|
||||
$journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', 'transactions.transaction_journal_id')
|
||||
->where('transactions.transaction_currency_id', '!=', \DB::raw('transaction_journals.transaction_currency_id'))
|
||||
->get(['transaction_journals.*'])
|
||||
;
|
||||
if (0 === $journals->count()) {
|
||||
$this->friendlyPositive('Journal currency integrity is OK');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Fixed %d journal(s) with mismatched currencies.', $journals->count()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -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') ?? [];
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances;
|
||||
namespace FireflyIII\JsonApi\V2\AccountBalances;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances;
|
||||
namespace FireflyIII\JsonApi\V2\AccountBalances;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances;
|
||||
namespace FireflyIII\JsonApi\V2\AccountBalances;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use LaravelJsonApi\Core\Schema\Schema;
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances\Capabilities;
|
||||
namespace FireflyIII\JsonApi\V2\AccountBalances\Capabilities;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use FireflyIII\Models\Account;
|
||||
60
app/JsonApi/V2/Accounts/AccountCollectionQuery.php
Normal file
60
app/JsonApi/V2/Accounts/AccountCollectionQuery.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Rules\IsAllowedGroupAction;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
class AccountCollectionQuery extends ResourceQuery
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request query parameters.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::fieldSets(),
|
||||
],
|
||||
'user_group_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
new IsAllowedGroupAction(Account::class, request()->method()),
|
||||
],
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::filter(),
|
||||
],
|
||||
'include' => [
|
||||
'nullable',
|
||||
'string',
|
||||
JsonApiRule::includePaths(),
|
||||
],
|
||||
'page' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::page(),
|
||||
],
|
||||
'sort' => [
|
||||
'nullable',
|
||||
'string',
|
||||
JsonApiRule::sort(),
|
||||
],
|
||||
'withCount' => [
|
||||
'nullable',
|
||||
'string',
|
||||
JsonApiRule::countable(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
98
app/JsonApi/V2/Accounts/AccountRepository.php
Normal file
98
app/JsonApi/V2/Accounts/AccountRepository.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?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\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
use LaravelJsonApi\NonEloquent\AbstractRepository;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\CrudRelations;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\HasCrudCapability;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\HasRelationsCapability;
|
||||
|
||||
/**
|
||||
* Class AccountRepository
|
||||
*
|
||||
* The repository collects a single or many (account) objects from the database and returns them to the
|
||||
* account resource. The account resource links all account properties to the JSON properties.
|
||||
*
|
||||
* For the queryAll thing, a separate query is constructed that does the actual querying of the database.
|
||||
* This is necessary because the user can't just query all accounts (it would return other user's data)
|
||||
* and because we also need to collect all kinds of metadata, like the currency and user info.
|
||||
*/
|
||||
class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
{
|
||||
use HasCrudCapability;
|
||||
use HasRelationsCapability;
|
||||
use UsergroupAware;
|
||||
|
||||
/**
|
||||
* SiteRepository constructor.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
public function exists(string $resourceId): bool
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return null !== Account::find((int) $resourceId);
|
||||
}
|
||||
|
||||
public function find(string $resourceId): ?object
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// throw new \RuntimeException('trace me');
|
||||
$account = Account::find((int) $resourceId);
|
||||
if (null === $account) {
|
||||
return null;
|
||||
}
|
||||
// enrich the collected data
|
||||
$enrichment = new AccountEnrichment();
|
||||
|
||||
return $enrichment->enrichSingle($account);
|
||||
}
|
||||
|
||||
public function queryAll(): Capabilities\AccountQuery
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return Capabilities\AccountQuery::make()
|
||||
->withUserGroup($this->userGroup)
|
||||
->withServer($this->server)
|
||||
->withSchema($this->schema)
|
||||
;
|
||||
}
|
||||
|
||||
protected function crud(): Capabilities\CrudAccount
|
||||
{
|
||||
return Capabilities\CrudAccount::make();
|
||||
}
|
||||
|
||||
protected function relations(): CrudRelations
|
||||
{
|
||||
return Capabilities\CrudAccountRelations::make();
|
||||
}
|
||||
}
|
||||
74
app/JsonApi/V2/Accounts/AccountResource.php
Normal file
74
app/JsonApi/V2/Accounts/AccountResource.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
/**
|
||||
* @property Account $resource
|
||||
*/
|
||||
class AccountResource extends JsonApiResource
|
||||
{
|
||||
/**
|
||||
* Get the resource id.
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return (string) $this->resource->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's attributes.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
'created_at' => $this->resource->created_at,
|
||||
'updated_at' => $this->resource->updated_at,
|
||||
'name' => $this->resource->name,
|
||||
'active' => $this->resource->active,
|
||||
'order' => $this->resource->order,
|
||||
'type' => $this->resource->account_type_string,
|
||||
'account_role' => $this->resource->account_role,
|
||||
'account_number' => '' === $this->resource->account_number ? null : $this->resource->account_number,
|
||||
|
||||
// currency
|
||||
'currency_id' => $this->resource->currency_id,
|
||||
'currency_name' => $this->resource->currency_name,
|
||||
'currency_code' => $this->resource->currency_code,
|
||||
'currency_symbol' => $this->resource->currency_symbol,
|
||||
'currency_decimal_places' => $this->resource->currency_decimal_places,
|
||||
|
||||
// liability things
|
||||
'liability_direction' => $this->resource->liability_direction,
|
||||
'interest' => $this->resource->interest,
|
||||
'interest_period' => $this->resource->interest_period,
|
||||
'current_debt' => $this->resource->current_debt,
|
||||
|
||||
'last_activity' => $this->resource->last_activity,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's relationships.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function relationships($request): iterable
|
||||
{
|
||||
return [
|
||||
$this->relation('user')->withData($this->resource->user),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts;
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -10,8 +10,13 @@ use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
/**
|
||||
* @property Account $resource
|
||||
*
|
||||
* This class collects the resources attributes, the account in this case.
|
||||
* Generally speaking, each property here is directly related to a property on the account object itself.
|
||||
* However, many properties are collected from other sources, like the user or the currency.
|
||||
* As a result, the account repository is where it's at, which is where the collection takes place and is optimised.
|
||||
*/
|
||||
class AccountResource extends JsonApiResource
|
||||
class AccountResourceOld extends JsonApiResource
|
||||
{
|
||||
/**
|
||||
* Get the resource's attributes.
|
||||
@@ -20,32 +25,23 @@ class AccountResource extends JsonApiResource
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
// fields removed here have been migrated.
|
||||
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,
|
||||
'created_at' => $this->resource->created_at,
|
||||
'updated_at' => $this->resource->updated_at,
|
||||
'name' => $this->resource->name,
|
||||
|
||||
// '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,
|
||||
@@ -65,11 +61,6 @@ class AccountResource extends JsonApiResource
|
||||
|
||||
// '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,
|
||||
@@ -86,15 +77,9 @@ class AccountResource extends JsonApiResource
|
||||
// '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,
|
||||
@@ -123,7 +108,8 @@ class AccountResource extends JsonApiResource
|
||||
{
|
||||
return [
|
||||
$this->relation('user')->withData($this->resource->user),
|
||||
$this->relation('account_balances')->withData($this->resource->balances),
|
||||
$this->relation('currency')->withData($this->resource->transactionCurrency),
|
||||
// $this->relation('account_balances')->withData($this->resource->balances),
|
||||
];
|
||||
}
|
||||
}
|
||||
88
app/JsonApi/V2/Accounts/AccountSchema.php
Normal file
88
app/JsonApi/V2/Accounts/AccountSchema.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Schema\Schema;
|
||||
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
|
||||
use LaravelJsonApi\NonEloquent\Fields\Attribute;
|
||||
use LaravelJsonApi\NonEloquent\Fields\ID;
|
||||
use LaravelJsonApi\NonEloquent\Filters\Filter;
|
||||
|
||||
class AccountSchema extends Schema
|
||||
{
|
||||
use UsergroupAware;
|
||||
|
||||
/**
|
||||
* The model the schema corresponds to.
|
||||
*/
|
||||
public static string $model = Account::class;
|
||||
|
||||
/**
|
||||
* Get the resource fields.
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
ID::make(),
|
||||
Attribute::make('created_at'),
|
||||
Attribute::make('updated_at'),
|
||||
|
||||
// basic info and meta data
|
||||
Attribute::make('name'),
|
||||
Attribute::make('active'),
|
||||
Attribute::make('order'),
|
||||
Attribute::make('type'),
|
||||
Attribute::make('account_role'),
|
||||
Attribute::make('account_number'),
|
||||
|
||||
// currency
|
||||
Attribute::make('currency_id'),
|
||||
Attribute::make('currency_name'),
|
||||
Attribute::make('currency_code'),
|
||||
Attribute::make('currency_symbol'),
|
||||
Attribute::make('currency_decimal_places'),
|
||||
|
||||
// liability things
|
||||
Attribute::make('liability_direction'),
|
||||
Attribute::make('interest'),
|
||||
Attribute::make('interest_period'),
|
||||
Attribute::make('current_debt'),
|
||||
|
||||
// dynamic data
|
||||
Attribute::make('last_activity'),
|
||||
|
||||
HasOne::make('user')->readOnly(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource filters.
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
Filter::make('id'),
|
||||
];
|
||||
}
|
||||
|
||||
public function repository(): AccountRepository
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$this->setUserGroup($this->server->getUsergroup());
|
||||
|
||||
return AccountRepository::make()
|
||||
->withServer($this->server)
|
||||
->withSchema($this)
|
||||
->withUserGroup($this->userGroup)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts;
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use LaravelJsonApi\Eloquent\Contracts\Paginator;
|
||||
@@ -17,7 +17,14 @@ use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
|
||||
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
|
||||
use LaravelJsonApi\Eloquent\Schema;
|
||||
|
||||
class AccountSchema extends Schema
|
||||
/**
|
||||
* Class AccountSchema
|
||||
*
|
||||
* This is the schema of all fields that an account exposes to the world.
|
||||
* Fields do not have to have a relation to the actual model.
|
||||
* Fields mentioned here still need to be filled in by the AccountResource.
|
||||
*/
|
||||
class AccountSchemaOld extends Schema
|
||||
{
|
||||
/**
|
||||
* The model the schema corresponds to.
|
||||
@@ -34,18 +41,19 @@ class AccountSchema extends Schema
|
||||
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'),
|
||||
// Str::make('account_type'),
|
||||
// Str::make('virtual_balance'),
|
||||
// Str::make('iban'),
|
||||
// Boolean::make('active'),
|
||||
// Number::make('order'),
|
||||
HasOne::make('user')->readOnly(),
|
||||
// HasMany::make('account_balances'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource filters.
|
||||
* Filters mentioned here can be used to filter the results.
|
||||
* TODO write down exactly how this works.
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
45
app/JsonApi/V2/Accounts/AccountSingleQuery.php
Normal file
45
app/JsonApi/V2/Accounts/AccountSingleQuery.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2\Accounts;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\ResourceQuery;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
class AccountSingleQuery extends ResourceQuery
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request query parameters.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
'fields' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::fieldSets(),
|
||||
],
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'array',
|
||||
JsonApiRule::filter()->forget('id'),
|
||||
],
|
||||
'include' => [
|
||||
'nullable',
|
||||
'string',
|
||||
JsonApiRule::includePaths(),
|
||||
],
|
||||
'page' => JsonApiRule::notSupported(),
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
'withCount' => [
|
||||
'nullable',
|
||||
'string',
|
||||
JsonApiRule::countable(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts\Capabilities;
|
||||
namespace FireflyIII\JsonApi\V2\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
@@ -29,6 +29,7 @@ use FireflyIII\Support\JsonApi\ExpandsQuery;
|
||||
use FireflyIII\Support\JsonApi\FiltersPagination;
|
||||
use FireflyIII\Support\JsonApi\SortsCollection;
|
||||
use FireflyIII\Support\JsonApi\ValidateSortParameters;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Contracts\Store\HasPagination;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
|
||||
@@ -43,31 +44,45 @@ class AccountQuery extends QueryAll implements HasPagination
|
||||
use ValidateSortParameters;
|
||||
|
||||
#[\Override]
|
||||
/**
|
||||
* This method returns all accounts, given a bunch of filters and sort fields, together with pagination.
|
||||
*/
|
||||
public function get(): iterable
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// collect filters
|
||||
$filters = $this->queryParameters->filter();
|
||||
// collect sort options
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
// collect pagination based on the page
|
||||
$pagination = $this->filtersPagination($this->queryParameters->page());
|
||||
$needsAll = $this->validateParams('account', $sort);
|
||||
// check if we need all accounts, regardless of pagination
|
||||
// This is necessary when the user wants to sort on specific params.
|
||||
$needsAll = $this->needsFullDataset('account', $sort);
|
||||
|
||||
// start the query
|
||||
$query = $this->userGroup->accounts();
|
||||
|
||||
// add pagination to the query, limiting the results.
|
||||
if (!$needsAll) {
|
||||
$query = $this->addPagination($query, $pagination);
|
||||
}
|
||||
|
||||
// add sort and filter parameters to the query.
|
||||
$query = $this->addSortParams($query, $sort);
|
||||
$query = $this->addFilterParams('account', $query, $filters);
|
||||
|
||||
// collect the result.
|
||||
$collection = $query->get(['accounts.*']);
|
||||
|
||||
// enrich data
|
||||
// enrich the collected data
|
||||
$enrichment = new AccountEnrichment();
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
// add filters after the query
|
||||
// TODO add filters after the query, if there are filters that cannot be applied to the database but only
|
||||
// to the enriched results.
|
||||
|
||||
// add sort after the query
|
||||
// sort the data after the query, and return it right away.
|
||||
return $this->sortCollection($collection, $sort);
|
||||
// var_dump($filters->value('name'));
|
||||
// exit;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountRepository.php
|
||||
* CrudAccount.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
@@ -21,33 +21,22 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts;
|
||||
namespace FireflyIII\JsonApi\V2\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
use LaravelJsonApi\NonEloquent\AbstractRepository;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\CrudResource;
|
||||
|
||||
class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
class CrudAccount extends CrudResource
|
||||
{
|
||||
use UsergroupAware;
|
||||
|
||||
/**
|
||||
* SiteRepository constructor.
|
||||
* Read the supplied site.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
public function find(string $resourceId): ?object
|
||||
public function read(Account $account): ?Account
|
||||
{
|
||||
return Account::find((int) $resourceId);
|
||||
}
|
||||
// enrich the collected data
|
||||
$enrichment = new AccountEnrichment();
|
||||
|
||||
public function queryAll(): Capabilities\AccountQuery
|
||||
{
|
||||
return Capabilities\AccountQuery::make()
|
||||
->withUserGroup($this->userGroup)
|
||||
->withServer($this->server)
|
||||
->withSchema($this->schema)
|
||||
;
|
||||
return $enrichment->enrichSingle($account);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/*
|
||||
* CrudAccountRelations.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\V2\Accounts\Capabilities;
|
||||
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\CrudRelations;
|
||||
|
||||
class CrudAccountRelations extends CrudRelations {}
|
||||
53
app/JsonApi/V2/Server.php
Normal file
53
app/JsonApi/V2/Server.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V2;
|
||||
|
||||
use FireflyIII\JsonApi\V2\Accounts\AccountSchema;
|
||||
use FireflyIII\JsonApi\V2\Users\UserSchema;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UserGroupDetectable;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Server\Server as BaseServer;
|
||||
|
||||
/**
|
||||
* Class Server
|
||||
*
|
||||
* This class serves as a generic class for the v2 API "server".
|
||||
*/
|
||||
class Server extends BaseServer
|
||||
{
|
||||
use UsergroupAware;
|
||||
use UserGroupDetectable;
|
||||
|
||||
/**
|
||||
* The base URI namespace for this server.
|
||||
*/
|
||||
protected string $baseUri = '/api/v2';
|
||||
|
||||
/**
|
||||
* Bootstrap the server when it is handling an HTTP request.
|
||||
*/
|
||||
public function serving(): void
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
// at this point the user may not actually have access to this user group.
|
||||
$res = $this->detectUserGroup();
|
||||
$this->setUserGroup($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server's list of schemas.
|
||||
*/
|
||||
protected function allSchemas(): array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
|
||||
return [
|
||||
AccountSchema::class,
|
||||
UserSchema::class,
|
||||
// AccountBalanceSchema::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Users;
|
||||
namespace FireflyIII\JsonApi\V2\Users;
|
||||
|
||||
use FireflyIII\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Users;
|
||||
namespace FireflyIII\JsonApi\V2\Users;
|
||||
|
||||
use FireflyIII\User;
|
||||
use LaravelJsonApi\Eloquent\Contracts\Paginator;
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -50,6 +51,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property null|Carbon $deleted_at
|
||||
* @property int $user_id
|
||||
* @property int $account_type_id
|
||||
* @property string $account_type_string
|
||||
* @property string $name
|
||||
* @property string $virtual_balance
|
||||
* @property null|string $iban
|
||||
@@ -74,6 +76,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
* @property Collection|Transaction[] $transactions
|
||||
* @property null|int $transactions_count
|
||||
* @property User $user
|
||||
* @property string $last_activity
|
||||
*
|
||||
* @method static EloquentBuilder|Account accountTypeIn($types)
|
||||
* @method static EloquentBuilder|Account newModelQuery()
|
||||
@@ -116,6 +119,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
*/
|
||||
class Account extends Model
|
||||
{
|
||||
use Cachable;
|
||||
use HasFactory;
|
||||
use ReturnsIntegerIdTrait;
|
||||
use ReturnsIntegerUserIdTrait;
|
||||
@@ -144,7 +148,7 @@ class Account extends Model
|
||||
public static function routeBinder(string $value): self
|
||||
{
|
||||
if (auth()->check()) {
|
||||
$accountId = (int)$value;
|
||||
$accountId = (int) $value;
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
@@ -246,7 +250,7 @@ class Account extends Model
|
||||
|
||||
public function setVirtualBalanceAttribute(mixed $value): void
|
||||
{
|
||||
$value = (string)$value;
|
||||
$value = (string) $value;
|
||||
if ('' === $value) {
|
||||
$value = null;
|
||||
}
|
||||
@@ -266,7 +270,7 @@ class Account extends Model
|
||||
protected function accountId(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int)$value,
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,21 +280,21 @@ class Account extends Model
|
||||
protected function accountTypeId(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int)$value,
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
|
||||
protected function iban(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string)$value)),
|
||||
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string) $value)),
|
||||
);
|
||||
}
|
||||
|
||||
protected function order(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int)$value,
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +304,7 @@ class Account extends Model
|
||||
protected function virtualBalance(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (string)$value,
|
||||
get: static fn ($value) => (string) $value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Models;
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
@@ -93,6 +94,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
*/
|
||||
class Transaction extends Model
|
||||
{
|
||||
use Cachable;
|
||||
use HasFactory;
|
||||
use ReturnsIntegerIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
@@ -554,6 +554,12 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
// reset the rest to zero.
|
||||
$all = [AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE];
|
||||
$this->user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereNotIn('account_types.type', $all)
|
||||
->update(['order' => 0])
|
||||
;
|
||||
}
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -241,6 +241,12 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
// reset the rest to zero.
|
||||
$all = [AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE];
|
||||
$this->user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereNotIn('account_types.type', $all)
|
||||
->update(['order' => 0])
|
||||
;
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
|
||||
|
||||
130
app/Rules/IsAllowedGroupAction.php
Normal file
130
app/Rules/IsAllowedGroupAction.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/*
|
||||
* IsAllowedGroupAction.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 FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class IsAllowedGroupAction implements ValidationRule
|
||||
{
|
||||
private string $className;
|
||||
private string $methodName;
|
||||
|
||||
private array $acceptedRoles;
|
||||
private UserGroupRepositoryInterface $repository;
|
||||
|
||||
public function __construct(string $className, string $methodName)
|
||||
{
|
||||
$this->className = $className;
|
||||
$this->methodName = $methodName;
|
||||
// you need these roles to do anything with any endpoint.
|
||||
$this->acceptedRoles = [UserRoleEnum::OWNER, UserRoleEnum::FULL];
|
||||
$this->repository = app(UserGroupRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
#[\Override]
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
if ('GET' === $this->methodName) {
|
||||
// need at least "read only rights".
|
||||
$this->acceptedRoles[] = UserRoleEnum::READ_ONLY;
|
||||
}
|
||||
if ('GET' !== $this->methodName) {
|
||||
// either post, put or delete or something else.. you need more access rights.
|
||||
switch ($this->className) {
|
||||
default:
|
||||
throw new AuthorizationException(sprintf('Cannot handle class "%s"', $this->className));
|
||||
|
||||
case Account::class:
|
||||
$this->acceptedRoles[] = UserRoleEnum::MANAGE_TRANSACTIONS;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->validateUserGroup((int)$value, $fail);
|
||||
}
|
||||
|
||||
private function validateUserGroup(int $userGroupId, \Closure $fail): void
|
||||
{
|
||||
Log::debug(sprintf('validateUserGroup: %s', static::class));
|
||||
if (!auth()->check()) {
|
||||
Log::debug('validateUserGroup: user is not logged in, return NULL.');
|
||||
$fail('validation.no_auth_user_group')->translate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
if (0 !== $userGroupId) {
|
||||
Log::debug(sprintf('validateUserGroup: user group submitted, search for memberships in group #%d.', $userGroupId));
|
||||
}
|
||||
if (0 === $userGroupId) {
|
||||
$userGroupId = $user->user_group_id;
|
||||
Log::debug(sprintf('validateUserGroup: no user group submitted, use default group #%d.', $userGroupId));
|
||||
}
|
||||
|
||||
$this->repository->setUser($user);
|
||||
$memberships = $this->repository->getMembershipsFromGroupId($userGroupId);
|
||||
|
||||
if (0 === $memberships->count()) {
|
||||
Log::debug(sprintf('validateUserGroup: user has no access to group #%d.', $userGroupId));
|
||||
$fail('validation.no_access_user_group')->translate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// need to get the group from the membership:
|
||||
$userGroup = $this->repository->getById($userGroupId);
|
||||
if (null === $userGroup) {
|
||||
Log::debug(sprintf('validateUserGroup: group #%d does not exist.', $userGroupId));
|
||||
$fail('validation.belongs_user_or_user_group')->translate();
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $userGroupId, $userGroup->title));
|
||||
Log::debug(sprintf('validateUserGroup: have %d roles to check.', count($this->acceptedRoles)), $this->acceptedRoles);
|
||||
|
||||
/** @var UserRoleEnum $role */
|
||||
foreach ($this->acceptedRoles as $role) {
|
||||
if ($user->hasRoleInGroupOrOwner($userGroup, $role)) {
|
||||
Log::debug(sprintf('validateUserGroup: User has role "%s" in group #%d, return.', $role->value, $userGroupId));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('validateUserGroup: User does NOT have role "%s" in group #%d, continue searching.', $role->value, $userGroupId));
|
||||
}
|
||||
|
||||
Log::debug('validateUserGroup: User does NOT have enough rights to access endpoint.');
|
||||
$fail('validation.belongs_user_or_user_group')->translate();
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
62
app/Support/JsonApi/Concerns/UserGroupDetectable.php
Normal file
62
app/Support/JsonApi/Concerns/UserGroupDetectable.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/*
|
||||
* UserGroupDetectable.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\JsonApi\Concerns;
|
||||
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\User;
|
||||
|
||||
trait UserGroupDetectable
|
||||
{
|
||||
/**
|
||||
* Return the user group or NULL if none is set.
|
||||
* Will throw exception if invalid.
|
||||
* TODO Duplicate from API v2 code.
|
||||
*/
|
||||
public function detectUserGroup(): ?UserGroup
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
app('log')->debug('Now in detectUserGroup()');
|
||||
|
||||
/** @var null|UserGroup $userGroup */
|
||||
$userGroup = request()->route()?->parameter('userGroup');
|
||||
if (null === $userGroup) {
|
||||
app('log')->debug('Request class has no userGroup parameter, but perhaps there is a parameter.');
|
||||
$userGroupId = (int)request()->get('user_group_id');
|
||||
if (0 === $userGroupId) {
|
||||
app('log')->debug(sprintf('Request class has no user_group_id parameter, grab default from user (group #%d).', $user->user_group_id));
|
||||
$userGroupId = (int)$user->user_group_id;
|
||||
}
|
||||
$userGroup = UserGroup::find($userGroupId);
|
||||
if (null === $userGroup) {
|
||||
app('log')->error(sprintf('Request class has user_group_id (#%d), but group does not exist.', $userGroupId));
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug('Request class has valid user_group_id.');
|
||||
}
|
||||
|
||||
return $userGroup;
|
||||
}
|
||||
}
|
||||
@@ -29,35 +29,56 @@ use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountEnrichment
|
||||
*
|
||||
* This class "enriches" accounts and adds data from other tables and models to each account model.
|
||||
*/
|
||||
class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private array $currencies;
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
private CurrencyRepositoryInterface $currencyRepository;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
/**
|
||||
* Do the actual enrichment.
|
||||
*/
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
Log::debug(sprintf('Now doing account enrichment for %d account(s)', $collection->count()));
|
||||
// prep local fields
|
||||
$this->collection = $collection;
|
||||
$this->currencies = [];
|
||||
|
||||
// do everything here:
|
||||
$this->getLastActivity();
|
||||
// $this->getMetaBalances();
|
||||
$this->collectAccountTypes();
|
||||
$this->collectMetaData();
|
||||
// $this->getMetaBalances();
|
||||
|
||||
$this->collection->transform(function (Account $account) {
|
||||
$account->user_array = ['id' => 1, 'bla bla' => 'bla'];
|
||||
$account->balances = collect([
|
||||
['balance_id' => 1, 'balance' => 5],
|
||||
['balance_id' => 2, 'balance' => 5],
|
||||
['balance_id' => 3, 'balance' => 5],
|
||||
]);
|
||||
|
||||
return $account;
|
||||
});
|
||||
// $this->collection->transform(function (Account $account) {
|
||||
// $account->user_array = ['id' => 1, 'bla bla' => 'bla'];
|
||||
// $account->balances = collect([
|
||||
// ['balance_id' => 1, 'balance' => 5],
|
||||
// ['balance_id' => 2, 'balance' => 5],
|
||||
// ['balance_id' => 3, 'balance' => 5],
|
||||
// ]);
|
||||
//
|
||||
// return $account;
|
||||
// });
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
@@ -67,9 +88,7 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
*/
|
||||
private function getLastActivity(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$lastActivity = $accountRepository->getLastActivity($this->collection);
|
||||
$lastActivity = $this->repository->getLastActivity($this->collection);
|
||||
foreach ($lastActivity as $row) {
|
||||
$this->collection->where('id', $row['account_id'])->first()->last_activity = Carbon::parse($row['date_max'], config('app.timezone'));
|
||||
}
|
||||
@@ -98,17 +117,15 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
*/
|
||||
private function collectAccountTypes(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accountTypes = $accountRepository->getAccountTypes($this->collection);
|
||||
$types = [];
|
||||
$accountTypes = $this->repository->getAccountTypes($this->collection);
|
||||
$types = [];
|
||||
|
||||
/** @var AccountType $row */
|
||||
foreach ($accountTypes as $row) {
|
||||
$types[$row->id] = $row->type;
|
||||
}
|
||||
$this->collection->transform(function (Account $account) use ($types) {
|
||||
$account->type = $types[$account->id];
|
||||
$account->account_type_string = $types[$account->id];
|
||||
|
||||
return $account;
|
||||
});
|
||||
@@ -116,17 +133,11 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectMetaData(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$metaFields = $this->repository->getMetaValues($this->collection, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
|
||||
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
|
||||
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
$metaFields = $accountRepository->getMetaValues($this->collection, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
|
||||
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
|
||||
|
||||
$currencies = [];
|
||||
foreach ($repository->getByIds($currencyIds) as $currency) {
|
||||
$currencies = [];
|
||||
foreach ($this->currencyRepository->getByIds($currencyIds) as $currency) {
|
||||
$id = $currency->id;
|
||||
$currencies[$id] = $currency;
|
||||
}
|
||||
@@ -137,6 +148,7 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$account->{$entry->name} = $entry->data;
|
||||
if ('currency_id' === $entry->name) {
|
||||
$id = (int) $entry->data;
|
||||
$account->currency_name = $currencies[$id]?->name;
|
||||
$account->currency_code = $currencies[$id]?->code;
|
||||
$account->currency_symbol = $currencies[$id]?->symbol;
|
||||
$account->currency_decimal_places = $currencies[$id]?->decimal_places;
|
||||
@@ -146,4 +158,14 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
return $account;
|
||||
});
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function enrichSingle(Model $model): Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface EnrichmentInterface
|
||||
{
|
||||
public function enrich(Collection $collection): Collection;
|
||||
|
||||
public function enrichSingle(Model $model): Model;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
trait ValidateSortParameters
|
||||
{
|
||||
public function validateParams(string $class, ?SortFields $params): bool
|
||||
public function needsFullDataset(string $class, ?SortFields $params): bool
|
||||
{
|
||||
if (null === $params) {
|
||||
return false;
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\Models;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\Transaction;
|
||||
@@ -120,15 +121,35 @@ class AccountBalanceCalculator
|
||||
$sumAmount = '' === $sumAmount ? '0' : $sumAmount;
|
||||
$sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount;
|
||||
|
||||
// at this point SQLite may return scientific notation because why not. Terrible.
|
||||
$sumAmount = app('steam')->floatalize($sumAmount);
|
||||
$sumForeignAmount = app('steam')->floatalize($sumForeignAmount);
|
||||
|
||||
// first create for normal currency:
|
||||
$entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumAmount);
|
||||
|
||||
try {
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumAmount);
|
||||
} catch (\ValueError $e) {
|
||||
$message = sprintf('[a] Could not add "%s" to "%s": %s', $entry->balance, $sumAmount, $e->getMessage());
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message, 0, $e);
|
||||
}
|
||||
$entry->save();
|
||||
|
||||
// then do foreign amount, if present:
|
||||
if ($foreignCurrency > 0) {
|
||||
$entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
|
||||
$entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
|
||||
|
||||
try {
|
||||
$entry->balance = bcadd((string) $entry->balance, $sumForeignAmount);
|
||||
} catch (\ValueError $e) {
|
||||
$message = sprintf('[b] Could not add "%s" to "%s": %s', $entry->balance, $sumForeignAmount, $e->getMessage());
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message, 0, $e);
|
||||
}
|
||||
$entry->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +750,8 @@ class Navigation
|
||||
$function = $functionMap[$range];
|
||||
$end->{$function}(); // @phpstan-ignore-line
|
||||
|
||||
Log::debug(sprintf('updateEndDate returns "%s"', $end->format('Y-m-d')));
|
||||
|
||||
return $end;
|
||||
}
|
||||
if ('6M' === $range) {
|
||||
@@ -806,6 +808,7 @@ class Navigation
|
||||
if (array_key_exists($range, $functionMap)) {
|
||||
$function = $functionMap[$range];
|
||||
$start->{$function}(); // @phpstan-ignore-line
|
||||
Log::debug(sprintf('updateStartDate returns "%s"', $start->format('Y-m-d')));
|
||||
|
||||
return $start;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ trait ChecksLogin
|
||||
/**
|
||||
* Return the user group or NULL if none is set.
|
||||
* Will throw exception if invalid.
|
||||
* TODO duplicated in JSONAPI code.
|
||||
*/
|
||||
public function getUserGroup(): ?UserGroup
|
||||
{
|
||||
|
||||
@@ -836,6 +836,7 @@ class Steam
|
||||
if (!str_contains($value, 'E')) {
|
||||
return $value;
|
||||
}
|
||||
Log::debug(sprintf('Floatalizing %s', $value));
|
||||
|
||||
$number = substr($value, 0, (int)strpos($value, 'E'));
|
||||
if (str_contains($number, '.')) {
|
||||
|
||||
@@ -128,7 +128,8 @@ class RecurrenceTransformer extends AbstractTransformer
|
||||
];
|
||||
|
||||
// get the (future) occurrences for this specific type of repetition:
|
||||
$occurrences = $this->repository->getXOccurrencesSince($repetition, $fromDate, new Carbon(), 5);
|
||||
$amount = 'daily' === $repetition->repetition_type ? 9 : 5;
|
||||
$occurrences = $this->repository->getXOccurrencesSince($repetition, $fromDate, now(), $amount);
|
||||
|
||||
/** @var Carbon $carbon */
|
||||
foreach ($occurrences as $carbon) {
|
||||
|
||||
23
changelog.md
23
changelog.md
@@ -3,6 +3,29 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 6.1.19 - 2024-07-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 8844](https://github.com/firefly-iii/firefly-iii/issues/8844) (Split recurring transaction gets wrong (split) titles) reported by @dreautall
|
||||
- [Issue 8981](https://github.com/firefly-iii/firefly-iii/issues/8981) (bcadd() error during Docker container startup) reported by @NoiTheCat
|
||||
- [Issue 8986](https://github.com/firefly-iii/firefly-iii/issues/8986) (Search with "internal_reference_is" finds all transactions with full word of search string) reported by @baflo
|
||||
- [Issue 9009](https://github.com/firefly-iii/firefly-iii/issues/9009) (Incorrect Amount Calculation in Reconciliation for Bank Account A) reported by @realzsan3
|
||||
- [Issue 9021](https://github.com/firefly-iii/firefly-iii/issues/9021) (Incorrect "Expected Withdrawals" for Daily Recurring Transactions) reported by @xMarcii
|
||||
- [Issue 9022](https://github.com/firefly-iii/firefly-iii/issues/9022) (Calendar Not Showing Green Fields for Recurring Transactions) reported by @xMarcii
|
||||
- Improved currency exchange rate downloader
|
||||
|
||||
## 6.1.18 - 2024-06-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 8978](https://github.com/firefly-iii/firefly-iii/issues/8978) (Error! Internal Firefly III Exception: bcadd(): Argument #2 ($num2) is not well-formed) reported by @el-rhazi
|
||||
- [Issue 8977](https://github.com/firefly-iii/firefly-iii/issues/8977) (Data Importer: "500 Server Error" with Firefly III v6.1.17) reported by @qtdzz
|
||||
|
||||
### Security
|
||||
|
||||
- [CVE-2024-37893](https://www.cve.org/CVERecord?id=CVE-2024-37893)
|
||||
|
||||
## 6.1.17 - 2024-06-16
|
||||
|
||||
### Added
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"bacon/bacon-qr-code": "2.*",
|
||||
"diglactic/laravel-breadcrumbs": "^9",
|
||||
"gdbots/query-parser": "^3.0",
|
||||
"genealabs/laravel-model-caching": "^11.0",
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"jc5/google2fa-laravel": "^2.0",
|
||||
"jc5/recovery": "^2",
|
||||
@@ -107,8 +108,8 @@
|
||||
"spatie/laravel-ignition": "^2",
|
||||
"spatie/period": "^2.4",
|
||||
"symfony/expression-language": "^7.0",
|
||||
"symfony/http-client": "^7.0",
|
||||
"symfony/mailgun-mailer": "^7.0"
|
||||
"symfony/http-client": "^7.1",
|
||||
"symfony/mailgun-mailer": "^7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.9",
|
||||
|
||||
912
composer.lock
generated
912
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -225,7 +225,7 @@ return [
|
||||
'show_copy' => false, // Show copy button next to the query,
|
||||
'slow_threshold' => false, // Only track queries that last longer than this time in ms
|
||||
'memory_usage' => false, // Show queries memory usage
|
||||
'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
|
||||
'soft_limit' => 250, // After the soft limit, no parameters/backtrace are captured
|
||||
'hard_limit' => 500, // After the hard limit, queries are ignored
|
||||
],
|
||||
'mail' => [
|
||||
|
||||
@@ -114,10 +114,10 @@ return [
|
||||
'telemetry' => false,
|
||||
'webhooks' => true,
|
||||
'handle_debts' => true,
|
||||
'expression_engine' => false,
|
||||
'expression_engine' => true,
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2024-06-17',
|
||||
'version' => 'develop/2024-07-29',
|
||||
'api_version' => '2.1.0',
|
||||
'db_version' => 24,
|
||||
|
||||
@@ -503,13 +503,13 @@ return [
|
||||
'remove_tag' => RemoveTag::class,
|
||||
'remove_all_tags' => RemoveAllTags::class,
|
||||
'set_description' => SetDescription::class,
|
||||
'append_description' => AppendDescription::class,
|
||||
'prepend_description' => PrependDescription::class,
|
||||
// 'append_description' => AppendDescription::class,
|
||||
// 'prepend_description' => PrependDescription::class,
|
||||
'set_source_account' => SetSourceAccount::class,
|
||||
'set_destination_account' => SetDestinationAccount::class,
|
||||
'set_notes' => SetNotes::class,
|
||||
'append_notes' => AppendNotes::class,
|
||||
'prepend_notes' => PrependNotes::class,
|
||||
// 'append_notes' => AppendNotes::class,
|
||||
// 'prepend_notes' => PrependNotes::class,
|
||||
'clear_notes' => ClearNotes::class,
|
||||
'link_to_bill' => LinkToBill::class,
|
||||
'convert_withdrawal' => ConvertToWithdrawal::class,
|
||||
@@ -518,10 +518,10 @@ return [
|
||||
'switch_accounts' => SwitchAccounts::class,
|
||||
'update_piggy' => UpdatePiggybank::class,
|
||||
'delete_transaction' => DeleteTransaction::class,
|
||||
'append_descr_to_notes' => AppendDescriptionToNotes::class,
|
||||
'append_notes_to_descr' => AppendNotesToDescription::class,
|
||||
'move_descr_to_notes' => MoveDescriptionToNotes::class,
|
||||
'move_notes_to_descr' => MoveNotesToDescription::class,
|
||||
// 'append_descr_to_notes' => AppendDescriptionToNotes::class,
|
||||
// 'append_notes_to_descr' => AppendNotesToDescription::class,
|
||||
// 'move_descr_to_notes' => MoveDescriptionToNotes::class,
|
||||
// 'move_notes_to_descr' => MoveNotesToDescription::class,
|
||||
'set_source_to_cash' => SetSourceToCashAccount::class,
|
||||
'set_destination_to_cash' => SetDestinationToCashAccount::class,
|
||||
'set_amount' => SetAmount::class,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\JsonApi\V3\Server;
|
||||
use FireflyIII\JsonApi\V2\Server;
|
||||
|
||||
return [
|
||||
/*
|
||||
@@ -30,6 +30,6 @@ return [
|
||||
| class name of the server class.
|
||||
*/
|
||||
'servers' => [
|
||||
'v3' => Server::class,
|
||||
'v2' => Server::class,
|
||||
],
|
||||
];
|
||||
|
||||
10
config/laravel-model-caching.php
Normal file
10
config/laravel-model-caching.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'cache-prefix' => '',
|
||||
'enabled' => env('MODEL_CACHE_ENABLED', true),
|
||||
'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true),
|
||||
'store' => env('MODEL_CACHE_STORE'),
|
||||
];
|
||||
967
package-lock.json
generated
967
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@johmun/vue-tags-input": "^2",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"@vue/compiler-sfc": "^3.4.34",
|
||||
"axios": "^1.3",
|
||||
"bootstrap-sass": "^3",
|
||||
"cross-env": "^7.0",
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"laravel-vite-plugin": "^1.0.2",
|
||||
"laravel-vite-plugin": "^1.0.5",
|
||||
"patch-package": "^8.0.0",
|
||||
"sass": "^1.75.0",
|
||||
"sass": "^1.77.8",
|
||||
"vite": "^5",
|
||||
"vite-plugin-manifest-sri": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-grid-community/client-side-row-model": "^31.0.3",
|
||||
"@ag-grid-community/core": "^31.0.3",
|
||||
"@ag-grid-community/infinite-row-model": "^31.0.3",
|
||||
"@ag-grid-community/styles": "^31.0.3",
|
||||
"@ag-grid-community/client-side-row-model": "^32.0.2",
|
||||
"@ag-grid-community/core": "^32.0.2",
|
||||
"@ag-grid-community/infinite-row-model": "^32.0.2",
|
||||
"@ag-grid-community/styles": "^32.0.0",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"admin-lte": "^4.0.0-alpha3",
|
||||
|
||||
@@ -268,6 +268,7 @@ return [
|
||||
'auto_budget_period_mandatory' => 'The auto budget period is a mandatory field.',
|
||||
|
||||
// no access to administration:
|
||||
'no_auth_user_group' => 'You have to be logged in to access this administration.',
|
||||
'no_access_user_group' => 'You do not have the correct access rights for this administration.',
|
||||
'administration_owner_rename' => 'You can\'t rename your standard administration.',
|
||||
];
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<!-- {{ trans('firefly.bill_expected_date', {date: entry.next_expected_match_diff }) }} -->
|
||||
</td>
|
||||
<td class="expected_in_period hidden-sm hidden-xs">
|
||||
|
||||
{% for date in entry.pay_dates %}
|
||||
{{ formatDate(date, monthAndDayFormat) }}<br>
|
||||
{% endfor %}
|
||||
@@ -111,6 +112,7 @@
|
||||
#}
|
||||
{% if entry.paid_dates|length > 0 and entry.active %}
|
||||
<td class="paid_in_period text-success">
|
||||
|
||||
{% for currentPaid in entry.paid_dates %}
|
||||
<a href="{{ route('transactions.show',currentPaid.transaction_group_id) }}">
|
||||
{{ formatDate(currentPaid.date, monthAndDayFormat) }}
|
||||
@@ -120,7 +122,9 @@
|
||||
</td>
|
||||
<td class="expected_in_period hidden-sm hidden-xs">
|
||||
{% if entry.next_expected_match %}
|
||||
{{ formatDate(entry.next_expected_match, monthAndDayFormat) }}
|
||||
{{ formatDate(entry.next_expected_match, monthAndDayFormat) }}
|
||||
{% else %}
|
||||
<span class="text-muted">{{ entry.next_expected_match_diff }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -22,14 +22,23 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\Http\Controllers\Api\V3\Controllers\AccountController;
|
||||
use FireflyIII\Api\V2\Controllers\JsonApi\AccountController;
|
||||
use LaravelJsonApi\Laravel\Facades\JsonApiRoute;
|
||||
use LaravelJsonApi\Laravel\Http\Controllers\JsonApiController;
|
||||
use LaravelJsonApi\Laravel\Routing\ActionRegistrar;
|
||||
use LaravelJsonApi\Laravel\Routing\Relationships;
|
||||
use LaravelJsonApi\Laravel\Routing\ResourceRegistrar;
|
||||
|
||||
// V2 auto complete controller(s)
|
||||
/*
|
||||
*
|
||||
* ____ ____ ___ .______ ______ __ __ .___________. _______ _______.
|
||||
* \ \ / / |__ \ | _ \ / __ \ | | | | | || ____| / |
|
||||
* \ \/ / ) | | |_) | | | | | | | | | `---| |----`| |__ | (----`
|
||||
* \ / / / | / | | | | | | | | | | | __| \ \
|
||||
* \ / / /_ | |\ \----.| `--' | | `--' | | | | |____.----) |
|
||||
* \__/ |____| | _| `._____| \______/ \______/ |__| |_______|_______/
|
||||
*/
|
||||
|
||||
// AUTOCOMPLETE ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Autocomplete',
|
||||
@@ -37,7 +46,6 @@ Route::group(
|
||||
'as' => 'api.v2.autocomplete.',
|
||||
],
|
||||
static function (): void {
|
||||
// Auto complete routes
|
||||
Route::get('accounts', ['uses' => 'AccountController@accounts', 'as' => 'accounts']);
|
||||
Route::get('categories', ['uses' => 'CategoryController@categories', 'as' => 'categories']);
|
||||
Route::get('tags', ['uses' => 'TagController@tags', 'as' => 'tags']);
|
||||
@@ -45,7 +53,7 @@ Route::group(
|
||||
}
|
||||
);
|
||||
|
||||
// V2 API routes for charts
|
||||
// CHART ROUTES
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Chart',
|
||||
@@ -54,28 +62,12 @@ Route::group(
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
||||
// Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||
// Route::get('category/dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'category.dashboard']);
|
||||
Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||
Route::get('category/dashboard', ['uses' => 'CategoryController@dashboard', 'as' => 'category.dashboard']);
|
||||
Route::get('balance/balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
||||
}
|
||||
);
|
||||
|
||||
// JsonApiRoute::server('v3')
|
||||
// ->prefix('v3')
|
||||
// ->resources(function (ResourceRegistrar $server) {
|
||||
// $server->resource('accounts', AccountController::class)->readOnly()->relationships(function (Relationships $relations) {
|
||||
// $relations->hasOne('user')->readOnly();
|
||||
// //$relations->hasMany('account_balances')->readOnly();
|
||||
// })
|
||||
// ->actions(function (ActionRegistrar $actions) {
|
||||
// $actions->withId()->get('account-balances', 'readAccountBalances'); // non-eloquent pseudo relation
|
||||
// });
|
||||
// $server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations) {
|
||||
// $relations->hasMany('accounts')->readOnly();
|
||||
// });
|
||||
// $server->resource('account-balances', JsonApiController::class);
|
||||
// });
|
||||
|
||||
// V2 API route for Summary boxes
|
||||
// BASIC
|
||||
Route::group(
|
||||
@@ -121,19 +113,19 @@ Route::group(
|
||||
}
|
||||
);
|
||||
|
||||
// V2 API route for accounts.
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Account',
|
||||
'prefix' => 'v2/accounts',
|
||||
'as' => 'api.v2.accounts.',
|
||||
],
|
||||
static function (): void {
|
||||
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
Route::get('{account}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
Route::put('{account}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
}
|
||||
);
|
||||
// // V2 API route for accounts.
|
||||
// Route::group(
|
||||
// [
|
||||
// 'namespace' => 'FireflyIII\Api\V2\Controllers\Model\Account',
|
||||
// 'prefix' => 'v2/accounts',
|
||||
// 'as' => 'api.v2.accounts.',
|
||||
// ],
|
||||
// static function (): void {
|
||||
// Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||
// Route::get('{account}', ['uses' => 'ShowController@show', 'as' => 'show']);
|
||||
// Route::put('{account}', ['uses' => 'UpdateController@update', 'as' => 'update']);
|
||||
// }
|
||||
// );
|
||||
|
||||
// V2 API route for subscriptions.
|
||||
Route::group(
|
||||
@@ -247,7 +239,30 @@ Route::group(
|
||||
}
|
||||
);
|
||||
|
||||
// down here is v1
|
||||
// V2 JSON API ROUTES
|
||||
JsonApiRoute::server('v2')->prefix('v2')
|
||||
->resources(function (ResourceRegistrar $server): void {
|
||||
// ACCOUNTS
|
||||
$server->resource('accounts', AccountController::class)->readOnly()->relationships(function (Relationships $relations): void {
|
||||
$relations->hasOne('user')->readOnly();
|
||||
});
|
||||
// $server->resource('accounts', AccountController::class)->readOnly();
|
||||
|
||||
// USERS
|
||||
$server->resource('users', JsonApiController::class)->readOnly()->relationships(function (Relationships $relations): void {
|
||||
$relations->hasMany('accounts')->readOnly();
|
||||
});
|
||||
})
|
||||
;
|
||||
|
||||
/*
|
||||
* ____ ____ __ .______ ______ __ __ .___________. _______ _______.
|
||||
* \ \ / / /_ | | _ \ / __ \ | | | | | || ____| / |
|
||||
* \ \/ / | | | |_) | | | | | | | | | `---| |----`| |__ | (----`
|
||||
* \ / | | | / | | | | | | | | | | | __| \ \
|
||||
* \ / | | | |\ \----.| `--' | | `--' | | | | |____.----) |
|
||||
* \__/ |_| | _| `._____| \______/ \______/ |__| |_______|_______/
|
||||
*/
|
||||
|
||||
// Autocomplete controllers
|
||||
Route::group(
|
||||
@@ -278,7 +293,7 @@ Route::group(
|
||||
);
|
||||
|
||||
// CHART ROUTES.
|
||||
// Accounts
|
||||
// Chart accounts
|
||||
Route::group(
|
||||
[
|
||||
'namespace' => 'FireflyIII\Api\V1\Controllers\Chart',
|
||||
|
||||
Reference in New Issue
Block a user